diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-17 03:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-09-17 03:51:28 +0000 |
commit | 2b07c041cb218eca6e548bac9c4347f8a90c474c (patch) | |
tree | 679142f3916fa927903c6f245896f5c0325a3254 /apache2 | |
parent | Initial commit. (diff) | |
download | libapache2-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.in | 10 | ||||
-rw-r--r-- | apache2/config.m4 | 6 | ||||
-rw-r--r-- | apache2/mod_qos.c | 14830 | ||||
-rw-r--r-- | apache2/mod_qos.h | 79 |
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, ©q, &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
0x01 bad pkg rate
0x02 normal behavior
0x04 bad behavior
0x08 blocked
0x10 limited
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 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 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 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\"> </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, " "); + } + 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 %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 </td>" + "<td >current </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 > </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 > </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 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" 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 </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, ¤t_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, ¤t_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__ */ |