summaryrefslogtreecommitdiffstats
path: root/apache2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-09-17 03:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-09-17 03:51:28 +0000
commit2b07c041cb218eca6e548bac9c4347f8a90c474c (patch)
tree679142f3916fa927903c6f245896f5c0325a3254 /apache2
parentInitial commit. (diff)
downloadlibapache2-mod-qos-upstream.tar.xz
libapache2-mod-qos-upstream.zip
Adding upstream version 11.74.upstream/11.74upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--apache2/Makefile.in10
-rw-r--r--apache2/config.m46
-rw-r--r--apache2/mod_qos.c14830
-rw-r--r--apache2/mod_qos.h79
4 files changed, 14925 insertions, 0 deletions
diff --git a/apache2/Makefile.in b/apache2/Makefile.in
new file mode 100644
index 0000000..d4733b2
--- /dev/null
+++ b/apache2/Makefile.in
@@ -0,0 +1,10 @@
+
+include $(top_srcdir)/build/special.mk
+
+# use the -lcrypto option when loading mod_qos as a DSO into
+# an Apache with dynamically loaded OpenSSL library (e.g. when not
+# using mod_ssl)
+SH_LDFLAGS += -lcrypto
+SH_LDFLAGS += -lpcre
+LDFLAGS += -lcrypto
+LDFLAGS += -lpcre \ No newline at end of file
diff --git a/apache2/config.m4 b/apache2/config.m4
new file mode 100644
index 0000000..a069da4
--- /dev/null
+++ b/apache2/config.m4
@@ -0,0 +1,6 @@
+
+APACHE_MODPATH_INIT(qos)
+
+APACHE_MODULE(qos, quality of service module, , , shared)
+
+APACHE_MODPATH_FINISH
diff --git a/apache2/mod_qos.c b/apache2/mod_qos.c
new file mode 100644
index 0000000..cef1cf9
--- /dev/null
+++ b/apache2/mod_qos.c
@@ -0,0 +1,14830 @@
+/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 2; -*-
+ */
+
+/**
+ * mod_qos.c: Quality of service module for Apache Web Server.
+ *
+ * The Apache Web Servers requires threads and processes to serve
+ * requests. Each TCP connection to the web server occupies one
+ * thread or process. Sometimes, a server gets too busy to serve
+ * every request due the lack of free processes or threads.
+ *
+ * This module implements control mechanisms that can provide
+ * different priority to different requests.
+ *
+ * mod_qos requires OpenSSL, PCRE, threading and shared memory
+ * support. It has been designed, developed and fully
+ * tested for Apache httpd MPM worker binaries and it is optimized
+ * to be used in a reverse proxy.
+ *
+ * See http://mod-qos.sourceforge.net/ for further
+ * details and to obtain the latest version of this module.
+ *
+ * Copyright (C) 2023 Pascal Buchbinder
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/************************************************************************
+ * Version
+ ***********************************************************************/
+static const char revision[] = "$Id: mod_qos.c 2706 2023-05-16 19:52:59Z pbuchbinder $";
+static const char g_revision[] = "11.74";
+
+/************************************************************************
+ * Includes
+ ***********************************************************************/
+/* std */
+#include <ctype.h>
+#include <time.h>
+
+#ifndef WIN32
+# include <arpa/inet.h>
+# include <unistd.h>
+#else
+# include <ws2tcpip.h>
+# include <windows.h>
+# include <direct.h>
+#endif
+
+#include <stdlib.h>
+
+// for socket options
+#ifdef __unix__
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+/* apache */
+#include <httpd.h>
+#include <http_core.h>
+#include <http_main.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_connection.h>
+#define CORE_PRIVATE
+#include <http_config.h>
+#include <http_log.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+#include <scoreboard.h>
+#include <ap_config.h>
+#include <ap_regex.h>
+#include <mpm_common.h>
+#include <util_md5.h>
+
+/* apr / scrlib */
+#include <apr_strings.h>
+#include <apr_file_info.h>
+#include <apr_base64.h>
+#include <apr_hooks.h>
+#include <apr_lib.h>
+#ifdef AP_NEED_SET_MUTEX_PERMS
+#include <unixd.h>
+#endif
+
+/* mod_qos requires OpenSSL */
+#include <openssl/rand.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+/* additional modules */
+#include "mod_status.h"
+
+/* Preprocessor Definitions
+ * this (optional header file allowing other modules to register to our hoos: */
+#ifdef QS_MOD_EXT_HOOKS
+#include "mod_qos.h"
+#endif
+
+/************************************************************************
+ * defines
+ ***********************************************************************/
+#define QOS_LOG_PFX(id) "mod_qos("#id"): "
+#define QOS_LOGD_PFX "mod_qos(): "
+#define QOS_LOG_MSGCT 200
+#define QOS_HASH_LEN 16
+#define QOS_MAX_AGE "3600"
+#define QOS_COOKIE_NAME "MODQOS"
+#define QOS_USER_TRACKING "mod_qos_user_id"
+#define QOS_USER_TRACKING_NEW "QOS_USER_ID_NEW"
+#define QOS_USER_TRACKING_RENEW "QOS_USER_ID_RENEW"
+#define QOS_DISABLE_UTC_ENFORCEMENT "DISABLE_UTC_ENFORCEMENT"
+#define QOS_MILESTONE "mod_qos_milestone"
+#define QOS_MILESTONE_TIMEOUT 3600
+#define QOS_MILESTONE_COOKIE "QSSCD"
+#define QS_SIM_IP_LEN 100
+#define QS_USR_SPE "mod_qos::user"
+#define QS_REC_COOKIE "mod_qos::gc"
+#define QS_R010_ALREADY_BLOCKED "R010B"
+#define QS_R012_ALREADY_BLOCKED "R012B"
+#define QS_R013_ALREADY_BLOCKED "R013B"
+#define QS_PKT_RATE_TH 3
+#define QS_BW_SAMPLING_RATE 10
+// split linear QS_SrvMaxConnPerIP* entry (conn->conn_ip) search:
+#ifndef QS_MEM_SEG
+#define QS_MEM_SEG 4
+#endif
+// buffer if srv opens new connections faster than it closes existing ones
+#define QS_DOUBLE_CONN_H 128
+#define QS_DOUBLE_CONN 32
+
+#define QS_CONN_ABORT "mod_qos_connection_aborted"
+
+/* Preprocessor Definitions
+ * QSLOG_CLID, QSLOG_EVENT, QSLOG_AVERAGE define parameters for QSLog: */
+#ifndef QSLOG_CLID
+#define QSLOG_CLID "mod_qos_user_id"
+#endif
+#ifndef QSLOG_EVENT
+#define QSLOG_EVENT "Event"
+#endif
+#ifndef QSLOG_AVERAGE
+#define QSLOG_AVERAGE "QS_AllConn"
+#endif
+
+/* Preprocessor Definitions
+ * logs repeating messages only once: */
+#ifndef QS_LOG_REPEAT
+#define QS_LOG_REPEAT 20
+#endif
+
+#define QS_IP4IN6 "::ffff:"
+
+#define QS_PARP_Q "qos-parp-query"
+#define QS_PARP_QUERY "qos-query"
+#define QS_PARP_PATH "qos-path"
+#define QS_PARP_LOC "qos-loc"
+
+#define QSLOGFORMAT "ISBiDUkEQaC"
+
+#define QS_SET_DSCP "QS_Set_DSCP"
+
+#define QS_RESDELYATIME "QS_ResponseDelayTime"
+#define QS_CONNID "QS_ConnectionId"
+#define QS_COUNTRY "QS_Country"
+#define QS_SERIALIZE "QS_Serialize"
+#define QS_SRVSERIALIZE "QS_SrvSerialize"
+#define QS_ErrorNotes "QS_ErrorNotes"
+#define QS_BLOCK "QS_Block"
+#define QS_BLOCK_SEEN "QS_Block_seen"
+#define QS_BLOCK_DEC "QS_Block_Decrement"
+#define QS_LIMIT_NAME_PFX "QS_Limit_VAR_NAME_IDX"
+#define QS_LIMIT_DEFAULT "QS_Limit"
+#define QS_LIMIT_SEEN "QS_Limit_seen"
+#define QS_COUNTER_SUFFIX "_Counter"
+#define QS_LIMIT_CLEAR "_Clear"
+#define QS_LIMIT_DEC "_Decrement"
+#define QS_LIMIT_REMAINING "_Remaining"
+#define QS_EVENT "QS_Event"
+#define QS_COND "QS_Cond"
+#define QS_ISVIPREQ "QS_IsVipRequest"
+#define QS_VipRequest "QS_VipRequest"
+#define QS_KEEPALIVE "QS_KeepAliveTimeout"
+#define QS_MAXKEEPALIVEREQ "QS_MaxKeepAliveRequests"
+#define QS_TIMEOUT "QS_Timeout"
+#define QS_CLOSE "QS_SrvMinDataRate"
+#define QS_MAXIP "QS_SrvMaxConnPerIP"
+#define QS_EMPTY_CON "NullConnection"
+#define QS_BROKEN_CON "BrokenConnection"
+#define QS_RuleId "QS_RuleId"
+
+// enable connection counter if one of the following feature is used
+#define QS_COUNT_CONNECTIONS(sconf) (sconf->max_conn != -1) || \
+ (sconf->min_rate_max != -1) || \
+ (sconf->max_conn_close != -1) || \
+ (sconf->max_conn_per_ip_connections != 1) || \
+ sconf->geodb
+
+
+// "3758096128","3758096383","AU"
+#define QS_GEO_PATTERN "\"([0-9]+)\",\"([0-9]+)\",\"([A-Z0-9]{2}|-)\""
+
+static const char *m_env_variables[] = {
+ QS_ErrorNotes,
+ QS_SERIALIZE,
+ QS_SRVSERIALIZE,
+ QS_BLOCK,
+ QS_BLOCK_SEEN,
+ QS_BLOCK_DEC,
+ QS_LIMIT_DEFAULT,
+ QS_LIMIT_SEEN,
+ QS_EVENT,
+ QS_COND,
+ QS_ISVIPREQ,
+ QS_VipRequest,
+ QS_KEEPALIVE,
+ QS_MAXKEEPALIVEREQ,
+ QS_CLOSE,
+ QS_EMPTY_CON,
+ QS_BROKEN_CON,
+ QS_RuleId,
+ NULL
+};
+
+static const char *m_note_variables[] = {
+ QS_PARP_PATH,
+ QS_PARP_QUERY,
+ NULL
+};
+
+#define QS_INCTX_ID inctx->id
+
+/* Preprocessor Definitions
+ This is the measure rate for QS_SrvRequestRate/QS_SrvMinDataRate which may
+ be increased to 10 or 30 seconds in order to compensate bandwidth variations.
+ You may also use the QS_SrvSampleRate directive to override this default.
+ Set it greater than the Apache Timeout directive to prevent from closing
+ unused speculative TCP pre-connections. */
+#ifndef QS_REQ_RATE_TM
+#define QS_REQ_RATE_TM 5
+#endif
+
+#define QS_MAX_DELAY 5000000
+
+#define QOS_DEC_MODE_FLAGS_URL 0x00
+#define QOS_DEC_MODE_FLAGS_HTML 0x01
+#define QOS_DEC_MODE_FLAGS_UNI 0x02
+#define QOS_DEC_MODE_FLAGS_ANSI 0x04
+
+#define QOS_LOW_FLAG_PKGRATE 0x01
+#define QOS_LOW_FLAG_BEHAVIOR_OK 0x02
+#define QOS_LOW_FLAG_BEHAVIOR_BAD 0x04
+#define QOS_LOW_FLAG_EVENTBLOCK 0x08
+#define QOS_LOW_FLAG_EVENTLIMIT 0x10
+#define QOS_LOW_FLAG_TIMEOUT 0x20
+#define QOS_LOW_TIMEOUT 86400
+
+#define QOS_CC_BEHAVIOR_THR 50000
+#define QOS_CC_BEHAVIOR_THR_SINGLE 50
+#ifdef QS_INTERNAL_TEST
+#undef QOS_CC_BEHAVIOR_THR
+#undef QOS_CC_BEHAVIOR_THR_SINGLE
+#define QOS_CC_BEHAVIOR_THR 50
+#define QOS_CC_BEHAVIOR_THR_SINGLE 20
+#endif
+#define QOS_CC_BEHAVIOR_TOLERANCE_STR "20"
+
+/* Apache 2.2 testing needs patch in util_pcre.c line 134 as it does not know AP_REG_DOTALL
+ * Add:
+ * if ((cflags & 0x40) != 0) options |= 0x04; */
+#ifndef AP_REG_DOTALL
+#define AP_REG_DOTALL 0x40
+#endif
+
+#define QS_ERR_TIME_FORMAT "%a %b %d %H:%M:%S %Y"
+
+#define QSMOD 4
+#define QOS_DELIM ";"
+
+// Apache 2.4 compat
+#if (AP_SERVER_MINORVERSION_NUMBER == 4)
+#define QS_APACHE_24 1
+#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
+#define QS_CONN_REMOTEIP(c) c->master ? c->master->client_ip : c->client_ip
+#define QS_CONN_REMOTEADDR(c) c->master ? c->master->client_addr : c->client_addr
+#define QS_CONN_MASTER(c) (c->master ? c->master : c)
+#else
+#define QS_CONN_REMOTEIP(c) c->client_ip
+#define QS_CONN_REMOTEADDR(c) c->client_addr
+#define QS_CONN_MASTER(c) (c)
+#endif
+#define QOS_MY_GENERATION(g) ap_mpm_query(AP_MPMQ_GENERATION, &g)
+#define qos_unixd_set_global_mutex_perms ap_unixd_set_global_mutex_perms
+#define QS_ISDEBUG(s) APLOG_IS_LEVEL(s, APLOG_DEBUG)
+#else
+#define QS_CONN_REMOTEIP(c) c->remote_ip
+#define QS_CONN_REMOTEADDR(c) c->remote_addr
+#define QS_CONN_MASTER(c) (c)
+#define QOS_MY_GENERATION(g) g = ap_my_generation
+#define qos_unixd_set_global_mutex_perms unixd_set_global_mutex_perms
+#define QS_ISDEBUG(s) s->loglevel >= APLOG_DEBUG
+#endif
+
+#ifdef QS_MOD_EXT_HOOKS
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(qos, QOS, apr_status_t, path_decode_hook,
+ (request_rec *r, char **path, int *len),
+ (r, path, len),
+ OK, DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(qos, QOS, apr_status_t, query_decode_hook,
+ (request_rec *r, char **query, int *len),
+ (r, query, len),
+ OK, DECLINED)
+#endif
+
+/************************************************************************
+ * structures
+ ***********************************************************************/
+typedef struct {
+ const char *name; /* variable name */
+ ap_regex_t *preg;
+ const char *url; /* redirect url */
+ int code;
+} qos_redirectif_entry_t;
+
+typedef struct {
+ unsigned long start;
+ unsigned long end;
+ char country[3];
+} qos_geo_entry_t;
+
+typedef struct {
+ qos_geo_entry_t *data; // fist element
+ int size; // number of elements
+ const char *path;
+} qos_geo_t;
+
+typedef struct {
+ const char *url;
+ const char *path;
+} qos_errelt_t;
+
+static const qos_errelt_t m_error_pages[] = {
+ { "/errorpages/server_error.html", "work/errorpages/server_error.html" },
+ { "/errorpages/forbidden.html", "work/errorpages/forbidden.html" },
+ { "/errorpages/500.html", "work/errorpages/500.html" },
+ { "/errorpages/error.html", "work/errorpages/error.html" },
+ { "/errorpages/error500.html", "work/errorpages/error500.html" },
+ { "/errorpages/gateway_error.html", "work/errorpages/gateway_error.html" },
+ { NULL, NULL }
+};
+
+typedef struct {
+ int id;
+ const char *name;
+} qos_dscp_t;
+
+static const qos_dscp_t m_dscp_desc[] = {
+ { 0, "none" },
+ { 8, "class selector 1" },
+ { 10, "assured forwarding 11" },
+ { 12, "assured forwarding 12" },
+ { 14, "assured forwarding 13" },
+ { 16, "class selector 2" },
+ { 18, "assured forwarding 21" },
+ { 20, "assured forwarding 22" },
+ { 22, "assured forwarding 23" },
+ { 24, "class selector 3" },
+ { 26, "assured forwarding 31" },
+ { 28, "assured forwarding 32" },
+ { 30, "assured forwarding 33" },
+ { 32, "class selector 4" },
+ { 34, "assured forwarding 41" },
+ { 36, "assured forwarding 42" },
+ { 38, "assured forwarding 43" },
+ { 40, "class selector 5" },
+ { 44, "voice admit" },
+ { 46, "expedited forwarding" },
+ { 48, "class selector 6" },
+ { 56, "class selector 7" },
+ { -1, "unknown" }
+};
+
+typedef struct {
+ unsigned short int limit;
+ time_t limitTime;
+} qos_s_entry_limit_t;
+
+typedef struct {
+ unsigned short int limit;
+ time_t limitTime;
+ const char *eventClearStr; // name of the var clearing the counter
+ const char *eventDecStr; // name of the var decrementing the counter
+ const char *condStr;
+ ap_regex_t *preg;
+} qos_s_entry_limit_conf_t;
+
+typedef struct {
+ apr_uint64_t ip6[2];
+ time_t lowrate;
+ unsigned int lowratestatus;
+ /* behavior */
+ unsigned int html;
+ unsigned int cssjs;
+ unsigned int img;
+ unsigned int other;
+ unsigned int notmodified;
+ unsigned int events;
+ /* serialization flag */
+ unsigned int serialize;
+ apr_time_t serializeQueue;
+ /* prefer */
+ short int vip;
+ /* ev block */
+ unsigned short int block;
+ short int blockMsg;
+ time_t time;
+ time_t blockTime;
+ qos_s_entry_limit_t *limit;
+ /* ev/sec */
+ time_t interval;
+ long req;
+ long req_per_sec;
+ int req_per_sec_block_rate;
+ int event_req;
+} qos_s_entry_t;
+
+typedef struct {
+ time_t t;
+ /* index */
+ qos_s_entry_t **ipd;
+ qos_s_entry_t **timed;
+ /* shm */
+ apr_shm_t *m;
+ char *lock_file;
+ apr_global_mutex_t *lock;
+ /* size */
+ int num;
+ int max;
+ int msize;
+ /* limit table settings */
+ apr_table_t *limitTable;
+ /* av. behavior */
+ unsigned long long html;
+ unsigned long long cssjs;
+ unsigned long long img;
+ unsigned long long other;
+ unsigned long long notmodified;
+ /* data */
+ int connections;
+ /* remember for which clients this rec has created (shared mem) */
+ int generation_locked; // indicates the the counters have been cleared for this generation
+ /* log counter */
+ unsigned long long eventTotal[QOS_LOG_MSGCT]; // total number of events since initial start
+ unsigned long long eventLast[QOS_LOG_MSGCT]; // number of events since last read
+} qos_s_t;
+
+typedef enum {
+ QS_IP_V6_DEFAULT = 0,
+ QS_IP_V6,
+ QS_IP_V4
+} qs_ip_type_e;
+
+typedef enum {
+ QS_CONN_STATE_NEW = 0,
+ QS_CONN_STATE_HEAD,
+ QS_CONN_STATE_BODY,
+ QS_CONN_STATE_CHUNKED,
+ QS_CONN_STATE_KEEP,
+ QS_CONN_STATE_RESPONSE,
+ QS_CONN_STATE_END,
+ QS_CONN_STATE_DESTROY
+} qs_conn_state_e;
+
+typedef enum {
+ QS_HEADERFILTER_OFF_DEFAULT = 0,
+ QS_HEADERFILTER_OFF,
+ QS_HEADERFILTER_ON,
+ QS_HEADERFILTER_SIZE_ONLY,
+ QS_HEADERFILTER_SILENT
+} qs_headerfilter_mode_e;
+
+typedef enum {
+ QS_FLT_ACTION_DROP,
+ QS_FLT_ACTION_DENY
+} qs_flt_action_e;
+
+typedef enum {
+ QS_EVENT_ACTION_DENY = 0
+} qs_event_action_e;
+
+typedef enum {
+ QS_DENY_REQUEST_LINE,
+ QS_DENY_PATH,
+ QS_DENY_QUERY,
+ QS_DENY_EVENT,
+ QS_PERMIT_URI
+} qs_rfilter_type_e;
+
+typedef enum {
+ QS_LOG = 0,
+ QS_DENY,
+ QS_OFF_DEFAULT,
+ QS_OFF
+} qs_rfilter_action_e;
+
+enum qos_cmp {
+ QS_CMP_EQ,
+ QS_CMP_NE,
+ QS_CMP_GT,
+ QS_CMP_LT
+};
+
+typedef struct {
+ enum qos_cmp cmp;
+ const char *left;
+ const char *right;
+ const char *variable;
+ const char *value;
+} qos_cmp_entry_t;
+
+typedef struct {
+ char *variable1;
+ char *variable2;
+ ap_regex_t *preg;
+ char *name;
+ char *value;
+} qos_setenvif_t;
+
+typedef struct {
+ ap_regex_t *preg;
+ char *name;
+ char *value;
+} qos_setenvifquery_t;
+
+typedef struct {
+ ap_regex_t *pregx;
+ char *name;
+ char *value;
+} qos_setenvifparpbody_t;
+
+/**
+ * generic request filter
+ */
+typedef struct {
+ ap_regex_t *preg;
+ char *text;
+ char *id;
+ qs_rfilter_type_e type;
+ qs_rfilter_action_e action;
+} qos_rfilter_t;
+
+/**
+ * list of in_filter ctx
+ */
+typedef struct {
+ apr_table_t *table;
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *lock;
+ apr_thread_t *thread;
+#endif
+ int exit;
+} qos_ifctx_list_t;
+
+/**
+ * ip entry
+ */
+typedef struct qs_ip_entry_st {
+ apr_uint64_t ip6[2];
+ int counter;
+ int error;
+} qs_ip_entry_t;
+
+typedef struct {
+ qs_ip_entry_t *conn_ip;
+ int conn_ip_len;
+ int connections;
+ int max_client;
+} qs_conn_t;
+
+typedef struct {
+ apr_time_t q1; // first request in queue
+ apr_time_t q2; // second request in queue
+ int locked;
+} qs_serial_t;
+
+/**
+ * session cookie
+ */
+typedef struct {
+ time_t time;
+} qos_session_t;
+
+/**
+ * cfg/act entry for event limitation
+ */
+typedef struct {
+ const char *env_var;// configured environment variable name
+ const char *eventDecStr;
+ int max; // configured max. num
+ int seconds; // configured duration
+ int limit; // event counter
+ time_t limitTime; // timer
+ qs_event_action_e action;
+ const char *condStr;
+ ap_regex_t *preg;
+} qos_event_limit_entry_t;
+
+/**
+ * access control table entry
+ */
+typedef struct qs_acentry_st {
+ int id;
+ /** pointer to lock of the actable */
+ apr_global_mutex_t *lock;
+ /** location rules */
+ char *url;
+ int url_len;
+ char *event;
+ ap_regex_t *regex;
+ ap_regex_t *regex_var;
+ ap_regex_t *condition;
+ int counter;
+ int limit;
+ /* measurement */
+ apr_time_t interval;
+ long req;
+ long req_per_sec;
+ long req_per_sec_limit;
+ int req_per_sec_block_rate;
+ long bytes; // transferred bytes
+ apr_time_t kbytes_interval_us; // elapsed time
+ apr_off_t kbytes_per_sec; // actual kbytes/sec (measured)
+ apr_off_t kbytes_per_sec_limit; // configured limitation
+ apr_off_t kbytes_per_sec_block_rate; // current wait time
+ struct qs_acentry_st *next;
+} qs_acentry_t;
+
+/**
+ * access control table (act)
+ */
+typedef struct qs_actable_st {
+ apr_size_t size;
+ apr_shm_t *m;
+ apr_pool_t *pool;
+ /** process pool is used to create user space data */
+ apr_pool_t *ppool;
+ /** rule entry list */
+ qs_acentry_t *entry; /* shm pointer */
+ int has_events;
+ /** event limit list */
+ qos_event_limit_entry_t *event_entry;
+ /** mutex */
+ char *lock_file;
+ apr_global_mutex_t *lock;
+ /** ip/conn data */
+ qs_conn_t *conn; /* shm pointer */
+ unsigned int timeout;
+ /* settings */
+ int child_init;
+ /* serialize */
+ qs_serial_t *serialize; /* shm pointer */
+ time_t *qsstatustimer; /* shm pointer */
+} qs_actable_t;
+
+/**
+ * network table (total connections, vip connections, first update, last update)
+ */
+typedef struct qs_netstat_st {
+ // int counter;
+ int vip;
+ // time_t first;
+ // time_t last;
+} qs_netstat_t;
+
+/**
+ * user space
+ */
+typedef struct {
+ int server_start;
+ apr_table_t *act_table;
+ /* client control */
+ qos_s_t *qos_cc;
+} qos_user_t;
+
+/**
+ * directory config
+ */
+typedef struct {
+ char *path;
+ apr_table_t *rfilter_table;
+ int inheritoff;
+ qs_headerfilter_mode_e headerfilter;
+ qs_headerfilter_mode_e resheaderfilter;
+ int bodyfilter_d;
+ int bodyfilter_p;
+ int dec_mode;
+ apr_off_t maxpost;
+ qs_rfilter_action_e urldecoding;
+ const char *response_pattern;
+ int response_pattern_len;
+ const char *response_pattern_var;
+ apr_array_header_t *redirectif;
+ int decodings;
+ apr_table_t *disable_reqrate_events;
+ apr_table_t *setenvstatus_t;
+ apr_array_header_t *setenvif_t;
+ apr_table_t *setenvifquery_t;
+ apr_array_header_t* setenvcmp;
+} qos_dir_config;
+
+/**
+ * server configuration
+ */
+typedef struct {
+ apr_pool_t *pool;
+ int is_virtual;
+ server_rec *base_server;
+ char *mfile;
+ qs_actable_t *act;
+ const char *error_page;
+ apr_table_t *location_t;
+ apr_table_t *setenv_t;
+ apr_table_t *setreqheader_t;
+ apr_table_t *setreqheaderlate_t;
+ apr_table_t *unsetresheader_t;
+ apr_table_t *unsetreqheader_t;
+ apr_array_header_t *setenvif_t;
+ apr_table_t *setenvifquery_t;
+ apr_table_t *setenvifparp_t;
+ apr_table_t *setenvifparpbody_t;
+ apr_table_t *setenvstatus_t;
+ apr_table_t *setenvresheader_t;
+ apr_table_t *setenvresheadermatch_t;
+ apr_table_t *setenvres_t;
+ qs_headerfilter_mode_e headerfilter;
+ qs_headerfilter_mode_e resheaderfilter;
+ apr_array_header_t *redirectif;
+ char *cookie_name;
+ char *cookie_path;
+ char *user_tracking_cookie;
+ char *user_tracking_cookie_force;
+ int user_tracking_cookie_session;
+ int user_tracking_cookie_jsredirect;
+ char *user_tracking_cookie_domain;
+ int max_age;
+ unsigned char key[EVP_MAX_KEY_LENGTH];
+ const unsigned char *rawKey;
+ int rawKeyLen;
+ int keyset;
+ char *header_name;
+ int header_name_drop;
+ ap_regex_t *header_name_regex;
+ apr_table_t *disable_reqrate_events;
+ char *ip_header_name;
+ int ip_header_name_drop;
+ ap_regex_t *ip_header_name_regex;
+ int vip_user;
+ int vip_ip_user;
+
+ int has_conn_counter;
+ int max_conn;
+ int max_conn_close;
+ int max_conn_close_percent;
+ int max_conn_per_ip;
+ int max_conn_per_ip_connections;
+ int max_conn_per_ip_ignore_vip;
+
+ int serialize;
+ int serializeTMO;
+ apr_table_t *exclude_ip;
+ qos_ifctx_list_t *inctx_t;
+ apr_table_t *hfilter_table; /* GLOBAL ONLY */
+ apr_table_t *reshfilter_table; /* GLOBAL ONLY */
+ /* event rule (enables rule validation) */
+ int has_event_filter;
+ int has_event_limit;
+ apr_array_header_t *event_limit_a;
+ /* min data rate */
+ int req_rate; /* GLOBAL ONLY */
+ int req_rate_start; /* GLOBAL ONLY */
+ int min_rate; /* GLOBAL ONLY */
+ int min_rate_max; /* GLOBAL ONLY */
+ int min_rate_off;
+ int req_ignore_vip_rate; /* GLOBAL ONLY */
+ int max_clients;
+ int max_clients_conf;
+#ifdef QS_INTERNAL_TEST
+ apr_table_t *testip;
+ int enable_testip;
+#endif
+ int disable_handler;
+ int log_only; /* GLOBAL ONLY */
+ int log_env;
+ /* client control */
+ int has_qos_cc; /* GLOBAL ONLY */
+ int qos_cc_size; /* GLOBAL ONLY */
+ int qos_cc_prefer; /* GLOBAL ONLY */
+ apr_table_t *cc_exclude_ip; /* GLOBAL ONLY */
+ int qos_cc_prefer_limit;
+ int qos_cc_event; /* GLOBAL ONLY */
+ int qos_cc_event_req; /* GLOBAL ONLY */
+ int qos_cc_block; /* GLOBAL ONLY */
+ int qos_cc_blockTime; /* GLOBAL ONLY */
+ apr_table_t *qos_cc_limitTable; /* GLOBAL ONLY */
+ char *qos_cc_forwardedfor; /* GLOBAL ONLY */
+ int qos_cc_serialize; /* GLOBAL ONLY */
+ apr_off_t maxpost;
+ int cc_tolerance; /* GLOBAL ONLY */
+ int cc_tolerance_max; /* GLOBAL ONLY */
+ int cc_tolerance_min; /* GLOBAL ONLY */
+ int qs_req_rate_tm; /* GLOBAL ONLY */
+ qos_geo_t *geodb; /* GLOBAL ONLY */
+ int geo_limit; /* GLOBAL ONLY */
+ apr_table_t *geo_priv; /* GLOBAL ONLY */
+ int geo_excludeUnknown; /* GLOBAL ONLY */
+ qs_ip_type_e ip_type; /* GLOBAL ONLY */
+ int qsstatus; /* GLOBAL ONLY */
+ int qsevents; /* GLOBAL ONLY */
+ apr_array_header_t *milestones;
+ time_t milestoneTimeout;
+ /* predefined client behavior */
+ int static_on;
+ unsigned long long static_html;
+ unsigned long long static_cssjs;
+ unsigned long long static_img;
+ unsigned long long static_other;
+ unsigned long long static_notmodified;
+ apr_file_t *qslog_p; /* GLOBAL ONLY */
+ const char *qslog_str; /* GLOBAL ONLY */
+} qos_srv_config;
+
+#if APR_HAS_THREADS
+typedef struct {
+ apr_thread_t *thread;
+ int exit;
+ int maxclients;
+ time_t *qsstatustimer; /* shm in act */
+ apr_global_mutex_t *lock; /* lock of act */
+ apr_pool_t *pool;
+ qos_srv_config *sconf;
+} qsstatus_t;
+#endif
+
+/**
+ * in_filter ctx
+ */
+typedef struct {
+ apr_socket_t *clientSocket;
+ qs_conn_state_e status;
+ apr_off_t cl_val;
+ conn_rec *c;
+ request_rec *r;
+ /* upload bandwidth (received bytes and start time) */
+ time_t time;
+ apr_size_t nbytes; // measuring the bytes/sec
+ int hasBytes;
+ int shutdown;
+ int errors;
+ int disabled;
+ int lowrate;
+ char *id;
+ qos_srv_config *sconf;
+} qos_ifctx_t;
+
+/**
+ * connection configuration
+ */
+typedef struct {
+ apr_uint64_t ip6[2];
+ conn_rec *mc; // master (real) connection
+ char *evmsg;
+ qos_srv_config *sconf;
+ int is_vip; /* is vip, either by request or by session or by ip */
+ int set_vip_by_header; /* received vip header from application/or auth. user (propagate to IP store)*/
+ int has_lowrate;
+ qs_conn_t *conn;
+} qs_conn_ctx;
+
+typedef struct {
+ qs_conn_ctx *cconf;
+ conn_rec *c;
+ qos_srv_config *sconf;
+ int requests; // number of requests processed (received) by this connection
+ apr_socket_t *clientSocket;
+} qs_conn_base_ctx;
+
+/**
+ * request configuration
+ */
+typedef struct {
+ qs_acentry_t *entry;
+ qs_acentry_t *entry_cond;
+ apr_table_t *event_entries;
+ char *evmsg;
+ int is_vip;
+ apr_off_t maxpostcount;
+ int cc_event_req_set;
+ apr_uint64_t cc_event_ip[2];
+ int cc_serialize_set;
+ apr_uint64_t cc_serialize_ip[2];
+ int srv_serialize_set;
+ char *body_window;
+ apr_off_t response_delayed; // indicates, if the response has been delayed (T)
+} qs_req_ctx;
+
+
+/**
+ * Delay filter context
+ */
+typedef struct {
+ qs_acentry_t *entry;
+ qs_req_ctx *rctx;
+} qos_delay_ctx_t;
+
+/**
+ * rule set
+ */
+typedef struct {
+ char *url;
+ char *event;
+ int limit;
+ ap_regex_t *regex;
+ ap_regex_t *regex_var;
+ ap_regex_t *condition;
+ long req_per_sec_limit;
+ apr_off_t kbytes_per_sec_limit;
+} qs_rule_ctx_t;
+
+typedef struct {
+ const char* name;
+ const char* pattern;
+ qs_flt_action_e action;
+ int size;
+} qos_her_t;
+
+typedef struct {
+ ap_regex_t *preg;
+ char *name;
+ char *value;
+} qos_pregval_t;
+
+typedef struct {
+ int num;
+ int thinktime;
+ const char* pattern;
+ ap_regex_t *preg;
+ qs_rfilter_action_e action;
+} qos_milestone_t;
+
+typedef struct {
+ char *text;
+ ap_regex_t *preg;
+ qs_flt_action_e action;
+ int size;
+} qos_fhlt_r_t;
+
+typedef struct {
+ apr_time_t request_time;
+ unsigned int in_addr;
+ unsigned int conn;
+#if APR_HAS_THREADS
+ apr_os_thread_t tid;
+#endif
+ unsigned int unique_id_counter;
+} qos_unique_id_t;
+
+/************************************************************************
+ * Globals
+ ***********************************************************************/
+
+module AP_MODULE_DECLARE_DATA qos_module;
+static int m_forced_close = 1;
+static int m_retcode = HTTP_INTERNAL_SERVER_ERROR;
+static int m_threaded_mpm = 1; // note: mod_qos is fully tested for Apache 2.2 worker MPM only
+static int m_event_mpm = 0;
+static double m_event_mpm_worker_factor = 2;
+static unsigned int m_hostcode = 0;
+static int m_generation = 0; // parent process (restart generation)
+static int m_qos_cc_partition = QSMOD;
+static qos_unique_id_t m_unique_id;
+static const char qos_basis_64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
+
+#ifdef QS_INTERNAL_TEST
+static int m_qs_sim_ip_len = QS_SIM_IP_LEN;
+#endif
+
+#ifdef QS_APACHE_24
+APLOG_USE_MODULE(qos);
+#endif
+
+/* mod_parp, forward and optional function */
+static apr_status_t qos_cleanup_conn(void *p);
+static apr_status_t qos_base_cleanup_conn(void *p);
+
+static qs_ip_type_e m_ip_type = QS_IP_V6_DEFAULT;
+
+APR_DECLARE_OPTIONAL_FN(apr_table_t *, parp_hp_table, (request_rec *));
+APR_DECLARE_OPTIONAL_FN(char *, parp_body_data, (request_rec *, apr_size_t *));
+static APR_OPTIONAL_FN_TYPE(parp_hp_table) *qos_parp_hp_table_fn = NULL;
+static APR_OPTIONAL_FN_TYPE(parp_body_data) *qos_parp_body_data_fn = NULL;
+static int m_requires_parp = 0;
+static int m_enable_audit = 0;
+/* mod_ssl, forward and optional function */
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *qos_is_https = NULL;
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, (apr_pool_t *, server_rec *, conn_rec *, request_rec *, char *));
+static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *qos_ssl_var = NULL;
+
+static void qs_inc_eventcounter(apr_pool_t *ppool, int event, int locked);
+#define QS_INC_EVENT(sconf, event) if(sconf->qsevents) qs_inc_eventcounter(sconf->act->ppool, event, 0)
+#define QS_INC_EVENT_LOCKED(sconf, event) if(sconf->qsevents) qs_inc_eventcounter(sconf->act->ppool, event, 1)
+static int m_knownEvents[] = { 10, 11, 12, 13, 21, 23, 25, 30, 31, 34, 35, 36, 37, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 60, 61, 62, 65, 66, 67, 68, 69, 101, 147, 0 };
+
+/* simple header rules allowing "the usual" header formats only (even drop requests using
+ extensions which are used rarely) */
+/* reserved (to be escaped): {}[]()^$.|*+?\ */
+static const qos_her_t qs_header_rules[] = {
+#define QS_URL_UNRESERVED "a-zA-Z0-9._~% -"
+#define QS_URL_GEN ":/?#\\[\\]@"
+#define QS_URL_SUB "!$&'()*+,;="
+#define QS_URL "["QS_URL_GEN""QS_URL_SUB""QS_URL_UNRESERVED"]"
+#define QS_2616TOKEN "[\\x21\\x23-\\x27\\x2a-\\x2e0-9A-Z\\x5-\\x60a-z\\x7e]+"
+#define QS_B64_SP "[a-zA-Z0-9 +/$=:]"
+#define QS_PIPE "\\|"
+#define QS_WEAK "(W/)?"
+#define QS_H_ACCEPT "[a-zA-Z0-9_*+-]+/[a-zA-Z0-9_*+.-]+(;[ ]?[a-zA-Z0-9]+=[0-9]+)?[ ]?(;[ ]?[qv]=[a-z0-9.]+)?"
+#define QS_H_ACCEPT_C "[a-zA-Z0-9*-]+(;[ ]?q=[0-9.]+)?"
+#define QS_H_ACCEPT_E "[a-zA-Z0-9*-]+(;[ ]?q=[0-9.]+)?"
+#define QS_H_ACCEPT_L "[a-zA-Z*-]+[0-9]{0,3}(;[ ]?q=[0-9.]+)?"
+#define QS_H_CACHE "no-cache|no-store|max-age=[0-9]+|max-stale(=[0-9]+)?|min-fresh=[0-9]+|no-transform|only-if-chached"
+#define QS_H_CONTENT "[\"a-zA-Z0-9*/; =-]+"
+#define QS_H_COOKIE "["QS_URL_GEN""QS_URL_SUB"\""QS_URL_UNRESERVED"]"
+#define QS_H_EXPECT "[a-zA-Z0-9= ;.,-]"
+#define QS_H_PRAGMA "[a-zA-Z0-9= ;.,-]"
+#define QS_H_FROM "[a-zA-Z0-9=@;.,()-]"
+#define QS_H_HOST "[a-zA-Z0-9.-]+(:[0-9]+)?"
+#define QS_H_IFMATCH "[a-zA-Z0-9=@;.,*\"-]"
+#define QS_H_DATE "[a-zA-Z0-9 :,]"
+#define QS_H_TE "[a-zA-Z0-9*-]+(;[ ]?q=[0-9.]+)?"
+ { "Accept", "^("QS_H_ACCEPT"){1}([ ]?,[ ]?("QS_H_ACCEPT"))*$", QS_FLT_ACTION_DROP, 300 },
+ { "Accept-Charset", "^("QS_H_ACCEPT_C"){1}([ ]?,[ ]?("QS_H_ACCEPT_C"))*$", QS_FLT_ACTION_DROP, 300 },
+ { "Accept-Encoding", "^("QS_H_ACCEPT_E"){1}([ ]?,[ ]?("QS_H_ACCEPT_E"))*$", QS_FLT_ACTION_DROP, 500 },
+ { "Accept-Language", "^("QS_H_ACCEPT_L"){1}([ ]?,[ ]?("QS_H_ACCEPT_L"))*$", QS_FLT_ACTION_DROP, 200 },
+ { "Access-Control-Request-Method", "^[a-zA-Z]+$", QS_FLT_ACTION_DROP, 10 },
+ { "Access-Control-Request-Headers", "^([a-zA-Z0-9-]+){1}([ ]?,[ ]?([a-zA-Z0-9-]+))*$", QS_FLT_ACTION_DROP, 500 },
+ { "Authorization", "^"QS_B64_SP"+$", QS_FLT_ACTION_DROP, 4000 },
+ { "Cache-Control", "^("QS_H_CACHE"){1}([ ]?,[ ]?("QS_H_CACHE"))*$", QS_FLT_ACTION_DROP, 100 },
+ { "Connection", "^([teTE]+,[ ]?)?([a-zA-Z0-9-]+){1}([ ]?,[ ]?([teTE]+))?$", QS_FLT_ACTION_DROP, 100 },
+ { "Content-Encoding", "^[a-zA-Z0-9-]+(,[ ]*[a-zA-Z0-9-]+)*$", QS_FLT_ACTION_DENY, 100 },
+ { "Content-Language", "^([0-9a-zA-Z]{0,8}(-[0-9a-zA-Z]{0,8})*)(,[ ]*([0-9a-zA-Z]{0,8}(-[0-9a-zA-Z]{0,8})*))*$", QS_FLT_ACTION_DROP, 100 },
+ { "Content-Length", "^[0-9]+$", QS_FLT_ACTION_DENY, 10 },
+ { "Content-Location", "^"QS_URL"+$", QS_FLT_ACTION_DENY, 200 },
+ { "Content-md5", "^"QS_B64_SP"+$", QS_FLT_ACTION_DENY, 50 },
+ { "Content-Range", "^(bytes[ ]+([0-9]+-[0-9]+)/([0-9]+|\\*))$", QS_FLT_ACTION_DENY, 50 },
+ { "Content-Type", "^("QS_H_CONTENT"){1}([ ]?,[ ]?("QS_H_CONTENT"))*$", QS_FLT_ACTION_DENY, 200 },
+ { "Cookie", "^"QS_H_COOKIE"+$", QS_FLT_ACTION_DROP, 3000 },
+ { "Cookie2", "^"QS_H_COOKIE"+$", QS_FLT_ACTION_DROP, 3000 },
+ { "DNT", "^[0-9]+$", QS_FLT_ACTION_DROP, 3 },
+ { "Expect", "^"QS_H_EXPECT"+$", QS_FLT_ACTION_DROP, 200 },
+ { "From", "^"QS_H_FROM"+$", QS_FLT_ACTION_DROP, 100 },
+ { "Host", "^"QS_H_HOST"$", QS_FLT_ACTION_DROP, 100 },
+ { "If-Invalid", "^[a-zA-Z0-9_.:;() /+!-]+$", QS_FLT_ACTION_DROP, 500 },
+ { "If-Match", "^"QS_WEAK""QS_H_IFMATCH"+$", QS_FLT_ACTION_DROP, 100 },
+ { "If-Modified-Since", "^"QS_H_DATE"+$", QS_FLT_ACTION_DROP, 100 },
+ { "If-None-Match", "^"QS_WEAK""QS_H_IFMATCH"+$", QS_FLT_ACTION_DROP, 100 },
+ { "If-Range", "^"QS_H_IFMATCH"+$", QS_FLT_ACTION_DROP, 100 },
+ { "If-Unmodified-Since", "^"QS_H_DATE"+$", QS_FLT_ACTION_DROP, 100 },
+ { "If-Valid", "^[a-zA-Z0-9_.:;() /+!-]+$", QS_FLT_ACTION_DROP, 500 },
+ { "Keep-Alive", "^[0-9]+$", QS_FLT_ACTION_DROP, 20 },
+ { "Max-Forwards", "^[0-9]+$", QS_FLT_ACTION_DROP, 20 },
+ { "Origin", "^"QS_URL"+$", QS_FLT_ACTION_DROP, 2000 },
+ { "Proxy-Authorization", "^"QS_B64_SP"+$", QS_FLT_ACTION_DROP, 400 },
+ { "Pragma", "^"QS_H_PRAGMA"+$", QS_FLT_ACTION_DROP, 200 },
+ { "Range", "^[a-zA-Z0-9=_.:;() /+!-]+$", QS_FLT_ACTION_DROP, 200 },
+ { "Referer", "^"QS_URL"+$", QS_FLT_ACTION_DROP, 2000 },
+ { "TE", "^("QS_H_TE"){1}([ ]?,[ ]?("QS_H_TE"))*$", QS_FLT_ACTION_DROP, 100 },
+ { "Transfer-Encoding", "^(chunked|Chunked|compress|Compress|deflate|Deflate|gzip|Gzip|identity|Identity)([ ]?,[ ]?(chunked|Chunked|compress|Compress|deflate|Deflate|gzip|Gzip|identity|Identity))*$", QS_FLT_ACTION_DENY, 100 },
+ { "Unless-Modified-Since", "^"QS_H_DATE"+$", QS_FLT_ACTION_DROP, 100 },
+ { "User-Agent", "^[a-zA-Z0-9]+[a-zA-Z0-9_.:;()\\[\\]@ /+!=,-]+$", QS_FLT_ACTION_DROP, 300 },
+ { "Upgrade-Insecure-Requests", "^1$", QS_FLT_ACTION_DROP, 1 },
+ { "Via", "^[a-zA-Z0-9_.:;() /+!-]+$", QS_FLT_ACTION_DROP, 100 },
+ { "X-Forwarded-For", "^[a-zA-Z0-9_.:-]+(, [a-zA-Z0-9_.:-]+)*$", QS_FLT_ACTION_DROP, 100 },
+ { "X-Forwarded-Host", "^[a-zA-Z0-9_.:-]+$", QS_FLT_ACTION_DROP, 100 },
+ { "X-Forwarded-Server", "^[a-zA-Z0-9_.:-]+$", QS_FLT_ACTION_DROP, 100 },
+ { "X-lori-time-1", "^[0-9]+$", QS_FLT_ACTION_DROP, 20 },
+ { "X-Do-Not-Track", "^[0-9]+$", QS_FLT_ACTION_DROP, 20 },
+ { NULL, NULL, 0, 0 }
+};
+
+/* list of allowed standard response headers */
+static const qos_her_t qs_res_header_rules[] = {
+ { "Age", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Accept-Ranges", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Allow-Origin", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Allow-Methods", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Allow-Headers", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Max-Age", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Allow-Credentials", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Access-Control-Expose-Headers", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Allow", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Cache-Control", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Disposition", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Encoding", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Language", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Length", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Location", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-MD5", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Range", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Content-Security-Policy", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 8000 },
+ { "Content-Type", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Connection", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Date", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "ETag", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Expect", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Expires", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Keep-Alive", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Last-Modified", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Location", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Proxy-Authenticate", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Public-Key-Pins", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Public-Key-Pins-Report-Only", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Retry-After", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Pragma", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Server", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Set-Cookie", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Set-Cookie2", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Strict-Transport-Security", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "Vary", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "WWW-Authenticate", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "X-Content-Security-Policy", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 8000 },
+ { "X-Content-Type-Options", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "X-Frame-Options", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { "X-XSS-Protection", "^[\\x20-\\xFF]*$", QS_FLT_ACTION_DROP, 4000 },
+ { NULL, NULL, 0, 0 }
+};
+
+/************************************************************************
+ * private functions
+ ***********************************************************************/
+
+static int qos_regexec_len(apr_pool_t *pool, const ap_regex_t *preg,
+ const char *buff, apr_size_t len) {
+#if (AP_SERVER_MINORVERSION_NUMBER < 4) && defined QS_INTERNAL_TEST
+ // Apache 2.2 is no longer supported (test only)
+ char *data = apr_palloc(pool, len + 1);
+ memcpy(data, buff, len);
+ data[len] = '\0';
+ return ap_regexec(preg, data, 0, NULL, 0); // won't work for null chars!
+#else
+ return ap_regexec_len(preg, buff, len, 0, NULL, 0);
+#endif
+}
+
+/**
+ * Converts an ip long array back to a string representation
+ *
+ * @param pool
+ * @param src Array of two unsigned long
+ * @return String or null for an invalid address
+ */
+static char *qos_ip_long2str(apr_pool_t *pool, const void *src) {
+ char *dst = apr_pcalloc(pool, INET6_ADDRSTRLEN);
+ char *ret = (char *)inet_ntop(AF_INET6, src, dst, INET6_ADDRSTRLEN);
+ if(ret) {
+ if((strncmp(ret, QS_IP4IN6, 7) == 0) &&
+ strchr(ret, '.')) {
+ ret = &ret[7];
+ }
+ }
+ return ret;
+}
+
+/**
+ * Converts an ip string to long array (128 bit) representation
+ *
+ * @param src String representation, e.g. 139.12.33.1 or 1::8
+ * @param dst Pointer to array of unsigned long (2) (contains "{ 0, 0 }" on error)
+ * @return 1 on success, 0 on error
+ */
+static int qos_ip_str2long(const char *src, apr_uint64_t *dst) {
+ char str[INET6_ADDRSTRLEN];
+ const char *convert = src;
+ apr_uint64_t *n = dst;
+ n[0] = 0;
+ n[1] = 0;
+ if(convert == NULL) {
+ return 0;
+ }
+ if((strchr(convert, ':') == NULL) &&
+ (strlen(convert) <= 15)) {
+ // looks like an IPv4 address
+ sprintf(str, QS_IP4IN6"%s", src);
+ convert = str;
+ }
+ return inet_pton(AF_INET6, convert, dst);
+}
+
+static int qos_encode64_binary(char *encoded, const char *string, int len) {
+ int i;
+ char *p;
+
+ p = encoded;
+ for (i = 0; i < len - 2; i += 3) {
+ *p++ = qos_basis_64[(string[i] >> 2) & 0x3F];
+ *p++ = qos_basis_64[((string[i] & 0x3) << 4) |
+ ((int) (string[i + 1] & 0xF0) >> 4)];
+ *p++ = qos_basis_64[((string[i + 1] & 0xF) << 2) |
+ ((int) (string[i + 2] & 0xC0) >> 6)];
+ *p++ = qos_basis_64[string[i + 2] & 0x3F];
+ }
+ if (i < len) {
+ *p++ = qos_basis_64[(string[i] >> 2) & 0x3F];
+ if (i == (len - 1)) {
+ *p++ = qos_basis_64[((string[i] & 0x3) << 4)];
+ *p++ = '=';
+ }
+ else {
+ *p++ = qos_basis_64[((string[i] & 0x3) << 4) |
+ ((int) (string[i + 1] & 0xF0) >> 4)];
+ *p++ = qos_basis_64[((string[i + 1] & 0xF) << 2)];
+ }
+ *p++ = '=';
+ }
+
+ *p++ = '\0';
+ return (int)(p - encoded);
+}
+
+/**
+ * loads the default header rules into the server configuration (see rules
+ * above).
+ * @param pool To allocate memory
+ * @param outHdrFltTable Table to add rules to
+ * @param hdrFltRuleDefArray built-in header rules
+ * @return error message (NULL on success)
+ */
+static char *qos_load_headerfilter(apr_pool_t *pool, apr_table_t *outHdrFltTable,
+ const qos_her_t *hdrFltRuleDefArray) {
+ const qos_her_t* hdrFltRuleDefEntry;
+ for(hdrFltRuleDefEntry = hdrFltRuleDefArray; hdrFltRuleDefEntry->name != NULL ; ++hdrFltRuleDefEntry) {
+ qos_fhlt_r_t *hdrFltElement = apr_pcalloc(pool, sizeof(qos_fhlt_r_t));
+ hdrFltElement->text = apr_pstrdup(pool, hdrFltRuleDefEntry->pattern);
+ hdrFltElement->preg = ap_pregcomp(pool, hdrFltRuleDefEntry->pattern, AP_REG_DOTALL);
+ hdrFltElement->action = hdrFltRuleDefEntry->action;
+ hdrFltElement->size = hdrFltRuleDefEntry->size;
+ if(hdrFltElement->preg == NULL) {
+ return apr_psprintf(pool, "could not compile regular expression '%s' for %s header",
+ hdrFltElement->text, hdrFltRuleDefEntry->name);
+ }
+ apr_table_setn(outHdrFltTable, hdrFltRuleDefEntry->name, (char *)hdrFltElement);
+ }
+ return NULL;
+}
+
+/**
+ * Returns string representation of filter type (for logging purposes)
+ * @param pool To allocate string
+ * @param type Rule type
+ * @return Name of the directive used to configure the rule
+ */
+static char *qos_rfilter_type2text(apr_pool_t *pool, qs_rfilter_type_e type) {
+ if(type == QS_DENY_REQUEST_LINE) return apr_pstrdup(pool, "QS_DenyRequestLine");
+ if(type == QS_DENY_PATH) return apr_pstrdup(pool, "QS_DenyPath");
+ if(type == QS_DENY_QUERY) return apr_pstrdup(pool, "QS_DenyQuery");
+ if(type == QS_DENY_EVENT) return apr_pstrdup(pool, "QS_DenyEvent");
+ if(type == QS_PERMIT_URI) return apr_pstrdup(pool, "QS_PermitUri");
+ return apr_pstrdup(pool, "UNKNOWN");
+}
+
+/**
+ * Sets unique apache instance id (hopefully) to the global m_hostcore variable
+ * @param ptemp Pool to allocate memory from
+ * @param s Base server record
+ */
+static void qos_hostcode(apr_pool_t *ptemp, server_rec *s) {
+ char *key = apr_psprintf(ptemp, "%s%s%s%d%s"
+#ifdef ap_http_scheme
+/* Apache 2.2 */
+ "%s"
+#endif
+ "%s",
+ s->defn_name ? s->defn_name : "",
+ s->server_admin ? s->server_admin : "",
+ s->server_hostname ? s->server_hostname : "",
+ s->addrs ? s->addrs->host_port : 0,
+ s->path ? s->path : "",
+ s->error_fname ? s->error_fname : ""
+#ifdef ap_http_scheme
+/* Apache 2.2 */
+ ,s->server_scheme ? s->server_scheme : ""
+#endif
+ );
+ int len = strlen(key);
+ int i;
+ char *p;
+ for(p = key, i = len; i; i--, p++) {
+ m_hostcode = m_hostcode * 33 + *p;
+ }
+}
+
+/**
+ * temp file name for the main/virtual serve
+ * @param pool Pool to allocate the file name from
+ * @param s Server record
+ * @return absolute file name
+ */
+static char *qos_tmpnam(apr_pool_t *pool, server_rec *s) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(s->module_config,
+ &qos_module);
+ const char *path = NULL;
+ char *ret;
+ char *file;
+ if(apr_temp_dir_get(&path, pool) != APR_SUCCESS) {
+ path = apr_pstrdup(pool, "/var/tmp");
+ }
+ if(sconf && sconf->mfile) {
+ path = sconf->mfile;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ QOS_LOGD_PFX"temporary directory for semaphores/shared memory: %s"
+ " (use QS_SemMemFile to override it).", path);
+ if(s) {
+ unsigned int scode = 0;
+ char *key = apr_psprintf(pool, "%u%s.%s.%d",
+ m_hostcode,
+ s->is_virtual ? "v" : "b",
+ s->server_hostname == NULL ? "-" : s->server_hostname,
+ s->addrs == NULL ? 0 : s->addrs->host_port);
+ int len = strlen(key);
+ int i;
+ char *p;
+ for(p = key, i = len; i; i--, p++) {
+ scode = scode * 33 + *p;
+ }
+ file = apr_psprintf(pool, "%u", scode);
+ } else {
+ file = apr_psprintf(pool, "%u", m_hostcode);
+ }
+ file[0] += 25; /* non numeric */
+ apr_filepath_merge(&ret, path, file, APR_FILEPATH_NATIVE, pool);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ QOS_LOGD_PFX"temporary file: %s", ret);
+ return ret;
+}
+
+/**
+ * QS_LimitRequestBody settings. Environment variable (dynamic) has higher prio than
+ * configuration (static) value.
+ * @param r
+ * @param sconf
+ * @param dconf
+ */
+static apr_off_t qos_maxpost(request_rec *r, qos_srv_config *sconf,
+ qos_dir_config *dconf) {
+ if(r->subprocess_env) {
+ const char *bytes = apr_table_get(r->subprocess_env, "QS_LimitRequestBody");
+ if(bytes) {
+ apr_off_t s;
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ char *errp = NULL;
+ if(APR_SUCCESS == apr_strtoff(&s, bytes, &errp, 10)) {
+ return s;
+ }
+#else
+ if((s = apr_atoi64(bytes)) >= 0) {
+ return s;
+ }
+#endif
+ }
+ }
+ if(dconf->maxpost != -1) {
+ return dconf->maxpost;
+ }
+ return sconf->maxpost;
+}
+
+
+/**
+ * Similar to strstr but restricting the length of s1 (supports strings which
+ * are not NULL terminated).
+ *
+ * @param s1 String to search in
+ * @param s2 Pattern to ind
+ * @param len Length of s1
+ * @return pointer to the beginning of the substring s2 within s1, or NULL
+ * if the substring is not found
+ */
+static char *qos_strnstr(const char *s1, const char *s2, int len) {
+ const char *e1 = &s1[len-1];
+ char *p1, *p2;
+ if (*s2 == '\0') {
+ /* an empty s2 */
+ return((char *)s1);
+ }
+ while(1) {
+ for ( ; (*s1 != '\0') && (s1 <= e1) && (apr_tolower(*s1) != apr_tolower(*s2)); s1++);
+ if (*s1 == '\0' || s1 > e1) {
+ return(NULL);
+ }
+ /* found first character of s2, see if the rest matches */
+ p1 = (char *)s1;
+ p2 = (char *)s2;
+ for (++p1, ++p2; (apr_tolower(*p1) == apr_tolower(*p2)) && (p1 <= e1); ++p1, ++p2) {
+ if((p1 > e1) && (*p2 != '\0')) {
+ // reached the end without match
+ return NULL;
+ }
+ if (*p2 == '\0') {
+ /* both strings ended together */
+ return((char *)s1);
+ }
+ }
+ if (*p2 == '\0') {
+ /* second string ended, a match */
+ break;
+ }
+ /* didn't find a match here, try starting at next character in s1 */
+ s1++;
+ }
+ return((char *)s1);
+}
+
+/**
+ * Determines, if the client IP shall be excluded from rule enforcement
+ *
+ * @param connection Connection to get the IP
+ * @param exclude_ip Table containing the rules
+ * @return 1 on match otherwise 0
+ */
+static int qos_is_excluded_ip(conn_rec *connection, apr_table_t *exclude_ip) {
+ conn_rec *c = connection;
+ if(apr_table_elts(exclude_ip)->nelts > 0) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(exclude_ip)->elts;
+ for(i = 0; i < apr_table_elts(exclude_ip)->nelts; i++) {
+ if(entry[i].val[0] == 'r') {
+ if(strncmp(entry[i].key, QS_CONN_REMOTEIP(c), strlen(entry[i].key)) == 0) {
+ return 1;
+ }
+ } else {
+ if(strcmp(entry[i].key, QS_CONN_REMOTEIP(c)) == 0) {
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/**
+ * Comperator (ip search) for the client ip store qos_cc_*()
+ * functions (used by bsearch/qsort)
+ */
+static int qos_cc_comp(const void *_pA, const void *_pB) {
+ qos_s_entry_t *pA=*(( qos_s_entry_t **)_pA);
+ qos_s_entry_t *pB=*(( qos_s_entry_t **)_pB);
+ if(pA->ip6[0] > pB->ip6[0]) return 2;
+ if(pA->ip6[0] < pB->ip6[0]) return -2;
+ if(pA->ip6[1] > pB->ip6[1]) return 1;
+ if(pA->ip6[1] < pB->ip6[1]) return -1;
+ return 0;
+}
+
+static int qos_cc_compv4(const void *_pA, const void *_pB) {
+ qos_s_entry_t *pA=*(( qos_s_entry_t **)_pA);
+ qos_s_entry_t *pB=*(( qos_s_entry_t **)_pB);
+ if(pA->ip6[1] > pB->ip6[1]) return 1;
+ if(pA->ip6[1] < pB->ip6[1]) return -1;
+ return 0;
+}
+
+/**
+ * Comperator (time search) for the client ip store qos_cc_*()
+ * functions (used by bsearch/qsort)
+ */
+static int qos_cc_comp_time(const void *_pA, const void *_pB) {
+ qos_s_entry_t *pA=*(( qos_s_entry_t **)_pA);
+ qos_s_entry_t *pB=*(( qos_s_entry_t **)_pB);
+ if(pA->time > pB->time) return 1;
+ if(pA->time < pB->time) return -1;
+ return 0;
+}
+
+/**
+ * creates new per client store
+ * @param pool Persistent process pool
+ * @param srec Server rec for sem/mutex
+ * @param size Number of entries
+ * @param limitTable Table of "QS_Limit" events
+ * @return pointer to the per client data array
+ */
+static qos_s_t *qos_cc_new(apr_pool_t *pool, server_rec *srec, int size,
+ apr_table_t *limitTable) {
+ char *file = "-";
+ apr_shm_t *clientMem; // per client memory table
+ apr_shm_t *limitMem; // "limit" memory table
+ apr_status_t res;
+ int limitTableSize = apr_table_elts(limitTable)->nelts;
+ int limitMemSize = 0;
+ int clientMemSize = APR_ALIGN_DEFAULT(sizeof(qos_s_t)) +
+ (APR_ALIGN_DEFAULT(sizeof(qos_s_entry_t)) * size) +
+ (2 * APR_ALIGN_DEFAULT(sizeof(qos_s_entry_t *)) * size);
+ int i;
+ qos_s_t *clientDataArray;
+ qos_s_entry_t *clientDataEntry;
+ qos_s_entry_limit_t *limitTableEntry = NULL;
+ clientMemSize = clientMemSize + 1024;
+ if(limitTableSize > 0) {
+ limitMemSize = APR_ALIGN_DEFAULT(sizeof(qos_s_entry_limit_t)) * limitTableSize * size;
+ limitMemSize = limitMemSize + 1024;
+ }
+ /* use anonymous shm by default */
+ if(limitTableSize > 0) {
+ apr_shm_create(&limitMem, limitMemSize, NULL, pool);
+ }
+ res = apr_shm_create(&clientMem, clientMemSize, NULL, pool);
+ if(APR_STATUS_IS_ENOTIMPL(res)) {
+ char *lfile = apr_psprintf(pool, "%s_cc_ml.mod_qos",
+ qos_tmpnam(pool, srec));
+ file = apr_psprintf(pool, "%s_cc_m.mod_qos",
+ qos_tmpnam(pool, srec));
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ if(limitTableSize > 0) {
+ apr_shm_remove(lfile, pool);
+ }
+ apr_shm_remove(file, pool);
+#endif
+ if(limitTableSize > 0) {
+ apr_shm_create(&limitMem, limitMemSize, lfile, pool);
+ }
+ res = apr_shm_create(&clientMem, clientMemSize, file, pool);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srec,
+ QOS_LOGD_PFX"create shared memory (client control)(%s): %d bytes",
+ file, clientMemSize + limitMemSize);
+ if(res != APR_SUCCESS) {
+ char buf[MAX_STRING_LEN];
+ apr_strerror(res, buf, sizeof(buf));
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, srec,
+ QOS_LOG_PFX(002)"failed to create shared memory (client control)(%s): "
+ "%s (%d bytes)",
+ file, buf, clientMemSize);
+ return NULL;
+ }
+ clientDataArray = apr_shm_baseaddr_get(clientMem);
+ clientDataArray->m = clientMem;
+ clientDataArray->generation_locked = -1;
+ if(limitTableSize > 0) {
+ apr_table_entry_t *te = (apr_table_entry_t *)apr_table_elts(limitTable)->elts;
+ limitTableEntry = apr_shm_baseaddr_get(limitMem);
+ clientDataArray->limitTable = apr_table_make(pool, limitTableSize+10);
+ for(i = 0; i < limitTableSize; i++) {
+ char *eventName = apr_pstrdup(pool, te[i].key);
+ qos_s_entry_limit_conf_t *eventLimitConf = apr_pcalloc(pool, sizeof(qos_s_entry_limit_conf_t));
+ qos_s_entry_limit_conf_t *src = (qos_s_entry_limit_conf_t*)te[i].val;
+ eventLimitConf->limit = src->limit;
+ eventLimitConf->limitTime = src->limitTime;
+ eventLimitConf->eventClearStr = apr_pstrcat(pool, eventName, QS_LIMIT_CLEAR, NULL);
+ eventLimitConf->eventDecStr = apr_pstrcat(pool, eventName, QS_LIMIT_DEC, NULL);
+ eventLimitConf->condStr = NULL;
+ eventLimitConf->preg = NULL;
+ if(src->condStr) {
+ eventLimitConf->condStr = apr_pstrdup(pool, src->condStr);
+ eventLimitConf->preg = ap_pregcomp(pool, src->condStr, AP_REG_EXTENDED);
+ }
+ apr_table_addn(clientDataArray->limitTable, eventName, (char *)eventLimitConf);
+ }
+ } else {
+ clientDataArray->limitTable = NULL;
+ }
+ clientDataArray->lock_file = apr_psprintf(pool, "%s_ccl.mod_qos",
+ qos_tmpnam(pool, srec));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srec,
+ QOS_LOGD_PFX"create mutex (client control)(%s)",
+ clientDataArray->lock_file);
+ res = apr_global_mutex_create(&clientDataArray->lock, clientDataArray->lock_file, APR_LOCK_DEFAULT, pool);
+ if(res != APR_SUCCESS) {
+ char buf[MAX_STRING_LEN];
+ apr_strerror(res, buf, sizeof(buf));
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, srec,
+ QOS_LOG_PFX(004)"failed to create mutex (client control)(%s): %s",
+ clientDataArray->lock_file, buf);
+ apr_shm_destroy(clientDataArray->m);
+ return NULL;
+ }
+#ifdef AP_NEED_SET_MUTEX_PERMS
+ qos_unixd_set_global_mutex_perms(clientDataArray->lock);
+#endif
+ clientDataEntry = (qos_s_entry_t *)&clientDataArray[1];
+ clientDataArray->ipd = (qos_s_entry_t **)&clientDataEntry[size];
+ clientDataArray->timed = (qos_s_entry_t **)&clientDataArray->ipd[size];
+ clientDataArray->num = 0;
+ clientDataArray->max = size;
+ clientDataArray->msize = clientMemSize;
+ clientDataArray->connections = 0;
+ clientDataArray->html = 0;
+ clientDataArray->cssjs = 0;
+ clientDataArray->img = 0;
+ clientDataArray->other = 0;
+ clientDataArray->notmodified = 0;
+ for(i = 0; i < size; i++) {
+ clientDataArray->ipd[i] = clientDataEntry;
+ clientDataArray->timed[i] = clientDataEntry;
+ if(limitTableSize > 0) {
+ clientDataEntry->limit = limitTableEntry;
+ limitTableEntry += limitTableSize;
+ } else {
+ clientDataEntry->limit = NULL;
+ }
+ clientDataEntry++;
+ }
+ clientDataArray->t = time(NULL);
+ for(i = 0; i < QOS_LOG_MSGCT; i++) {
+ clientDataArray->eventTotal[i] = 0;
+ clientDataArray->eventLast[i] = 0;
+ }
+ return clientDataArray;
+}
+
+/**
+ * Destroys the client data store
+ */
+static void qos_cc_free(qos_s_t *s) {
+ /*
+ We don't need to destroy locks or shared memory
+ manually as apr_global_mutex_create() and
+ apr_shm_create() register the cleanup methods
+ themself to the pool we used when allocating the
+ locks/memory.
+ if(s->lock) {
+ apr_global_mutex_destroy(s->lock);
+ }
+ if(s->m) {
+ apr_shm_destroy(s->m);
+ }
+ if(s->lm) {
+ apr_shm_destroy(s->lm);
+ }
+ */
+}
+
+/**
+ * searches an entry
+ * @param s Client store (locked)
+ * @param pA IP to search
+ * @param now Current time (update access to the entry)
+ * @return client entry or NULL if not available
+ */
+static qos_s_entry_t **qos_cc_get0(qos_s_t *s, qos_s_entry_t *pA, time_t now) {
+ qos_s_entry_t **pB;
+ unsigned char *b = (void *)&pA->ip6[1];
+ int mod = b[7] % m_qos_cc_partition;
+ int max = (s->max / m_qos_cc_partition);
+ int start = mod * max;
+ if(m_ip_type == QS_IP_V4) {
+ pB = bsearch((const void *)&pA, (const void *)&s->ipd[start],
+ max, sizeof(qos_s_entry_t *), qos_cc_compv4);
+ } else {
+ pB = bsearch((const void *)&pA, (const void *)&s->ipd[start],
+ max, sizeof(qos_s_entry_t *), qos_cc_comp);
+ }
+ if(pB) {
+ if(now != 0) {
+ s->t = now;
+ }
+ (*pB)->time = s->t;
+ }
+ return pB;
+}
+
+/**
+ * inserts a new entry to the client data store
+ * @param s Client store (locked)
+ * @param pA IP to insert
+ * @param now Current time (last access)
+ * @return inserted entry
+ */
+static qos_s_entry_t **qos_cc_set(qos_s_t *s, qos_s_entry_t *pA, time_t now) {
+ qos_s_entry_t **pB;
+ unsigned char *b = (void *)&pA->ip6[1];
+ int mod = b[7] % m_qos_cc_partition;
+ int max = (s->max / m_qos_cc_partition);
+ int start = mod * max;
+ s->t = now;
+ qsort(&s->timed[start], max, sizeof(qos_s_entry_t *), qos_cc_comp_time);
+ if(s->num < s->max) {
+ s->num++;
+ }
+ pB = &s->timed[start];
+ (*pB)->ip6[0] = pA->ip6[0];
+ (*pB)->ip6[1] = pA->ip6[1];
+ (*pB)->time = now;
+ if(m_ip_type == QS_IP_V4) {
+ qsort(&s->ipd[start], max, sizeof(qos_s_entry_t *), qos_cc_compv4);
+ } else {
+ qsort(&s->ipd[start], max, sizeof(qos_s_entry_t *), qos_cc_comp);
+ }
+
+ (*pB)->vip = 0;
+ (*pB)->lowrate = 0;
+ (*pB)->lowratestatus = 0;
+ (*pB)->block = 0;
+ (*pB)->blockMsg = 0;
+ (*pB)->blockTime = 0;
+ if(s->limitTable) {
+ int i;
+ for(i = 0; i < apr_table_elts(s->limitTable)->nelts; i++) {
+ (*pB)->limit[i].limit = 0;
+ (*pB)->limit[i].limitTime = 0;
+ }
+ }
+ (*pB)->interval = now;
+ (*pB)->req = 0;
+ (*pB)->req_per_sec = 0;
+ (*pB)->req_per_sec_block_rate = 0;
+ (*pB)->event_req = 0;
+ (*pB)->serialize = 0;
+ (*pB)->serializeQueue = 0;
+ (*pB)->html = 1;
+ (*pB)->cssjs = 1;
+ (*pB)->img = 1;
+ (*pB)->other = 1;
+ (*pB)->notmodified = 1;
+ (*pB)->events = 0;
+ return pB;
+}
+
+/**
+ * searches or inserts (if not available) an entry from/to the client data store
+ * @param s Client store (locked)
+ * @param pA IP to insert
+ * @param now Current time (last access)
+ * @return inserted entry
+ */
+static qos_s_entry_t **qos_cc_getOrSet(qos_s_t *s, qos_s_entry_t *pA, time_t now) {
+ qos_s_entry_t **clientEntry = clientEntry = qos_cc_get0(s, pA, now);
+ if(!clientEntry) {
+ clientEntry = qos_cc_set(s, pA, now != 0 ? now : time(NULL));
+ }
+ return clientEntry;
+}
+
+static int qos_isnum(const char *x) {
+ const char *p = x;
+ if(x == NULL || x[0] == 0) {
+ return 0;
+ }
+ while(p && p[0]) {
+ if(!apr_isdigit(p[0])) {
+ return 0;
+ }
+ p++;
+ }
+ return 1;
+}
+
+/* 000-255 */
+int qos_dec32c(const char *x) {
+ char buf[4];
+ strncpy(buf, x, 3);
+ buf[3] = '\0';
+ return atoi(buf);
+}
+
+int qos_dec22c(const char *x) {
+ char buf[4];
+ strncpy(buf, x, 2);
+ buf[2] = '\0';
+ return atoi(buf);
+}
+
+/**
+ * hex value for the char
+ * @param x
+ * @return hex value
+ */
+int qos_hex2c(const char *x) {
+ int i, ch;
+ ch = x[0];
+ if (isdigit(ch)) {
+ i = ch - '0';
+ }else if (isupper(ch)) {
+ i = ch - ('A' - 10);
+ } else {
+ i = ch - ('a' - 10);
+ }
+ i <<= 4;
+
+ ch = x[1];
+ if (isdigit(ch)) {
+ i += ch - '0';
+ } else if (isupper(ch)) {
+ i += ch - ('A' - 10);
+ } else {
+ i += ch - ('a' - 10);
+ }
+ return i;
+}
+
+#define QOS_ISHEX(x) (((x >= '0') && (x <= '9')) || \
+ ((x >= 'a') && (x <= 'f')) || \
+ ((x >= 'A') && (x <= 'F')))
+
+
+/**
+ * url unescaping (%xx, \xHH, '+')
+ * optional decoding:
+ * - uni: MS IIS unicode %uXXXX
+ * - ansi: ansi c esc (\n, \r, ...), not implemented
+ * - char: charset conv, not implemented
+ * - html: (amp/angelbr, &#xHH;, &#DDD;, &#DD;), not implemented ('&' is delimiter)
+ */
+static int qos_unescaping(char *x, int mode, int *error) {
+ /* start with standard url decoding*/
+ int i, j, ch;
+ if(x == 0) {
+ return 0;
+ }
+ if(x[0] == '\0') {
+ return 0;
+ }
+ for(i = 0, j = 0; x[i] != '\0'; i++, j++) {
+ ch = x[i];
+ if(ch == '%') {
+ if(QOS_ISHEX(x[i + 1]) && QOS_ISHEX(x[i + 2])) {
+ /* url %xx */
+ ch = qos_hex2c(&x[i + 1]);
+ i += 2;
+ } else if((mode & QOS_DEC_MODE_FLAGS_UNI) &&
+ ((x[i + 1] == 'u') || (x[i + 1] == 'U')) &&
+ QOS_ISHEX(x[i + 2]) &&
+ QOS_ISHEX(x[i + 3]) &&
+ QOS_ISHEX(x[i + 4]) &&
+ QOS_ISHEX(x[i + 5])) {
+ /* unicode %uXXXX */
+ ch = qos_hex2c(&x[i + 4]);
+ if((ch > 0x00) && (ch < 0x5f) &&
+ ((x[i + 2] == 'f') || (x[i + 2] == 'F')) &&
+ ((x[i + 3] == 'f') || (x[i + 3] == 'F'))) {
+ ch += 0x20;
+ }
+ i += 5;
+ } else {
+ (*error)++;
+ }
+ } else if((ch == '\\') &&
+ (mode & QOS_DEC_MODE_FLAGS_UNI) &&
+ ((x[i + 1] == 'u') || (x[i + 1] == 'U'))) {
+ if(QOS_ISHEX(x[i + 2]) &&
+ QOS_ISHEX(x[i + 3]) &&
+ QOS_ISHEX(x[i + 4]) &&
+ QOS_ISHEX(x[i + 5])) {
+ /* unicode \uXXXX */
+ ch = qos_hex2c(&x[i + 4]);
+ if((ch > 0x00) && (ch < 0x5f) &&
+ ((x[i + 2] == 'f') || (x[i + 2] == 'F')) &&
+ ((x[i + 3] == 'f') || (x[i + 3] == 'F'))) {
+ ch += 0x20;
+ }
+ i += 5;
+ } else {
+ (*error)++;
+ }
+ } else if(ch == '\\' && (x[i + 1] == 'x')) {
+ if(QOS_ISHEX(x[i + 2]) && QOS_ISHEX(x[i + 3])) {
+ /* url \xHH */
+ ch = qos_hex2c(&x[i + 2]);
+ i += 3;
+ } else {
+ (*error)++;
+ }
+ } else if(ch == '+') {
+ ch = ' ';
+ }
+ x[j] = ch;
+ }
+ x[j] = '\0';
+ return j;
+}
+
+/**
+ * returns the request id from mod_unique_id (if available)
+ */
+static const char *qos_unique_id(request_rec *r, const char *eid) {
+ const char *uid = apr_table_get(r->subprocess_env, "UNIQUE_ID");
+ if(eid) {
+ apr_table_set(r->notes, "error-notes", eid);
+ apr_table_set(r->subprocess_env, QS_ErrorNotes, eid);
+ }
+ if(uid == NULL) {
+ /* generate simple id if mod_unique_id has not been not loaded */
+ qos_unique_id_t id;
+ char *uidstr;
+ int len;
+
+ m_unique_id.unique_id_counter++;
+ id.request_time = r->request_time;
+ id.in_addr = m_unique_id.in_addr;
+#if APR_HAS_THREADS
+ id.tid = apr_os_thread_current();
+#endif
+ id.conn = r->connection->id;
+ id.unique_id_counter = m_unique_id.unique_id_counter;
+ uidstr = (char *)apr_pcalloc(r->pool, apr_base64_encode_len(sizeof(qos_unique_id_t)));
+ len = qos_encode64_binary(uidstr, (const char *)&id, sizeof(qos_unique_id_t));
+ uidstr[len-2] = (id.unique_id_counter%8)+50;
+ uid = uidstr;
+ apr_table_set(r->subprocess_env, "UNIQUE_ID", uid);
+ }
+ return uid;
+}
+
+static void qos_log_env(request_rec *r, const char *handler) {
+ char *msg = "";
+ int i;
+ apr_table_entry_t *e = (apr_table_entry_t *) apr_table_elts(r->subprocess_env)->elts;
+ for (i = 0; i < apr_table_elts(r->subprocess_env)->nelts; ++i) {
+ msg = apr_psprintf(r->pool, "%s=%s;%s", e[i].key, e[i].val, msg);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
+ QOS_LOG_PFX(210)"ENV %s %s %s", handler, msg, qos_unique_id(r, NULL));
+ return;
+}
+
+static char *qos_ipv6_hash(request_rec *r, const char *var) {
+ char *md = ap_md5_binary(r->pool, (unsigned char *)var, strlen(var));
+ char *hash = apr_pcalloc(r->pool, 64);
+ char *d = hash;
+ int c = 0;
+ while(md[0]) {
+ d[0] = md[0];
+ d++;
+ c++;
+ md++;
+ if(c == 4 && md[0]) {
+ c=0;
+ d[0] = ':';
+ d++;
+ }
+ }
+ d[0] = '\0';
+ return hash;
+}
+
+static const char *qos_forwardedfor_fromHeader(request_rec *r, const char *header) {
+ const char *forwardedfor = apr_table_get(r->headers_in, header);
+ if(forwardedfor == NULL && r->prev) {
+ // internal redirect?
+ forwardedfor = apr_table_get(r->prev->headers_in, header);
+ }
+ if(forwardedfor == NULL && r->main) {
+ // internal redirect?
+ forwardedfor = apr_table_get(r->main->headers_in, header);
+ }
+ return forwardedfor;
+}
+
+static const char *qos_forwardedfor_fromSSL(request_rec *r) {
+ if(qos_ssl_var) {
+ const char *dn = qos_ssl_var(r->pool, r->server, r->connection, r, "SSL_CLIENT_S_DN");
+ const char *issuer = qos_ssl_var(r->pool, r->server, r->connection, r, "SSL_CLIENT_I_DN");
+ char *header = apr_pstrcat(r->pool, dn, issuer, NULL);
+ if(header && header[0]) {
+ return header;
+ }
+ }
+ return NULL;
+}
+
+static const char *qos_pseudoip(request_rec *r, const char *header) {
+ const char *forwardedfor = NULL;
+ if(strcmp("SSL_CLIENT_S_DN", header) == 0) {
+ forwardedfor = qos_forwardedfor_fromSSL(r);
+ } else {
+ forwardedfor = qos_forwardedfor_fromHeader(r, header);
+ }
+ if(forwardedfor && forwardedfor[0]) {
+ return qos_ipv6_hash(r, forwardedfor);
+ }
+ return NULL;
+}
+
+static const char *qos_forwardedfor(request_rec *r, const char *header) {
+ const char *forwardedfor = NULL;
+ if(header[0] == '#') {
+ forwardedfor = qos_pseudoip(r, &header[1]);
+ } else {
+ forwardedfor = qos_forwardedfor_fromHeader(r, header);
+ }
+ return forwardedfor;
+}
+
+/**
+ * Returns the client IP, either from the connection
+ * of from the forwarded-for header if configured
+ *
+ * @param r Request to get the IP from the header
+ * @param sconf
+ * @param cconf (if available) to get the real IP from
+ * @param caller Which caller of the method
+ * @param ip6 The client's IP address to be used (pointer to array of unsigned long (2))
+ * @return The client's IP address as a string (for logging)
+ */
+static const char *qos_get_clientIP(request_rec *r, qos_srv_config *sconf,
+ qs_conn_ctx *cconf, const char *caller,
+ apr_uint64_t *ip6) {
+ const char *forwardedForLogIP;
+ if(sconf->qos_cc_forwardedfor) {
+ const char *forwardedfor = qos_forwardedfor(r, sconf->qos_cc_forwardedfor);
+ if(forwardedfor) {
+ if(qos_ip_str2long(forwardedfor, ip6) == 0) {
+ if(apr_table_get(r->notes, "QOS_LOG_PFX069") == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(069)"no valid IP header found (@%s):"
+ " invalid header value '%s',"
+ " fallback to connection's IP %s, id=%s",
+ caller,
+ forwardedfor,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "069"));
+ apr_table_set(r->notes, "QOS_LOG_PFX069", "log once");
+ QS_INC_EVENT(sconf, 69);
+ }
+ } else {
+ // done
+ return forwardedfor;
+ }
+ } else {
+ if(apr_table_get(r->notes, "QOS_LOG_PFX069") == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(069)"no valid IP header found (@%s):"
+ " header '%s' not available,"
+ " fallback to connection's IP %s, id=%s",
+ caller,
+ sconf->qos_cc_forwardedfor,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "069"));
+ apr_table_set(r->notes, "QOS_LOG_PFX069", "log once");
+ QS_INC_EVENT(sconf, 69);
+ }
+ }
+ }
+ // use the real IP
+ if(cconf) {
+ forwardedForLogIP = QS_CONN_REMOTEIP(cconf->mc);
+ // works for real connections only (no HTTP/2)
+ ip6[0] = cconf->ip6[0];
+ ip6[1] = cconf->ip6[1];
+ } else {
+ // HTTP/2
+ forwardedForLogIP = QS_CONN_REMOTEIP(r->connection);
+ qos_ip_str2long(forwardedForLogIP, ip6);
+ }
+ return forwardedForLogIP;
+}
+
+/**
+ * returns the version number of mod_qos
+ * @param p Pool to alloc version string from
+ * @return Version string
+ */
+static char *qos_revision(apr_pool_t *p) {
+ return apr_pstrdup(p, g_revision);
+}
+
+/**
+ * returns the request context
+ */
+static qs_req_ctx *qos_rctx_config_get(request_rec *r) {
+ qs_req_ctx *rctx = ap_get_module_config(r->request_config, &qos_module);
+ if(rctx == NULL) {
+ rctx = apr_pcalloc(r->pool, sizeof(qs_req_ctx));
+ rctx->entry = NULL;
+ rctx->entry_cond = NULL;
+ rctx->evmsg = NULL;
+ rctx->is_vip = 0;
+ rctx->event_entries = apr_table_make(r->pool, 1);
+ rctx->maxpostcount = 0;
+ rctx->cc_event_req_set = 0;
+ rctx->cc_event_ip[0] = 0;
+ rctx->cc_event_ip[1] = 0;
+ rctx->cc_serialize_set = 0;
+ rctx->cc_serialize_ip[0] = 0;
+ rctx->cc_serialize_ip[1] = 0;
+ rctx->srv_serialize_set = 0;
+ rctx->body_window = NULL;
+ rctx->response_delayed = 0;
+ ap_set_module_config(r->request_config, &qos_module, rctx);
+ }
+ return rctx;
+}
+
+/**
+ * Adds the defined mod_qos_ev id to the request context (creates
+ * the req ctx if necessary).
+ *
+ * @param r
+ * @param id ID to set, e.g. "L;" or "D;"
+ */
+static void qs_set_evmsg(request_rec *r, const char *id) {
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ if(rctx->evmsg == NULL || (strstr(rctx->evmsg, id) == NULL)) {
+ rctx->evmsg = apr_pstrcat(r->pool, id, rctx->evmsg, NULL);
+ }
+}
+
+/**
+ * Encrypts and base64 encodes the provided buffer
+ * @param r
+ * @param sconf Secret to use (sconf->key)
+ * @param b Buffer to encrypt
+ * @param l Length of the buffer
+ * @return Encrypted string (or NULL on error)
+ */
+static char *qos_encrypt(request_rec *r, qos_srv_config *sconf,
+ const unsigned char *b, int l) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX cipher_ctx;
+ EVP_CIPHER_CTX *cipher_ctx_p = &cipher_ctx;
+ HMAC_CTX hmac;
+ HMAC_CTX *hmac_p = &hmac;
+#else
+ EVP_CIPHER_CTX *cipher_ctx_p;
+ HMAC_CTX *hmac_p;
+#endif
+ unsigned char hash[HMAC_MAX_MD_CBLOCK];
+ unsigned int hashLen = HMAC_MAX_MD_CBLOCK;
+ int buf_len = 0;
+ int len = 0;
+ unsigned char *buf = apr_pcalloc(r->pool, +
+ EVP_MAX_IV_LENGTH +
+ QOS_HASH_LEN +
+ l +
+ EVP_CIPHER_block_size(EVP_des_ede3_cbc()));
+
+#if APR_HAS_RANDOM
+ if(apr_generate_random_bytes(buf, EVP_MAX_IV_LENGTH) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ QOS_LOG_PFX(080)"Can't generate random data, id=%s",
+ qos_unique_id(r, NULL));
+ }
+#else
+ if(!RAND_bytes(buf, EVP_MAX_IV_LENGTH)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ QOS_LOG_PFX(080)"Can't generate random data, id=%s",
+ qos_unique_id(r, NULL));
+ }
+#endif
+
+ /* checksum */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_init(hmac_p);
+#else
+ hmac_p = HMAC_CTX_new();
+#endif
+#ifndef OPENSSL_NO_MD5
+ HMAC_Init_ex(hmac_p, sconf->rawKey, sconf->rawKeyLen, EVP_md5(), NULL);
+#else
+ HMAC_Init_ex(hmac_p, sconf->rawKey, sconf->rawKeyLen, EVP_sha256(), NULL);
+#endif
+ HMAC_Update(hmac_p, b, l);
+ HMAC_Final(hmac_p, hash, &hashLen);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_cleanup(hmac_p);
+#else
+ HMAC_CTX_free(hmac_p);
+#endif
+
+ /* sym enc */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_init(cipher_ctx_p);
+#else
+ cipher_ctx_p = EVP_CIPHER_CTX_new();
+#endif
+ EVP_EncryptInit(cipher_ctx_p, EVP_des_ede3_cbc(), sconf->key, (unsigned char *)buf);
+
+ // skip iv, enc(hash + data)
+ buf_len = EVP_MAX_IV_LENGTH;
+ if(!EVP_EncryptUpdate(cipher_ctx_p, &buf[buf_len], &len, hash, QOS_HASH_LEN)) {
+ goto failed;
+ }
+ buf_len+=len;
+ if(!EVP_EncryptUpdate(cipher_ctx_p, &buf[buf_len], &len, b, l)) {
+ goto failed;
+ }
+ buf_len+=len;
+ if(!EVP_EncryptFinal(cipher_ctx_p, &buf[buf_len], &len)) {
+ goto failed;
+ }
+ buf_len+=len;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_cleanup(cipher_ctx_p);
+#else
+ EVP_CIPHER_CTX_free(cipher_ctx_p);
+#endif
+
+ /* b64 encode */
+ {
+ char *data = (char *)apr_pcalloc(r->pool, 1 + apr_base64_encode_len(buf_len));
+ len = apr_base64_encode(data, (const char *)buf, buf_len);
+ data[len] = '\0';
+ return data;
+ }
+
+ failed:
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_cleanup(cipher_ctx_p);
+#else
+ EVP_CIPHER_CTX_free(cipher_ctx_p);
+#endif
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"qos_encrypt() encryption operation failed, id=%s",
+ qos_unique_id(r, NULL));
+ }
+ return NULL;
+}
+
+/**
+ * Decryptes the base64 encoded string (see qos_encrypt()).
+ * @param r
+ * @param sconf To access the secret
+ * @param ret_buf Pointer to the decypted data (result), NULL if decyption fails
+ * @param value Base64 encded string to decrypt
+ * @return Length of the decypted data (0 if decyption failed)
+ */
+static int qos_decrypt(request_rec *r, qos_srv_config* sconf,
+ unsigned char **ret_buf, const char *value) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX cipher_ctx;
+ EVP_CIPHER_CTX *cipher_ctx_p = &cipher_ctx;
+#else
+ EVP_CIPHER_CTX *cipher_ctx_p;
+#endif
+
+ /* decode */
+ char *dec = (char *)apr_pcalloc(r->pool, 1 + apr_base64_decode_len(value));
+ int dec_len = apr_base64_decode(dec, value);
+ *ret_buf = NULL;
+
+ if(dec_len < (EVP_MAX_IV_LENGTH + QOS_HASH_LEN)) {
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"qos_decrypt() base64 decoding failed, id=%s",
+ qos_unique_id(r, NULL));
+ }
+ return 0;
+ } else {
+ /* decrypt */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX hmac;
+ HMAC_CTX *hmac_p = &hmac;
+#else
+ HMAC_CTX *hmac_p;
+#endif
+ unsigned char hash[HMAC_MAX_MD_CBLOCK];
+ unsigned int hashLen = HMAC_MAX_MD_CBLOCK;
+ int len = 0;
+ int buf_len = 0;
+ unsigned char *buf;
+ dec_len -= EVP_MAX_IV_LENGTH;
+ buf = apr_pcalloc(r->pool, dec_len);
+
+ /* sym dec */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_init(cipher_ctx_p);
+#else
+ cipher_ctx_p = EVP_CIPHER_CTX_new();
+#endif
+ EVP_DecryptInit(cipher_ctx_p, EVP_des_ede3_cbc(), sconf->key, (unsigned char *)dec);
+ if(!EVP_DecryptUpdate(cipher_ctx_p, (unsigned char *)&buf[buf_len], &len,
+ (const unsigned char *)&dec[EVP_MAX_IV_LENGTH], dec_len)) {
+ goto failed;
+ }
+ buf_len+=len;
+ if(!EVP_DecryptFinal(cipher_ctx_p, (unsigned char *)&buf[buf_len], &len)) {
+ goto failed;
+ }
+ buf_len+=len;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_cleanup(cipher_ctx_p);
+#else
+ EVP_CIPHER_CTX_free(cipher_ctx_p);
+#endif
+
+ // hash + data
+ if(buf_len < (QOS_HASH_LEN + 1)) {
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"qos_decrypt() misshing hash, id=%s",
+ qos_unique_id(r, NULL));
+ }
+ return 0;
+ }
+
+ /* checksum */
+ buf_len -= QOS_HASH_LEN;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_init(hmac_p);
+#else
+ hmac_p = HMAC_CTX_new();
+#endif
+#ifndef OPENSSL_NO_MD5
+ HMAC_Init_ex(hmac_p, sconf->rawKey, sconf->rawKeyLen, EVP_md5(), NULL);
+#else
+ HMAC_Init_ex(hmac_p, sconf->rawKey, sconf->rawKeyLen, EVP_sha256(), NULL);
+#endif
+ HMAC_Update(hmac_p, &buf[QOS_HASH_LEN], buf_len);
+ HMAC_Final(hmac_p, hash, &hashLen);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_cleanup(hmac_p);
+#else
+ HMAC_CTX_free(hmac_p);
+#endif
+ if(hashLen > QOS_HASH_LEN) {
+ // we don't keep more than 16 bytes
+ hashLen = QOS_HASH_LEN;
+ }
+ if(memcmp(hash, buf, hashLen) != 0) {
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"qos_decrypt() invalid hash, id=%s",
+ qos_unique_id(r, NULL));
+ }
+ return 0;
+ }
+
+ /* decrypted and valid */
+ *ret_buf = &buf[QOS_HASH_LEN];
+ return buf_len;
+ }
+
+ failed:
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_CIPHER_CTX_cleanup(cipher_ctx_p);
+#else
+ EVP_CIPHER_CTX_free(cipher_ctx_p);
+#endif
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"qos_decrypt() decryption operation failed, id=%s",
+ qos_unique_id(r, NULL));
+ }
+ return 0;
+}
+
+/**
+ * Adds the user tracking cookie to r->headers_out if QOS_USER_TRACKING_NEW env variable
+ * has been set.
+ * @param r
+ * @param sconf
+ * @param status (302 or other)
+ */
+static void qos_send_user_tracking_cookie(request_rec *r, qos_srv_config* sconf,
+ int status) {
+ const char *new_user = apr_table_get(r->subprocess_env, QOS_USER_TRACKING_NEW);
+ if(new_user) {
+ char *setCookieVal;
+ apr_size_t retcode;
+ char timeString[MAX_STRING_LEN];
+ apr_time_exp_t timeEx;
+ int new_user_len = strlen(new_user);
+ int len = 2 + new_user_len;
+ unsigned char *value = apr_pcalloc(r->pool, len + 1);
+ char *encryptedVal;
+ char *domain = NULL;
+ apr_time_exp_gmt(&timeEx, r->request_time);
+ apr_strftime(timeString, &retcode, sizeof(timeString), "%m", &timeEx);
+#ifdef QS_INTERNAL_TEST
+ {
+ const char *m = apr_table_get(r->headers_in, "X-TEST-USER-TRACK-MONTH");
+ if(m) {
+ strcpy(timeString, m);
+ }
+ }
+#endif
+ memcpy(value, timeString, 2);
+ memcpy(&value[2], new_user, new_user_len);
+ value[len] = '\0';
+ encryptedVal = qos_encrypt(r, sconf, value, len + 1);
+ if(sconf->user_tracking_cookie_domain != NULL) {
+ domain = apr_pstrcat(r->pool, "; Domain=", sconf->user_tracking_cookie_domain, NULL);
+ }
+ /* set cookie valid for 300 days or for this session only */
+ setCookieVal = apr_psprintf(r->pool, "%s=%s; Path=/%s%s",
+ sconf->user_tracking_cookie, encryptedVal,
+ sconf->user_tracking_cookie_session < 1 ? "; Max-Age=25920000" : "",
+ domain != NULL ? domain : "");
+ if(status != HTTP_MOVED_TEMPORARILY) {
+ apr_table_add(r->headers_out, "Set-Cookie", setCookieVal);
+ } else {
+ apr_table_add(r->err_headers_out, "Set-Cookie", setCookieVal);
+ }
+ }
+ return;
+}
+
+/**
+ * Verifies and sets the user tracking cookie
+ * - QOS_USER_TRACKING if the cookie was available
+ * - QOS_USER_TRACKING_NEW if a new cookie needs to be set
+ * - QOS_USER_TRACKING_RENEW if the cookie shall be renewed
+ *
+ * syntax: b64(enc(<month><UNIQUE_ID>))
+ *
+ * shall be called after(!) mod_unique_id has created an id
+ *
+ * @param r
+ * @param sconf
+ * @param value Cookie received from the client, possibly null (see qos_get_remove_cookie())
+ */
+static void qos_get_create_user_tracking(request_rec *r, qos_srv_config* sconf,
+ const char *value) {
+ const char *uid = qos_unique_id(r, NULL);
+ const char *verified = NULL;
+ const char *newUID = NULL;
+ if(value != NULL) {
+ int buf_len = 0;
+ unsigned char *buf;
+ buf_len = qos_decrypt(r, sconf, &buf, value);
+ if(buf_len > 0) {
+ verified = (char *)buf;
+ }
+ }
+ if(verified == NULL) {
+ newUID = uid;
+ apr_table_set(r->subprocess_env, QOS_USER_TRACKING_NEW, newUID);
+ qs_set_evmsg(r, "u;");
+ } else if(strlen(verified) > 2) {
+ apr_size_t retcode;
+ char timeString[MAX_STRING_LEN];
+ apr_time_exp_t timeEx;
+ apr_time_exp_gmt(&timeEx, r->request_time);
+ apr_strftime(timeString, &retcode, sizeof(timeString), "%m", &timeEx);
+ if(strncmp(timeString, verified, 2) != 0) {
+ /* renew, if not from this month */
+ apr_table_set(r->subprocess_env, QOS_USER_TRACKING_NEW, &verified[2]);
+ apr_table_set(r->subprocess_env, QOS_USER_TRACKING_RENEW, "1");
+ }
+ newUID = &verified[2];
+ } else {
+ newUID = uid;
+ apr_table_set(r->subprocess_env, QOS_USER_TRACKING_NEW, newUID);
+ }
+ apr_table_set(r->subprocess_env, QOS_USER_TRACKING, newUID);
+ return;
+}
+
+/**
+ * Adds new milestone cookie to the response headers if QOS_MILESTONE_COOKIE
+ * has been set.
+ * See qos_verify_milestone() about the syntax.
+ */
+static void qos_update_milestone(request_rec *r, qos_srv_config* sconf) {
+ const char *new_ms = apr_table_get(r->subprocess_env, QOS_MILESTONE_COOKIE);
+ if(new_ms) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ int new_ms_len = strlen(new_ms);
+ int len = sizeof(apr_time_t) + new_ms_len;
+ unsigned char *value = apr_pcalloc(r->pool, len + 1);
+ char *encryptedVal;
+
+ apr_table_unset(r->subprocess_env, QOS_MILESTONE_COOKIE);
+ memcpy(value, &now, sizeof(apr_time_t));
+ memcpy(&value[sizeof(apr_time_t)], new_ms, new_ms_len);
+ value[len] = '\0';
+ encryptedVal = qos_encrypt(r, sconf, value, len);
+ apr_table_add(r->headers_out, "Set-Cookie",
+ apr_psprintf(r->pool, "%s=%s; Path=/;",
+ QOS_MILESTONE_COOKIE, encryptedVal));
+ }
+ return;
+}
+
+/**
+ * Verifies the milestone. Evaluates rule and enforces it. Does also set the
+ * QOS_MILESTONE_COOKIE variable if a new milestone has been reached.
+ *
+ * milestone cookie syntax: b64(enc(<time><milestone>))
+ *
+ * @param r
+ * @param sconf
+ * @param value Cookie received from the client (contains the already reached milestones)
+ * @return APR_SUCCESS if request is allowed, otherwise HTTP_FORBIDDEN
+ */
+
+static int qos_verify_milestone(request_rec *r, qos_srv_config* sconf, const char *value) {
+ char *the_request;
+ int the_request_len;
+ int escerr = 0;
+ qos_milestone_t *milestone = NULL;
+ qos_milestone_t *entries;
+ int i;
+ int ms = -1; // milestone the user has reached
+ int required = -1; // required for this request
+ apr_time_t age = 0; // milestone's age
+
+ if(value != NULL) {
+ int buf_len = 0;
+ unsigned char *buf;
+ buf_len = qos_decrypt(r, sconf, &buf, value);
+ if(buf_len >= (sizeof(apr_time_t) + 1)) {
+ apr_time_t *t = (apr_time_t *)buf;
+ apr_time_t now = apr_time_sec(r->request_time);
+ age = now - *t;
+ if(now <= (*t + sconf->milestoneTimeout)) {
+ ms = atoi((char *)&buf[sizeof(apr_time_t)]);
+ }
+ }
+ }
+ the_request = apr_pstrdup(r->pool, r->the_request);
+ the_request_len = qos_unescaping(the_request, QOS_DEC_MODE_FLAGS_URL, &escerr);
+ entries = (qos_milestone_t *)sconf->milestones->elts;
+ for(i = 0; i < sconf->milestones->nelts; ++i) {
+ milestone = &entries[i];
+ if(qos_regexec_len(r->pool, milestone->preg, the_request, the_request_len) == 0) {
+ required = milestone->num;
+ break;
+ }
+ }
+ if(milestone && (required >= 0)) {
+ int severity = milestone->action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
+ if(ms < (required - 1)) {
+ /* not allowed */
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(047)"access denied, reached milestone '%d' (%s),"
+ " user has already passed '%s',"
+ " action=%s, c=%s, id=%s",
+ required, milestone->pattern,
+ ms == -1 ? "none" : apr_psprintf(r->pool, "%d", ms),
+ !sconf->log_only && milestone->action == QS_DENY ?
+ "deny" : "log only (pass milestone)",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "047"));
+ QS_INC_EVENT(sconf, 47);
+ if(milestone->action == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ if(milestone->thinktime > 0) {
+ if(age < milestone->thinktime) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(147)"access denied, reached milestone '%d' (%s),"
+ " earlier than expected (right after %"APR_TIME_T_FMT" instead of %d seconds),"
+ " action=%s, c=%s, id=%s",
+ required, milestone->pattern,
+ age, milestone->thinktime,
+ !sconf->log_only && milestone->action == QS_DENY ?
+ "deny" : "log only (pass milestone)",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "147"));
+ QS_INC_EVENT(sconf, 147);
+ if(milestone->action == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+ if(required > ms) {
+ /* update milestone */
+ apr_table_set(r->subprocess_env, QOS_MILESTONE_COOKIE, apr_psprintf(r->pool, "%d", required));
+ }
+ }
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Extracts the cookie from the request.
+ * @param r
+ * @param cookieName Name of the cookie to remove from the request headers
+ * @param Cookie if available of NULL if not
+ */
+static char *qos_get_remove_cookie(request_rec *r, const char *cookieName) {
+ const char *cookieHdr = apr_table_get(r->headers_in, "cookie");
+ if(cookieHdr) {
+ char *cn = apr_pstrcat(r->pool, cookieName, "=", NULL);
+ char *pt = ap_strcasestr(cookieHdr, cn);
+ char *p = NULL;
+ while(pt && !p) {
+ // ensure we found the real cookie (and not an ending b64 str)
+ if(pt == cookieHdr) {
+ // @beginning of the header
+ p = pt;
+ pt = NULL;
+ } else {
+ char pre = pt[-1];
+ if(pre == ' ' ||
+ pre == ';') {
+ // @beginning of a cookie
+ p = pt;
+ pt = NULL;
+ } else {
+ // found patter somewhere else
+ pt++;
+ pt = ap_strcasestr(pt, cn);
+ }
+ }
+ }
+ if(p) {
+ char *sp = p;
+ char *value = NULL;
+ p[0] = '\0'; /* terminates the beginning of the cookie header */
+ sp--; /* deletes spaces "in front" of the qos cookie */
+ while((sp > cookieHdr) && (sp[0] == ' ')) {
+ sp[0] = '\0';
+ sp--;
+ }
+ p = &p[strlen(cn)];
+ value = ap_getword(r->pool, (const char **)&p, ';');
+ while(p && (p[0] == ' ')) p++;
+ /* skip a path, if there is any */
+ if(p && (strncasecmp(p, "$path=", strlen("$path=")) == 0)) {
+ ap_getword(r->pool, (const char **)&p, ';');
+ }
+ /* restore cookie header appending the part left*/
+ if(p && p[0]) {
+ if(cookieHdr[0]) {
+ if(p[0] == ' ') {
+ cookieHdr = apr_pstrcat(r->pool, cookieHdr, p, NULL);
+ } else {
+ cookieHdr = apr_pstrcat(r->pool, cookieHdr, " ", p, NULL);
+ }
+ } else {
+ cookieHdr = apr_pstrcat(r->pool, p, NULL);
+ }
+ }
+ if(strlen(cookieHdr) == 0) {
+ apr_table_unset(r->headers_in, "cookie");
+ } else {
+ if((strncasecmp(cookieHdr, "$Version=", strlen("$Version=")) == 0) &&
+ (strlen(cookieHdr) <= strlen("$Version=X; "))) {
+ /* nothing left */
+ apr_table_unset(r->headers_in, "cookie");
+ } else {
+ apr_table_set(r->headers_in, "cookie", cookieHdr);
+ }
+ }
+ return value;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * verifies the session cookie 0=failed, 1=succeeded
+ */
+static int qos_verify_session(request_rec *r, qos_srv_config* sconf) {
+ int buf_len = 0;
+ unsigned char *buf;
+ char *value = qos_get_remove_cookie(r, sconf->cookie_name);
+ if(value == NULL) {
+ return 0;
+ }
+ buf_len = qos_decrypt(r, sconf, &buf, value);
+ if(buf_len != sizeof(qos_session_t)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(021)"session cookie verification failed, "
+ "decoding failed, id=%s", qos_unique_id(r, "021"));
+ QS_INC_EVENT(sconf, 21);
+ return 0;
+ } else {
+ qos_session_t *s = (qos_session_t *)buf;
+ if(s->time < (apr_time_sec(r->request_time) - sconf->max_age)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(023)"session cookie verification failed, "
+ "expired, id=%s", qos_unique_id(r, "023"));
+ QS_INC_EVENT(sconf, 23);
+ return 0;
+ }
+ }
+ /* success */
+ apr_table_set(r->notes, QS_REC_COOKIE, "");
+ return 1;
+}
+
+/**
+ * set/update the session cookie
+ */
+static void qos_set_session(request_rec *r, qos_srv_config *sconf) {
+ qos_session_t *s = (qos_session_t *)apr_pcalloc(r->pool, sizeof(qos_session_t));
+ char *cookie;
+ char *session;
+ qs_set_evmsg(r, "V;"); // log VIP session creation
+ /* payload */
+ s->time = time(NULL);
+ session = qos_encrypt(r, sconf, (const unsigned char *)s, sizeof(qos_session_t));
+ if(session == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(025)"failed to create session cookie, id=%s",
+ qos_unique_id(r, "025"));
+ QS_INC_EVENT(sconf, 25);
+ return;
+ }
+ cookie = apr_psprintf(r->pool, "%s=%s; Path=%s; Max-Age=%d",
+ sconf->cookie_name, session,
+ sconf->cookie_path, sconf->max_age);
+ apr_table_add(r->headers_out,"Set-Cookie", cookie);
+ return;
+}
+
+/**
+ * destroy shared memory and mutexes
+ */
+static void qos_destroy_act(qs_actable_t *act) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ QOS_LOGD_PFX"cleanup shared memory: %"APR_SIZE_T_FMT" bytes",
+ act->size);
+ act->child_init = 0;
+ if(act->lock_file && act->lock_file[0]) {
+ /*
+ We don't need to destroy locks manually as
+ apr_global_mutex_create() registers the cleanup
+ method itself to the pool we used when allocating the
+ lock.
+ if(act->lock) {
+ apr_global_mutex_destroy(act->lock);
+ }
+ */
+ act->lock_file[0] = '\0';
+ act->lock_file = NULL;
+ }
+ /*
+ We don't need to destroy shared memory
+ manually as apr_shm_create() registers the
+ cleanup method to the pool we used when
+ allocating the memory.
+ if(act->m) {
+ apr_shm_destroy(act->m);
+ }
+ */
+ apr_pool_destroy(act->pool);
+}
+
+/**
+ * returns the persistent configuration (restarts)
+ */
+static qos_user_t *qos_get_user_conf(apr_pool_t *ppool) {
+ void *v;
+ qos_user_t *u;
+ apr_pool_userdata_get(&v, QS_USR_SPE, ppool);
+ u = v;
+ if(v) {
+ return v;
+ }
+ u = (qos_user_t *)apr_pcalloc(ppool, sizeof(qos_user_t));
+ u->server_start = 0;
+ u->act_table = apr_table_make(ppool, 2);
+ apr_pool_userdata_set(u, QS_USR_SPE, apr_pool_cleanup_null, ppool);
+ u->qos_cc = NULL;
+ return u;
+}
+
+/**
+ * increments the event counters for the specified event
+ * @param ppool Process pool to fetch user ctx from
+ * @param event Event number
+ * @param locked Set this to 1 if user ctx is already locked
+ */
+static void qs_inc_eventcounter(apr_pool_t *ppool, int event, int locked) {
+ qos_user_t *u = qos_get_user_conf(ppool);
+ if(u->qos_cc == NULL) {
+ return;
+ }
+ if(!locked) {
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT49 */
+ }
+ u->qos_cc->eventTotal[event]++;
+ u->qos_cc->eventLast[event]++;
+ if(!locked) {
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT49 */
+ }
+}
+
+static int qs_calc_maxClients(server_rec *bs, qos_srv_config *bsconf) {
+ int maxThreads = 0;
+ int maxDaemons = 0;
+ int maxClientsCalc = 0;
+ int maxClients = 0;
+
+ ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &maxDaemons);
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &maxThreads);
+ maxClientsCalc = (maxThreads == 0 ? 1 : maxThreads) * (maxDaemons == 0 ? 1 : maxDaemons);
+ if(m_event_mpm) {
+ /* QS_MaxClients = (AsyncRequestWorkerFactor + 1) * MaxRequestWorkers */
+ maxClientsCalc = (m_event_mpm_worker_factor + 1) * maxClientsCalc;
+ }
+ if(bsconf->max_clients_conf > 0) {
+ maxClients = bsconf->max_clients_conf;
+ } else {
+ maxClients = maxClientsCalc;
+ }
+ if(maxClients != maxClientsCalc) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(007)"calculated MaxClients/MaxRequestWorkers (max connections): %d,"
+ " applied limit: %d (QS_MaxClients)",
+ maxClientsCalc, maxClients);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"calculated MaxClients/MaxRequestWorkers (max connections): %d",
+ maxClients);
+ }
+ return maxClients;
+}
+
+#ifdef QS_APACHE_24
+/**
+ * tells if server is terminating immediately or not
+ */
+static int qos_is_graceful() {
+ if(ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_EXITING) {
+ return 0;
+ }
+ if(ap_state_query(AP_SQ_CONFIG_GEN) == 0) {
+ return 0;
+ }
+ return 1;
+}
+#else
+/**
+ * tells if server is terminating immediately or not
+ */
+static int qos_is_graceful() {
+ int mpm_gen = 0;
+ QOS_MY_GENERATION(mpm_gen);
+ if(mpm_gen != m_generation) return 1;
+ return 0;
+}
+#endif
+
+/* clear all counters of the per client data store at graceful restart
+ used to prevent counter grow due blocked/crashed client processes*/
+static void qos_clear_cc(qos_user_t *u) {
+ if(u->qos_cc) {
+ qos_s_entry_t **entry;
+ int i;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT37 */
+ u->qos_cc->connections = 0;
+ if(m_generation > 0) {
+ // this process generation must not update the connections counter anymore
+ u->qos_cc->generation_locked = m_generation;
+ }
+ entry = u->qos_cc->ipd;
+ for(i = 0; i < u->qos_cc->max; i++) {
+ (*entry)->event_req = 0;
+ (*entry)->serialize = 0;
+ entry++;
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT37 */
+ }
+}
+
+/**
+ * destroys the act
+ * shared memory must not be destroyed before graceful restart has
+ * been finished due running requests still need the shared memory
+ * till they have finished.
+ * keep the memory leak as little as possible ...
+ */
+static apr_status_t qos_cleanup_shm(void *p) {
+ qs_actable_t *act = p;
+ qos_user_t *u = qos_get_user_conf(act->ppool);
+ char *this_generation;
+ char *last_generation;
+ int i;
+ apr_table_entry_t *entry;
+
+ this_generation = apr_psprintf(act->ppool, "%d", m_generation);
+ last_generation = apr_psprintf(act->pool, "%d", m_generation-1);
+ qos_clear_cc(u);
+
+ /* delete acts from the last graceful restart */
+ entry = (apr_table_entry_t *)apr_table_elts(u->act_table)->elts;
+ for(i = 0; i < apr_table_elts(u->act_table)->nelts; i++) {
+ if(strcmp(entry[i].key, last_generation) == 0) {
+ qs_actable_t *a = (qs_actable_t *)entry[i].val;
+#ifdef QS_INTERNAL_TEST
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL,
+ QOS_LOGD_PFX"clear ACT generation '%s' at '%d'", last_generation, m_generation);
+#endif
+ qos_destroy_act(a);
+ }
+ }
+ apr_table_unset(u->act_table, last_generation);
+
+ if(qos_is_graceful()) {
+ /* don't delete this act now, but at next server restart ... */
+ apr_table_addn(u->act_table, this_generation, (char *)act);
+ } else {
+ if(u->qos_cc) {
+ qos_cc_free(u->qos_cc);
+ u->qos_cc = NULL;
+ }
+#ifdef QS_INTERNAL_TEST
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL,
+ QOS_LOGD_PFX"clear ACT generation 'current' at '%d'", m_generation);
+#endif
+ qos_destroy_act(act);
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * init the shared memory of the act
+ * act->serialize <- start
+ act->qsstatustimer <- start + sizeof(serialize)
+ * act->conn <- start + sizeof(serialize) + sizeof(time_t)
+ * act->conn->conn_ip <- start + sizeof(serialize) + sizeof(time_t) + sizeof(conn) * QS_MEM_SEG
+ * + [max_ip]
+ * act->entry <-
+ * + [rule_entries]
+ * act->event_limit <-
+ * + [event_limit_entries]
+ */
+static apr_status_t qos_init_shm(server_rec *s, qos_srv_config *sconf, qs_actable_t *act,
+ apr_table_t *locRuleCfgTable, int maxClients) {
+ char *file = "-";
+ apr_status_t res;
+ int i;
+ int ruleEntries = apr_table_elts(locRuleCfgTable)->nelts;
+ apr_table_entry_t *locRuleEntry = (apr_table_entry_t *)apr_table_elts(locRuleCfgTable)->elts;
+ int event_limit_entries = sconf->event_limit_a->nelts;
+ qs_acentry_t *actEntry = NULL;
+ int max_ip = maxClients;
+
+ act->size = ((max_ip+QS_DOUBLE_CONN) * QS_MEM_SEG * APR_ALIGN_DEFAULT(sizeof(qs_ip_entry_t))) +
+ (ruleEntries * APR_ALIGN_DEFAULT(sizeof(qs_acentry_t))) +
+ (event_limit_entries * APR_ALIGN_DEFAULT(sizeof(qos_event_limit_entry_t))) +
+ APR_ALIGN_DEFAULT(sizeof(qs_conn_t)) +
+ APR_ALIGN_DEFAULT(sizeof(qs_serial_t)) +
+ APR_ALIGN_DEFAULT(sizeof(time_t)) +
+ 2048;
+ /* use anonymous shm by default */
+ res = apr_shm_create(&act->m, act->size, NULL, act->pool);
+ if(APR_STATUS_IS_ENOTIMPL(res)) {
+ file = apr_psprintf(act->pool, "%s_m.mod_qos",
+ qos_tmpnam(act->pool, s));
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ apr_shm_remove(file, act->pool);
+#endif
+ res = apr_shm_create(&act->m, act->size, file, act->pool);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ QOS_LOGD_PFX"%s(%s), create shared memory (ACT)(%s): %"APR_SIZE_T_FMT" bytes"
+ " (r=%d,ip=%d)",
+ s->server_hostname == NULL ? "-" : s->server_hostname,
+ s->is_virtual ? "v" : "b",
+ file,
+ act->size,
+ ruleEntries, max_ip);
+ if(res != APR_SUCCESS) {
+ char buf[MAX_STRING_LEN];
+ apr_strerror(res, buf, sizeof(buf));
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
+ QOS_LOG_PFX(002)"failed to create shared memory (ACT)(%s): %s"
+ " (%"APR_SIZE_T_FMT" bytes)",
+ file, buf, act->size);
+ return res;
+ } else {
+ qs_serial_t *sp = apr_shm_baseaddr_get(act->m);
+ time_t *qsstatustimer = (time_t *)&sp[1];
+ qs_conn_t *connEntryP = (qs_conn_t *)&qsstatustimer[1];
+ qs_ip_entry_t *conn_ip = (qs_ip_entry_t *)&connEntryP[1];
+ apr_time_t now = apr_time_now();
+ act->serialize = sp;
+ act->serialize->q1 = 0;
+ act->serialize->q2 = 0;
+ act->serialize->locked = 0;
+ act->qsstatustimer = qsstatustimer;
+ *act->qsstatustimer = 0;
+ act->conn = connEntryP;
+ act->conn->conn_ip_len = (max_ip + QS_DOUBLE_CONN) * QS_MEM_SEG;
+ act->conn->conn_ip = conn_ip;
+ act->conn->max_client = max_ip;
+ act->conn->connections = 0;
+ for(i = 0; i < act->conn->conn_ip_len; i++) {
+ conn_ip->ip6[0] = 0;
+ conn_ip->ip6[1] = 0;
+ conn_ip->counter = 0;
+ conn_ip->error = 0;
+ conn_ip++;
+ }
+ if(ruleEntries) {
+ act->entry = (qs_acentry_t *)conn_ip;
+ actEntry = act->entry;
+ } else {
+ act->entry = NULL;
+ }
+ /* init rule entries (link data, init mutex) */
+ for(i = 0; i < ruleEntries; i++) {
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)locRuleEntry[i].val;
+ actEntry->next = &actEntry[1];
+ actEntry->id = i;
+ actEntry->url = rule->url;
+ actEntry->url_len = strlen(actEntry->url);
+ actEntry->event = rule->event;
+ if(actEntry->event) {
+ act->has_events++;
+ }
+ actEntry->regex = rule->regex;
+ actEntry->condition = rule->condition;
+ actEntry->regex_var = rule->regex_var;
+ actEntry->limit = rule->limit;
+ if(actEntry->limit == 0 ) {
+ if((actEntry->condition == NULL) && (actEntry->event == NULL)) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, s,
+ QOS_LOG_PFX(003)"request level rule %s has no "
+ "concurrent request limitations",
+ actEntry->url);
+ }
+ }
+ actEntry->kbytes_interval_us = now;
+ actEntry->interval = apr_time_sec(now);
+ actEntry->bytes = 0;
+ actEntry->req_per_sec_limit = rule->req_per_sec_limit;
+ actEntry->kbytes_per_sec_limit = rule->kbytes_per_sec_limit;
+ actEntry->kbytes_per_sec = 0;
+ actEntry->counter = 0;
+ actEntry->lock = act->lock;
+ if(i < ruleEntries - 1) {
+ actEntry = actEntry->next;
+ } else {
+ actEntry->next = NULL;
+ }
+ }
+ if(event_limit_entries == 0) {
+ act->event_entry = NULL;
+ } else {
+ // source (config) event limit array
+ qos_event_limit_entry_t *eventEntrySrc = (qos_event_limit_entry_t *)sconf->event_limit_a->elts;
+ // target (act) event limit array
+ qos_event_limit_entry_t *eventEntryDst;
+ if(actEntry) {
+ // end of the last act rule entry
+ act->event_entry = (qos_event_limit_entry_t *)&actEntry[1];
+ } else {
+ // end of the last connection entry
+ act->event_entry = (qos_event_limit_entry_t *)conn_ip;
+ }
+ eventEntryDst = act->event_entry;
+ // set config
+ for(i = 0; i < event_limit_entries; i++) {
+ eventEntryDst->env_var = eventEntrySrc->env_var;
+ eventEntryDst->eventDecStr = eventEntrySrc->eventDecStr;
+ eventEntryDst->max = eventEntrySrc->max;
+ eventEntryDst->seconds = eventEntrySrc->seconds;
+ eventEntryDst->limit = 0;
+ eventEntryDst->limitTime = 0;
+ eventEntryDst->action = eventEntrySrc->action;
+ eventEntryDst->condStr = eventEntrySrc->condStr;
+ eventEntryDst->preg = eventEntrySrc->preg;
+ eventEntryDst++;
+ eventEntrySrc++;
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * Loads the geo database. See QS_GEO_PATTERN about the file format.
+ * @param pool To allocate memory from
+ * @param geodb Data structore to initialize
+ * @param msg Error message if something went wrong while loading the db
+ * @param errors Number of errors
+ * @return APR_SUCCESS if data could be loaded resp. lines have been counted
+ */
+static apr_status_t qos_loadgeo(apr_pool_t *pool, qos_geo_t *geodb,
+ char **msg, int *errors) {
+ ap_regmatch_t ma[AP_MAX_REG_MATCH];
+ ap_regex_t *preg;
+ qos_geo_entry_t *entry = NULL;
+ qos_geo_entry_t *last = NULL;
+
+ int lines = 0;
+ char line[HUGE_STRING_LEN];
+ FILE *file;
+
+ preg = ap_pregcomp(pool, QS_GEO_PATTERN, AP_REG_EXTENDED);
+ if(preg == NULL) {
+ // internal error
+ *msg = apr_pstrdup(pool, "failed to compile regular"
+ " expression "QS_GEO_PATTERN);
+ (*errors)++;
+ return APR_INCOMPLETE;
+ }
+
+ file = fopen(geodb->path, "r");
+ if(!file) {
+ *msg = apr_psprintf(pool, "could not open file %s (%s)",
+ geodb->path, strerror(errno));
+ (*errors)++;
+ return APR_INCOMPLETE;
+ }
+
+ // check syntax and determine required memory size
+ while(fgets(line, sizeof(line), file) != NULL) {
+ if(strlen(line) > 0) {
+ if(ap_regexec(preg, line, 0, NULL, 0) == 0) {
+ lines++;
+ } else {
+ *msg = apr_psprintf(pool, "invalid entry in database: '%s'", line);
+ (*errors)++;
+ }
+ }
+ }
+ if(*errors != 0) {
+ return APR_INCOMPLETE;
+ }
+
+ geodb->size = lines;
+ geodb->data = apr_pcalloc(pool, APR_ALIGN_DEFAULT(sizeof(qos_geo_entry_t)) * geodb->size);
+
+ // load the file into the memory
+ entry = geodb->data;
+ fseek(file, 0, SEEK_SET);
+ lines = 0;
+ while(fgets(line, sizeof(line), file) != NULL) {
+ lines++;
+ if(strlen(line) > 0) {
+ if(ap_regexec(preg, line, AP_MAX_REG_MATCH, ma, 0) == 0) {
+ line[ma[1].rm_eo] = '\0';
+ line[ma[2].rm_eo] = '\0';
+ line[ma[3].rm_eo] = '\0';
+ entry->start = atoll(&line[ma[1].rm_so]);
+ entry->end = atoll(&line[ma[2].rm_so]);
+ strncpy(entry->country, &line[ma[3].rm_so], 2);
+ if(last) {
+ if(entry->start < last->start) {
+ *msg = apr_psprintf(pool, "wrong order/lines not sorted (line %d)",
+ lines);
+ (*errors)++;
+ }
+ }
+ last = entry;
+ entry++;
+ }
+ }
+ }
+
+ fclose(file);
+ if(*errors == 0) {
+ return APR_SUCCESS;
+ } else {
+ return APR_INCOMPLETE;
+ }
+}
+
+/**
+ * Verifies if the string is a number
+ * @param num Number to test
+ * @param 1 if numeric (0 if not)
+ */
+static int qos_is_num(const char *num) {
+ int i = 0;
+ while(num[i]) {
+ if(!isdigit(num[i])) {
+ return 0;
+ }
+ i++;
+ }
+ return 1;
+}
+
+/**
+ * Helper for the status viewer (unsigned long to char).
+ */
+static void qos_collect_ip(request_rec *r, qos_srv_config *sconf,
+ apr_table_t *entries, int limit,
+ int html) {
+ int i = sconf->act->conn->conn_ip_len;
+ qs_ip_entry_t *conn_ip = sconf->act->conn->conn_ip;
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT8 */
+ while(i) {
+ if(conn_ip->ip6[0] || conn_ip->ip6[1]) {
+ char *red = "style=\"background-color: rgb(240,153,155);\"";
+ if(html) {
+ apr_table_addn(entries, apr_psprintf(r->pool, "%s</td><td %s colspan=\"3\">%d",
+ qos_ip_long2str(r->pool, conn_ip->ip6),
+ ((limit != -1) && conn_ip->counter >= limit) ? red : "",
+ conn_ip->counter), "");
+ } else {
+ apr_table_addn(entries, qos_ip_long2str(r->pool, conn_ip->ip6), apr_psprintf(r->pool, "%d", conn_ip->counter));
+ }
+ }
+ conn_ip++;
+ i--;
+ }
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT8 */
+}
+
+
+/**
+ * Count's the number of free ip entries (for the status viewer only)
+ */
+static int qos_count_free_ip(qos_srv_config *sconf) {
+ int c = sconf->act->conn->max_client;
+ int i = sconf->act->conn->conn_ip_len;
+ qs_ip_entry_t *conn_ip = sconf->act->conn->conn_ip;
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT7 */
+ while(i) {
+ if((conn_ip->ip6[0] != 0) ||
+ (conn_ip->ip6[1] != 0)) {
+ c--;
+ }
+ conn_ip++;
+ i--;
+ }
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT7 */
+ return c > 0 ? c : 0;
+}
+
+/**
+ * Checks the subprocess_env table for
+ * event variable and returns its numeric value.
+ *
+ * @param r
+ * @param variable Name of the variable to lookup
+ * @return Number within the variable or 0 if not set
+ */
+static int get_qs_event(request_rec *r, const char *variable) {
+ const char *eventStr = apr_table_get(r->subprocess_env, variable);
+ if(eventStr == NULL) {
+ return 0;
+ }
+ if(qos_is_num(eventStr) && (strlen(eventStr) > 0)) {
+ int num = atoi(eventStr);
+ if(num <= 0) {
+ num = 1;
+ }
+ return num;
+ }
+ return 1;
+}
+
+/**
+ * adds an ip entry (insert or increment)
+ *
+ * @param sconf
+ * @param cconf Configuration record containing the ip table(s)
+ * @param e Pointer to the IP entry
+ * NOTE: we can't sort the list since the address of this pointer
+ * must not be change (we don't keep the lock)
+ * @return The number of connections open by this IP
+ */
+static int qos_inc_ip(qos_srv_config *sconf,
+ qs_conn_ctx *cconf, qs_ip_entry_t **e) {
+ int num = -1;
+ qs_ip_entry_t *free = NULL;
+ int i = cconf->sconf->act->conn->conn_ip_len / QS_MEM_SEG; // size of the array
+ int seqnum = (cconf->ip6[1] % QS_MEM_SEG) * i; // array offset
+ qs_ip_entry_t *conn_ip = cconf->sconf->act->conn->conn_ip;
+ conn_ip = &conn_ip[seqnum]; // address of the first entry
+
+ apr_global_mutex_lock(cconf->sconf->act->lock); /* @CRT1 */
+
+ // search the whole list (until we find an exiting entry for this ip)
+ while(i) {
+ if((conn_ip->ip6[0] == 0) &&
+ (conn_ip->ip6[1] == 0) &&
+ (free == NULL)) {
+ // first free entry
+ free = conn_ip;
+ }
+ if((conn_ip->ip6[0] == cconf->ip6[0]) &&
+ (conn_ip->ip6[1] == cconf->ip6[1])) {
+ // found an existing entry
+ conn_ip->counter++;
+ num = conn_ip->counter;
+ *e = conn_ip;
+ break;
+ }
+ conn_ip++;
+ i--;
+ }
+ if(num == -1) {
+ // no entry found, use the first free entry
+ if(free) {
+ free->ip6[0] = cconf->ip6[0];
+ free->ip6[1] = cconf->ip6[1];
+ free->counter++;
+ num = free->counter;
+ *e = free;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, sconf->base_server,
+ QOS_LOG_PFX(035)"QS_SrvMaxConn: no free IP slot available!"
+ " Check log for unclean child exit and consider"
+ " to do a graceful server restart if this condition persists."
+ " You might also increase the number of supported connections"
+ " using the 'QS_MaxClients' directive.");
+ QS_INC_EVENT_LOCKED(sconf, 35);
+ }
+ }
+
+ apr_global_mutex_unlock(cconf->sconf->act->lock); /* @CRT1 */
+ return num;
+}
+
+/**
+ * removes an ip entry (deletes/decrements)
+ */
+static void qos_dec_ip(qs_conn_ctx *cconf) {
+ int i = cconf->sconf->act->conn->conn_ip_len / QS_MEM_SEG;
+ int seqnum = (cconf->ip6[1] % QS_MEM_SEG) * i;
+ qs_ip_entry_t *conn_ip = cconf->sconf->act->conn->conn_ip;
+ conn_ip = &conn_ip[seqnum];
+ apr_global_mutex_lock(cconf->sconf->act->lock); /* @CRT2 */
+ while(i) {
+ if((conn_ip->ip6[0] == cconf->ip6[0]) &&
+ (conn_ip->ip6[1] == cconf->ip6[1])) {
+ // entry found, decrement and exit
+ conn_ip->counter--;
+ if(conn_ip->counter == 0) {
+ // entry is no longer used by this ip
+ conn_ip->ip6[0] = 0;
+ conn_ip->ip6[1] = 0;
+ conn_ip->error = 0;
+ }
+ break;
+ }
+ conn_ip++;
+ i--;
+ }
+ apr_global_mutex_unlock(cconf->sconf->act->lock); /* @CRT2 */
+}
+
+static apr_status_t qos_cleanup_inctx(void *p) {
+ qos_ifctx_t *inctx = p;
+ qos_srv_config *sconf = inctx->sconf;
+#if APR_HAS_THREADS
+ if(sconf && sconf->inctx_t && !sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT25 */
+ inctx->status = QS_CONN_STATE_DESTROY;
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT25 */
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+/**
+ * creates a new connection ctx (remember to set the socket, connection and timeout)
+ */
+static qos_ifctx_t *qos_create_ifctx(conn_rec *connection, qos_srv_config *sconf) {
+ conn_rec *c = connection;
+ char buf[128];
+ qos_ifctx_t *inctx = apr_pcalloc(c->pool, sizeof(qos_ifctx_t));
+ inctx->clientSocket = NULL;
+ inctx->status = QS_CONN_STATE_NEW;
+ inctx->cl_val = 0;
+ inctx->c = c;
+ inctx->r = NULL;
+ inctx->clientSocket = NULL;
+ inctx->time = 0;
+ inctx->nbytes = 0;
+ inctx->hasBytes = 0;
+ inctx->shutdown = 0;
+ inctx->disabled = 0;
+ inctx->lowrate = -1;
+ sprintf(buf, "%p", inctx);
+ inctx->id = apr_psprintf(c->pool, "%s%.16lx", buf, c->id);
+ inctx->sconf = sconf;
+ apr_pool_pre_cleanup_register(c->pool, inctx, qos_cleanup_inctx);
+ return inctx;
+}
+
+/**
+ * returns the context from the r->connection->input_filters
+ */
+static qos_ifctx_t *qos_get_ifctx(ap_filter_t *f) {
+ qos_ifctx_t *inctx = NULL;
+ while(f) {
+ if(strcmp(f->frec->name, "qos-in-filter") == 0) {
+ inctx = f->ctx;
+ break;
+ }
+ f = f->next;
+ }
+ return inctx;
+}
+
+static qs_conn_base_ctx *qos_create_conn_base_ctx(conn_rec *connection, qos_srv_config *sconf) {
+ conn_rec *c = connection;
+ qs_conn_base_ctx *base = apr_pcalloc(c->pool, sizeof(qs_conn_base_ctx));
+ base->cconf = NULL;
+ base->requests = 0;
+ base->c = c;
+ base->sconf = sconf;
+ base->clientSocket = NULL;
+ ap_set_module_config(c->conn_config, &qos_module, base);
+ apr_pool_pre_cleanup_register(c->pool, base, qos_base_cleanup_conn);
+ return base;
+}
+
+static qs_conn_base_ctx *qos_get_conn_base_ctx(conn_rec *connection) {
+ conn_rec *c = connection;
+ qs_conn_base_ctx *base = (qs_conn_base_ctx*)ap_get_module_config(c->conn_config, &qos_module);
+ return base;
+}
+
+/**
+ * send server error, used for connection errors
+ */
+static int qos_return_error_andclose(conn_rec *connection, apr_socket_t *socket) {
+ conn_rec *c = connection;
+ char *line = apr_pstrcat(c->pool, AP_SERVER_PROTOCOL, " ",
+ ap_get_status_line(500), CRLF CRLF, NULL);
+ apr_bucket *e = apr_bucket_pool_create(line, strlen(line), c->pool, c->bucket_alloc);
+ apr_bucket_brigade *bb = apr_brigade_create(c->pool, c->bucket_alloc);
+
+ c->keepalive = AP_CONN_CLOSE;
+ c->aborted = 1;
+ if(c->cs) {
+ c->cs->state = CONN_STATE_LINGER;
+ }
+ apr_table_set(c->notes, QS_CONN_ABORT, QS_CONN_ABORT);
+ if (m_forced_close == 0) {
+ return DECLINED;
+ }
+
+ //apr_brigade_cleanup(bb);
+ APR_BRIGADE_INSERT_HEAD(bb, e);
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_pass_brigade(c->output_filters, bb);
+
+// if(socket) {
+// // speed up connection termination
+// qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
+//#ifdef QS_INTERNAL_TEST
+// struct timespec delay;
+// delay.tv_sec = 0;
+// delay.tv_nsec = 1000000; // 1ms to allow testing
+// nanosleep(&delay, NULL);
+//#endif
+// apr_socket_shutdown(socket, APR_SHUTDOWN_READ);
+// if(inctx) {
+// qos_cleanup_inctx(inctx);
+// }
+// }
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+/**
+ * returns custom error page
+ */
+static int qos_error_response(request_rec *r, const char *error_page) {
+ if(r->subprocess_env) {
+ const char *v = apr_table_get(r->subprocess_env, "QS_ErrorPage");
+ if(v) {
+ error_page = v;
+ }
+ }
+
+ if(error_page) {
+ /* do (almost) the same as ap_die() does */
+ const char *error_notes;
+ r->status = m_retcode;
+ r->connection->keepalive = AP_CONN_CLOSE;
+ r->no_local_copy = 1;
+ apr_table_setn(r->subprocess_env, "REQUEST_METHOD", r->method);
+ if ((error_notes = apr_table_get(r->notes,
+ "error-notes")) != NULL) {
+ apr_table_setn(r->subprocess_env, "ERROR_NOTES", error_notes);
+ }
+ /* external or internal redirect */
+ if(strncasecmp(error_page, "http", 4) == 0) {
+ apr_table_set(r->headers_out, "Location", error_page);
+ return HTTP_MOVED_TEMPORARILY;
+ } else {
+ r->method = apr_pstrdup(r->pool, "GET");
+ r->method_number = M_GET;
+ ap_internal_redirect(error_page, r);
+ return DONE;
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * returns the matching regex with the lowest limitation
+ */
+static qs_acentry_t *qos_getrule_byregex(request_rec *r, qos_srv_config *sconf) {
+ qs_acentry_t *ret = NULL;
+ qs_actable_t *act = sconf->act;
+ qs_acentry_t *actEntry = act->entry;
+ int limit = -1;
+ while(actEntry) {
+ if((actEntry->event == NULL) && (actEntry->regex != NULL) && (actEntry->condition == NULL)) {
+ if((limit == -1) || (actEntry->limit < limit)) {
+ if(ap_regexec(actEntry->regex, r->unparsed_uri, 0, NULL, 0) == 0) {
+ if(limit == -1) {
+ ret = actEntry;
+ limit = actEntry->limit;
+ } else if(actEntry->limit < limit) {
+ ret = actEntry;
+ limit = actEntry->limit;
+ }
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ return ret;
+}
+
+/**
+ * returns the matching conditional regex with the lowest limitation
+ */
+static qs_acentry_t *qos_getcondrule_byregex(request_rec *r, qos_srv_config *sconf) {
+ qs_acentry_t *ret = NULL;
+ qs_actable_t *act = sconf->act;
+ qs_acentry_t *actEntry = act->entry;
+ int limit = -1;
+ while(actEntry) {
+ if((actEntry->event == NULL) && (actEntry->regex != NULL) && (actEntry->condition != NULL)) {
+ if((limit == -1) || (actEntry->limit < limit)) {
+ if(ap_regexec(actEntry->regex, r->unparsed_uri, 0, NULL, 0) == 0) {
+ if(limit == -1) {
+ ret = actEntry;
+ limit = actEntry->limit;
+ } else if(actEntry->limit < limit) {
+ ret = actEntry;
+ limit = actEntry->limit;
+ }
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ return ret;
+}
+
+/**
+ * returns the best matching location entry
+ */
+static qs_acentry_t *qos_getrule_bylocation(request_rec * r, qos_srv_config *sconf) {
+ qs_acentry_t *ret = NULL;
+ qs_actable_t *act = sconf->act;
+ qs_acentry_t *actEntry = act->entry;
+ int match_len = 0;
+ while(actEntry) {
+ if((actEntry->event == NULL) && (actEntry->regex == NULL)) {
+ /* per location limitation */
+ if(actEntry->url && (strncmp(actEntry->url, r->parsed_uri.path, actEntry->url_len) == 0)) {
+ /* best match */
+ if(actEntry->url_len > match_len) {
+ match_len = actEntry->url_len;
+ ret = actEntry;
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ return ret;
+}
+
+/**
+ * checks for VIP user (may pass restrictions)
+ */
+static int qos_is_vip(request_rec *r, qos_srv_config *sconf) {
+ if(qos_verify_session(r, sconf)) {
+ apr_table_set(r->subprocess_env, QS_VipRequest, "yes");
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ return 1;
+ }
+ if(r->subprocess_env) {
+ const char *v = apr_table_get(r->subprocess_env, QS_VipRequest);
+ if(v && (strcasecmp(v, "yes") == 0)) {
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * writes the parp table to a single query line
+ */
+static const char *qos_parp_query(request_rec *r, apr_table_t *tl, const char *add) {
+ int add_len = 0;
+ char *query = NULL;
+ int len = 0;
+ char *p;
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(tl)->elts;
+ for(i = 0; i < apr_table_elts(tl)->nelts; i++) {
+ len = len +
+ (entry[i].key == NULL ? 0 : strlen(entry[i].key)) +
+ (entry[i].val == NULL ? 0 : strlen(entry[i].val)) +
+ 2;
+ }
+ if(add && add[0]) {
+ add_len = strlen(add);
+ len = len + add_len + 1;
+ }
+ query = apr_pcalloc(r->pool, len + 2);
+ query[0] = '?';
+ if(add_len) {
+ memcpy(&query[1], add, add_len);
+ p = &query[add_len];
+ } else {
+ p = &query[1];
+ }
+ p[0] = '\0';
+ for(i = 0; i < apr_table_elts(tl)->nelts; i++) {
+ int l = strlen(entry[i].key);
+ if(p != &query[1]) {
+ p[0] = '&';
+ p++;
+ p[0] = '\0';
+ }
+ memcpy(p, entry[i].key, l);
+ p += l;
+ p[0] = '=';
+ p++;
+ l = strlen(entry[i].val);
+ memcpy(p, entry[i].val, l);
+ p += l;
+ p[0] = '\0';
+ }
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), query);
+ return &query[1];
+}
+
+/* filter events */
+static int qos_per_dir_event_rules(request_rec *r, qos_srv_config *sconf,
+ qos_dir_config *dconf) {
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(dconf->rfilter_table)->elts;
+ int i;
+ for(i = 0; i < apr_table_elts(dconf->rfilter_table)->nelts; i++) {
+ if(entry[i].key[0] == '+') {
+ int deny_rule = 0;
+ int ex = -1;
+ qos_rfilter_t *rfilter = (qos_rfilter_t *)entry[i].val;
+ if(rfilter->type == QS_DENY_EVENT) {
+ deny_rule = 1;
+ if(rfilter->text[0] == '!') {
+ if(apr_table_get(r->subprocess_env, &rfilter->text[1]) == NULL) {
+ ex = 0;
+ }
+ } else {
+ if(apr_table_get(r->subprocess_env, rfilter->text) != NULL) {
+ ex = 0;
+ }
+ }
+ }
+ if(deny_rule && (ex == 0)) {
+ int severity = rfilter->action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(040)"access denied, %s rule id: %s (%s),"
+ " action=%s, c=%s, id=%s",
+ qos_rfilter_type2text(r->pool, rfilter->type),
+ rfilter->id,
+ rfilter->text,
+ (!sconf->log_only) && (rfilter->action == QS_DENY) ? "deny" : "log only",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "040"));
+ QS_INC_EVENT(sconf, 40);
+ if(rfilter->action == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/* json parser start ------------------------------------------------------- */
+#define QOS_J_ERROR "HTTP_BAD_REQUEST QOS JSON PARSER: FORMAT ERROR"
+#define QOS_j_RECURSION 80
+
+static int j_val(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec);
+
+static char *j_escape_url(apr_pool_t *pool, const char *c) {
+ char buf[4];
+ char special[] = " \t()<>@,;:\\/[]?={}\"'&%+";
+ char *r = apr_pcalloc(pool, 3 * strlen(c));
+ const char *p = c;
+ int i = 0;
+ while(p && p[0]) {
+ char c = p[0];
+ if(!apr_isprint(c) || strchr(special, c)) {
+ sprintf(buf, "%02x", p[0]);
+ r[i] = '%'; i++;
+ r[i] = buf[0]; i++;
+ r[i] = buf[1]; i++;
+ } else {
+ r[i] = c;
+ i++;
+ }
+ p++;
+ }
+ return r;
+}
+
+static char *j_strchr(char *data, char d) {
+ char *q = data;
+ if(!q) {
+ return NULL;
+ }
+ if(q[0] == d) {
+ return q;
+ }
+ while(q[0]) {
+ if((q[0] == d) && (q[-1] != '\\')) {
+ return q;
+ }
+ q++;
+ }
+ return NULL;
+}
+
+static char *j_skip(char *in) {
+ if(!in) return NULL;
+ while(in[0] && ((in[0] == ' ') ||
+ (in[0] == '\t') ||
+ (in[0] == '\r') ||
+ (in[0] == '\n') ||
+ (in[0] == '\f'))) {
+ in++;
+ }
+ return in;
+}
+
+static int j_string(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, char **n) {
+ char *d = *val;
+ char *v = d;
+ char *end = j_strchr(d, '"');
+ if(!end) {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing string (no ending double quote)");
+ return HTTP_BAD_REQUEST;
+ }
+ end[0] = '\0';
+ end++;
+ *val = j_skip(end);
+ /* TODO, improve string format validation */
+ while(v[0]) {
+ if(v[0] < ' ') {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing string (invalid character)");
+ return HTTP_BAD_REQUEST;
+ }
+ v++;
+ }
+ *n = d;
+ return APR_SUCCESS;
+}
+
+static int j_num(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, char **n) {
+ char *s = *val;
+ char *d = *val;
+ while(d && ((d[0] >= '0' && d[0] <= '9') ||
+ d[0] == '.' ||
+ d[0] == 'e' ||
+ d[0] == 'E' ||
+ d[0] == '+' ||
+ d[0] == '-')) {
+ d++;
+ }
+ *n = apr_pstrndup(pool, s, d-s);
+ *val = d;
+ return APR_SUCCESS;
+}
+
+static int j_obj(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
+ char *d = j_skip(*val);
+ int rc;
+ while(d && d[0]) {
+ if(*d != '\"') {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing object (missing string)");
+ return HTTP_BAD_REQUEST;
+ } else {
+ /* list of string ":" value pairs (sepated by ',') */
+ char *v = NULL;
+ char *thisname;
+ d++;
+ rc = j_string(pool, &d, tl, name, &v);
+ if(rc != APR_SUCCESS) {
+ return rc;
+ }
+ thisname = apr_pstrcat(pool, name, "_" , v, NULL);
+ d = j_skip(d);
+ if(!d || d[0] != ':') {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing object (missing value/wrong delimiter)");
+ return HTTP_BAD_REQUEST;
+ }
+ d++;
+ rc = j_val(pool, &d, tl, thisname, rec);
+ if(rc != APR_SUCCESS) {
+ return rc;
+ }
+ d = j_skip(d);
+ if(!d) {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing object (unexpected end)");
+ return HTTP_BAD_REQUEST;
+ }
+ if(d[0] == '}') {
+ d++;
+ *val = d;
+ return APR_SUCCESS;
+ } else if(d[0] == ',') {
+ d = j_strchr(d, '"');
+ } else {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing object (unexpected end/wrong delimiter)");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static int j_ar(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
+ char *d = j_skip(*val);
+ int rc;
+ int index = 0;
+ while(d && d[0]) {
+ rc = j_val(pool, &d, tl, apr_psprintf(pool, "%s%d", name, index), rec);
+ if(rc != APR_SUCCESS) {
+ return rc;
+ }
+ d = j_skip(d);
+ if(!d) {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing array (unexpected end)");
+ return HTTP_BAD_REQUEST;
+ }
+ if(d[0] == ']') {
+ d++;
+ *val = d;
+ return APR_SUCCESS;
+ } else if(d[0] == ',') {
+ d++;
+ d = j_skip(d);
+ } else {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing array (unexpected end/wrong delimiter)");
+ return HTTP_BAD_REQUEST;
+ }
+ index++;
+ }
+ return APR_SUCCESS;
+}
+
+static int j_val(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
+ char *d = j_skip(*val);
+ int rc = APR_SUCCESS;
+ rec++;
+ if(rec > QOS_j_RECURSION) {
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing string (reached recursion limit)");
+ return HTTP_BAD_REQUEST;
+ }
+ /* either object, array, string, number, "true", "false", or "null" */
+ if(d[0] == '{') {
+ d++;
+ rc = j_obj(pool, &d, tl, apr_pstrcat(pool, name, "_o", NULL), rec);
+ } else if(d[0] == '[') {
+ d++;
+ rc = j_ar(pool, &d, tl, apr_pstrcat(pool, name, "_a", NULL), rec);
+ } else if(strncmp(d,"null",4) == 0) {
+ d+=4;
+ apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "null");
+ } else if(strncmp(d,"true",4) == 0) {
+ apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "true");
+ d+=4;
+ } else if(strncmp(d,"false",5) == 0) {
+ apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "false");
+ d+=5;
+ } else if(*d == '-' || (*d >= '0' && *d <= '9')) {
+ char *n = apr_pstrcat(pool, name, "_n", NULL);
+ char *v = NULL;
+ rc = j_num(pool, &d, tl, n, &v);
+ if(rc == APR_SUCCESS) {
+ apr_table_addn(tl, j_escape_url(pool, n), j_escape_url(pool, v));
+ }
+ } else if(*d == '\"') {
+ char *n = apr_pstrcat(pool, name, "_v", NULL);
+ char *v = NULL;
+ d++;
+ rc = j_string(pool, &d, tl, n, &v);
+ if(rc == APR_SUCCESS) {
+ apr_table_addn(tl, j_escape_url(pool, n), j_escape_url(pool, v));
+ }
+ } else {
+ /* error */
+ apr_table_add(tl, QOS_J_ERROR, "error while parsing value (invalid type)");
+ return HTTP_BAD_REQUEST;
+ }
+ if(rc != APR_SUCCESS) {
+ return rc;
+ }
+ *val = d;
+ rec--;
+ return APR_SUCCESS;
+}
+/* json parser end --------------------------------------------------------- */
+
+/**
+ * Process json data retrieved from parp (request body)
+ * @param r
+ * @param dconf
+ * @param query Query to add data
+ * @param msg Error message if paring fails
+ * @return APR_SUCCESS if processed without errors.
+ */
+static int qos_json(request_rec *r, qos_dir_config *dconf, const char **query, const char **msg) {
+ const char *contenttype = apr_table_get(r->headers_in, "Content-Type");
+ if(contenttype && (strncasecmp(contenttype, "application/json", 16) == 0)) {
+ apr_size_t len = 0;
+ const char *data = NULL;
+ /* check if parp has body data to process (requires "PARP_BodyData application/json")
+ or if the json message is stored within the query */
+ if(qos_parp_body_data_fn) {
+ data = qos_parp_body_data_fn(r, &len);
+ }
+ if(data == NULL) {
+ data = *query;
+ if(data && (data[0] == '[' || data[0] == '{')) {
+ int escerr = 0;
+ char *copyq = apr_pstrdup(r->pool, data);
+ *query = NULL;
+ // the query needs to be unescaped before getting parsed
+ len = qos_unescaping(copyq, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_path_decode_hook(r, &copyq, &len);
+#endif
+ data = copyq;
+ if(strlen(data) != len) {
+ *msg = apr_pstrdup(r->pool, "null character within data structure in query");
+ return HTTP_BAD_REQUEST;
+ }
+ } else {
+ // does not look like a json structure (strict)
+ data = NULL;
+ }
+ }
+ if(data && (len > 0)) {
+ char *value = apr_pstrndup(r->pool, data, len);
+ apr_table_t *tl = apr_table_make(r->pool, 200);
+ int rc;
+ if(strlen(value) != len) {
+ *msg = apr_pstrdup(r->pool, "null character within data structure");
+ return HTTP_BAD_REQUEST;
+ }
+ rc = j_val(r->pool, &value, tl, "J", 0);
+ if(rc != APR_SUCCESS) {
+ *msg = apr_table_get(tl, QOS_J_ERROR);
+ apr_table_unset(tl, QOS_J_ERROR);
+ return rc;
+ }
+ if(value && value[0]) {
+ value = j_skip(value);
+ if(value && value[0]) {
+ /* error, there is still some data */
+ *msg = apr_pstrdup(r->pool, "more than one element");
+ }
+ }
+ *query = qos_parp_query(r, tl, *query);
+ if(*query) {
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), *query);
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * processes the per location rules QS_Permit* and QS_Deny*
+ */
+static int qos_per_dir_rules(request_rec *r, qos_srv_config *sconf,
+ qos_dir_config *dconf) {
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(dconf->rfilter_table)->elts;
+ int i;
+ char *path = apr_pstrdup(r->pool, r->parsed_uri.path);
+ char *query = NULL;
+ char *fragment = NULL;
+ char *request_line = apr_pstrdup(r->pool, r->the_request);
+ char *uri = path;
+ int request_line_len;
+ int path_len;
+ int query_len = 0;
+ int fragment_len = 0;
+ int uri_len;
+ int permit_rule = 0;
+ int permit_rule_match = 0;
+ int permit_rule_action = QS_DENY;
+ int escerr = 0;
+ request_line_len = qos_unescaping(request_line, dconf->dec_mode, &escerr);
+ path_len = qos_unescaping(path, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_path_decode_hook(r, &path, &path_len);
+#endif
+ uri_len = path_len;
+ if(dconf->bodyfilter_p == 1 || dconf->bodyfilter_d == 1) {
+ const char *q = apr_table_get(r->notes, QS_PARP_Q);
+ if((q == NULL) && qos_parp_hp_table_fn) {
+ const char *msg = NULL;
+ apr_table_t *tl = qos_parp_hp_table_fn(r);
+ if(tl) {
+ if(apr_table_elts(tl)->nelts > 0) {
+ q = qos_parp_query(r, tl, NULL);
+ if(q) {
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), q);
+ }
+ }
+ } else {
+ /* no table provided by mod_parp (unsupported content type?),
+ use query string if available */
+ if(r->parsed_uri.query) {
+ q = r->parsed_uri.query;
+ }
+ }
+ if(qos_json(r, dconf, &q, &msg) != APR_SUCCESS) {
+ /* parser error */
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(048)"access denied, invalid JSON syntax (%s),"
+ " action=%s, c=%s, id=%s",
+ msg ? msg : "-",
+ sconf->log_only ? "log only" : "deny",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "048"));
+ QS_INC_EVENT(sconf, 48);
+ return HTTP_FORBIDDEN;
+ }
+ }
+ if(q) {
+ /* prepare unescaped body query (parp) */
+ char *q1 = apr_pstrdup(r->pool, q);
+ int q1_len = 0;
+ q1 = apr_pstrdup(r->pool, q);
+ q1_len = qos_unescaping(q1, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_query_decode_hook(r, &q1, &q1_len);
+#endif
+ if(dconf->bodyfilter_d == 1) {
+ /* use body for query deny filter */
+ query = q1;
+ query_len = q1_len;
+ } else {
+ /* don't use body for query deny filter */
+ if(r->parsed_uri.query) {
+ query = apr_pstrdup(r->pool, r->parsed_uri.query);
+ query_len = qos_unescaping(query, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_query_decode_hook(r, &query, &query_len);
+#endif
+ }
+ }
+ if(dconf->bodyfilter_p != 1) {
+ /* don' use body for permit filter */
+ if(r->parsed_uri.query) {
+ q1 = apr_pstrdup(r->pool, r->parsed_uri.query);
+ q1_len = qos_unescaping(q1, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_query_decode_hook(r, &q1, &q1_len);
+#endif
+ } else {
+ q1 = NULL;
+ q1_len = 0;
+ }
+ }
+ if(q1) {
+ uri = apr_pcalloc(r->pool, path_len + 1 + q1_len + 1);
+ memcpy(uri, path, path_len);
+ uri[path_len] = '?';
+ memcpy(&uri[path_len+1], q1, q1_len);
+ uri[path_len+1+q1_len] = '\0';
+ uri_len = path_len + 1 + q1_len;
+ }
+ }
+ } else {
+ if(r->parsed_uri.query) {
+ query = apr_pstrdup(r->pool, r->parsed_uri.query);
+ query_len = qos_unescaping(query, dconf->dec_mode, &escerr);
+#ifdef QS_MOD_EXT_HOOKS
+ qos_run_query_decode_hook(r, &query, &query_len);
+#endif
+ uri = apr_pcalloc(r->pool, path_len + 1 + query_len + 1);
+ memcpy(uri, path, path_len);
+ uri[path_len] = '?';
+ memcpy(&uri[path_len+1], query, query_len);
+ uri[path_len+1+query_len] = '\0';
+ uri_len = path_len + 1 + query_len;
+ }
+ }
+ if(r->parsed_uri.fragment) {
+ fragment = apr_pstrdup(r->pool, r->parsed_uri.fragment);
+ fragment_len = qos_unescaping(fragment, dconf->dec_mode, &escerr);
+ uri = apr_pcalloc(r->pool, path_len + 1 + fragment_len + 1);
+ memcpy(uri, path, path_len);
+ uri[path_len] = '?';
+ memcpy(&uri[path_len+1], fragment, fragment_len);
+ uri[path_len+1+fragment_len] = '\0';
+ uri_len = path_len + 1 + fragment_len;
+ }
+ if(escerr > 0 && (dconf->urldecoding < QS_OFF_DEFAULT)) {
+ int severity = dconf->urldecoding == QS_DENY ? APLOG_ERR : APLOG_WARNING;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(046)"access denied, invalid url encoding, action=%s, c=%s, id=%s",
+ (!sconf->log_only) && (dconf->urldecoding == QS_DENY) ? "deny" : "log only",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "046"));
+ QS_INC_EVENT(sconf, 46);
+ if(dconf->urldecoding == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ /* process deny- and allow- list rules in one loop */
+ for(i = 0; i < apr_table_elts(dconf->rfilter_table)->nelts; i++) {
+ if(entry[i].key[0] == '+') {
+ int deny_rule = 0;
+ int ex = -1;
+ qos_rfilter_t *rfilter = (qos_rfilter_t *)entry[i].val;
+ if(rfilter->type == QS_DENY_REQUEST_LINE) {
+ deny_rule = 1;
+ ex = qos_regexec_len(r->pool, rfilter->preg, request_line, request_line_len);
+ } else if(rfilter->type == QS_DENY_PATH) {
+ deny_rule = 1;
+ ex = qos_regexec_len(r->pool, rfilter->preg, path, path_len);
+ } else if(rfilter->type == QS_DENY_QUERY) {
+ deny_rule = 1;
+ ex = qos_regexec_len(r->pool, rfilter->preg, query, query_len);
+ } else if(rfilter->type == QS_DENY_EVENT) {
+ /* event rules are processed separately */
+ } else {
+ permit_rule = 1;
+ ex = qos_regexec_len(r->pool, rfilter->preg, uri, uri_len);
+ permit_rule_action = rfilter->action;
+ if(ex == 0) {
+ permit_rule_match = 1;
+ }
+ }
+ if(deny_rule && (ex == 0)) {
+ int severity = rfilter->action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
+ apr_table_set(r->subprocess_env, QS_RuleId, rfilter->id);
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(040)"access denied, %s rule id: %s (%s),"
+ " action=%s, c=%s, id=%s",
+ qos_rfilter_type2text(r->pool, rfilter->type),
+ rfilter->id,
+ rfilter->text,
+ (!sconf->log_only) && (rfilter->action == QS_DENY) ? "deny" : "log only",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "040"));
+ QS_INC_EVENT(sconf, 40);
+ if(rfilter->action == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+ }
+ if(permit_rule && !permit_rule_match) {
+ int severity = permit_rule_action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(041)"access denied, no permit rule match, action=%s, c=%s, id=%s",
+ (!sconf->log_only) && (permit_rule_action == QS_DENY) ? "deny" : "log only",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "041"));
+ QS_INC_EVENT(sconf, 41);
+ if(permit_rule_action == QS_DENY) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * request/response header filter, drops headers which are not allowed
+ */
+static int qos_header_filter(request_rec *r, qos_srv_config *sconf,
+ apr_table_t *headers, const char *type,
+ apr_table_t *hfilter_table,
+ qs_headerfilter_mode_e mode) {
+ apr_table_t *delete = apr_table_make(r->pool, 1);
+ apr_table_t *reason = NULL;
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(headers)->elts;
+ for(i = 0; i < apr_table_elts(headers)->nelts; i++) {
+ qos_fhlt_r_t *he = (qos_fhlt_r_t *)apr_table_get(hfilter_table, entry[i].key);
+ int denied = 0;
+ if(he) {
+ if(mode != QS_HEADERFILTER_SIZE_ONLY) {
+ if(ap_regexec(he->preg, entry[i].val, 0, NULL, 0) != 0) {
+ denied = 1;
+ }
+ }
+ if(strlen(entry[i].val) > he->size) {
+ denied += 2;
+ }
+ if(denied) {
+ char *pattern = apr_psprintf(r->pool, "(pattern=%s, max. length=%d)",
+ he->text, he->size);
+ if(he->action == QS_FLT_ACTION_DENY) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(043)"access denied%s, %s header: \'%s: %s\', %s, c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ type,
+ entry[i].key, entry[i].val,
+ pattern,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "043"));
+ QS_INC_EVENT(sconf, 43);
+ return HTTP_FORBIDDEN;
+ }
+ if(reason == NULL) {
+ reason = apr_table_make(r->pool, 1);
+ }
+ apr_table_add(delete, entry[i].key, entry[i].val);
+ apr_table_add(reason, entry[i].key, pattern);
+ }
+ } else {
+ if(reason == NULL) {
+ reason = apr_table_make(r->pool, 1);
+ }
+ apr_table_add(delete, entry[i].key, entry[i].val);
+ apr_table_add(reason, entry[i].key, "(no rule available)");
+ }
+ }
+ entry = (apr_table_entry_t *)apr_table_elts(delete)->elts;
+ for(i = 0; i < apr_table_elts(delete)->nelts; i++) {
+ if(mode != QS_HEADERFILTER_SILENT) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(042)"drop %s header%s: \'%s: %s\', %s, c=%s, id=%s",
+ type,
+ sconf->log_only ? " (log only)" : "",
+ entry[i].key, entry[i].val,
+ apr_table_get(reason, entry[i].key),
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "042"));
+ QS_INC_EVENT(sconf, 42);
+ }
+ if(!sconf->log_only) {
+ apr_table_unset(headers, entry[i].key);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * returns list of all query name=value pairs
+ */
+static apr_table_t *qos_get_query_table(request_rec *r) {
+ apr_table_t *av = apr_table_make(r->pool, 2);
+ if(r->parsed_uri.query) {
+ const char *q = apr_pstrdup(r->pool, r->parsed_uri.query);
+ while(q && q[0]) {
+ const char *t = ap_getword(r->pool, &q, '&');
+ const char *name = ap_getword(r->pool, &t, '=');
+ const char *value = t;
+ if(name && (strlen(name) > 0)) {
+ if(value && (strlen(value) > 0)) {
+ apr_table_add(av, name, value);
+ } else if((strlen(name) > 0)) {
+ apr_table_add(av, name, "");
+ }
+ }
+ }
+ }
+ return av;
+}
+
+/** add "\n" */
+#define QOS_ALERT_LINE_LEN 65
+static char *qos_crline(request_rec *r, const char *line) {
+ char *string = "";
+ const char *pos = line;
+ while(pos && pos[0]) {
+ int len = strlen(pos);
+ if(len > QOS_ALERT_LINE_LEN) {
+ string = apr_pstrcat(r->pool, string,
+ apr_psprintf(r->pool, "%.*s", QOS_ALERT_LINE_LEN, pos), "\n", NULL);
+ pos = &pos[QOS_ALERT_LINE_LEN];
+ } else {
+ string = apr_pstrcat(r->pool, string, pos, NULL);
+ pos = NULL;
+ }
+ }
+ return string;
+}
+
+/**
+ * calculates the rec/sec block rate
+ */
+static void qos_cal_req_sec(qos_srv_config *sconf, request_rec *r, qs_acentry_t *e) {
+ if(e->req_per_sec > e->req_per_sec_limit) {
+ int factor = ((e->req_per_sec * 100) / e->req_per_sec_limit) - 100;
+ e->req_per_sec_block_rate = e->req_per_sec_block_rate + factor;
+ if(e->req_per_sec_block_rate > QS_MAX_DELAY/1000) {
+ e->req_per_sec_block_rate = QS_MAX_DELAY/1000;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(050)"request rate limit, rule: %s(%ld), req/sec=%ld,"
+ " delay=%dms%s",
+ e->url, e->req_per_sec_limit,
+ e->req_per_sec, e->req_per_sec_block_rate,
+ e->req_per_sec_block_rate == QS_MAX_DELAY/1000 ? " (max)" : "");
+ QS_INC_EVENT(sconf, 50);
+ } else if(e->req_per_sec_block_rate > 0) {
+ if(e->req_per_sec_block_rate < 50) {
+ e->req_per_sec_block_rate = 0;
+ } else {
+ int factor = e->req_per_sec_block_rate / 4;
+ e->req_per_sec_block_rate = e->req_per_sec_block_rate - factor;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
+ QOS_LOG_PFX(051)"request rate limit, rule: %s(%ld), req/sec=%ld,"
+ " delay=%dms",
+ e->url, e->req_per_sec_limit,
+ e->req_per_sec, e->req_per_sec_block_rate);
+ QS_INC_EVENT(sconf, 51);
+ }
+}
+
+/**
+ * QS_DenyEvent enforcement at header parser
+ * @param r
+ * @param sconf
+ * @param dconf
+ # returns DECLINED if no events has been detected
+ */
+static int qos_hp_event_deny_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
+ apr_status_t rv = qos_per_dir_event_rules(r, sconf, dconf);
+ if(rv != APR_SUCCESS) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return rv;
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * QS_Permit* / QS_Deny* enforcement at header parser
+ * @param r
+ * @param sconf
+ * @param dconf
+ * @return
+ */
+static int qos_hp_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
+ apr_status_t rv = APR_SUCCESS;
+ if(sconf->milestones) {
+ char *value = qos_get_remove_cookie(r, QOS_MILESTONE_COOKIE);
+ rv = qos_verify_milestone(r, sconf, value);
+ }
+
+ if((rv == APR_SUCCESS) && (apr_table_elts(dconf->rfilter_table)->nelts > 0)) {
+ rv = qos_per_dir_rules(r, sconf, dconf);
+ }
+
+ if(rv != APR_SUCCESS) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return rv;
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * QS_SetEnvRes (outfilter)
+ * Detects events at response time.
+ */
+static void qos_setenvres(request_rec *r, qos_srv_config *sconf) {
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvres_t)->elts;
+ for(i = 0; i < apr_table_elts(sconf->setenvres_t)->nelts; i++) {
+ const char *val = apr_table_get(r->subprocess_env, entry[i].key);
+ if(val) {
+ qos_pregval_t *pregval = (qos_pregval_t *)entry[i].val;
+ if(ap_regexec(pregval->preg, val, AP_MAX_REG_MATCH, regm, 0) == 0) {
+ if(pregval->value) {
+ char *replaced = ap_pregsub(r->pool, pregval->value, val, AP_MAX_REG_MATCH, regm);
+ apr_table_set(r->subprocess_env, pregval->name, replaced);
+ } else {
+ apr_table_set(r->subprocess_env, pregval->name, "1");
+ }
+ }
+ }
+ }
+}
+
+/**
+ * QS_SetEnvResHeader(Match) (outfilter)
+ * Matches response headers and sets an event on match.
+ * @param r
+ * @param sconf
+ */
+static void qos_setenvresheader(request_rec *r, qos_srv_config *sconf) {
+ apr_table_t *headers = r->headers_out;
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvresheader_t)->elts;
+ apr_table_entry_t *entryMatch = (apr_table_entry_t *)apr_table_elts(sconf->setenvresheadermatch_t)->elts;
+ while(headers) {
+ for(i = 0; i < apr_table_elts(sconf->setenvresheadermatch_t)->nelts; i++) {
+ const char *val = apr_table_get(headers, entryMatch[i].key);
+ if(val) {
+ ap_regex_t *preg = (ap_regex_t *)entryMatch[i].val;
+ if(ap_regexec(preg, val, 0, NULL, 0) == 0) {
+ apr_table_set(r->subprocess_env, entryMatch[i].key, val);
+ }
+ }
+ }
+ for(i = 0; i < apr_table_elts(sconf->setenvresheader_t)->nelts; i++) {
+ const char *val = apr_table_get(headers, entry[i].key);
+ if(val) {
+ apr_table_set(r->subprocess_env, entry[i].key, val);
+ if(strcasecmp(entry[i].val, "drop") == 0) {
+ apr_table_unset(headers, entry[i].key);
+ }
+ }
+ }
+ if(headers == r->headers_out) {
+ headers = r->err_headers_out;
+ } else {
+ headers = NULL;
+ }
+ }
+}
+
+/**
+ * QS_SetEnvIfStatus
+ * Match response status code
+ *
+ * @param r
+ * @param sconf
+ * @param dconf
+ */
+static void qos_setenvstatus(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
+ char *code = apr_psprintf(r->pool, "%d", r->status);
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvstatus_t)->elts;
+ for(i = 0; i < apr_table_elts(sconf->setenvstatus_t)->nelts; i++) {
+ if(strcmp(entry[i].key, code) == 0) {
+ char *var = apr_pstrdup(r->pool, entry[i].val);
+ char *value = strchr(var, '=');
+ if(value) {
+ // a value has been defined
+ value[0] = '\0';
+ value++;
+ } else {
+ if(strcmp(var, QS_BLOCK) == 0) {
+ // QS_Block optionally defines the weight for error codes
+ value = apr_pstrdup(r->pool, "1");
+ } else {
+ // by default, the value becomes the status code
+ value = code;
+ }
+ }
+ apr_table_set(r->subprocess_env, var, value);
+ }
+ }
+ if(dconf) {
+ entry = (apr_table_entry_t *)apr_table_elts(dconf->setenvstatus_t)->elts;
+ for(i = 0; i < apr_table_elts(dconf->setenvstatus_t)->nelts; i++) {
+ if(strcmp(entry[i].key, code) == 0) {
+ char *var = apr_pstrdup(r->pool, entry[i].val);
+ char *value = strchr(var, '=');
+ if(value) {
+ value[0] = '\0';
+ value++;
+ } else {
+ value = code;
+ }
+ apr_table_set(r->subprocess_env, var, value);
+ }
+ }
+ }
+}
+
+/**
+ * Returns the best matching server name (the configured ServerName,
+ * supporting ServerAlias directive or the value provided by the
+ * caller (usually the Host header)).
+ *
+ * @param r
+ * @param server_hostname Host name the client expects (Host header) <host>[:<port>]
+ * @param match Indicates if the provide name matches the ServerName/ServerAlias
+ * @return hostname
+ */
+static char *qos_server_alias(request_rec *r, const char *server_hostname, int *match) {
+ char *server = apr_pstrdup(r->pool, r->server->server_hostname); // default (hostname, no port)
+ *match = 0;
+ if(server_hostname) {
+ const char *search = server_hostname;
+ char *port = strchr(search, ':');
+ if(port) {
+ // without the port
+ search = apr_pstrndup(r->pool, search, port - search);
+ }
+ if(strcasecmp(search, r->server->server_hostname) == 0) {
+ /* match ServerName */
+ // we already did: server = apr_pstrdup(r->pool, r->server->server_hostname);
+ *match = 1;
+ } else if(r->server->names) {
+ int i;
+ apr_array_header_t *names = r->server->names;
+ char **name = (char **)names->elts;
+ for(i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if(strcasecmp(search, name[i]) == 0) {
+ /* match ServerAlias */
+ server = apr_pstrdup(r->pool, name[i]);
+ *match = 1;
+ }
+ }
+ } else if(r->server->wild_names) {
+ int i;
+ apr_array_header_t *names = r->server->wild_names;
+ char **name = (char **)names->elts;
+ for(i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if(!ap_strcasecmp_match(search, name[i])) {
+ /* match ServerAlias using wildcards */
+ server = apr_pstrdup(r->pool, search);
+ *match = 1;
+ }
+ }
+ }
+ }
+ return server;
+}
+
+/**
+ * Returns the url to this server, e.g. https://server1 or http://server1:8080
+ * used for redirects.
+ *
+ * @param r
+ * @return schema/hostname
+ */
+static char *qos_this_host(request_rec *r) {
+ const char *hostport = apr_table_get(r->headers_in, "Host");
+ int port = 0;
+ int ssl = 0;
+ int default_port;
+ const char *server_hostname = r->server->server_hostname;
+ if(qos_is_https) {
+ ssl = qos_is_https(r->connection);
+ }
+ if(hostport) {
+ char *p;
+ int match;
+ hostport = apr_pstrdup(r->pool, hostport);
+ if((p = strchr(hostport, ':')) != NULL) {
+ p[0] = '\0';
+ p++;
+ port = atoi(p);
+ }
+ server_hostname = qos_server_alias(r, hostport, &match);
+ }
+ if(port == 0) {
+ // pref. vhost
+ port = r->server->addrs->host_port;
+ }
+ if(port == 0) {
+ // main srv
+ port = r->server->port;
+ }
+ default_port = ssl ? 443 : 80;
+ if(port == default_port) {
+ return apr_psprintf(r->pool, "%s%s",
+ ssl ? "https://" : "http://",
+ server_hostname);
+ }
+ return apr_psprintf(r->pool, "%s%s:%d",
+ ssl ? "https://" : "http://",
+ server_hostname,
+ port);
+}
+
+/**
+ * Enables mod_parp if mod_qos requires access to the request body.
+ * @param r
+ */
+static void qos_enable_parp(request_rec *r) {
+ const char *ct = apr_table_get(r->headers_in, "Content-Type");
+ if(ct) {
+ if(ap_strcasestr(ct, "application/x-www-form-urlencoded") ||
+ ap_strcasestr(ct, "multipart/form-data") ||
+ ap_strcasestr(ct, "multipart/mixed") ||
+ ap_strcasestr(ct, "application/json")) {
+ apr_table_set(r->subprocess_env, "parp", "mod_qos");
+ }
+ }
+}
+
+/**
+ * Generic request validation / sanity check:
+ * We ensure to have at least a valid, decoded request uri received.
+ * (no further uri validation required in your code)
+ * @param r
+ * @param sconf
+ * @return HTTP_BAD_REQUEST for requests which may not be processed by mod_qos, otherwise
+ * APR_SUCCESS
+ */
+static apr_status_t qos_request_check(request_rec *r, qos_srv_config *sconf) {
+ if((r->unparsed_uri == NULL) || (r->parsed_uri.path == NULL)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(045)"access denied, invalid request line:"
+ " can't parse uri, c=%s, id=%s",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "045"));
+ QS_INC_EVENT(sconf, 45);
+ return HTTP_BAD_REQUEST;
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * QS_SetEnvIfParp (prr), enable parp
+ */
+static apr_status_t qos_parp_prr(request_rec *r, qos_srv_config *sconf) {
+ if(apr_table_elts(sconf->setenvifparp_t)->nelts > 0) {
+ qos_enable_parp(r);
+ }
+ return DECLINED;
+}
+
+/**
+ * QS_SetEnvIfQuery/QS_SetEnvIfParp
+ */
+static void qos_setenvif_ex(request_rec *r, const char *query, apr_table_t *table_setenvif) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(table_setenvif)->elts;
+ for(i = 0; i < apr_table_elts(table_setenvif)->nelts; i++) {
+ qos_setenvifquery_t *setenvif = (qos_setenvifquery_t *)entry[i].val;
+ char *name = setenvif->name;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ if(ap_regexec(setenvif->preg, query, AP_MAX_REG_MATCH, regm, 0) == 0) {
+ if(name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &name[1]);
+ } else {
+ char *replaced = "";
+ if(setenvif->value) {
+ replaced = ap_pregsub(r->pool, setenvif->value, query, AP_MAX_REG_MATCH, regm);
+ }
+ apr_table_set(r->subprocess_env, name, replaced);
+ }
+ }
+ }
+}
+
+/**
+ * Process body events (QS_SetEnvIfBody) and sets the r->subprocess_env variables
+ * @param r
+ * @param sconf
+ */
+static void qos_parp_hp_body(request_rec *r, qos_srv_config *sconf) {
+ apr_size_t len;
+ const char *data = qos_parp_body_data_fn(r, &len);
+ if(data && (len > 0)) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvifparpbody_t)->elts;
+#if (AP_SERVER_MINORVERSION_NUMBER < 4) && defined QS_INTERNAL_TEST
+ // Apache 2.2 is no longer supported (test only)
+ char *tmpData = apr_palloc(r->pool, len + 1);
+ memcpy(tmpData, data, len);
+ tmpData[len] = '\0';
+ data = (const char *)tmpData;
+#endif
+ for(i = 0; i < apr_table_elts(sconf->setenvifparpbody_t)->nelts; i++) {
+ qos_setenvifparpbody_t *setenvif = (qos_setenvifparpbody_t *)entry[i].val;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+#if (AP_SERVER_MINORVERSION_NUMBER < 4) && defined QS_INTERNAL_TEST
+ if(ap_regexec(setenvif->pregx, data, AP_MAX_REG_MATCH, regm, 0) == 0) { // won't work for null chars!
+#else
+ if(ap_regexec_len(setenvif->pregx, data, len, AP_MAX_REG_MATCH, regm, 0) == 0) {
+#endif
+ char *name = setenvif->name;
+ if(name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &name[1]);
+ } else {
+ char *value = apr_pstrdup(r->pool, setenvif->value);
+ char *p = strstr(value, "$1");
+ if(p) {
+ char *c = apr_pstrndup(r->pool, &data[regm[0].rm_so], regm[0].rm_eo - regm[0].rm_so);
+ if(ap_regexec(setenvif->pregx, c, AP_MAX_REG_MATCH, regm, 0) == 0) {
+ value = ap_pregsub(r->pool, value, c, AP_MAX_REG_MATCH, regm);
+ }
+ }
+ apr_table_set(r->subprocess_env, name, value != NULL ? value : "");
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Setting events based on request payload (query), QS_SetEnvIfParp (hp)
+ * @param r
+ * @param sconf
+ */
+static void qos_parp_hp(request_rec *r, qos_srv_config *sconf) {
+ const char *query = apr_table_get(r->notes, QS_PARP_Q);
+ if((query == NULL) && qos_parp_hp_table_fn) {
+ apr_table_t *tl = qos_parp_hp_table_fn(r);
+ if(tl) {
+ if(apr_table_elts(tl)->nelts > 0) {
+ query = qos_parp_query(r, tl, NULL);
+ if(query) {
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), query);
+ }
+ }
+ } else {
+ /* no table provided by mod_parp (unsupported content type?),
+ use query string if available */
+ if(r->parsed_uri.query) {
+ query = r->parsed_uri.query;
+ }
+ }
+ }
+ if(query) {
+ qos_setenvif_ex(r, query, sconf->setenvifparp_t);
+ }
+}
+
+/**
+ * Replaces ${var} by the value in var
+ * @param p Pool for memory allocation
+ * @param vars Available variables to lookup
+ * @param string String to replace variables
+ * @return 1 on success or 0 if string still contains "${"
+ */
+static int qos_reslove_variable(apr_pool_t *p, apr_table_t *vars, char **string) {
+ int i;
+ int start;
+ int line_end;
+ char *var_name;
+ char *new_line = *string;
+ char *line = *string;
+ const char *val;
+
+ once_again:
+ i = 0;
+ while(line[i] != 0) {
+ if((line[i] == '$') && (line[i+1] == '{')) {
+ line_end = i;
+ i=i+2;
+ start = i;
+ while((line[i] != 0) && (line[i] != '}')) {
+ i++;
+ }
+ if(line[i] != '}') {
+ /* no end found */
+ break;
+ } else {
+ var_name = apr_pstrndup(p, &line[start], i - start);
+ val = apr_table_get(vars, var_name);
+ if(val) {
+ line[line_end] = 0;
+ i++;
+ new_line = apr_pstrcat(p, line, val, &line[i], NULL);
+ line = new_line;
+ goto once_again;
+ }
+ }
+ }
+ i++;
+ }
+ if(!new_line[0] || strstr(new_line, "${")) {
+ return 0;
+ }
+ *string = new_line;
+ return 1;
+}
+
+/**
+ * QS_SetEnvIfQuery (hp)
+ * @param r
+ * @param sconf
+ * @param dconf
+ */
+static void qos_setenvifquery(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
+ qos_setenvif_ex(r, r->parsed_uri.query, sconf->setenvifquery_t);
+ qos_setenvif_ex(r, r->parsed_uri.query, dconf->setenvifquery_t);
+}
+
+/**
+ * QS_SetEnv
+ * @param r
+ * @param sconf
+ */
+static void qos_setenv(request_rec *r, qos_srv_config *sconf) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenv_t)->elts;
+ for(i = 0; i < apr_table_elts(sconf->setenv_t)->nelts; i++) {
+ char *variable = entry[i].val;
+ char *value = apr_pstrdup(r->pool, strchr(entry[i].key, '='));
+ value++;
+ if(qos_reslove_variable(r->pool, r->subprocess_env, &value)) {
+ apr_table_set(r->subprocess_env, variable, value);
+ }
+ }
+}
+
+/**
+ * QS_SetReqHeader
+ * @param r
+ * @param header_t
+ */
+static void qos_setreqheader(request_rec *r, apr_table_t *header_t) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(header_t)->elts;
+ for(i = 0; i < apr_table_elts(header_t)->nelts; i++) {
+ char *header = entry[i].val;
+ char *variable = apr_pstrdup(r->pool, strchr(entry[i].key, '='));
+ const char *val;
+ variable++;
+ val = apr_table_get(r->subprocess_env, variable);
+ if(val) {
+ if(header[0] == '!') {
+ apr_table_unset(r->headers_in, &header[1]);
+ } else {
+ apr_table_set(r->headers_in, header, val);
+ }
+ }
+ }
+}
+
+/**
+ * QS_SetEnvIf (hp and logger)
+ * @param r
+ * @param setenvif_t
+ */
+static void qos_setenvif(request_rec *r, apr_array_header_t *setenvif_t) {
+ int i;
+ qos_setenvif_t *entries = (qos_setenvif_t *)setenvif_t->elts;
+ for(i = 0; i < setenvif_t->nelts; i++) {
+ qos_setenvif_t *setenvif = &entries[i];
+ if(setenvif->preg == NULL) {
+ // mode 1 (boolean AND operator)
+ if((setenvif->variable1[0] == '!') && (setenvif->variable2[0] == '!')) {
+ if(!apr_table_get(r->subprocess_env, &setenvif->variable1[1]) &&
+ !apr_table_get(r->subprocess_env, &setenvif->variable2[1])) {
+ if(setenvif->name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &setenvif->name[1]);
+ } else {
+ apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
+ }
+ }
+ } else if(setenvif->variable1[0] == '!') {
+ if(!apr_table_get(r->subprocess_env, &setenvif->variable1[1]) &&
+ apr_table_get(r->subprocess_env, setenvif->variable2)) {
+ if(setenvif->name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &setenvif->name[1]);
+ } else {
+ apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
+ }
+ }
+ } else if(setenvif->variable2[0] == '!') {
+ if(apr_table_get(r->subprocess_env, setenvif->variable1) &&
+ !apr_table_get(r->subprocess_env, &setenvif->variable2[1])) {
+ if(setenvif->name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &setenvif->name[1]);
+ } else {
+ apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
+ }
+ }
+ } else {
+ if(apr_table_get(r->subprocess_env, setenvif->variable1) &&
+ apr_table_get(r->subprocess_env, setenvif->variable2)) {
+ if(setenvif->name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &setenvif->name[1]);
+ } else {
+ apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
+ }
+ }
+ }
+ } else {
+ // mode 2 (pattern match)
+ const char *value = apr_table_get(r->subprocess_env, setenvif->variable1);
+ if(value) {
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ if(ap_regexec(setenvif->preg, value, AP_MAX_REG_MATCH, regm, 0) == 0) {
+ if(setenvif->name[0] == '!') {
+ apr_table_unset(r->subprocess_env, &setenvif->name[1]);
+ } else {
+ char *replaced = ap_pregsub(r->pool, setenvif->value, value, AP_MAX_REG_MATCH, regm);
+ apr_table_set(r->subprocess_env, setenvif->name, replaced);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * QS_RequestHeaderFilter enforcement
+ * @param r
+ * @param sconf
+ * @parm dconf
+ * @return
+ */
+static int qos_hp_header_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
+ qs_headerfilter_mode_e mode = sconf->headerfilter;
+ if(dconf->headerfilter > QS_HEADERFILTER_OFF_DEFAULT) {
+ // overrides server configuration
+ mode = dconf->headerfilter;
+ }
+ if(mode > QS_HEADERFILTER_OFF) {
+ apr_status_t rv = qos_header_filter(r, sconf, r->headers_in, "request",
+ sconf->hfilter_table, mode);
+ if(rv != APR_SUCCESS) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return rv;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * Dynamic keep alive.
+ * Creates a copy of the server_rec and adjusts the keep-aliva settings
+ * for this request.
+ *
+ * @param r
+ * @param sconf
+ */
+static void qos_keepalive(request_rec *r, qos_srv_config *sconf) {
+ if(r->subprocess_env) {
+ const char *vtmo = apr_table_get(r->subprocess_env, QS_KEEPALIVE);
+ const char *vmax = apr_table_get(r->subprocess_env, QS_MAXKEEPALIVEREQ);
+ int ka = -1; // keep alive timeout
+ int km = -1; // max keep alive requests
+ if(vtmo) {
+ ka = atoi(vtmo);
+ if(ka == 0 && vtmo[0] != '0') {
+ ka = -1;
+ }
+ }
+ if(vmax) {
+ km = atoi(vmax);
+ if(km == 0 && vmax[0] != '0') {
+ km = -1;
+ }
+ }
+ if(ka >= 0 || km >= 0) {
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ if(m_event_mpm) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
+ QOS_LOG_PFX(037)"loaded MPM is 'event'"
+ " and the QS_KeepAliveTimeout/QS_MaxKeepAliveRequests"
+ " directives can't be used.");
+ QS_INC_EVENT(sconf, 37);
+ return;
+ }
+ if(QS_ISDEBUG(r->server)) {
+ int kaorig = apr_time_sec(r->server->keep_alive_timeout);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"set keepalive timeout to %d seconds and max"
+ " keepalive requests to %d%s, id=%s",
+ ka >= 0 ? ka : kaorig,
+ km >= 0 ? km : r->server->keep_alive_max,
+ sconf->log_only ? " (log only)" : "",
+ qos_unique_id(r, NULL));
+ }
+ /* copy the server record (I know......., but this works) */
+ if(!rctx->evmsg || !strstr(rctx->evmsg, "T;")) {
+ /* copy it only once (@hp or @out-filter) */
+ if(!sconf->log_only) {
+ server_rec *sr = apr_pcalloc(r->connection->pool, sizeof(server_rec));
+ server_rec *sc = apr_pcalloc(r->connection->pool, sizeof(server_rec));
+ memcpy(sr, r->server, sizeof(server_rec));
+ memcpy(sc, r->connection->base_server, sizeof(server_rec));
+ r->server = sr;
+ r->connection->base_server = sc;
+ }
+ qs_set_evmsg(r, "T;");
+ }
+ if(!sconf->log_only) {
+ if(ka >= 0) {
+ apr_interval_time_t kat = apr_time_from_sec(ka);
+ r->server->keep_alive_timeout = kat;
+ r->connection->base_server->keep_alive_timeout = kat;
+ }
+ if(km >= 0) {
+ r->server->keep_alive_max = km;
+ r->connection->base_server->keep_alive_max = km;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * QS_EventPerSecLimit
+ */
+static void qos_lg_event_update(request_rec *r, apr_time_t *t) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qs_actable_t *act = sconf->act;
+ if(act->has_events && (apr_table_get(r->notes, QS_R012_ALREADY_BLOCKED) == NULL)) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ qs_acentry_t *actEntry = act->entry;
+ *t = now;
+ if(actEntry) {
+ apr_global_mutex_lock(act->lock); /* @CRT13 */
+ while(actEntry) {
+ if(actEntry->event) {
+ if(((actEntry->event[0] != '!') && apr_table_get(r->subprocess_env, actEntry->event)) ||
+ ((actEntry->event[0] == '!') && !apr_table_get(r->subprocess_env, &actEntry->event[1]))) {
+ if(actEntry->req < LONG_MAX) {
+ actEntry->req++;
+ }
+ if(now > (actEntry->interval + QS_BW_SAMPLING_RATE)) {
+ if(actEntry->req_per_sec_limit) {
+ /* QS_EventPerSecLimit */
+ actEntry->req_per_sec = actEntry->req / (now - actEntry->interval);
+ actEntry->req = 0;
+ actEntry->interval = now;
+ qos_cal_req_sec(sconf, r, actEntry);
+ }
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT13 */
+ }
+ }
+}
+
+/**
+ * QS_EventLimitCount, propagte variable
+ */
+static void qos_pr_event_limit(request_rec *r, qos_srv_config *sconf) {
+ qs_actable_t *act = sconf->act;
+ if(act->event_entry && (sconf->event_limit_a->nelts > 0)) {
+ int i;
+ qos_event_limit_entry_t *entry = act->event_entry;
+ apr_time_t now = apr_time_sec(r->request_time);
+ apr_global_mutex_lock(act->lock); /* @CRT46 */
+ for(i = 0; i < sconf->event_limit_a->nelts; i++) {
+ if(entry->action == QS_EVENT_ACTION_DENY) {
+ // propagte to environment (previous value)
+ if(entry->limitTime + entry->seconds >= now) {
+ apr_table_set(r->subprocess_env,
+ apr_pstrcat(r->pool, entry->env_var, QS_COUNTER_SUFFIX, NULL),
+ apr_psprintf(r->pool, "%d", entry->limit));
+ }
+ }
+ // next rule
+ entry++;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT46 */
+ }
+}
+
+/**
+ * QS_EventLimitCount, detect and enforce
+ */
+static int qos_hp_event_limit(request_rec *r, qos_srv_config *sconf) {
+ apr_status_t rv = DECLINED;
+ qs_actable_t *act = sconf->act;
+ if(act->event_entry) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ int i;
+ qos_event_limit_entry_t *entry = act->event_entry;
+ apr_global_mutex_lock(act->lock); /* @CRT41 */
+ for(i = 0; i < sconf->event_limit_a->nelts; i++) {
+ if(entry->action == QS_EVENT_ACTION_DENY) {
+ if(apr_table_get(r->subprocess_env, entry->env_var) != NULL) {
+ char *eventLimitId = apr_pstrcat(r->pool, QS_R013_ALREADY_BLOCKED, entry->env_var, NULL);
+ apr_table_set(r->notes, eventLimitId, "");
+ // reset required (expired)?
+ if(entry->limitTime + entry->seconds < now) {
+ entry->limit = 0;
+ entry->limitTime = 0;
+ }
+ /* increment limit event */
+ if(entry->limit < INT_MAX) {
+ entry->limit++;
+ }
+ if(entry->limit == 1) {
+ /* ... and start timer */
+ entry->limitTime = now;
+ }
+ // check limit
+ if(entry->limit > entry->max) {
+ int block = 1;
+ char *conditional = "";
+ if(entry->condStr != NULL) {
+ // conditional enforcement...
+ const char *condition = apr_table_get(r->subprocess_env, QS_COND);
+ conditional = apr_pstrdup(r->pool, "Cond");
+ if(condition == NULL) {
+ block = 0; // variable not set
+ } else {
+ if(ap_regexec(entry->preg, condition, 0, NULL, 0) != 0) {
+ block = 0; // pattern does not match
+ }
+ }
+ }
+ if(block) {
+ rv = m_retcode;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(013)"access denied%s,"
+ " QS_%sEventLimitCount rule: %s,"
+ " max=%d, current=%d,"
+ " c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ conditional,
+ entry->env_var, entry->max, entry->limit,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "013"));
+ QS_INC_EVENT_LOCKED(sconf, 13);
+ }
+ }
+ }
+ // propagte to environment (current)
+ apr_table_set(r->subprocess_env,
+ apr_pstrcat(r->pool, entry->env_var, QS_COUNTER_SUFFIX, NULL),
+ apr_psprintf(r->pool, "%d", entry->limit));
+ }
+ // next rule
+ entry++;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT41 */
+ }
+ if(rv != DECLINED) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ rv = rc;
+ }
+ } else {
+ return DECLINED;
+ }
+ }
+ return rv;
+}
+
+/**
+ * QS_EventRequestLimit
+ */
+static int qos_hp_event_filter(request_rec *r, qos_srv_config *sconf) {
+ apr_status_t rv = DECLINED;
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ qs_actable_t *act = sconf->act;
+ if(act->has_events) {
+ qs_acentry_t *actEntry = act->entry;
+ if(actEntry) {
+ apr_global_mutex_lock(act->lock); /* @CRT31 */
+ while(actEntry) {
+ if(actEntry->event && (actEntry->limit != -1)) {
+ const char *var = apr_table_get(r->subprocess_env, actEntry->event);
+ if(var) {
+ int match = 1;
+ if(actEntry->regex_var) {
+ if(ap_regexec(actEntry->regex_var, var, 0, NULL, 0) != 0) {
+ match = 0;
+ }
+ }
+ if(match) {
+ apr_table_addn(rctx->event_entries, actEntry->url, (char *)actEntry);
+ if(actEntry->counter < INT_MAX) {
+ actEntry->counter++;
+ }
+ if(actEntry->counter > actEntry->limit) {
+ rv = m_retcode;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(012)"access denied%s,"
+ " QS_EventRequestLimit rule: %s(%d),"
+ " concurrent requests=%d,"
+ " c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ actEntry->url, actEntry->limit, actEntry->counter,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "012"));
+ apr_table_set(r->notes, QS_R012_ALREADY_BLOCKED, "");
+ QS_INC_EVENT_LOCKED(sconf, 12);
+ }
+ apr_table_add(r->subprocess_env,
+ apr_psprintf(r->pool, "QS_EventRequestLimit_%s_Counter", actEntry->event),
+ apr_psprintf(r->pool, "%d", actEntry->counter));
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT31 */
+ }
+ }
+ if(rv != DECLINED) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ rv = rc;
+ }
+ } else {
+ return DECLINED;
+ }
+ }
+ return rv;
+}
+
+static qs_conn_ctx *qos_get_cconf(conn_rec *connection) {
+ conn_rec *c = QS_CONN_MASTER(connection);
+ qs_conn_ctx *cconf = NULL;
+ qs_conn_base_ctx *base = qos_get_conn_base_ctx(c);
+ if(base) {
+ cconf = base->cconf;
+ }
+ return cconf;
+}
+
+static qs_conn_ctx *qos_create_cconf(conn_rec *connection, qos_srv_config *sconf) {
+ conn_rec *c = QS_CONN_MASTER(connection);
+ qs_conn_base_ctx *base = qos_get_conn_base_ctx(c);
+ qs_conn_ctx *cconf = apr_pcalloc(c->pool, sizeof(qs_conn_ctx));
+ cconf->mc = c;
+ cconf->ip6[0] = 0;
+ cconf->ip6[1] = 0;
+ cconf->evmsg = NULL;
+ cconf->sconf = sconf;
+ cconf->is_vip = 0;
+ cconf->set_vip_by_header = 0;
+ cconf->has_lowrate = 0;
+ apr_pool_pre_cleanup_register(c->pool, cconf, qos_cleanup_conn);
+ if(base == NULL) {
+ base = qos_create_conn_base_ctx(c, sconf);
+ }
+ base->cconf = cconf;
+ return cconf;
+}
+
+/*
+ * QS_SrvSerialize
+ */
+static void qos_hp_srv_serialize(request_rec *r, qos_srv_config *sconf,
+ qs_req_ctx * rctx) {
+ int loops = 0;
+ int locked = 0; // we got the lock for this request
+ if(!rctx) {
+ rctx = qos_rctx_config_get(r);
+ }
+ while(!locked) {
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT44 */
+ if(sconf->act->serialize->locked == 0) {
+ // free!! check if we might get the lock for this request
+ if(sconf->act->serialize->q1 == 0) {
+ // yes: no other request waiting
+ locked = 1;
+ } else if(sconf->act->serialize->q1 == r->request_time) {
+ // yes: waiting for this request (we are the next in the queue)
+ locked = 1;
+ sconf->act->serialize->q1 = sconf->act->serialize->q2;
+ sconf->act->serialize->q2 = 0;
+ } else if(sconf->act->serialize->q1 > r->request_time) {
+ // yes: not yet in the queue but this request is waiting for a longer time
+ // keep the others in the queue
+ locked = 1;
+ } else if(sconf->act->serialize->q2 == 0 || sconf->act->serialize->q2 > r->request_time) {
+ // no: it's not yet our time... but take the second place in the queue
+ sconf->act->serialize->q2 = r->request_time;
+ }
+ } else {
+ // put this request into one of the queues if possible
+ if(sconf->act->serialize->q1 == 0) {
+ // no other request waiting
+ sconf->act->serialize->q1 = r->request_time;
+ } else if(sconf->act->serialize->q1 == r->request_time) {
+ // already next in the queue
+ } else if(sconf->act->serialize->q1 > r->request_time) {
+ // older request is waiting, take over
+ sconf->act->serialize->q2 = sconf->act->serialize->q1;
+ sconf->act->serialize->q1 = r->request_time;
+ } else if(sconf->act->serialize->q2 == 0) {
+ // no one in on the second place
+ sconf->act->serialize->q2 = r->request_time;
+ } else if(sconf->act->serialize->q2 == r->request_time) {
+ // already next in the queue
+ } else if(sconf->act->serialize->q2 > r->request_time) {
+ // older request is waiting in the second position, take over
+ sconf->act->serialize->q2 = r->request_time;
+ }
+ }
+ if(locked) {
+ sconf->act->serialize->locked = 1;
+ rctx->srv_serialize_set = 1;
+ }
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT44 */
+ if(!locked) {
+ /* sleep 50ms */
+ qs_set_evmsg(r, "s;");
+ if(sconf->log_only) {
+ return;
+ }
+ apr_sleep(50000);
+ }
+ if(loops >= sconf->serializeTMO) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(068)"QS_SrvSerialize exceeds limit of %d seconds, "
+ "id=%s",
+ sconf->serializeTMO / 20,
+ qos_unique_id(r, "037"));
+ QS_INC_EVENT(sconf, 37);
+ /* remove this request from the queue resp. clear the queue
+ to avoid a deadlock */
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT44.1 */
+ sconf->act->serialize->q2 = 0;
+ sconf->act->serialize->q1 = 0;
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT44.1 */
+ break;
+ }
+ loops++;
+ }
+}
+
+/*
+ * QS_ClientSerialize
+ */
+static void qos_hp_cc_serialize(request_rec *r, qos_srv_config *sconf, qs_req_ctx * rctx) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(!rctx) {
+ rctx = qos_rctx_config_get(r);
+ }
+ if(u && cconf) {
+ int loops = 0;
+ int locked = 0;
+ qos_s_entry_t searchE;
+ const char *forwardedForLogIP = qos_get_clientIP(r, sconf, cconf,
+ "hp", rctx->cc_serialize_ip);
+ searchE.ip6[0] = rctx->cc_serialize_ip[0];
+ searchE.ip6[1] = rctx->cc_serialize_ip[1];
+
+ /* wait until we get a lock */
+ while(!locked) {
+ qos_s_entry_t **clientEntry = NULL;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT36 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ if((*clientEntry)->serialize == 0) {
+ // free! check if this request is the next in the queue */
+ if(((*clientEntry)->serializeQueue == 0) || (r->request_time <= (*clientEntry)->serializeQueue)) {
+ (*clientEntry)->serialize = 1;
+ (*clientEntry)->serializeQueue = 0;
+ rctx->cc_serialize_set = 1;
+ locked = 1;
+ }
+ } else {
+ // put the request into the queue
+ if((*clientEntry)->serializeQueue == 0) {
+ // the only waiting req
+ (*clientEntry)->serializeQueue = r->request_time;
+ } else {
+ if((*clientEntry)->serializeQueue > r->request_time) {
+ // this request is waiting for a longer time
+ (*clientEntry)->serializeQueue = r->request_time;
+ }
+ }
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT36 */
+ if(!locked) {
+ /* sleep 100ms */
+ qs_set_evmsg(r, "s;");
+ if(sconf->log_only) {
+ return;
+ }
+ apr_sleep(100000);
+ }
+ // max wait time: 5 minutes
+ if(loops >= 3000) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(068)"QS_ClientSerialize exceeds limit of 5 minutes, "
+ "c=%s, id=%s",
+ forwardedForLogIP == NULL ? "-" : forwardedForLogIP,
+ qos_unique_id(r, "068"));
+ QS_INC_EVENT(sconf, 68);
+ /* remove this request from the queue resp. clear the queue
+ to avoid a deadlock */
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT36.1 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ (*clientEntry)->serializeQueue = 0;
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT36.1 */
+ break;
+ }
+ loops++;
+ }
+ }
+}
+
+/*
+ * QS_ClientEventRequestLimit
+ */
+static int qos_hp_cc_event_count(request_rec *r, qos_srv_config *sconf,
+ qs_req_ctx * rctx) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(!rctx) {
+ rctx = qos_rctx_config_get(r);
+ }
+ if(u && cconf &&
+ r->subprocess_env && apr_table_get(r->subprocess_env, "QS_EventRequest")) {
+ int vip = 0;
+ int count = 0;
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ const char *forwardedForLogIP = qos_get_clientIP(r, sconf, cconf,
+ "hp", rctx->cc_event_ip);
+
+ rctx->cc_event_req_set = 1;
+ searchE.ip6[0] = rctx->cc_event_ip[0];
+ searchE.ip6[1] = rctx->cc_event_ip[1];
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT33 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ if((*clientEntry)->event_req < INT_MAX) {
+ (*clientEntry)->event_req++;
+ }
+ count = (*clientEntry)->event_req;
+ if((*clientEntry)->vip || rctx->is_vip) {
+ vip = 1;
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT33 */
+ if(vip) {
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ if(count > sconf->qos_cc_event_req) {
+ if(vip) {
+ qs_set_evmsg(r, "S;");
+ } else {
+ int rc;
+ const char *error_page = sconf->error_page;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(065)"access denied%s,"
+ " QS_ClientEventRequestLimit rule:"
+ " max=%d, current=%d, c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->qos_cc_event_req,
+ count,
+ forwardedForLogIP == NULL ? "-" :
+ forwardedForLogIP,
+ qos_unique_id(r, "065"));
+ QS_INC_EVENT(sconf, 65);
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return m_retcode;
+ }
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/* QS_SetEnvIfCmp */
+static void qos_setenvifcmp(request_rec *r, apr_array_header_t *cmps) {
+ int i;
+ qos_cmp_entry_t *entries = (qos_cmp_entry_t *)cmps->elts;
+ for(i = 0; i < cmps->nelts; ++i) {
+ qos_cmp_entry_t *b = &entries[i];
+ const char *leftStr = apr_table_get(r->subprocess_env, b->left);
+ const char *rightStr = apr_table_get(r->subprocess_env, b->right);
+ if(leftStr != NULL && rightStr != NULL) {
+ int set = 0;
+ if(qos_isnum(leftStr) && qos_isnum(rightStr)) {
+ int left = atoi(leftStr);
+ int right = atoi(rightStr);
+ switch (b->cmp) {
+ case QS_CMP_EQ:
+ if(left == right) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_NE:
+ if(left != right) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_GT:
+ if(left > right) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_LT:
+ if(left < right) {
+ set = 1;
+ }
+ break;
+ }
+ } else {
+ int c = strcasecmp(leftStr, rightStr);
+ switch (b->cmp) {
+ case QS_CMP_EQ:
+ if(c == 0) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_NE:
+ if(c != 0) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_GT:
+ if(c < 0) {
+ set = 1;
+ }
+ break;
+ case QS_CMP_LT:
+ if(c > 0) {
+ set = 1;
+ }
+ break;
+ }
+ }
+ if(set) {
+ if(b->variable[0] == '!') {
+ apr_table_unset(r->subprocess_env, &b->variable[1]);
+ } else {
+ apr_table_set(r->subprocess_env, b->variable, b->value);
+ }
+ }
+ }
+ }
+ return;
+}
+
+/*
+ * QS_EventPerSecLimit/QS_EventKBytesPerSecLimit
+ * returns the max req_per_sec_block_rate/kbytes_per_sec_limit and the event
+ * with the lowest kbytes_per_sec_limit.
+ */
+static qs_acentry_t *qos_hp_event_count(request_rec *r,
+ int *req_per_sec_block,
+ apr_off_t *kbytes_per_sec_limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qs_actable_t *act = sconf->act;
+ qs_acentry_t *event_kbytes_limit = NULL;
+ *req_per_sec_block = 0;
+ *kbytes_per_sec_limit = 0;
+ if(act->has_events) {
+ qs_acentry_t *actEntry = act->entry;
+ if(actEntry) {
+ apr_global_mutex_lock(act->lock); /* @CRT12 */
+ while(actEntry) {
+ if(actEntry->event && (actEntry->limit == -1)) {
+ if(((actEntry->event[0] != '!') && apr_table_get(r->subprocess_env, actEntry->event)) ||
+ ((actEntry->event[0] == '!') && !apr_table_get(r->subprocess_env, &actEntry->event[1]))) {
+ if(actEntry->req_per_sec_limit) {
+ /* QS_EventPerSecLimit */
+ if(actEntry->req_per_sec_block_rate > *req_per_sec_block) {
+ *req_per_sec_block = actEntry->req_per_sec_block_rate;
+ }
+ } else {
+ /* QS_EventKBytesPerSecLimit */
+ if(actEntry->kbytes_per_sec_limit) {
+ if((*kbytes_per_sec_limit == 0) ||
+ (actEntry->kbytes_per_sec_limit < *kbytes_per_sec_limit)) {
+ *kbytes_per_sec_limit = actEntry->kbytes_per_sec_limit;
+ event_kbytes_limit = actEntry;
+ }
+ }
+ }
+ }
+ }
+ actEntry = actEntry->next;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT12 */
+ }
+ }
+ return event_kbytes_limit;
+}
+
+static apr_size_t qos_packet_rate(qos_ifctx_t *inctx, apr_bucket_brigade *bb) {
+ apr_bucket *b;
+ apr_size_t total = 0;
+ for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ if(b->length) {
+ total = total + b->length;
+ }
+ }
+ return total;
+}
+
+/**
+ * start packet rate measure (if filter has not already been inserted)
+ */
+static void qos_pktrate_pc(conn_rec *connection, qos_srv_config *sconf) {
+ conn_rec *c = connection;
+ qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
+ if(inctx == NULL) {
+ inctx = qos_create_ifctx(c, sconf);
+ ap_add_input_filter("qos-in-filter", inctx, NULL, c);
+ }
+ inctx->lowrate = 0;
+}
+
+/**
+ * timeout control at process connection handler
+ */
+static void qos_timeout_pc(conn_rec *connection, qos_srv_config *sconf) {
+ conn_rec *c = connection;
+ qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
+ if(inctx) {
+ inctx->status = QS_CONN_STATE_HEAD;
+ inctx->time = time(NULL);
+ inctx->nbytes = 0;
+#if APR_HAS_THREADS
+ if(sconf->inctx_t && !sconf->inctx_t->exit && sconf->min_rate_off == 0) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT22 */
+ apr_table_setn(sconf->inctx_t->table,
+ QS_INCTX_ID,
+ (char *)inctx);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT22 */
+ }
+#endif
+ }
+}
+
+/**
+ * determines client behavior based on accessed content types
+ *
+ * @return 0=normal, -1=unknown (not enough data), >=1 abnormal
+ */
+static int qos_content_type(request_rec *r, qos_srv_config *sconf,
+ qos_s_t *s, qos_s_entry_t *e, int limit) {
+ int penalty = -1;
+ const char *ct = apr_table_get(r->headers_out, "Content-Type");
+ e->events++; // events counts requests and connections
+ if(r->status == 304) {
+ e->notmodified ++;
+ s->notmodified ++;
+ }
+ if(ct) {
+ if(ap_strcasestr(ct, "html")) {
+ e->events++; // learn faster if user requests HTML content (main pages)
+ e->html++;
+ s->html++;
+ goto end;
+ } else if(ap_strcasestr(ct, "image")) {
+ e->img++;
+ s->img++;
+ goto end;
+ } else if(ap_strcasestr(ct, "css")) {
+ e->cssjs++;
+ s->cssjs++;
+ goto end;
+ } else if(ap_strcasestr(ct, "javascript")) {
+ e->cssjs++;
+ s->cssjs++;
+ goto end;
+ }
+ }
+ e->other++;
+ s->other++;
+
+ end:
+ /* compare this client with other clients */
+ if(e->events > QOS_CC_BEHAVIOR_THR_SINGLE) {
+ penalty = 0;
+ if(limit &&
+ ((sconf->static_on == 1) ||
+ (s->html > QOS_CC_BEHAVIOR_THR && s->html && s->img && s->cssjs && s->other && s->notmodified))) {
+ int i;
+ unsigned int server[5];
+ unsigned int client[5];
+ // note: all e->* variables are initialized by "1" to avoid FPE
+ if(sconf->static_on == 1) {
+ /* use predefined value */
+ unsigned long e_all = e->html + e->img + e->cssjs + e->other + e->notmodified;
+ server[0] = sconf->static_html;
+ server[1] = sconf->static_cssjs;
+ server[2] = sconf->static_img;
+ server[3] = sconf->static_other;
+ server[4] = sconf->static_notmodified;
+ client[0] = 100 * e->html / e_all;
+ client[1] = 100 * e->cssjs / e_all;
+ client[2] = 100 * e->img / e_all;
+ client[3] = 100 * e->other / e_all;
+ client[4] = 100 * e->notmodified / e_all;
+ } else {
+ /* learn average */
+ unsigned long long s_all = s->html + s->img + s->cssjs + s->other + s->notmodified;
+ unsigned long e_all = e->html + e->img + e->cssjs + e->other + e->notmodified;
+ server[0] = 100 * s->html / s_all;
+ server[1] = 100 * s->cssjs / s_all;
+ server[2] = 100 * s->img / s_all;
+ server[3] = 100 * s->other / s_all;
+ server[4] = 100 * s->notmodified / s_all;
+ client[0] = 100 * e->html / e_all;
+ client[1] = 100 * e->cssjs / e_all;
+ client[2] = 100 * e->img / e_all;
+ client[3] = 100 * e->other / e_all;
+ client[4] = 100 * e->notmodified / e_all;
+ }
+ for(i = 0; i < 5; i++) {
+ if(client[i] > (server[i] + sconf->cc_tolerance)) {
+ penalty++;
+ } else {
+ if((server[i] > sconf->cc_tolerance) &&
+ (client[i] < (server[i] - sconf->cc_tolerance))) {
+ penalty++;
+ }
+ }
+ }
+ }
+ }
+ return penalty;
+}
+
+//static void qos_error_log(const char *file, int line, int level,
+// apr_status_t status, const server_rec *s,
+// const request_rec *r, apr_pool_t *pool,
+// const char *errstr) {
+// return;
+//}
+
+/**
+ * QS_EventLimitCount, detect/update only
+ */
+static void qos_logger_event_limit(request_rec *r, qos_srv_config *sconf) {
+ qs_actable_t *act = sconf->act;
+ if(act->event_entry && (sconf->event_limit_a->nelts > 0)) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ int i;
+ qos_event_limit_entry_t *actEntry = act->event_entry;
+ apr_global_mutex_lock(act->lock); /* @CRT42 */
+ for(i = 0; i < sconf->event_limit_a->nelts; i++) {
+ if(actEntry->action == QS_EVENT_ACTION_DENY) {
+ int decEvent = get_qs_event(r, actEntry->eventDecStr);
+ if(decEvent > 0) {
+ if(decEvent >= actEntry->limit) {
+ actEntry->limit = 0;
+ actEntry->limitTime = 0;
+ } else {
+ actEntry->limit = actEntry->limit - decEvent;
+ }
+ }
+ if(apr_table_get(r->subprocess_env, actEntry->env_var) != NULL) {
+ // increment only once
+ char *eventLimitId = apr_pstrcat(r->pool, QS_R013_ALREADY_BLOCKED, actEntry->env_var, NULL);
+ if(apr_table_get(r->notes, eventLimitId) == NULL) {
+ // reset required (expired)?
+ if(actEntry->limitTime + actEntry->seconds < now) {
+ actEntry->limit = 0;
+ actEntry->limitTime = 0;
+ }
+ /* increment limit event */
+ if(actEntry->limit < INT_MAX) {
+ actEntry->limit++;
+ }
+ if(actEntry->limit == 1) {
+ /* ... and start timer */
+ actEntry->limitTime = now;
+ }
+ }
+ }
+ }
+ // next rule
+ actEntry++;
+ }
+ apr_global_mutex_unlock(act->lock); /* @CRT42 */
+ }
+}
+
+/**
+ * client control rules at log transaction
+ */
+static void qos_logger_cc(request_rec *r, qos_srv_config *sconf, qs_req_ctx *rctx) {
+ int lowrate = 0;
+ int unusual_behavior = -1;
+ int block_event = get_qs_event(r, QS_BLOCK);
+ const char *block_seen = apr_table_get(r->subprocess_env, QS_BLOCK_SEEN);
+ int block_dec = get_qs_event(r, QS_BLOCK_DEC);
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ apr_time_t now = apr_time_sec(r->request_time);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t **clientEntryFromHdr = NULL; // client ip entry from header
+ qos_s_entry_t searchE;
+ qos_s_entry_t searchEFromHdr;
+
+ if(block_seen != NULL) {
+ block_event = 0;
+ }
+
+ if(sconf->qos_cc_prefer_limit || (sconf->req_rate != -1)) {
+ qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
+ if(inctx) {
+ if(inctx->lowrate > QS_PKT_RATE_TH) {
+ lowrate = inctx->lowrate;
+ }
+ if(inctx->lowrate != -1) {
+ inctx->lowrate = 0;
+ }
+ if(inctx->status > QS_CONN_STATE_NEW) {
+ inctx->r = NULL;
+ inctx->status = QS_CONN_STATE_KEEP;
+ }
+ if(inctx->shutdown) {
+ lowrate++;
+ inctx->shutdown = 0;
+ }
+ }
+ }
+
+ if(cconf) {
+ // works for real connections only (no HTTP/2)
+ searchE.ip6[0] = cconf->ip6[0];
+ searchE.ip6[1] = cconf->ip6[1];
+ } else {
+ // HTTP/2
+ apr_uint64_t ci6[2];
+ qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), ci6);
+ searchE.ip6[0] = ci6[0];
+ searchE.ip6[1] = ci6[1];
+ }
+ qos_get_clientIP(r, sconf, cconf, "logger", searchEFromHdr.ip6);
+
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT19 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ if(searchEFromHdr.ip6[0] == searchE.ip6[0] && searchEFromHdr.ip6[1] == searchE.ip6[1]) {
+ clientEntryFromHdr = clientEntry; // same as connection
+ } else {
+ clientEntryFromHdr = qos_cc_getOrSet(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
+ }
+
+ if(rctx->cc_event_req_set) {
+ /* QS_ClientEventRequestLimit */
+ qos_s_entry_t **eEvent = NULL;
+ rctx->cc_event_req_set = 0;
+ if(rctx->cc_event_ip[0] == searchE.ip6[0] &&
+ rctx->cc_event_ip[1] == searchE.ip6[1]) {
+ // connection ip
+ eEvent = clientEntry;
+ } else if(rctx->cc_event_ip[0] == searchEFromHdr.ip6[0] &&
+ rctx->cc_event_ip[1] == searchEFromHdr.ip6[1]) {
+ // from header
+ eEvent = clientEntryFromHdr;
+ } else {
+ // looks like the header has changed or is no longer available
+ qos_s_entry_t searchEvent;
+ searchEvent.ip6[0] = rctx->cc_event_ip[0];
+ searchEvent.ip6[1] = rctx->cc_event_ip[1];
+ eEvent = qos_cc_get0(u->qos_cc, &searchEvent, apr_time_sec(r->request_time));
+ }
+ if(eEvent) {
+ if((*eEvent)->event_req > 0) {
+ (*eEvent)->event_req--;
+ }
+ }
+ }
+
+ if(rctx->cc_serialize_set) {
+ /* QS_ClientSerialize */
+ qos_s_entry_t **eSerialize = NULL;
+ rctx->cc_serialize_set = 0;
+ if(rctx->cc_serialize_ip[0] == searchE.ip6[0] &&
+ rctx->cc_serialize_ip[1] == searchE.ip6[1]) {
+ // connection ip
+ eSerialize = clientEntry;
+ } else if(rctx->cc_serialize_ip[0] == searchEFromHdr.ip6[0] &&
+ rctx->cc_serialize_ip[1] == searchEFromHdr.ip6[1]) {
+ // from header
+ eSerialize = clientEntryFromHdr;
+ } else {
+ // looks like the header has changed or is no longer available
+ qos_s_entry_t searchSerialize;
+ searchSerialize.ip6[0] = rctx->cc_serialize_ip[0];
+ searchSerialize.ip6[1] = rctx->cc_serialize_ip[1];
+ eSerialize = qos_cc_get0(u->qos_cc, &searchSerialize, apr_time_sec(r->request_time));
+ }
+ if(eSerialize) {
+ (*eSerialize)->serialize = 0;
+ }
+ }
+
+ if(sconf->qos_cc_prefer) {
+ // QS_ClientPrefer is not enabled
+ unusual_behavior = qos_content_type(r, sconf, u->qos_cc, *clientEntry, sconf->qos_cc_prefer_limit);
+ if(unusual_behavior == 0) {
+ // normal behavior
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_BEHAVIOR_OK;
+ (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_BAD;
+ } else {
+ // unknown or bad behavior
+ (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_OK;
+ }
+ }
+
+ if(block_event || block_dec || lowrate || (unusual_behavior > 0)) {
+ if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) < now) {
+ /* reset expired events */
+ if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
+ // write remaining log lines
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->connection->base_server,
+ QOS_LOG_PFX(060)"access denied (previously), "
+ "QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, "
+ "message repeated %d times, "
+ "c=%s",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ (*clientEntry)->blockMsg % QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(r->connection)/*no id here, this r is okay*/);
+ QS_INC_EVENT_LOCKED(sconf, 60);
+ (*clientEntry)->blockMsg = 0;
+ }
+ (*clientEntry)->block = 0;
+ (*clientEntry)->blockTime = 0;
+ }
+ /* mark lowpkt client */
+ if(lowrate || (unusual_behavior > 0)) {
+ (*clientEntry)->lowrate = apr_time_sec(r->request_time);
+ if(lowrate) {
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_PKGRATE;
+ }
+ if(unusual_behavior > 1) {
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_BEHAVIOR_BAD;
+ (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_OK;
+ }
+ qs_set_evmsg(r, "r;");
+ }
+ if(block_dec > 0) {
+ if(block_dec >= (*clientEntry)->block) {
+ (*clientEntry)->block = 0;
+ (*clientEntry)->blockTime = 0;
+ } else {
+ (*clientEntry)->block = (*clientEntry)->block - block_dec;
+ }
+ }
+ if(block_event) {
+ int newValue = (*clientEntry)->block + block_event;
+ if((*clientEntry)->block == 0) {
+ /* start timer */
+ (*clientEntry)->blockTime = now;
+ }
+ /* ...and increment/increase block event counter */
+ (*clientEntry)->block = newValue > USHRT_MAX ? USHRT_MAX : newValue;
+ }
+ } else if((*clientEntry)->lowrate) {
+ /* reset low prio client after 24h (resp. QOS_LOW_TIMEOUT seconds) */
+ if(((*clientEntry)->lowrate + QOS_LOW_TIMEOUT) < now) {
+ (*clientEntry)->lowrate = 0;
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
+ (*clientEntry)->lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK;
+ } else {
+ (*clientEntry)->lowratestatus = 0;
+ }
+ }
+ }
+
+ /* QS_Limit* */
+ if(u->qos_cc->limitTable) {
+ int limitTableIndex;
+ apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
+ for(limitTableIndex = 0;
+ limitTableIndex < apr_table_elts(u->qos_cc->limitTable)->nelts;
+ limitTableIndex++) {
+ int eventSet = 0;
+ const char *eventName = limitTableEntry[limitTableIndex].key;
+ qos_s_entry_limit_conf_t *eventLimitConf = (qos_s_entry_limit_conf_t *)limitTableEntry[limitTableIndex].val;
+ const char *clearEvent = apr_table_get(r->subprocess_env, eventLimitConf->eventClearStr);
+ int decEvent = get_qs_event(r, eventLimitConf->eventDecStr);
+
+ /*
+ * reset expired events, clear event counter, decrement event counter
+ */
+ if(clearEvent ||
+ (((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) < now)) {
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
+ (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
+ }
+ if(decEvent > 0) {
+ if(decEvent >= (*clientEntryFromHdr)->limit[limitTableIndex].limit) {
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
+ (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
+ } else {
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = (*clientEntryFromHdr)->limit[limitTableIndex].limit - decEvent;
+ }
+ }
+
+ /*
+ * check for new events
+ */
+ eventSet = get_qs_event(r, eventName);
+ if(eventSet) {
+ char *seenEvent;
+ if(strcasecmp(eventName, QS_LIMIT_DEFAULT) == 0) {
+ // backward compat/event forwarding
+ seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, NULL);
+ } else {
+ seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, eventName, NULL);
+ }
+ if(apr_table_get(r->subprocess_env, seenEvent) == NULL) {
+ int newValue = (*clientEntryFromHdr)->limit[limitTableIndex].limit + eventSet;
+ /* only once per request */
+ apr_table_set(r->subprocess_env, seenEvent, "");
+ if((*clientEntryFromHdr)->limit[limitTableIndex].limit == 0) {
+ /* start timer... */
+ (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = now;
+ }
+ /* ... and increase limit event */
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = newValue > USHRT_MAX ? USHRT_MAX : newValue;
+ }
+ }
+ }
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT19 */
+ if(block_event) {
+ /* only once per request */
+ apr_table_set(r->subprocess_env, QS_BLOCK_SEEN, "");
+ apr_table_set(r->connection->notes, QS_BLOCK_SEEN, "");
+ }
+}
+
+/**
+ * client control rules at header parser
+ */
+static int qos_hp_cc(request_rec *r, qos_srv_config *sconf, char **msg, char **uid) {
+ int ret = DECLINED;
+ if(sconf->has_qos_cc) {
+ int req_per_sec_block_rate = 0;
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t **clientEntryFromHdr = NULL;
+ qos_s_entry_t searchE;
+ qos_s_entry_t searchEFromHdr;
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ const char *forwardedForLogIP = QS_CONN_REMOTEIP(r->connection);
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ int excludeFromBlock = qos_is_excluded_ip(r->connection, sconf->cc_exclude_ip);
+ if(cconf) {
+ searchE.ip6[0] = cconf->ip6[0];
+ searchE.ip6[1] = cconf->ip6[1];
+ } else {
+ // HTTP/2
+ apr_uint64_t ci6[2];
+ qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), ci6);
+ searchE.ip6[0] = ci6[0];
+ searchE.ip6[1] = ci6[1];
+ }
+
+ forwardedForLogIP = qos_get_clientIP(r, sconf, cconf, "hp",
+ searchEFromHdr.ip6);
+
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT17 */
+ clientEntry = qos_cc_get0(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ if(!clientEntry) {
+ clientEntry = qos_cc_set(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ } else {
+ /* update time */
+ (*clientEntry)->time = apr_time_sec(r->request_time);
+ }
+ if(searchEFromHdr.ip6[0] == searchE.ip6[0] && searchEFromHdr.ip6[1] == searchE.ip6[1]) {
+ clientEntryFromHdr = clientEntry; // same as connection
+ } else {
+ clientEntryFromHdr = qos_cc_get0(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
+ if(!clientEntryFromHdr) {
+ clientEntryFromHdr = qos_cc_set(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
+ } else {
+ /* update time */
+ (*clientEntryFromHdr)->time = apr_time_sec(r->request_time);
+ }
+ }
+ if(sconf->qos_cc_event) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ const char *v = apr_table_get(r->subprocess_env, QS_EVENT);
+ if(v) {
+ if((*clientEntry)->req < LONG_MAX) {
+ (*clientEntry)->req++;
+ }
+ if(now > (*clientEntry)->interval + QS_BW_SAMPLING_RATE) {
+ /* calc req/sec */
+ (*clientEntry)->req_per_sec = (*clientEntry)->req / (now - (*clientEntry)->interval);
+ (*clientEntry)->req = 0;
+ (*clientEntry)->interval = now;
+ /* calc block rate */
+ if((*clientEntry)->req_per_sec > sconf->qos_cc_event) {
+ int factor = (((*clientEntry)->req_per_sec * 100) / sconf->qos_cc_event) - 100;
+ (*clientEntry)->req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate + factor;
+ if((*clientEntry)->req_per_sec_block_rate > QS_MAX_DELAY/1000) {
+ (*clientEntry)->req_per_sec_block_rate = QS_MAX_DELAY/1000;
+ }
+ /* QS_ClientEventPerSecLimit */
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ QOS_LOG_PFX(061)"request rate limit,"
+ " rule: "QS_EVENT"(%d), req/sec=%ld,"
+ " delay=%dms%s",
+ sconf->qos_cc_event,
+ (*clientEntry)->req_per_sec, (*clientEntry)->req_per_sec_block_rate,
+ (*clientEntry)->req_per_sec_block_rate == QS_MAX_DELAY/1000 ? " (max)" : "");
+ QS_INC_EVENT_LOCKED(sconf, 61);
+ } else if((*clientEntry)->req_per_sec_block_rate > 0) {
+ if((*clientEntry)->req_per_sec_block_rate < 50) {
+ (*clientEntry)->req_per_sec_block_rate = 0;
+ } else {
+ int factor = (*clientEntry)->req_per_sec_block_rate / 4;
+ (*clientEntry)->req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate - factor;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
+ QOS_LOG_PFX(062)"request rate limit,"
+ " rule: "QS_EVENT"(%d), req/sec=%ld,"
+ " delay=%dms",
+ sconf->qos_cc_event,
+ (*clientEntry)->req_per_sec, (*clientEntry)->req_per_sec_block_rate);
+ QS_INC_EVENT_LOCKED(sconf, 62);
+ }
+ }
+ req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate;
+ }
+ }
+ if(sconf->qos_cc_block && !excludeFromBlock) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ int block_event = get_qs_event(r, QS_BLOCK);
+ if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) < now) {
+ /* reset expired events */
+ if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
+ // write remaining log lines
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->connection->base_server,
+ QOS_LOG_PFX(060)"access denied (previously), "
+ "QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, "
+ "message repeated %d times, "
+ "c=%s",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ (*clientEntry)->blockMsg % QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(r->connection)/*no id here, this r is okay*/);
+ QS_INC_EVENT_LOCKED(sconf, 60);
+ (*clientEntry)->blockMsg = 0;
+ }
+ (*clientEntry)->block = 0;
+ (*clientEntry)->blockTime = 0;
+ }
+ if(block_event) {
+ if((*clientEntry)->block == 0) {
+ /* start timer */
+ (*clientEntry)->blockTime = now;
+ }
+ /* ... and increment block event */
+ (*clientEntry)->block += block_event;
+ /* only once per request */
+ apr_table_set(r->subprocess_env, QS_BLOCK_SEEN, "");
+ apr_table_set(r->connection->notes, QS_BLOCK_SEEN, "");
+ }
+ if((*clientEntry)->block >= sconf->qos_cc_block) {
+ *uid = apr_pstrdup(r->connection->pool, "060");
+ *msg = apr_psprintf(r->connection->pool,
+ QOS_LOG_PFX(060)"access denied%s, "
+ "QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ now - (*clientEntry)->blockTime,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(r->connection));
+ QS_INC_EVENT_LOCKED(sconf, 60);
+ ret = m_retcode;
+ (*clientEntry)->lowrate = apr_time_sec(r->request_time);
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_EVENTBLOCK;
+ qs_set_evmsg(r, "r;");
+ }
+ }
+ if(u->qos_cc->limitTable) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ int limitTableIndex;
+ apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
+ for(limitTableIndex = 0;
+ limitTableIndex < apr_table_elts(u->qos_cc->limitTable)->nelts;
+ limitTableIndex++) {
+ time_t remaining = 0;
+ int eventSet = 0;
+ const char *eventName = limitTableEntry[limitTableIndex].key;
+ qos_s_entry_limit_conf_t *eventLimitConf = (qos_s_entry_limit_conf_t *)limitTableEntry[limitTableIndex].val;
+ const char *clearEvent = apr_table_get(r->subprocess_env, eventLimitConf->eventClearStr);
+
+ /*
+ * reset expired events or clear event counter
+ */
+ if(clearEvent ||
+ (((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) < now)) {
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
+ (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
+ }
+
+ /*
+ * check for new events
+ */
+ eventSet = get_qs_event(r, eventName);
+ if(eventSet) {
+ char *seenEvent;
+ if(strcasecmp(eventName, QS_LIMIT_DEFAULT) == 0) {
+ // backward compat/event forwarding
+ seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, NULL);
+ } else {
+ seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, eventName, NULL);
+ }
+ if(apr_table_get(r->subprocess_env, seenEvent) == NULL) {
+ int newValue = (*clientEntryFromHdr)->limit[limitTableIndex].limit + eventSet;
+ // first occurrence
+ apr_table_set(r->subprocess_env, seenEvent, "");
+ if((*clientEntryFromHdr)->limit[limitTableIndex].limit == 0) {
+ /* .start timer */
+ (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = now;
+ }
+ /* increment limit event */
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit = newValue > USHRT_MAX ? USHRT_MAX : newValue;
+ }
+ }
+
+ /*
+ * propagate to env
+ */
+ apr_table_set(r->subprocess_env,
+ apr_pstrcat(r->pool, QS_LIMIT_NAME_PFX, eventName, NULL),
+ eventName);
+ apr_table_set(r->subprocess_env,
+ apr_pstrcat(r->pool, eventName, QS_COUNTER_SUFFIX, NULL),
+ apr_psprintf(r->pool, "%d", (*clientEntryFromHdr)->limit[limitTableIndex].limit));
+
+ remaining = ((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) - now;
+ apr_table_set(r->subprocess_env,
+ apr_pstrcat(r->pool, eventName, QS_LIMIT_REMAINING, NULL),
+ apr_psprintf(r->pool, "%"APR_TIME_T_FMT, remaining > 0 ? remaining : 0));
+
+ /*
+ * enforce limit
+ */
+ if((*clientEntryFromHdr)->limit[limitTableIndex].limit >= eventLimitConf->limit) {
+ int block = 1;
+ char *conditional = "";
+ if(eventLimitConf->condStr != NULL) {
+ // conditional enforcement...
+ const char *condition = apr_table_get(r->subprocess_env, QS_COND);
+ conditional = apr_pstrdup(r->pool, "Cond");
+ if(condition == NULL) {
+ block = 0; // variable not set
+ } else {
+ if(ap_regexec(eventLimitConf->preg, condition, 0, NULL, 0) != 0) {
+ block = 0; // pattern does not match
+ }
+ }
+ }
+ if(block) {
+ if(ret == DECLINED || clientEntryFromHdr != clientEntry) {
+ /* log only one error (either block or limit) */
+ *uid = apr_pstrdup(r->connection->pool, "067");
+ *msg = apr_psprintf(r->connection->pool,
+ QOS_LOG_PFX(067)"access denied%s, "
+ "QS_%sClientEventLimitCount rule: "
+ "event=%s, "
+ "max=%hu, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
+ sconf->log_only ? " (log only)" : "",
+ conditional,
+ eventName,
+ eventLimitConf->limit,
+ (*clientEntryFromHdr)->limit[limitTableIndex].limit,
+ now - (*clientEntryFromHdr)->limit[limitTableIndex].limitTime,
+ forwardedForLogIP == NULL ? "-" : forwardedForLogIP);
+ QS_INC_EVENT_LOCKED(sconf, 67);
+ ret = m_retcode;
+ }
+ }
+ if(clientEntryFromHdr == clientEntry) {
+ (*clientEntry)->lowrate = apr_time_sec(r->request_time);
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_EVENTLIMIT;
+ qs_set_evmsg(r, "r;");
+ }
+ }
+ }
+ }
+
+ /* reset low prio client after 24h */
+ if(clientEntry && sconf->qos_cc_prefer) {
+ apr_time_t now = apr_time_sec(r->request_time);
+ if(((*clientEntry)->lowrate + QOS_LOW_TIMEOUT) < now) {
+ (*clientEntry)->lowrate = 0;
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
+ (*clientEntry)->lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK;
+ } else {
+ (*clientEntry)->lowratestatus = 0;
+ }
+ }
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT17 */
+
+ if(req_per_sec_block_rate) {
+ qs_set_evmsg(r, "L;");
+ if(!sconf->log_only) {
+ apr_sleep(req_per_sec_block_rate*1000);
+ }
+ }
+ }
+ return ret;
+}
+
+#if APR_HAS_THREADS
+static apr_status_t qos_cleanup_status_thread(void *selfv) {
+ qsstatus_t *s = selfv;
+ s->exit = 1;
+ /* may long up to 100ms */
+ if(m_threaded_mpm) {
+ apr_status_t status;
+ apr_thread_join(&status, s->thread);
+ //apr_pool_destroy(s->pool);
+ }
+ return APR_SUCCESS;
+}
+
+// checks if connection counting is enabled (any host)
+static int qos_count_connections(qos_srv_config *sconf) {
+ server_rec *s = sconf->base_server;
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(QS_COUNT_CONNECTIONS(bsconf)) {
+ return 1;
+ }
+ s = s->next;
+ while(s) {
+ qos_srv_config *sc = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(QS_COUNT_CONNECTIONS(sc)) {
+ return 1;
+ }
+ s = s->next;
+ }
+ return 0;
+}
+
+// total (server/all hosts) connections
+static int qos_server_connections(qos_srv_config *sconf) {
+ server_rec *s = sconf->base_server;
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ int connections = bsconf->act->conn->connections;
+ s = s->next;
+ while(s) {
+ qos_srv_config *sc = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(sc->act->conn != bsconf->act->conn) {
+ // server has either his own counter or the base server's counter
+ connections += sc->act->conn->connections;
+ }
+ s = s->next;
+ }
+ return connections;
+ /*
+ int i, j;
+ worker_score *ws_record;
+ process_score *ps_record;
+ for(i = 0; i < sconf->server_limit; ++i) {
+ ps_record = ap_get_scoreboard_process(i);
+ for(j = 0; j < sconf->thread_limit; ++j) {
+ ws_record = ap_get_scoreboard_worker(i, j);
+ if(!ps_record->quiescing && ps_record->pid) {
+ if(ws_record->status == SERVER_READY && ps_record->generation == qos_my_generation) {
+ ready++;
+ }
+ }
+ }
+ }
+ */
+}
+
+/**
+ * Status logger thread
+ *
+ * @param thread
+ * @param selfv Base server_rec
+ */
+static void *APR_THREAD_FUNC qos_status_thread(apr_thread_t *thread, void *selfv) {
+ qsstatus_t *s = selfv;
+ int server_limit, thread_limit;
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
+ while(!s->exit) {
+ char clientContentTypes[8192];
+ char allConn[64];
+ int s_busy = 0;
+ int s_open = 0;
+ int s_ready = 0;
+ int s_read = 0;
+ int s_write = 0;
+ int s_keep = 0;
+ int s_start = 0;
+ int s_log = 0;
+ int s_dns = 0;
+ int s_closing = 0;
+ int s_usr1 = 0;
+ int s_kill = 0;
+ worker_score ws_record;
+ int c, i, e;
+ time_t now = time(NULL);
+ int run = 0;
+ e = 60 - (now % 60);
+ e = e * 10;
+ for(c = 0; c < e; c++) {
+ apr_sleep(100000);
+ if(s->exit) {
+ run = 0;
+ break;
+ }
+ }
+ if(!s->exit) {
+ apr_global_mutex_lock(s->lock); /* @CRT47 */
+ now = time(NULL);
+ if(*s->qsstatustimer <= now) {
+ // set next and fetch the data
+ *s->qsstatustimer = (now/60*60 + 60);
+ run = 1;
+ } else {
+ // another child already did the job
+ run = 0;
+ }
+ apr_global_mutex_unlock(s->lock); /* @CRT47 */
+ }
+ if(!s->exit && run) {
+ for(i = 0; i < server_limit; ++i) {
+ int j;
+ for(j = 0; j < thread_limit; ++j) {
+ int res;
+ ap_copy_scoreboard_worker(&ws_record, i, j);
+ res = ws_record.status;
+ if(res == SERVER_DEAD) {
+ s_open++;
+ } else if(res == SERVER_READY) {
+ s_ready++;
+ } else if(res == SERVER_BUSY_READ) {
+ s_read++;
+ s_busy++;
+ } else if(res == SERVER_BUSY_WRITE) {
+ s_write++;
+ s_busy++;
+ } else if(res == SERVER_BUSY_KEEPALIVE) {
+ s_keep++;
+ s_busy++;
+ } else if(res == SERVER_STARTING) {
+ s_start++;
+ } else if(res == SERVER_BUSY_LOG) {
+ s_log++;
+ s_busy++;
+ } else if(res == SERVER_BUSY_DNS) {
+ s_dns++;
+ s_busy++;
+ } else if(res == SERVER_CLOSING) {
+ s_closing++;
+ } else if(res == SERVER_GRACEFUL) {
+ s_usr1++;
+ } else if(res == SERVER_IDLE_KILL) {
+ s_kill++;
+ }
+ }
+ }
+ clientContentTypes[0] = '\0';
+ if(s->sconf->qos_cc_prefer) {
+ qos_user_t *u = qos_get_user_conf(s->sconf->act->ppool);
+ if(u) {
+ unsigned long long html;
+ unsigned long long cssjs;
+ unsigned long long img;
+ unsigned long long other;
+ unsigned long long notmodified;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT48 */
+ html = u->qos_cc->html;
+ cssjs = u->qos_cc->cssjs;
+ img = u->qos_cc->img;
+ other = u->qos_cc->other;
+ notmodified = u->qos_cc->notmodified;
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT48 */
+ snprintf(clientContentTypes, 8191, ", \"clientContentTypes\": { "
+ "\"html\": %llu, \"css/js\": %llu,"
+ " \"images\": %llu, \"other\": %llu, \"304\": %llu }",
+ html, cssjs,
+ img, other, notmodified
+ );
+ }
+ }
+ allConn[0] = '\0';
+ if(qos_count_connections(s->sconf)) {
+ apr_global_mutex_lock(s->lock); /* @CRT52 */
+ int all_connections = qos_server_connections(s->sconf);
+ snprintf(allConn, 64, ", \"QS_AllConn\": %d",
+ all_connections
+ );
+ apr_global_mutex_unlock(s->lock); /* @CRT52 */
+ }
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s->sconf->base_server,
+ QOS_LOG_PFX(200)"{ \"scoreboard\": { "
+ "\"open\": %d, \"waiting\": %d, \"read\": %d, "
+ "\"write\": %d, \"keepalive\": %d, "
+ "\"start\": %d, \"log\": %d, "
+ "\"dns\": %d, \"closing\": %d, "
+ "\"finishing\": %d, \"idle\": %d }, "
+ "\"maxclients\": { "
+ "\"max\": %d, \"busy\": %d"
+ "%s }"
+ "%s }",
+ s_open, s_ready, s_read,
+ s_write, s_keep,
+ s_start, s_log,
+ s_dns, s_closing,
+ s_usr1, s_kill,
+ s->maxclients, s_busy,
+ allConn,
+ clientContentTypes);
+ }
+ }
+ if(m_threaded_mpm) {
+ apr_thread_exit(thread, APR_SUCCESS);
+ }
+ return NULL;
+}
+
+/**
+ * Starts the stats logger thread
+ */
+static void qos_init_status_thread(apr_pool_t *p, qos_srv_config *sconf, int maxclients) {
+ qs_actable_t *act = sconf->act;
+ apr_pool_t *pool;
+ apr_threadattr_t *tattr;
+ qsstatus_t *s;
+ apr_pool_create(&pool, NULL);
+ s = apr_pcalloc(pool, sizeof(qsstatus_t));
+ s->exit = 0;
+ s->pool = pool;
+ s->maxclients = maxclients;
+ s->qsstatustimer = act->qsstatustimer;
+ s->lock = act->lock;
+ s->sconf = sconf;
+ if(apr_threadattr_create(&tattr, pool) == APR_SUCCESS) {
+ if(apr_thread_create(&s->thread, tattr, qos_status_thread, s, pool) == APR_SUCCESS) {
+ apr_pool_pre_cleanup_register(p, s, qos_cleanup_status_thread);
+ }
+ }
+}
+#endif
+
+/**
+ * client control rules at process connection handler
+ */
+static int qos_cc_pc_filter(conn_rec *connection, qs_conn_ctx *cconf, qos_user_t *u, char **msg) {
+ conn_rec *c = connection;
+ int ret = DECLINED;
+ if(cconf && cconf->sconf->has_qos_cc) {
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ searchE.ip6[0] = cconf->ip6[0];
+ searchE.ip6[1] = cconf->ip6[1];
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT14 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+
+ /* early vip detection */
+ if((*clientEntry)->vip) {
+ cconf->is_vip = 1;
+ apr_table_set(c->notes, QS_ISVIPREQ, "yes");
+ }
+
+ /* max connections */
+ if(cconf->sconf->has_qos_cc && cconf->sconf->qos_cc_prefer) {
+ if(m_generation != u->qos_cc->generation_locked) {
+ u->qos_cc->connections++;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, c->base_server,
+ QOS_LOG_PFX(166)"unexpected connection dispatching, skipping"
+ " connection counter update for QS_ClientPrefer rule, c=%s",
+ QS_CONN_REMOTEIP(cconf->mc) == NULL ? "-" :
+ QS_CONN_REMOTEIP(cconf->mc));
+ }
+ if((*clientEntry)->lowrate) {
+ if(c->notes) {
+ char *flags = apr_psprintf(c->pool, "0x%02x", (*clientEntry)->lowratestatus);
+ apr_table_set(c->notes, "QS_ClientLowPrio", flags);
+ }
+ }
+ /* non vip (allow all vip addresses - no restrictions) */
+ if(!(*clientEntry)->vip) {
+ if(u->qos_cc->connections > cconf->sconf->qos_cc_prefer_limit) {
+ int penalty = 4; // 2 to 12 points
+ int reqSpare = 0;
+ if((*clientEntry)->lowrate) {
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_PKGRATE) {
+ penalty++;
+ }
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_BAD) {
+ penalty+=2;
+ }
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_EVENTBLOCK) {
+ penalty+=2;
+ }
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_EVENTLIMIT) {
+ penalty+=2;
+ }
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_TIMEOUT) {
+ penalty++;
+ }
+ }
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
+ // normal behavior
+ penalty-=2;
+ }
+ // calculate the min. required free connections allowing us to serve request by this IP
+ reqSpare = (cconf->sconf->max_clients - cconf->sconf->qos_cc_prefer_limit) * penalty / 12;
+ if((cconf->sconf->max_clients - u->qos_cc->connections) < reqSpare) {
+ /* not enough free connections */
+ if(u->qos_cc->connections > cconf->sconf->qos_cc_prefer_limit) {
+ *msg = apr_psprintf(cconf->mc->pool,
+ QOS_LOG_PFX(066)"access denied%s, "
+ "QS_ClientPrefer rule (penalty=%d 0x%02x): "
+ "max=%d, concurrent connections=%d, c=%s",
+ cconf->sconf->log_only ? " (log only)" : "",
+ penalty, (*clientEntry)->lowratestatus,
+ cconf->sconf->qos_cc_prefer_limit, u->qos_cc->connections,
+ QS_CONN_REMOTEIP(cconf->mc) == NULL ? "-" :
+ QS_CONN_REMOTEIP(cconf->mc));
+ QS_INC_EVENT_LOCKED(cconf->sconf, 66);
+ ret = m_retcode;
+ }
+ }
+ }
+ }
+ }
+
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT14 */
+ }
+ return ret;
+}
+
+/**
+ * calculates the current minimal up/download bandwidth
+ */
+static int qos_req_rate_calc(qos_srv_config *sconf, int *current) {
+ int req_rate = sconf->req_rate;
+ if(sconf->min_rate_max != -1) {
+ int connections = qos_server_connections(sconf);
+ if(connections > sconf->req_rate_start) {
+ /* keep the minimal rate until reaching the min connections */
+ req_rate = req_rate + (sconf->min_rate_max * connections / sconf->max_clients);
+ if(connections > sconf->max_clients) {
+ // limit the max rate to its max if we have more connections then expected
+ if(connections > (sconf->max_clients + QS_DOUBLE_CONN_H)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, sconf->base_server,
+ QOS_LOG_PFX(036)"QS_SrvMinDataRate: unexpected connection status!"
+ " connections=%d,"
+ " cal. request rate=%d,"
+ " max. limit=%d."
+ " Check log for unclean child exit and consider"
+ " to do a graceful server restart if this condition persists."
+ " You might also increase the number of supported connections"
+ " using the 'QS_MaxClients' directive.",
+ connections, req_rate, sconf->min_rate_max);
+ }
+ QS_INC_EVENT(sconf, 36);
+ req_rate = sconf->min_rate_max;
+ }
+ }
+ *current = connections;
+ }
+ return req_rate;
+}
+
+qos_s_entry_limit_conf_t *qos_getQSLimitEvent(qos_user_t *u, const char *event,
+ int *limitTableIndex) {
+ int i = 0;
+ apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
+ for(i = 0; i < apr_table_elts(u->qos_cc->limitTable)->nelts; i++) {
+ const char *eventName = limitTableEntry[i].key;
+ if(strcasecmp(eventName, event) == 0) {
+ *limitTableIndex = i;
+ return (qos_s_entry_limit_conf_t *)limitTableEntry[i].val;
+ }
+ }
+ return NULL;
+}
+
+/************************************************************************
+ * "public"
+ ***********************************************************************/
+
+/**
+ * short status viewer
+ */
+static void qos_ext_status_short(request_rec *r, apr_table_t *qt) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ server_rec *s = sconf->base_server;
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config,
+ &qos_module);
+ const char *option = apr_table_get(qt, "option");
+ const char *all_connections = apr_table_get(r->subprocess_env, "QS_AllConn");
+ apr_time_t now = apr_time_sec(r->request_time);
+ double av[1];
+
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+
+#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
+ getloadavg(av, 1);
+ ap_rprintf(r, "b"QOS_DELIM"system.load: %.2f\n", av[0]);
+#endif
+
+ if(u->qos_cc && sconf->qos_cc_prefer) {
+ unsigned long long html;
+ unsigned long long cssjs;
+ unsigned long long img;
+ unsigned long long other;
+ unsigned long long notmodified;
+ char clientContentTypes[8192];
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT51 */
+ html = u->qos_cc->html;
+ cssjs = u->qos_cc->cssjs;
+ img = u->qos_cc->img;
+ other = u->qos_cc->other;
+ notmodified = u->qos_cc->notmodified;
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT51 */
+ snprintf(clientContentTypes, 8191,
+ "b;clientContentTypes(html,css/js,images,other,304): "
+ "%llu %llu %llu %llu %llu",
+ html, cssjs, img, other, notmodified
+ );
+ ap_rprintf(r, "%s\n", clientContentTypes);
+ }
+ while(s) {
+ char *sn = apr_psprintf(r->pool, "%s"QOS_DELIM"%s"QOS_DELIM"%d",
+ s->is_virtual ? "v" : "b",
+ s->server_hostname == NULL ? "-" :
+ ap_escape_html(r->pool, s->server_hostname),
+ s->addrs->host_port);
+ sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(all_connections && !s->is_virtual) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_AllConn: %s\n", sn, all_connections);
+ }
+ if((s->is_virtual && (sconf != bsconf)) || !s->is_virtual) {
+ qs_acentry_t *actEntry;
+ if(!s->is_virtual && sconf->has_qos_cc && sconf->qos_cc_prefer_limit) {
+ int hc = u->qos_cc->connections; /* not synchronized ... */
+ ap_rprintf(r, "%s"QOS_DELIM"QS_ClientPrefer"QOS_DELIM"%d[]: %d\n", sn,
+ sconf->qos_cc_prefer_limit, hc);
+ }
+ /* request level */
+ actEntry = sconf->act->entry;
+ while(actEntry) {
+ if((actEntry->limit > 0) && !actEntry->condition && !actEntry->event) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_LocRequestLimit%s"QOS_DELIM"%d[%s]: %d\n", sn,
+ actEntry->regex == NULL ? "" : "Match",
+ actEntry->limit,
+ actEntry->url,
+ actEntry->counter);
+ }
+ if((actEntry->req_per_sec_limit > 0) && !actEntry->event) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_LocRequestPerSecLimit%s"QOS_DELIM"%ld[%s]: %ld\n", sn,
+ actEntry->regex == NULL ? "" : "Match",
+ actEntry->req_per_sec_limit,
+ actEntry->url,
+ actEntry->req_per_sec);
+ }
+ if((actEntry->kbytes_per_sec_limit > 0) && !actEntry->event) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_LocKBytesPerSecLimit%s"QOS_DELIM"%"APR_OFF_T_FMT"[%s]: %"APR_OFF_T_FMT"\n", sn,
+ actEntry->regex == NULL ? "" : "Match",
+ actEntry->kbytes_per_sec_limit,
+ actEntry->url,
+ actEntry->kbytes_per_sec);
+ }
+ if(actEntry->condition && !actEntry->event) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_CondLocRequestLimitMatch"QOS_DELIM"%d[%s]: %d\n", sn,
+ actEntry->limit,
+ actEntry->url,
+ actEntry->counter);
+ }
+ if(actEntry->event && (actEntry->limit != -1)) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_EventRequestLimit"QOS_DELIM"%d[%s]: %d\n", sn,
+ actEntry->limit,
+ actEntry->url,
+ actEntry->counter);
+ }
+ if(actEntry->event && (actEntry->kbytes_per_sec_limit != 0)) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_EventKBytesPerSecLimit"QOS_DELIM"%"APR_OFF_T_FMT"[%s]: %"APR_OFF_T_FMT"\n", sn,
+ actEntry->kbytes_per_sec_limit,
+ actEntry->url,
+ now > (apr_time_sec(actEntry->kbytes_interval_us) + (QS_BW_SAMPLING_RATE*10)) ? 0 : actEntry->kbytes_per_sec);
+ }
+ if(actEntry->event && (actEntry->req_per_sec_limit > 0)) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_EventPerSecLimit"QOS_DELIM"%ld[%s]: %ld\n", sn,
+ actEntry->req_per_sec_limit,
+ actEntry->url,
+ now > (actEntry->interval + (QS_BW_SAMPLING_RATE*3)) ? 0 : actEntry->req_per_sec);
+ }
+ actEntry = actEntry->next;
+ }
+ /* event limit */
+ if(sconf->event_limit_a->nelts > 0) {
+ int ie = 0;
+ qos_event_limit_entry_t *event_limit = sconf->act->event_entry;
+ for(ie = 0; ie < sconf->event_limit_a->nelts; ie++) {
+ int elimit = event_limit->limit;
+ if(event_limit->limitTime + event_limit->seconds <= now) {
+ elimit = 0;
+ }
+ if(event_limit->action == QS_EVENT_ACTION_DENY) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_%sEventLimitCount"QOS_DELIM"%d/%d[%s]: %d\n",
+ sn,
+ event_limit->condStr == NULL ? "" : "Cond",
+ event_limit->max,
+ event_limit->seconds,
+ event_limit->env_var,
+ elimit);
+ }
+ event_limit++;
+ }
+ }
+ if(!s->is_virtual || sconf->act->conn != bsconf->act->conn) {
+ if(sconf->max_conn != -1) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConn"QOS_DELIM"%d[]: %d\n", sn,
+ sconf->max_conn,
+ sconf->act->conn->connections);
+ }
+ if(sconf->max_conn_close != -1) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConnClose"QOS_DELIM"%d[]: %d\n", sn,
+ sconf->max_conn_close,
+ sconf->act->conn->connections);
+ }
+ if(option && strstr(option, "ip")) {
+ if(sconf->act->conn->connections) {
+ apr_table_t *entries = apr_table_make(r->pool, 100);
+ int j;
+ apr_table_entry_t *entry;
+ qos_collect_ip(r, sconf, entries, sconf->max_conn_per_ip, 0);
+ entry = (apr_table_entry_t *)apr_table_elts(entries)->elts;
+ for(j = 0; j < apr_table_elts(entries)->nelts; j++) {
+ ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConnPerIP"QOS_DELIM"%s: %s\n",
+ sn,
+ entry[j].key, entry[j].val);
+ }
+ }
+ }
+ }
+ }
+ s = s->next;
+ }
+ if(u->qos_cc && sconf->qsevents) {
+ int i = 0;
+ apr_table_t *eTable = apr_table_make(r->pool, QOS_LOG_MSGCT);
+ apr_table_entry_t *entry;
+ char buf1[1024];
+ char buf2[1024];
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT50 */
+ while(m_knownEvents[i] > 0) {
+ snprintf(buf1, 1023, "e;mod_qos(%03d);new: %llu",
+ m_knownEvents[i], u->qos_cc->eventLast[m_knownEvents[i]]);
+ snprintf(buf2, 1023, "e;mod_qos(%03d);total: %llu",
+ m_knownEvents[i], u->qos_cc->eventTotal[m_knownEvents[i]]);
+ apr_table_add(eTable, buf1, buf2);
+ u->qos_cc->eventLast[m_knownEvents[i]] = 0;
+ i++;
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT50 */
+ entry = (apr_table_entry_t *)apr_table_elts(eTable)->elts;
+ for(i = 0; i < apr_table_elts(eTable)->nelts; ++i) {
+ ap_rprintf(r, "%s\n%s\n", entry[i].key, entry[i].val);
+ }
+ }
+}
+
+/**
+ * Comperator for bsearch function
+ */
+static int qos_geo_comp(const void *_pA, const void *_pB) {
+ unsigned long *pA = (unsigned long *)_pA;
+ qos_geo_entry_t *pB = (qos_geo_entry_t *)_pB;
+ unsigned long search = *pA;
+ if((search >= pB->start) && (search <= pB->end)) return 0;
+ if(search > pB->start) return 1;
+ if(search < pB->start) return -1;
+ return -1; // error
+}
+
+/**
+ * Translates an IP address (from geo csv) to a numeric value.
+ *
+ * @param pool To dup the string while parsing.
+ * @param ip
+ * @return
+ */
+static unsigned long qos_geo_str2long(apr_pool_t *pool, const char *ip) {
+ char *p;
+ char *i = apr_pstrdup(pool, ip);
+ unsigned long addr = 0;
+
+ p = strchr(i, '.');
+ if(!p) return 0;
+ p[0] = '\0';
+ if(!qos_is_num(i)) return 0;
+ addr += (atol(i) * 16777216);
+ i = p;
+ i++;
+
+ p = strchr(i, '.');
+ if(!p) return 0;
+ p[0] = '\0';
+ if(!qos_is_num(i)) return 0;
+ addr += (atol(i) * 65536);
+ i = p;
+ i++;
+
+ p = strchr(i, '.');
+ if(!p) return 0;
+ p[0] = '\0';
+ if(!qos_is_num(i)) return 0;
+ addr += (atol(i) * 256);
+ i = p;
+ i++;
+
+ if(!qos_is_num(i)) return 0;
+ addr += (atol(i));
+
+ return addr;
+}
+
+/**
+ * Viewer settings about ip address information.
+ */
+static void qos_show_ip(request_rec *r, qos_srv_config *sconf, apr_table_t *qt) {
+ int max_conn_per_ip = 0;
+ server_rec *s = sconf->base_server;
+ apr_time_t now = apr_time_sec(r->request_time);
+ while(s) {
+ // enable per client connection search if any server has enabled QS_SrvMaxConnPerIP
+ qos_srv_config *conf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(conf->max_conn_per_ip != -1) {
+ max_conn_per_ip = 1;
+ break;
+ }
+ s = s->next;
+ }
+ if(sconf->has_qos_cc || max_conn_per_ip) {
+ const char *option = apr_table_get(qt, "option");
+ const char *refresh = apr_table_get(qt, "refresh");
+ const char *address = apr_table_get(qt, "address");
+ if(address) {
+ int escerr = 0;
+ char *ta = apr_pstrdup(r->pool, address);
+ qos_unescaping(ta, QOS_DEC_MODE_FLAGS_URL, &escerr);
+ address = ta;
+ }
+ if(strcmp(r->handler, "qos-viewer") == 0) {
+ ap_rputs("<table class=\"btable\"><tbody>\n", r);
+ ap_rputs(" <tr class=\"row\"><td>\n", r);
+ } else {
+ ap_rputs("<table border=\"1\"><tbody>\n", r);
+ ap_rputs(" <tr><td>\n", r);
+ }
+ if(strcmp(r->handler, "qos-viewer") == 0) {
+ ap_rputs(" <table border=\"0\" cellpadding=\"2\" "
+ "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
+ } else {
+ ap_rputs(" <table border=\"1\" cellpadding=\"2\" "
+ "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
+ }
+ ap_rputs(" <tr class=\"rowe\">\n", r);
+ ap_rputs(" <td colspan=\"9\">viewer settings</td>\n", r);
+ ap_rputs(" </tr>\n", r);
+ /* auto refresh */
+ ap_rputs(" <tr class=\"rows\">\n"
+ " <td colspan=\"1\">auto refresh</td>\n", r);
+ ap_rputs(" <td colspan=\"8\">\n", r);
+ ap_rprintf(r, " <form action=\"%s\" method=\"get\">\n",
+ ap_escape_html(r->pool, r->parsed_uri.path));
+ if(option && strstr(option, "ip")) {
+ ap_rprintf(r, " <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
+ }
+ if(address) {
+ ap_rprintf(r, " <input name=\"address\" value=\"%s\" type=\"hidden\">\n",
+ ap_escape_html(r->pool, address));
+ }
+ if(refresh) {
+ ap_rprintf(r, " <input name=\"action\" value=\"disable\" type=\"submit\">\n");
+ } else {
+ ap_rprintf(r, " <input name=\"action\" value=\"enable\" type=\"submit\">\n");
+ ap_rprintf(r, " <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
+ }
+ ap_rputs(" </form>\n", r);
+ ap_rputs(" </td>\n", r);
+ ap_rputs(" </tr>\n", r);
+ /* show ip addresses and their connections */
+ ap_rputs(" <tr class=\"rows\">\n"
+ " <td colspan=\"1\">client ip connections</td>\n", r);
+ ap_rputs(" <td colspan=\"8\">\n", r);
+ ap_rprintf(r, " <form action=\"%s\" method=\"get\">\n",
+ ap_escape_html(r->pool, r->parsed_uri.path));
+ if(!option || (option && !strstr(option, "ip")) ) {
+ ap_rprintf(r, " <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
+ ap_rprintf(r, " <input name=\"action\" value=\"enable\" type=\"submit\">\n");
+ } else {
+ ap_rprintf(r, " <input name=\"option\" value=\"no\" type=\"hidden\">\n");
+ ap_rprintf(r, " <input name=\"action\" value=\"disable\" type=\"submit\">\n");
+ }
+ if(address) {
+ ap_rprintf(r, " <input name=\"address\" value=\"%s\" type=\"hidden\">\n",
+ ap_escape_html(r->pool, address));
+ }
+ if(refresh) {
+ ap_rprintf(r, " <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
+ }
+ ap_rputs(" </form>\n", r);
+ ap_rputs(" </td>\n", r);
+ ap_rputs(" </tr>\n", r);
+
+ if(sconf->has_qos_cc) {
+ ap_rputs(" <tr class=\"rows\">\n"
+ " <td colspan=\"1\">search a client ip entry</td>\n", r);
+ ap_rputs(" <td colspan=\"8\">\n", r);
+ ap_rprintf(r, " <form action=\"%s\" method=\"get\">\n",
+ ap_escape_html(r->pool, r->parsed_uri.path));
+ if(option && strstr(option, "ip")) {
+ ap_rprintf(r, " <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
+ }
+ if(refresh) {
+ ap_rprintf(r, " <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
+ }
+ ap_rprintf(r, " <input name=\"address\" value=\"%s\" type=\"text\">\n",
+ address ? ap_escape_html(r->pool, address) : "0.0.0.0");
+ ap_rprintf(r, " <input name=\"action\" value=\"search\" type=\"submit\">\n");
+ ap_rputs(" </form>\n", r);
+ ap_rputs(" </td>\n", r);
+ ap_rputs(" </tr>\n", r);
+ if(address) {
+ apr_uint64_t ip[2];
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ if(qos_ip_str2long(address, ip)) {
+ unsigned long html;
+ unsigned long cssjs;
+ unsigned long img;
+ unsigned long other;
+ unsigned long notmodified;
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ int found = 0;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT20 */
+ html = u->qos_cc->html;
+ cssjs = u->qos_cc->cssjs;
+ img = u->qos_cc->img;
+ other = u->qos_cc->other;
+ notmodified = u->qos_cc->notmodified;
+ searchE.ip6[0] = ip[0];
+ searchE.ip6[1] = ip[1];
+ clientEntry = qos_cc_get0(u->qos_cc, &searchE, 0);
+ if(clientEntry) {
+ found = 1;
+ searchE.vip = (*clientEntry)->vip;
+ searchE.lowrate = (*clientEntry)->lowrate;
+ searchE.lowratestatus = (*clientEntry)->lowratestatus;
+ searchE.time = (*clientEntry)->time;
+ searchE.block = (*clientEntry)->block;
+ searchE.blockTime = (*clientEntry)->blockTime;
+ searchE.limit = (*clientEntry)->limit;
+ searchE.req_per_sec = (*clientEntry)->req_per_sec;
+ searchE.req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate;
+ searchE.other = (*clientEntry)->other - 1;
+ searchE.html = (*clientEntry)->html - 1;
+ searchE.cssjs = (*clientEntry)->cssjs - 1;
+ searchE.img = (*clientEntry)->img - 1;
+ searchE.notmodified = (*clientEntry)->notmodified - 1;
+ searchE.event_req = (*clientEntry)->event_req;
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT20 */
+ ap_rputs(" <tr class=\"rowt\">\n", r);
+ ap_rputs(" <td colspan=\"1\">IP</td>\n", r);
+ ap_rputs(" <td colspan=\"2\">last request</td>\n", r);
+ ap_rputs(" <td colspan=\"1\">"
+ "<div title=\"QS_VipHeaderName|QS_VipIPHeaderName\">vip</div></td>\n", r);
+ ap_rputs(" <td colspan=\"1\">"
+ "<div title=\"QS_ClientEventBlockCount\">blocked</div></td>\n", r);
+ ap_rputs(" <td colspan=\"1\">"
+ "<div title=\"QS_ClientEventLimitCount (QS_Limit)\">limited</div></td>\n", r);
+ ap_rputs(" <td colspan=\"2\">"
+ "<div title=\"QS_ClientEventPerSecLimit\">events/sec</div></td>\n", r);
+ ap_rputs(" <td colspan=\"1\">"
+ "<div title=\"QS_ClientPrefer&#013;0x01 bad pkg rate&#013;0x02 normal behavior&#013;0x04 bad behavior&#013;0x08 blocked&#013;0x10 limited&#013;0x20 timeout\">low prio</div></td>\n", r);
+ ap_rputs(" </tr>\n", r);
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"1\">%s</td>", ap_escape_html(r->pool, address));
+ if(!found) {
+ ap_rputs("<td colspan=\"8\"><i>not found</i></td>\n", r);
+ } else {
+ char buf[1024];
+ struct tm *ptr = localtime(&searchE.time);
+ strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M:%S", ptr);
+ ap_rprintf(r, "<td colspan=\"2\">%s</td>", buf);
+ ap_rprintf(r, "<td colspan=\"1\">%s</td>", searchE.vip ? "yes" : "no");
+ if(sconf->qos_cc_blockTime > (time(NULL) - searchE.blockTime)) {
+ ap_rprintf(r, "<td colspan=\"1\">%d, %ld&nbsp;sec</td>",
+ searchE.block, time(NULL) - searchE.blockTime);
+ } else {
+ ap_rprintf(r, "<td colspan=\"1\">no</td>");
+ }
+
+ if(u->qos_cc->limitTable) {
+ int limitTableIndex;
+ qos_s_entry_limit_conf_t *eventLimitConf = qos_getQSLimitEvent(u, QS_LIMIT_DEFAULT, &limitTableIndex);
+ if(eventLimitConf) {
+ if(eventLimitConf->limitTime > (time(NULL) - searchE.limit[limitTableIndex].limitTime)) {
+ ap_rprintf(r, "<td colspan=\"1\">%hu, %ld&nbsp;sec</td>",
+ searchE.limit[limitTableIndex].limit, time(NULL) - searchE.limit[limitTableIndex].limitTime);
+ } else {
+ ap_rprintf(r, "<td colspan=\"1\">no</td>");
+ }
+ } else {
+ ap_rprintf(r, "<td colspan=\"1\">off</td>");
+ }
+ } else {
+ ap_rprintf(r, "<td colspan=\"1\">off</td>");
+ }
+ ap_rprintf(r, "<td colspan=\"1\">%ld</td>", searchE.req_per_sec);
+ ap_rprintf(r, "<td colspan=\"1\">%d&nbsp;ms</td>", searchE.req_per_sec_block_rate);
+ ap_rprintf(r, "<td colspan=\"1\">%s (0x%02x)</td>\n",
+ (searchE.lowrate + QOS_LOW_TIMEOUT) > now ? "yes" : "no",
+ searchE.lowratestatus);
+ ap_rputs("</tr>\n", r);
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"6\">&nbsp;</td>"
+ "<td>"
+ "<div title=\"QS_ClientEventRequestLimit\">events:</div></td>"
+ "<td style=\"width:9%%\">%s</td>"
+ "<td colspan=\"1\"></td>"
+ "</tr>\n", (sconf->qos_cc_event_req == -1 ? "off" : apr_psprintf(r->pool, "%d", searchE.event_req)));
+ }
+ if(sconf->qos_cc_prefer) {
+ ap_rprintf(r, " <tr class=\"rowt\">"
+ "<td colspan=\"4\"></td>"
+ "<td style=\"width:9%%\">html</td>"
+ "<td style=\"width:9%%\">css/js</td>"
+ "<td style=\"width:9%%\">images</td>"
+ "<td style=\"width:9%%\">other</td>"
+ "<td style=\"width:9%%\">304</td>"
+ "</tr>\n");
+ if(found) {
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"4\"></td>"
+ "<td style=\"width:9%%\">%u</td>"
+ "<td style=\"width:9%%\">%u</td>"
+ "<td style=\"width:9%%\">%u</td>"
+ "<td style=\"width:9%%\">%u</td>"
+ "<td style=\"width:9%%\">%u</td>"
+ "</tr>\n", searchE.html,
+ searchE.cssjs,
+ searchE.img,
+ searchE.other,
+ searchE.notmodified);
+ }
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"3\"></td>"
+ "<td style=\"width:9%%\"><div title=\"QS_ClientContentTypes\">all clients</div></td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "</tr>\n", html, cssjs, img, other, notmodified);
+ if(sconf->static_on == 1) {
+ unsigned long shtml = sconf->static_html;
+ unsigned long scssjs = sconf->static_cssjs;
+ unsigned long simg = sconf->static_img;
+ unsigned long sother = sconf->static_other;
+ unsigned long snotmodified = sconf->static_notmodified;
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"3\"></td>"
+ "<td style=\"width:9%%\"><div title=\"QS_ClientContentTypes\">configured (global)</div></td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "<td style=\"width:9%%\">%lu</td>"
+ "</tr>\n", shtml, scssjs,
+ simg, sother, snotmodified);
+ }
+ }
+ }
+ }
+ }
+ ap_rprintf(r, " <tr class=\"row\">"
+ "<td style=\"width:28%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "</tr>\n");
+ ap_rputs(" </tbody></table>\n", r);
+ ap_rputs(" </tr></td>\n", r);
+ ap_rputs("</tbody></table>\n", r);
+ }
+}
+
+/**
+ * Daws the load/connection bars at the top of the status page
+ *
+ * @param r
+ * @param bs
+ */
+static void qos_bars(request_rec *r, server_rec *bs) {
+ server_rec *s = bs;
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ if(bsconf->act && bsconf->act->conn) {
+ int connections = -1;
+ double av[1];
+ int load = 0;
+#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
+ getloadavg(av, 1);
+ load = av[0];
+ if(load > 0) {
+ load = 100*load/(10+load);
+ }
+#endif
+
+ ap_rputs("<table class=\"btable\"><tbody>\n", r);
+ ap_rputs(" <tr class=\"row\"><td>\n", r);
+
+ ap_rputs("<table border=\"0\" cellpadding=\"2\" "
+ "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
+ ap_rputs("<tr class=\"rowe\">\n", r);
+ ap_rputs("<td colspan=\"2\">overview</td>", r);
+ ap_rputs("</tr>\n", r);
+
+ if(bsconf->log_only) {
+ ap_rputs("<tr class=\"rowt\">\n", r);
+ ap_rputs("<td colspan=\"2\">running in 'log only' mode - rules are NOT enforced</td>", r);
+ ap_rputs("</tr>\n", r);
+ }
+ if(qos_count_connections(bsconf)) {
+ connections = qos_server_connections(bsconf);
+ }
+ if(connections != -1) {
+#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
+ ap_rprintf(r, "<tr class=\"rowt\">"
+ "<td colspan=\"1\">connections: %d</td>"
+ "<td colspan=\"1\">load: %.2f</td>"
+ "</tr>\n", connections, av[0]);
+#else
+ ap_rprintf(r, "<tr class=\"rowt\">"
+ "<td colspan=\"1\">connections: %d</td>"
+ "<td colspan=\"1\">load: n/a</td>"
+ "</tr>\n", connections);
+#endif
+ } else {
+#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
+ ap_rprintf(r, "<tr class=\"rowt\">"
+ "<td colspan=\"1\">connections: n/a</td>"
+ "<td colspan=\"1\">load: %.2f</td>"
+ "</tr>\n", av[0]);
+#else
+ ap_rprintf(r, "<tr class=\"rowt\">"
+ "<td colspan=\"1\">connections: n/a</td>"
+ "<td colspan=\"1\">load: n/a</td>"
+ "</tr>\n");
+#endif
+ }
+ ap_rprintf(r, "<tr class=\"rows\">");
+ ap_rprintf(r, "<td>");
+ if(connections != -1) {
+ int percentage = 100 * connections / bsconf->max_clients;
+ if(percentage > 100) percentage = 100;
+ ap_rprintf(r, "<div class=\"prog-border\">"
+ "<div class=\"%s\" style=\"width: %d%%;\"></div></div>",
+ percentage < 90 ? "prog-bar" : "prog-bar-limit",
+ percentage);
+ ap_rprintf(r, "</td>");
+ } else {
+ ap_rprintf(r, "&nbsp;");
+ }
+ ap_rprintf(r, "<td>");
+ ap_rprintf(r, "<div class=\"prog-border\">"
+ "<div class=\"prog-bar\" style=\"width: %d%%;\"></div></div>",
+ load);
+ ap_rprintf(r, "</td>");
+ ap_rprintf(r, "</tr>\n");
+
+ ap_rputs("</tbody></table>\n", r);
+
+ ap_rputs(" </tr></td>\n", r);
+ ap_rputs("</tbody></table>\n", r);
+ }
+}
+
+/**
+ * (Extendet-)Status viewer, used by internal and mod_status handler.
+ */
+static int qos_ext_status_hook(request_rec *r, int flags) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ server_rec *s = sconf->base_server;
+ int i = 0;
+ apr_time_t now = apr_time_sec(r->request_time);
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config,
+ &qos_module);
+ apr_table_t *qt = qos_get_query_table(r);
+ const char *option = apr_table_get(qt, "option");
+ if(sconf->disable_handler == 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(072)"handler has been disabled for this host, id=%s",
+ qos_unique_id(r, "072"));
+ return OK;
+ }
+ if (flags & AP_STATUS_SHORT) {
+ qos_ext_status_short(r, qt);
+ return OK;
+ }
+ if(qt && (apr_table_get(qt, "auto") != NULL)) {
+ qos_ext_status_short(r, qt);
+ return OK;
+ }
+ if(strcmp(r->handler, "qos-viewer") != 0) {
+ ap_rputs("<hr>\n", r);
+ ap_rputs("<table style=\"width:400px\" cellspacing=0 cellpadding=0>\n", r);
+ ap_rputs(" <tr><td bgcolor=\"#000000\">\n", r);
+ ap_rputs(" <b><font color=\"#ffffff\" face=\"Arial,Helvetica\">", r);
+ ap_rprintf(r, "mod_qos&nbsp;%s", ap_escape_html(r->pool, qos_revision(r->pool)));
+ ap_rputs(" </font></b>\r", r);
+ ap_rputs(" </td></tr>\n", r);
+ ap_rputs("</table>\n", r);
+ if(sconf->log_only) {
+ ap_rputs("<p>running in 'log only' mode - rules are NOT enforced</p>\n", r);
+ }
+ }
+#ifdef QS_INTERNAL_TEST
+ {
+ apr_uint64_t remoteip[2];
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), remoteip);
+ if(cconf) {
+ remoteip[0] = cconf->ip6[0];
+ remoteip[1] = cconf->ip6[1];
+ }
+ ap_rputs("<p>TEST BINARY, NOT FOR PRODUCTIVE USE<br>\n", r);
+ ap_rprintf(r, "client ip=%s</p>\n", qos_ip_long2str(r->pool, remoteip));
+ }
+#endif
+ if(strcmp(r->handler, "qos-viewer") == 0) {
+ qos_bars(r, s);
+ }
+ qos_show_ip(r, bsconf, qt);
+ if(strcmp(r->handler, "qos-viewer") == 0) {
+ ap_rputs("<table class=\"btable\"><tbody>\n", r);
+ ap_rputs(" <tr class=\"row\"><td>\n", r);
+ } else {
+ ap_rputs("<table border=\"1\"><tbody>\n", r);
+ ap_rputs(" <tr><td>\n", r);
+ }
+ while(s) {
+ qs_acentry_t *actEntry;
+ if(strcmp(r->handler, "qos-viewer") == 0) {
+ ap_rputs(" <table border=\"0\" cellpadding=\"2\" "
+ "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
+ } else {
+ ap_rputs(" <table border=\"1\" cellpadding=\"2\" "
+ "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
+ }
+ ap_rputs(" <tr class=\"rowe\">\n", r);
+ ap_rprintf(r, " <td colspan=\"9\">%s:%d (%s)</td>\n",
+ s->server_hostname == NULL ? "-" : ap_escape_html(r->pool, s->server_hostname),
+ s->addrs->host_port,
+ s->is_virtual ? "virtual" : "base");
+ ap_rputs(" </tr>\n", r);
+ sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+
+ if((sconf == bsconf) && s->is_virtual) {
+ ap_rputs(" <tr class=\"rows\">\n"
+ " <td colspan=\"9\"><i>uses base server settings and counters</i></td>\n </tr>\n", r);
+ } else {
+ if(!s->is_virtual && sconf->has_qos_cc) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ int num = 0;
+ int max = 0;
+ int hc = -1;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT16 */
+ hc = u->qos_cc->connections;
+ num = u->qos_cc->num;
+ max = u->qos_cc->max;
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT16 */
+ ap_rputs("<tr class=\"rowt\">"
+ "<td colspan=\"6\">client control</td>"
+ "<td >max</td>"
+ "<td >limit&nbsp;</td>"
+ "<td >current&nbsp;</td>", r);
+ ap_rputs("</tr>\n", r);
+ ap_rprintf(r, "<tr class=\"rows\">");
+ ap_rprintf(r, "<td colspan=\"6\"><div title=\"QS_ClientEntries\">clients in memory</div></td>");
+ ap_rprintf(r, "<td >%d</td>", max);
+ ap_rprintf(r, "<td >-</td>");
+ ap_rprintf(r, "<td >%d</td>", num);
+ ap_rputs("</tr>\n", r);
+ if(sconf->qos_cc_prefer) {
+ ap_rprintf(r, "<tr class=\"rows\">");
+ ap_rprintf(r, "<td colspan=\"6\"><div title=\"QS_ClientPrefer\">connections</div></td>");
+ ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_prefer);
+ ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_prefer_limit);
+ ap_rprintf(r, "<td >%d</td>", hc);
+ ap_rputs("</tr>\n", r);
+ }
+ /*
+ if(sconf->qos_cc_block) {
+ ap_rprintf(r, "<tr class=\"rows\">");
+ ap_rprintf(r, "<td colspan=\"6\">block event</td>");
+ ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_block);
+ ap_rprintf(r, "<td >&nbsp</td>");
+ ap_rprintf(r, "<td >%d</td>", blocked);
+ ap_rputs("</tr>\n", r);
+ }
+ */
+ }
+ /* request level */
+ actEntry = sconf->act->entry;
+ if(actEntry) {
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"1\">rule</td>"
+ "<td colspan=\"2\">"
+ "<div title=\"QS_LocRequestLimitMatch|QS_LocRequestLimit"
+ "|QS_CondLocRequestLimitMatch|QS_EventRequestLimit\">"
+ "concurrent requests</div></td>"
+ "<td colspan=\"3\">"
+ "<div title=\"QS_LocRequestPerSecLimitMatch|"
+ "QS_LocRequestPerSecLimit|QS_EventPerSecLimit\">"
+ "requests/second</div></td>"
+ "<td colspan=\"3\">"
+ "<div title=\"QS_LocKBytesPerSecLimitMatch|QS_LocKBytesPerSecLimit\">"
+ "kbytes/second</div></td>", r);
+ ap_rputs("</tr>\n", r);
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td >&nbsp;</td>"
+ "<td >limit</td>"
+ "<td >current</td>"
+ "<td >wait rate</td>"
+ "<td >limit</td>"
+ "<td >current</td>"
+ "<td >wait rate</td>"
+ "<td >limit</td>"
+ "<td >current</td>", r);
+ ap_rputs("</tr>\n", r);
+ }
+ while(actEntry) {
+ char *red = "style=\"background-color: rgb(240,153,155);\"";
+ ap_rputs(" <tr class=\"rows\">", r);
+ ap_rprintf(r, "<!--%d--><td>%s%s</a></td>", i,
+ ap_escape_html(r->pool, qos_crline(r, actEntry->url)),
+ actEntry->condition == NULL ? "" : " <small>(conditional)</small>");
+ if((actEntry->limit == 0) || (actEntry->limit == -1)) {
+ ap_rprintf(r, "<td>-</td>");
+ ap_rprintf(r, "<td>-</td>");
+ } else {
+ ap_rprintf(r, "<td>%d</td>", actEntry->limit);
+ ap_rprintf(r, "<td %s>%d</td>",
+ ((actEntry->counter * 100) / actEntry->limit) > 90 ? red : "",
+ actEntry->counter);
+ }
+ if(actEntry->req_per_sec_limit == 0) {
+ ap_rprintf(r, "<td>-</td>");
+ ap_rprintf(r, "<td>-</td>");
+ ap_rprintf(r, "<td>-</td>");
+ } else {
+ ap_rprintf(r, "<td %s>%d&nbsp;ms</td>",
+ actEntry->req_per_sec_block_rate ? red : "",
+ actEntry->req_per_sec_block_rate);
+ ap_rprintf(r, "<td>%ld</td>", actEntry->req_per_sec_limit);
+ ap_rprintf(r, "<td %s>%ld</td>",
+ ((actEntry->req_per_sec * 100) / actEntry->req_per_sec_limit) > 90 ? red : "",
+ now > (actEntry->interval + (QS_BW_SAMPLING_RATE*3)) ? 0 : actEntry->req_per_sec);
+ }
+ if(actEntry->kbytes_per_sec_limit == 0) {
+ ap_rprintf(r, "<td>-</td>");
+ ap_rprintf(r, "<td>-</td>");
+ ap_rprintf(r, "<td>-</td>");
+ } else {
+ int hasActualData = now > (apr_time_sec(actEntry->kbytes_interval_us) + QS_BW_SAMPLING_RATE) ? 0 : 1;
+ ap_rprintf(r, "<td %s>%"APR_OFF_T_FMT"&nbsp;ms</td>",
+ hasActualData && (actEntry->kbytes_per_sec_block_rate >= 1000) ? red : "",
+ actEntry->kbytes_per_sec_block_rate / 1000);
+ ap_rprintf(r, "<td>%"APR_OFF_T_FMT"</td>", actEntry->kbytes_per_sec_limit);
+ ap_rprintf(r, "<td %s>%"APR_OFF_T_FMT"</td>",
+ hasActualData && (((actEntry->kbytes_per_sec * 100) / actEntry->kbytes_per_sec_limit) > 90) ? red : "",
+ hasActualData ? actEntry->kbytes_per_sec : 0);
+ }
+ ap_rputs("</tr>\n", r);
+ actEntry = actEntry->next;
+ }
+ /* event limit */
+ if(sconf->event_limit_a->nelts > 0) {
+ char *red = "style=\"background-color: rgb(240,153,155);\"";
+ int ie = 0;
+ qos_event_limit_entry_t *event_limit = sconf->act->event_entry;
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"5\"><div title=\"QS_EventLimitCount\">events</div></td>"
+ "<td colspan=\"1\">limit</td>"
+ "<td colspan=\"1\">seconds</td>"
+ "<td colspan=\"2\">current</td>", r);
+ ap_rputs("</tr>\n", r);
+ for(ie = 0; ie < sconf->event_limit_a->nelts; ie++) {
+ int edelta = event_limit->limitTime + event_limit->seconds - now;
+ int elimit = event_limit->limit;
+ if(event_limit->limitTime + event_limit->seconds <= now) {
+ elimit = 0;
+ edelta = 0;
+ }
+ if(event_limit->action == QS_EVENT_ACTION_DENY) {
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"5\">%s%s</td>"
+ "<td>%d</td><td>%ds</td><td %s>%d</td><td>%ds</td>"
+ "</tr>\n",
+ event_limit->env_var,
+ event_limit->condStr == NULL ? "" : " <small>(conditional)</small>",
+ event_limit->max,
+ event_limit->seconds,
+ elimit >= event_limit->max ? red : "",
+ elimit,
+ edelta);
+ }
+ event_limit++;
+ }
+ }
+ /* connection level */
+ if(sconf->has_conn_counter == 1 || !s->is_virtual) {
+ char *red = "style=\"background-color: rgb(240,153,155);\"";
+ int c = qos_count_free_ip(sconf);
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"9\">connections</td>", r);
+ ap_rputs("</tr>\n", r);
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<!--%d--><td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConnPerIP\">free ip entries</div></td>"
+ "<td colspan=\"3\">%d</td></tr>\n", i, c);
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<!--%d--><td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConn|QS_SrvMaxConnClose\">current connections</div></td>"
+ "<td %s colspan=\"3\">%d</td></tr>\n", i,
+ ( ( (sconf->max_conn_close != -1) &&
+ (sconf->act->conn->connections >= sconf->max_conn_close) ) ||
+ ( (sconf->max_conn != -1) &&
+ (sconf->act->conn->connections >= sconf->max_conn) ) ) ? red : "",
+ sconf->act->conn->connections);
+
+ if(!s->is_virtual) {
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<!--base--><td colspan=\"6\">"
+ "<div>total connections</div></td>"
+ "<td colspan=\"3\">%d</td></tr>\n",
+ qos_server_connections(sconf));
+ }
+
+ if(option && strstr(option, "ip")) {
+ apr_table_t *entries = apr_table_make(r->pool, 100);
+ int j;
+ apr_table_entry_t *entry;
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConnPerIP\">client ip connections</div></td>"
+ "<td colspan=\"3\">current&nbsp;</td>", r);
+ ap_rputs("</tr>\n", r);
+ qos_collect_ip(r, sconf, entries, sconf->max_conn_per_ip, 1);
+ entry = (apr_table_entry_t *)apr_table_elts(entries)->elts;
+ for(j = 0; j < apr_table_elts(entries)->nelts; j++) {
+ ap_rputs(" <tr class=\"rows\">", r);
+ ap_rputs("<td colspan=\"6\">", r);
+ ap_rprintf(r, "%s</td></tr>\n", entry[j].key);
+ }
+ }
+
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"9\">connection settings</td>", r);
+ ap_rputs("</tr>\n", r);
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConn\">max connections</div></td>");
+ if(sconf->max_conn == -1) {
+ ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
+ } else {
+ ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn);
+ }
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConnClose\">max connections with keep-alive</div></td>");
+ if(sconf->max_conn_close == -1) {
+ ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
+ } else {
+ ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn_close);
+ }
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"6\">"
+ "<div title=\"QS_SrvMaxConnPerIP\">max connections per client ip</div></td>");
+ if(sconf->max_conn_per_ip == -1) {
+ ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
+ } else {
+ ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn_per_ip);
+ }
+ ap_rprintf(r, " <tr class=\"rows\">"
+ "<td colspan=\"6\">"
+ "<div title=\"QS_SrvMinDataRate|QS_SrvRequestRate\">"
+ "min. data rate (bytes/sec) (min/max/current)</div></td>");
+ if(sconf->req_rate == -1) {
+ ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
+ } else {
+ int connections;
+ int rt = qos_req_rate_calc(sconf, &connections);
+ ap_rprintf(r, "<td colspan=\"3\">%d/%d/%d</td></tr>\n",
+ sconf->req_rate,
+ sconf->min_rate_max == -1 ? sconf->req_rate : sconf->min_rate_max,
+ rt);
+ }
+ } else {
+ ap_rputs(" <tr class=\"rowt\">"
+ "<td colspan=\"9\">connections</td>", r);
+ ap_rputs("</tr>\n", r);
+ ap_rputs(" <tr class=\"rows\">\n"
+ " <td colspan=\"9\"><i>uses base server settings and counters</i></td>\n </tr>\n", r);
+ }
+ }
+ ap_rprintf(r, " <tr class=\"row\">"
+ "<td style=\"width:28%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "<td style=\"width:9%%\"></td>"
+ "</tr>");
+ i++;
+ s = s->next;
+ ap_rputs("</tbody></table>\n", r);
+ }
+ ap_rputs(" </td></tr>\n", r);
+ ap_rputs("</tbody></table>\n", r);
+ return OK;
+}
+
+/**
+ * Disables request rate enforcements for all child processes (at start/fork) if
+ * init has failed.
+ *
+ * @param bs Base server_rec to iterate through all client configurations
+ * @param msg Error message to log (reason what has failed @init).
+ */
+static void qos_disable_req_rate(server_rec *bs, const char *msg) {
+ server_rec *s = bs->next;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, bs,
+ QOS_LOG_PFX(008)"could not create supervisor thread (%s),"
+ " disable request rate enforcement", msg);
+ sconf->req_rate = -1;
+ while(s) {
+ sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+ sconf->req_rate = -1;
+ s = s->next;
+ }
+}
+
+/**
+ * stores the IP of the connection into the array and
+ * increments the array pointer (for QS_Block for connection errors)
+ */
+static apr_uint64_t *qos_inc_block(conn_rec *connection, qos_srv_config *sconf,
+ qs_conn_ctx *cconf, apr_uint64_t *ip) {
+ conn_rec *c = connection;
+ if(sconf->qos_cc_block &&
+ apr_table_get(sconf->setenvstatus_t, QS_CLOSE) &&
+ !apr_table_get(c->notes, QS_BLOCK_SEEN)) {
+ apr_table_set(c->notes, QS_BLOCK_SEEN, "");
+ *ip = cconf->ip6[0];
+ ip++;
+ *ip = cconf->ip6[1];
+ ip++;
+ }
+ return ip;
+}
+
+#if APR_HAS_THREADS
+/**
+ * Supervisior thread monitoring the bandith of registered connections.
+ *
+ * Connections are closed by a apr_socket_close/shutdown which must be
+ * detected by the thread processing the connection in order to
+ * de-register the connection and to terminate the pending request in
+ * order to free resources (thread).
+ *
+ * @param thread
+ * @param selfv Base server_rec
+ */
+static void *APR_THREAD_FUNC qos_req_rate_thread(apr_thread_t *thread, void *selfv) {
+ server_rec *bs = selfv;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
+ // list of ip addr. for whose we shall inc. block count
+ apr_uint64_t *ips = calloc(sconf->max_clients * 2, APR_ALIGN_DEFAULT(sizeof(apr_uint64_t)));
+ while(!sconf->inctx_t->exit) {
+ apr_uint64_t *ip = ips;
+ int current_con = 0;
+ int req_rate = qos_req_rate_calc(sconf, &current_con);
+ apr_time_t now = apr_time_sec(apr_time_now());
+ apr_time_t interval = now - sconf->qs_req_rate_tm;
+ int i;
+ apr_table_entry_t *entry;
+ // every second
+ apr_sleep(APR_USEC_PER_SEC);
+ if(sconf->inctx_t->exit) {
+ break;
+ }
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT21 */
+ entry = (apr_table_entry_t *)apr_table_elts(sconf->inctx_t->table)->elts;
+ for(i = 0; i < apr_table_elts(sconf->inctx_t->table)->nelts; i++) {
+ qos_ifctx_t *inctx = (qos_ifctx_t *)entry[i].val;
+ if(inctx->status == QS_CONN_STATE_KEEP) {
+ /* enforce keep alive */
+ apr_interval_time_t current_timeout = 0;
+ apr_socket_timeout_get(inctx->clientSocket, &current_timeout);
+ /* add 5sec tolerance to receive the request line
+ or let Apache close the connection */
+ /* ignored by event MPM ! */
+ if(!m_event_mpm &&
+ (now > (apr_time_sec(current_timeout) + 5 + inctx->time))) {
+ qs_conn_ctx *cconf = qos_get_cconf(inctx->c);
+ int level = APLOG_ERR;
+ if(cconf) {
+ /* disabled by vip priv */
+ if(cconf->is_vip && sconf->req_ignore_vip_rate != 1) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ /* disabled for this request/connection */
+ if(inctx->disabled) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ /* enable only if min. num of connection reached */
+ if(current_con < sconf->req_rate_start) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ ip = qos_inc_block(inctx->c, sconf, cconf, ip);
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|level, 0, inctx->c->base_server,
+ QOS_LOG_PFX(034)"%s,"
+ " QS_SrvMinDataRate rule (enforce keep-alive),"
+ " c=%s",
+ (level == APLOG_DEBUG) || sconf->log_only ?
+ "log only (allowed)"
+ : "access denied",
+ QS_CONN_REMOTEIP(inctx->c) == NULL ? "-" : QS_CONN_REMOTEIP(inctx->c));
+ QS_INC_EVENT_LOCKED(sconf, 34);
+ inctx->time = now;
+ inctx->nbytes = 0;
+ if((level == APLOG_ERR) &&
+ !sconf->log_only) {
+ apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_READ);
+ }
+ /* mark slow clients (QS_ClientPrefer) even they are VIP */
+ inctx->shutdown = 1;
+ }
+ //else {
+ // // HTTP/2
+ //}
+ }
+ } else {
+ if(interval > inctx->time) {
+ int rate = inctx->nbytes / sconf->qs_req_rate_tm;
+ if(rate < req_rate) {
+ if(inctx->clientSocket) {
+ qs_conn_ctx *cconf = qos_get_cconf(inctx->c);
+ int level = APLOG_ERR;
+ int notUsed = 0;
+ if(cconf) {
+ /* disabled by vip priv */
+ if(cconf->is_vip && sconf->req_ignore_vip_rate != 1) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ /* disabled for this request/connection */
+ if(inctx->disabled) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ /* enable only if min. num of connection reached */
+ if(current_con < sconf->req_rate_start) {
+ level = APLOG_DEBUG;
+ cconf->has_lowrate = 1; /* mark connection low rate */
+ }
+ ip = qos_inc_block(inctx->c, sconf, cconf, ip);
+ if((inctx->hasBytes == 0) &&
+ (inctx->c->keepalives == 0) &&
+ (inctx->status == QS_CONN_STATE_HEAD)) {
+ // not even received the request line
+ notUsed = 1;
+ }
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|level, 0, inctx->c->base_server,
+ QOS_LOG_PFX(034)"%s, QS_SrvMinDataRate rule (%s%s): min=%d,"
+ " this connection=%d,"
+ " c=%s",
+ (level == APLOG_DEBUG) || sconf->log_only ?
+ "log only (allowed)"
+ : "access denied",
+ inctx->status == QS_CONN_STATE_RESPONSE ? "out" : "in",
+ notUsed ? ":0" : "",
+ req_rate,
+ rate,
+ QS_CONN_REMOTEIP(inctx->c) == NULL ? "-" : QS_CONN_REMOTEIP(inctx->c));
+ QS_INC_EVENT_LOCKED(sconf, 34);
+ inctx->time = interval + sconf->qs_req_rate_tm;
+ inctx->nbytes = 0;
+ if((level == APLOG_ERR) && !sconf->log_only) {
+ if(inctx->status == QS_CONN_STATE_RESPONSE) {
+ apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_WRITE);
+ /* close out socket (the hard way) */
+ apr_socket_close(inctx->clientSocket);
+ } else {
+ apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_READ);
+ }
+ }
+ /* mark slow clients (QS_ClientPrefer) even they are VIP */
+ inctx->shutdown = 1;
+ }
+ }
+ //else {
+ // // HTTP/2
+ //}
+ } else {
+ inctx->time = interval + sconf->qs_req_rate_tm;
+ inctx->nbytes = 0;
+ }
+ }
+ }
+ }
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT21 */
+ /* QS_Block for connection errors */
+ while(ip != ips) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT38 */
+ ip--;
+ searchE.ip6[1] = *ip;
+ ip--;
+ searchE.ip6[0] = *ip;
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+
+ /* increment block event */
+ if((*clientEntry)->block < USHRT_MAX) {
+ (*clientEntry)->block++;
+ }
+ if((*clientEntry)->block == 1) {
+ /* ... and start timer */
+ (*clientEntry)->blockTime = apr_time_sec(apr_time_now());
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT38 */
+ }
+ }
+ // apr_thread_mutex_lock(sconf->inctx_t->lock);
+ // apr_thread_mutex_unlock(sconf->inctx_t->lock);
+ // called via apr_pool_cleanup_register():
+ // apr_thread_mutex_destroy(sconf->inctx_t->lock);
+ free(ips);
+ if(m_threaded_mpm) {
+ apr_thread_exit(thread, APR_SUCCESS);
+ }
+ return NULL;
+}
+
+/**
+ * Terminates the connection supervisor thread.
+ * (works for mpm_worker only)
+ *
+ * @param selfv The base server_rec
+ * @return APR_SUCCESS
+ */
+static apr_status_t qos_cleanup_req_rate_thread(void *selfv) {
+ server_rec *bs = selfv;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
+ sconf->inctx_t->exit = 1;
+ /* may long up to one second */
+ if(m_threaded_mpm) {
+ apr_status_t status;
+ apr_thread_join(&status, sconf->inctx_t->thread);
+ }
+ return APR_SUCCESS;
+}
+#endif
+
+/**
+ * Writes the query/body to the env variables which may be used
+ * for the qsfilter* audit log.
+ *
+ * @param r
+ * @param dconf
+ */
+static void qos_audit(request_rec *r, qos_dir_config *dconf) {
+ const char *q = NULL;
+ const char *u = apr_table_get(r->notes, QS_PARP_PATH);
+ if(dconf->bodyfilter_p == 1 || dconf->bodyfilter_d == 1) {
+ q = apr_table_get(r->notes, QS_PARP_QUERY);
+ }
+ if(u == NULL) {
+ if(r->parsed_uri.path) {
+ u = apr_pstrdup(r->pool, r->parsed_uri.path);
+ } else {
+ u = apr_pstrdup(r->pool, "");
+ }
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_PATH), u);
+ }
+ if(q == NULL) {
+ if(r->parsed_uri.query) {
+ q = apr_pstrcat(r->pool, "?", r->parsed_uri.query, NULL);
+ } else {
+ q = apr_pstrdup(r->pool, "");
+ }
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), q);
+ }
+ apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_LOC), dconf->path);
+ if(r->next) {
+ apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_PATH), u);
+ apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), q);
+ apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_LOC), dconf->path);
+ }
+}
+
+/**
+ * Adds the configured (QS_Delay env var) delay to the request
+ *
+ * @param r
+ * @param sconf Do set log-only mode
+ */
+static void qos_delay(request_rec *r, qos_srv_config *sconf) {
+ const char *d = apr_table_get(r->subprocess_env, "QS_Delay");
+ if(d) {
+ apr_off_t s;
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ char *errp = NULL;
+ if((APR_SUCCESS == apr_strtoff(&s, d, &errp, 10)) && s > 0)
+#else
+ if((s = apr_atoi64(d)) > 0)
+#endif
+ {
+ qs_set_evmsg(r, "L;");
+ if(!sconf->log_only) {
+ apr_sleep(s*1000);
+ }
+ }
+ }
+}
+
+/**
+ * Enables mod_deflate
+ * QS_DeflateReqBody (if parp has been enabled)
+ *
+ * @param r
+ */
+static void qos_deflate(request_rec *r) {
+ if(apr_table_get(r->subprocess_env, "QS_DeflateReqBody") &&
+ apr_table_get(r->subprocess_env, "parp")) {
+ ap_add_input_filter("DEFLATE", NULL, r, r->connection);
+ }
+}
+
+/**
+ * Adjusts the content-length header
+ *
+ * @param r
+ */
+static void qos_deflate_contentlength(request_rec *r) {
+ if(apr_table_get(r->subprocess_env, "QS_DeflateReqBody") &&
+ apr_table_get(r->subprocess_env, "parp")) {
+ const char *PARPContentLength = apr_table_get(r->subprocess_env, "PARPContentLength");
+ const char *contentLength = apr_table_get(r->headers_in, "Content-Length");
+ if(PARPContentLength && contentLength) {
+ apr_table_set(r->headers_in, "Content-Length", PARPContentLength);
+ }
+ }
+}
+
+/**
+ * Verifies Apache and MPM version and writes error message (notice)
+ * for incompatibel version/type.
+ *
+ * Apache MPM worker binaries is the only configuration which
+ * has been fully tested (as mentioned in the documentation, see index.html).
+ *
+ * - old (2.0.x, 2.2.x) MPM Apache prefork versions do not unload the
+ * DSO properly or child exit may cause a segfault (pool cleanup)
+ * - regular expression are only fully supported with Apach 2.4
+ * - Apache 2.4 should work (but is not fully tested)
+ * (see CHANGES.txt for more information)
+ * - Apache 2.0 does not support all directives (e.g. QS_ClientPrefer) and
+ * we do no longer test against this version (the module does probably
+ * not even compile with version 2.0)
+ *
+ */
+static void qos_version_check(server_rec *bs) {
+ ap_version_t version;
+
+ if(strcasecmp(ap_show_mpm(), "event") == 0) {
+ ap_directive_t *pdir;
+ m_event_mpm = 1; // disable features like keep-alive control
+ for(pdir = ap_conftree; pdir != NULL; pdir = pdir->next) {
+ if(strcasecmp(pdir->directive, "AsyncRequestWorkerFactor") == 0) {
+ double val = 0;
+ char *endptr;
+ const char *arg = pdir->args;
+ if(arg == NULL) {
+ continue;
+ }
+ val = strtod(arg, &endptr);
+ if(*endptr) {
+ continue;
+ }
+ if(val < 0) {
+ continue;
+ }
+ m_event_mpm_worker_factor = val;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"found AsyncRequestWorkerFactor directive '%f'",
+ m_event_mpm_worker_factor);
+ }
+ }
+ } else if(strcasecmp(ap_show_mpm(), "worker") != 0) {
+ // mod_qos is fully tested for MPM worker (and "works" with event)
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(009)"loaded MPM is '%s'"
+ " but mod_qos should be used with MPM 'Worker' or 'Event' only.",
+ ap_show_mpm());
+ }
+
+ if(strcasecmp(ap_show_mpm(), "prefork") == 0) {
+ m_threaded_mpm = 0; // disable child cleanup (if neither worker nor event MPM)
+ }
+
+ ap_get_server_revision(&version);
+ if(version.major == 2 && version.minor == 4 && version.patch == 49) {
+ // 2.4.49 compat: prevents Apache segfault on connection close
+ m_forced_close = 0;
+ }
+
+ if(version.major != 2 || version.minor != 4) {
+ // 2.4 should work fine / older or newer versions are not tested
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(009)"server version is %d.%d"
+ " but mod_qos should be used with Apache 2.4 only.",
+ version.major, version.minor);
+ }
+}
+
+/**
+ * enforces the QS_RedirectIf variable
+ * @param r
+ * @param sconf
+ * @param rules Rules array
+ * @return HTTP_MOVED_TEMPORARILY/HTTP_TEMPORARY_REDIRECT or DECLINED
+ */
+static int qos_redirectif(request_rec *r, qos_srv_config *sconf,
+ apr_array_header_t *rules) {
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ int i;
+ qos_redirectif_entry_t *entries = (qos_redirectif_entry_t *)rules->elts;
+ for(i = 0; i < rules->nelts; ++i) {
+ qos_redirectif_entry_t *entry = &entries[i];
+ const char *val = apr_table_get(r->subprocess_env, entry->name);
+ if(val) {
+ if(ap_regexec(entry->preg, val, AP_MAX_REG_MATCH, regm, 0) == 0) {
+ int severity = sconf->log_only ? APLOG_WARNING : APLOG_ERR;
+ char *replaced = ap_pregsub(r->pool, entry->url, val, AP_MAX_REG_MATCH, regm);
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
+ QOS_LOG_PFX(049)"redirect to %s,"
+ " var=%s,"
+ " action=%s, c=%s, id=%s",
+ replaced,
+ entry->name,
+ sconf->log_only ? "log only" : "redirect",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "049"));
+ QS_INC_EVENT(sconf, 49);
+ if(!sconf->log_only) {
+ apr_table_set(r->headers_out, "Location", replaced);
+ return entry->code;
+ }
+ }
+ }
+ }
+ return DECLINED;
+}
+
+static void qos_init_unique_id(apr_pool_t *p, server_rec *bs) {
+ char str[APRMAXHOSTLEN + 1];
+ apr_sockaddr_t *sockaddr;
+ str[APRMAXHOSTLEN] = '\0';
+ unsigned int in_addr = 0;
+ if(apr_gethostname(str, sizeof(str) - 1, p) == APR_SUCCESS) {
+ if(apr_sockaddr_info_get(&sockaddr, str, AF_INET, 0, 0, p) == APR_SUCCESS) {
+ in_addr = sockaddr->sa.sin.sin_addr.s_addr;
+ }
+ }
+ pid_t pid = getpid();
+ m_unique_id.in_addr = pid^in_addr;
+ m_unique_id.unique_id_counter = time(NULL);
+}
+
+/**
+ * propagates environment variables to sub-requests (for logging)
+ */
+static void qos_propagate_events(request_rec *r) {
+ request_rec *mr = NULL;
+ const char **var;
+ if(r->prev) {
+ mr = r->prev;
+ } else if(r->main) {
+ mr = r->main;
+ } else if(r->next) {
+ mr = r->next;
+ }
+ var = m_env_variables;
+ while(*var) {
+ int propagated = 0;
+ if(mr) {
+ const char *p = apr_table_get(mr->subprocess_env, *var);
+ if(p) {
+ propagated = 1;
+ apr_table_set(r->subprocess_env, *var, p);
+ }
+ if(!propagated) {
+ p = apr_table_get(r->subprocess_env, *var);
+ if(p) {
+ propagated = 1;
+ apr_table_set(mr->subprocess_env, *var, p);
+ }
+ }
+ }
+ var++;
+ }
+ if(r->prev) {
+ // internal redirect (e.g. error page)
+ int i;
+ int len = strlen(QS_LIMIT_NAME_PFX);
+ request_rec *mp = r->prev;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(mp->subprocess_env)->elts;
+ for(i = 0; i < apr_table_elts(mp->subprocess_env)->nelts; ++i) {
+ if(strncmp(entry[i].key, QS_LIMIT_NAME_PFX, len) == 0) {
+ const char *eventName = entry[i].val;
+ const char *name = apr_pstrcat(r->pool, eventName, QS_COUNTER_SUFFIX, NULL);
+ const char *value = apr_table_get(mp->subprocess_env, name);
+ if(value) {
+ apr_table_set(r->subprocess_env, name, value);
+ }
+ name = eventName;
+ value = apr_table_get(mp->subprocess_env, name);
+ if(value) {
+ apr_table_set(r->subprocess_env, name, value);
+ }
+ name = apr_pstrcat(r->pool, eventName, QS_LIMIT_REMAINING, NULL);
+ value = apr_table_get(mp->subprocess_env, name);
+ if(value) {
+ apr_table_set(r->subprocess_env, name, value);
+ }
+ name = apr_pstrcat(r->pool, eventName, QS_LIMIT_SEEN, NULL);
+ value = apr_table_get(mp->subprocess_env, name);
+ if(value) {
+ apr_table_set(r->subprocess_env, name, value);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * ensure that every request record has the error notes to log
+ */
+static void qos_propagate_notes(request_rec *r) {
+ request_rec *mr = NULL;
+ const char **var;
+ if(r->prev) {
+ mr = r->prev;
+ } else if(r->main) {
+ mr = r->main;
+ } else if(r->next) {
+ mr = r->next;
+ }
+ var = m_note_variables;
+ while(*var) {
+ int propagated = 0;
+ if(mr) {
+ const char *p = apr_table_get(mr->notes, *var);
+ if(p) {
+ propagated = 1;
+ apr_table_setn(r->notes, *var, p);
+ }
+ if(!propagated) {
+ p = apr_table_get(r->notes, *var);
+ if(p) {
+ propagated = 1;
+ apr_table_setn(mr->notes, *var, p);
+ }
+ }
+ }
+ var++;
+ }
+}
+
+
+/* QS_UnsetReqHeader */
+static void qos_unset_reqheader(request_rec *r, qos_srv_config *sconf) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->unsetreqheader_t)->elts;
+ for(i = 0; i < apr_table_elts(sconf->unsetreqheader_t)->nelts; i++) {
+ apr_table_unset(r->headers_in, entry[i].key);
+ }
+ return;
+}
+
+/* QS_UnsetResHeader */
+static void qos_unset_resheader(request_rec *r, qos_srv_config *sconf) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->unsetresheader_t)->elts;
+ for(i = 0; i < apr_table_elts(sconf->unsetresheader_t)->nelts; i++) {
+ apr_table_unset(r->headers_out, entry[i].key);
+ apr_table_unset(r->err_headers_out, entry[i].key);
+ }
+ return;
+}
+
+/************************************************************************
+ * handlers
+ ***********************************************************************/
+
+/**
+ * Destructor to handle connections which do not have been established
+ * successfully resp. have been closed unexpectedly.
+ *
+ * Increments block counter.
+ *
+ * @param p Connection base context
+ * @return APR_SUCCESS
+ */
+static apr_status_t qos_base_cleanup_conn(void *p) {
+ qs_conn_base_ctx *base = p;
+ if(base->sconf->has_qos_cc || base->sconf->qos_cc_prefer) {
+ int connRuleViolation = 0;
+ const char *type = QS_EMPTY_CON;
+ if(base->requests == 0 &&
+ apr_table_get(base->sconf->setenvstatus_t, QS_EMPTY_CON) &&
+ !apr_table_get(base->c->notes, QS_BLOCK_SEEN)) {
+ connRuleViolation = 1;
+ apr_table_set(base->c->notes, QS_BLOCK_SEEN, "");
+ }
+ if(apr_table_get(base->c->notes, QS_BROKEN_CON)) {
+ connRuleViolation = 1;
+ type = QS_BROKEN_CON;
+ }
+ if(apr_table_get(base->c->notes, QS_MAXIP)) {
+ connRuleViolation = 1;
+ type = QS_MAXIP;
+ }
+ if(connRuleViolation) {
+ qos_user_t *u = qos_get_user_conf(base->sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ qos_ip_str2long(QS_CONN_REMOTEIP(base->c), searchE.ip6); // no ip simulation here
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT40 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+
+ /* increment block event */
+ if((*clientEntry)->block < USHRT_MAX) {
+ (*clientEntry)->block++;
+ }
+ if((*clientEntry)->block == 1) {
+ /* ... and start timer */
+ (*clientEntry)->blockTime = apr_time_sec(apr_time_now());
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT40 */
+ if(QS_ISDEBUG(base->c->base_server)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base->c->base_server,
+ QOS_LOGD_PFX"QS_ClientEventBlockCount rule: "
+ "%s event detected "
+ "c=%s",
+ type,
+ QS_CONN_REMOTEIP(base->c) == NULL ? "-" : QS_CONN_REMOTEIP(base->c));
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * Connection destructor.
+ *
+ * Updates per IP events and connection counter.
+ *
+ * @param p Connection context
+ * @return APR_SUCCESS
+ */
+static apr_status_t qos_cleanup_conn(void *p) {
+ qs_conn_ctx *cconf = p;
+ if(cconf->sconf->has_qos_cc || cconf->sconf->qos_cc_prefer) {
+ qos_user_t *u = qos_get_user_conf(cconf->sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ searchE.ip6[0] = cconf->ip6[0];
+ searchE.ip6[1] = cconf->ip6[1];
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT15 */
+ // generation: check if it has been cleard for this process generation
+ if((m_generation != u->qos_cc->generation_locked) && u->qos_cc->connections > 0) {
+ u->qos_cc->connections--;
+ }
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+ if((*clientEntry)->events < UINT_MAX) {
+ (*clientEntry)->events++; // update event activity even there is no valid request (logger)
+ }
+ if(cconf->set_vip_by_header) {
+ (*clientEntry)->vip = 1;
+ }
+ if(cconf->has_lowrate) {
+ (*clientEntry)->lowrate = time(NULL);
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_PKGRATE;
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT15 */
+ }
+ /* QS_SrvMaxConn or Geo */
+ if(qos_count_connections(cconf->sconf)) {
+ apr_global_mutex_lock(cconf->sconf->act->lock); /* @CRT3 */
+ if(cconf->sconf->act->conn && cconf->sconf->act->conn->connections > 0) {
+ cconf->sconf->act->conn->connections--;
+ }
+ apr_global_mutex_unlock(cconf->sconf->act->lock); /* @CRT3 */
+ }
+ if(cconf->sconf->max_conn_per_ip != -1) {
+ qos_dec_ip(cconf);
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * handler to close the connection in the case the abort
+ * by the pre-connect hook was ignored (Apache 2.4.28 Event MPM)
+ */
+static int qos_process_connection(conn_rec *connection) {
+#if (AP_SERVER_MINORVERSION_NUMBER == 4)
+#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
+ if(connection->master) {
+ // skip slave connections / process "real" connections only
+ return DECLINED;
+ }
+#endif
+#endif
+ if(connection->aborted == 1 && apr_table_get(connection->notes, QS_CONN_ABORT)) {
+ if(connection->cs) {
+ connection->cs->state = CONN_STATE_LINGER;
+ }
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, connection->base_server,
+ QOS_LOG_PFX(167)"closing connection at process connection hook, c=%s",
+ QS_CONN_REMOTEIP(connection) == NULL ? "-" :
+ QS_CONN_REMOTEIP(connection));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return DECLINED;
+}
+
+/**
+ * Connection constructor. Rules that are applied to established connections.
+ *
+ * @param c
+ * @return
+ */
+static int qos_pre_process_connection(conn_rec *connection, void *skt) {
+ conn_rec *c = connection;
+ qs_conn_ctx *cconf;
+ int vip = 0;
+ apr_socket_t *socket = skt;
+
+ if(c->sbh == NULL) {
+ // proxy connections do NOT have any relation to the score board, don't handle them
+ return DECLINED;
+ }
+
+#if (AP_SERVER_MINORVERSION_NUMBER == 4)
+#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
+ if(connection->master) {
+ // skip slave connections / process "real" connections only
+ return DECLINED;
+ }
+#endif
+#endif
+
+ cconf = qos_get_cconf(c);
+ if(cconf == NULL) {
+ int client_control = DECLINED;
+ int connections = 0;
+ int all_connections = 0;
+ int current = 0;
+ qs_ip_entry_t *conn_ip = NULL;
+ char *msg = NULL;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(c->base_server->module_config,
+ &qos_module);
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ cconf = qos_create_cconf(c, sconf);
+
+ /* control timeout */
+ if(sconf->req_rate != -1) {
+ qos_timeout_pc(c, sconf);
+ }
+
+ /* packet rate */
+ if(sconf->qos_cc_prefer_limit) {
+ qos_pktrate_pc(c, sconf);
+ }
+
+ /* evaluates client ip */
+ qos_ip_str2long(QS_CONN_REMOTEIP(c), cconf->ip6);
+#ifdef QS_INTERNAL_TEST
+ /* use one of the predefined ip addresses */
+ if(cconf->sconf->enable_testip) {
+ char *testid = apr_psprintf(c->pool, "%d", rand()%(m_qs_sim_ip_len-1));
+ const char *testip = apr_table_get(cconf->sconf->testip, testid);
+ qos_ip_str2long(testip, cconf->ip6);
+ }
+#endif
+
+ /* ------------------------------------------------------------
+ * update data
+ */
+ /* client control */
+ client_control = qos_cc_pc_filter(c, cconf, u, &msg);
+ /* QS_SrvMaxConn: vhost connections or Geo */
+ if(qos_count_connections(sconf)) {
+ apr_global_mutex_lock(cconf->sconf->act->lock); /* @CRT4 */
+ if(cconf->sconf->act->conn) {
+ cconf->sconf->act->conn->connections++;
+ all_connections = qos_server_connections(sconf);
+ connections = cconf->sconf->act->conn->connections;
+ apr_table_set(c->notes, "QS_SrvConn", apr_psprintf(c->pool, "%d", connections));
+ apr_table_set(c->notes, "QS_AllConn", apr_psprintf(c->pool, "%d", all_connections));
+ }
+ apr_global_mutex_unlock(cconf->sconf->act->lock); /* @CRT4 */
+ }
+
+ /* single source ip */
+ if(sconf->max_conn_per_ip != -1) {
+ current = qos_inc_ip(sconf, cconf, &conn_ip);
+ apr_table_set(c->notes, "QS_IPConn", apr_psprintf(c->pool, "%d", current));
+ }
+ /* Check for vip (by ip) */
+ vip = qos_is_excluded_ip(cconf->mc, sconf->exclude_ip);
+ if(vip == 0) {
+ // check if qos_cc_pc_filter() got vip status form the cc store
+ vip = cconf->is_vip;
+ }
+ if(vip) {
+ /* propagate vip to connection */
+ cconf->is_vip = vip;
+ if(!cconf->evmsg || !strstr(cconf->evmsg, "S;")) {
+ cconf->evmsg = apr_pstrcat(c->pool, "S;", cconf->evmsg, NULL);
+ }
+ }
+
+ /* ------------------------------------------------------------
+ * enforce rules
+ */
+ /* client control */
+ if((client_control != DECLINED) && !vip) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ "%s",
+ msg == NULL ? "-" : msg);
+ if(!sconf->log_only) {
+ return qos_return_error_andclose(c, socket);
+ }
+ }
+ /* Geo */
+ if(sconf->geodb) {
+ unsigned long ip = qos_geo_str2long(c->pool, QS_CONN_REMOTEIP(c));
+ qos_geo_entry_t *pB = bsearch(&ip,
+ sconf->geodb->data,
+ sconf->geodb->size,
+ sizeof(qos_geo_entry_t),
+ qos_geo_comp);
+ if(pB) {
+ apr_table_set(c->notes, QS_COUNTRY, pB->country);
+ }
+ if(sconf->geo_limit != -1) {
+ if(all_connections >= sconf->geo_limit) {
+ if(sconf->geo_excludeUnknown == 1 && (pB == NULL || pB->country[0] == '\0')) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server,
+ QOS_LOGD_PFX"skip QS_ClientGeoCountryPriv enforcement"
+ " for client address %s which could not be mapped to country code",
+ QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN");
+ } else if(pB == NULL || apr_table_get(sconf->geo_priv, pB->country) == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(101)"access denied%s,"
+ " QS_ClientGeoCountryPriv rule: max=%d,"
+ " concurrent connections=%d,"
+ " c=%s"
+ " country=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->geo_limit,
+ all_connections,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c),
+ pB != NULL ? pB->country : "--");
+ QS_INC_EVENT(sconf, 101);
+ if(!sconf->log_only) {
+ return qos_return_error_andclose(c, socket);
+ }
+ }
+ }
+ }
+ }
+ /* QS_SrvMaxConn: vhost connections */
+ if((sconf->max_conn != -1) && !vip) {
+ if(connections > sconf->max_conn) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(030)"access denied%s, QS_SrvMaxConn rule: max=%d,"
+ " concurrent connections=%d,"
+ " c=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->max_conn, connections,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ QS_INC_EVENT(sconf, 30);
+ if(!sconf->log_only) {
+ return qos_return_error_andclose(c, socket);
+ }
+ }
+ }
+ /* single source ip */
+ if((sconf->max_conn_per_ip != -1) && (!vip || sconf->max_conn_per_ip_ignore_vip == 1)) {
+ if((current > sconf->max_conn_per_ip) &&
+ (all_connections >= sconf->max_conn_per_ip_connections)) {
+ conn_ip->error++;
+ if(apr_table_get(sconf->setenvstatus_t, QS_MAXIP)) {
+ apr_table_set(c->notes, QS_MAXIP, "1");
+ }
+ /* only print the first 20 messages for this client */
+ QS_INC_EVENT(sconf, 31);
+ if(conn_ip->error <= QS_LOG_REPEAT) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(031)"access denied%s,"
+ " QS_SrvMaxConnPerIP rule: max=%d,"
+ " concurrent connections=%d,"
+ " c=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->max_conn_per_ip, current,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ } else {
+ if((conn_ip->error % QS_LOG_REPEAT) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(031)"access denied%s,"
+ " QS_SrvMaxConnPerIP rule: max=%d,"
+ " concurrent connections=%d,"
+ " message repeated %d times,"
+ " c=%s",
+ sconf->log_only ? " (log only)" : "",
+ sconf->max_conn_per_ip, current,
+ QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ }
+ }
+ if(!sconf->log_only) {
+ return qos_return_error_andclose(c, socket);
+ }
+ } else {
+ if(conn_ip) {
+ if(conn_ip->error > QS_LOG_REPEAT) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(031)"access denied (previously),"
+ " QS_SrvMaxConnPerIP rule: max=%d,"
+ " concurrent connections=%d,"
+ " message repeated %d times,"
+ " c=%s",
+ sconf->max_conn_per_ip, current,
+ conn_ip->error % QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ }
+ conn_ip->error = 0;
+ }
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * Pre connection
+ * - constructs the connection ctx (stores socket ref)
+ * - enforces block counter (as early as possible)
+ */
+static int qos_pre_connection(conn_rec *connection, void *skt) {
+ conn_rec *c = connection;
+ int ret = DECLINED;
+ qos_srv_config *sconf;
+ qs_conn_base_ctx *base;
+ int excludeFromBlock;
+
+ if(c->sbh == NULL) {
+ // proxy connections do NOT have any relation to the score board, don't handle them
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server,
+ QOS_LOGD_PFX"skip processing of outgoing/virtual connection %s<->%s",
+ QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN",
+ c->local_ip ? c->local_ip : "UNKNOWN");
+ return ret;
+ }
+
+#if (AP_SERVER_MINORVERSION_NUMBER == 4)
+#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
+ if(connection->master) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server,
+ QOS_LOGD_PFX"skip slave connection %s",
+ QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN");
+ return ret;
+ }
+#endif
+#endif
+
+ sconf = (qos_srv_config*)ap_get_module_config(c->base_server->module_config, &qos_module);
+ excludeFromBlock = qos_is_excluded_ip(c, sconf->cc_exclude_ip);
+ base = qos_get_conn_base_ctx(c);
+ if(base == NULL) {
+ base = qos_create_conn_base_ctx(c, sconf);
+ base->clientSocket = skt;
+ }
+
+ if(sconf && (sconf->req_rate != -1)) {
+ qos_ifctx_t *inctx = qos_create_ifctx(c, sconf);
+ inctx->clientSocket = skt;
+ ap_add_input_filter("qos-in-filter", inctx, NULL, c);
+ }
+
+ /* blocked by event (block only, no limit) - very aggressive */
+ if(sconf->qos_cc_block && !excludeFromBlock) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ qos_ip_str2long(QS_CONN_REMOTEIP(c), searchE.ip6); // no ip simulation here
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT39 */
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+ if((*clientEntry)->block >= sconf->qos_cc_block) {
+ apr_time_t now = time(NULL);
+ if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) > now) {
+ (*clientEntry)->blockMsg++;;
+ // stop logging every event if we have logged it many times
+ QS_INC_EVENT_LOCKED(sconf, 60);
+ if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
+ if(((*clientEntry)->blockMsg % QS_LOG_REPEAT) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(060)"access denied, "
+ "QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, "
+ "message repeated %d times, "
+ "c=%s",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ }
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(060)"access denied, QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ now - (*clientEntry)->blockTime,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ }
+ if(!sconf->log_only) {
+ apr_table_set(c->notes, QS_BLOCK_SEEN, ""); // suppress NullConnection messages
+ c->keepalive = AP_CONN_CLOSE;
+ c->aborted = 1;
+ if(c->cs) {
+ c->cs->state = CONN_STATE_LINGER;
+ }
+ apr_table_set(c->notes, QS_CONN_ABORT, QS_CONN_ABORT);
+ if (m_forced_close == 0) {
+ ret = DECLINED;
+ } else {
+ ret = m_retcode;
+ }
+ }
+ } else {
+ /* release */
+ if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
+ // write remaining log lines
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
+ QOS_LOG_PFX(060)"access denied (previously), QS_ClientEventBlockCount rule: "
+ "max=%d, current=%hu, "
+ "message repeated %d times, "
+ "c=%s",
+ sconf->qos_cc_block,
+ (*clientEntry)->block,
+ (*clientEntry)->blockMsg % QS_LOG_REPEAT,
+ QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
+ (*clientEntry)->blockMsg = 0;
+ }
+ (*clientEntry)->block = 0;
+ (*clientEntry)->blockTime = 0;
+ }
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT39 */
+ }
+
+ return ret;
+}
+
+/**
+ * Process user tracking cookie (QS_UserTrackingCookieName)
+ *
+ * @param r
+ * @return DECLINED or 302
+ */
+static int qos_post_read_request_later(request_rec *r) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ char *value;
+ const char *ignore;
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">PR_2");
+ }
+
+ if(!ap_is_initial_req(r) || !sconf->user_tracking_cookie) {
+ return DECLINED;
+ }
+
+ value = qos_get_remove_cookie(r, sconf->user_tracking_cookie);
+ qos_get_create_user_tracking(r, sconf, value);
+
+ if(!sconf->user_tracking_cookie_force) {
+ return DECLINED;
+ }
+
+ if(qos_request_check(r, sconf) != APR_SUCCESS) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ if(strcmp("/favicon.ico", r->parsed_uri.path) == 0) {
+ return DECLINED;
+ }
+
+ ignore = apr_table_get(r->subprocess_env, QOS_DISABLE_UTC_ENFORCEMENT);
+ if(ignore) {
+ return DECLINED;
+ }
+ if(strcmp(sconf->user_tracking_cookie_force, r->parsed_uri.path) == 0) {
+ /* access to check url */
+ if(sconf->user_tracking_cookie_jsredirect == 1) {
+ apr_table_set(r->subprocess_env, "QS_UT_NAME", sconf->user_tracking_cookie);
+ apr_table_set(r->subprocess_env, "QS_UT_INITIAL_URI", "/");
+ apr_table_set(r->subprocess_env, "QS_UT_QUERY", "qs=init");
+ if(r->parsed_uri.query && strcmp(r->parsed_uri.query, "qs=init") == 0) {
+ apr_table_add(r->headers_out, "Cache-Control", "no-cache, no-store");
+ qos_send_user_tracking_cookie(r, sconf, HTTP_OK);
+ return DECLINED;
+ }
+ if(r->parsed_uri.query && (strncmp(r->parsed_uri.query, "r=", 2) == 0)) {
+ char *redirect_page;
+ int buf_len = 0;
+ unsigned char *buf;
+ char *q = r->parsed_uri.query;
+ buf_len = qos_decrypt(r, sconf, &buf, &q[2]);
+ if(buf_len > 0) {
+ redirect_page = apr_psprintf(r->pool, "%.*s",
+ buf_len, buf);
+ apr_table_set(r->subprocess_env, "QS_UT_INITIAL_URI", redirect_page);
+ }
+ }
+ }
+ if(apr_table_get(r->subprocess_env, QOS_USER_TRACKING_NEW) == NULL) {
+ if(r->parsed_uri.query && (strncmp(r->parsed_uri.query, "r=", 2) == 0)) {
+ /* client has send a cookie, redirect to original url */
+ int buf_len = 0;
+ unsigned char *buf;
+ char *q = r->parsed_uri.query;
+ buf_len = qos_decrypt(r, sconf, &buf, &q[2]);
+ if(buf_len > 0) {
+ char *redirect_page = apr_psprintf(r->pool, "%s%.*s",
+ qos_this_host(r),
+ buf_len, buf);
+ apr_table_set(r->headers_out, "Location", redirect_page);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ }
+ } /* else, "grant access" to the error page */
+ /* but prevent page caching (the browser shall always access the
+ server when redireced to this url */
+ apr_table_add(r->headers_out, "Cache-Control", "no-cache, no-store");
+ } else if(apr_table_get(r->subprocess_env, QOS_USER_TRACKING_NEW) != NULL) {
+ if((r->method_number == M_GET || sconf->user_tracking_cookie_jsredirect == 1) &&
+ (apr_table_get(r->subprocess_env, QOS_USER_TRACKING_RENEW) == NULL)) {
+ /* no valid cookie in request, redirect to check page */
+ char *redirect_page = apr_pstrcat(r->pool, qos_this_host(r),
+ sconf->user_tracking_cookie_force,
+ "?r=",
+ qos_encrypt(r, sconf,
+ (unsigned char *)r->unparsed_uri,
+ strlen(r->unparsed_uri)),
+ NULL);
+ apr_table_set(r->headers_out, "Location", redirect_page);
+ if(sconf->user_tracking_cookie_jsredirect < 1) {
+ qos_send_user_tracking_cookie(r, sconf, HTTP_MOVED_TEMPORARILY);
+ }
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * All headers has been read. End/updates connection level filters and propagtes
+ * per connection events to the request_rec.
+ *
+ * @param r
+ * @return DECLINED
+ */
+static int qos_post_read_request(request_rec *r) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qos_ifctx_t *inctx = NULL;
+
+ /* propagate connection env vars to req, geo data and QS_SrvMaxConn */
+ const char *country = apr_table_get(r->connection->notes, QS_COUNTRY);
+ const char *connections = apr_table_get(r->connection->notes, "QS_SrvConn");
+ const char *all_connections = apr_table_get(r->connection->notes, "QS_AllConn");
+ const char *fromCurrentIp = apr_table_get(r->connection->notes, "QS_IPConn");
+ const char *connectionid = apr_table_get(r->connection->notes, QS_CONNID);
+ const char *lowPrioFlags = apr_table_get(r->connection->notes, "QS_ClientLowPrio");
+ const char *isVipIP = apr_table_get(r->connection->notes, QS_ISVIPREQ);
+
+ /* QS_UnsetReqHeader */
+ qos_unset_reqheader(r, sconf);
+
+ if(sconf->geodb) {
+ if(sconf->qos_cc_forwardedfor) {
+ // override country determined on a per connection basis
+ const char *forwardedfor = qos_forwardedfor_fromHeader(r, sconf->qos_cc_forwardedfor);
+ if(forwardedfor) {
+ unsigned long ip = qos_geo_str2long(r->pool, forwardedfor);
+ if(ip) {
+ qos_geo_entry_t *pB = bsearch(&ip,
+ sconf->geodb->data,
+ sconf->geodb->size,
+ sizeof(qos_geo_entry_t),
+ qos_geo_comp);
+ if(pB) {
+ country = apr_pstrdup(r->pool, pB->country);
+ }
+ } else {
+ if(apr_table_get(r->notes, "QOS_LOG_PFX069") == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(069)"no valid IP header found (@prr):"
+ " invalid header value '%s', fallback to connection's IP %s, id=%s",
+ forwardedfor,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "069"));
+ apr_table_set(r->notes, "QOS_LOG_PFX069", "log once");
+ QS_INC_EVENT(sconf, 69);
+ }
+ }
+ } else {
+ if(apr_table_get(r->notes, "QOS_LOG_PFX069") == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(069)"no valid IP header found (@prr):"
+ " header '%s' not available, fallback to connection's IP %s, id=%s",
+ sconf->qos_cc_forwardedfor,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "069"));
+ apr_table_set(r->notes, "QOS_LOG_PFX069", "log once");
+ QS_INC_EVENT(sconf, 69);
+ }
+ }
+ }
+ }
+ if(country) {
+ apr_table_set(r->subprocess_env, QS_COUNTRY, country);
+ }
+ if(connections) {
+ apr_table_set(r->subprocess_env, "QS_SrvConn", connections);
+ }
+ if(fromCurrentIp) {
+ apr_table_set(r->subprocess_env, "QS_IPConn", fromCurrentIp);
+ }
+ if(all_connections) {
+ apr_table_set(r->subprocess_env, "QS_AllConn", all_connections);
+ }
+ if(connectionid == NULL) {
+ connectionid = apr_psprintf(r->pool, "%"APR_TIME_T_FMT"%.2ld%.5"APR_PID_T_FMT,
+ r->request_time,
+ r->connection->id % 100,
+ getpid());
+ apr_table_set(r->connection->notes, QS_CONNID, connectionid);
+ }
+ apr_table_set(r->subprocess_env, QS_CONNID, connectionid);
+
+ if(!ap_is_initial_req(r)) {
+ // sub-request
+ qos_propagate_events(r);
+ } else {
+ qos_pr_event_limit(r, sconf);
+ }
+
+ /* QS_ClientPrefer: propagate connection env vars to req */
+ if(lowPrioFlags) {
+ apr_table_set(r->subprocess_env, "QS_ClientLowPrio", lowPrioFlags);
+ }
+ /* QS_IsVipRequest is set due VIP IP */
+ if(isVipIP) {
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, isVipIP);
+ }
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">PR_1");
+ }
+
+ if(qos_request_check(r, sconf) != APR_SUCCESS) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ if(!ap_is_initial_req(r)) {
+ // we are done for this request (e.g. error page)
+ return DECLINED;
+ }
+
+ qos_parp_prr(r, sconf);
+ if(sconf && (sconf->req_rate != -1)) {
+ inctx = qos_get_ifctx(r->connection->input_filters);
+ if(inctx) {
+ const char *te = apr_table_get(r->headers_in, "Transfer-Encoding");
+ inctx->r = r;
+ if(r->read_chunked || (te && (strcasecmp(te, "chunked") == 0))) {
+ ap_add_input_filter("qos-in-filter2", inctx, r, r->connection);
+ inctx->status = QS_CONN_STATE_CHUNKED;
+ } else {
+ const char *cl = apr_table_get(r->headers_in, "Content-Length");
+ if(cl == NULL) {
+ inctx->status = QS_CONN_STATE_END;
+#if APR_HAS_THREADS
+ if(!sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT26 */
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT26 */
+ }
+#endif
+ } else {
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ if(APR_SUCCESS == apr_strtoff(&inctx->cl_val, cl, NULL, 0))
+#else
+ if((inctx->cl_val = apr_atoi64(cl)) >= 0)
+#endif
+ {
+ ap_add_input_filter("qos-in-filter2", inctx, r, r->connection);
+ inctx->status = QS_CONN_STATE_BODY;
+ } else {
+ /* header filter should block this request */
+ }
+ }
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * QS_LimitRequestBody, if content-length header is available.
+ *
+ * @param r
+ * @param sconf Either server or dir config is used (or env var)
+ * @param dconf Either server or dir config is used (or env var)
+ * @return HTTP_REQUEST_ENTITY_TOO_LARGE if not allowed
+ */
+static apr_status_t qos_limitrequestbody_ctl(request_rec *r, qos_srv_config *sconf,
+ qos_dir_config *dconf) {
+ apr_off_t maxpost = qos_maxpost(r, sconf, dconf);
+ if(maxpost != -1) {
+ const char *l = apr_table_get(r->headers_in, "Content-Length");
+ if(l != NULL) {
+ apr_off_t s;
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ char *errp = NULL;
+ if((APR_SUCCESS != apr_strtoff(&s, l, &errp, 10)) || (s < 0))
+#else
+ if(((s = apr_atoi64(l)) < 0) || (s < 0))
+#endif
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(044)"access denied%s, QS_LimitRequestBody:"
+ " invalid content-length header, c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "044"));
+ QS_INC_EVENT(sconf, 44);
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ if(s > maxpost) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(044)"access denied%s, QS_LimitRequestBody:"
+ " max=%"APR_OFF_T_FMT" this=%"APR_OFF_T_FMT", c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ maxpost, s,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "044"));
+ QS_INC_EVENT(sconf, 44);
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ } else {
+ int read_chunked = 0;
+ if(r->read_chunked) {
+ read_chunked = 1;
+ } else {
+ // Apache 2.4
+ const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ if(tenc && strcasecmp(tenc, "chunked") == 0) {
+ read_chunked = 1;
+ }
+ }
+ if(ap_is_initial_req(r) && read_chunked) {
+ ap_add_input_filter("qos-in-filter3", NULL, r, r->connection);
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * Header parser (executed after mod_setenvif but before mod_parp).
+ * Implements content-length based request body size limit and activates
+ * content-length adijustmen for compressed request body.
+ *
+ * @param r
+ * @return
+ */
+static int qos_header_parser1(request_rec * r) {
+ if(ap_is_initial_req(r)) {
+ apr_status_t rv;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qos_dir_config *dconf = (qos_dir_config*)ap_get_module_config(r->per_dir_config,
+ &qos_module);
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">HP_2");
+ }
+
+ qos_deflate(r);
+
+ /** QS_LimitRequestBody */
+ rv = qos_limitrequestbody_ctl(r, sconf, dconf);
+ if(rv != APR_SUCCESS) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return rv;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * Header parser (executed before mod_setenvif or mod_parp).
+ * Enables mod_parp if request body processing (filter) has been enabled
+ * and implements the request header filter.
+ *
+ * @param r
+ * @return
+ */
+static int qos_header_parser0(request_rec * r) {
+ if(ap_is_initial_req(r)) {
+ int rc = DECLINED;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qos_dir_config *dconf = (qos_dir_config*)ap_get_module_config(r->per_dir_config,
+ &qos_module);
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">HP_1");
+ }
+
+ /*
+ * QS_DenyBody requires mod_parp
+ */
+ if(dconf && (dconf->bodyfilter_p == 1 || dconf->bodyfilter_d == 1)) {
+ qos_enable_parp(r);
+ }
+
+ /*
+ * QS_RequestHeaderFilter enforcement
+ */
+ rc = qos_hp_header_filter(r, sconf, dconf);
+ if(rc != DECLINED) {
+ return rc;
+ }
+
+ }
+ return DECLINED;
+}
+
+/**
+ * Header parser implements restrictions on a per location (url) basis.
+ *
+ * @param r
+ * @return
+ */
+static int qos_header_parser(request_rec * r) {
+ /* apply rules only to main request (avoid filtering of error documents) */
+ if(ap_is_initial_req(r)) {
+ char *msg = NULL;
+ char *uid = NULL;
+ int req_per_sec_block = 0;
+ apr_off_t kbytes_per_sec_limit = 0;
+ qs_acentry_t *event_kbytes_per_sec = NULL;
+ int status;
+ const char *tmostr = NULL;
+ qs_acentry_t *actEntry = NULL;
+ qs_acentry_t *actEntryCond = NULL;
+ qs_acentry_t *actEntryMain = NULL; // either e or e_cond (used for locking)
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qos_dir_config *dconf = (qos_dir_config*)ap_get_module_config(r->per_dir_config,
+ &qos_module);
+ qs_req_ctx *rctx = NULL;
+ const char *error_page = sconf->error_page;
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">HP_3");
+ }
+
+ qos_deflate_contentlength(r);
+
+ /* QS_SetEnvIfResBody */
+ if(dconf && dconf->response_pattern) {
+ ap_add_output_filter("qos-out-filter-body", NULL, r, r->connection);
+ }
+
+ /* enable broken connection detection (connection abort by client) */
+ if(apr_table_get(sconf->setenvstatus_t, QS_BROKEN_CON)) {
+ ap_add_output_filter("qos-out-filter-brokencon", NULL, r, r->connection);
+ }
+
+ /*
+ * QS_Permit* / QS_Deny* enforcement (but not QS_DenyEvent)
+ */
+ status = qos_hp_filter(r, sconf, dconf);
+ /* prepare audit log */
+ if(m_enable_audit && dconf) {
+ qos_audit(r, dconf);
+ }
+ if(status != DECLINED) {
+ return status;
+ }
+
+ /*
+ * Dynamic keep alive
+ */
+ if(!sconf->log_only) {
+ qos_keepalive(r, sconf);
+ }
+
+ /*
+ * VIP control
+ */
+ if(sconf->header_name || sconf->vip_user) {
+ rctx = qos_rctx_config_get(r);
+ rctx->is_vip = qos_is_vip(r, sconf);
+ if(rctx->is_vip) {
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(cconf) {
+ cconf->is_vip = 1;
+ }
+ }
+ }
+
+ /*
+ * additional variables
+ */
+ if(apr_table_elts(sconf->setenvifparp_t)->nelts > 0) {
+ qos_parp_hp(r, sconf);
+ }
+ if((apr_table_elts(sconf->setenvifparpbody_t)->nelts > 0) && qos_parp_body_data_fn) {
+ qos_parp_hp_body(r, sconf);
+ }
+ if(r->parsed_uri.query) {
+ qos_setenvifquery(r, sconf, dconf);
+ }
+ if(sconf->setenvif_t->nelts > 0) {
+ qos_setenvif(r, sconf->setenvif_t);
+ }
+ if(dconf->setenvif_t->nelts > 0) {
+ qos_setenvif(r, dconf->setenvif_t);
+ }
+ if(apr_table_elts(sconf->setenv_t)->nelts > 0) {
+ qos_setenv(r, sconf);
+ }
+ if(apr_table_elts(sconf->setreqheader_t)->nelts > 0) {
+ qos_setreqheader(r, sconf->setreqheader_t);
+ }
+ tmostr = apr_table_get(r->subprocess_env, QS_TIMEOUT);
+ if(tmostr) {
+ apr_interval_time_t timeout = apr_time_from_sec(atoi(tmostr));
+ if(timeout > 0) {
+ qs_conn_base_ctx *bctx = qos_get_conn_base_ctx(r->connection);
+ if(bctx && bctx->clientSocket) {
+ if(QS_ISDEBUG(r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"set connection timeout to %s seconds, id=%s",
+ tmostr, qos_unique_id(r, NULL));
+ }
+ apr_socket_timeout_set(bctx->clientSocket, timeout);
+ }
+ }
+ }
+
+ /*
+ * QS_DenyEvent
+ */
+ if(apr_table_elts(dconf->rfilter_table)->nelts > 0) {
+ status = qos_hp_event_deny_filter(r, sconf, dconf);
+ if(status != DECLINED) {
+ return status;
+ }
+ }
+
+ /*
+ * QS_EventLimitCount
+ */
+ if(sconf->event_limit_a->nelts > 0) {
+ status = qos_hp_event_limit(r, sconf);
+ if(status != DECLINED) {
+ return status;
+ }
+ }
+
+ /*
+ * QS_EventRequestLimit
+ */
+ if(sconf->has_event_filter) {
+ status = qos_hp_event_filter(r, sconf);
+ if(status != DECLINED) {
+ return status;
+ }
+ }
+
+ /*
+ * QS_EventPerSecLimit/QS_EventKBytesPerSecLimit
+ */
+ if(sconf->has_event_limit) {
+ event_kbytes_per_sec = qos_hp_event_count(r, &req_per_sec_block, &kbytes_per_sec_limit);
+ }
+
+ /*
+ * QS_SetEnvIfCmp
+ */
+ qos_setenvifcmp(r, dconf->setenvcmp);
+
+ /*
+ * QS_ClientEventRequestLimit
+ */
+ if(sconf->qos_cc_event_req >= 0) {
+ status = qos_hp_cc_event_count(r, sconf, rctx);
+ if(status != DECLINED) {
+ return status;
+ }
+ }
+
+ /*
+ * QS_ClientSerialize
+ */
+ if(sconf->qos_cc_serialize && apr_table_get(r->subprocess_env, QS_SERIALIZE)) {
+ qos_hp_cc_serialize(r, sconf, rctx);
+ }
+
+ /*
+ * QS_SrvSerialize
+ */
+ if((sconf->serialize == 1) && apr_table_get(r->subprocess_env, QS_SRVSERIALIZE)) {
+ qos_hp_srv_serialize(r, sconf, rctx);
+ }
+
+ /*
+ * client control
+ */
+ if(qos_hp_cc(r, sconf, &msg, &uid) != DECLINED) {
+ int rc;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ "%s, id=%s", msg == NULL ? "-" : msg,
+ qos_unique_id(r, uid));
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return m_retcode;
+ }
+ }
+
+ /*
+ * Request level control
+ * get rule with conditional enforcement
+ */
+ actEntryCond = qos_getcondrule_byregex(r, sconf);
+ /* 1st prio has "Match" rule */
+ actEntry = qos_getrule_byregex(r, sconf);
+ /* 2th prio has "URL" rule */
+ if(!actEntry) actEntry = qos_getrule_bylocation(r, sconf);
+ if(actEntry) {
+ actEntryMain = actEntry;
+ } else if(actEntryCond) {
+ actEntryMain = actEntryCond;
+ }
+
+ if(!rctx) {
+ rctx = qos_rctx_config_get(r);
+ }
+ // optimistic locking (write only)
+ if(actEntryMain) {
+ rctx->entry_cond = actEntryCond;
+ rctx->entry = actEntry;
+
+ apr_global_mutex_lock(actEntryMain->lock); /* @CRT5 */
+
+ if(actEntryCond) {
+ actEntryCond->counter++;
+ }
+
+ if(actEntry) {
+ actEntry->counter++;
+ if(actEntry->req_per_sec_block_rate > req_per_sec_block) {
+ /* update req_per_sec_block if event restriction has returned worse block rate */
+ req_per_sec_block = actEntry->req_per_sec_block_rate;
+ }
+ }
+
+ apr_global_mutex_unlock(actEntryMain->lock); /* @CRT5 */
+
+ }
+
+ if(actEntry) {
+ /*
+ * QS_LocRequestLimitMatch/QS_LocRequestLimit/QS_LocRequestLimitDefault enforcement
+ */
+ if(actEntry->limit && (actEntry->counter > actEntry->limit)) {
+ /* vip session has no limitation */
+ if(rctx->is_vip) {
+ qs_set_evmsg(r, "S;");
+ } else {
+ /* std user */
+ int rc;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(010)"access denied%s, QS_LocRequestLimit* rule: %s(%d),"
+ " concurrent requests=%d,"
+ " c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ actEntry->url, actEntry->limit, actEntry->counter,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "010"));
+ QS_INC_EVENT(sconf, 10);
+ qs_set_evmsg(r, "D;");
+ // request has already been blocked, don't cont this request for req/sec violations!
+ apr_table_set(r->notes, QS_R010_ALREADY_BLOCKED, "");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return m_retcode;
+ }
+ }
+ }
+ /*
+ * QS_LocRequestPerSecLimit/QS_EventPerSecLimit enforcement
+ */
+ if(req_per_sec_block) {
+ if(rctx->is_vip) {
+ qs_set_evmsg(r, "S;");
+ } else {
+ qs_set_evmsg(r, "L;");
+ if(!sconf->log_only) {
+ apr_sleep(req_per_sec_block*1000);
+ }
+ /* don't wait more than once */
+ req_per_sec_block = 0;
+ }
+ }
+
+ /*
+ * QS_LocKBytesPerSecLimit selection
+ */
+ if(actEntry->kbytes_per_sec_limit) {
+ if(kbytes_per_sec_limit) {
+ if(actEntry->kbytes_per_sec_limit < kbytes_per_sec_limit) {
+ // this is lower than the event limitation
+ kbytes_per_sec_limit = actEntry->kbytes_per_sec_limit;
+ event_kbytes_per_sec = actEntry;
+ }
+ } else {
+ kbytes_per_sec_limit = actEntry->kbytes_per_sec_limit;
+ event_kbytes_per_sec = actEntry;
+ }
+ }
+ }
+
+ /*
+ * QS_EventKBytesPerSecLimit or QS_LocKBytesPerSecLimit enforcement
+ */
+ if(kbytes_per_sec_limit) {
+ if(rctx->is_vip) {
+ qs_set_evmsg(r, "S;");
+ } else {
+ qos_delay_ctx_t *dctx = apr_pcalloc(r->pool, sizeof(qos_delay_ctx_t));
+ dctx->rctx = rctx;
+ dctx->entry = event_kbytes_per_sec;
+ if(!sconf->log_only) {
+ ap_add_output_filter("qos-out-filter-delay", dctx, r, r->connection);
+ }
+ }
+ }
+
+ if(actEntryCond) {
+ /*
+ * QS_CondLocRequestLimitMatch
+ */
+ if(actEntryCond->limit && (actEntryCond->counter > actEntryCond->limit)) {
+ /* check condition */
+ const char *condition = apr_table_get(r->subprocess_env, QS_COND);
+ if(condition) {
+ if(ap_regexec(actEntryCond->condition, condition, 0, NULL, 0) == 0) {
+ /* vip session has no limitation */
+ if(rctx->is_vip) {
+ qs_set_evmsg(r, "S;");
+ } else {
+ /* std user */
+ int rc;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(011)"access denied, QS_CondLocRequestLimitMatch"
+ " rule: %s(%d),"
+ " concurrent requests=%d,"
+ " c=%s, id=%s",
+ actEntryCond->url, actEntryCond->limit, actEntryCond->counter,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "011"));
+ QS_INC_EVENT(sconf, 11);
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return m_retcode;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * QS_EventPerSecLimit
+ */
+ if(req_per_sec_block) {
+ qs_set_evmsg(r, "L;");
+ if(!sconf->log_only) {
+ apr_sleep(req_per_sec_block*1000);
+ }
+ }
+
+ /*
+ * QS_Delay
+ */
+ qos_delay(r, sconf);
+
+ }
+ return DECLINED;
+}
+
+/**
+ * QS_LimitRequestBody
+ * Input filter limiting request body size for chunked encoded requests.
+ *
+ * @param f
+ * @param bb
+ * @param mode
+ * @param block
+ * @param nbytes
+ * @return
+ */
+static apr_status_t qos_in_filter3(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t nbytes) {
+ apr_status_t rv = ap_get_brigade(f->next, bb, mode, block, nbytes);
+ request_rec *r = f->r;
+
+ qos_srv_config *sconf = ap_get_module_config(r->server->module_config, &qos_module);
+ qos_dir_config *dconf = ap_get_module_config(r->per_dir_config, &qos_module);
+ apr_off_t maxpost = qos_maxpost(r, sconf, dconf);
+
+ if(rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if(maxpost != -1) {
+ apr_size_t bytes = 0;
+ apr_bucket *b;
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ bytes = bytes + b->length;
+ }
+ rctx->maxpostcount += bytes;
+ if(rctx->maxpostcount > maxpost) {
+ int rc;
+ const char *error_page = sconf->error_page;
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(044)"access denied%s, QS_LimitRequestBody:"
+ " max=%"APR_OFF_T_FMT" this=%"APR_OFF_T_FMT", c=%s, id=%s",
+ sconf->log_only ? " (log only)" : "",
+ maxpost, rctx->maxpostcount,
+ QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
+ qos_unique_id(r, "044"));
+ QS_INC_EVENT(sconf, 44);
+ qs_set_evmsg(r, "D;");
+ if(!sconf->log_only) {
+ rc = qos_error_response(r, error_page);
+ if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
+ return rc;
+ }
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * Input filter removes connection from sconf->inctx_t->table
+ * when reading EOS.
+ *
+ * @param f
+ * @param bb
+ * @param mode
+ * @param block
+ * @param nbytes
+ * @return
+ */
+static apr_status_t qos_in_filter2(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t nbytes) {
+ qos_ifctx_t *inctx = f->ctx;
+ apr_status_t rv = ap_get_brigade(f->next, bb, mode, block, nbytes);
+ if((rv == APR_SUCCESS) && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(inctx->c->base_server->module_config,
+ &qos_module);
+ ap_remove_input_filter(f);
+#if APR_HAS_THREADS
+ if(!sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT28 */
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT28 */
+ }
+#endif
+ }
+ return rv;
+}
+
+/**
+ * Input filter, used to log timeout event, mark slow clients,
+ * and to calculate packet rate.
+ *
+ * Adds/removes the connection from the sconf->inctx_t->table
+ * dapending of the request state (read head/body, keepalive, ...).
+ *
+ * @param f
+ * @param bb
+ * @param mode
+ * @param block
+ * @param nbytes
+ * @return
+ */
+static apr_status_t qos_in_filter(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t nbytes) {
+ apr_status_t rv;
+ qos_ifctx_t *inctx = f->ctx;
+ apr_size_t bytes = 0;
+ int crs = inctx->status;
+ rv = ap_get_brigade(f->next, bb, mode, block, nbytes);
+ if(rv == APR_SUCCESS) {
+ if(inctx->lowrate != -1) {
+ bytes = qos_packet_rate(inctx, bb);
+ }
+ }
+ if(inctx->status == QS_CONN_STATE_KEEP) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(inctx->c->base_server->module_config,
+ &qos_module);
+ inctx->time = time(NULL);
+ inctx->nbytes = 0;
+ inctx->status = QS_CONN_STATE_HEAD;
+#if APR_HAS_THREADS
+ if(sconf->inctx_t && !sconf->inctx_t->exit && sconf->min_rate_off == 0) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT23 */
+ apr_table_setn(sconf->inctx_t->table,
+ QS_INCTX_ID,
+ (char *)inctx);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT23 */
+ }
+#endif
+ }
+ if(rv != APR_SUCCESS) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(inctx->c->base_server->module_config,
+ &qos_module);
+ inctx->status = QS_CONN_STATE_END;
+ inctx->time = 0;
+ inctx->nbytes = 0;
+#if APR_HAS_THREADS
+ if(sconf->inctx_t && !sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT24 */
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT24 */
+ }
+#endif
+ }
+ if(inctx->status > QS_CONN_STATE_NEW) {
+ if(rv == APR_SUCCESS) {
+ if(bytes == 0) {
+ apr_bucket *b;
+ for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ bytes = bytes + b->length;
+ }
+ }
+ inctx->nbytes = inctx->nbytes + bytes;
+ inctx->hasBytes = inctx->nbytes;
+ if(inctx->status == QS_CONN_STATE_BODY) {
+ if(inctx->cl_val >= bytes) {
+ inctx->cl_val = inctx->cl_val - bytes;
+ }
+ if(inctx->cl_val == 0) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(inctx->c->base_server->module_config,
+ &qos_module);
+#if APR_HAS_THREADS
+ if(!sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT27 */
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT27 */
+ }
+#endif
+ }
+ }
+ }
+ if((rv == APR_TIMEUP) &&
+ (crs != QS_CONN_STATE_END) &&
+ (crs != QS_CONN_STATE_KEEP)) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(inctx->c->base_server->module_config,
+ &qos_module);
+ /* mark clients causing a timeout */
+ if(sconf && sconf->has_qos_cc) {
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ request_rec *r = f->r;
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT18 */
+ qos_ip_str2long(QS_CONN_REMOTEIP(inctx->c), searchE.ip6);
+ clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
+ (*clientEntry)->lowrate = time(NULL);
+ (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_TIMEOUT;
+ if(r) {
+ qs_set_evmsg(r, "r;");
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT18 */
+ }
+ inctx->lowrate = QS_PKT_RATE_TH + 1;
+ }
+ }
+ return rv;
+}
+
+/**
+ * BrokenConnection
+ */
+static apr_status_t qos_out_filter_brokencon(ap_filter_t *f, apr_bucket_brigade *bb) {
+ apr_status_t rc = ap_pass_brigade(f->next, bb);
+ if(rc == APR_ECONNABORTED || rc == APR_EPIPE) {
+ // client closed the connection (abort or broken pipe)
+ request_rec *r = f->r;
+ qs_set_evmsg(r, "A;");
+ apr_table_set(r->connection->notes, QS_BROKEN_CON, "");
+ }
+ return rc;
+}
+
+/**
+ * QS_SetEnvIfResBody
+ *
+ * Searches the response body for the pattern defined by the QS_SetEnvIfResBody
+ * directive (supports only one search pattern (literal string)).
+ *
+ * @param f
+ * @param bb
+ * @return
+ */
+static apr_status_t qos_out_filter_body(ap_filter_t *f, apr_bucket_brigade *bb) {
+ request_rec *r = f->r;
+ qos_dir_config *dconf = ap_get_module_config(r->per_dir_config, &qos_module);
+ qs_req_ctx *rctx;
+ int len;
+ apr_bucket *b;
+
+ if((dconf == NULL) || (dconf->response_pattern == NULL)) {
+ // not used
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ rctx = qos_rctx_config_get(r);
+ len = dconf->response_pattern_len;
+
+ if((apr_table_get(r->subprocess_env, "QS_SetEnvIfResBodyIgnore") != NULL) &&
+ rctx->body_window == NULL) {
+ // skip this response (disabled and nothing processed yet)
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ if(APR_BUCKET_IS_EOS(b)) {
+ /* If we ever see an EOS, make sure to FLUSH. */
+ apr_bucket *flush = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, flush);
+ }
+ if(!(APR_BUCKET_IS_METADATA(b))) {
+ const char *buf;
+ apr_size_t nbytes;
+ if(apr_bucket_read(b, &buf, &nbytes, APR_BLOCK_READ) == APR_SUCCESS) {
+ if(nbytes > 0) {
+ int blen = nbytes > len ? len : nbytes - 1;
+ /* 1. overlap: this buffer avoids that we miss a string if it is cut apart
+ within two buckets
+ e.g., [Logi][n Page] instead of [Login Page] when searching for "Login Page" */
+ if(rctx->body_window == NULL) {
+ // first call, create a window buffer
+ rctx->body_window = apr_pcalloc(r->pool, (len*2)+1);
+ rctx->body_window[0] = '\0';
+ } else {
+ // subsequent call, searches within the window too
+ int wlen = strlen(rctx->body_window);
+ strncpy(&rctx->body_window[wlen], buf, blen);
+ rctx->body_window[wlen+blen+1] = '\0';
+ if(strstr(rctx->body_window, dconf->response_pattern)) {
+ /* found pattern */
+ if(dconf->response_pattern_var[0] == '!') {
+ apr_table_unset(r->subprocess_env, &dconf->response_pattern_var[1]);
+ } else {
+ apr_table_set(r->subprocess_env, dconf->response_pattern_var, dconf->response_pattern);
+ }
+ ap_remove_output_filter(f);
+ }
+ }
+ /* 2. new buffer (don't want to copy the data) */
+ if(qos_strnstr(buf, dconf->response_pattern, nbytes)) {
+ /* found pattern */
+ if(dconf->response_pattern_var[0] == '!') {
+ apr_table_unset(r->subprocess_env, &dconf->response_pattern_var[1]);
+ } else {
+ apr_table_set(r->subprocess_env, dconf->response_pattern_var, dconf->response_pattern);
+ }
+ ap_remove_output_filter(f);
+ }
+ /* 3. store the end (for next loop) */
+ strncpy(rctx->body_window, &buf[nbytes - blen], blen);
+ rctx->body_window[blen] = '\0';
+ }
+ }
+ }
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+/**
+ * Helper used by qos_out_filter_delay() to calculate/update
+ * the delay rate. Shall be called for every bucket we are
+ * sending to the client.
+ * @param request_time Time this request has started
+ * @param entry Rule entry to update / measure
+ * @param length Bytes we are going to transfer
+ * @return Wait time in microsseconds
+ */
+static apr_off_t qos_calc_kbytes_per_sec_wait_time(apr_time_t request_time,
+ qs_acentry_t *entry,
+ apr_off_t length) {
+ apr_off_t kbps_wait_time;
+ apr_global_mutex_lock(entry->lock); /* @CRT43 */
+ kbps_wait_time = entry->kbytes_per_sec_block_rate;
+ if(((entry->bytes / 1024) > entry->kbytes_per_sec_limit) ||
+ (request_time > (entry->kbytes_interval_us + APR_USEC_PER_SEC))) {
+ /* transferred more than the limit/sec OR
+ it's long time ago since we last updated the rate
+ => check within which time we did */
+ apr_time_t now = apr_time_now();
+ apr_time_t duration = now - entry->kbytes_interval_us;
+ apr_off_t kbs;
+ if(duration == 0) {
+ duration = 1;
+ }
+ kbs = entry->bytes * 1000 / duration;
+ entry->kbytes_per_sec = (entry->kbytes_per_sec + kbs) / 2;
+ if(duration > APR_USEC_PER_SEC) {
+ // lower than the defined kbytes/sec rate
+ if(kbps_wait_time > 0) {
+ // reduce wait time
+ apr_off_t newtime = kbps_wait_time * kbs / entry->kbytes_per_sec_limit;
+ // PI like closed-loop control
+ kbps_wait_time = (kbps_wait_time + newtime) / 2;
+ }
+ } else {
+ // higher than the defined kbytes/sec rate
+ if(kbps_wait_time == 0) {
+ kbps_wait_time = 1000; // start with 1 ms
+ } else {
+ // increase wait time
+ apr_off_t newtime = kbps_wait_time * kbs / entry->kbytes_per_sec_limit;
+ // PI like closed-loop control
+ kbps_wait_time = (kbps_wait_time + newtime) / 2;
+ }
+ }
+ if(kbps_wait_time > QS_MAX_DELAY) {
+ kbps_wait_time = QS_MAX_DELAY;
+ }
+ entry->kbytes_interval_us = now;
+ entry->bytes = 0;
+ }
+ entry->bytes = entry->bytes + length;
+ entry->kbytes_per_sec_block_rate = kbps_wait_time;
+ apr_global_mutex_unlock(entry->lock); /* @CRT43 */
+ return kbps_wait_time;
+}
+
+/**
+ * Output filter adds response delay.
+ *
+ * @param f
+ * @param bb
+ * @return
+ */
+static apr_status_t qos_out_filter_delay(ap_filter_t *f, apr_bucket_brigade *bb) {
+ qos_delay_ctx_t *dctx = f->ctx;
+ qs_acentry_t *entry = dctx->entry;
+ request_rec *r = f->r;
+ if(entry) {
+ apr_off_t length;
+ if(apr_brigade_length(bb, 1, &length) == APR_SUCCESS) {
+ if(length > 0) {
+ if(length > APR_BUCKET_BUFF_SIZE) {
+ // split (no proxy)
+ while(!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b, *first, *next;
+ apr_bucket_brigade *tmp_bb;
+ apr_status_t rv;
+ apr_off_t kbps_wait_time;
+ rv = apr_brigade_partition(bb, APR_BUCKET_BUFF_SIZE, &next);
+ if(rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ return rv;
+ }
+ if(rv == APR_INCOMPLETE) { /* no split needed */
+ break;
+ }
+ first = APR_BRIGADE_FIRST(bb);
+ APR_BUCKET_REMOVE(first);
+ kbps_wait_time = qos_calc_kbytes_per_sec_wait_time(r->request_time,
+ entry, first->length);
+ if(kbps_wait_time > 0) {
+ dctx->rctx->response_delayed = (1 + dctx->rctx->response_delayed + kbps_wait_time) / 2;
+ apr_sleep(kbps_wait_time);
+ }
+ tmp_bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmp_bb, first);
+ b = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmp_bb, b);
+ rv = ap_pass_brigade(f->next, tmp_bb);
+ if(rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ } else {
+ // sleep once every 8k
+ apr_off_t kbps_wait_time = qos_calc_kbytes_per_sec_wait_time(r->request_time,
+ entry, length);
+ if(length < APR_BUCKET_BUFF_SIZE) {
+ // be fair with very small responses
+ kbps_wait_time = kbps_wait_time * length / APR_BUCKET_BUFF_SIZE;
+ }
+ if(kbps_wait_time > 0) {
+ dctx->rctx->response_delayed = (1 + dctx->rctx->response_delayed + kbps_wait_time) / 2;
+ apr_sleep(kbps_wait_time);
+ }
+ }
+ }
+ }
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+/**
+ * Out filter measuring the minimal download bandwidth.
+ *
+ * @param f
+ * @param bb
+ * @return
+ */
+static apr_status_t qos_out_filter_min(ap_filter_t *f, apr_bucket_brigade *bb) {
+ request_rec *r = f->r;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
+ if(APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+#if APR_HAS_THREADS
+ if(!sconf->inctx_t->exit) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT30 */
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT30 */
+ }
+#endif
+ inctx->status = QS_CONN_STATE_END;
+ ap_remove_output_filter(f);
+ } else {
+ apr_size_t total = 0;
+ apr_bucket *b;
+ for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ total = total + b->length;
+ }
+ inctx->nbytes = inctx->nbytes + total;
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+/**
+ * Merges two rule tables. Entries whose key/name begin with a "+" are added
+ * while those with a "-" prefix are removed.
+ *
+ * @param p Pool to allocate new table from.
+ * @param b_rfilter_table Base rule table (parent)
+ * @param o_rfilter_table Over rule table (child)
+ * @return Merged table
+ */
+apr_table_t *qos_table_merge_create(apr_pool_t *p, apr_table_t *b_rfilter_table,
+ apr_table_t *o_rfilter_table) {
+ int i;
+ apr_table_t *rfilter_table = apr_table_make(p, apr_table_elts(b_rfilter_table)->nelts +
+ apr_table_elts(o_rfilter_table)->nelts);
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(b_rfilter_table)->elts;
+ // add additional (+) entries from the base/parent table
+ for(i = 0; i < apr_table_elts(b_rfilter_table)->nelts; ++i) {
+ if(entry[i].key[0] == '+') {
+ apr_table_setn(rfilter_table, entry[i].key, entry[i].val);
+ }
+ }
+ // add additional (+) entries from the over/child table
+ entry = (apr_table_entry_t *)apr_table_elts(o_rfilter_table)->elts;
+ for(i = 0; i < apr_table_elts(o_rfilter_table)->nelts; ++i) {
+ if(entry[i].key[0] == '+') {
+ apr_table_setn(rfilter_table, entry[i].key, entry[i].val);
+ }
+ }
+ // remove the "-" entries
+ for(i = 0; i < apr_table_elts(o_rfilter_table)->nelts; ++i) {
+ if(entry[i].key[0] == '-') {
+ char *id = apr_psprintf(p, "+%s", &entry[i].key[1]);
+ apr_table_unset(rfilter_table, id);
+ }
+ }
+ return rfilter_table;
+}
+
+/* QS_SrvMinDataRateOffEvent */
+#if APR_HAS_THREADS
+static void qos_disable_rate(request_rec *r, qos_srv_config *sconf,
+ qos_dir_config *dconf) {
+ if(dconf && sconf && (sconf->req_rate != -1) && (sconf->min_rate != -1)) {
+ apr_table_t *disable_reqrate_events = dconf->disable_reqrate_events;
+ if(apr_table_elts(sconf->disable_reqrate_events)->nelts > 0) {
+ disable_reqrate_events = qos_table_merge_create(r->pool, sconf->disable_reqrate_events,
+ dconf->disable_reqrate_events);
+ }
+ if(apr_table_elts(disable_reqrate_events)->nelts > 0) {
+ qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
+ if(inctx) {
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(disable_reqrate_events)->elts;
+ int i;
+ for(i = 0; i < apr_table_elts(disable_reqrate_events)->nelts; i++) {
+ char *v = entry[i].key;
+ if(apr_table_get(r->subprocess_env, &v[1])) {
+ inctx->disabled = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+#endif
+
+static void qos_start_res_rate(request_rec *r, qos_srv_config *sconf) {
+ if(sconf && (sconf->req_rate != -1) && (sconf->min_rate != -1)) {
+ qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
+ if(inctx) {
+ inctx->status = QS_CONN_STATE_RESPONSE;
+ inctx->time = time(NULL);
+ inctx->nbytes = 0;
+#if APR_HAS_THREADS
+ if(sconf->inctx_t && !sconf->inctx_t->exit && sconf->min_rate_off == 0) {
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT29 */
+ apr_table_setn(sconf->inctx_t->table,
+ QS_INCTX_ID,
+ (char *)inctx);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT29 */
+ }
+ ap_add_output_filter("qos-out-filter-min", NULL, r, r->connection);
+#endif
+ }
+ }
+}
+
+/* QS_SET_DSCP */
+static void qos_set_dscp(request_rec *r) {
+ const char *dscpStr = apr_table_get(r->subprocess_env, QS_SET_DSCP);
+ if(dscpStr) {
+#ifdef __unix__
+ qs_conn_base_ctx *base = qos_get_conn_base_ctx(r->connection);
+ int rc = -2;
+ int hasSocket = 0;
+ if(base!=NULL && base->clientSocket!=NULL) {
+ apr_socket_t *sock = base->clientSocket;
+ int fd;
+ int dscp = atoi(dscpStr);
+ hasSocket = 1;
+ apr_os_sock_get(&fd, sock);
+ if(dscp >= 0 && dscp < 64) {
+ int tos = dscp << 2;
+ if(QS_ISDEBUG(r->server)) {
+ int n = 0;
+ const char *d = "unknown";
+ while(m_dscp_desc[n].id >= 0) {
+ if(m_dscp_desc[n].id == dscp) {
+ d = m_dscp_desc[n].name;
+ }
+ n++;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ QOS_LOGD_PFX"%s=%s, tos=0x%02x, dscp=0x%02x (%s), id=%s",
+ QS_SET_DSCP, dscpStr, tos, dscp, d, qos_unique_id(r, NULL));
+ }
+ rc = setsockopt(fd,
+ IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+ }
+ }
+ if(rc != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
+ QOS_LOG_PFX(038)"DSCP, failed to set socket options, "
+ QS_SET_DSCP"=%s, socket=%s, rc=%d, id=%s",
+ dscpStr,
+ hasSocket ? "yes" : "no",
+ rc,
+ qos_unique_id(r, "038"));
+ }
+#else
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
+ QOS_LOG_PFX(038)QS_SET_DSCP" is not available for this platform");
+#endif
+ }
+}
+
+static void qos_end_res_rate(request_rec *r, qos_srv_config *sconf) {
+ if((sconf->req_rate != -1) && (sconf->min_rate != -1)) {
+ qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
+ if(inctx) {
+ inctx->time = time(NULL);
+ inctx->nbytes = 0;
+ if(r->connection->keepalive == AP_CONN_CLOSE) {
+ if(!sconf->inctx_t->exit) {
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT30 */
+#endif
+ inctx->status = QS_CONN_STATE_END;
+#if APR_HAS_THREADS
+ apr_table_unset(sconf->inctx_t->table,
+ QS_INCTX_ID);
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT30 */
+#endif
+ }
+ } else {
+ if(!sconf->inctx_t->exit) {
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(sconf->inctx_t->lock); /* @CRT30 */
+#endif
+ if(inctx->status != QS_CONN_STATE_DESTROY) {
+ inctx->status = QS_CONN_STATE_KEEP;
+#if APR_HAS_THREADS
+ apr_table_setn(sconf->inctx_t->table,
+ QS_INCTX_ID, (char *)inctx);
+#endif
+ }
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT30 */
+#endif
+ }
+ }
+ }
+ }
+}
+
+/**
+ * process response:
+ * - start min data measure
+ * - setenvif header
+ * - detects vip header and create session
+ * - header filter
+ */
+static apr_status_t qos_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) {
+ request_rec *r = f->r;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ qos_dir_config *dconf = ap_get_module_config(r->per_dir_config, &qos_module);
+ qs_headerfilter_mode_e mode;
+
+ qos_start_res_rate(r, sconf);
+ qos_setenvstatus(r, sconf, dconf);
+ qos_setenvresheader(r, sconf);
+ qos_setenvres(r, sconf);
+ if(sconf->user_tracking_cookie) {
+ if((sconf->user_tracking_cookie_jsredirect < 1) ||
+ (apr_table_get(r->subprocess_env, QOS_USER_TRACKING_RENEW) != NULL)) {
+ qos_send_user_tracking_cookie(r, sconf, r->status);
+ }
+ }
+ if(sconf->milestones) {
+ qos_update_milestone(r, sconf);
+ }
+ if(sconf->ip_header_name) {
+ const char *ctrl_h = apr_table_get(r->headers_out, sconf->ip_header_name);
+ if(ctrl_h) {
+ int match = 1;
+ if(sconf->ip_header_name_regex) {
+ if(ap_regexec(sconf->ip_header_name_regex, ctrl_h, 0, NULL, 0) != 0) {
+ match = 0;
+ }
+ }
+ if(match) {
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(cconf) {
+ qs_set_evmsg(r, "v;");
+ cconf->is_vip = 1;
+ cconf->set_vip_by_header = 1;
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ }
+ if(sconf->ip_header_name_drop) {
+ apr_table_unset(r->headers_out, sconf->ip_header_name);
+ }
+ }
+ }
+ if(sconf->header_name) {
+ /* got a vip header: create new session (if non exists) */
+ const char *ctrl_h = apr_table_get(r->headers_out, sconf->header_name);
+ if(ctrl_h && !apr_table_get(r->notes, QS_REC_COOKIE)) {
+ int match = 1;
+ if(sconf->header_name_regex) {
+ if(ap_regexec(sconf->header_name_regex, ctrl_h, 0, NULL, 0) != 0) {
+ match = 0;
+ }
+ }
+ if(match) {
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ qos_set_session(r, sconf);
+ if(cconf) {
+ qs_set_evmsg(r, "v;");
+ cconf->is_vip = 1;
+ cconf->set_vip_by_header = 1;
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ apr_table_set(r->notes, QS_REC_COOKIE, "");
+ }
+ if(sconf->header_name_drop) {
+ apr_table_unset(r->headers_out, sconf->header_name);
+ }
+ }
+ }
+ if(sconf->vip_user && r->user) {
+ if(!apr_table_get(r->notes, QS_REC_COOKIE)) {
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ qos_set_session(r, sconf);
+ if(cconf) {
+ qs_set_evmsg(r, "v;");
+ cconf->is_vip = 1;
+ cconf->set_vip_by_header = 1;
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ apr_table_set(r->notes, QS_REC_COOKIE, "");
+ }
+ }
+ if(sconf->vip_ip_user && r->user) {
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(cconf) {
+ qs_set_evmsg(r, "v;");
+ cconf->is_vip = 1;
+ cconf->set_vip_by_header = 1;
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ }
+ qos_unset_resheader(r, sconf);
+ /* don't handle response status since response header filter use "drop" action only */
+ mode = sconf->resheaderfilter;
+ if(dconf->resheaderfilter > QS_HEADERFILTER_OFF_DEFAULT) {
+ // override server configuration
+ mode = dconf->resheaderfilter;
+ }
+ if(mode > QS_HEADERFILTER_OFF) {
+ qos_header_filter(r, sconf, r->headers_out, "response",
+ sconf->reshfilter_table, mode);
+ }
+ qos_set_dscp(r);
+ qos_keepalive(r, sconf);
+ if(sconf->max_conn_close != -1) {
+ if(sconf->act->conn->connections > sconf->max_conn_close) {
+ qs_set_evmsg(r, "K;");
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+ }
+ /* disable request rate for certain connections */
+#if APR_HAS_THREADS
+ qos_disable_rate(r, sconf, dconf);
+#endif
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+}
+
+static apr_status_t qos_out_err_filter(ap_filter_t *f, apr_bucket_brigade *bb) {
+ request_rec *r = f->r;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+
+ if(sconf) {
+ qos_dir_config *dconf = ap_get_module_config(r->per_dir_config, &qos_module);
+ qos_setenvstatus(r, sconf, dconf);
+ qos_setenvresheader(r, sconf);
+ qos_setenvres(r, sconf);
+ if(sconf->milestones) {
+ qos_update_milestone(r, sconf);
+ }
+ }
+
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+}
+
+/**
+ * QS_SrvSerialize
+ */
+static void qos_logger_serialize(qos_srv_config *sconf, qs_req_ctx *rctx) {
+ if(rctx->srv_serialize_set) {
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT45 */
+ sconf->act->serialize->locked = 0;
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT45 */
+ }
+}
+
+/**
+ * QS_EventRequestLimit
+ * reset event counter
+ */
+static void qos_event_reset(qos_srv_config *sconf, qs_req_ctx *rctx) {
+ int i;
+ apr_table_entry_t *entry;
+ apr_global_mutex_lock(sconf->act->lock); /* @CRT32 */
+ entry = (apr_table_entry_t *)apr_table_elts(rctx->event_entries)->elts;
+ for(i = 0; i < apr_table_elts(rctx->event_entries)->nelts; i++) {
+ qs_acentry_t *e = (qs_acentry_t *)entry[i].val;
+ if(e->counter > 0) {
+ e->counter--;
+ }
+ }
+ apr_global_mutex_unlock(sconf->act->lock); /* @CRT32 */
+}
+
+static int qos_fixup(request_rec * r) {
+ int rc = 0;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ qos_dir_config *dconf = (qos_dir_config*)ap_get_module_config(r->per_dir_config,
+ &qos_module);
+ /* QS_VipUser/QS_VipIpUser */
+ if(sconf && (sconf->vip_user || sconf->vip_ip_user) && r->user) {
+ /* check r->user early (final status is update is implemented in output-filter) */
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ if(cconf) {
+ qs_set_evmsg(r, "v;");
+ cconf->is_vip = 1;
+ cconf->set_vip_by_header = 1;
+ apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
+ }
+ }
+
+ if(sconf->log_env == 1) {
+ qos_log_env(r, ">FX_1");
+ }
+
+#if APR_HAS_THREADS
+ qos_disable_rate(r, sconf, dconf);
+#endif
+
+ if(apr_table_elts(sconf->setreqheaderlate_t)->nelts > 0) {
+ qos_setreqheader(r, sconf->setreqheaderlate_t);
+ }
+
+ rc = qos_redirectif(r, sconf, sconf->redirectif);
+ if(rc != DECLINED) {
+ return rc;
+ }
+ rc = qos_redirectif(r, sconf, dconf->redirectif);
+ if(rc != DECLINED) {
+ return rc;
+ }
+
+ return DECLINED;
+}
+
+/**
+ * "free resources" and update stats
+ */
+static int qos_logger(request_rec *r) {
+ qs_req_ctx *rctx = qos_rctx_config_get(r);
+ qs_acentry_t *actEntry = rctx->entry;
+ qs_acentry_t *actEntryCond = rctx->entry_cond;
+ qs_acentry_t *actEntryMain = actEntry;
+ qs_conn_base_ctx *base = qos_get_conn_base_ctx(r->connection);
+ qs_conn_ctx *cconf = qos_get_cconf(r->connection);
+ apr_time_t now = 0;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ qos_dir_config *dconf = ap_get_module_config(r->per_dir_config, &qos_module);
+ if(actEntryMain == NULL) {
+ actEntryMain = actEntryCond;
+ }
+ if(rctx->response_delayed) {
+ qs_set_evmsg(r, "L;");
+ apr_table_set(r->subprocess_env, QS_RESDELYATIME,
+ apr_psprintf(r->pool, "%"APR_OFF_T_FMT, rctx->response_delayed));
+ }
+ qos_propagate_notes(r);
+ qos_propagate_events(r);
+ if(sconf->log_env == 1) {
+ qos_log_env(r, "<LG_1");
+ }
+ qos_end_res_rate(r, sconf);
+ if(sconf->setenvif_t->nelts > 0) {
+ qos_setenvif(r, sconf->setenvif_t);
+ }
+ if(dconf->setenvif_t->nelts > 0) {
+ qos_setenvif(r, dconf->setenvif_t);
+ }
+ if(sconf->has_qos_cc) {
+ qos_logger_cc(r, sconf, rctx);
+ }
+ qos_logger_event_limit(r, sconf);
+ if(base) {
+ base->requests++;
+ }
+ if(cconf) {
+ if(cconf->evmsg) {
+ rctx->evmsg = apr_pstrcat(r->pool, cconf->evmsg, rctx->evmsg, NULL);
+ }
+ }
+ if(sconf->has_event_filter) {
+ qos_event_reset(sconf, rctx);
+ }
+ if(sconf->serialize == 1) {
+ qos_logger_serialize(sconf, rctx);
+ }
+ if(sconf->has_event_limit) {
+ qos_lg_event_update(r, &now);
+ }
+ if(actEntryMain) {
+ char *h;
+ if(!now) {
+ now = apr_time_sec(r->request_time);
+ }
+ apr_global_mutex_lock(actEntryMain->lock); /* @CRT6 */
+ h = apr_psprintf(r->pool, "%d", actEntryMain->counter);
+ if(actEntryCond) {
+ if(actEntryCond->counter > 0) {
+ actEntryCond->counter--;
+ }
+ }
+ if(actEntry) {
+ if(actEntry->counter > 0) {
+ actEntry->counter--;
+ }
+ if(apr_table_get(r->notes, QS_R010_ALREADY_BLOCKED) == NULL) {
+ if(actEntry->req < LONG_MAX) {
+ actEntry->req++;
+ }
+ if(now > (actEntry->interval + QS_BW_SAMPLING_RATE)) {
+ actEntry->req_per_sec = actEntry->req / (now - actEntry->interval);
+ actEntry->req = 0;
+ actEntry->interval = now;
+ if(actEntry->req_per_sec_limit) {
+ qos_cal_req_sec(sconf, r, actEntry);
+ }
+ }
+ }
+ }
+ apr_global_mutex_unlock(actEntryMain->lock); /* @CRT6 */
+ /* allow logging of the current location usage */
+ apr_table_set(r->subprocess_env, "mod_qos_cr", h);
+ if(r->next) {
+ apr_table_set(r->next->subprocess_env, "mod_qos_cr", h);
+ }
+ /* decrement only once */
+ ap_set_module_config(r->request_config, &qos_module, NULL);
+ }
+ if(cconf && (cconf->sconf->max_conn != -1)) {
+ char *cc = apr_psprintf(r->pool, "%d", cconf->sconf->act->conn->connections);
+ apr_table_set(r->subprocess_env, "mod_qos_con", cc);
+ if(r->next) {
+ apr_table_set(r->next->subprocess_env, "mod_qos_con", cc);
+ }
+ }
+ if(rctx->evmsg) {
+ apr_table_set(r->subprocess_env, "mod_qos_ev", rctx->evmsg);
+ if(r->next) {
+ apr_table_set(r->next->subprocess_env, "mod_qos_ev", rctx->evmsg);
+ }
+ }
+#if APR_HAS_THREADS
+ qos_disable_rate(r, sconf, dconf);
+#endif
+
+ if(sconf->qslog_p) {
+ // ISBiDUkEQaC equiv %h %>s %B %{Content-Length}i %D %{mod_qos_user_id}e %k %{Event}e %{mod_qos_ev}e %{QS_AllConn}e '%v:%U'
+ apr_size_t nbytes;
+ const char *clID = apr_table_get(r->subprocess_env, QSLOG_CLID); // client identifier (individual users)
+ const char *event = apr_table_get(r->subprocess_env, QSLOG_EVENT); // generic event variable
+ const char *mod_qos_ev = apr_table_get(r->subprocess_env, "mod_qos_ev"); // qos events
+ const char *averageConn = apr_table_get(r->subprocess_env, QSLOG_AVERAGE); // average counter, e.g. connections
+ // TODO: measure the real traffic instead of using the (optional) content-length header
+ const char *contentLength = apr_table_get(r->headers_in, "Content-Length");
+ char *qslogstr = apr_psprintf(r->pool, "%s %s %s %s %s %s %d %s %s %s '%s:%s'\n",
+ QS_CONN_REMOTEIP(r->connection), /* %h */
+ (r->status <= 0) ? "-" : apr_itoa(r->pool, r->status), /* %s */
+ (!r->sent_bodyct || !r->bytes_sent) ? "0" : apr_off_t_toa(r->pool, r->bytes_sent), /* %B */
+ contentLength == NULL ? "-" : contentLength, /* %{content-length} */
+ apr_psprintf(r->pool, "%" APR_TIME_T_FMT, (apr_time_now() - r->request_time)), /* %D */
+ clID == NULL ? "-" : clID, /* %{mod_qos_user_id}e */
+ r->connection->keepalives ? r->connection->keepalives - 1 : 0, /* %k */
+ event == NULL ? "-" : event, /* %{Event}e */
+ mod_qos_ev == NULL ? "-" : mod_qos_ev, /* %{mod_qos_ev}e */
+ averageConn == NULL ? "- " : averageConn, /* %{QS_AllConn}e */
+ ap_escape_logitem(r->pool, ap_get_server_name(r)), /* %v */
+ ap_escape_logitem(r->pool, r->uri) /* %U */
+ );
+ nbytes = strlen(qslogstr);
+ apr_file_write(sconf->qslog_p, qslogstr, &nbytes);
+ }
+
+ return DECLINED;
+}
+
+static void qos_audit_check(ap_directive_t * node) {
+ ap_directive_t *pdir;
+ for(pdir = node; pdir != NULL; pdir = pdir->next) {
+ if(pdir->args &&
+ strstr(pdir->args, "%{"QS_PARP_PATH"}n") &&
+ strstr(pdir->args, "%{"QS_PARP_QUERY"}n")) {
+ m_enable_audit = 1;
+ }
+ if(pdir->first_child != NULL) {
+ qos_audit_check(pdir->first_child);
+ }
+ }
+}
+
+static int qos_module_check(const char *m) {
+ module *modp = NULL;
+ for(modp = ap_top_module; modp; modp = modp->next) {
+ if(strcmp(modp->name, m) == 0) {
+ return APR_SUCCESS;
+ }
+ }
+ return DECLINED;
+}
+
+/**
+ * inits each child
+ */
+static void qos_child_init(apr_pool_t *p, server_rec *bs) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_ifctx_list_t *inctx_t = NULL;
+#ifdef QS_INTERNAL_TEST
+#ifdef PREFORK_MPM
+ int seed = getpid() + time(NULL);
+#if APR_HAS_THREADS
+ seed += apr_os_thread_current();
+#endif
+ srand(seed);
+#endif
+#endif
+ qos_init_unique_id(p, bs);
+#if APR_HAS_THREADS
+ if(sconf->req_rate != -1) {
+ inctx_t = apr_pcalloc(p, sizeof(qos_ifctx_list_t));
+ inctx_t->exit = 0;
+ inctx_t->table = apr_table_make(p, 64);
+ sconf->inctx_t = inctx_t;
+ if(apr_thread_mutex_create(&sconf->inctx_t->lock, APR_THREAD_MUTEX_DEFAULT, p) != APR_SUCCESS) {
+ qos_disable_req_rate(bs, "create mutex");
+ } else {
+ apr_threadattr_t *tattr;
+ if(apr_threadattr_create(&tattr, p) != APR_SUCCESS) {
+ qos_disable_req_rate(bs, "create thread attr");
+ } else {
+ if(apr_thread_create(&sconf->inctx_t->thread, tattr,
+ qos_req_rate_thread, bs, p) != APR_SUCCESS) {
+ qos_disable_req_rate(bs, "create thread");
+ } else {
+ server_rec *sn = bs->next;
+ apr_pool_pre_cleanup_register(p, bs, qos_cleanup_req_rate_thread);
+ while(sn) {
+ qos_srv_config *sc = (qos_srv_config*)ap_get_module_config(sn->module_config, &qos_module);
+ sc->inctx_t = inctx_t;
+ sn = sn->next;
+ }
+ }
+ }
+ }
+ }
+#endif
+ if(sconf->has_qos_cc) {
+ apr_global_mutex_child_init(&u->qos_cc->lock, u->qos_cc->lock_file, p);
+ }
+ if(!sconf->act->child_init) {
+ sconf->act->child_init = 1;
+ /* propagate mutex to child process (required by some platforms) */
+ apr_global_mutex_child_init(&sconf->act->lock, sconf->act->lock_file, p);
+ }
+#if APR_HAS_THREADS
+ if(sconf->qsstatus) {
+ qos_init_status_thread(p, sconf, sconf->max_clients);
+ }
+#endif
+}
+
+/*
+static const char *qos_search_docroot(apr_pool_t *pconf, server_rec *bs,
+ ap_directive_t *node) {
+ ap_directive_t *pdir;
+ for(pdir = node; pdir != NULL; pdir = pdir->next) {
+ if(strcasecmp(pdir->directive, "DocumentRoot") == 0) {
+ return pdir->args;
+ }
+ if(pdir->first_child != NULL) {
+ const char *docroot = qos_search_docroot(pconf, bs, pdir->first_child);
+ if(docroot != NULL) {
+ return docroot;
+ }
+ }
+ }
+ return NULL;
+}
+*/
+
+static const char *detectErrorPage(apr_pool_t *ptemp, server_rec *bs, ap_directive_t *pdir) {
+ const qos_errelt_t *e = m_error_pages;
+ apr_finfo_t finfo;
+ /*
+ const char *docroot = qos_search_docroot(ptemp, bs, pdir);
+ if(docroot) {
+ docroot = ap_server_root_relative(ptemp, docroot);
+ }
+ */
+ while(e->path != NULL) {
+ char *path = ap_server_root_relative(ptemp, e->path);
+ if(apr_stat(&finfo, path, APR_FINFO_TYPE, ptemp) == APR_SUCCESS) {
+ return e->url;
+ }
+ /*
+ if(docroot) {
+ path = apr_pstrcat(ptemp, docroot, "/", e->path, NULL);
+ if(apr_stat(&finfo, path, APR_FINFO_TYPE, ptemp) == APR_SUCCESS) {
+ return e->url;
+ }
+ }
+ */
+ e++;
+ }
+ return NULL;
+}
+
+/**
+ * inits the server configuration
+ */
+static int qos_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *bs) {
+ qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
+ char *rev = qos_revision(ptemp);
+ qos_user_t *u;
+ int maxClients;
+ int cc_net_prefer_limit = 0;
+ apr_status_t rv;
+ ap_directive_t *pdir = ap_conftree;
+ const char *error_page = detectErrorPage(ptemp, bs, pdir);
+ int auto_error_page = 0;
+
+ /* verify if this Apache version is supported/mod_qos has been tested for
+ and enable/disable features */
+ qos_version_check(bs);
+
+ maxClients = qs_calc_maxClients(bs, bsconf);
+ QOS_MY_GENERATION(m_generation);
+ bsconf->max_clients = maxClients;
+
+ if(bsconf->ip_type == QS_IP_V4) {
+ m_ip_type = QS_IP_V4;
+ } else {
+ m_ip_type = QS_IP_V6;
+ }
+
+ qos_hostcode(ptemp, bs);
+
+ if(bsconf->log_only) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(009)"running in 'log only' mode - rules are NOT enforced!");
+ }
+ if(bsconf->geo_limit != -1 && !bsconf->geodb) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, bs,
+ QOS_LOG_PFX(100)"QS_ClientGeoCountryDB has not been configured");
+ }
+
+ if(bsconf->max_conn_close_percent) {
+ bsconf->max_conn_close = maxClients * bsconf->max_conn_close_percent / 100;
+ }
+ cc_net_prefer_limit = maxClients * bsconf->qos_cc_prefer / 100;
+ if(bsconf->qos_cc_prefer) {
+ bsconf->qos_cc_prefer = maxClients;
+ bsconf->qos_cc_prefer_limit = cc_net_prefer_limit;
+ } else {
+ bsconf->qos_cc_prefer = 0;
+ bsconf->qos_cc_prefer_limit = 0;
+ }
+ u = qos_get_user_conf(bs->process->pool);
+ if(u == NULL) return !OK;
+ u->server_start++;
+ /* mutex init */
+ if(bsconf->act->lock_file == NULL) {
+ bsconf->act->lock_file = apr_psprintf(bsconf->act->pool, "%s.mod_qos",
+ qos_tmpnam(bsconf->act->pool, bs));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"create mutex (ACT)(%s)",
+ bsconf->act->lock_file);
+ rv = apr_global_mutex_create(&bsconf->act->lock, bsconf->act->lock_file,
+ APR_LOCK_DEFAULT, bsconf->act->pool);
+ if (rv != APR_SUCCESS) {
+ char buf[MAX_STRING_LEN];
+ apr_strerror(rv, buf, sizeof(buf));
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, bs,
+ QOS_LOG_PFX(004)"failed to create mutex (ACT)(%s): %s",
+ bsconf->act->lock_file, buf);
+ exit(1);
+ }
+#ifdef AP_NEED_SET_MUTEX_PERMS
+ qos_unixd_set_global_mutex_perms(bsconf->act->lock);
+#endif
+ }
+ bsconf->base_server = bs;
+ bsconf->act->timeout = apr_time_sec(bs->timeout);
+ if(bsconf->act->timeout == 0) bsconf->act->timeout = 300;
+ if(qos_init_shm(bs, bsconf, bsconf->act, bsconf->location_t, maxClients) != APR_SUCCESS) {
+ return !OK;
+ }
+ apr_pool_pre_cleanup_register(bsconf->pool, bsconf->act, qos_cleanup_shm);
+
+ if((qos_module_check("mod_unique_id.c") != APR_SUCCESS) &&
+ (qos_module_check("mod_navajo.cpp") != APR_SUCCESS)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, bs,
+ QOS_LOG_PFX(009)"mod_unique_id not available"
+ " (mod_qos generates simple request id if required)");
+ }
+ qos_audit_check(ap_conftree);
+ qos_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ qos_ssl_var = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
+ if(qos_is_https == NULL || qos_ssl_var == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, bs,
+ QOS_LOG_PFX(009)"could not retrieve mod_ssl functions");
+ }
+ if(m_requires_parp) {
+ if(qos_module_check("mod_parp.c") != APR_SUCCESS) {
+ qos_parp_hp_table_fn = NULL;
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, bs,
+ QOS_LOG_PFX(009)"mod_parp not available"
+ " (required by some directives)");
+ } else {
+ qos_parp_hp_table_fn = APR_RETRIEVE_OPTIONAL_FN(parp_hp_table);
+ qos_parp_body_data_fn = APR_RETRIEVE_OPTIONAL_FN(parp_body_data);
+ }
+ }
+ if(u->server_start == 2) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(bsconf->hfilter_table)->elts;
+ for(i = 0; i < apr_table_elts(bsconf->hfilter_table)->nelts; i++) {
+ qos_fhlt_r_t *he = (qos_fhlt_r_t *)entry[i].val;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"request header filter rule (%s) %s: %s max=%d",
+ he->action == QS_FLT_ACTION_DROP ? "drop" : "deny", entry[i].key,
+ he->text, he->size);
+ }
+ entry = (apr_table_entry_t *)apr_table_elts(bsconf->reshfilter_table)->elts;
+ for(i = 0; i < apr_table_elts(bsconf->reshfilter_table)->nelts; i++) {
+ qos_fhlt_r_t *he = (qos_fhlt_r_t *)entry[i].val;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"response header filter rule (%s) %s: %s max=%d",
+ he->action == QS_FLT_ACTION_DROP ? "drop" : "deny", entry[i].key,
+ he->text, he->size);
+ }
+ }
+ if(bsconf->has_qos_cc) {
+ if(!u->qos_cc) {
+ u->qos_cc = qos_cc_new(bs->process->pool, bs, bsconf->qos_cc_size, bsconf->qos_cc_limitTable);
+ if(u->qos_cc == NULL) {
+ return !OK;
+ }
+ } else {
+ int configOk = 1;
+ int limitTableSize = apr_table_elts(bsconf->qos_cc_limitTable)->nelts;
+ if(u->qos_cc->limitTable) {
+ int i;
+ apr_table_entry_t *te = (apr_table_entry_t *)apr_table_elts(bsconf->qos_cc_limitTable)->elts;
+ for(i = 0; i < limitTableSize; i++) {
+ const char *name = te[i].key;
+ qos_s_entry_limit_conf_t *newentry = (qos_s_entry_limit_conf_t *)te[i].val;
+ qos_s_entry_limit_conf_t *entryConf = (qos_s_entry_limit_conf_t *)apr_table_get(u->qos_cc->limitTable, name);
+ if(entryConf) {
+ entryConf->limit = newentry->limit;
+ entryConf->limitTime = newentry->limitTime;
+ entryConf->condStr = NULL;
+ entryConf->preg = NULL;
+ if(newentry->condStr) {
+ entryConf->condStr = apr_pstrdup(bs->process->pool, newentry->condStr);
+ entryConf->preg = ap_pregcomp(bs->process->pool, newentry->condStr, AP_REG_EXTENDED);
+ }
+ } else {
+ // new variable
+ configOk = 0;
+ }
+ if(apr_table_elts(u->qos_cc->limitTable)->nelts != limitTableSize) {
+ // removed variable
+ configOk = 0;
+ }
+ }
+ } else {
+ if(limitTableSize > 0) {
+ // enabled after graceful restart
+ configOk = 0;
+ }
+ }
+ if(!configOk) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, bs,
+ QOS_LOG_PFX(001)"QS_ClientEventLimitCount directives"
+ " can't be added/removed by graceful restart. A server"
+ " restart is required to apply the new configuration!");
+ }
+ }
+ }
+ if(bsconf->error_page == NULL && error_page != NULL) {
+ bsconf->error_page = error_page;
+ auto_error_page = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"QS_ErrorPage: use %s for server %s:%d (global)",
+ error_page,
+ bs->server_hostname == NULL ? "-" : bs->server_hostname,
+ bs->addrs->host_port);
+ }
+
+ if(bsconf->qslog_str) {
+ char *qslogarg = apr_psprintf(pconf, "%s -f %s", bsconf->qslog_str, QSLOGFORMAT);
+ piped_log *pl = ap_open_piped_log(pconf, qslogarg);
+ if(pl == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(009)"failed to initialize the qslog facility '%s'", qslogarg);
+ }
+ bsconf->qslog_p = ap_piped_log_write_fd(pl);
+ }
+
+ {
+ server_rec *s = bs->next;
+ while(s) {
+ qos_srv_config *ssconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
+
+ /* mutex init */
+ if(ssconf->act->lock == NULL) {
+ ssconf->act->lock_file = bsconf->act->lock_file;
+ ssconf->act->lock = bsconf->act->lock;
+ }
+ ssconf->base_server = bs;
+ ssconf->act->timeout = apr_time_sec(s->timeout);
+ ssconf->qos_cc_prefer = bsconf->qos_cc_prefer;
+ ssconf->qos_cc_prefer_limit = bsconf->qos_cc_prefer_limit;
+ ssconf->max_clients = bsconf->max_clients;
+ ssconf->max_clients_conf = bsconf->max_clients_conf;
+ if(ssconf->max_conn_close_percent) {
+ ssconf->max_conn_close = maxClients * ssconf->max_conn_close_percent / 100;
+ }
+ if(ssconf->act->timeout == 0) {
+ ssconf->act->timeout = 300;
+ }
+ ssconf->qslog_p = bsconf->qslog_p;
+ if(ssconf->is_virtual) {
+ if(qos_init_shm(s, ssconf, ssconf->act, ssconf->location_t, maxClients) != APR_SUCCESS) {
+ return !OK;
+ }
+ apr_pool_pre_cleanup_register(ssconf->pool, ssconf->act,
+ qos_cleanup_shm);
+ if(ssconf->has_conn_counter == 0 && bsconf->has_conn_counter == 1) {
+ // shall use global counter because vhost has not QS_SrvMaxConn* directive
+ ssconf->act->conn = bsconf->act->conn;
+ }
+ }
+ if(ssconf->error_page == NULL && error_page != NULL) {
+ ssconf->error_page = error_page;
+ auto_error_page |= 2;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs,
+ QOS_LOGD_PFX"QS_ErrorPage: use %s for server %s:%d",
+ error_page,
+ s->server_hostname == NULL ? "-" : s->server_hostname,
+ s->addrs->host_port);
+ }
+ s = s->next;
+ }
+ }
+ if(auto_error_page) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs,
+ QOS_LOG_PFX(009)"found default error document '%s'. Use the QS_ErrorPage"
+ " directive to override this default page.",
+ error_page);
+ }
+ ap_add_version_component(pconf, apr_psprintf(pconf, "mod_qos/%s", rev));
+
+#ifdef QS_INTERNAL_TEST
+ fprintf(stdout, "\033[1mmod_qos TEST BINARY, NOT FOR PRODUCTIVE USE\033[0m\n");
+ fflush(stdout);
+#endif
+#ifndef QS_NO_STATUS_HOOK
+ APR_OPTIONAL_HOOK(ap, status_hook, qos_ext_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+
+ return DECLINED;
+}
+
+/**
+ * mod_qos
+ */
+static int qos_favicon(request_rec *r) {
+ int i;
+ unsigned const char ico[] = {
+ 0x00,0x00,0x01,0x00,0x01,0x00,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x05,
+ 0x00,0x00,0x16,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x20,0x00,
+ 0x00,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x0f,0x29,0x21,0x00,0x11,0x29,0x21,0x00,0x9c,0x9d,0x9c,0x00,0x8d,0x8e,
+ 0x8d,0x00,0x65,0x65,0x65,0x00,0x73,0xaf,0x9d,0x00,0xf1,0xf3,0xf2,0x00,0x04,0x0e,
+ 0x0b,0x00,0x05,0x0e,0x0b,0x00,0x1b,0x3b,0x31,0x00,0x26,0x60,0x4d,0x00,0x45,0x45,
+ 0x45,0x00,0x9c,0xc8,0xb9,0x00,0x38,0x89,0x6e,0x00,0x35,0x7d,0x67,0x00,0x7d,0x7d,
+ 0x7d,0x00,0x6f,0x27,0x80,0x00,0x3d,0x28,0x3d,0x00,0x0c,0x10,0x0f,0x00,0x04,0x05,
+ 0x05,0x00,0x5f,0x64,0x62,0x00,0x20,0x50,0x42,0x00,0x85,0xca,0xb6,0x00,0x61,0x22,
+ 0x98,0x00,0x76,0xb4,0xa2,0x00,0x69,0x6a,0x6a,0x00,0x02,0x03,0x03,0x00,0xaa,0xda,
+ 0xca,0x00,0x25,0x5c,0x4a,0x00,0xfc,0xfc,0xfc,0x00,0x87,0xae,0xa2,0x00,0xaa,0xcc,
+ 0xc0,0x00,0x01,0x01,0x01,0x00,0x6a,0xa0,0x91,0x00,0x31,0x75,0x5f,0x00,0x44,0xa5,
+ 0x85,0x00,0xe6,0xe5,0xec,0x00,0x31,0x7a,0x62,0x00,0x0b,0x1d,0x16,0x00,0xc2,0xcb,
+ 0xdc,0x00,0x2e,0x6c,0x58,0x00,0x22,0x53,0x44,0x00,0xa5,0xd4,0xc4,0x00,0x3e,0x42,
+ 0x41,0x00,0x68,0x85,0x7b,0x00,0x31,0x5a,0x51,0x00,0x55,0x4e,0xd5,0x00,0x8b,0x8b,
+ 0x8a,0x00,0x02,0x06,0x05,0x00,0x04,0x06,0x05,0x00,0x48,0x62,0x5b,0x00,0x0c,0x1d,
+ 0x17,0x00,0x01,0x04,0x03,0x00,0x03,0x04,0x03,0x00,0x2f,0x3d,0x38,0x00,0x65,0x81,
+ 0x77,0x00,0xef,0xf1,0xf5,0x00,0x57,0x25,0x51,0x00,0xc1,0xbd,0xc3,0x00,0x34,0x81,
+ 0x69,0x00,0x39,0x5d,0x52,0x00,0xff,0xff,0xff,0x00,0x2f,0x31,0x31,0x00,0x79,0x7d,
+ 0xd5,0x00,0x1b,0x46,0x39,0x00,0x4d,0x46,0xdd,0x00,0x13,0x13,0x13,0x00,0x5a,0x40,
+ 0x71,0x00,0xb4,0xb4,0xb4,0x00,0x71,0x74,0x73,0x00,0x4c,0x59,0x55,0x00,0x02,0x02,
+ 0x02,0x00,0xec,0xec,0xec,0x00,0x6f,0x72,0x71,0x00,0x67,0x67,0x67,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x4a,0x26,0x26,0x15,0x3e,0x3e,0x3e,0x3e,0x1e,0x28,0x39,0x3e,0x3f,
+ 0x3e,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x20,0x2f,0x11,0x18,0x25,0x3e,0x3e,0x3e,
+ 0x00,0x24,0x08,0x00,0x05,0x4b,0x21,0x42,0x3a,0x0f,0x40,0x3e,0x3e,0x3e,0x3e,0x17,
+ 0x2e,0x00,0x0c,0x45,0x00,0x00,0x44,0x12,0x24,0x09,0x3c,0x3e,0x3e,0x3e,0x3c,0x3c,
+ 0x00,0x3c,0x00,0x00,0x0d,0x2b,0x24,0x24,0x00,0x00,0x3c,0x46,0x3e,0x3e,0x3c,0x3c,
+ 0x30,0x43,0x24,0x31,0x1c,0x1c,0x0e,0x00,0x48,0x3b,0x3c,0x3c,0x3e,0x3e,0x3c,0x06,
+ 0x3e,0x00,0x23,0x1c,0x1c,0x1c,0x1c,0x00,0x36,0x3e,0x16,0x3c,0x3e,0x3e,0x3c,0x22,
+ 0x3e,0x00,0x33,0x1c,0x37,0x1f,0x1c,0x3d,0x14,0x3e,0x41,0x3c,0x3e,0x3e,0x3c,0x3c,
+ 0x49,0x00,0x00,0x32,0x1c,0x1c,0x2a,0x24,0x00,0x3e,0x3c,0x3c,0x3e,0x3e,0x47,0x3c,
+ 0x00,0x00,0x27,0x24,0x29,0x13,0x00,0x02,0x24,0x00,0x3c,0x0a,0x3e,0x3e,0x3e,0x3c,
+ 0x19,0x34,0x24,0x21,0x48,0x1b,0x00,0x00,0x01,0x0b,0x3c,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x1d,0x3c,0x00,0x1a,0x3e,0x3e,0x10,0x00,0x3c,0x35,0x3e,0x3e,0x3e,0x3e,0x3e,0x2c,
+ 0x3e,0x3c,0x3c,0x3c,0x2d,0x38,0x3c,0x3c,0x3c,0x07,0x3f,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x03,0x3c,0x3c,0x3c,0x3c,0x04,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,
+ 0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
+ ap_set_content_type(r, "image/x-icon");
+ for(i=0; i < sizeof(ico); i++) {
+ ap_rputc(ico[i], r);
+ }
+ return OK;
+}
+
+static int qos_console_dump(request_rec * r, const char *event) {
+ qos_srv_config *sconf = sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
+ &qos_module);
+ if(sconf && sconf->has_qos_cc) {
+ int i = 0;
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ /* table requires heap (100'000 ~ 4MB) but we avoid io with drawn lock */
+ apr_table_t *iptable = apr_table_make(r->pool, u->qos_cc->max);
+ apr_table_entry_t *entry;
+ apr_time_t now = apr_time_sec(r->request_time);
+ ap_set_content_type(r, "text/plain");
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT35 */
+ clientEntry = u->qos_cc->ipd;
+ for(i = 0; i < u->qos_cc->max; i++) {
+ if((clientEntry[i]->ip6[0] != 0) ||
+ (clientEntry[i]->ip6[1] != 0)) {
+ char *k;
+ int limit = 0;
+ time_t limitTime = 0;
+ if(u->qos_cc->limitTable) {
+ int limitTableIndex;
+ qos_s_entry_limit_conf_t *eventLimitConf = qos_getQSLimitEvent(u, event, &limitTableIndex);
+ if(eventLimitConf) {
+ limit = clientEntry[i]->limit[limitTableIndex].limit;
+ limitTime = (eventLimitConf->limitTime >= (time(NULL) - clientEntry[i]->limit[limitTableIndex].limitTime)) ?
+ (eventLimitConf->limitTime - (time(NULL) - clientEntry[i]->limit[limitTableIndex].limitTime)) : 0;
+ }
+ }
+ k = apr_psprintf(r->pool,
+ "%010d %s vip=%s lowprio=%s block=%hu/%ld limit=%d/%ld %ld",
+ i,
+ qos_ip_long2str(r->pool, clientEntry[i]->ip6),
+ clientEntry[i]->vip ? "yes" : "no",
+ (clientEntry[i]->lowrate + QOS_LOW_TIMEOUT) > now ? "yes" : "no",
+ clientEntry[i]->block,
+ (sconf->qos_cc_blockTime >= (time(NULL) - clientEntry[i]->blockTime)) ?
+ (sconf->qos_cc_blockTime - (time(NULL) - clientEntry[i]->blockTime)) : 0,
+ limit,
+ limitTime,
+ clientEntry[i]->time);
+ apr_table_addn(iptable, k, NULL);
+ }
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT35 */
+ entry = (apr_table_entry_t *)apr_table_elts(iptable)->elts;
+ for(i = 0; i < apr_table_elts(iptable)->nelts; ++i) {
+ ap_rprintf(r, "%s\n", entry[i].key);
+ }
+ return OK;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable, "
+ "qos client control has not been activated, id=%s",
+ qos_unique_id(r, "070"));
+ return HTTP_NOT_ACCEPTABLE;
+}
+
+#ifdef QS_INTERNAL_TEST
+static int qos_handler_headerfilter(request_rec * r) {
+ int i;
+ apr_table_entry_t *entry;
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ if(strcmp(r->handler, "qos-headerfilter") != 0) {
+ return DECLINED;
+ }
+ ap_set_content_type(r, "text/plain");
+
+ ap_rprintf(r, "\nQS_RequestHeaderFilter rules:\n\n");
+ entry = (apr_table_entry_t *)apr_table_elts(sconf->hfilter_table)->elts;
+ for(i = 0; i < apr_table_elts(sconf->hfilter_table)->nelts; i++) {
+ qos_fhlt_r_t *he = (qos_fhlt_r_t *)entry[i].val;
+ ap_rprintf(r, " name=%s, action=%s, size=%d, pattern=%s\n",
+ entry[i].key,
+ he->action == QS_FLT_ACTION_DROP ? "drop" : "deny",
+ he->size, he->text);
+ }
+ ap_rprintf(r, "\nQS_ResponseHeaderFilter rules:\n\n");
+ entry = (apr_table_entry_t *)apr_table_elts(sconf->reshfilter_table)->elts;
+ for(i = 0; i < apr_table_elts(sconf->reshfilter_table)->nelts; i++) {
+ qos_fhlt_r_t *he = (qos_fhlt_r_t *)entry[i].val;
+ ap_rprintf(r, " name=%s, action=%s, size=%d, pattern=%s\n",
+ entry[i].key,
+ he->action == QS_FLT_ACTION_DROP ? "drop" : "deny",
+ he->size, he->text);
+ }
+
+ ap_rprintf(r, "\nmod_qos %s\n", g_revision);
+
+ return OK;
+}
+
+static int qos_handler_man1(request_rec * r) {
+ module *modp = NULL;
+ if(strcmp(r->handler, "qos-man1") != 0) {
+ return DECLINED;
+ }
+ ap_set_content_type(r, "text/plain");
+ for(modp = ap_top_module; modp; modp = modp->next) {
+ if(strcmp(modp->name, "mod_qos.c") == 0) {
+ const command_rec *cmd = modp->cmds;
+ char time_string[64];
+ time_t tm = time(NULL);
+ struct tm *ptr = localtime(&tm);
+ strftime(time_string, sizeof(time_string), "%B %Y", ptr);
+ ap_rprintf(r, ".TH MOD_QOS 1 \"%s\" \"mod_qos Apache Module\" \"mod_qos\"\n", time_string);
+ ap_rprintf(r, ".SH NAME\n");
+ ap_rprintf(r, "mod_qos - quality of service module for the Apache Web server\n");
+ ap_rprintf(r, ".SH DESCRIPTION\n");
+ ap_rprintf(r, "mod_qos is a quality of service module for the Apache web server implementing control mechanisms that can provide different levels of priority to different HTTP requests.\n");
+ ap_rprintf(r, ".SH OPTIONS\n");
+ while(cmd) {
+ if(cmd->name) {
+ if(cmd->errmsg && cmd->errmsg[0] &&
+ ((strstr(cmd->errmsg, "QS_") != NULL) || (strstr(cmd->errmsg, "QSLog") != NULL))) {
+ ap_rprintf(r, ".TP\n");
+ ap_rprintf(r, "%s\n", cmd->errmsg);
+ }
+ cmd++;
+ } else {
+ break;
+ }
+ }
+ ap_rprintf(r, ".SH AUTHOR\n");
+ ap_rprintf(r, "Pascal Buchbinder, http://mod-qos.sourceforge.net/\n");
+ }
+ }
+ return OK;
+}
+#endif
+
+static int qos_handler_console(request_rec * r) {
+ apr_table_t *qt;
+ const char *ip;
+ const char *cmd;
+ const char *event;
+ apr_uint64_t addr[2];
+ qos_srv_config *sconf;
+ int status = HTTP_NOT_ACCEPTABLE;;
+ if (strcmp(r->handler, "qos-console") != 0) {
+ return DECLINED;
+ }
+ sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ if(sconf->disable_handler == 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(072)"handler has been disabled for this host, id=%s",
+ qos_unique_id(r, "072"));
+ return DECLINED;
+ }
+ apr_table_add(r->err_headers_out, "Cache-Control", "no-cache");
+ qt = qos_get_query_table(r);
+ ip = apr_table_get(qt, "address");
+ cmd = apr_table_get(qt, "action");
+ event = apr_table_get(qt, "event");
+ if(event == NULL) {
+ event = apr_pstrdup(r->pool, QS_LIMIT_DEFAULT);
+ }
+ if(!cmd || !ip) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable,"
+ " missing request query (action/address), id=%s",
+ qos_unique_id(r, "070"));
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ if(ip) {
+ int escerr = 0;
+ char *ta = apr_pstrdup(r->pool, ip);
+ qos_unescaping(ta, QOS_DEC_MODE_FLAGS_URL, &escerr);
+ ip = ta;
+ }
+ if(!sconf->has_qos_cc) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable,"
+ " client data store has not been enabled, id=%s",
+ qos_unique_id(r, "070"));
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ if((strcasecmp(cmd, "search") == 0) && (strcmp(ip, "*") == 0)) {
+ return qos_console_dump(r, event);
+ }
+ if(qos_ip_str2long(ip, addr) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable,"
+ " invalid ip/wrong format, id=%s",
+ qos_unique_id(r, "070"));
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ if(sconf->has_qos_cc) {
+ char *msg = "not available";
+ qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
+ qos_s_entry_t **clientEntry = NULL;
+ qos_s_entry_t searchE;
+ int limitTableIndex = 0;
+ qos_s_entry_limit_conf_t *eventLimitConf = NULL;
+ int limit = 0;
+ time_t limitTime = 0;
+ apr_time_t now = apr_time_sec(r->request_time);
+ apr_global_mutex_lock(u->qos_cc->lock); /* @CRT34 */
+ searchE.ip6[0] = addr[0];
+ searchE.ip6[1] = addr[1];
+ clientEntry = qos_cc_get0(u->qos_cc, &searchE, apr_time_sec(r->request_time));
+ if(!clientEntry) {
+ if(strcasecmp(cmd, "search") != 0) {
+ clientEntry = qos_cc_set(u->qos_cc, &searchE, time(NULL));
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
+ QOS_LOG_PFX(071)"console, add new client ip entry '%s', is=%s",
+ ip, qos_unique_id(r, "071"));
+ }
+ }
+ status = OK;
+ if(u->qos_cc->limitTable) {
+ eventLimitConf = qos_getQSLimitEvent(u, event, &limitTableIndex);
+ }
+ if(strcasecmp(cmd, "setvip") == 0) {
+ (*clientEntry)->vip = 1;
+ } else if(strcasecmp(cmd, "unsetvip") == 0) {
+ (*clientEntry)->vip = 0;
+ } else if(strcasecmp(cmd, "setlowprio") == 0) {
+ (*clientEntry)->lowrate = time(NULL);
+ (*clientEntry)->lowratestatus = 0xff;
+ (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_OK;
+ } else if(strcasecmp(cmd, "unsetlowprio") == 0) {
+ (*clientEntry)->lowrate = 0;
+ if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
+ (*clientEntry)->lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK;
+ } else {
+ (*clientEntry)->lowratestatus = 0;
+ }
+ } else if(strcasecmp(cmd, "unblock") == 0) {
+ (*clientEntry)->blockTime = 0;
+ (*clientEntry)->block = 0;
+ } else if(strcasecmp(cmd, "block") == 0) {
+ (*clientEntry)->blockTime = time(NULL);
+ (*clientEntry)->block = sconf->qos_cc_block + 1000;
+ } else if(strcasecmp(cmd, "unlimit") == 0) {
+ if(eventLimitConf) {
+ (*clientEntry)->limit[limitTableIndex].limitTime = 0;
+ (*clientEntry)->limit[limitTableIndex].limit = 0;
+ }
+ } else if(strcasecmp(cmd, "limit") == 0) {
+ if(eventLimitConf) {
+ (*clientEntry)->limit[limitTableIndex].limitTime = time(NULL);
+ (*clientEntry)->limit[limitTableIndex].limit = eventLimitConf->limit + 1000;
+ }
+ } else if(strcasecmp(cmd, "inclimit") == 0) {
+ if(eventLimitConf) {
+ if(((*clientEntry)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) < now) {
+ // expired
+ (*clientEntry)->limit[limitTableIndex].limit = 0;
+ (*clientEntry)->limit[limitTableIndex].limitTime = 0;
+ }
+ // increment limit event
+ if((*clientEntry)->limit[limitTableIndex].limit < USHRT_MAX) {
+ (*clientEntry)->limit[limitTableIndex].limit++;
+ }
+ if((*clientEntry)->limit[limitTableIndex].limit == 1) {
+ // first, start timer
+ (*clientEntry)->limit[limitTableIndex].limitTime = now;
+ }
+ }
+ } else if(strcasecmp(cmd, "search") == 0) {
+ /* nothing to do here */
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable, unknown action '%s', id=%s",
+ cmd, qos_unique_id(r, "070"));
+ status = HTTP_NOT_ACCEPTABLE;
+ }
+ if(clientEntry) {
+ if(eventLimitConf) {
+ limit = (*clientEntry)->limit[limitTableIndex].limit;
+ limitTime = (eventLimitConf->limitTime >= (time(NULL) - (*clientEntry)->limit[limitTableIndex].limitTime)) ?
+ (eventLimitConf->limitTime - (time(NULL) - (*clientEntry)->limit[limitTableIndex].limitTime)) : 0;
+ }
+ msg = apr_psprintf(r->pool, "%s vip=%s lowprio=%s block=%hu/%ld limit=%d/%ld", ip,
+ (*clientEntry)->vip ? "yes" : "no",
+ ((*clientEntry)->lowrate + QOS_LOW_TIMEOUT) > now ? "yes" : "no",
+ (*clientEntry)->block,
+ (sconf->qos_cc_blockTime >= (time(NULL) - (*clientEntry)->blockTime)) ?
+ (sconf->qos_cc_blockTime - (time(NULL) - (*clientEntry)->blockTime)) : 0,
+ limit,
+ limitTime);
+ }
+ apr_global_mutex_unlock(u->qos_cc->lock); /* @CRT34 */
+ if(status == OK) {
+ ap_set_content_type(r, "text/plain");
+ ap_rprintf(r, "%s\n", msg);
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
+ QOS_LOG_PFX(071)"console, action '%s' applied to client ip entry '%s', id=%s",
+ cmd, ip, qos_unique_id(r, "071"));
+ }
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(070)"console, not acceptable,"
+ " qos client control has not been activated, id=%s",
+ qos_unique_id(r, "070"));
+ status = HTTP_NOT_ACCEPTABLE;
+ }
+ return status;
+}
+
+/**
+ * viewer which may be used as an alternative to mod_status
+ */
+static int qos_handler_view(request_rec * r) {
+ qos_srv_config *sconf;
+ apr_table_t *qt;
+ if (strcmp(r->handler, "qos-viewer") != 0) {
+ return DECLINED;
+ }
+ sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config, &qos_module);
+ if(sconf->disable_handler == 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ QOS_LOG_PFX(072)"handler has been disabled for this host, id=%s",
+ qos_unique_id(r, "072"));
+ return DECLINED;
+ }
+ if(strstr(r->parsed_uri.path, "favicon.ico") != NULL) {
+ apr_table_add(r->err_headers_out, "Cache-Control", "public, max-age=2592000");
+ return qos_favicon(r);
+ }
+ apr_table_add(r->err_headers_out, "Cache-Control", "no-cache");
+ qt = qos_get_query_table(r);
+ if(qt && (apr_table_get(qt, "refresh") != NULL)) {
+ apr_table_add(r->err_headers_out, "Refresh", "10");
+ }
+ if(qt && (apr_table_get(qt, "auto") != NULL)) {
+ ap_set_content_type(r, "text/plain");
+ qos_ext_status_short(r, qt);
+ return OK;
+ }
+ ap_set_content_type(r, "text/html");
+ if(!r->header_only) {
+ int hasSlash = 1;
+ if(strlen(r->parsed_uri.path) > 0) {
+ if(r->parsed_uri.path[strlen(r->parsed_uri.path)-1] != '/') {
+ hasSlash = 0;
+ }
+ }
+ ap_rputs("<html><head><title>mod_qos</title>\n", r);
+ ap_rprintf(r,"<link rel=\"shortcut icon\" href=\"%s%sfavicon.ico\"/>\n",
+ ap_escape_html(r->pool, r->parsed_uri.path),
+ hasSlash ? "" : "/");
+ ap_rputs("<meta http-equiv=\"content-type\" content=\"text/html; charset=ISO-8859-1\">\n", r);
+ ap_rputs("<meta name=\"author\" content=\"Pascal Buchbinder\">\n", r);
+ ap_rputs("<meta http-equiv=\"Pragma\" content=\"no-cache\">\n", r);
+ ap_rputs("<style TYPE=\"text/css\">\n", r);
+ ap_rputs("<!--", r);
+ ap_rputs(" body {\n\
+ background-color: rgb(248,250,246);\n\
+ color: black;\n\
+ font-family: arial, helvetica, verdana, sans-serif;\n\
+ }\n\
+ .btable{\n\
+ background-color: white;\n\
+ border: 1px solid; padding: 0px;\n\
+ margin: 6px; width: 920px;\n\
+ font-weight: normal;\n\
+ border-collapse: collapse;\n\
+ }\n\
+ .rowts {\n\
+ background-color: rgb(150,165,158);\n\
+ vertical-align: top;\n\
+ border: 1px solid;\n\
+ border-color: black;\n\
+ font-weight: normal;\n\
+ padding: 0px;\n\
+ margin: 0px;\n\
+ }\n\
+ .rowt {\n\
+ background-color: rgb(210,220,215);\n\
+ vertical-align: top;\n\
+ border: 1px solid;\n\
+ border-color: black;\n\
+ font-weight: normal;\n\
+ padding: 0px;\n\
+ margin: 0px;\n\
+ }\n\
+ .rows {\n\
+ background-color: rgb(228,235,230);\n\
+ vertical-align: top;\n\
+ border: 1px solid;\n\
+ border-color: black;\n\
+ font-weight: normal;\n\
+ padding: 0px;\n\
+ margin: 0px;\n\
+ }\n\
+ .row {\n\
+ background-color: white;\n\
+ vertical-align: top;\n\
+ border: 1px solid;\n\
+ border-color: black;\n\
+ font-weight: normal;\n\
+ padding: 0px;\n\
+ margin: 0px;\n\
+ }\n\
+ .rowe {\n\
+ background-color: rgb(186,200,190);\n\
+ vertical-align: top;\n\
+ border: 1px solid;\n\
+ border-color: black;\n\
+ font-weight: normal;\n\
+ padding: 0px;\n\
+ margin: 0px;\n\
+ }\n\
+ .small {\n\
+ font-size: 0.75em;\n\
+ font-family: courier;\n\
+ }\n\
+ .prog-border {\n\
+ height: 10px;\n\
+ width: 150px;\n\
+ background: #eee;\n\
+ border: 1px solid #000;\n\
+ padding: 2px;\n\
+ font-family: arial, helvetica, verdana, sans-serif; font-size: 10px; color: #000;\n\
+ }\n\
+ .prog-bar {\n\
+ height: 10px;\n\
+ padding: 0;\n\
+ background: #339900;\n\
+ font-family: arial, helvetica, verdana, sans-serif; font-size: 10px; color: #000;\n\
+ }\n\
+ .prog-bar-limit {\n\
+ height: 10px;\n\
+ padding: 0;\n\
+ background: #993300;\n\
+ font-family: arial, helvetica, verdana, sans-serif; font-size: 10px; color: #000;\n\
+ }\n\
+ form { display: inline; }\n", r);
+ ap_rputs("-->\n", r);
+ ap_rputs("</style>\n", r);
+ ap_rputs("</head><body>\n", r);
+ qos_ext_status_hook(r, 0);
+ {
+ apr_time_t nowtime = apr_time_now();
+ ap_rvputs(r, "<div class=\"small\">",
+ ap_ht_time(r->pool, nowtime, QS_ERR_TIME_FORMAT, 0), NULL);
+ ap_rprintf(r, ", mod_qos %s\n", ap_escape_html(r->pool, qos_revision(r->pool)));
+ }
+ ap_rputs("</body></html>", r);
+ }
+ return OK;
+}
+
+static int qos_handler(request_rec * r) {
+ int status = qos_handler_view(r);
+ if(status != DECLINED) {
+ return status;
+ }
+ status = qos_handler_console(r);
+ if(status != DECLINED) {
+ return status;
+ }
+#ifdef QS_INTERNAL_TEST
+ status = qos_handler_man1(r);
+ if(status != DECLINED) {
+ return status;
+ }
+ status = qos_handler_headerfilter(r);
+ if(status != DECLINED) {
+ return status;
+ }
+#endif
+ return DECLINED;
+}
+
+/**
+ * insert response filter
+ */
+static void qos_insert_filter(request_rec *r) {
+ ap_add_output_filter("qos-out-filter", NULL, r, r->connection);
+}
+static void qos_insert_err_filter(request_rec *r) {
+ ap_add_output_filter("qos-out-err-filter", NULL, r, r->connection);
+}
+
+/************************************************************************
+ * directiv handlers
+ ***********************************************************************/
+static void qos_table_merge(apr_table_t *o, apr_table_t *b) {
+ int i;
+ apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(b)->elts;
+ for(i = 0; i < apr_table_elts(b)->nelts; ++i) {
+ if(apr_table_get(o, entry[i].key) == NULL) {
+ // copy the pointer only!!!
+ apr_table_setn(o, entry[i].key, entry[i].val);
+ }
+ }
+}
+
+static void *qos_dir_config_create(apr_pool_t *p, char *d) {
+ qos_dir_config *dconf = apr_pcalloc(p, sizeof(qos_dir_config));
+ dconf->path = d;
+ dconf->rfilter_table = apr_table_make(p, 1);
+ dconf->inheritoff = 0;
+ dconf->headerfilter = QS_HEADERFILTER_OFF_DEFAULT;
+ dconf->resheaderfilter = QS_HEADERFILTER_OFF_DEFAULT;
+ dconf->bodyfilter_p = -1;
+ dconf->bodyfilter_d = -1;
+ dconf->dec_mode = QOS_DEC_MODE_FLAGS_URL;
+ dconf->maxpost = -1;
+ dconf->urldecoding = QS_OFF_DEFAULT;
+ dconf->response_pattern = NULL;
+ dconf->response_pattern_var = NULL;
+ dconf->redirectif = apr_array_make(p, 20, sizeof(qos_redirectif_entry_t));
+ dconf->disable_reqrate_events = apr_table_make(p, 1);
+ dconf->setenvstatus_t = apr_table_make(p, 5);
+ dconf->setenvif_t = apr_array_make(p, 20, sizeof(qos_setenvif_t));
+ dconf->setenvifquery_t = apr_table_make(p, 1);
+ dconf->setenvcmp = apr_array_make(p, 2, sizeof(qos_cmp_entry_t));
+ return dconf;
+}
+
+/**
+ * merges dir config, inheritoff disables merge of rfilter_table.
+ */
+static void *qos_dir_config_merge(apr_pool_t *p, void *basev, void *addv) {
+ qos_dir_config *b = (qos_dir_config *)basev;
+ qos_dir_config *o = (qos_dir_config *)addv;
+ qos_dir_config *dconf = apr_pcalloc(p, sizeof(qos_dir_config));
+ dconf->path = o->path;
+ if(o->headerfilter != QS_HEADERFILTER_OFF_DEFAULT) {
+ dconf->headerfilter = o->headerfilter;
+ } else {
+ dconf->headerfilter = b->headerfilter;
+ }
+ if(o->resheaderfilter != QS_HEADERFILTER_OFF_DEFAULT) {
+ dconf->resheaderfilter = o->resheaderfilter;
+ } else {
+ dconf->resheaderfilter = b->resheaderfilter;
+ }
+ if(o->bodyfilter_p != -1) {
+ dconf->bodyfilter_p = o->bodyfilter_p;
+ } else {
+ dconf->bodyfilter_p = b->bodyfilter_p;
+ }
+ if(o->bodyfilter_d != -1) {
+ dconf->bodyfilter_d = o->bodyfilter_d;
+ } else {
+ dconf->bodyfilter_d = b->bodyfilter_d;
+ }
+ if((o->dec_mode != QOS_DEC_MODE_FLAGS_URL) ||
+ (o->inheritoff)) {
+ dconf->dec_mode = o->dec_mode;
+ } else {
+ dconf->dec_mode = b->dec_mode;
+ }
+ if(o->inheritoff) {
+ dconf->rfilter_table = o->rfilter_table;
+ } else {
+ dconf->rfilter_table = qos_table_merge_create(p, b->rfilter_table, o->rfilter_table);
+ }
+ if(o->maxpost != -1) {
+ dconf->maxpost = o->maxpost;
+ } else {
+ dconf->maxpost = b->maxpost;
+ }
+ if(o->urldecoding == QS_OFF_DEFAULT) {
+ dconf->urldecoding = b->urldecoding;
+ } else {
+ dconf->urldecoding = o->urldecoding;
+ }
+ if(o->response_pattern) {
+ dconf->response_pattern = o->response_pattern;
+ dconf->response_pattern_len = o->response_pattern_len;
+ dconf->response_pattern_var = o->response_pattern_var;
+ } else {
+ dconf->response_pattern = b->response_pattern;
+ dconf->response_pattern_len = b->response_pattern_len;
+ dconf->response_pattern_var = b->response_pattern_var;
+ }
+ dconf->disable_reqrate_events = qos_table_merge_create(p, b->disable_reqrate_events,
+ o->disable_reqrate_events);
+ dconf->redirectif = apr_array_append(p, b->redirectif, o->redirectif);
+
+ dconf->setenvstatus_t = apr_table_copy(p, b->setenvstatus_t);
+ qos_table_merge(dconf->setenvstatus_t, o->setenvstatus_t);
+
+ dconf->setenvif_t = apr_array_append(p, b->setenvif_t, o->setenvif_t);
+
+ dconf->setenvifquery_t = apr_table_copy(p, b->setenvifquery_t);
+ qos_table_merge(dconf->setenvifquery_t, o->setenvifquery_t);
+
+ dconf->setenvcmp = apr_array_append(p, b->setenvcmp, o->setenvcmp);
+
+ return dconf;
+}
+
+static void *qos_srv_config_create(apr_pool_t *p, server_rec *s) {
+ qos_srv_config *sconf;
+ apr_pool_t *act_pool;
+ apr_pool_create(&act_pool, NULL);
+ sconf =(qos_srv_config *)apr_pcalloc(p, sizeof(qos_srv_config));
+ sconf->pool = p;
+ sconf->location_t = apr_table_make(sconf->pool, 2);
+ sconf->setenvif_t = apr_array_make(sconf->pool, 20, sizeof(qos_setenvif_t));
+ sconf->setenv_t = apr_table_make(sconf->pool, 1);
+ sconf->setreqheader_t = apr_table_make(sconf->pool, 5);
+ sconf->setreqheaderlate_t = apr_table_make(sconf->pool, 5);
+ sconf->unsetreqheader_t = apr_table_make(sconf->pool, 5);
+ sconf->unsetresheader_t = apr_table_make(sconf->pool, 5);
+ sconf->setenvifquery_t = apr_table_make(sconf->pool, 1);
+ sconf->setenvifparp_t = apr_table_make(sconf->pool, 1);
+ sconf->setenvifparpbody_t = apr_table_make(sconf->pool, 1);
+ sconf->setenvstatus_t = apr_table_make(sconf->pool, 5);
+ sconf->setenvresheader_t = apr_table_make(sconf->pool, 1);
+ sconf->setenvresheadermatch_t = apr_table_make(sconf->pool, 1);
+ sconf->setenvres_t = apr_table_make(sconf->pool, 1);
+ sconf->headerfilter = QS_HEADERFILTER_OFF_DEFAULT;
+ sconf->resheaderfilter = QS_HEADERFILTER_OFF_DEFAULT;
+ sconf->redirectif = apr_array_make(p, 20, sizeof(qos_redirectif_entry_t));
+ sconf->error_page = NULL;
+ sconf->req_rate = -1;
+ sconf->req_rate_start = 0;
+ sconf->min_rate = -1;
+ sconf->min_rate_max = -1;
+ sconf->min_rate_off = 0;
+ sconf->req_ignore_vip_rate = -1;
+ sconf->max_clients = 1024;
+ sconf->max_clients_conf = -1;
+ sconf->has_event_filter = 0;
+ sconf->has_event_limit = 0;
+ sconf->event_limit_a = apr_array_make(p, 2, sizeof(qos_event_limit_entry_t));
+ sconf->mfile = NULL;
+ sconf->act = (qs_actable_t *)apr_pcalloc(act_pool, sizeof(qs_actable_t));
+ sconf->act->pool = act_pool;
+ sconf->act->ppool = s->process->pool;
+ sconf->act->child_init = 0;
+ sconf->act->timeout = apr_time_sec(s->timeout);
+ sconf->act->has_events = 0;
+ sconf->act->lock_file = NULL;
+ sconf->act->lock = NULL;
+ sconf->is_virtual = s->is_virtual;
+ sconf->cookie_name = apr_pstrdup(sconf->pool, QOS_COOKIE_NAME);
+ sconf->cookie_path = apr_pstrdup(sconf->pool, "/");
+ sconf->user_tracking_cookie = NULL;
+ sconf->user_tracking_cookie_force = NULL;
+ sconf->user_tracking_cookie_session = -1;
+ sconf->user_tracking_cookie_jsredirect = -1;
+ sconf->user_tracking_cookie_domain = NULL;
+ sconf->max_age = atoi(QOS_MAX_AGE);
+ sconf->header_name = NULL;
+ sconf->header_name_drop = 0;
+ sconf->header_name_regex = NULL;
+ sconf->ip_header_name = NULL;
+ sconf->ip_header_name_drop = 0;
+ sconf->ip_header_name_regex = NULL;
+ sconf->vip_user = 0;
+ sconf->vip_ip_user = 0;
+
+ sconf->has_conn_counter = 0;
+ sconf->max_conn = -1;
+ sconf->max_conn_close = -1;
+ sconf->max_conn_per_ip = -1;
+ sconf->max_conn_per_ip_connections = -1;
+ sconf->max_conn_per_ip_ignore_vip = -1;
+
+ sconf->serialize = -1;
+ sconf->exclude_ip = apr_table_make(sconf->pool, 2);
+ sconf->hfilter_table = apr_table_make(p, 5);
+ sconf->reshfilter_table = apr_table_make(p, 5);
+ sconf->disable_reqrate_events = apr_table_make(p, 1);
+ sconf->log_only = 0;
+ sconf->log_env = -1;
+ sconf->has_qos_cc = 0;
+ sconf->cc_exclude_ip = apr_table_make(sconf->pool, 2);
+ sconf->qos_cc_size = 50000;
+ sconf->qos_cc_prefer = 0;
+ sconf->qos_cc_prefer_limit = 0;
+ sconf->qos_cc_event = 0;
+ sconf->qos_cc_event_req = -1;
+ sconf->qos_cc_block = 0;
+ sconf->qos_cc_serialize = 0;
+ sconf->serializeTMO = 6000; // 6000 * 50ms = 5 minutes
+ sconf->cc_tolerance = atoi(QOS_CC_BEHAVIOR_TOLERANCE_STR);
+ sconf->qs_req_rate_tm = QS_REQ_RATE_TM;
+ sconf->geodb = NULL;
+ sconf->geo_limit = -1;
+ sconf->geo_priv = apr_table_make(p, 20);
+ sconf->geo_excludeUnknown = -1;
+ sconf->qslog_p = NULL;
+ sconf->qsstatus = 0;
+ sconf->qsevents = 0;
+ sconf->qslog_str = NULL;
+ sconf->ip_type = QS_IP_V6_DEFAULT;
+ sconf->qos_cc_blockTime = 600;
+ sconf->qos_cc_limitTable = apr_table_make(p, 5);
+ sconf->qos_cc_forwardedfor = NULL;
+ sconf->disable_handler = -1;
+ sconf->maxpost = -1;
+ sconf->milestones = NULL;
+ sconf->milestoneTimeout = QOS_MILESTONE_TIMEOUT;
+ sconf->static_on = -1;
+ sconf->static_html = 0;
+ sconf->static_cssjs = 0;
+ sconf->static_img = 0;
+ sconf->static_other = 0;
+ sconf->static_notmodified = 0;
+ if(!s->is_virtual) {
+ char *msg = qos_load_headerfilter(p, sconf->hfilter_table, qs_header_rules);
+ if(msg) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
+ QOS_LOG_PFX(006)"could not compile request header filter rules: %s", msg);
+ exit(1);
+ }
+ msg = qos_load_headerfilter(p, sconf->reshfilter_table, qs_res_header_rules);
+ if(msg) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
+ QOS_LOG_PFX(006)"could not compile response header filter rules: %s", msg);
+ exit(1);
+ }
+ }
+
+ {
+ int len = EVP_MAX_KEY_LENGTH;
+ unsigned char *rand = apr_pcalloc(p, len);
+#if APR_HAS_RANDOM
+ if(apr_generate_random_bytes(rand, len) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ QOS_LOG_PFX(083)"Can't generate random data.");
+ }
+#else
+ if(!RAND_bytes(rand, len)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ QOS_LOG_PFX(083)"Can't generate random data.");
+ }
+#endif
+ EVP_BytesToKey(EVP_des_ede3_cbc(), EVP_sha1(), NULL, rand, len, 1, sconf->key, NULL);
+ sconf->rawKey = rand;
+ sconf->rawKeyLen = len;
+ sconf->keyset = 0;
+ }
+#ifdef QS_INTERNAL_TEST
+ {
+ int i;
+ sconf->testip = apr_table_make(sconf->pool, m_qs_sim_ip_len);
+ sconf->enable_testip = 1;
+ for(i = 0; i < (m_qs_sim_ip_len*3/4); i++) {
+ char *qsmi = apr_psprintf(p, "%d.%d.%d.%d", rand()%255, rand()%255, rand()%255, rand()%255);
+ apr_table_add(sconf->testip, apr_psprintf(p, "%d", i), qsmi);
+ }
+ for(i = m_qs_sim_ip_len*3/4; i < m_qs_sim_ip_len; i++) {
+ char *qsmi = apr_psprintf(p, "fe%d::%d:%d:%d", rand()%100, rand()%4000, rand()%4000, rand()%400);
+ apr_table_add(sconf->testip, apr_psprintf(p, "%d", i), qsmi);
+ }
+ }
+#endif
+ return sconf;
+}
+
+/**
+ * "merges" server configuration: virtual host overwrites global settings (if
+ * any rule has been specified)
+ * but: global settings such as header filter table and connection timeouts
+ * are always used from the base server
+ */
+static void *qos_srv_config_merge(apr_pool_t *p, void *basev, void *addv) {
+ qos_srv_config *b = (qos_srv_config *)basev;
+ qos_srv_config *o = (qos_srv_config *)addv;
+ /* GLOBAL ONLY directives: */
+ o->hfilter_table = b->hfilter_table;
+ o->reshfilter_table = b->reshfilter_table;
+ o->log_only = b->log_only;
+ if(o->log_env == -1) {
+ o->log_env = b->log_env;
+ }
+ o->has_qos_cc = b->has_qos_cc;
+ o->cc_exclude_ip = b->cc_exclude_ip;
+ o->qos_cc_size = b->qos_cc_size;
+ o->qos_cc_prefer = b->qos_cc_prefer;
+ o->qos_cc_prefer_limit = b->qos_cc_prefer_limit;
+ o->qos_cc_event = b->qos_cc_event;
+ o->qos_cc_event_req = b->qos_cc_event_req;
+ o->qos_cc_block = b->qos_cc_block;
+ o->qos_cc_blockTime = b->qos_cc_blockTime;
+ o->qos_cc_limitTable = b->qos_cc_limitTable;
+ o->qos_cc_forwardedfor = b->qos_cc_forwardedfor;
+ o->qos_cc_serialize = b->qos_cc_serialize;
+ o->cc_tolerance = b->cc_tolerance;
+ o->qs_req_rate_tm = b->qs_req_rate_tm;
+ o->geodb = b->geodb;
+ o->geo_limit = b->geo_limit;
+ o->geo_priv = b->geo_priv;
+ o->geo_excludeUnknown = b->geo_excludeUnknown;
+ o->qslog_p = b->qslog_p;
+ o->qsstatus = b->qsstatus;
+ o->qsevents = b->qsevents;
+ o->qslog_str = b->qslog_str;
+ o->ip_type = b->ip_type;
+ o->req_rate = b->req_rate;
+ o->req_rate_start = b->req_rate_start;
+ o->min_rate = b->min_rate;
+ o->min_rate_max = b->min_rate_max;
+ o->req_ignore_vip_rate = b->req_ignore_vip_rate;
+ o->event_limit_a = apr_array_append(p, b->event_limit_a, o->event_limit_a);
+ /* end GLOBAL ONLY directives */
+ if(o->disable_handler == -1) {
+ o->disable_handler = b->disable_handler;
+ }
+#ifdef QS_INTERNAL_TEST
+ o->enable_testip = b->enable_testip;
+#endif
+ if(o->error_page == NULL) {
+ o->error_page = b->error_page;
+ }
+ qos_table_merge(o->location_t, b->location_t);
+ o->setenvif_t = apr_array_append(p, b->setenvif_t, o->setenvif_t);
+ qos_table_merge(o->setenv_t, b->setenv_t);
+ qos_table_merge(o->setreqheader_t, b->setreqheader_t);
+ qos_table_merge(o->setreqheaderlate_t, b->setreqheaderlate_t);
+ qos_table_merge(o->unsetreqheader_t, b->unsetreqheader_t);
+ qos_table_merge(o->unsetresheader_t, b->unsetresheader_t);
+ qos_table_merge(o->setenvifquery_t, b->setenvifquery_t);
+ qos_table_merge(o->setenvifparp_t, b->setenvifparp_t);
+ qos_table_merge(o->setenvifparpbody_t, b->setenvifparpbody_t);
+ qos_table_merge(o->setenvstatus_t, b->setenvstatus_t);
+ qos_table_merge(o->setenvresheader_t, b->setenvresheader_t);
+ qos_table_merge(o->setenvresheadermatch_t, b->setenvresheadermatch_t);
+ qos_table_merge(o->setenvres_t, b->setenvres_t);
+ qos_table_merge(o->exclude_ip, b->exclude_ip);
+ o->disable_reqrate_events = qos_table_merge_create(p, b->disable_reqrate_events,
+ o->disable_reqrate_events);
+ if(o->headerfilter == QS_HEADERFILTER_OFF_DEFAULT) {
+ o->headerfilter = b->headerfilter;
+ }
+ if(o->resheaderfilter == QS_HEADERFILTER_OFF_DEFAULT) {
+ o->resheaderfilter = b->resheaderfilter;
+ }
+ o->redirectif = apr_array_append(p, b->redirectif, o->redirectif);
+ if(o->mfile == NULL) {
+ o->mfile = b->mfile;
+ }
+ if(strcmp(o->cookie_name, QOS_COOKIE_NAME) == 0) {
+ o->cookie_name = b->cookie_name;
+ }
+ if(strcmp(o->cookie_path, "/") == 0) {
+ o->cookie_path = b->cookie_path;
+ }
+ if(o->max_age == atoi(QOS_MAX_AGE)) {
+ o->max_age = b->max_age;
+ }
+ if(o->user_tracking_cookie == NULL) {
+ o->user_tracking_cookie = b->user_tracking_cookie;
+ o->user_tracking_cookie_force = b->user_tracking_cookie_force;
+ o->user_tracking_cookie_session = b->user_tracking_cookie_session;
+ o->user_tracking_cookie_jsredirect = b->user_tracking_cookie_jsredirect;
+ o->user_tracking_cookie_domain = b->user_tracking_cookie_domain;
+ }
+ if(o->keyset == 0) {
+ memcpy(o->key, b->key, sizeof(o->key));
+ o->rawKey = b->rawKey;
+ o->rawKeyLen = b->rawKeyLen;
+ }
+ if(o->header_name == NULL) {
+ o->header_name = b->header_name;
+ o->header_name_drop = b->header_name_drop;
+ o->header_name_regex = b->header_name_regex;
+ }
+ if(o->ip_header_name == NULL) {
+ o->ip_header_name = b->ip_header_name;
+ o->ip_header_name_drop = b->ip_header_name_drop;
+ o->ip_header_name_regex = b->ip_header_name_regex;
+ }
+ if(o->vip_user == 0) {
+ o->vip_user = b->vip_user;
+ }
+ if(o->vip_ip_user == 0) {
+ o->vip_ip_user = b->vip_ip_user;
+ }
+ if(o->max_conn == -1) {
+ o->max_conn = b->max_conn;
+ }
+ if(o->max_conn_close == -1) {
+ o->max_conn_close = b->max_conn_close;
+ o->max_conn_close_percent = b->max_conn_close_percent;
+ }
+ if(o->max_conn_per_ip == -1) {
+ o->max_conn_per_ip = b->max_conn_per_ip;
+ }
+ if(o->max_conn_per_ip_ignore_vip == -1) {
+ o->max_conn_per_ip_ignore_vip = b->max_conn_per_ip_ignore_vip;
+ }
+ if(o->max_conn_per_ip_connections == -1) {
+ o->max_conn_per_ip_connections = b->max_conn_per_ip_connections;
+ }
+ if(o->serialize == -1) {
+ o->serialize = b->serialize;
+ o->serializeTMO = b->serializeTMO;
+ }
+ if(o->has_event_filter == 0) {
+ o->has_event_filter = b->has_event_filter;
+ }
+ if(o->has_event_limit == 0) {
+ o->has_event_limit = b->has_event_limit;
+ }
+ if(o->maxpost == -1) {
+ o->maxpost = b->maxpost;
+ }
+ if(o->milestones == NULL) {
+ o->milestones = b->milestones;
+ o->milestoneTimeout = b->milestoneTimeout;
+ }
+ if(o->static_on == -1) {
+ /* use base settings if not configured per vhost */
+ o->static_on = b->static_on;
+ o->static_html = b->static_html;
+ o->static_cssjs = b->static_cssjs;
+ o->static_img = b->static_img;
+ o->static_other = b->static_other;
+ o->static_notmodified = b->static_notmodified;
+ }
+ return o;
+}
+
+const char *qos_logonly_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->log_only = flag;
+ return NULL;
+}
+
+const char *qos_logenv_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->log_env = flag;
+ return NULL;
+}
+
+/**
+ * QS_MaxClients
+ */
+const char *qos_maxclients_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->max_clients_conf = atoi(arg1);
+ if(sconf->max_clients_conf <= 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_mfile_cmd(cmd_parms *cmd, void *dcfg, const char *path) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ apr_finfo_t finfo;
+ apr_status_t rc;
+ if(!path[0]) {
+ return apr_psprintf(cmd->pool, "%s: invalid path",
+ cmd->directive->directive);
+ }
+ if((rc = apr_stat(&finfo, path, APR_FINFO_TYPE, cmd->pool)) != APR_SUCCESS) {
+ char *p = apr_pstrdup(cmd->pool, path);
+ /* file? */
+ if(p[strlen(p)-1] == '/') {
+ return apr_psprintf(cmd->pool, "%s: path does not exist",
+ cmd->directive->directive);
+ } else {
+ char *e = strrchr(p, '/');
+ if(e) {
+ e[0] = '\0';
+ }
+ if(((rc = apr_stat(&finfo, p, APR_FINFO_TYPE, cmd->pool)) != APR_SUCCESS) ||
+ (finfo.filetype != APR_DIR)){
+ return apr_psprintf(cmd->pool, "%s: path does not exist",
+ cmd->directive->directive);
+ }
+ }
+ }
+ sconf->mfile = apr_pstrdup(cmd->pool, path);
+ return NULL;
+}
+
+/**
+ * command to define the concurrent request limitation for a location
+ */
+const char *qos_loc_con_cmd(cmd_parms *cmd, void *dcfg, const char *loc, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, loc);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, loc);
+ }
+ rule->limit = atoi(limit);
+ if((rule->limit < 0) || ((rule->limit == 0) && limit && (strcmp(limit, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ rule->event = NULL;
+ rule->regex = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, loc), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_LocRequestPerSecLimit: command to define the req/sec limitation for a location
+ */
+const char *qos_loc_rs_cmd(cmd_parms *cmd, void *dcfg, const char *loc, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, loc);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, loc);
+ }
+ rule->req_per_sec_limit = atol(limit);
+ if(rule->req_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ rule->event = NULL;
+ rule->regex = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, loc), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_LocKBytesPerSecLimit: command to define the kbytes/sec limitation for a location
+ */
+const char *qos_loc_bs_cmd(cmd_parms *cmd, void *dcfg, const char *loc, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, loc);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, loc);
+ }
+ rule->kbytes_per_sec_limit = atol(limit);
+ if(rule->kbytes_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ rule->event = NULL;
+ rule->regex = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, loc), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_LocRequestLimitMatch: defines the maximum of concurrent requests matching the specified
+ * request line pattern
+ */
+const char *qos_match_con_cmd(cmd_parms *cmd, void *dcfg, const char *match, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, match);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, match);
+ }
+ rule->limit = atoi(limit);
+ if((rule->limit < 0) || ((rule->limit == 0) && limit && (strcmp(limit, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ rule->regex = ap_pregcomp(cmd->pool, match, AP_REG_EXTENDED);
+ if(rule->regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regular expression (%s)",
+ cmd->directive->directive, match);
+ }
+ rule->event = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, match), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_CondLocRequestLimitMatch: defines the maximum of concurrent requests
+ * matching the specified request line pattern
+ */
+const char *qos_cond_match_con_cmd(cmd_parms *cmd, void *dcfg, const char *match,
+ const char *limit, const char *pattern) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, match);
+ rule->limit = atoi(limit);
+ if((rule->limit < 0) || ((rule->limit == 0) && limit && (strcmp(limit, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ rule->regex = ap_pregcomp(cmd->pool, match, AP_REG_EXTENDED);
+ rule->condition = ap_pregcomp(cmd->pool, pattern, AP_REG_EXTENDED);
+ if(rule->regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regular expression (%s)",
+ cmd->directive->directive, match);
+ }
+ if(rule->condition == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regular expression (%s)",
+ cmd->directive->directive, pattern);
+ }
+ rule->event = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrcat(cmd->pool, match, "##conditional##", NULL), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_LocRequestPerSecLimitMatch: defines the maximum requests/sec for
+ * the matching request line pattern
+ */
+const char *qos_match_rs_cmd(cmd_parms *cmd, void *dcfg, const char *match, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, match);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, match);
+ }
+ rule->req_per_sec_limit = atol(limit);
+ if(rule->req_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ rule->regex = ap_pregcomp(cmd->pool, match, AP_REG_EXTENDED);
+ if(rule->regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regular expression (%s)",
+ cmd->directive->directive, match);
+ }
+ rule->event = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, match), (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_LocKBytesPerSecLimitMatch: defines the maximum kbytes/sec for
+ * the matching request line pattern
+ */
+const char *qos_match_bs_cmd(cmd_parms *cmd, void *dcfg, const char *match, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_table_get(sconf->location_t, match);
+ if(rule == NULL) {
+ rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrdup(cmd->pool, match);
+ }
+ rule->kbytes_per_sec_limit = atol(limit);
+ if(rule->kbytes_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ rule->regex = ap_pregcomp(cmd->pool, match, AP_REG_EXTENDED);
+ if(rule->regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regular expression (%s)",
+ cmd->directive->directive, match);
+ }
+ rule->event = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, apr_pstrdup(cmd->pool, match), (char *)rule);
+ return NULL;
+}
+
+/**
+ * sets the default limitation of cuncurrent requests
+ */
+const char *qos_loc_con_def_cmd(cmd_parms *cmd, void *dcfg, const char *limit) {
+ return qos_loc_con_cmd(cmd, dcfg, "/", limit);
+}
+
+/**
+ * QS_EventRequestLimit: defines the number of concurrent events
+ */
+const char *qos_event_req_cmd(cmd_parms *cmd, void *dcfg, const char *event, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ char *p = strchr(event, '=');
+ rule->url = apr_pstrcat(cmd->pool, "var=(", event, ")", NULL);
+ rule->limit = atoi(limit);
+ rule->req_per_sec_limit = 0;
+ rule->req_per_sec_limit = 0;
+ if((rule->limit < 0) || ((rule->limit == 0) && limit && (strcmp(limit, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ sconf->has_event_filter = 1;
+ if(p) {
+ p++;
+ rule->regex_var = ap_pregcomp(cmd->pool, p, AP_REG_EXTENDED);
+ if(rule->regex_var == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, p);
+ }
+ rule->event = apr_pstrndup(cmd->pool, event, p - event - 1);
+ } else {
+ rule->regex_var = NULL;
+ rule->event = apr_pstrdup(cmd->pool, event);
+ }
+ rule->regex = NULL;
+ rule->condition = NULL;
+ apr_table_setn(sconf->location_t, rule->url, (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_EventPerSecLimit: defines the maximum requests/sec for the matching variable.
+ */
+const char *qos_event_rs_cmd(cmd_parms *cmd, void *dcfg, const char *event, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrcat(cmd->pool, "var=[", event, "]", NULL);
+ rule->req_per_sec_limit = atol(limit);
+ rule->kbytes_per_sec_limit = 0;
+ if(rule->req_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ sconf->has_event_limit = 1;
+ rule->event = apr_pstrdup(cmd->pool, event);
+ rule->regex = NULL;
+ rule->condition = NULL;
+ rule->limit = -1;
+ apr_table_setn(sconf->location_t, rule->url, (char *)rule);
+ return NULL;
+}
+
+/**
+ * QS_EventKBytesPerSecLimit: maximum download per event
+ */
+const char *qos_event_bps_cmd(cmd_parms *cmd, void *dcfg, const char *event, const char *limit) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qs_rule_ctx_t *rule = (qs_rule_ctx_t *)apr_pcalloc(cmd->pool, sizeof(qs_rule_ctx_t));
+ rule->url = apr_pstrcat(cmd->pool, "var={", event, "}", NULL);
+ rule->kbytes_per_sec_limit = atol(limit);
+ rule->req_per_sec_limit = 0;
+ if(rule->kbytes_per_sec_limit == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ sconf->has_event_limit = 1;
+ rule->event = apr_pstrdup(cmd->pool, event);
+ rule->regex = NULL;
+ rule->condition = NULL;
+ rule->limit = -1;
+ apr_table_setn(sconf->location_t, rule->url, (char *)rule);
+ return NULL;
+}
+
+// QS_CondEventLimitCount
+const char *qos_cond_event_limit_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[]) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_event_limit_entry_t *new = apr_array_push(sconf->event_limit_a);
+ if(argc < 4) {
+ return apr_psprintf(cmd->pool, "%s: takes 3 arguments",
+ cmd->directive->directive);
+ }
+ new->env_var = apr_pstrdup(cmd->pool, argv[0]);
+ new->eventDecStr = apr_pstrcat(cmd->pool, argv[0], QS_LIMIT_DEC, NULL);
+ new->max = atoi(argv[1]);
+ new->seconds = atoi(argv[2]);
+ new->action = QS_EVENT_ACTION_DENY;
+ if(new->max == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ if(new->seconds == 0) {
+ return apr_psprintf(cmd->pool, "%s: seconds must be numeric value >0",
+ cmd->directive->directive);
+ }
+ new->condStr = apr_pstrdup(cmd->pool, argv[3]);
+ new->preg = ap_pregcomp(cmd->pool, new->condStr, AP_REG_EXTENDED);
+ if(new->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, new->condStr);
+ }
+ return NULL;
+}
+
+// QS_EventLimitCount
+const char *qos_event_limit_cmd(cmd_parms *cmd, void *dcfg, const char *event,
+ const char *number, const char *seconds) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_event_limit_entry_t *new = apr_array_push(sconf->event_limit_a);
+ new->env_var = apr_pstrdup(cmd->pool, event);
+ new->max = atoi(number);
+ new->seconds = atoi(seconds);
+ new->action = QS_EVENT_ACTION_DENY;
+ new->condStr = NULL;
+ if(new->max == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ if(new->seconds == 0) {
+ return apr_psprintf(cmd->pool, "%s: seconds must be numeric value >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_event_setenvifstatus_cmd(cmd_parms *cmd, void *dcfg, const char *rc, const char *var) {
+ apr_table_t *setenvstatus_t;
+ if(cmd->path) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ setenvstatus_t = dconf->setenvstatus_t;
+ } else {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ setenvstatus_t = sconf->setenvstatus_t;
+ }
+
+ if(strcasecmp(rc, QS_CLOSE) == 0) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if(err != NULL) {
+ return apr_psprintf(cmd->pool, "%s: "QS_CLOSE" may only be defined globally",
+ cmd->directive->directive);
+ }
+ if(strcasecmp(var, QS_BLOCK) != 0) {
+ return apr_psprintf(cmd->pool, "%s: "QS_CLOSE" may only be defined for the event "QS_BLOCK,
+ cmd->directive->directive);
+ }
+ } else if(strcasecmp(rc, QS_MAXIP) == 0) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if(err != NULL) {
+ return apr_psprintf(cmd->pool, "%s: "QS_MAXIP" may only be defined globally",
+ cmd->directive->directive);
+ }
+ if(strcasecmp(var, QS_BLOCK) != 0) {
+ return apr_psprintf(cmd->pool, "%s: "QS_MAXIP" may only be defined for the event "QS_BLOCK,
+ cmd->directive->directive);
+ }
+ } else if(strcasecmp(rc, QS_EMPTY_CON) == 0) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if(err != NULL) {
+ return apr_psprintf(cmd->pool, "%s: "QS_EMPTY_CON" may only be defined globally",
+ cmd->directive->directive);
+ }
+ if(strcasecmp(var, QS_BLOCK) != 0) {
+ return apr_psprintf(cmd->pool, "%s: "QS_EMPTY_CON" may only be defined for the event "QS_BLOCK,
+ cmd->directive->directive);
+ }
+ } else if(strcasecmp(rc, QS_BROKEN_CON) == 0) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if(err != NULL) {
+ return apr_psprintf(cmd->pool, "%s: "QS_BROKEN_CON" may only be defined globally",
+ cmd->directive->directive);
+ }
+ if(strcasecmp(var, QS_BLOCK) != 0) {
+ return apr_psprintf(cmd->pool, "%s: "QS_BROKEN_CON" may only be defined for the event "QS_BLOCK,
+ cmd->directive->directive);
+ }
+ } else {
+ int code = atoi(rc);
+ if(code <= 0) {
+ return apr_psprintf(cmd->pool, "%s: invalid HTTP status code",
+ cmd->directive->directive);
+ }
+ }
+ apr_table_set(setenvstatus_t, rc, var);
+ return NULL;
+}
+
+/** QS_SetEnvIfResBody */
+const char *qos_event_setenvifresbody_cmd(cmd_parms *cmd, void *dcfg, const char *pattern,
+ const char *var) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ if(dconf->response_pattern) {
+ return apr_psprintf(cmd->pool, "%s: only one pattern must be configured for a location",
+ cmd->directive->directive);
+ }
+ dconf->response_pattern = apr_pstrdup(cmd->pool, pattern);
+ dconf->response_pattern_len = strlen(dconf->response_pattern);
+ dconf->response_pattern_var = apr_pstrdup(cmd->pool, var);
+ if(var[0] == '!' && !var[1]) {
+ return apr_psprintf(cmd->pool, "%s: variable name is too short",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+/* QS_SetEnv */
+const char *qos_setenv_cmd(cmd_parms *cmd, void *dcfg, const char *variable,
+ const char *value) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ if(!variable[0] || !value[0]) {
+ return apr_psprintf(cmd->pool, "%s: invalid parameter",
+ cmd->directive->directive);
+ }
+ if(strchr(variable, '=')) {
+ return apr_psprintf(cmd->pool, "%s: variable must not contain a '='",
+ cmd->directive->directive);
+ }
+ apr_table_set(sconf->setenv_t, apr_pstrcat(cmd->pool, variable, "=", value, NULL), variable);
+ return NULL;
+}
+
+/* QS_SetReqHeader */
+const char *qos_setreqheader_cmd(cmd_parms *cmd, void *dcfg, const char *header,
+ const char *variable, const char *late) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+
+ if(!variable[0] || !header[0]) {
+ return apr_psprintf(cmd->pool, "%s: invalid parameter",
+ cmd->directive->directive);
+ }
+ if(header[0] == '!' && !header[1]) {
+ return apr_psprintf(cmd->pool, "%s: header name is too short",
+ cmd->directive->directive);
+ }
+ if(strchr(header, '=')) {
+ return apr_psprintf(cmd->pool, "%s: header name must not contain a '='",
+ cmd->directive->directive);
+ }
+ if(late != NULL) {
+ if(strcasecmp(late, "late") != 0) {
+ return apr_psprintf(cmd->pool, "%s: third parameter can only be 'late'",
+ cmd->directive->directive);
+ }
+ apr_table_set(sconf->setreqheaderlate_t,
+ apr_pstrcat(cmd->pool, header, "=", variable, NULL), header);
+ } else {
+ apr_table_set(sconf->setreqheader_t,
+ apr_pstrcat(cmd->pool, header, "=", variable, NULL), header);
+ }
+ return NULL;
+}
+
+/* QS_UnsetReqHeader */
+const char *qos_unsetreqheader_cmd(cmd_parms *cmd, void *dcfg, const char *header) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ apr_table_set(sconf->unsetreqheader_t, header, "");
+ return NULL;
+}
+
+/* QS_UnsetResHeader */
+const char *qos_unsetresheader_cmd(cmd_parms *cmd, void *dcfg, const char *header) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ apr_table_set(sconf->unsetresheader_t, header, "");
+ return NULL;
+}
+
+const char *qos_event_setenvresheader_cmd(cmd_parms *cmd, void *dcfg, const char *hdr,
+ const char *action) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ apr_table_set(sconf->setenvresheader_t, hdr, action == NULL ? "" : action);
+ return NULL;
+}
+
+const char *qos_event_setenvresheadermatch_cmd(cmd_parms *cmd, void *dcfg, const char *hdr,
+ const char *pcres) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ ap_regex_t *preg = ap_pregcomp(cmd->pool, pcres, AP_REG_DOTALL | AP_REG_ICASE);
+ if(preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive, pcres);
+ }
+ apr_table_setn(sconf->setenvresheadermatch_t, apr_pstrdup(cmd->pool, hdr), (char *)preg);
+ return NULL;
+}
+
+const char *qos_redirectif_cmd(cmd_parms *cmd, void *dcfg, const char *var,
+ const char *pattern, const char *url) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ qos_redirectif_entry_t *new;
+ if(cmd->path) {
+ new = apr_array_push(dconf->redirectif);
+ } else {
+ new = apr_array_push(sconf->redirectif);
+ }
+ new->name = apr_pstrdup(cmd->pool, var);
+ new->preg = ap_pregcomp(cmd->pool, pattern, (AP_REG_EXTENDED | AP_REG_ICASE));
+ if(new->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression %s",
+ cmd->directive->directive, pattern);
+ }
+ if(strncasecmp(url, "307:", 4) == 0) {
+ new->code = HTTP_TEMPORARY_REDIRECT;
+ new->url = apr_pstrdup(cmd->pool, &url[4]);
+ } else if(strncasecmp(url, "301:", 4) == 0) {
+ new->code = HTTP_MOVED_PERMANENTLY;
+ new->url = apr_pstrdup(cmd->pool, &url[4]);
+ } else if(strncasecmp(url, "302:", 4) == 0) {
+ new->code = HTTP_MOVED_TEMPORARILY;
+ new->url = apr_pstrdup(cmd->pool, &url[4]);
+ } else {
+ new->code = HTTP_MOVED_TEMPORARILY;
+ new->url = apr_pstrdup(cmd->pool, url);
+ }
+ return NULL;
+}
+
+const char *qos_setenvres_cmd(cmd_parms *cmd, void *dcfg, const char *var,
+ const char *pattern, const char *var2) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_pregval_t *pregval = apr_pcalloc(cmd->pool, sizeof(qos_pregval_t));
+ pregval->name = apr_pstrdup(cmd->pool, var2);
+ pregval->value = strchr(pregval->name, '=');
+ if(pregval->value) {
+ pregval->value[0] = '\0';
+ pregval->value++;
+ }
+ pregval->preg = ap_pregcomp(cmd->pool, pattern, AP_REG_EXTENDED);
+ if(pregval->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive, pattern);
+ }
+ apr_table_addn(sconf->setenvres_t, apr_pstrdup(cmd->pool, var), (char *)pregval);
+ return NULL;
+}
+
+/** QS_SetEnvIf */
+const char *qos_event_setenvif_cmd(cmd_parms *cmd, void *dcfg, const char *v1, const char *v2,
+ const char *a3) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_setenvif_t *setenvif;
+ if(cmd->path) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ setenvif = apr_array_push(dconf->setenvif_t);
+ } else {
+ setenvif = apr_array_push(sconf->setenvif_t);
+ }
+
+ if(a3) {
+ // mode 1 (boolean AND operator)
+ setenvif->variable1 = apr_pstrdup(cmd->pool, v1);
+ setenvif->variable2 = apr_pstrdup(cmd->pool, v2);
+ setenvif->preg = NULL;
+ setenvif->name = apr_pstrdup(cmd->pool, a3);
+ setenvif->value = strchr(setenvif->name, '=');
+ if(setenvif->value == NULL) {
+ if(setenvif->name[0] == '!') {
+ setenvif->value = apr_pstrdup(cmd->pool, "");
+ } else {
+ return apr_psprintf(cmd->pool, "%s: new variable must have the format <name>=<value>",
+ cmd->directive->directive);
+ }
+ } else {
+ setenvif->value[0] = '\0';
+ setenvif->value++;
+ }
+ } else {
+ // mode 2 (pattern match)
+ char *pattern;
+ setenvif->variable1 = apr_pstrdup(cmd->pool, v1);
+ pattern = strchr(setenvif->variable1, '=');
+ if(pattern == NULL) {
+ return apr_psprintf(cmd->pool, "%s: missing pattern for variable1",
+ cmd->directive->directive);
+ }
+ pattern[0] = '\0';
+ pattern++;
+ setenvif->variable2 = NULL;
+ setenvif->preg = ap_pregcomp(cmd->pool, pattern, AP_REG_EXTENDED);
+ if(setenvif->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, pattern);
+ }
+ setenvif->name = apr_pstrdup(cmd->pool, v2);
+ setenvif->value = strchr(setenvif->name, '=');
+ if(setenvif->value == NULL) {
+ if(setenvif->name[0] == '!') {
+ setenvif->value = apr_pstrdup(cmd->pool, "");
+ } else {
+ return apr_psprintf(cmd->pool, "%s: new variable must have the format <name>=<value>",
+ cmd->directive->directive);
+ }
+ } else {
+ setenvif->value[0] = '\0';
+ setenvif->value++;
+ }
+ }
+ return NULL;
+}
+
+/** QS_SetEnvIfCmp */
+const char *qos_cmp_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[]) {
+ qos_cmp_entry_t *new;
+ qos_dir_config *conf = dcfg;
+ char *del;
+ if(argc != 4) {
+ return apr_psprintf(cmd->pool, "%s: requires 4 arguments",
+ cmd->directive->directive);
+ }
+ new = apr_array_push(conf->setenvcmp);
+ new->left = apr_pstrdup(cmd->pool, argv[0]);
+ if(strcasecmp(argv[1], "eq") == 0) {
+ new->cmp = QS_CMP_EQ;
+ } else if(strcasecmp(argv[1], "ne") == 0) {
+ new->cmp = QS_CMP_NE;
+ } else if(strcasecmp(argv[1], "lt") == 0) {
+ new->cmp = QS_CMP_LT;
+ } else if(strcasecmp(argv[1], "gt") == 0) {
+ new->cmp = QS_CMP_GT;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid operator '%s",
+ cmd->directive->directive, argv[1]);
+ }
+ new->right = apr_pstrdup(cmd->pool, argv[2]);
+ new->variable = apr_pstrdup(cmd->pool, argv[3]);
+ del = strchr(new->variable, '=');
+ if(del) {
+ new->value = &del[1];
+ del[0] = '\0';
+ } else {
+ new->value = apr_pstrdup(cmd->pool, "");
+ }
+ return NULL;
+}
+
+/** QS_SetEnvIfQuery */
+const char *qos_event_setenvifquery_cmd(cmd_parms *cmd, void *dcfg, const char *rx, const char *v) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_setenvifquery_t *setenvif = apr_pcalloc(cmd->pool, sizeof(qos_setenvifquery_t));
+ char *p;
+ setenvif->preg = ap_pregcomp(cmd->pool, rx, AP_REG_EXTENDED);
+ if(setenvif->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, rx);
+ }
+ if(strlen(v) < 2) {
+ return apr_psprintf(cmd->pool, "%s: variable name is too short (%s)",
+ cmd->directive->directive, v);
+ }
+ setenvif->name = apr_pstrdup(cmd->pool, v);
+ p = strchr(setenvif->name, '=');
+ if(p == NULL) {
+ setenvif->value = apr_pstrdup(cmd->pool, "");
+ } else {
+ p[0] = '\0';
+ p++;
+ setenvif->value = p;
+ }
+ if(cmd->path) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ apr_table_setn(dconf->setenvifquery_t, apr_pstrdup(cmd->pool, rx), (char *)setenvif);
+ } else {
+ apr_table_setn(sconf->setenvifquery_t, apr_pstrdup(cmd->pool, rx), (char *)setenvif);
+ }
+ return NULL;
+}
+
+const char *qos_event_setenvifparpbody_cmd(cmd_parms *cmd, void *dcfg,
+ const char *rx, const char *v) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_setenvifparpbody_t *setenvif = apr_pcalloc(cmd->pool, sizeof(qos_setenvifparpbody_t));
+ char *p;
+ setenvif->pregx = ap_pregcomp(cmd->pool, rx, AP_REG_DOTALL | AP_REG_EXTENDED | AP_REG_ICASE);
+ if(setenvif->pregx == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, rx);
+ }
+ setenvif->name = apr_pstrdup(cmd->pool, v);
+ p = strchr(setenvif->name, '=');
+ if(p == NULL) {
+ setenvif->value = apr_pstrdup(cmd->pool, "");
+ } else {
+ p[0] = '\0';
+ p++;
+ setenvif->value = p;
+ }
+ m_requires_parp = 1;
+ apr_table_setn(sconf->setenvifparpbody_t, apr_pstrdup(cmd->pool, rx), (char *)setenvif);
+ return NULL;
+}
+
+const char *qos_event_setenvifparp_cmd(cmd_parms *cmd, void *dcfg, const char *rx, const char *v) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_setenvifquery_t *setenvif = apr_pcalloc(cmd->pool, sizeof(qos_setenvifquery_t));
+ char *p;
+ setenvif->preg = ap_pregcomp(cmd->pool, rx, AP_REG_EXTENDED);
+ if(setenvif->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, rx);
+ }
+ if(strlen(v) < 2) {
+ return apr_psprintf(cmd->pool, "%s: variable name is too short (%s)",
+ cmd->directive->directive, v);
+ }
+ setenvif->name = apr_pstrdup(cmd->pool, v);
+ p = strchr(setenvif->name, '=');
+ if(p == NULL) {
+ setenvif->value = apr_pstrdup(cmd->pool, "");
+ } else {
+ p[0] = '\0';
+ p++;
+ setenvif->value = p;
+ }
+ m_requires_parp = 1;
+ apr_table_setn(sconf->setenvifparp_t, apr_pstrdup(cmd->pool, rx), (char *)setenvif);
+ return NULL;
+}
+
+/**
+ * defines custom error page
+ */
+const char *qos_error_page_cmd(cmd_parms *cmd, void *dcfg, const char *path) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->error_page = apr_pstrdup(cmd->pool, path);
+ if((sconf->error_page[0] != '/') &&
+ (strncmp(sconf->error_page, "http", 4) != 0)) {
+ return apr_psprintf(cmd->pool, "%s: requires absolute path (%s)",
+ cmd->directive->directive, sconf->error_page);
+ }
+ return NULL;
+}
+
+#if APR_HAS_THREADS
+/**
+ * QS_Status
+ */
+const char *qos_qsstatus_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qsstatus = flag;
+ return NULL;
+}
+#endif
+
+/**
+ * QS_EventCount
+ */
+const char *qos_qsevents_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qsevents = flag;
+ return NULL;
+}
+
+/**
+ * pipe to global qslog tool (per Apache instance stat)
+ */
+const char *qos_qlog_cmd(cmd_parms *cmd, void *dcfg, const char *arg) {
+ qos_srv_config *sconf = ap_get_module_config(cmd->server->module_config, &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qslog_str = apr_pstrdup(cmd->pool, arg);
+ return NULL;
+}
+
+/**
+ * global error code setting
+ */
+const char *qos_error_code_cmd(cmd_parms *cmd, void *dcfg, const char *arg) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ int idx500 = ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
+ if (err != NULL) {
+ return err;
+ }
+ m_retcode = atoi(arg);
+ if((m_retcode < 400) || (m_retcode > 599)) {
+ return apr_psprintf(cmd->pool, "%s: HTTP response code code must be a"
+ " numeric value between 400 and 599",
+ cmd->directive->directive);
+ }
+ if(m_retcode != 500) {
+ if(ap_index_of_response(m_retcode) == idx500) {
+ return apr_psprintf(cmd->pool, "%s: unsupported HTTP response code",
+ cmd->directive->directive);
+ }
+ }
+ return NULL;
+}
+
+/**
+ * global connection close behavior
+ */
+const char *qos_forced_close_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ m_forced_close = flag;
+ return NULL;
+}
+
+/** QS_UserTrackingCookieName */
+
+#ifdef AP_TAKE_ARGV
+const char *qos_user_tracking_cookie_cmd(cmd_parms *cmd, void *dcfg,
+ int argc, char *const argv[]) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ int pos = 1;
+ if(argc == 0) {
+ return apr_psprintf(cmd->pool, "%s: takes 1 to 4 arguments",
+ cmd->directive->directive);
+ }
+ sconf->user_tracking_cookie = apr_pstrdup(cmd->pool, argv[0]);
+ while(pos < argc) {
+ const char *value = argv[pos];
+ if(value[0] == '/') {
+ sconf->user_tracking_cookie_force = apr_pstrdup(cmd->pool, value);
+ } else if(strcasecmp(value, "session") == 0) {
+ sconf->user_tracking_cookie_session = 1;
+ } else if(strcasecmp(value, "jsredirect") == 0) {
+ sconf->user_tracking_cookie_jsredirect = 1;
+ } else {
+ if(sconf->user_tracking_cookie_domain != NULL) {
+ return apr_psprintf(cmd->pool, "%s: invalid attribute"
+ " (expects <name>, <path>, 'session', or <domain>",
+ cmd->directive->directive);
+ }
+ sconf->user_tracking_cookie_domain = apr_pstrdup(cmd->pool, value);
+ }
+ pos++;
+ }
+ return NULL;
+}
+#else
+const char *qos_user_tracking_cookie_cmd(cmd_parms *cmd, void *dcfg,
+ const char *name,
+ const char *option1,
+ const char *option2) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *force = NULL;
+ sconf->user_tracking_cookie = apr_pstrdup(cmd->pool, name);
+ if(option1) {
+ if((strcasecmp(option1, "session") == 0) ||
+ (strcasecmp(option1, "'session'") == 0)) {
+ sconf->user_tracking_cookie_session = 1;
+ } else {
+ force = option1;
+ }
+ }
+ if(option2) {
+ if((strcasecmp(option2, "session") == 0) ||
+ (strcasecmp(option2, "'session'") == 0)) {
+ sconf->user_tracking_cookie_session = 1;
+ } else {
+ if(force == NULL) {
+ force = option2;
+ }
+ }
+ }
+ if(force) {
+ if(force[0] != '/') {
+ return apr_psprintf(cmd->pool, "%s: invalid path '%s'",
+ cmd->directive->directive, force);
+ }
+ sconf->user_tracking_cookie_force = apr_pstrdup(cmd->pool, force);
+ }
+ return NULL;
+}
+#endif
+
+/**
+ * session definitions: cookie name and path, expiration/max-age
+ */
+const char *qos_cookie_name_cmd(cmd_parms *cmd, void *dcfg, const char *name) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->cookie_name = apr_pstrdup(cmd->pool, name);
+ return NULL;
+}
+
+const char *qos_cookie_path_cmd(cmd_parms *cmd, void *dcfg, const char *path) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->cookie_path = apr_pstrdup(cmd->pool, path);
+ return NULL;
+}
+
+const char *qos_timeout_cmd(cmd_parms *cmd, void *dcfg, const char *sec) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->max_age = atoi(sec);
+ if(sconf->max_age == 0) {
+ return apr_psprintf(cmd->pool, "%s: timeout must be numeric value >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_key_cmd(cmd_parms *cmd, void *dcfg, const char *seed) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->rawKey = (unsigned char *)apr_pstrdup(cmd->pool, seed);
+ sconf->rawKeyLen = strlen(seed);
+ EVP_BytesToKey(EVP_des_ede3_cbc(), EVP_sha1(), NULL,
+ sconf->rawKey, sconf->rawKeyLen, 1, sconf->key, NULL);
+ sconf->keyset = 1;
+ return NULL;
+}
+
+/**
+ * name of the http header to mark a vip
+ */
+const char *qos_header_name_cmd(cmd_parms *cmd, void *dcfg, const char *n, const char *drop) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *name = apr_pstrdup(cmd->pool, n);
+ char *p = strchr(name, '=');
+ if(p) {
+ p[0] = '\0';
+ p++;
+ sconf->header_name_regex = ap_pregcomp(cmd->pool, p, AP_REG_EXTENDED);
+ if(sconf->header_name_regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, p);
+ }
+ } else {
+ sconf->header_name_regex = NULL;
+ }
+ if(drop && (strcasecmp(drop, "drop") == 0)) {
+ sconf->header_name_drop = 1;
+ } else {
+ sconf->header_name_drop = 0;
+ }
+ sconf->header_name = name;
+ return NULL;
+}
+
+const char *qos_ip_header_name_cmd(cmd_parms *cmd, void *dcfg, const char *n, const char *drop) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *name = apr_pstrdup(cmd->pool, n);
+ char *p = strchr(name, '=');
+ if(p) {
+ p[0] = '\0';
+ p++;
+ sconf->ip_header_name_regex = ap_pregcomp(cmd->pool, p, AP_REG_EXTENDED);
+ if(sconf->ip_header_name_regex == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, p);
+ }
+ } else {
+ sconf->ip_header_name_regex = NULL;
+ }
+ if(drop && (strcasecmp(drop, "drop") == 0)) {
+ sconf->ip_header_name_drop = 1;
+ } else {
+ sconf->ip_header_name_drop = 0;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->ip_header_name = name;
+ return NULL;
+}
+
+const char *qos_vip_u_cmd(cmd_parms *cmd, void *dcfg) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->vip_user = 1;
+ return NULL;
+}
+
+const char *qos_vip_ip_u_cmd(cmd_parms *cmd, void *dcfg) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->vip_ip_user = 1;
+ return NULL;
+}
+
+/**
+ * max concurrent connections per server
+ */
+const char *qos_max_conn_cmd(cmd_parms *cmd, void *dcfg, const char *number) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->has_conn_counter = 1;
+ sconf->max_conn = atoi(number);
+ if(sconf->max_conn == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+/**
+ * QS_SrvMaxConnClose, disable keep-alive
+ */
+const char *qos_max_conn_close_cmd(cmd_parms *cmd, void *dcfg, const char *number) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *n = apr_pstrdup(cmd->temp_pool, number);
+ sconf->has_conn_counter = 1;
+ if((strlen(n) > 1) &&
+ (n[strlen(n)-1] == '%')) {
+ n[strlen(n)-1] = '\0';
+ sconf->max_conn_close = atoi(n);
+ sconf->max_conn_close_percent = sconf->max_conn_close;
+ if(sconf->max_conn_close > 99) {
+ return apr_psprintf(cmd->pool, "%s: number must be a percentage <100",
+ cmd->directive->directive);
+ }
+ } else {
+ sconf->max_conn_close = atoi(n);
+ sconf->max_conn_close_percent = 0;
+ }
+ if(sconf->max_conn_close == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+/**
+ * max concurrent connections per client ip
+ */
+const char *qos_max_conn_ip_cmd(cmd_parms *cmd, void *dcfg, const char *number,
+ const char *connections) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->has_conn_counter = 1;
+ sconf->max_conn_per_ip = atoi(number);
+ if(sconf->max_conn_per_ip == 0) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ if(connections) {
+ sconf->max_conn_per_ip_connections = atoi(connections);
+ if((sconf->max_conn_per_ip_connections == 0) &&
+ (strcmp(connections, "0") != 0)) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >0",
+ cmd->directive->directive);
+ }
+ }
+ return NULL;
+}
+
+/* QS_SrvMaxConnPerIPIgnoreVIP */
+const char *qos_max_conn_ip_vip_off_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->max_conn_per_ip_ignore_vip = flag;
+ return NULL;
+}
+
+/**
+ * QS_SrvSerialize
+ */
+const char *qos_serialize_cmd(cmd_parms *cmd, void *dcfg, const char * flag,
+ const char *seconds) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ if(strcasecmp(flag, "on") == 0) {
+ sconf->serialize = 1;
+ } else if(strcasecmp(flag, "off") == 0) {
+ sconf->serialize = 0;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: flag needs to be either 'on' or 'off'",
+ cmd->directive->directive);
+ }
+ if(seconds) {
+ sconf->serializeTMO = atoi(seconds);
+ if(sconf->serializeTMO <= 0) {
+ return apr_psprintf(cmd->pool, "%s: timeout (seconds) must be a numeric value >0",
+ cmd->directive->directive);
+ }
+ // n * 50 milliseconds
+ sconf->serializeTMO = sconf->serializeTMO * 20;
+ }
+ return NULL;
+}
+
+/**
+ * ip address without any limitation
+ */
+const char *qos_max_conn_ex_cmd(cmd_parms *cmd, void *dcfg, const char *addr) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ if(addr[strlen(addr)-1] == '.') {
+ /* address range */
+ apr_table_add(sconf->exclude_ip, addr, "r");
+ } else if(addr[strlen(addr)-1] == ':') {
+ /* address range */
+ apr_table_add(sconf->exclude_ip, addr, "r");
+ } else {
+ /* single ip */
+ apr_table_add(sconf->exclude_ip, addr, "s");
+ }
+ return NULL;
+}
+
+const char *qos_req_rate_off_cmd(cmd_parms *cmd, void *dcfg) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->min_rate_off = 1;
+ return NULL;
+}
+
+/** verify, that the platform supports "%p" in sprintf */
+static int qos_sprintfcheck() {
+ char buf[128];
+ char buf2[128];
+ sprintf(buf, "%p", buf);
+ sprintf(buf2, "%p", buf2);
+ if((strcmp(buf, buf2) == 0) || (strlen(buf) < 4)) {
+ /* not okay */
+ return 0;
+ }
+ return 1;
+}
+
+const char *qos_req_rate_cmd(cmd_parms *cmd, void *dcfg, const char *sec, const char *secmax) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ if(!qos_sprintfcheck()) {
+ return apr_psprintf(cmd->pool, "%s: directive can't be used on this platform",
+ cmd->directive->directive);
+ }
+ if(sconf->req_rate != -1) {
+ return apr_psprintf(cmd->pool, "%s: directive can't be used together with QS_SrvMinDataRate",
+ cmd->directive->directive);
+ }
+ sconf->req_rate = atoi(sec);
+ if(sconf->req_rate <= 0) {
+ return apr_psprintf(cmd->pool, "%s: request rate must be a numeric value >0",
+ cmd->directive->directive);
+ }
+ if(secmax) {
+ sconf->min_rate_max = atoi(secmax);
+ if(sconf->min_rate_max <= sconf->min_rate) {
+ return apr_psprintf(cmd->pool, "%s: max. data rate must be a greater than min. value",
+ cmd->directive->directive);
+ }
+ }
+ return NULL;
+}
+
+/* QS_SrvMinDataRateOffEvent */
+const char *qos_min_rate_off_cmd(cmd_parms *cmd, void *dcfg, const char *var) {
+ apr_table_t *disable_reqrate_events;
+ if(cmd->path) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ disable_reqrate_events = dconf->disable_reqrate_events;
+ } else {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ disable_reqrate_events = sconf->disable_reqrate_events;
+ }
+ if(((var[0] != '+') && (var[0] != '-')) || (strlen(var) < 2)) {
+ return apr_psprintf(cmd->pool, "%s: invalid variable (requires +/- prefix)",
+ cmd->directive->directive);
+ }
+ apr_table_set(disable_reqrate_events, var, "");
+ return NULL;
+}
+
+#ifdef AP_TAKE_ARGV
+const char *qos_min_rate_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[])
+#else
+const char *qos_min_rate_cmd(cmd_parms *cmd, void *dcfg, const char *_sec, const char *_secmax)
+#endif
+{
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ const char *sec = NULL;
+ const char *secmax = NULL;
+ const char *connections = NULL;
+#ifdef AP_TAKE_ARGV
+ if(argc == 0) {
+ return apr_psprintf(cmd->pool, "%s: takes 1 to 3 arguments",
+ cmd->directive->directive);
+ }
+ sec = argv[0];
+ if(argc > 1) {
+ secmax = argv[1];
+ }
+ if(argc > 2) {
+ connections = argv[2];
+ }
+#else
+ sec = _sec;
+ secmax = _secmax;
+#endif
+ if (err != NULL) {
+ return err;
+ }
+ if(!qos_sprintfcheck()) {
+ return apr_psprintf(cmd->pool, "%s: directive can't be used on this platform",
+ cmd->directive->directive);
+ }
+ if(sconf->req_rate != -1) {
+ return apr_psprintf(cmd->pool, "%s: directive can't be used together with QS_SrvRequestRate",
+ cmd->directive->directive);
+ }
+ sconf->req_rate = atoi(sec);
+ sconf->min_rate = sconf->req_rate;
+ if(connections) {
+ sconf->req_rate_start = atoi(connections);
+ if(sconf->req_rate_start <= 0) {
+ return apr_psprintf(cmd->pool, "%s: number of connections must be a numeric value >0",
+ cmd->directive->directive);
+ }
+ }
+ if(sconf->req_rate <= 0) {
+ return apr_psprintf(cmd->pool, "%s: minimal data rate must be a numeric value >0",
+ cmd->directive->directive);
+ }
+ if(secmax) {
+ sconf->min_rate_max = atoi(secmax);
+ if(sconf->min_rate_max <= sconf->min_rate) {
+ return apr_psprintf(cmd->pool, "%s: max. data rate must be a greater than min. value",
+ cmd->directive->directive);
+ }
+ }
+ return NULL;
+}
+
+/* QS_SrvMinDataRateIgnoreVIP */
+const char *qos_min_rate_vip_off_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->req_ignore_vip_rate = flag;
+ return NULL;
+}
+
+/**
+ * generic filter command
+ */
+const char *qos_deny_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *pcres,
+ qs_rfilter_type_e type, int options) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ qos_rfilter_t *flt = apr_pcalloc(cmd->pool, sizeof(qos_rfilter_t));
+ flt->type = type;
+ if(((id[0] != '+') && (id[0] != '-')) || (strlen(id) < 2)) {
+ return apr_psprintf(cmd->pool, "%s: invalid rule id",
+ cmd->directive->directive);
+ }
+ flt->id = apr_pstrdup(cmd->pool, &id[1]);
+ if(strcasecmp(action, "log") == 0) {
+ flt->action = QS_LOG;
+ } else if(strcasecmp(action, "deny") == 0) {
+ flt->action = QS_DENY;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid action",
+ cmd->directive->directive);
+ }
+ if(flt->type != QS_DENY_EVENT) {
+ flt->preg = ap_pregcomp(cmd->pool, pcres, AP_REG_DOTALL | options);
+ if(flt->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive,
+ pcres);
+ }
+ }
+ flt->text = apr_pstrdup(cmd->pool, pcres);
+ apr_table_setn(dconf->rfilter_table, apr_pstrdup(cmd->pool, id), (char *)flt);
+ return NULL;
+}
+const char *qos_deny_rql_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *pcres) {
+ return qos_deny_cmd(cmd, dcfg, id, action, pcres, QS_DENY_REQUEST_LINE, AP_REG_ICASE);
+}
+const char *qos_deny_path_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *pcres) {
+ return qos_deny_cmd(cmd, dcfg, id, action, pcres, QS_DENY_PATH, AP_REG_ICASE);
+}
+const char *qos_deny_query_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *pcres) {
+ return qos_deny_cmd(cmd, dcfg, id, action, pcres, QS_DENY_QUERY, AP_REG_ICASE);
+}
+const char *qos_deny_event_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *event) {
+ return qos_deny_cmd(cmd, dcfg, id, action, event, QS_DENY_EVENT, 0);
+}
+const char *qos_permit_uri_cmd(cmd_parms *cmd, void *dcfg,
+ const char *id, const char *action, const char *pcres) {
+ return qos_deny_cmd(cmd, dcfg, id, action, pcres, QS_PERMIT_URI, 0);
+}
+const char *qos_deny_urlenc_cmd(cmd_parms *cmd, void *dcfg, const char *mode) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ if(strcasecmp(mode, "log") == 0) {
+ dconf->urldecoding = QS_LOG;
+ } else if(strcasecmp(mode, "deny") == 0) {
+ dconf->urldecoding = QS_DENY;
+ } else if(strcasecmp(mode, "off") == 0) {
+ dconf->urldecoding = QS_OFF;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid action",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_milestone_tmo_cmd(cmd_parms *cmd, void *dcfg, const char *sec) {
+ qos_srv_config *sconf = ap_get_module_config(cmd->server->module_config, &qos_module);
+ sconf->milestoneTimeout = atoi(sec);
+ if(sconf->milestoneTimeout <= 0) {
+ return apr_psprintf(cmd->pool, "%s: timeout must be numeric value >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_milestone_cmd(cmd_parms *cmd, void *dcfg, const char *action,
+ const char *pattern, const char *thinktimestr) {
+ qos_srv_config *sconf = ap_get_module_config(cmd->server->module_config, &qos_module);
+ qos_milestone_t *ms;
+ if(sconf->milestones == NULL) {
+ sconf->milestones = apr_array_make(cmd->pool, 100, sizeof(qos_milestone_t));
+ }
+ ms = apr_array_push(sconf->milestones);
+ ms->num = sconf->milestones->nelts - 1;
+ if(thinktimestr != NULL) {
+ ms->thinktime = atoi(thinktimestr);
+ if(ms->thinktime <= 0) {
+ return apr_psprintf(cmd->pool, "%s: invalid 'think time' (must be numeric value >0)",
+ cmd->directive->directive);
+ }
+ } else {
+ ms->thinktime = 0;
+ }
+ ms->preg = ap_pregcomp(cmd->pool, pattern, AP_REG_DOTALL);
+ if(ms->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive, pattern);
+ }
+ ms->pattern = apr_pstrdup(cmd->pool, pattern);
+ if(strcasecmp(action, "deny") == 0) {
+ ms->action = QS_DENY;
+ } else if(strcasecmp(action, "log") == 0) {
+ ms->action = QS_LOG;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid action %s",
+ cmd->directive->directive, action);
+ }
+ return NULL;
+}
+
+const char *qos_maxpost_cmd(cmd_parms *cmd, void *dcfg, const char *bytes) {
+ apr_off_t s;
+ char *errp = NULL;
+#ifdef ap_http_scheme
+ /* Apache 2.2 */
+ if(APR_SUCCESS != apr_strtoff(&s, bytes, &errp, 10))
+#else
+ if((s = apr_atoi64(bytes)) < 0)
+#endif
+ {
+ return "QS_LimitRequestBody argument is not parsable";
+ }
+ if(s < 0) {
+ return "QS_LimitRequestBody requires a non-negative integer";
+ }
+ if(cmd->path == NULL) {
+ /* server */
+ qos_srv_config *sconf = ap_get_module_config(cmd->server->module_config, &qos_module);
+ sconf->maxpost = s;
+ } else {
+ /* location */
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->maxpost = s;
+ }
+ return NULL;
+}
+
+/* QS_Decoding */
+const char *qos_dec_cmd(cmd_parms *cmd, void *dcfg, const char *arg) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+// if(strcasecmp(arg, "html") == 0) {
+// dconf->dec_mode |= QOS_DEC_MODE_FLAGS_HTML;
+// } else
+ if(strcasecmp(arg, "uni") == 0) {
+ dconf->dec_mode |= QOS_DEC_MODE_FLAGS_UNI;
+// } if(strcasecmp(arg, "ansi") == 0) {
+// dconf->dec_mode |= QOS_DEC_MODE_FLAGS_ANSI;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: unknown decoding '%s'",
+ cmd->directive->directive, arg);
+ }
+ return NULL;
+}
+
+const char *qos_denyinheritoff_cmd(cmd_parms *cmd, void *dcfg) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->inheritoff = 1;
+ return NULL;
+}
+
+const char *qos_denybody_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->bodyfilter_p = flag;
+ dconf->bodyfilter_d = flag;
+ if(flag) {
+ m_requires_parp = 1;
+ }
+ return NULL;
+}
+
+const char *qos_denybody_d_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->bodyfilter_d = flag;
+ if(flag) {
+ m_requires_parp = 1;
+ }
+ return NULL;
+}
+
+const char *qos_denybody_p_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->bodyfilter_p = flag;
+ if(flag) {
+ m_requires_parp = 1;
+ }
+ return NULL;
+}
+
+/* QS_RequestHeaderFilter enables/disables header filter */
+const char *qos_headerfilter_cmd(cmd_parms *cmd, void *dcfg, const char *flag) {
+ qs_headerfilter_mode_e headerfilter;
+ if(strcasecmp(flag, "on") == 0) {
+ headerfilter = QS_HEADERFILTER_ON;
+ } else if(strcasecmp(flag, "off") == 0) {
+ headerfilter = QS_HEADERFILTER_OFF;
+ } else if(strcasecmp(flag, "size") == 0) {
+ headerfilter = QS_HEADERFILTER_SIZE_ONLY;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid argument",
+ cmd->directive->directive);
+ }
+ if(cmd->path) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ dconf->headerfilter = headerfilter;
+ } else {
+ qos_srv_config *sconf = ap_get_module_config(cmd->server->module_config, &qos_module);
+ sconf->headerfilter = headerfilter;
+ }
+ return NULL;
+}
+
+/* QS_ResponseHeaderFilter */
+const char *qos_resheaderfilter_cmd(cmd_parms *cmd, void *dcfg, const char *flag) {
+ qos_dir_config *dconf = (qos_dir_config*)dcfg;
+ if(strcasecmp(flag, "on") == 0) {
+ dconf->resheaderfilter = QS_HEADERFILTER_ON;
+ } else if(strcasecmp(flag, "off") == 0) {
+ dconf->resheaderfilter = QS_HEADERFILTER_OFF;
+ } else if(strcasecmp(flag, "silent") == 0) {
+ dconf->resheaderfilter = QS_HEADERFILTER_SILENT;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid argument",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+/* QS_RequestHeaderFilterRule: set custom header rules (global only)
+ name, action, pcre, size */
+#ifdef AP_TAKE_ARGV
+const char *qos_headerfilter_rule_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[])
+#else
+const char *qos_headerfilter_rule_cmd(cmd_parms *cmd, void *dcfg,
+ const char *header, const char *action,
+ const char *rule)
+#endif
+ {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_fhlt_r_t *he;
+#ifdef AP_TAKE_ARGV
+ const char *header;
+ const char *rule;
+ const char *action;
+#endif
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+#ifdef AP_TAKE_ARGV
+ if(argc != 4) {
+ return apr_psprintf(cmd->pool, "%s: takes 4 arguments",
+ cmd->directive->directive);
+ }
+#endif
+ he = apr_pcalloc(cmd->pool, sizeof(qos_fhlt_r_t));
+#ifdef AP_TAKE_ARGV
+ header = argv[0];
+ action = argv[1];
+ rule = argv[2];
+ he->size = atoi(argv[3]);
+#else
+ he->size = 9000;
+#endif
+ he->text = apr_pstrdup(cmd->pool, rule);
+ he->preg = ap_pregcomp(cmd->pool, rule, AP_REG_DOTALL);
+ if(strcasecmp(action, "deny") == 0) {
+ he->action = QS_FLT_ACTION_DENY;
+ } else if(strcasecmp(action, "drop") == 0) {
+ he->action = QS_FLT_ACTION_DROP;
+ } else {
+ return apr_psprintf(cmd->pool, "%s: invalid action %s",
+ cmd->directive->directive, action);
+ }
+ if(he->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive, rule);
+ }
+ if(he->size <= 0) {
+ return apr_psprintf(cmd->pool, "%s: size must be numeric value >0",
+ cmd->directive->directive);
+ }
+ apr_table_setn(sconf->hfilter_table, apr_pstrdup(cmd->pool, header), (char *)he);
+ return NULL;
+}
+
+const char *qos_resheaderfilter_rule_cmd(cmd_parms *cmd, void *dcfg,
+ const char *header,
+ const char *rule, const char *size) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ qos_fhlt_r_t *he;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ he = apr_pcalloc(cmd->pool, sizeof(qos_fhlt_r_t));
+ he->size = atoi(size);
+ he->text = apr_pstrdup(cmd->pool, rule);
+ he->preg = ap_pregcomp(cmd->pool, rule, AP_REG_DOTALL);
+ he->action = QS_FLT_ACTION_DROP;
+ if(he->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: could not compile regular expression '%s'",
+ cmd->directive->directive, rule);
+ }
+ if(he->size <= 0) {
+ return apr_psprintf(cmd->pool, "%s: size must be numeric value >0",
+ cmd->directive->directive);
+ }
+ apr_table_setn(sconf->reshfilter_table, apr_pstrdup(cmd->pool, header), (char *)he);
+ return NULL;
+}
+
+const char *qos_geodb_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *msg = NULL;
+ int errors = 0;
+ qos_geo_t *geodb = apr_pcalloc(cmd->pool, sizeof(qos_geo_t));
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ sconf->geodb = geodb;
+ sconf->geodb->data = NULL;
+ sconf->geodb->path = ap_server_root_relative(cmd->pool, arg1);
+ sconf->geodb->size = 0;
+
+ if(qos_loadgeo(cmd->pool, sconf->geodb, &msg, &errors) != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool, "%s: failed to load the database: %s"
+ " (total %d errors)",
+ cmd->directive->directive,
+ msg ? msg : "-",
+ errors);
+ }
+
+ return NULL;
+}
+
+const char *qos_geopriv_cmd(cmd_parms *cmd, void *dcfg, const char *list, const char *con,
+ const char *excludeUnknown) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *next = apr_pstrdup(cmd->pool, list);
+ int geo_limit;
+ char *name;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ name = apr_strtok(next, ",", &next);
+ if(name == NULL) {
+ return apr_psprintf(cmd->pool, "%s: empty list",
+ cmd->directive->directive);
+ }
+ while(name) {
+ apr_table_set(sconf->geo_priv, name, "");
+ name = apr_strtok(NULL, ",", &next);
+ }
+ geo_limit = atoi(con);
+ if(geo_limit <= 0 && con[0] != '0' && con[1] != '\0') {
+ return apr_psprintf(cmd->pool, "%s: invalid connection number",
+ cmd->directive->directive);
+ }
+ if(sconf->geo_limit != -1 && sconf->geo_limit != geo_limit) {
+ return apr_psprintf(cmd->pool, "%s: already configured with a different limitation",
+ cmd->directive->directive);
+ }
+ if(excludeUnknown != NULL) {
+ if(strcasecmp(excludeUnknown, excludeUnknown) != 0) {
+ return apr_psprintf(cmd->pool, "%s: invalid argument %s",
+ cmd->directive->directive, excludeUnknown);
+ }
+ sconf->geo_excludeUnknown = 1;
+ }
+ sconf->geo_limit = geo_limit;
+ return NULL;
+}
+
+const char *qos_enable_ipv6_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ if(flag) {
+ sconf->ip_type = QS_IP_V6;
+ } else {
+ sconf->ip_type = QS_IP_V4;
+ }
+ return NULL;
+}
+
+const char *qos_client_ex_cmd(cmd_parms *cmd, void *dcfg, const char *addr) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ if(strlen(addr) == 0) {
+ return apr_psprintf(cmd->pool, "%s: invalid address",
+ cmd->directive->directive);
+ }
+ if(addr[strlen(addr)-1] == '.') {
+ /* address range */
+ apr_table_add(sconf->cc_exclude_ip, addr, "r");
+ } else if(addr[strlen(addr)-1] == ':') {
+ /* address range */
+ apr_table_add(sconf->cc_exclude_ip, addr, "r");
+ } else {
+ /* single ip */
+ apr_table_add(sconf->cc_exclude_ip, addr, "s");
+ }
+ return NULL;
+}
+
+const char *qos_client_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qos_cc_size = atoi(arg1);
+#ifdef QS_INTERNAL_TEST
+ sconf->qos_cc_size = sconf->qos_cc_size / 100 * 100 ;
+#else
+ sconf->qos_cc_size = sconf->qos_cc_size / 640 * 640 ;
+#endif
+ if(sconf->qos_cc_size < 50000) {
+ m_qos_cc_partition = 2;
+ }
+ if(sconf->qos_cc_size >= 100000) {
+ m_qos_cc_partition = 8;
+ }
+ if(sconf->qos_cc_size >= 500000) {
+ m_qos_cc_partition = 16;
+ }
+ if(sconf->qos_cc_size >= 1000000) {
+ m_qos_cc_partition = 32;
+ }
+ if(sconf->qos_cc_size >= 4000000) {
+ m_qos_cc_partition = 64;
+ }
+ if(sconf->qos_cc_size <= 0 || sconf->qos_cc_size > 10000000) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value gearter than 640"
+ " and less than 10000000",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+#ifdef AP_TAKE_ARGV
+const char *qos_client_pref_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[])
+#else
+const char *qos_client_pref_cmd(cmd_parms *cmd, void *dcfg)
+#endif
+ {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->qos_cc_prefer = 80;
+#ifdef AP_TAKE_ARGV
+ if(argc) {
+ char *copy = apr_pstrdup(cmd->pool, argv[0]);
+ char *p = strchr(copy, '%');
+ if(p) {
+ p[0] = '\0';
+ }
+ sconf->qos_cc_prefer = atoi(copy);
+ }
+#endif
+ if((sconf->qos_cc_prefer < 1) || (sconf->qos_cc_prefer > 99)) {
+ return apr_psprintf(cmd->pool, "%s: percentage must be a numeric value"
+ " between 1 and 99",
+ cmd->directive->directive);
+ }
+#ifdef AP_TAKE_ARGV
+ if(argc > 1) {
+ return apr_psprintf(cmd->pool, "%s: command takes not more than one argument",
+ cmd->directive->directive);
+ }
+#endif
+ return NULL;
+}
+
+const char *qos_client_block_cmd(cmd_parms *cmd, void *dcfg, const char *arg1,
+ const char *arg2) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->qos_cc_block = atoi(arg1);
+ if((sconf->qos_cc_block < 0) || sconf->qos_cc_block >= (USHRT_MAX-1) || ((sconf->qos_cc_block == 0) && (strcmp(arg1, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0 and <%d.",
+ cmd->directive->directive, USHRT_MAX-1);
+ }
+ if(arg2) {
+ sconf->qos_cc_blockTime = atoi(arg2);
+ }
+ if(sconf->qos_cc_blockTime == 0) {
+ return apr_psprintf(cmd->pool, "%s: time must be numeric value >0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_client_limit_int_cmd(cmd_parms *cmd, void *dcfg, const char *arg_number,
+ const char *arg_sec, const char *arg_varname,
+ const char *arg_condition) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ char *limit_name = QS_LIMIT_DEFAULT;
+ int limit;
+ time_t limitTime = 600;
+ qos_s_entry_limit_conf_t *entry = apr_pcalloc(cmd->pool, sizeof(qos_s_entry_limit_conf_t));
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ limit = atoi(arg_number);
+ if((limit < 0) || limit >= (USHRT_MAX-1) || ((limit == 0) && (strcmp(arg_number, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0 and <%d.",
+ cmd->directive->directive, USHRT_MAX-1);
+ }
+ if(arg_sec) {
+ limitTime = atoi(arg_sec);
+ }
+ if(limitTime == 0) {
+ return apr_psprintf(cmd->pool, "%s: time must be numeric value >0",
+ cmd->directive->directive);
+ }
+ if(arg_varname) {
+ limit_name = apr_pstrdup(cmd->pool, arg_varname);
+ }
+ entry->limit = limit;
+ entry->limitTime = limitTime;
+ entry->condStr = NULL;
+ entry->preg = NULL;
+ if(arg_condition) {
+ entry->condStr = apr_pstrdup(cmd->pool, arg_condition);
+ entry->preg = ap_pregcomp(cmd->pool, entry->condStr, AP_REG_EXTENDED);
+ if(entry->preg == NULL) {
+ return apr_psprintf(cmd->pool, "%s: failed to compile regex (%s)",
+ cmd->directive->directive, entry->condStr);
+ }
+ }
+ if(apr_table_get(sconf->qos_cc_limitTable, limit_name) != NULL) {
+ return apr_psprintf(cmd->pool, "%s: variable %s has already been used by"
+ " another QS_[Cond]ClientEventLimitCount directive",
+ cmd->directive->directive, limit_name);
+ }
+ apr_table_setn(sconf->qos_cc_limitTable, limit_name, (char *)entry);
+ return NULL;
+}
+
+/* QS_ClientEventLimitCount <number> <seconds> <variable> */
+const char *qos_client_limit_cmd(cmd_parms *cmd, void *dcfg, const char *arg_number,
+ const char *arg_sec, const char *arg_varname) {
+ return qos_client_limit_int_cmd(cmd, dcfg, arg_number, arg_sec, arg_varname, NULL);
+}
+
+#ifdef AP_TAKE_ARGV
+/* QS_CondClientEventLimitCount <number> <seconds> <variable> <pattern> */
+const char *qos_cond_client_limit_cmd(cmd_parms *cmd, void *dcfg, int argc, char *const argv[]) {
+ if(argc != 4) {
+ return apr_psprintf(cmd->pool, "%s: takes 4 arguments",
+ cmd->directive->directive);
+ }
+ return qos_client_limit_int_cmd(cmd, dcfg, argv[0], argv[1], argv[2], argv[3]);
+}
+#endif
+
+const char *qos_client_forwardedfor_cmd(cmd_parms *cmd, void *dcfg, const char *header) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qos_cc_forwardedfor = apr_pstrdup(cmd->pool, header);
+ return NULL;
+}
+
+const char *qos_client_serial_cmd(cmd_parms *cmd, void *dcfg) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->qos_cc_serialize = 1;
+ return NULL;
+}
+
+const char *qos_req_rate_tm_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->qs_req_rate_tm= atoi(arg1);
+ if(sconf->qs_req_rate_tm < 2) {
+ return apr_psprintf(cmd->pool, "%s: must be numeric value between >1",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_client_tolerance_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ char *value = apr_pstrdup(cmd->pool, arg1);
+ char *p = strchr(value, '%');
+ if(p) {
+ p[0] = '\0';
+ }
+ if (err != NULL) {
+ return err;
+ }
+ sconf->cc_tolerance = atoi(value);
+ if(sconf->cc_tolerance < 5 || sconf->cc_tolerance > 80) {
+ return apr_psprintf(cmd->pool, "%s: must be numeric value between 5 and 80",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+#ifdef AP_TAKE_ARGV
+const char *qos_client_contenttype(cmd_parms *cmd, void *dcfg, int argc, char *const argv[]) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ if(argc != 5) {
+ return apr_psprintf(cmd->pool, "%s: requires five arguments",
+ cmd->directive->directive);
+ }
+ sconf->static_on = 1;
+ sconf->static_html = atol(argv[0]);
+ sconf->static_cssjs = atol(argv[1]);
+ sconf->static_img = atol(argv[2]);
+ sconf->static_other = atol(argv[3]);
+ sconf->static_notmodified = atol(argv[4]);
+ if(sconf->static_html == 0 ||
+ sconf->static_cssjs == 0 ||
+ sconf->static_img == 0 ||
+ sconf->static_other == 0 ||
+ sconf->static_notmodified == 0) {
+ return apr_psprintf(cmd->pool, "%s: requires numeric values greater than 0",
+ cmd->directive->directive);
+ } else {
+ unsigned long long s_all = sconf->static_html + sconf->static_img + sconf->static_cssjs +
+ sconf->static_other + sconf->static_notmodified;
+ unsigned long long s_2html = 100 * sconf->static_html / s_all;
+ unsigned long long s_2cssjs = 100 * sconf->static_cssjs / s_all;
+ unsigned long long s_2img = 100 * sconf->static_img / s_all;
+ unsigned long long s_2other = 100 * sconf->static_other / s_all;
+ unsigned long long s_2notmodified = 100 * sconf->static_notmodified / s_all;
+ sconf->static_html = s_2html;
+ sconf->static_cssjs = s_2cssjs;
+ sconf->static_img = s_2img;
+ sconf->static_other = s_2other;
+ sconf->static_notmodified = s_2notmodified;
+ }
+ return NULL;
+}
+#endif
+
+const char *qos_client_event_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->qos_cc_event = atoi(arg1);
+ if((sconf->qos_cc_event < 0) || ((sconf->qos_cc_event == 0) && (strcmp(arg1, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_client_event_req_cmd(cmd_parms *cmd, void *dcfg, const char *arg1) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ sconf->has_qos_cc = 1;
+ sconf->qos_cc_event_req = atoi(arg1);
+ if((sconf->qos_cc_event_req < 0) || ((sconf->qos_cc_event_req == 0) && (strcmp(arg1, "0") != 0))) {
+ return apr_psprintf(cmd->pool, "%s: number must be numeric value >=0",
+ cmd->directive->directive);
+ }
+ return NULL;
+}
+
+const char *qos_disable_handler_cmd(cmd_parms *cmd, void *dcfg, int flag) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ sconf->disable_handler = flag;
+ return NULL;
+}
+
+#ifdef QS_INTERNAL_TEST
+const char *qos_disable_int_ip_cmd(cmd_parms *cmd, void *dcfg, const char *arg) {
+ qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(cmd->server->module_config,
+ &qos_module);
+ if(strcasecmp(arg, "off") == 0 ) {
+ sconf->enable_testip = 0;
+ } else if(strcasecmp(arg, "on") == 0 ) {
+ sconf->enable_testip = 1;
+ } else {
+ sconf->enable_testip = 1;
+ m_qs_sim_ip_len = atoi(arg);
+ if(m_qs_sim_ip_len == 0) {
+ return apr_psprintf(cmd->pool, "%s: must be on/off or the number of IPs",
+ cmd->directive->directive);
+ }
+ }
+ return NULL;
+}
+#endif
+
+static const command_rec qos_config_cmds[] = {
+ /* request limitation per location */
+ AP_INIT_TAKE1("QS_LocRequestLimitDefault", qos_loc_con_def_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocRequestLimitDefault <number>, defines the default for the"
+ " QS_LocRequestLimit and QS_LocRequestLimitMatch directive."),
+
+ AP_INIT_TAKE2("QS_LocRequestLimit", qos_loc_con_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocRequestLimit <location> <number>, defines the maximum number of"
+ " concurrent requests allowed to access the specified location. Default is defined by the"
+ " QS_LocRequestLimitDefault directive."),
+
+ AP_INIT_TAKE2("QS_LocRequestPerSecLimit", qos_loc_rs_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocRequestPerSecLimit <location> <number>, defines the allowed"
+ " number of requests per second to a location. Requests are limited"
+ " by adding a delay to each requests. This directive should be used"
+ " in conjunction with QS_LocRequestLimit only."),
+
+ AP_INIT_TAKE2("QS_LocKBytesPerSecLimit", qos_loc_bs_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocKBytesPerSecLimit <location> <kbytes>, defines the allowed"
+ " download bandwidth to the defined kbytes per second. Responses are"
+ "slowed by adding a delay to each response (non-linear, bigger files"
+ " get longer delay than smaller ones). This directive should be used"
+ " in conjunction with QS_LocRequestLimit only."),
+
+ AP_INIT_TAKE2("QS_LocRequestLimitMatch", qos_match_con_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocRequestLimitMatch <regex> <number>, defines the number of"
+ " concurrent requests to the uri (path and query) pattern."
+ " Default is defined by the QS_LocRequestLimitDefault directive."),
+
+ AP_INIT_TAKE2("QS_LocRequestPerSecLimitMatch", qos_match_rs_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocRequestPerSecLimitMatch <regex> <number>, defines the allowed"
+ " number of requests per second to the uri (path and query) pattern."
+ " Requests are limited by adding a delay to each requests."
+ " This directive should be used in conjunction with"
+ " QS_LocRequestLimitMatch only."),
+
+ AP_INIT_TAKE2("QS_LocKBytesPerSecLimitMatch", qos_match_bs_cmd, NULL,
+ RSRC_CONF,
+ "QS_LocKBytesPerSecLimitMatch <regex> <kbytes>, defines the allowed"
+ " download bandwidth to the location matching the defined URL (path"
+ " and query) pattern. Responses are slowed down"
+ " by adding a delay to each response (non-linear, bigger files"
+ " get longer delay than smaller ones). This directive should be used"
+ " in conjunction with QS_LocRequestLimitMatch only."),
+
+ /* conditional per location */
+ AP_INIT_TAKE3("QS_CondLocRequestLimitMatch", qos_cond_match_con_cmd, NULL,
+ RSRC_CONF,
+ "QS_CondLocRequestLimitMatch <regex> <number> <pattern>, defines the number of"
+ " concurrent requests to the uri (path and query) regex."
+ " Rule is only enforced if the "QS_COND" variable matches the specified"
+ " pattern (regex)."),
+
+ /* event based rules */
+ AP_INIT_TAKE2("QS_EventRequestLimit", qos_event_req_cmd, NULL,
+ RSRC_CONF,
+ "QS_EventRequestLimit <variable>[=<regex>] <number>, defines the"
+ " number of concurrent events. Directive works similar to"
+ " QS_LocRequestLimit, but counts the requests having the same"
+ " environment variable (and optionally matching its value, too)"
+ " rather than those that have the same URL pattern."),
+
+ AP_INIT_TAKE2("QS_EventPerSecLimit", qos_event_rs_cmd, NULL,
+ RSRC_CONF,
+ "QS_EventPerSecLimit [!]<variable> <number>, defines how"
+ " often requests may have the defined environment variable"
+ " (literal string) set. It measures the occurrences of the defined"
+ " environment variable on a request per seconds level and tries to"
+ " limit this occurrence to the defined number. It works similar to"
+ " as QS_LocRequestPerSecLimit, but counts only the requests with the"
+ " specified variable (or without it if the variable name is"
+ " prefixed by a '!'). If a request matches multiple events, the"
+ " rule with the lowest bandwidth is applied. Events are limited"
+ " by adding a delay to each request causing an event."),
+ AP_INIT_TAKE2("QS_EventKBytesPerSecLimit", qos_event_bps_cmd, NULL,
+ RSRC_CONF,
+ "QS_EventKBytesPerSecLimit [!]<variable> <kbytes>, throttles the"
+ " download bandwidth of all requests having the defined"
+ " variable set to the defined kbytes per second. Responses are slowed"
+ " by adding a delay to each response (non-linear, bigger files get"
+ " longer delay than smaller ones). By default, no limitation is active."
+ " This directive should be used in conjunction with QS_EventRequestLimit"
+ " only (you must use the same variable name for both directives)."),
+ AP_INIT_TAKE3("QS_EventLimitCount", qos_event_limit_cmd, NULL,
+ RSRC_CONF,
+ "QS_EventLimitCount <env-variable> <number> <seconds>,"
+ " defines the maximum number of events allowed within the defined"
+ " time. Requests are denied when reaching this limitation for the"
+ " specified time (blocked at request level)."),
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_CondEventLimitCount", qos_cond_event_limit_cmd, NULL,
+ RSRC_CONF,
+ "QS_CondEventLimitCount <env-variable> <number> <seconds> <pattern>,"
+ " same as QS_EventLimitCount but blocks requests only if the "QS_COND
+ " variable matches the specified pattern (regex)."),
+#endif
+
+ /* server / connection limitation */
+ AP_INIT_TAKE1("QS_SrvMaxConn", qos_max_conn_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMaxConn <number>, defines the maximum number of concurrent"
+ " TCP connections for this server (virtual host)."),
+
+ AP_INIT_TAKE1("QS_SrvMaxConnClose", qos_max_conn_close_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMaxConnClose <number>[%], defines the maximum number of"
+ " concurrent TCP connections until the server disables"
+ " keep-alive for this server (closes the connection after"
+ " each requests. You may specify the number of connections"
+ " as a percentage of MaxClients if adding the suffix '%'"
+ " to the specified value."),
+
+ AP_INIT_TAKE12("QS_SrvMaxConnPerIP", qos_max_conn_ip_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMaxConnPerIP <number> [<connections>], defines the maximum number"
+ " of connections per source IP address for this server (virtual host)."
+ " 'connections' defines the number of busy connections of the server"
+ " (all virtual hosts) to enable this limitation, default is 0."),
+
+ AP_INIT_TAKE1("QS_SrvMaxConnExcludeIP", qos_max_conn_ex_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMaxConnExcludeIP <addr>, excludes an IP address or"
+ " address range from being limited."),
+
+ AP_INIT_FLAG("QS_SrvMaxConnPerIPIgnoreVIP", qos_max_conn_ip_vip_off_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMinDataRateIgnoreVIP tells the QS_SrvMaxConnPerIP"
+ " directive to ignore (if set to \"on\") the VIP status"
+ " of clients. Default is \"off\", which means that"
+ " QS_SrvMaxConnPerIP is disabled for VIPs."),
+
+ AP_INIT_TAKE12("QS_SrvSerialize", qos_serialize_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvSerialize 'on'|'off' [<seconds>], ensures that not more than one request"
+ " having the "QS_SRVSERIALIZE" variable set is processed"
+ " at the same time by serializing them (process one after"
+ " each other)."),
+
+#if APR_HAS_THREADS
+ AP_INIT_NO_ARGS("QS_SrvDataRateOff", qos_req_rate_off_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvDataRateOff,"
+ " disables the QS_SrvRequestRate and QS_SrvMinDataRate enforcement for"
+ " a virtual host (only port/address based but not for name based"
+ " virtual hosts)."),
+
+ AP_INIT_TAKE12("QS_SrvRequestRate", qos_req_rate_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvRequestRate <bytes per seconds> [<max bytes per second>],"
+ " defines the minimum upload"
+ " throughput a client must generate. See also QS_SrvMinDataRate."),
+
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_SrvMinDataRate", qos_min_rate_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMinDataRate <bytes per seconds> [<max bytes per second> [<connections>]],"
+ " defines the minimum upload/download"
+ " throughput a client must generate (the bytes send/received by the client"
+ " per seconds). This bandwidth is measured while transmitting the data"
+ " (request line, header fields, request body, or response data). The"
+ " client connection get closed if the client does not fulfill the"
+ " required data rate and the IP address of the causing client get marked"
+ " in order to be handled with low priority (see the QS_ClientPrefer"
+ " directive)."
+ " The \"max bytes per second\" activates dynamic"
+ " minimum throughput control: The required minimal throughput"
+ " is increased in parallel to the number of concurrent clients"
+ " sending/receiving data. The \"max bytes per second\""
+ " setting is reached when the number of sending/receiving"
+ " clients is equal to the MaxClients setting."
+ " The \"connections\" argument is used to specify the"
+ " number of busy TCP connections a server must have to"
+ " enable this feature (0 by default)."
+ " No limitation is set by default."),
+#else
+ AP_INIT_TAKE12("QS_SrvMinDataRate", qos_min_rate_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMinDataRate <bytes per seconds> [<max bytes per second>],"
+ " defines the minimum upload/download throughput"
+ " a client must generate (the bytes send/received by the client"
+ " per seconds). This bandwidth is measured while transmitting the data"
+ " (request line, header fields, request body, or response data). The"
+ " client connection get closed if the client does not fulfill the"
+ " required data rate and the IP address of the causing client get marked"
+ " in order to be handled with low priority (see the QS_ClientPrefer"
+ " directive)."
+ " The \"max bytes per second\" activates dynamic"
+ " minimum throughput control: The required minimal throughput"
+ " is increased in parallel to the number of concurrent clients"
+ " sending/receiving data. The \"max bytes per second\""
+ " setting is reached when the number of sending/receiving"
+ " clients is equal to the MaxClients setting."
+ " No limitation is set by default."),
+#endif // ARGV
+ AP_INIT_TAKE1("QS_SrvMinDataRateOffEvent", qos_min_rate_off_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_SrvMinDataRateOffEvent '+'|'-'<env-variable>,"
+ " disables the minimal data rate enfocement (QS_SrvMinDataRate)"
+ " for a certain connection if the defined environment variable"
+ " has been set. The '+' prefix is used to add a variable"
+ " to the configuration while the '-' prefix is used"
+ " to remove a variable."),
+
+ AP_INIT_FLAG("QS_SrvMinDataRateIgnoreVIP", qos_min_rate_vip_off_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvMinDataRateIgnoreVIP tells the QS_SrvMinDataRate"
+ " directive to ignore (if set to \"on\") the VIP status"
+ " of clients. Default is \"off\", which means that"
+ " QS_SrvMinDataRate is disabled for VIPs."),
+
+#endif // has threads
+
+ AP_INIT_TAKE1("QS_SrvSampleRate", qos_req_rate_tm_cmd, NULL,
+ RSRC_CONF,
+ "QS_SrvSampleRate <seconds>, defines the sampling rate used"
+ " by the QS_SrvMinDataRate directive to measure the"
+ " throughput of a connection."),
+
+ /* generic request filter */
+ AP_INIT_TAKE3("QS_DenyRequestLine", qos_deny_rql_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyRequestLine '+'|'-'<id> 'log'|'deny' <regular expression>, generic"
+ " request line (method, path, query and protocol) filter used"
+ " to deny access for requests matching the defined regular expression."
+ " '+' adds a new rule while '-' removes a rule for a location."
+ " The action is either 'log' (access is granted but rule"
+ " match is logged) or 'deny' (access is denied)."),
+
+ AP_INIT_TAKE3("QS_DenyPath", qos_deny_path_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyPath, same as QS_DenyRequestLine but applied to the"
+ " path only."),
+
+ AP_INIT_TAKE3("QS_DenyQuery", qos_deny_query_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyQuery, same as QS_DenyRequestLine but applied to the"
+ " query only."),
+
+ AP_INIT_TAKE3("QS_DenyEvent", qos_deny_event_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyEvent '+'|'-'<id> 'log'|'deny' [!]<variable>, matches"
+ " requests having the defined process"
+ " environment variable set (or NOT set if prefixed by a '!')."
+ " The action taken for matching rules"
+ " is either 'log' (access is granted but the rule match is"
+ " logged) or 'deny' (access is denied)."),
+
+ AP_INIT_TAKE3("QS_PermitUri", qos_permit_uri_cmd, NULL,
+ ACCESS_CONF,
+ "QS_PermitUri, '+'|'-'<id> 'log'|'deny' <regular expression>, generic"
+ " request filter applied to the request uri (path and query)."
+ " Only requests matching at least one QS_PermitUri pattern are"
+ " allowed. If a QS_PermitUri pattern has been defined an the"
+ " request does not match any rule, the request is denied albeit of"
+ " any server resource availability (allow list). All rules"
+ " must define the same action. Regular expression is case sensitive."),
+
+ AP_INIT_FLAG("QS_DenyBody", qos_denybody_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyBody 'on'|'off', enabled body data filter (obsolete)."),
+
+ AP_INIT_FLAG("QS_DenyQueryBody", qos_denybody_d_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyQueryBody 'on'|'off', enabled body data filter for QS_DenyQuery."),
+
+ AP_INIT_FLAG("QS_PermitUriBody", qos_denybody_p_cmd, NULL,
+ ACCESS_CONF,
+ "QS_PermitUriBody 'on'|'off', enabled body data filter for QS_PermitUriBody."),
+
+ AP_INIT_TAKE1("QS_InvalidUrlEncoding", qos_deny_urlenc_cmd, NULL,
+ ACCESS_CONF,
+ "QS_InvalidUrlEncoding 'log'|'deny'|'off',"
+ " enforces correct URL decoding in conjunction with the"
+ " QS_DenyRequestLine, QS_DenyPath, and QS_DenyQuery"
+ " directives. Default is \"off\"."),
+
+ AP_INIT_TAKE1("QS_LimitRequestBody", qos_maxpost_cmd, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "QS_LimitRequestBody <bytes>, limits the allowed size"
+ " of an HTTP request message body."),
+
+ AP_INIT_ITERATE("QS_Decoding", qos_dec_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyDecoding 'uni', enabled additional string decoding"
+ " functions which are applied before"
+ " matching QS_Deny* and QS_Permit* directives."
+ " Default is URL decoding (%xx, \\xHH, '+')."),
+
+ AP_INIT_NO_ARGS("QS_DenyInheritanceOff", qos_denyinheritoff_cmd, NULL,
+ ACCESS_CONF,
+ "QS_DenyInheritanceOff, disable inheritance of QS_Deny* and QS_Permit*"
+ " directives to a location."),
+
+ AP_INIT_TAKE1("QS_RequestHeaderFilter", qos_headerfilter_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_RequestHeaderFilter 'on'|'off'|'size', filters request headers by allowing"
+ " only these headers which match the request header rules defined by"
+ " mod_qos. Request headers which do not conform these definitions"
+ " are either dropped or the whole request is denied. Custom"
+ " request headers may be added by the QS_RequestHeaderFilterRule"
+ " directive. Using the 'size' option, the header field max. size"
+ " is verified only (similar to LimitRequestFieldsize but using"
+ " individual values for each header type) while the pattern is ignored."),
+
+ AP_INIT_TAKE1("QS_ResponseHeaderFilter", qos_resheaderfilter_cmd, NULL,
+ ACCESS_CONF,
+ "QS_ResponseHeaderFilter 'on'|'off', filters response headers by allowing"
+ " only these headers which match the response header rules defined by"
+ " mod_qos. Response headers which do not conform these definitions"
+ " are dropped."),
+
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_RequestHeaderFilterRule", qos_headerfilter_rule_cmd, NULL,
+ RSRC_CONF,
+ "QS_RequestHeaderFilterRule <header name> 'drop'|'deny' <regular expression> <size>, used"
+ " to add custom request header filter rules which override the internal"
+ " filter rules of mod_qos."
+ " Directive is allowed in global server context only."),
+#else
+ AP_INIT_TAKE3("QS_RequestHeaderFilterRule", qos_headerfilter_rule_cmd, NULL,
+ RSRC_CONF,
+ "QS_RequestHeaderFilterRule <header name> 'drop'|'deny' <regular expression>, used"
+ " to add custom request header filter rules which override the internal"
+ " filter rules of mod_qos."
+ " Directive is allowed in global server context only."),
+#endif
+
+ AP_INIT_TAKE3("QS_ResponseHeaderFilterRule", qos_resheaderfilter_rule_cmd, NULL,
+ RSRC_CONF,
+ "QS_ResponseHeaderFilterRule <header name> <regular expression> <size>, used"
+ " to add custom response header filter rules which override the internal"
+ " filter rules of mod_qos."
+ " Directive is allowed in global server context only."),
+
+ /* milestones */
+ AP_INIT_TAKE23("QS_MileStone", qos_milestone_cmd, NULL,
+ RSRC_CONF,
+ "QS_MileStone 'log'|'deny' <pattern> [<thinktime>], defines request line patterns"
+ " a client must access in the defined order as they are defined in the"
+ " configuration file."),
+
+ AP_INIT_TAKE1("QS_MileStoneTimeout", qos_milestone_tmo_cmd, NULL,
+ RSRC_CONF,
+ "QS_MileStoneTimeout <seconds>, defines the time in seconds"
+ " within a client must reach the next milestone."
+ " Default are 3600 seconds."),
+
+ /* session / vip */
+ AP_INIT_TAKE1("QS_SessionCookieName", qos_cookie_name_cmd, NULL,
+ RSRC_CONF,
+ "QS_SessionCookieName <name>, defines a custom session cookie name,"
+ " default is "QOS_COOKIE_NAME"."),
+
+ AP_INIT_TAKE1("QS_SessionCookiePath", qos_cookie_path_cmd, NULL,
+ RSRC_CONF,
+ "QS_SessionCookiePath <path>, defines the cookie path, default is \"/\"."),
+
+ AP_INIT_TAKE1("QS_SessionTimeout", qos_timeout_cmd, NULL,
+ RSRC_CONF,
+ "QS_SessionTimeout <seconds>, defines the session life time for a VIP."
+ " It is only used for session based (cookie) VIP identification (not"
+ " for IP based). Default is "QOS_MAX_AGE" seconds."),
+
+ AP_INIT_TAKE1("QS_SessionKey", qos_key_cmd, NULL,
+ RSRC_CONF,
+ "QS_SessionKey <string>, secret key used for cookie encryption."
+ " Used when using the same session cookie for multiple web servers"
+ " (load balancing) or sessions should survive a server restart."
+ " By default, a random key is used which changes every server restart."),
+
+ AP_INIT_TAKE12("QS_VipHeaderName", qos_header_name_cmd, NULL,
+ RSRC_CONF,
+ "QS_VipHeaderName <name>[=<regex>] [drop], defines an HTTP"
+ " response header which marks a user as a VIP. mod_qos"
+ " creates a session for this user by setting a cookie,"
+ " e.g., after successful user authentication. Tests"
+ " optionally its value against the provided regular"
+ " expression. Specify the action 'drop' if you want mod_qos"
+ " to remove this control header from the HTTP response."),
+
+ AP_INIT_TAKE12("QS_VipIPHeaderName", qos_ip_header_name_cmd, NULL,
+ RSRC_CONF,
+ "QS_VipIPHeaderName <name>[=<regex>] [drop], defines an HTTP"
+ " response header which marks a client source IP address as"
+ " a VIP. Tests optionally its value against the provided"
+ " regular expression."
+ " Specify the action 'drop' if you want mod_qos to remove"
+ " this control header from the HTTP response."),
+
+ AP_INIT_NO_ARGS("QS_VipUser", qos_vip_u_cmd, NULL,
+ RSRC_CONF,
+ "QS_VipUser, creates a VIP session for users which have been"
+ " authenticated by the Apache server, e.g., by the standard"
+ " mod_auth* modules. It works similar to the"
+ " QS_VipHeaderName directive."),
+
+ AP_INIT_NO_ARGS("QS_VipIpUser", qos_vip_ip_u_cmd, NULL,
+ RSRC_CONF,
+ "QS_VipIpUser, marks a source IP address as a VIP if the"
+ " user has been authenticated by the Apache server, e.g."
+ " by the standard mod_auth* modules. It works similar to"
+ " the QS_VipIPHeaderName directive."),
+
+ /* user tracking */
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_UserTrackingCookieName", qos_user_tracking_cookie_cmd, NULL,
+ RSRC_CONF,
+ "QS_UserTrackingCookieName <name> [<path>] [<domain>] ['session'] ['jsredirect'],"
+ " enables the user tracking cookie by defining a cookie"
+ " name. The \"path\" parameter is an option cookie"
+ " check page which is used to ensure the client accepts"
+ " cookies. The \"domain\" option defines the Domain attriibute"
+ " for the Set-Cookie header. The option \"session\" indicates"
+ " that the cookie shall be a session cookie expiring when the"
+ " user closes it's browser."
+ " User tracking requires mod_unique_id."
+ " This feature is disabled by default."
+ " Ignores QS_LogOnly."),
+#else
+ AP_INIT_TAKE123("QS_UserTrackingCookieName", qos_user_tracking_cookie_cmd, NULL,
+ RSRC_CONF,
+ "QS_UserTrackingCookieName <name> [<path>] ['session'],"
+ " enables the user tracking cookie by defining a cookie"
+ " name. The \"path\" parameter is an option cookie"
+ " check page which is used to ensure the client accepts"
+ " cookies. The option \"session\" indicates that the"
+ " cookie shall be a session cookie expiring when the"
+ " user closes it's browser."
+ " User tracking requires mod_unique_id."
+ " This feature is disabled by default."
+ " Ignores QS_LogOnly."),
+#endif
+
+ /* env vars */
+ AP_INIT_TAKE23("QS_SetEnvIf", qos_event_setenvif_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_SetEnvIf [!]<variable1>[=<regex>] [[!]<variable2>] [!]<variable=value>,"
+ " sets (or unsets) the 'variable=value' (literal string) if"
+ " variable1 (literal string) AND variable2 (literal string)"
+ " are set in the request environment variable list (not case"
+ " sensitive). This is used to combine multiple variables"
+ " to a new event type. Alternatively, a regular expression"
+ " can be specified for variable1's value and variable2 must be"
+ " omitted in order to simply set a new variable if"
+ " the regular expression matches."),
+
+ AP_INIT_TAKE_ARGV("QS_SetEnvIfCmp", qos_cmp_cmd, NULL,
+ ACCESS_CONF,
+ "QS_SetEnvIfCmpP <env-variable1> eq|ne|gt|lt <env-variable2> [!]<env-variable>[=<value>],"
+ " sets the specified environment variable if the specified env-variables"
+ " are alphabetically or numerical equal (eq), not equal (ne),"
+ " greater (gt), less (lt)."),
+
+ AP_INIT_TAKE2("QS_SetEnvIfQuery", qos_event_setenvifquery_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_SetEnvIfQuery <regex> [!]<variable>[=value],"
+ " directive works quite similar to the SetEnvIf directive"
+ " of the Apache module mod_setenvif, but the specified regex"
+ " is applied against the query string portion of the request"
+ " line. The directive recognizes the occurrences of $1..$9"
+ " within value and replaces them by the sub-expressions of"
+ " the defined regex pattern."),
+
+ AP_INIT_TAKE2("QS_SetEnvIfParp", qos_event_setenvifparp_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnvIfParp <regex> [!]<variable>[=value],"
+ " directive parsing the request payload using the Apache module"
+ " mod_parp. It matches the request URL query and the HTTP"
+ " request message body data as well ('application/x-www-form-urlencoded',"
+ " 'multipart/form-data', and 'multipart/mixed') and sets the defined"
+ " process variable (quite similar to the QS_SetEnvIfQuery directive)."
+ " The directive recognizes the occurrences of $1..$9 within value"
+ " and replaces them by the sub-expressions of the defined regex"
+ " pattern. This directive activates mod_parp for every request to"
+ " the virtual host. You may deactivate mod_parp for selected requests"
+ " using the SetEnvIf directive: unset the variable 'parp' to do so."
+ " Important: request message body processing requires that the server"
+ " loads the whole request into its memory (at least twice the length"
+ " of the message). You should limit the allowed size of the HTTP"
+ " request message body using the QS_LimitRequestBody directive"
+ " when using QS_SetEnvIfParp!"),
+
+ AP_INIT_TAKE2("QS_SetEnvIfBody", qos_event_setenvifparpbody_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnvIfBody <regex> [!]<variable>[=value],"
+ " parses the request body using the Apache module mod_parp."
+ " Specify the content types to process using the mod_parp"
+ " directive PARP_BodyData and ensure that mod_parp is enabled"
+ " using the SetEnvIf directive of the Apache module mod_setenvif."
+ " You should limit the allowed size of HTTP requests message body"
+ " using the QS_LimitRequestBody directive when using mod_parp."
+ " The directive recognizes the occurrence of $1 within the variable"
+ " value and replaces it by the sub-expressions of the defined regex"
+ " pattern."),
+
+ AP_INIT_TAKE2("QS_SetEnvStatus", qos_event_setenvifstatus_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_SetEnvStatus (deprecated, use QS_SetEnvIfStatus)"),
+
+ AP_INIT_TAKE2("QS_SetEnvIfStatus", qos_event_setenvifstatus_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_SetEnvIfStatus <status code> <variable>, adds the defined"
+ " request environment variable if the HTTP status code matches the"
+ " defined value. The value '"QS_CLOSE"' may be used as a special"
+ " status code to set a "QS_BLOCK" event in order to handle"
+ " connection close events caused by "QS_CLOSE" rules while"
+ " the status '"QS_EMPTY_CON"' may be used to mark connections"
+ " which are closed before any HTTP request has ever been received."
+ " The '"QS_MAXIP"' value may be used to count "QS_BLOCK" events for"
+ " connections closed by the "QS_MAXIP" directive."
+ " The '"QS_BROKEN_CON"' value may be used to mark clients not"
+ " reading the full HTTP response."),
+
+ AP_INIT_TAKE2("QS_SetEnvResBody", qos_event_setenvifresbody_cmd, NULL,
+ ACCESS_CONF,
+ "QS_SetEnvResBody (deprecated, use QS_SetEnvIfResBody)"),
+
+ AP_INIT_TAKE2("QS_SetEnvIfResBody", qos_event_setenvifresbody_cmd, NULL,
+ ACCESS_CONF,
+ "QS_SetEnvIfResBody <string> [!]<variable>, adds the defined"
+ " request environment variable (e.g. "QS_BLOCK") if the HTTP"
+ " response body contains the defined literal string."
+ " Supports only one pattern per location."),
+
+ AP_INIT_TAKE2("QS_SetEnv", qos_setenv_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnv <variable> <value>, sets the defined variable"
+ " with the value where the value string may contain"
+ " other environment variables surrounded by \"${\" and \"}\"."
+ " The variable is only set if all defined variables within"
+ " the value can be resolved."),
+
+ AP_INIT_TAKE23("QS_SetReqHeader", qos_setreqheader_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetReqHeader [!]<header name> <variable> ['late'], sets the defined"
+ " HTTP request header to the request if the specified"
+ " environment variable is set."),
+
+ AP_INIT_TAKE1("QS_UnsetReqHeader", qos_unsetreqheader_cmd, NULL,
+ RSRC_CONF,
+ "QS_UnsetReqHeader <header name>, Removes the specified header from the request."),
+
+ AP_INIT_TAKE1("QS_UnsetResHeader", qos_unsetresheader_cmd, NULL,
+ RSRC_CONF,
+ "QS_UnsetResHeader <header name>, Removes the specified header from the response."),
+
+ AP_INIT_TAKE12("QS_SetEnvResHeader", qos_event_setenvresheader_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnvResHeader <header name> [drop], sets the defined"
+ " HTTP response header (name and value) to the request environment variables"
+ " Deletes the header if the action 'drop' has been specified."),
+
+ AP_INIT_TAKE2("QS_SetEnvResHeaderMatch", qos_event_setenvresheadermatch_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnvResHeaderMatch <header name> <regex>, sets the defined"
+ " HTTP response header (name and value) to the request environment variables"
+ " if the specified regular expression matches the header value."),
+
+ AP_INIT_TAKE3("QS_SetEnvRes", qos_setenvres_cmd, NULL,
+ RSRC_CONF,
+ "QS_SetEnvRes <variable> <regex> <variable2>[=<value>], sets the environment"
+ " variable2 if the regular expression matches against the value of"
+ " the environment variable. Occurrences of $1..$9 within the value"
+ " and replace them by parenthesized subexpressions of the regular expression."),
+
+ AP_INIT_TAKE3("QS_RedirectIf", qos_redirectif_cmd, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "QS_RedirectIf <variable> <regex> [<code>:]<url>,"
+ " redirects the client to the configured url"
+ " if the regular expression matches"
+ " the value of the the environment variable."),
+
+ /* client control */
+ AP_INIT_TAKE1("QS_ClientEntries", qos_client_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEntries <number>, defines the number of individual"
+ " clients managed by mod_qos. Default is 50000."
+ " Directive is allowed in global server context only."),
+
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_ClientPrefer", qos_client_pref_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientPrefer [<percent>], prefers known VIP clients"
+ " when server has less than 80% (or the configured value)"
+ " of free TCP connections. Preferred clients"
+ " are VIP clients (or those without any negative penalties),"
+ " see QS_VipHeaderName directive."
+ " Directive is allowed in global server context only."),
+#else
+ AP_INIT_NO_ARGS("QS_ClientPrefer", qos_client_pref_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientPrefer, prefers known VIP clients"
+ " when server has less than 80% of free TCP connections."
+ " Preferred clients are VIP clients only,"
+ " see QS_VipHeaderName directive."
+ " Directive is allowed in global server context only."),
+#endif
+
+ AP_INIT_TAKE1("QS_ClientTolerance", qos_client_tolerance_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientTolerance <percent>, defines the allowed tolerance (variation)"
+ " from a \"normal\" client (average) in percent."
+ " Default is "QOS_CC_BEHAVIOR_TOLERANCE_STR"%."
+ " Directive is allowed in global server context only."),
+
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_ClientContentTypes", qos_client_contenttype, NULL,
+ RSRC_CONF,
+ "QS_ClientContentTypes <html> <css/js> <images> <other> <304>,"
+ " defines the distribution of HTTP response content types a client normally"
+ " receives when accessing the server. mod_qos normally learns the average"
+ " behavior automatically by default but you may specify a static configuration"
+ " in order to avoid influences by a high number of abnormal clients."),
+#endif
+
+ AP_INIT_TAKE12("QS_ClientEventBlockCount", qos_client_block_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEventBlockCount <number> [<seconds>], defines the maximum number"
+ " of "QS_BLOCK" allowed within the defined time (default are 10 minutes)."
+ " Directive is allowed in global server context only."),
+
+ AP_INIT_TAKE1("QS_ClientEventBlockExcludeIP", qos_client_ex_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEventBlockExcludeIP <addr>, excludes an IP address or"
+ " address range from being limited by QS_ClientEventBlockCount."),
+
+ AP_INIT_TAKE123("QS_ClientEventLimitCount", qos_client_limit_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEventLimitCount <number> [<seconds> [<variable>]],"
+ " defines the maximum number"
+ " of the specified environment variable ("QS_LIMIT_DEFAULT" by default)"
+ " allowed within the defined time (default are 10 minutes)."
+ " Directive is allowed in global server context only."),
+
+#ifdef AP_TAKE_ARGV
+ AP_INIT_TAKE_ARGV("QS_CondClientEventLimitCount", qos_cond_client_limit_cmd, NULL,
+ RSRC_CONF,
+ "QS_CondClientEventLimitCount <number> <seconds> <variable> <pattern>,"
+ " defines the maximum number"
+ " of the specified environment variable"
+ " allowed within the defined time."
+ " Directive works similar as QS_ClientEventLimitCount but"
+ " requests are only blocked if the "QS_COND" variable matches"
+ " the defined pattern (regex)."
+ " Directive is allowed in global server context only."),
+#endif
+
+ AP_INIT_TAKE1("QS_ClientEventPerSecLimit", qos_client_event_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEventPerSecLimit <number>, defines the number"
+ " events pro seconds on a per client (source IP) basis."
+ " Events are identified by requests having the"
+ " "QS_EVENT" variable set."
+ " Directive is allowed in global server context only."),
+
+ AP_INIT_TAKE1("QS_ClientEventRequestLimit", qos_client_event_req_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientEventRequestLimit <number>, defines the allowed"
+ " number of concurrent requests coming from the same client"
+ " source IP address"
+ " having the QS_EventRequest variable set."
+ " Directive is allowed in global server context only."),
+
+ AP_INIT_NO_ARGS("QS_ClientSerialize", qos_client_serial_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientSerialize, serializes requests having the "QS_SERIALIZE" variable"
+ " set if they are coming from the same IP address."),
+
+ AP_INIT_TAKE1("QS_ClientIpFromHeader", qos_client_forwardedfor_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientIpFromHeader <header>, defines a HTTP request header to read"
+ " the client's source IP address from (instead of taking the IP address"
+ " of the client opening the TCP connection). This may be used for the"
+ " QS_ClientEventLimitCount directive and QS_Country variable."),
+
+ /* geo ip */
+ AP_INIT_TAKE1("QS_ClientGeoCountryDB", qos_geodb_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientGeoCountryDB <path>, path to the geograpical database file."),
+
+ AP_INIT_TAKE23("QS_ClientGeoCountryPriv", qos_geopriv_cmd, NULL,
+ RSRC_CONF,
+ "QS_ClientGeoCountryPriv <list> <connections> ['excludeUnknown'],"
+ " defines a comma separated list of country codes"
+ " for origin client IP address which are allowed to"
+ " access the server if the number of busy TCP connections reaches"
+ " the defined number of connections while others are denied access."
+ " Clients whose IP can't be mapped to a country code can be excluded"
+ " from the limitation by configuring the 'excludeUnknown' argument."),
+
+ /* error documents */
+ AP_INIT_TAKE1("QS_ErrorPage", qos_error_page_cmd, NULL,
+ RSRC_CONF,
+ "QS_ErrorPage <url>, defines a custom error page."),
+
+ AP_INIT_TAKE1("QS_ErrorResponseCode", qos_error_code_cmd, NULL,
+ RSRC_CONF,
+ "QS_ErrorResponseCode <code>, defines the HTTP response code which"
+ " is used when a request is denied, default is 500."),
+
+ AP_INIT_FLAG("QS_ForcedClose", qos_forced_close_cmd, NULL,
+ RSRC_CONF,
+ "QS_ForcedClose 'on'|'off', defines if mod_qos connection handler shall"
+ " exit with an error code (on) or not. Default is on (except for"
+ " Apache 2.4.49)."),
+
+ /* module settings / various stuff */
+ AP_INIT_FLAG("QS_LogOnly", qos_logonly_cmd, NULL,
+ RSRC_CONF,
+ "QS_LogOnly 'on'|'off', enables the log only mode of the module"
+ " where no limitations are enforced. Default is off."
+ " Directive is allowed in global server context only."),
+
+ AP_INIT_FLAG("QS_LogEnv", qos_logenv_cmd, NULL,
+ RSRC_CONF,
+ "QS_LogEnv 'on'|'off', enables logging of environment"
+ " variables."),
+
+ AP_INIT_FLAG("QS_SupportIPv6", qos_enable_ipv6_cmd, NULL,
+ RSRC_CONF,
+ "QS_SupportIPv6 'on'|'off', enables IPv6 address support."
+ " Default is on."),
+
+ AP_INIT_TAKE1("QS_SemMemFile", qos_mfile_cmd, NULL,
+ RSRC_CONF,
+ "QS_SemMemFile <path>, optional path to a directory or file"
+ " which shall be used for file based semaphores/shared memory"
+ " usage, e.g. /var/tmp."),
+
+ AP_INIT_TAKE1("QS_MaxClients", qos_maxclients_cmd, NULL,
+ RSRC_CONF,
+ "QS_MaxClients <number>, optional override for mod_qos's"
+ " MaxClients/MaxRequestWorkers calculation which defines"
+ " the maximum number of TCP connections the server can handle."),
+
+ AP_INIT_FLAG("QS_DisableHandler", qos_disable_handler_cmd, NULL,
+ RSRC_CONF,
+ "QS_DisableHandler 'on'|'off', disables the qos-viewer"
+ " and qos-console for a virtual host"),
+
+#if APR_HAS_THREADS
+ AP_INIT_FLAG("QS_Status", qos_qsstatus_cmd, NULL,
+ RSRC_CONF,
+ "QS_Status 'on'|'off', writes a log message containing server"
+ " statistics once every minute. Default is off."),
+#endif
+
+ AP_INIT_FLAG("QS_EventCount", qos_qsevents_cmd, NULL,
+ RSRC_CONF,
+ "QS_EventCount 'on'|'off', enables error event counting"
+ " (counters are shown in the machine-readable version"
+ " of the status viewer). Default is off."),
+
+ AP_INIT_TAKE1("QSLog", qos_qlog_cmd, NULL,
+ RSRC_CONF,
+ "QSLog <arg>, used to configure a global (per Apache"
+ " instance) 'qslog' logger."),
+
+#ifdef QS_INTERNAL_TEST
+ AP_INIT_TAKE1("QS_EnableInternalIPSimulation", qos_disable_int_ip_cmd, NULL,
+ RSRC_CONF,
+ ""),
+#endif
+
+ { NULL }
+};
+
+
+/************************************************************************
+ * apache register
+ ***********************************************************************/
+static void qos_register_hooks(apr_pool_t * p) {
+ static const char *pre[] = { "mod_setenvif.c", "mod_setenvifplus.c", "mod_parp.c", NULL };
+ static const char *preuid[] = { "mod_setenvif.c", "mod_setenvifplus.c", "mod_parp.c", "mod_unique_id.c", NULL };
+ static const char *pressl[] = { "mod_ssl.c", NULL };
+ static const char *preconf[] = { "mod_setenvif.c", "mod_setenvifplus.c", "mod_parp.c", "mod_ssl.c", NULL };
+ static const char *post[] = { "mod_setenvif.c", "mod_setenvifplus.c", NULL };
+ static const char *postlog[] = { "mod_logio.c", NULL };
+ static const char *parp[] = { "mod_parp.c", NULL };
+ static const char *prelast[] = { "mod_setenvif.c", "mod_setenvifplus.c", "mod_ssl.c", NULL };
+ static const char *preFix[] = { "mod_ssl.c", "mod_setenvifplus.c", NULL };
+
+ ap_hook_post_config(qos_post_config, preconf, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_child_init(qos_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // before ssl_hook_pre_connection@APR_HOOK_MIDDLE but after logio_pre_conn@APR_HOOK_MIDDLE
+ ap_hook_pre_connection(qos_pre_connection, postlog, pressl, APR_HOOK_MIDDLE);
+ // after ssl_hook_pre_connection@APR_HOOK_MIDDLE (and a many others)
+ ap_hook_pre_connection(qos_pre_process_connection, prelast, NULL, APR_HOOK_LAST);
+
+ ap_hook_process_connection(qos_process_connection, NULL, NULL, APR_HOOK_MIDDLE);
+
+ // be before sp_post_read_request@APR_HOOK_MIDDLE
+ ap_hook_post_read_request(qos_post_read_request, NULL, post, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(qos_post_read_request_later, preuid, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_header_parser(qos_header_parser0, NULL, post, APR_HOOK_FIRST);
+ ap_hook_header_parser(qos_header_parser1, post, parp, APR_HOOK_FIRST);
+ ap_hook_header_parser(qos_header_parser, pre, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_fixups(qos_fixup, preFix, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_handler(qos_handler, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_log_transaction(qos_logger, NULL, NULL, APR_HOOK_FIRST);
+ //ap_hook_error_log(qos_error_log, NULL, NULL, APR_HOOK_LAST);
+
+ ap_register_input_filter("qos-in-filter", qos_in_filter, NULL, AP_FTYPE_CONNECTION);
+ ap_register_input_filter("qos-in-filter2", qos_in_filter2, NULL, AP_FTYPE_RESOURCE);
+ ap_register_input_filter("qos-in-filter3", qos_in_filter3, NULL, AP_FTYPE_CONTENT_SET);
+ /* AP_FTYPE_RESOURCE+1 ensures the filter is executed after mod_setenvifplus
+ * AP_FTYPE_PROTOCOL+3 ensures the filter is executed after mod_deflate */
+ ap_register_output_filter("qos-out-filter", qos_out_filter, NULL, AP_FTYPE_RESOURCE+1);
+ ap_register_output_filter("qos-out-filter-min", qos_out_filter_min, NULL, AP_FTYPE_RESOURCE+1);
+ ap_register_output_filter("qos-out-filter-delay", qos_out_filter_delay, NULL, AP_FTYPE_PROTOCOL+3);
+ ap_register_output_filter("qos-out-filter-body", qos_out_filter_body, NULL, AP_FTYPE_RESOURCE+1);
+ ap_register_output_filter("qos-out-filter-brokencon", qos_out_filter_brokencon, NULL, AP_FTYPE_PROTOCOL+3);
+ ap_register_output_filter("qos-out-err-filter", qos_out_err_filter, NULL, AP_FTYPE_RESOURCE+1);
+
+ ap_hook_insert_filter(qos_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_error_filter(qos_insert_err_filter, NULL, NULL, APR_HOOK_MIDDLE);
+
+}
+
+/************************************************************************
+ * apache module definition
+ ***********************************************************************/
+module AP_MODULE_DECLARE_DATA qos_module ={
+ STANDARD20_MODULE_STUFF,
+ qos_dir_config_create, /**< dir config creator */
+ qos_dir_config_merge, /**< dir merger */
+ qos_srv_config_create, /**< server config */
+ qos_srv_config_merge, /**< server merger */
+ qos_config_cmds, /**< command table */
+ qos_register_hooks, /**< hook registration */
+};
diff --git a/apache2/mod_qos.h b/apache2/mod_qos.h
new file mode 100644
index 0000000..3372abe
--- /dev/null
+++ b/apache2/mod_qos.h
@@ -0,0 +1,79 @@
+/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 2; -*-
+ */
+
+/**
+ * mod_qos.h: Quality of service module for Apache Web Server.
+ *
+ * The Apache Web Servers requires threads and processes to serve
+ * requests. Each TCP connection to the web server occupies one
+ * thread or process. Sometimes, a server gets too busy to serve
+ * every request due the lack of free processes or threads.
+ *
+ * This module implements control mechanisms that can provide
+ * different priority to different requests.
+ *
+ * See http://mod-qos.sourceforge.net/ for further
+ * details.
+ *
+ * Copyright (C) 2023 Pascal Buchbinder
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef __MOD_QOS_H__
+#define __MOD_QOS_H__
+
+/**************************************************************************
+ * Hooks
+ **************************************************************************/
+#if !defined(WIN32)
+#define QOS_DECLARE(type) type
+#define QOS_DECLARE_NONSTD(type) type
+#define QOS_DECLARE_DATA
+#elif defined(QOS_DECLARE_STATIC)
+#define QOS_DECLARE(type) type __stdcall
+#define QOS_DECLARE_NONSTD(type) type
+#define QOS_DECLARE_DATA
+#elif defined(QOS_DECLARE_EXPORT)
+#define QOS_DECLARE(type) __declspec(dllexport) type __stdcall
+#define QOS_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define QOS_DECLARE_DATA __declspec(dllexport)
+#else
+#define QOS_DECLARE(type) __declspec(dllimport) type __stdcall
+#define QOS_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define QOS_DECLARE_DATA __declspec(dllimport)
+#endif
+
+#define QOS_OPTIONAL_HOOK(name,fn,pre,succ,order) \
+ APR_OPTIONAL_HOOK(qos,name,fn,pre,succ,order)
+
+/**
+ * mod_qos.h header file defining hooks for path/query
+ * decoding (used by QS_Deny* and QS_Permit* rules).
+ *
+ * Define QS_MOD_EXT_HOOKS in order to enable these hooks
+ * within mod_qos.c.
+ */
+
+/* hook to decode/unescape the path portion of the request uri */
+APR_DECLARE_EXTERNAL_HOOK(qos, QOS, apr_status_t, path_decode_hook,
+ (request_rec *r, char **path, int *len))
+/* hook to decode/unescape the query portion of the request uri */
+APR_DECLARE_EXTERNAL_HOOK(qos, QOS, apr_status_t, query_decode_hook,
+ (request_rec *r, char **query, int *len))
+
+#endif /* __MOD_QOS_H__ */