From 2b07c041cb218eca6e548bac9c4347f8a90c474c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 17 Sep 2024 05:51:28 +0200 Subject: Adding upstream version 11.74. Signed-off-by: Daniel Baumann --- README.TXT | 29 + apache2/Makefile.in | 10 + apache2/config.m4 | 6 + apache2/mod_qos.c | 14830 +++++++++++++++++++++++++++++ apache2/mod_qos.h | 79 + doc/CHANGES.txt | 1947 ++++ doc/LICENSE.txt | 202 + doc/MESSAGES.txt | 79 + doc/favicon.ico | Bin 0 -> 1406 bytes doc/glossary.html | 603 ++ doc/headerfilterrules.txt | 97 + doc/images/ClientPrefer.png | Bin 0 -> 38948 bytes doc/images/ClosedLoop.png | Bin 0 -> 10852 bytes doc/images/Events.png | Bin 0 -> 35347 bytes doc/images/LimitCount.png | Bin 0 -> 27334 bytes doc/images/LimitCountExample.png | Bin 0 -> 17097 bytes doc/images/QS_ClientEventBlockCount.png | Bin 0 -> 35395 bytes doc/images/Rule.png | Bin 0 -> 15894 bytes doc/images/Serialization.png | Bin 0 -> 29805 bytes doc/images/SrvMinDataRate.png | Bin 0 -> 28390 bytes doc/images/UserTracking.png | Bin 0 -> 64473 bytes doc/images/directive_seq.gif | Bin 0 -> 186238 bytes doc/images/download.jpg | Bin 0 -> 3265 bytes doc/images/link.png | Bin 0 -> 208 bytes doc/images/mod_qos.gif | Bin 0 -> 7839 bytes doc/images/qsloc.png | Bin 0 -> 82611 bytes doc/images/qsloc1.png | Bin 0 -> 17772 bytes doc/images/qsloc2.png | Bin 0 -> 31104 bytes doc/images/qsloc3.png | Bin 0 -> 34926 bytes doc/images/qslogFormat.png | Bin 0 -> 45252 bytes doc/images/qslog_spreadsheet_example.png | Bin 0 -> 224579 bytes doc/index.html | 2751 ++++++ doc/qsdt.1.html | 85 + doc/qsexec.1.html | 72 + doc/qsfilter2.1.html | 127 + doc/qsfilter2_process.gif | Bin 0 -> 14909 bytes doc/qsgeo.1.html | 77 + doc/qsgrep.1.html | 67 + doc/qshead.1.html | 54 + doc/qslog.1.html | 146 + doc/qslogger.1.html | 75 + doc/qspng.1.html | 58 + doc/qsre.1.html | 56 + doc/qsrespeed.1.html | 54 + doc/qsrotate.1.html | 86 + doc/qssign.1.html | 79 + doc/qstail.1.html | 56 + tools/Makefile.am | 2 + tools/Makefile.in | 766 ++ tools/config.h.in | 128 + tools/configure | 6185 ++++++++++++ tools/configure.ac | 88 + tools/depcomp | 791 ++ tools/install-sh | 508 + tools/man1/mod_qos.1 | 198 + tools/man1/qsdt.1 | 46 + tools/man1/qsexec.1 | 36 + tools/man1/qsfilter2.1 | 99 + tools/man1/qsgeo.1 | 39 + tools/man1/qsgrep.1 | 28 + tools/man1/qshead.1 | 16 + tools/man1/qslog.1 | 109 + tools/man1/qslogger.1 | 41 + tools/man1/qspng.1 | 22 + tools/man1/qsre.1 | 19 + tools/man1/qsrespeed.1 | 16 + tools/man1/qsrotate.1 | 50 + tools/man1/qssign.1 | 44 + tools/man1/qstail.1 | 19 + tools/missing | 215 + tools/src/Makefile.am | 49 + tools/src/Makefile.in | 734 ++ tools/src/char.h | 539 ++ tools/src/qs_apo.c | 135 + tools/src/qs_apo.h | 31 + tools/src/qs_util.c | 409 + tools/src/qs_util.h | 98 + tools/src/qscheck.c | 413 + tools/src/qsdt.c | 336 + tools/src/qsexec.c | 376 + tools/src/qsfilter2.c | 1826 ++++ tools/src/qsgeo.c | 607 ++ tools/src/qsgrep.c | 301 + tools/src/qshead.c | 132 + tools/src/qslog.c | 2681 ++++++ tools/src/qslogger.c | 423 + tools/src/qspng.c | 784 ++ tools/src/qsre.c | 226 + tools/src/qsrespeed.c | 308 + tools/src/qsrotate.c | 522 + tools/src/qssign.c | 799 ++ tools/src/qstail.c | 237 + 92 files changed, 43156 insertions(+) create mode 100644 README.TXT create mode 100644 apache2/Makefile.in create mode 100644 apache2/config.m4 create mode 100644 apache2/mod_qos.c create mode 100644 apache2/mod_qos.h create mode 100644 doc/CHANGES.txt create mode 100644 doc/LICENSE.txt create mode 100644 doc/MESSAGES.txt create mode 100644 doc/favicon.ico create mode 100644 doc/glossary.html create mode 100644 doc/headerfilterrules.txt create mode 100644 doc/images/ClientPrefer.png create mode 100644 doc/images/ClosedLoop.png create mode 100644 doc/images/Events.png create mode 100644 doc/images/LimitCount.png create mode 100644 doc/images/LimitCountExample.png create mode 100644 doc/images/QS_ClientEventBlockCount.png create mode 100644 doc/images/Rule.png create mode 100644 doc/images/Serialization.png create mode 100644 doc/images/SrvMinDataRate.png create mode 100644 doc/images/UserTracking.png create mode 100644 doc/images/directive_seq.gif create mode 100644 doc/images/download.jpg create mode 100644 doc/images/link.png create mode 100644 doc/images/mod_qos.gif create mode 100644 doc/images/qsloc.png create mode 100644 doc/images/qsloc1.png create mode 100644 doc/images/qsloc2.png create mode 100644 doc/images/qsloc3.png create mode 100644 doc/images/qslogFormat.png create mode 100644 doc/images/qslog_spreadsheet_example.png create mode 100644 doc/index.html create mode 100644 doc/qsdt.1.html create mode 100644 doc/qsexec.1.html create mode 100644 doc/qsfilter2.1.html create mode 100644 doc/qsfilter2_process.gif create mode 100644 doc/qsgeo.1.html create mode 100644 doc/qsgrep.1.html create mode 100644 doc/qshead.1.html create mode 100644 doc/qslog.1.html create mode 100644 doc/qslogger.1.html create mode 100644 doc/qspng.1.html create mode 100644 doc/qsre.1.html create mode 100644 doc/qsrespeed.1.html create mode 100644 doc/qsrotate.1.html create mode 100644 doc/qssign.1.html create mode 100644 doc/qstail.1.html create mode 100644 tools/Makefile.am create mode 100644 tools/Makefile.in create mode 100644 tools/config.h.in create mode 100755 tools/configure create mode 100644 tools/configure.ac create mode 100755 tools/depcomp create mode 100755 tools/install-sh create mode 100644 tools/man1/mod_qos.1 create mode 100644 tools/man1/qsdt.1 create mode 100644 tools/man1/qsexec.1 create mode 100644 tools/man1/qsfilter2.1 create mode 100644 tools/man1/qsgeo.1 create mode 100644 tools/man1/qsgrep.1 create mode 100644 tools/man1/qshead.1 create mode 100644 tools/man1/qslog.1 create mode 100644 tools/man1/qslogger.1 create mode 100644 tools/man1/qspng.1 create mode 100644 tools/man1/qsre.1 create mode 100644 tools/man1/qsrespeed.1 create mode 100644 tools/man1/qsrotate.1 create mode 100644 tools/man1/qssign.1 create mode 100644 tools/man1/qstail.1 create mode 100755 tools/missing create mode 100644 tools/src/Makefile.am create mode 100644 tools/src/Makefile.in create mode 100644 tools/src/char.h create mode 100644 tools/src/qs_apo.c create mode 100644 tools/src/qs_apo.h create mode 100644 tools/src/qs_util.c create mode 100644 tools/src/qs_util.h create mode 100644 tools/src/qscheck.c create mode 100644 tools/src/qsdt.c create mode 100644 tools/src/qsexec.c create mode 100644 tools/src/qsfilter2.c create mode 100644 tools/src/qsgeo.c create mode 100644 tools/src/qsgrep.c create mode 100644 tools/src/qshead.c create mode 100644 tools/src/qslog.c create mode 100644 tools/src/qslogger.c create mode 100644 tools/src/qspng.c create mode 100644 tools/src/qsre.c create mode 100644 tools/src/qsrespeed.c create mode 100644 tools/src/qsrotate.c create mode 100644 tools/src/qssign.c create mode 100644 tools/src/qstail.c diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..d11de7d --- /dev/null +++ b/README.TXT @@ -0,0 +1,29 @@ + + Quality of service module for Apache Web Server. + http://mod-qos.sourceforge.net/ + + 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. + + Please refer to ./doc/index.html for further information. + + Directories in this archive: + apache2/ The source code of the module (mod_qos.c). + doc/ Contains the documentation (HTML). + tools/ Source code of several supplement tools incl. their + man pages. + 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 +#include + +#ifndef WIN32 +# include +# include +#else +# include +# include +# include +#endif + +#include + +// for socket options +#ifdef __unix__ +#include +#include +#endif + +/* apache */ +#include +#include +#include +#include +#include +#include +#define CORE_PRIVATE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* apr / scrlib */ +#include +#include +#include +#include +#include +#ifdef AP_NEED_SET_MUTEX_PERMS +#include +#endif + +/* mod_qos requires OpenSSL */ +#include +#include +#include + +/* 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()) + * + * 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(