/* -*-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) 2025 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 2724 2025-01-03 15:05:21Z pbuchbinder $";
static const char g_revision[] = "11.76";

/************************************************************************
 * 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_forwardedfor_fromUserAgentIP(request_rec *r) {
  const char *useragent_ip = NULL;
#if (AP_SERVER_MINORVERSION_NUMBER == 4) && (AP_SERVER_PATCHLEVEL_NUMBER > 18)
  useragent_ip = r->useragent_ip;
#endif
  if(QS_ISDEBUG(r->server)) {
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
                  QOS_LOGD_PFX"fromUserAgentIP() USERAGENT_IP=%s, id=%s",
                  useragent_ip == NULL ? "null" : useragent_ip,    
                  qos_unique_id(r, NULL));
  }
  return useragent_ip;
}

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) {
  if(header[0] == '#') {
    if(strcmp("USERAGENT_IP", &header[1]) == 0) {
      return qos_forwardedfor_fromUserAgentIP(r);
    }
    return qos_pseudoip(r, &header[1]);
  } else {
    return qos_forwardedfor_fromHeader(r, header);
  }
}

/**
 * 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_setn(c->notes, "short-lingering-close", "1");
  apr_table_set(c->notes, QS_CONN_ABORT, QS_CONN_ABORT);
  if (m_forced_close == 0) {
    return DECLINED;
  }

  //apr_brigade_cleanup(bb);
  APR_BRIGADE_INSERT_HEAD(bb, e);
  e = apr_bucket_flush_create(c->bucket_alloc);
  APR_BRIGADE_INSERT_TAIL(bb, e);
  ap_pass_brigade(c->output_filters, bb);

//  if(socket) {
//    // speed up connection termination
//    qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
//#ifdef QS_INTERNAL_TEST
//    struct timespec delay;
//    delay.tv_sec  = 0;
//    delay.tv_nsec = 1000000; // 1ms to allow testing
//    nanosleep(&delay, NULL);
//#endif
//    apr_socket_shutdown(socket, APR_SHUTDOWN_READ);
//    if(inctx) {
//      qos_cleanup_inctx(inctx);
//    }
//  }

  return HTTP_INTERNAL_SERVER_ERROR;
}

/**
 * returns custom error page
 */
static int qos_error_response(request_rec *r, const char *error_page) {
  if(r->subprocess_env) {
    const char *v = apr_table_get(r->subprocess_env, "QS_ErrorPage");
    if(v) {
      error_page = v;
    }
  }

  if(error_page) {
    /* do (almost) the same as ap_die() does */
    const char *error_notes;
    r->status = m_retcode;
    r->connection->keepalive = AP_CONN_CLOSE;
    r->no_local_copy = 1;
    apr_table_setn(r->subprocess_env, "REQUEST_METHOD", r->method);
    if ((error_notes = apr_table_get(r->notes, 
                                     "error-notes")) != NULL) {
      apr_table_setn(r->subprocess_env, "ERROR_NOTES", error_notes);
    }
    /* external or internal redirect */
    if(strncasecmp(error_page, "http", 4) == 0) {
      apr_table_set(r->headers_out, "Location", error_page);
      return HTTP_MOVED_TEMPORARILY;
    } else {
      r->method = apr_pstrdup(r->pool, "GET");
      r->method_number = M_GET;
      ap_internal_redirect(error_page, r);
      return DONE;
    }
  }
  return DECLINED;
}

/**
 * returns the matching regex with the lowest limitation
 */
static qs_acentry_t *qos_getrule_byregex(request_rec *r, qos_srv_config *sconf) {
  qs_acentry_t *ret = NULL;
  qs_actable_t *act = sconf->act;
  qs_acentry_t *actEntry = act->entry;
  int limit = -1;
  while(actEntry) {
    if((actEntry->event == NULL) && (actEntry->regex != NULL) && (actEntry->condition == NULL)) {
      if((limit == -1) || (actEntry->limit < limit)) {
        if(ap_regexec(actEntry->regex, r->unparsed_uri, 0, NULL, 0) == 0) {
          if(limit == -1) {
            ret = actEntry;
            limit = actEntry->limit;
          } else if(actEntry->limit < limit) {
            ret = actEntry;
            limit = actEntry->limit;
          }
        }
      }
    }
    actEntry = actEntry->next;
  }
  return ret;
}

/**
 * returns the matching conditional regex with the lowest limitation
 */
static qs_acentry_t *qos_getcondrule_byregex(request_rec *r, qos_srv_config *sconf) {
  qs_acentry_t *ret = NULL;
  qs_actable_t *act = sconf->act;
  qs_acentry_t *actEntry = act->entry;
  int limit = -1;
  while(actEntry) {
    if((actEntry->event == NULL) && (actEntry->regex != NULL) && (actEntry->condition != NULL)) {
      if((limit == -1) || (actEntry->limit < limit)) {
        if(ap_regexec(actEntry->regex, r->unparsed_uri, 0, NULL, 0) == 0) {
          if(limit == -1) {
            ret = actEntry;
            limit = actEntry->limit;
          } else if(actEntry->limit < limit) {
            ret = actEntry;
            limit = actEntry->limit;
          }
        }
      }
    }
    actEntry = actEntry->next;
  }
  return ret;
}

/**
 * returns the best matching location entry
 */
static qs_acentry_t *qos_getrule_bylocation(request_rec * r, qos_srv_config *sconf) {
  qs_acentry_t *ret = NULL;
  qs_actable_t *act = sconf->act;
  qs_acentry_t *actEntry = act->entry;
  int match_len = 0;
  while(actEntry) {
    if((actEntry->event == NULL) && (actEntry->regex == NULL)) {
      /* per location limitation */
      if(actEntry->url && (strncmp(actEntry->url, r->parsed_uri.path, actEntry->url_len) == 0)) {
        /* best match */
        if(actEntry->url_len > match_len) {
          match_len = actEntry->url_len;
          ret = actEntry;
        }
      }
    }
    actEntry = actEntry->next;
  }
  return ret;
}

/**
 * checks for VIP user (may pass restrictions)
 */
static int qos_is_vip(request_rec *r, qos_srv_config *sconf) {
  if(qos_verify_session(r, sconf)) {
    apr_table_set(r->subprocess_env, QS_VipRequest, "yes");
    apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
    return 1;
  }
  if(r->subprocess_env) {
    const char *v = apr_table_get(r->subprocess_env, QS_VipRequest);
    if(v && (strcasecmp(v, "yes") == 0)) {
      apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
      return 1;
    }
  }
  return 0;
}

/**
 * writes the parp table to a single query line
 */
static const char *qos_parp_query(request_rec *r, apr_table_t *tl, const char *add) {
  int add_len = 0;
  char *query = NULL;
  int len = 0;
  char *p;
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(tl)->elts;
  for(i = 0; i < apr_table_elts(tl)->nelts; i++) {
    len = len + 
      (entry[i].key == NULL ? 0 : strlen(entry[i].key)) +
      (entry[i].val == NULL ? 0 : strlen(entry[i].val)) +
      2;
  }
  if(add && add[0]) {
    add_len = strlen(add);
    len = len + add_len + 1;
  }
  query = apr_pcalloc(r->pool, len + 2);
  query[0] = '?';
  if(add_len) {
    memcpy(&query[1], add, add_len);
    p = &query[add_len];
  } else {
    p = &query[1];
  }
  p[0] = '\0';
  for(i = 0; i < apr_table_elts(tl)->nelts; i++) {
    int l = strlen(entry[i].key);
    if(p != &query[1]) {
      p[0] = '&';
      p++;
      p[0] = '\0';
    }
    memcpy(p, entry[i].key, l);
    p += l;
    p[0] = '=';
    p++;
    l = strlen(entry[i].val);
    memcpy(p, entry[i].val, l);
    p += l;
    p[0] = '\0';
  }
  apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), query);
  return &query[1];
}

/* filter events */
static int qos_per_dir_event_rules(request_rec *r, qos_srv_config *sconf,
                                   qos_dir_config *dconf) {
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(dconf->rfilter_table)->elts;
  int i;
  for(i = 0; i < apr_table_elts(dconf->rfilter_table)->nelts; i++) {
    if(entry[i].key[0] == '+') {
      int deny_rule = 0;
      int ex = -1;
      qos_rfilter_t *rfilter = (qos_rfilter_t *)entry[i].val;
      if(rfilter->type == QS_DENY_EVENT) {
        deny_rule = 1;
        if(rfilter->text[0] == '!') {
          if(apr_table_get(r->subprocess_env, &rfilter->text[1]) == NULL) {
            ex = 0;
          }
        } else {
          if(apr_table_get(r->subprocess_env, rfilter->text) != NULL) {
            ex = 0;
          }
        }
      }
      if(deny_rule && (ex == 0)) {
        int severity = rfilter->action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
                      QOS_LOG_PFX(040)"access denied, %s rule id: %s (%s),"
                      " action=%s, c=%s, id=%s",
                      qos_rfilter_type2text(r->pool, rfilter->type),
                      rfilter->id,
                      rfilter->text,
                      (!sconf->log_only) && (rfilter->action == QS_DENY) ? "deny" : "log only",
                      QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                      qos_unique_id(r, "040"));
        QS_INC_EVENT(sconf, 40);
        if(rfilter->action == QS_DENY) {
          return HTTP_FORBIDDEN;
        }
      }
    }
  }
  return APR_SUCCESS;
}

/* json parser start ------------------------------------------------------- */
#define QOS_J_ERROR "HTTP_BAD_REQUEST QOS JSON PARSER: FORMAT ERROR"
#define QOS_j_RECURSION 80

static int j_val(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec);

static char *j_escape_url(apr_pool_t *pool, const char *c) {
  char buf[4];
  char special[] = " \t()<>@,;:\\/[]?={}\"'&%+";
  char *r = apr_pcalloc(pool, 3 * strlen(c));
  const char *p = c;
  int i = 0;
  while(p && p[0]) {
    char c = p[0];
    if(!apr_isprint(c) || strchr(special, c)) {
      sprintf(buf, "%02x", p[0]);
      r[i] = '%'; i++;
      r[i] = buf[0]; i++;
      r[i] = buf[1]; i++;
    } else {
      r[i] = c;
      i++;
    }
    p++;
  }
  return r;
}

static char *j_strchr(char *data, char d) {
  char *q = data;
  if(!q) {
    return NULL;
  }
  if(q[0] == d) {
    return q;
  }
  while(q[0]) {
    if((q[0] == d) && (q[-1] != '\\')) {
      return q;
    }
    q++;
  }
  return NULL;
}

static char *j_skip(char *in) {
  if(!in) return NULL;
  while(in[0] && ((in[0] == ' ') ||
		  (in[0] == '\t') ||
		  (in[0] == '\r') ||
		  (in[0] == '\n') ||
		  (in[0] == '\f'))) {
    in++;
  }
  return in;
}

static int j_string(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, char **n) {
  char *d = *val;
  char *v = d;
  char *end = j_strchr(d, '"');
  if(!end) {
    apr_table_add(tl, QOS_J_ERROR, "error while parsing string (no ending double quote)");
    return HTTP_BAD_REQUEST;
  }
  end[0] = '\0';
  end++;
  *val = j_skip(end);
  /* TODO, improve string format validation */
  while(v[0]) {
    if(v[0] < ' ') {
      apr_table_add(tl, QOS_J_ERROR, "error while parsing string (invalid character)");
      return HTTP_BAD_REQUEST;
    }
    v++;
  }
  *n = d;
  return APR_SUCCESS;
}

static int j_num(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, char **n) {
  char *s = *val;
  char *d = *val;
  while(d && ((d[0] >= '0' && d[0] <= '9') ||
	      d[0] == '.' ||
	      d[0] == 'e' ||
	      d[0] == 'E' ||
	      d[0] == '+' ||
	      d[0] == '-')) {
    d++;
  }
  *n = apr_pstrndup(pool, s, d-s);
  *val = d;
  return APR_SUCCESS;
}

static int j_obj(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
  char *d = j_skip(*val);
  int rc;
  while(d && d[0]) {
    if(*d != '\"') {
      apr_table_add(tl, QOS_J_ERROR, "error while parsing object (missing string)");
      return HTTP_BAD_REQUEST;
    } else {
      /* list of string ":" value pairs (sepated by ',') */
      char *v = NULL;
      char *thisname;
      d++;
      rc = j_string(pool, &d, tl, name, &v);
      if(rc != APR_SUCCESS) {
	return rc;
      }
      thisname = apr_pstrcat(pool, name, "_" , v, NULL);
      d = j_skip(d);
      if(!d || d[0] != ':') {
	apr_table_add(tl, QOS_J_ERROR, "error while parsing object (missing value/wrong delimiter)");
	return HTTP_BAD_REQUEST;
      }
      d++;
      rc = j_val(pool, &d, tl, thisname, rec);
      if(rc != APR_SUCCESS) {
	return rc;
      }
      d = j_skip(d);
      if(!d) {
	apr_table_add(tl, QOS_J_ERROR, "error while parsing object (unexpected end)");
	return HTTP_BAD_REQUEST;
      }
      if(d[0] == '}') {
	d++;
	*val = d;
	return APR_SUCCESS;
      } else if(d[0] == ',') {
	d = j_strchr(d, '"');
      } else {
	apr_table_add(tl, QOS_J_ERROR, "error while parsing object (unexpected end/wrong delimiter)");
	return HTTP_BAD_REQUEST;
      }
    }
  }
  return APR_SUCCESS;
}

static int j_ar(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
  char *d = j_skip(*val);
  int rc;
  int index = 0;
  while(d && d[0]) {
    rc = j_val(pool, &d, tl, apr_psprintf(pool, "%s%d", name, index), rec);
    if(rc != APR_SUCCESS) {
      return rc;
    }
    d = j_skip(d);
    if(!d) {
      apr_table_add(tl, QOS_J_ERROR, "error while parsing array (unexpected end)");
      return HTTP_BAD_REQUEST;
    }
    if(d[0] == ']') {
      d++;
      *val = d;
      return APR_SUCCESS;
    } else if(d[0] == ',') {
      d++;
      d = j_skip(d);
    } else {
      apr_table_add(tl, QOS_J_ERROR, "error while parsing array (unexpected end/wrong delimiter)");
      return HTTP_BAD_REQUEST;
    }
    index++;
  }
  return APR_SUCCESS;
}

static int j_val(apr_pool_t *pool, char **val, apr_table_t *tl, char *name, int rec) {
  char *d = j_skip(*val);
  int rc = APR_SUCCESS;
  rec++;
  if(rec > QOS_j_RECURSION) {
    apr_table_add(tl, QOS_J_ERROR, "error while parsing string (reached recursion limit)");
    return HTTP_BAD_REQUEST;
  }
  /* either object, array, string, number, "true", "false", or "null" */
  if(d[0] == '{') {
    d++;
    rc = j_obj(pool, &d, tl, apr_pstrcat(pool, name, "_o", NULL), rec);
  } else if(d[0] == '[') {
    d++;
    rc = j_ar(pool, &d, tl, apr_pstrcat(pool, name, "_a", NULL), rec);
  } else if(strncmp(d,"null",4) == 0) {
    d+=4;
    apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "null");
  } else if(strncmp(d,"true",4) == 0) {
    apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "true");
    d+=4;
  } else if(strncmp(d,"false",5) == 0) {
    apr_table_add(tl, apr_pstrcat(pool, j_escape_url(pool, name), "_b", NULL), "false");
    d+=5;
  } else if(*d == '-' || (*d >= '0' && *d <= '9')) {
    char *n = apr_pstrcat(pool, name, "_n", NULL);
    char *v = NULL;
    rc = j_num(pool, &d, tl, n, &v);
    if(rc == APR_SUCCESS) {
      apr_table_addn(tl, j_escape_url(pool, n), j_escape_url(pool, v));
    }
  } else if(*d == '\"') {
    char *n = apr_pstrcat(pool, name, "_v", NULL);
    char *v = NULL;
    d++;
    rc = j_string(pool, &d, tl, n, &v);
    if(rc == APR_SUCCESS) {
      apr_table_addn(tl, j_escape_url(pool, n), j_escape_url(pool, v));
    }
  } else {
    /* error */
    apr_table_add(tl, QOS_J_ERROR, "error while parsing value (invalid type)");
    return HTTP_BAD_REQUEST;
  }
  if(rc != APR_SUCCESS) {
    return rc;
  }
  *val = d;
  rec--;
  return APR_SUCCESS;
}
/* json parser end --------------------------------------------------------- */

/**
 * Process json data retrieved from parp (request body)
 * @param r
 * @param dconf
 * @param query Query to add data
 * @param msg Error message if paring fails
 * @return APR_SUCCESS if processed without errors.
 */
static int qos_json(request_rec *r, qos_dir_config *dconf, const char **query, const char **msg) {
  const char *contenttype = apr_table_get(r->headers_in, "Content-Type");
  if(contenttype && (strncasecmp(contenttype, "application/json", 16) == 0)) {
    apr_size_t len = 0;
    const char *data = NULL;
    /* check if parp has body data to process (requires "PARP_BodyData application/json")
       or if the json message is stored within the query */
    if(qos_parp_body_data_fn) {
      data = qos_parp_body_data_fn(r, &len);
    }
    if(data == NULL) {
      data = *query;
      if(data && (data[0] == '[' || data[0] == '{')) {
        int escerr = 0;
        char *copyq = apr_pstrdup(r->pool, data);
        *query = NULL;
        // the query needs to be unescaped before getting parsed
        len = qos_unescaping(copyq, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
        qos_run_path_decode_hook(r, &copyq, &len);
#endif
        data = copyq;
        if(strlen(data) != len) {
          *msg = apr_pstrdup(r->pool, "null character within data structure in query");
          return HTTP_BAD_REQUEST;
        }
      } else {
        // does not look like a json structure (strict)
        data = NULL;
      }
    }
    if(data && (len > 0)) {
      char *value = apr_pstrndup(r->pool, data, len);
      apr_table_t *tl = apr_table_make(r->pool, 200);
      int rc;
      if(strlen(value) != len) {
        *msg = apr_pstrdup(r->pool, "null character within data structure");
        return HTTP_BAD_REQUEST;
      }
      rc = j_val(r->pool, &value, tl, "J", 0);
      if(rc != APR_SUCCESS) {
        *msg = apr_table_get(tl, QOS_J_ERROR); 
        apr_table_unset(tl, QOS_J_ERROR);
        return rc;
      }
      if(value && value[0]) {
        value = j_skip(value);
        if(value && value[0]) {
          /* error, there is still some data */
          *msg = apr_pstrdup(r->pool, "more than one element");
        }
      }
      *query = qos_parp_query(r, tl, *query);
      if(*query) {
        apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), *query);
      }
    }
  }
  return APR_SUCCESS;
}

/**
 * processes the per location rules QS_Permit* and QS_Deny*
 */
static int qos_per_dir_rules(request_rec *r, qos_srv_config *sconf,
                             qos_dir_config *dconf) {
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(dconf->rfilter_table)->elts;
  int i;
  char *path = apr_pstrdup(r->pool, r->parsed_uri.path);
  char *query = NULL;
  char *fragment = NULL;
  char *request_line = apr_pstrdup(r->pool, r->the_request);
  char *uri = path;
  int request_line_len;
  int path_len;
  int query_len = 0;
  int fragment_len = 0;
  int uri_len;
  int permit_rule = 0;
  int permit_rule_match = 0;
  int permit_rule_action = QS_DENY;
  int escerr = 0;
  request_line_len = qos_unescaping(request_line, dconf->dec_mode, &escerr);
  path_len = qos_unescaping(path, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
  qos_run_path_decode_hook(r, &path, &path_len);
#endif
  uri_len = path_len;
  if(dconf->bodyfilter_p == 1 || dconf->bodyfilter_d == 1) {
    const char *q = apr_table_get(r->notes, QS_PARP_Q);
    if((q == NULL) && qos_parp_hp_table_fn) {
      const char *msg = NULL;
      apr_table_t *tl = qos_parp_hp_table_fn(r);
      if(tl) {
        if(apr_table_elts(tl)->nelts > 0) {
          q = qos_parp_query(r, tl, NULL);
          if(q) {
            apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), q);
          }
        }
      } else {
        /* no table provided by mod_parp (unsupported content type?),
           use query string if available */
        if(r->parsed_uri.query) {
          q = r->parsed_uri.query;
        }
      }
      if(qos_json(r, dconf, &q, &msg) != APR_SUCCESS) {
        /* parser error */
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                      QOS_LOG_PFX(048)"access denied, invalid JSON syntax (%s),"
                      " action=%s, c=%s, id=%s",
                      msg ? msg : "-",
                      sconf->log_only ? "log only" : "deny",
                      QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                      qos_unique_id(r, "048"));
        QS_INC_EVENT(sconf, 48);
        return HTTP_FORBIDDEN;
      }
    }
    if(q) {
      /* prepare unescaped body query (parp) */
      char *q1 = apr_pstrdup(r->pool, q);
      int q1_len = 0;
      q1 = apr_pstrdup(r->pool, q);
      q1_len = qos_unescaping(q1, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
      qos_run_query_decode_hook(r, &q1, &q1_len);
#endif
      if(dconf->bodyfilter_d == 1) {
        /* use body for query deny filter */
        query = q1;
        query_len = q1_len;
      } else {
        /* don't use body for query deny filter */
        if(r->parsed_uri.query) {
          query = apr_pstrdup(r->pool, r->parsed_uri.query);
          query_len = qos_unescaping(query, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
          qos_run_query_decode_hook(r, &query, &query_len);
#endif
        }
      }
      if(dconf->bodyfilter_p != 1) {
        /* don' use body for permit filter */
        if(r->parsed_uri.query) {
          q1 = apr_pstrdup(r->pool, r->parsed_uri.query);
          q1_len = qos_unescaping(q1, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
          qos_run_query_decode_hook(r, &q1, &q1_len);
#endif
        } else {
          q1 = NULL;
          q1_len = 0;
        }
      }
      if(q1) {
        uri = apr_pcalloc(r->pool, path_len + 1 + q1_len + 1);
        memcpy(uri, path, path_len);
        uri[path_len] = '?';
        memcpy(&uri[path_len+1], q1, q1_len);
        uri[path_len+1+q1_len] = '\0';
        uri_len = path_len + 1 + q1_len;
      }
    }
  } else {
    if(r->parsed_uri.query) {
      query = apr_pstrdup(r->pool, r->parsed_uri.query);
      query_len = qos_unescaping(query, dconf->dec_mode, &escerr);
#ifdef QS_MOD_EXT_HOOKS
      qos_run_query_decode_hook(r, &query, &query_len);
#endif
      uri = apr_pcalloc(r->pool, path_len + 1 + query_len + 1);
      memcpy(uri, path, path_len);
      uri[path_len] = '?';
      memcpy(&uri[path_len+1], query, query_len);
      uri[path_len+1+query_len] = '\0';
      uri_len = path_len + 1 + query_len;
    }
  }
  if(r->parsed_uri.fragment) {
    fragment = apr_pstrdup(r->pool, r->parsed_uri.fragment);
    fragment_len = qos_unescaping(fragment, dconf->dec_mode, &escerr);
    uri = apr_pcalloc(r->pool, path_len + 1 + fragment_len + 1);
    memcpy(uri, path, path_len);
    uri[path_len] = '?';
    memcpy(&uri[path_len+1], fragment, fragment_len);
    uri[path_len+1+fragment_len] = '\0';
    uri_len = path_len + 1 + fragment_len;
  }
  if(escerr > 0 && (dconf->urldecoding < QS_OFF_DEFAULT)) {
    int severity = dconf->urldecoding == QS_DENY ? APLOG_ERR : APLOG_WARNING;
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
                  QOS_LOG_PFX(046)"access denied, invalid url encoding, action=%s, c=%s, id=%s",
                  (!sconf->log_only) && (dconf->urldecoding == QS_DENY) ? "deny" : "log only",
                  QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                  qos_unique_id(r, "046"));
    QS_INC_EVENT(sconf, 46);
    if(dconf->urldecoding == QS_DENY) {
      return HTTP_FORBIDDEN;
    }
  }
  /* process deny- and allow- list rules in one loop */
  for(i = 0; i < apr_table_elts(dconf->rfilter_table)->nelts; i++) {
    if(entry[i].key[0] == '+') {
      int deny_rule = 0;
      int ex = -1;
      qos_rfilter_t *rfilter = (qos_rfilter_t *)entry[i].val;
      if(rfilter->type == QS_DENY_REQUEST_LINE) {
        deny_rule = 1;
        ex = qos_regexec_len(r->pool, rfilter->preg, request_line, request_line_len);
      } else if(rfilter->type == QS_DENY_PATH) {
        deny_rule = 1;
        ex = qos_regexec_len(r->pool, rfilter->preg, path, path_len);
      } else if(rfilter->type == QS_DENY_QUERY) {
        deny_rule = 1;
        ex = qos_regexec_len(r->pool, rfilter->preg, query, query_len);
      } else if(rfilter->type == QS_DENY_EVENT) {
        /* event rules are processed separately */
      } else {
        permit_rule = 1;
        ex = qos_regexec_len(r->pool, rfilter->preg, uri, uri_len);
        permit_rule_action = rfilter->action;
        if(ex == 0) {
          permit_rule_match = 1; 
        }
      }
      if(deny_rule && (ex == 0)) {
        int severity = rfilter->action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
        apr_table_set(r->subprocess_env, QS_RuleId, rfilter->id);
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
                      QOS_LOG_PFX(040)"access denied, %s rule id: %s (%s),"
                      " action=%s, c=%s, id=%s",
                      qos_rfilter_type2text(r->pool, rfilter->type),
                      rfilter->id,
                      rfilter->text,
                      (!sconf->log_only) && (rfilter->action == QS_DENY) ? "deny" : "log only",
                      QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                      qos_unique_id(r, "040"));
        QS_INC_EVENT(sconf, 40);
        if(rfilter->action == QS_DENY) {
          return HTTP_FORBIDDEN;
        }
      }
    }
  }
  if(permit_rule && !permit_rule_match) {
    int severity = permit_rule_action == QS_DENY ? APLOG_ERR : APLOG_WARNING;
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
                  QOS_LOG_PFX(041)"access denied, no permit rule match, action=%s, c=%s, id=%s",
                  (!sconf->log_only) && (permit_rule_action == QS_DENY) ? "deny" : "log only",
                  QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                  qos_unique_id(r, "041"));
    QS_INC_EVENT(sconf, 41);
    if(permit_rule_action == QS_DENY) {
      return HTTP_FORBIDDEN;
    }
  }
  return APR_SUCCESS;
}

/**
 * request/response header filter, drops headers which are not allowed
 */
static int qos_header_filter(request_rec *r, qos_srv_config *sconf,
                             apr_table_t *headers, const char *type,
                             apr_table_t *hfilter_table,
                             qs_headerfilter_mode_e mode) {
  apr_table_t *delete = apr_table_make(r->pool, 1);
  apr_table_t *reason = NULL;
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(headers)->elts;
  for(i = 0; i < apr_table_elts(headers)->nelts; i++) {
    qos_fhlt_r_t *he = (qos_fhlt_r_t *)apr_table_get(hfilter_table, entry[i].key);
    int denied = 0;
    if(he) {
      if(mode != QS_HEADERFILTER_SIZE_ONLY) {
        if(ap_regexec(he->preg, entry[i].val, 0, NULL, 0) != 0) {
          denied = 1;
        }
      }
      if(strlen(entry[i].val) > he->size) {
        denied += 2;
      }
      if(denied) {
        char *pattern = apr_psprintf(r->pool, "(pattern=%s, max. length=%d)",
                                     he->text, he->size);
        if(he->action == QS_FLT_ACTION_DENY) {
          ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                        QOS_LOG_PFX(043)"access denied%s, %s header: \'%s: %s\', %s, c=%s, id=%s",
                        sconf->log_only ? " (log only)" : "",
                        type,
                        entry[i].key, entry[i].val,
                        pattern,
                        QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                        qos_unique_id(r, "043"));
          QS_INC_EVENT(sconf, 43);
          return HTTP_FORBIDDEN;
        }
        if(reason == NULL) {
          reason = apr_table_make(r->pool, 1);
        }
        apr_table_add(delete, entry[i].key, entry[i].val);
        apr_table_add(reason, entry[i].key, pattern);
      }
    } else {
      if(reason == NULL) {
        reason = apr_table_make(r->pool, 1);
      }
      apr_table_add(delete, entry[i].key, entry[i].val);
      apr_table_add(reason, entry[i].key, "(no rule available)");
    }
  }
  entry = (apr_table_entry_t *)apr_table_elts(delete)->elts;
  for(i = 0; i < apr_table_elts(delete)->nelts; i++) {
    if(mode != QS_HEADERFILTER_SILENT) {
      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
                    QOS_LOG_PFX(042)"drop %s header%s: \'%s: %s\', %s, c=%s, id=%s",
                    type,
                    sconf->log_only ? " (log only)" : "",
                    entry[i].key, entry[i].val,
                    apr_table_get(reason, entry[i].key),
                    QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                    qos_unique_id(r, "042"));
      QS_INC_EVENT(sconf, 42);
    }
    if(!sconf->log_only) {
      apr_table_unset(headers, entry[i].key);
    }
  }
  return APR_SUCCESS;
}

/**
 * returns list of all query name=value pairs
 */
static apr_table_t *qos_get_query_table(request_rec *r) {
  apr_table_t *av = apr_table_make(r->pool, 2);
  if(r->parsed_uri.query) {
    const char *q = apr_pstrdup(r->pool, r->parsed_uri.query);
    while(q && q[0]) {
      const char *t = ap_getword(r->pool, &q, '&');
      const char *name = ap_getword(r->pool, &t, '=');
      const char *value = t;
      if(name && (strlen(name) > 0)) {
        if(value && (strlen(value) > 0)) {
          apr_table_add(av, name, value);
        } else if((strlen(name) > 0)) {
          apr_table_add(av, name, "");
        }
      }
    }
  }
  return av;
}

/** add "\n" */
#define QOS_ALERT_LINE_LEN 65
static char *qos_crline(request_rec *r, const char *line) {
  char *string = "";
  const char *pos = line;
  while(pos && pos[0]) {
    int len = strlen(pos);
    if(len > QOS_ALERT_LINE_LEN) {
      string = apr_pstrcat(r->pool, string,
                           apr_psprintf(r->pool, "%.*s", QOS_ALERT_LINE_LEN, pos), "\n", NULL);
      pos = &pos[QOS_ALERT_LINE_LEN];
    } else {
      string = apr_pstrcat(r->pool, string, pos, NULL);
      pos = NULL;
    }
  }
  return string;
}

/**
 * calculates the rec/sec block rate
 */
static void qos_cal_req_sec(qos_srv_config *sconf, request_rec *r, qs_acentry_t *e) {
  if(e->req_per_sec > e->req_per_sec_limit) {
    int factor = ((e->req_per_sec * 100) / e->req_per_sec_limit) - 100;
    e->req_per_sec_block_rate = e->req_per_sec_block_rate + factor;
    if(e->req_per_sec_block_rate > QS_MAX_DELAY/1000) {
      e->req_per_sec_block_rate = QS_MAX_DELAY/1000;
    }
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
                  QOS_LOG_PFX(050)"request rate limit, rule: %s(%ld), req/sec=%ld,"
                  " delay=%dms%s",
                  e->url, e->req_per_sec_limit,
                  e->req_per_sec, e->req_per_sec_block_rate,
                  e->req_per_sec_block_rate == QS_MAX_DELAY/1000 ? " (max)" : "");
    QS_INC_EVENT(sconf, 50);
  } else if(e->req_per_sec_block_rate > 0) {
    if(e->req_per_sec_block_rate < 50) {
      e->req_per_sec_block_rate = 0;
    } else {
      int factor = e->req_per_sec_block_rate / 4;
      e->req_per_sec_block_rate = e->req_per_sec_block_rate - factor;
    }
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
                  QOS_LOG_PFX(051)"request rate limit, rule: %s(%ld), req/sec=%ld,"
                  " delay=%dms",
                  e->url, e->req_per_sec_limit,
                  e->req_per_sec, e->req_per_sec_block_rate);
    QS_INC_EVENT(sconf, 51);
  }
}

/**
 * QS_DenyEvent enforcement at header parser
 * @param r
 * @param sconf
 * @param dconf
 # returns DECLINED if no events has been detected
 */
static int qos_hp_event_deny_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
  apr_status_t rv = qos_per_dir_event_rules(r, sconf, dconf);
  if(rv != APR_SUCCESS) {
    int rc;
    const char *error_page = sconf->error_page;
    qs_set_evmsg(r, "D;"); 
    if(!sconf->log_only) {
      rc = qos_error_response(r, error_page);
      if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
        return rc;
      }
      return rv;
    }
  }
  return DECLINED;
}

/**
 * QS_Permit* / QS_Deny* enforcement at header parser
 * @param r
 * @param sconf
 * @param dconf
 * @return
 */
static int qos_hp_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
  apr_status_t rv = APR_SUCCESS;
  if(sconf->milestones) {
    char *value = qos_get_remove_cookie(r, QOS_MILESTONE_COOKIE);
    rv = qos_verify_milestone(r, sconf, value);
  }

  if((rv == APR_SUCCESS) && (apr_table_elts(dconf->rfilter_table)->nelts > 0)) {
    rv = qos_per_dir_rules(r, sconf, dconf);
  }

  if(rv != APR_SUCCESS) {
    int rc;
    const char *error_page = sconf->error_page;
    qs_set_evmsg(r, "D;"); 
    if(!sconf->log_only) {
      rc = qos_error_response(r, error_page);
      if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
        return rc;
      }
      return rv;
    }
  }
  return DECLINED;
}

/**
 * QS_SetEnvRes (outfilter)
 * Detects events at response time.
 */
static void qos_setenvres(request_rec *r, qos_srv_config *sconf) {
  ap_regmatch_t regm[AP_MAX_REG_MATCH];
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvres_t)->elts;
  for(i = 0; i < apr_table_elts(sconf->setenvres_t)->nelts; i++) {
    const char *val = apr_table_get(r->subprocess_env, entry[i].key);
    if(val) {
      qos_pregval_t *pregval = (qos_pregval_t *)entry[i].val;
      if(ap_regexec(pregval->preg, val, AP_MAX_REG_MATCH, regm, 0) == 0) {
        if(pregval->value) {
          char *replaced = ap_pregsub(r->pool, pregval->value, val, AP_MAX_REG_MATCH, regm);
          apr_table_set(r->subprocess_env, pregval->name, replaced);
        } else {
          apr_table_set(r->subprocess_env, pregval->name, "1");
        }
      }
    }
  }
}

/**
 * QS_SetEnvResHeader(Match) (outfilter)
 * Matches response headers and sets an event on match.
 * @param r
 * @param sconf
 */
static void qos_setenvresheader(request_rec *r, qos_srv_config *sconf) {
  apr_table_t *headers = r->headers_out;
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvresheader_t)->elts;
  apr_table_entry_t *entryMatch = (apr_table_entry_t *)apr_table_elts(sconf->setenvresheadermatch_t)->elts;
  while(headers) {
    for(i = 0; i < apr_table_elts(sconf->setenvresheadermatch_t)->nelts; i++) {
      const char *val = apr_table_get(headers, entryMatch[i].key);
      if(val) {
        ap_regex_t *preg = (ap_regex_t *)entryMatch[i].val;
        if(ap_regexec(preg, val, 0, NULL, 0) == 0) {
          apr_table_set(r->subprocess_env, entryMatch[i].key, val);
        }
      }
    }
    for(i = 0; i < apr_table_elts(sconf->setenvresheader_t)->nelts; i++) {
      const char *val = apr_table_get(headers, entry[i].key);
      if(val) {
        apr_table_set(r->subprocess_env, entry[i].key, val);
        if(strcasecmp(entry[i].val, "drop") == 0) {
          apr_table_unset(headers, entry[i].key);
        }
      }
    }
    if(headers == r->headers_out) {
      headers = r->err_headers_out;
    } else {
      headers = NULL;
    }
  }
}

/**
 * QS_SetEnvIfStatus
 * Match response status code
 *
 * @param r
 * @param sconf
 * @param dconf
 */
static void qos_setenvstatus(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
  char *code = apr_psprintf(r->pool, "%d", r->status);
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvstatus_t)->elts;
  for(i = 0; i < apr_table_elts(sconf->setenvstatus_t)->nelts; i++) {
    if(strcmp(entry[i].key, code) == 0) {
      char *var = apr_pstrdup(r->pool, entry[i].val);
      char *value = strchr(var, '=');
      if(value) {
        // a value has been defined
        value[0] = '\0';
        value++;
      } else {
        if(strcmp(var, QS_BLOCK) == 0) {
          // QS_Block optionally defines the weight for error codes
          value = apr_pstrdup(r->pool, "1");
        } else {
          // by default, the value becomes the status code
          value = code;
        }
      }
      apr_table_set(r->subprocess_env, var, value);
    }
  }
  if(dconf) {
    entry = (apr_table_entry_t *)apr_table_elts(dconf->setenvstatus_t)->elts;
    for(i = 0; i < apr_table_elts(dconf->setenvstatus_t)->nelts; i++) {
      if(strcmp(entry[i].key, code) == 0) {
        char *var = apr_pstrdup(r->pool, entry[i].val);
        char *value = strchr(var, '=');
        if(value) {
          value[0] = '\0';
          value++;
        } else {
          value = code;
        }
        apr_table_set(r->subprocess_env, var, value);
      }
    }
  }
}

/**
 * Returns the best matching server name (the configured ServerName,
 * supporting ServerAlias directive or the value provided by the
 * caller (usually the Host header)).
 *
 * @param r
 * @param server_hostname Host name the client expects (Host header) <host>[:<port>]
 * @param match Indicates if the provide name matches the ServerName/ServerAlias
 * @return hostname
 */
static char *qos_server_alias(request_rec *r, const char *server_hostname, int *match) {
  char *server = apr_pstrdup(r->pool, r->server->server_hostname); // default (hostname, no port)
  *match = 0;
  if(server_hostname) {
    const char *search = server_hostname;
    char *port = strchr(search, ':');
    if(port) {
      // without the port
      search = apr_pstrndup(r->pool, search, port - search);
    }
    if(strcasecmp(search, r->server->server_hostname) == 0) {
      /* match ServerName */
      // we already did: server = apr_pstrdup(r->pool, r->server->server_hostname);
      *match = 1;
    } else if(r->server->names) {
      int i;
      apr_array_header_t *names = r->server->names;
      char **name = (char **)names->elts;
      for(i = 0; i < names->nelts; ++i) {
        if(!name[i]) continue;
        if(strcasecmp(search, name[i]) == 0) {
          /* match ServerAlias */
          server = apr_pstrdup(r->pool, name[i]);
          *match = 1;
        }
      }
    } else if(r->server->wild_names) {
      int i;
      apr_array_header_t *names = r->server->wild_names;
      char **name = (char **)names->elts;
      for(i = 0; i < names->nelts; ++i) {
        if(!name[i]) continue;
        if(!ap_strcasecmp_match(search, name[i])) {
          /* match ServerAlias using wildcards */
          server = apr_pstrdup(r->pool, search);
          *match = 1;
        }
      }
    }
  }
  return server;
}

/** 
 * Returns the url to this server, e.g. https://server1 or http://server1:8080
 * used for redirects.
 *
 * @param r
 * @return schema/hostname
 */
static char *qos_this_host(request_rec *r) {
  const char *hostport = apr_table_get(r->headers_in, "Host");
  int port = 0;
  int ssl = 0;
  int default_port;
  const char *server_hostname = r->server->server_hostname;
  if(qos_is_https) {
    ssl = qos_is_https(r->connection);
  }
  if(hostport) {
    char *p;
    int match;
    hostport = apr_pstrdup(r->pool, hostport);
    if((p = strchr(hostport, ':')) != NULL) {
      p[0] = '\0';
      p++;
      port = atoi(p);
    }
    server_hostname = qos_server_alias(r, hostport, &match);
  }
  if(port == 0) {
    // pref. vhost
    port = r->server->addrs->host_port;
  }
  if(port == 0) {
    // main srv
    port = r->server->port;
  }
  default_port = ssl ? 443 : 80;
  if(port == default_port) {
    return apr_psprintf(r->pool, "%s%s",
                        ssl ? "https://" : "http://",
                        server_hostname);
  }
  return apr_psprintf(r->pool, "%s%s:%d",
                      ssl ? "https://" : "http://",
                      server_hostname,
                      port);
}

/**
 * Enables mod_parp if mod_qos requires access to the request body.
 * @param r
 */
static void qos_enable_parp(request_rec *r) {
  const char *ct = apr_table_get(r->headers_in, "Content-Type");
  if(ct) {
    if(ap_strcasestr(ct, "application/x-www-form-urlencoded") ||
       ap_strcasestr(ct, "multipart/form-data") ||
       ap_strcasestr(ct, "multipart/mixed") ||
       ap_strcasestr(ct, "application/json")) {
      apr_table_set(r->subprocess_env, "parp", "mod_qos");
    }
  }
}

/** 
 * Generic request validation / sanity check:
 * We ensure to have at least a valid, decoded request uri received.
 * (no further uri validation required in your code)
 * @param r
 * @param sconf
 * @return HTTP_BAD_REQUEST for requests which may not be processed by mod_qos, otherwise
 *         APR_SUCCESS
 */
static apr_status_t qos_request_check(request_rec *r, qos_srv_config *sconf) {
  if((r->unparsed_uri == NULL) || (r->parsed_uri.path == NULL)) {
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                  QOS_LOG_PFX(045)"access denied, invalid request line:"
                  " can't parse uri, c=%s, id=%s",
                  QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                  qos_unique_id(r, "045"));
    QS_INC_EVENT(sconf, 45);
    return HTTP_BAD_REQUEST;
  }
  return APR_SUCCESS;
}

/**
 * QS_SetEnvIfParp (prr), enable parp
 */
static apr_status_t qos_parp_prr(request_rec *r, qos_srv_config *sconf) {
  if(apr_table_elts(sconf->setenvifparp_t)->nelts > 0) {
    qos_enable_parp(r);
  }
  return DECLINED;
}

/**
 * QS_SetEnvIfQuery/QS_SetEnvIfParp
 */
static void qos_setenvif_ex(request_rec *r, const char *query, apr_table_t *table_setenvif) {
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(table_setenvif)->elts;
  for(i = 0; i < apr_table_elts(table_setenvif)->nelts; i++) {
    qos_setenvifquery_t *setenvif = (qos_setenvifquery_t *)entry[i].val;
    char *name = setenvif->name;
    ap_regmatch_t regm[AP_MAX_REG_MATCH];
    if(ap_regexec(setenvif->preg, query, AP_MAX_REG_MATCH, regm, 0) == 0) {
      if(name[0] == '!') {
        apr_table_unset(r->subprocess_env, &name[1]);
      } else {
        char *replaced = "";
        if(setenvif->value) {
          replaced = ap_pregsub(r->pool, setenvif->value, query, AP_MAX_REG_MATCH, regm);
        }
        apr_table_set(r->subprocess_env, name, replaced);
      }
    }
  }
}

/**
 * Process body events (QS_SetEnvIfBody) and sets the r->subprocess_env variables
 * @param r
 * @param sconf
 */
static void qos_parp_hp_body(request_rec *r, qos_srv_config *sconf) {
  apr_size_t len;
  const char *data = qos_parp_body_data_fn(r, &len);
  if(data && (len > 0)) {
    int i;
    apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenvifparpbody_t)->elts;
#if (AP_SERVER_MINORVERSION_NUMBER < 4) && defined QS_INTERNAL_TEST
    // Apache 2.2 is no longer supported (test only)
    char *tmpData = apr_palloc(r->pool, len + 1);
    memcpy(tmpData, data, len);
    tmpData[len] = '\0';
    data = (const char *)tmpData;
#endif
    for(i = 0; i < apr_table_elts(sconf->setenvifparpbody_t)->nelts; i++) {
      qos_setenvifparpbody_t *setenvif = (qos_setenvifparpbody_t *)entry[i].val;
      ap_regmatch_t regm[AP_MAX_REG_MATCH];
#if (AP_SERVER_MINORVERSION_NUMBER < 4) && defined QS_INTERNAL_TEST
      if(ap_regexec(setenvif->pregx, data, AP_MAX_REG_MATCH, regm, 0) == 0) { // won't work for null chars!
#else
      if(ap_regexec_len(setenvif->pregx, data, len, AP_MAX_REG_MATCH, regm, 0) == 0) {
#endif
        char *name = setenvif->name;
        if(name[0] == '!') {
          apr_table_unset(r->subprocess_env, &name[1]);
        } else {
          char *value = apr_pstrdup(r->pool, setenvif->value);
          char *p = strstr(value, "$1");
          if(p) {
            char *c = apr_pstrndup(r->pool, &data[regm[0].rm_so], regm[0].rm_eo - regm[0].rm_so);
            if(ap_regexec(setenvif->pregx, c, AP_MAX_REG_MATCH, regm, 0) == 0) {
              value = ap_pregsub(r->pool, value, c, AP_MAX_REG_MATCH, regm);
            }
          }
          apr_table_set(r->subprocess_env, name, value != NULL ? value : "");
        }
      }
    }
  }
}

/**
 * Setting events based on request payload (query), QS_SetEnvIfParp (hp)
 * @param r
 * @param sconf
 */
static void qos_parp_hp(request_rec *r, qos_srv_config *sconf) {
  const char *query = apr_table_get(r->notes, QS_PARP_Q);
  if((query == NULL) && qos_parp_hp_table_fn) {
    apr_table_t *tl = qos_parp_hp_table_fn(r);
    if(tl) {
      if(apr_table_elts(tl)->nelts > 0) {
        query = qos_parp_query(r, tl, NULL);
        if(query) {
          apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_Q), query);
        }
      }
    } else {
      /* no table provided by mod_parp (unsupported content type?),
         use query string if available */
      if(r->parsed_uri.query) {
        query = r->parsed_uri.query;
      }
    }
  }
  if(query) {
    qos_setenvif_ex(r, query, sconf->setenvifparp_t);
  }
}

/**
 * Replaces ${var} by the value in var
 * @param p Pool for memory allocation
 * @param vars Available variables to lookup
 * @param string String to replace variables
 * @return 1 on success or 0 if string still contains "${"
 */
static int qos_reslove_variable(apr_pool_t *p, apr_table_t *vars, char **string) {
  int i;
  int start;
  int line_end;
  char *var_name;
  char *new_line = *string;
  char *line = *string;
  const char *val;

 once_again:
  i = 0;
  while(line[i] != 0) {
    if((line[i] == '$') && (line[i+1] == '{')) {
      line_end = i;
      i=i+2;
      start = i;
      while((line[i] != 0) && (line[i] != '}')) {
        i++;
      }
      if(line[i] != '}') {
        /* no end found */
        break;
      } else {
        var_name = apr_pstrndup(p, &line[start], i - start);
        val = apr_table_get(vars, var_name);
        if(val) {
          line[line_end] = 0;
          i++;
          new_line = apr_pstrcat(p, line, val, &line[i], NULL);
          line = new_line;
          goto once_again;
        }      
      }
    }
    i++;
  }
  if(!new_line[0] || strstr(new_line, "${")) {
    return 0;
  }
  *string = new_line;
  return 1;
}

/**
 * QS_SetEnvIfQuery (hp)
 * @param r
 * @param sconf
 * @param dconf
 */
static void qos_setenvifquery(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
  qos_setenvif_ex(r, r->parsed_uri.query, sconf->setenvifquery_t);
  qos_setenvif_ex(r, r->parsed_uri.query, dconf->setenvifquery_t);
}

/**
 * QS_SetEnv
 * @param r
 * @param sconf
 */
static void qos_setenv(request_rec *r, qos_srv_config *sconf) {
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->setenv_t)->elts;
  for(i = 0; i < apr_table_elts(sconf->setenv_t)->nelts; i++) {
    char *variable = entry[i].val;
    char *value = apr_pstrdup(r->pool, strchr(entry[i].key, '='));
    value++;
    if(qos_reslove_variable(r->pool, r->subprocess_env, &value)) {
      apr_table_set(r->subprocess_env, variable, value);
    }
  }
}

/**
 * QS_SetReqHeader
 * @param r
 * @param header_t
 */
static void qos_setreqheader(request_rec *r, apr_table_t *header_t) {
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(header_t)->elts;
  for(i = 0; i < apr_table_elts(header_t)->nelts; i++) {
    char *header = entry[i].val;
    char *variable = apr_pstrdup(r->pool, strchr(entry[i].key, '='));
    const char *val;
    variable++;
    val = apr_table_get(r->subprocess_env, variable);
    if(val) {
      if(header[0] == '!') {
        apr_table_unset(r->headers_in, &header[1]);
      } else {
        apr_table_set(r->headers_in, header, val);
      }
    }
  }
}

/**
 * QS_SetEnvIf (hp and logger)
 * @param r
 * @param setenvif_t
 */
static void qos_setenvif(request_rec *r, apr_array_header_t *setenvif_t) {
  int i;
  qos_setenvif_t *entries = (qos_setenvif_t *)setenvif_t->elts;
  for(i = 0; i < setenvif_t->nelts; i++) {
    qos_setenvif_t *setenvif = &entries[i];
    if(setenvif->preg == NULL) {
      // mode 1 (boolean AND operator)
      if((setenvif->variable1[0] == '!') && (setenvif->variable2[0] == '!')) {
        if(!apr_table_get(r->subprocess_env, &setenvif->variable1[1]) &&
           !apr_table_get(r->subprocess_env, &setenvif->variable2[1])) {
          if(setenvif->name[0] == '!') {
            apr_table_unset(r->subprocess_env, &setenvif->name[1]);
          } else {
            apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
          }
        }
      } else if(setenvif->variable1[0] == '!') {
        if(!apr_table_get(r->subprocess_env, &setenvif->variable1[1]) &&
           apr_table_get(r->subprocess_env, setenvif->variable2)) {
          if(setenvif->name[0] == '!') {
            apr_table_unset(r->subprocess_env, &setenvif->name[1]);
          } else {
            apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
          }
        }
      } else if(setenvif->variable2[0] == '!') {
        if(apr_table_get(r->subprocess_env, setenvif->variable1) &&
           !apr_table_get(r->subprocess_env, &setenvif->variable2[1])) {
          if(setenvif->name[0] == '!') {
            apr_table_unset(r->subprocess_env, &setenvif->name[1]);
          } else {
            apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
          }
        }
      } else {
        if(apr_table_get(r->subprocess_env, setenvif->variable1) &&
           apr_table_get(r->subprocess_env, setenvif->variable2)) {
          if(setenvif->name[0] == '!') {
            apr_table_unset(r->subprocess_env, &setenvif->name[1]);
          } else {
            apr_table_set(r->subprocess_env, setenvif->name, setenvif->value);
          }
        }
      }
    } else {
      // mode 2 (pattern match)
      const char *value = apr_table_get(r->subprocess_env, setenvif->variable1);
      if(value) {
        ap_regmatch_t regm[AP_MAX_REG_MATCH];
        if(ap_regexec(setenvif->preg, value, AP_MAX_REG_MATCH, regm, 0) == 0) {
          if(setenvif->name[0] == '!') {
            apr_table_unset(r->subprocess_env, &setenvif->name[1]);
          } else {
            char *replaced = ap_pregsub(r->pool, setenvif->value, value, AP_MAX_REG_MATCH, regm);
            apr_table_set(r->subprocess_env, setenvif->name, replaced);
          }
        }
      }
    }
  }
}

/**
 * QS_RequestHeaderFilter enforcement
 * @param r
 * @param sconf
 * @parm dconf
 * @return
 */
static int qos_hp_header_filter(request_rec *r, qos_srv_config *sconf, qos_dir_config *dconf) {
  qs_headerfilter_mode_e mode = sconf->headerfilter;
  if(dconf->headerfilter > QS_HEADERFILTER_OFF_DEFAULT) {
    // overrides server configuration
    mode = dconf->headerfilter;
  }
  if(mode > QS_HEADERFILTER_OFF) {
    apr_status_t rv = qos_header_filter(r, sconf, r->headers_in, "request",
                                        sconf->hfilter_table, mode);
    if(rv != APR_SUCCESS) {
      int rc;
      const char *error_page = sconf->error_page;
      qs_set_evmsg(r, "D;"); 
      if(!sconf->log_only) {
        rc = qos_error_response(r, error_page);
        if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
          return rc;
        }
        return rv;
      }
    }
  }
  return DECLINED;
}

/**
 * Dynamic keep alive.
 * Creates a copy of the server_rec and adjusts the keep-aliva settings 
 * for this request.
 *
 * @param r
 * @param sconf
 */
static void qos_keepalive(request_rec *r, qos_srv_config *sconf) {
  if(r->subprocess_env) {
    const char *vtmo = apr_table_get(r->subprocess_env, QS_KEEPALIVE);
    const char *vmax = apr_table_get(r->subprocess_env, QS_MAXKEEPALIVEREQ);
    int ka = -1; // keep alive timeout
    int km = -1; // max keep alive requests
    if(vtmo) {
      ka = atoi(vtmo);
      if(ka == 0 && vtmo[0] != '0') {
        ka = -1;
      }
    }
    if(vmax) {
      km = atoi(vmax);
      if(km == 0 && vmax[0] != '0') {
        km = -1;
      }
    }
    if(ka >= 0 || km >= 0) {
      qs_req_ctx *rctx = qos_rctx_config_get(r);
      if(m_event_mpm) {
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, 
                      QOS_LOG_PFX(037)"loaded MPM is 'event'"
                      " and the QS_KeepAliveTimeout/QS_MaxKeepAliveRequests"
                      " directives can't be used.");
        QS_INC_EVENT(sconf, 37);
        return;
      }
      if(QS_ISDEBUG(r->server)) {
        int kaorig = apr_time_sec(r->server->keep_alive_timeout);
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
                      QOS_LOGD_PFX"set keepalive timeout to %d seconds and max"
                      " keepalive requests to %d%s, id=%s",
                      ka >= 0 ? ka :  kaorig,
                      km >= 0 ? km : r->server->keep_alive_max,
                      sconf->log_only ? " (log only)" : "",
                      qos_unique_id(r, NULL));
      }
      /* copy the server record (I know......., but this works) */
      if(!rctx->evmsg || !strstr(rctx->evmsg, "T;")) {
        /* copy it only once (@hp or @out-filter) */
        if(!sconf->log_only) {
          server_rec *sr = apr_pcalloc(r->connection->pool, sizeof(server_rec));
          server_rec *sc = apr_pcalloc(r->connection->pool, sizeof(server_rec));
          memcpy(sr, r->server, sizeof(server_rec));
          memcpy(sc, r->connection->base_server, sizeof(server_rec));
          r->server = sr;
          r->connection->base_server = sc;
        }
        qs_set_evmsg(r, "T;"); 
      }
      if(!sconf->log_only) {
        if(ka >= 0) {
          apr_interval_time_t kat = apr_time_from_sec(ka);
          r->server->keep_alive_timeout = kat;
          r->connection->base_server->keep_alive_timeout = kat;
        }
        if(km >= 0) {
          r->server->keep_alive_max = km;
          r->connection->base_server->keep_alive_max = km;
        }
      }
    }
  }
}

/**
 * QS_EventPerSecLimit
 */
static void qos_lg_event_update(request_rec *r, apr_time_t *t) {
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
                                                                &qos_module);
  qs_actable_t *act = sconf->act;
  if(act->has_events && (apr_table_get(r->notes, QS_R012_ALREADY_BLOCKED) == NULL)) {
    apr_time_t now = apr_time_sec(r->request_time);
    qs_acentry_t *actEntry = act->entry;
    *t = now;
    if(actEntry) {
      apr_global_mutex_lock(act->lock);     /* @CRT13 */
      while(actEntry) {
        if(actEntry->event) {
          if(((actEntry->event[0] != '!') && apr_table_get(r->subprocess_env, actEntry->event)) ||
             ((actEntry->event[0] == '!') && !apr_table_get(r->subprocess_env, &actEntry->event[1]))) {
            if(actEntry->req < LONG_MAX) {
              actEntry->req++;
            }
            if(now > (actEntry->interval + QS_BW_SAMPLING_RATE)) {
              if(actEntry->req_per_sec_limit) {
                /* QS_EventPerSecLimit */
                actEntry->req_per_sec = actEntry->req / (now - actEntry->interval);
                actEntry->req = 0;
                actEntry->interval = now;
                qos_cal_req_sec(sconf, r, actEntry);
              }
            }
          }
        }
        actEntry = actEntry->next;
      }
      apr_global_mutex_unlock(act->lock);   /* @CRT13 */
    }
  }
}

/**
 * QS_EventLimitCount, propagte variable
 */
static void qos_pr_event_limit(request_rec *r, qos_srv_config *sconf) {
  qs_actable_t *act = sconf->act;
  if(act->event_entry && (sconf->event_limit_a->nelts > 0)) {
    int i;
    qos_event_limit_entry_t *entry = act->event_entry;
    apr_time_t now = apr_time_sec(r->request_time);
    apr_global_mutex_lock(act->lock);     /* @CRT46 */
    for(i = 0; i < sconf->event_limit_a->nelts; i++) {
      if(entry->action == QS_EVENT_ACTION_DENY) {
        // propagte to environment (previous value)
        if(entry->limitTime + entry->seconds >= now) {
          apr_table_set(r->subprocess_env,
                        apr_pstrcat(r->pool, entry->env_var, QS_COUNTER_SUFFIX, NULL),
                        apr_psprintf(r->pool, "%d", entry->limit));
        }
      }
      // next rule
      entry++;
    }
    apr_global_mutex_unlock(act->lock);   /* @CRT46 */
  }
}

/**
 * QS_EventLimitCount, detect and enforce
 */
static int qos_hp_event_limit(request_rec *r, qos_srv_config *sconf) {
  apr_status_t rv = DECLINED;
  qs_actable_t *act = sconf->act;
  if(act->event_entry) {
    apr_time_t now = apr_time_sec(r->request_time);
    int i;
    qos_event_limit_entry_t *entry = act->event_entry;
    apr_global_mutex_lock(act->lock);     /* @CRT41 */
    for(i = 0; i < sconf->event_limit_a->nelts; i++) {
      if(entry->action == QS_EVENT_ACTION_DENY) {
        if(apr_table_get(r->subprocess_env, entry->env_var) != NULL) {
          char *eventLimitId = apr_pstrcat(r->pool, QS_R013_ALREADY_BLOCKED, entry->env_var, NULL);
          apr_table_set(r->notes, eventLimitId, "");
          // reset required (expired)?
          if(entry->limitTime + entry->seconds < now) {
            entry->limit = 0;
            entry->limitTime = 0;
          }
          /* increment limit event */
          if(entry->limit < INT_MAX) {
            entry->limit++;
          }
          if(entry->limit == 1) {
            /* ... and start timer */
            entry->limitTime = now;
          }
          // check limit
          if(entry->limit > entry->max) {
            int block = 1;
            char *conditional = "";
            if(entry->condStr != NULL) {
              // conditional enforcement...
              const char *condition = apr_table_get(r->subprocess_env, QS_COND);
              conditional = apr_pstrdup(r->pool, "Cond");
              if(condition == NULL) {
                block = 0; // variable not set
              } else {
                if(ap_regexec(entry->preg, condition, 0, NULL, 0) != 0) {
                  block = 0; // pattern does not match
                }
              }
            }
            if(block) {
              rv = m_retcode;
              ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                            QOS_LOG_PFX(013)"access denied%s,"
                            " QS_%sEventLimitCount rule: %s,"
                            " max=%d, current=%d,"
                            " c=%s, id=%s",
                            sconf->log_only ? " (log only)" : "",
                            conditional,
                            entry->env_var, entry->max, entry->limit,
                            QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                            qos_unique_id(r, "013"));
              QS_INC_EVENT_LOCKED(sconf, 13);
            }
          }
        }
        // propagte to environment (current)
        apr_table_set(r->subprocess_env,
                      apr_pstrcat(r->pool, entry->env_var, QS_COUNTER_SUFFIX, NULL),
                      apr_psprintf(r->pool, "%d", entry->limit));
      }
      // next rule
      entry++;
    }
    apr_global_mutex_unlock(act->lock);   /* @CRT41 */
  }
  if(rv != DECLINED) {
    int rc;
    const char *error_page = sconf->error_page;
    qs_set_evmsg(r, "D;"); 
    if(!sconf->log_only) {
      rc = qos_error_response(r, error_page);
      if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
        rv = rc;
      }
    } else {
      return DECLINED;
    }
  }
  return rv;
}

/**
 * QS_EventRequestLimit
 */
static int qos_hp_event_filter(request_rec *r, qos_srv_config *sconf) {
  apr_status_t rv = DECLINED;
  qs_req_ctx *rctx = qos_rctx_config_get(r);
  qs_actable_t *act = sconf->act;
  if(act->has_events) {
    qs_acentry_t *actEntry = act->entry;
    if(actEntry) {
      apr_global_mutex_lock(act->lock);   /* @CRT31 */
      while(actEntry) {
        if(actEntry->event && (actEntry->limit != -1)) {
          const char *var = apr_table_get(r->subprocess_env, actEntry->event);
          if(var) {
            int match = 1;
            if(actEntry->regex_var) {
              if(ap_regexec(actEntry->regex_var, var, 0, NULL, 0) != 0) {
                match = 0;
              }
            }
            if(match) {
              apr_table_addn(rctx->event_entries, actEntry->url, (char *)actEntry);
              if(actEntry->counter < INT_MAX) {
                actEntry->counter++;
              }
              if(actEntry->counter > actEntry->limit) {
                rv = m_retcode;
                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                              QOS_LOG_PFX(012)"access denied%s,"
                              " QS_EventRequestLimit rule: %s(%d),"
                              " concurrent requests=%d,"
                              " c=%s, id=%s",
                              sconf->log_only ? " (log only)" : "",
                              actEntry->url, actEntry->limit, actEntry->counter,
                              QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : QS_CONN_REMOTEIP(r->connection),
                              qos_unique_id(r, "012"));
                apr_table_set(r->notes, QS_R012_ALREADY_BLOCKED, "");
                QS_INC_EVENT_LOCKED(sconf, 12);
              }
              apr_table_add(r->subprocess_env,
                            apr_psprintf(r->pool, "QS_EventRequestLimit_%s_Counter", actEntry->event),
                            apr_psprintf(r->pool, "%d", actEntry->counter));
            }
          }
        }
        actEntry = actEntry->next;
      }
      apr_global_mutex_unlock(act->lock); /* @CRT31 */
    }
  }
  if(rv != DECLINED) {
    int rc;
    const char *error_page = sconf->error_page;
    qs_set_evmsg(r, "D;"); 
    if(!sconf->log_only) {
      rc = qos_error_response(r, error_page);
      if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
        rv = rc;
      }
    } else {
      return DECLINED;
    }
  }
  return rv;
}

static qs_conn_ctx *qos_get_cconf(conn_rec *connection) {
  conn_rec *c = QS_CONN_MASTER(connection);
  qs_conn_ctx *cconf = NULL;
  qs_conn_base_ctx *base = qos_get_conn_base_ctx(c);
  if(base) {
    cconf = base->cconf;
  }
  return cconf;
}

static qs_conn_ctx *qos_create_cconf(conn_rec *connection, qos_srv_config *sconf) {
  conn_rec *c = QS_CONN_MASTER(connection);
  qs_conn_base_ctx *base = qos_get_conn_base_ctx(c);
  qs_conn_ctx *cconf = apr_pcalloc(c->pool, sizeof(qs_conn_ctx));
  cconf->mc = c;
  cconf->ip6[0] = 0;
  cconf->ip6[1] = 0;
  cconf->evmsg = NULL;
  cconf->sconf = sconf;
  cconf->is_vip = 0;
  cconf->set_vip_by_header = 0;
  cconf->has_lowrate = 0;
  apr_pool_pre_cleanup_register(c->pool, cconf, qos_cleanup_conn);
  if(base == NULL) {
    base = qos_create_conn_base_ctx(c, sconf);
  }
  base->cconf = cconf;
  return cconf;  
}

/*
 * QS_SrvSerialize
 */
static void qos_hp_srv_serialize(request_rec *r, qos_srv_config *sconf, 
                                 qs_req_ctx * rctx) {
  int loops = 0;
  int locked = 0; // we got the lock for this request
  if(!rctx) {
    rctx = qos_rctx_config_get(r);
  }
  while(!locked) {
    apr_global_mutex_lock(sconf->act->lock);   /* @CRT44 */
    if(sconf->act->serialize->locked == 0) {
      // free!! check if we might get the lock for this request
      if(sconf->act->serialize->q1 == 0) {
        // yes: no other request waiting
        locked = 1;
      } else if(sconf->act->serialize->q1 == r->request_time) {
        // yes: waiting for this request (we are the next in the queue)
        locked = 1;
        sconf->act->serialize->q1 = sconf->act->serialize->q2;
        sconf->act->serialize->q2 = 0;
      } else if(sconf->act->serialize->q1 > r->request_time) {
        // yes: not yet in the queue but this request is waiting for a longer time
        //      keep the others in the queue
        locked = 1;
      } else if(sconf->act->serialize->q2 == 0 || sconf->act->serialize->q2 > r->request_time) {
        // no: it's not yet our time... but take the second place in the queue
        sconf->act->serialize->q2 = r->request_time;
      }
    } else {
      // put this request into one of the queues if possible
      if(sconf->act->serialize->q1 == 0) {
        // no other request waiting
        sconf->act->serialize->q1 = r->request_time;
      } else if(sconf->act->serialize->q1 == r->request_time) {
        // already next in the queue
      } else if(sconf->act->serialize->q1 > r->request_time) {
        // older request is waiting, take over
        sconf->act->serialize->q2 = sconf->act->serialize->q1;
        sconf->act->serialize->q1 = r->request_time;
      } else if(sconf->act->serialize->q2 == 0) {
        // no one in on the second place
        sconf->act->serialize->q2 = r->request_time;
      } else if(sconf->act->serialize->q2 == r->request_time) {
        // already next in the queue
      } else if(sconf->act->serialize->q2 > r->request_time) {
        // older request is waiting in the second position, take over
        sconf->act->serialize->q2 = r->request_time;
      }
    }
    if(locked) {
        sconf->act->serialize->locked = 1;
        rctx->srv_serialize_set = 1;
    }
    apr_global_mutex_unlock(sconf->act->lock);   /* @CRT44 */
    if(!locked) {
      /* sleep 50ms */
      qs_set_evmsg(r, "s;"); 
      if(sconf->log_only) {
        return;
      }
      apr_sleep(50000);
    }
    if(loops >= sconf->serializeTMO) {
      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
                    QOS_LOG_PFX(068)"QS_SrvSerialize exceeds limit of %d seconds, "
                    "id=%s",
                    sconf->serializeTMO / 20,
                    qos_unique_id(r, "037"));
      QS_INC_EVENT(sconf, 37);
      /* remove this request from the queue resp. clear the queue
         to avoid a deadlock */
      apr_global_mutex_lock(sconf->act->lock);     /* @CRT44.1 */
      sconf->act->serialize->q2 = 0;
      sconf->act->serialize->q1 = 0;
      apr_global_mutex_unlock(sconf->act->lock);   /* @CRT44.1 */
      break;
    }
    loops++;
  }
}

/*
 * QS_ClientSerialize
 */
static void qos_hp_cc_serialize(request_rec *r, qos_srv_config *sconf, qs_req_ctx * rctx) {
  qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
  qs_conn_ctx *cconf = qos_get_cconf(r->connection);
  if(!rctx) {
    rctx = qos_rctx_config_get(r);
  }
  if(u && cconf) {
    int loops = 0;
    int locked = 0;
    qos_s_entry_t searchE;
    const char *forwardedForLogIP = qos_get_clientIP(r, sconf, cconf,
                                                     "hp", rctx->cc_serialize_ip);
    searchE.ip6[0] = rctx->cc_serialize_ip[0];
    searchE.ip6[1] = rctx->cc_serialize_ip[1];

    /* wait until we get a lock */
    while(!locked) {
      qos_s_entry_t **clientEntry = NULL;
      apr_global_mutex_lock(u->qos_cc->lock);          /* @CRT36 */
      clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
      if((*clientEntry)->serialize == 0) {
        // free! check if this request is the next in the queue */
        if(((*clientEntry)->serializeQueue == 0) || (r->request_time <= (*clientEntry)->serializeQueue)) {
          (*clientEntry)->serialize = 1;
          (*clientEntry)->serializeQueue = 0;
          rctx->cc_serialize_set = 1;
          locked = 1;
        }
      } else {
        // put the request into the queue
        if((*clientEntry)->serializeQueue == 0) {
          // the only waiting req
          (*clientEntry)->serializeQueue = r->request_time;
        } else {
          if((*clientEntry)->serializeQueue > r->request_time) {
            // this request is waiting for a longer time
            (*clientEntry)->serializeQueue = r->request_time;
          }
        }
      }
      apr_global_mutex_unlock(u->qos_cc->lock);        /* @CRT36 */   
      if(!locked) {
        /* sleep 100ms */
        qs_set_evmsg(r, "s;"); 
        if(sconf->log_only) {
          return;
        }
        apr_sleep(100000);
      }
      // max wait time: 5 minutes
      if(loops >= 3000) {
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
                      QOS_LOG_PFX(068)"QS_ClientSerialize exceeds limit of 5 minutes, "
                      "c=%s, id=%s",
                      forwardedForLogIP == NULL ? "-" : forwardedForLogIP,
                      qos_unique_id(r, "068"));
        QS_INC_EVENT(sconf, 68);
        /* remove this request from the queue resp. clear the queue
           to avoid a deadlock */
        apr_global_mutex_lock(u->qos_cc->lock);          /* @CRT36.1 */
        clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
        (*clientEntry)->serializeQueue = 0;
        apr_global_mutex_unlock(u->qos_cc->lock);        /* @CRT36.1 */      
        break;
      }
      loops++;
    }
  }
}

/*
 * QS_ClientEventRequestLimit
 */
static int qos_hp_cc_event_count(request_rec *r, qos_srv_config *sconf, 
                                 qs_req_ctx * rctx) {
  qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
  qs_conn_ctx *cconf = qos_get_cconf(r->connection);
  if(!rctx) {
    rctx = qos_rctx_config_get(r);
  }
  if(u && cconf &&
     r->subprocess_env && apr_table_get(r->subprocess_env, "QS_EventRequest")) {
    int vip = 0;
    int count = 0;
    qos_s_entry_t **clientEntry = NULL;
    qos_s_entry_t searchE;
    const char *forwardedForLogIP = qos_get_clientIP(r, sconf, cconf,
                                                     "hp", rctx->cc_event_ip);
    
    rctx->cc_event_req_set = 1;
    searchE.ip6[0] = rctx->cc_event_ip[0];
    searchE.ip6[1] = rctx->cc_event_ip[1];
    apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT33 */
    clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
    if((*clientEntry)->event_req < INT_MAX) {
      (*clientEntry)->event_req++;
    }
    count = (*clientEntry)->event_req;
    if((*clientEntry)->vip || rctx->is_vip) {
      vip = 1;
    }
    apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT33 */
    if(vip) {
      apr_table_set(r->subprocess_env, QS_ISVIPREQ, "yes");
    }
    if(count > sconf->qos_cc_event_req) {
      if(vip) {
        qs_set_evmsg(r, "S;"); 
      } else {
        int rc;
        const char *error_page = sconf->error_page;
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                      QOS_LOG_PFX(065)"access denied%s,"
                      " QS_ClientEventRequestLimit rule:"
                      " max=%d, current=%d, c=%s, id=%s",
                      sconf->log_only ? " (log only)" : "",
                      sconf->qos_cc_event_req,
                      count,
                      forwardedForLogIP == NULL ? "-" : 
                      forwardedForLogIP,
                      qos_unique_id(r, "065"));
        QS_INC_EVENT(sconf, 65);
        qs_set_evmsg(r, "D;"); 
        if(!sconf->log_only) {
          rc = qos_error_response(r, error_page);
          if((rc == DONE) || (rc == HTTP_MOVED_TEMPORARILY)) {
            return rc;
          }
          return m_retcode;
        }
      }
    }
  }
  return DECLINED;
}

/* QS_SetEnvIfCmp */
static void qos_setenvifcmp(request_rec *r, apr_array_header_t *cmps) {
  int i;
  qos_cmp_entry_t *entries = (qos_cmp_entry_t *)cmps->elts;
  for(i = 0; i < cmps->nelts; ++i) {
    qos_cmp_entry_t *b = &entries[i];
    const char *leftStr = apr_table_get(r->subprocess_env, b->left);
    const char *rightStr = apr_table_get(r->subprocess_env, b->right);
    if(leftStr != NULL && rightStr != NULL) {
      int set = 0;
      if(qos_isnum(leftStr) && qos_isnum(rightStr)) {
        int left = atoi(leftStr);
        int right = atoi(rightStr);
        switch (b->cmp) {
        case QS_CMP_EQ:
          if(left == right) {
            set = 1;
          }
          break;
        case QS_CMP_NE:
          if(left != right) {
            set = 1;
          }
          break;
        case QS_CMP_GT:
          if(left > right) {
            set = 1;
          }
          break;
        case QS_CMP_LT:
          if(left < right) {
            set = 1;
          }
          break;
        }
      } else {
        int c = strcasecmp(leftStr, rightStr);
        switch (b->cmp) {
        case QS_CMP_EQ:
          if(c == 0) {
            set = 1;
          }
          break;
        case QS_CMP_NE:
          if(c != 0) {
            set = 1;
          }
          break;
        case QS_CMP_GT:
          if(c < 0) {
            set = 1;
          }
          break;
        case QS_CMP_LT:
          if(c > 0) {
            set = 1;
          }
          break;
        }
      }
      if(set) {
        if(b->variable[0] == '!') {
          apr_table_unset(r->subprocess_env, &b->variable[1]);
        } else {
          apr_table_set(r->subprocess_env, b->variable, b->value);
        }
      }
    }
  }
  return;
}

/*
 * QS_EventPerSecLimit/QS_EventKBytesPerSecLimit
 * returns the max req_per_sec_block_rate/kbytes_per_sec_limit and the event
 *         with the lowest kbytes_per_sec_limit.
 */
static qs_acentry_t *qos_hp_event_count(request_rec *r, 
                                        int *req_per_sec_block, 
                                        apr_off_t *kbytes_per_sec_limit) {
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
                                                                &qos_module);
  qs_actable_t *act = sconf->act;
  qs_acentry_t *event_kbytes_limit = NULL;
  *req_per_sec_block = 0;
  *kbytes_per_sec_limit = 0;
  if(act->has_events) {
    qs_acentry_t *actEntry = act->entry;
    if(actEntry) {
      apr_global_mutex_lock(act->lock);   /* @CRT12 */
      while(actEntry) {
        if(actEntry->event && (actEntry->limit == -1)) {
          if(((actEntry->event[0] != '!') && apr_table_get(r->subprocess_env, actEntry->event)) ||
             ((actEntry->event[0] == '!') && !apr_table_get(r->subprocess_env, &actEntry->event[1]))) {
            if(actEntry->req_per_sec_limit) {
              /* QS_EventPerSecLimit */
              if(actEntry->req_per_sec_block_rate > *req_per_sec_block) {
                *req_per_sec_block = actEntry->req_per_sec_block_rate;
              }
            } else {
              /* QS_EventKBytesPerSecLimit */
              if(actEntry->kbytes_per_sec_limit) {
                if((*kbytes_per_sec_limit == 0) ||
                   (actEntry->kbytes_per_sec_limit < *kbytes_per_sec_limit)) {
                  *kbytes_per_sec_limit = actEntry->kbytes_per_sec_limit;
                  event_kbytes_limit = actEntry;
                }
              }
            }
          }
        }
        actEntry = actEntry->next;
      }
      apr_global_mutex_unlock(act->lock); /* @CRT12 */
    }
  }
  return event_kbytes_limit;
}

static apr_size_t qos_packet_rate(qos_ifctx_t *inctx, apr_bucket_brigade *bb) {
  apr_bucket *b;
  apr_size_t total = 0;
  for(b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
    if(b->length) {
      total = total + b->length;
    }
  }
  return total;
}

/**
 * start packet rate measure (if filter has not already been inserted)
 */
static void qos_pktrate_pc(conn_rec *connection, qos_srv_config *sconf) {
  conn_rec *c = connection;
  qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
  if(inctx == NULL) {
    inctx = qos_create_ifctx(c, sconf);
    ap_add_input_filter("qos-in-filter", inctx, NULL, c);
  }
  inctx->lowrate = 0;
}

/**
 * timeout control at process connection handler
 */
static void qos_timeout_pc(conn_rec *connection, qos_srv_config *sconf) {
  conn_rec *c = connection;
  qos_ifctx_t *inctx = qos_get_ifctx(c->input_filters);
  if(inctx) {
    inctx->status = QS_CONN_STATE_HEAD;
    inctx->time = time(NULL);
    inctx->nbytes = 0;
#if APR_HAS_THREADS
    if(sconf->inctx_t && !sconf->inctx_t->exit && sconf->min_rate_off == 0) {
      apr_thread_mutex_lock(sconf->inctx_t->lock);     /* @CRT22 */
      apr_table_setn(sconf->inctx_t->table,
                     QS_INCTX_ID,
                     (char *)inctx);
      apr_thread_mutex_unlock(sconf->inctx_t->lock);   /* @CRT22 */
    }
#endif
  }
}

/** 
 * determines client behavior based on accessed content types
 *
 * @return 0=normal, -1=unknown (not enough data), >=1 abnormal
 */
static int qos_content_type(request_rec *r, qos_srv_config *sconf,
                            qos_s_t *s, qos_s_entry_t *e, int limit) {
  int penalty = -1;
  const char *ct = apr_table_get(r->headers_out, "Content-Type");
  e->events++; // events counts requests and connections
  if(r->status == 304) {
    e->notmodified ++;
    s->notmodified ++;
  }
  if(ct) {
    if(ap_strcasestr(ct, "html")) {
      e->events++; // learn faster if user requests HTML content (main pages)
      e->html++;
      s->html++;
      goto end;
    } else if(ap_strcasestr(ct, "image")) {
      e->img++;
      s->img++;
      goto end;
    } else if(ap_strcasestr(ct, "css")) {
      e->cssjs++;
      s->cssjs++;
      goto end;
    } else if(ap_strcasestr(ct, "javascript")) {
      e->cssjs++;
      s->cssjs++;
      goto end;
    }
  }
  e->other++;
  s->other++;

 end:
  /* compare this client with other clients */
  if(e->events > QOS_CC_BEHAVIOR_THR_SINGLE) {
    penalty = 0;
    if(limit &&
       ((sconf->static_on == 1) ||
        (s->html > QOS_CC_BEHAVIOR_THR && s->html && s->img && s->cssjs && s->other && s->notmodified))) {
      int i;
      unsigned int server[5];
      unsigned int client[5];
      // note: all e->* variables are initialized by "1" to avoid FPE
      if(sconf->static_on == 1) {
        /* use predefined value */
        unsigned long e_all = e->html + e->img + e->cssjs + e->other + e->notmodified;
        server[0] = sconf->static_html;
        server[1] = sconf->static_cssjs;
        server[2] = sconf->static_img;
        server[3] = sconf->static_other;
        server[4] = sconf->static_notmodified;
        client[0] = 100 * e->html / e_all;
        client[1] = 100 * e->cssjs / e_all;
        client[2] = 100 * e->img / e_all;
        client[3] = 100 * e->other / e_all;
        client[4] = 100 * e->notmodified / e_all;
      } else {
        /* learn average */
        unsigned long long s_all = s->html + s->img + s->cssjs + s->other + s->notmodified;
        unsigned long e_all = e->html + e->img + e->cssjs + e->other + e->notmodified;
        server[0] = 100 * s->html / s_all;
        server[1] = 100 * s->cssjs / s_all;
        server[2] = 100 * s->img / s_all;
        server[3] = 100 * s->other / s_all;
        server[4] = 100 * s->notmodified / s_all;
        client[0] = 100 * e->html / e_all;
        client[1] = 100 * e->cssjs / e_all;
        client[2] = 100 * e->img / e_all;
        client[3] = 100 * e->other / e_all;
        client[4] = 100 * e->notmodified / e_all;
      }
      for(i = 0; i < 5; i++) {
        if(client[i] > (server[i] + sconf->cc_tolerance)) {
          penalty++;
        } else {
          if((server[i] > sconf->cc_tolerance) &&
             (client[i] < (server[i] - sconf->cc_tolerance))) {
            penalty++;
          }
        }
      }
    }
  }
  return penalty;
}

//static void qos_error_log(const char *file, int line, int level,
//                          apr_status_t status, const server_rec *s,
//                          const request_rec *r, apr_pool_t *pool,
//                          const char *errstr) {
//  return;
//}

/**
 * QS_EventLimitCount, detect/update only
 */
static void qos_logger_event_limit(request_rec *r, qos_srv_config *sconf) {
  qs_actable_t *act = sconf->act;
  if(act->event_entry && (sconf->event_limit_a->nelts > 0)) {
    apr_time_t now = apr_time_sec(r->request_time);
    int i;
    qos_event_limit_entry_t *actEntry = act->event_entry;
    apr_global_mutex_lock(act->lock);     /* @CRT42 */
    for(i = 0; i < sconf->event_limit_a->nelts; i++) {
      if(actEntry->action == QS_EVENT_ACTION_DENY) {
        int decEvent = get_qs_event(r, actEntry->eventDecStr);
        if(decEvent > 0) {
          if(decEvent >= actEntry->limit) {
            actEntry->limit = 0;
            actEntry->limitTime = 0;
          } else {
            actEntry->limit = actEntry->limit - decEvent;
          }
        }
        if(apr_table_get(r->subprocess_env, actEntry->env_var) != NULL) {
          // increment only once
          char *eventLimitId = apr_pstrcat(r->pool, QS_R013_ALREADY_BLOCKED, actEntry->env_var, NULL);
          if(apr_table_get(r->notes, eventLimitId) == NULL) {
            // reset required (expired)?
            if(actEntry->limitTime + actEntry->seconds < now) {
              actEntry->limit = 0;
              actEntry->limitTime = 0;
            }
            /* increment limit event */
            if(actEntry->limit < INT_MAX) {
              actEntry->limit++;
            }
            if(actEntry->limit == 1) {
              /* ... and start timer */
              actEntry->limitTime = now;
            }
          }
        }
      }
      // next rule
      actEntry++;
    }
    apr_global_mutex_unlock(act->lock);   /* @CRT42 */
  }
}

/**
 * client control rules at log transaction
 */
static void qos_logger_cc(request_rec *r, qos_srv_config *sconf, qs_req_ctx *rctx) {
  int lowrate = 0;
  int unusual_behavior = -1;
  int block_event = get_qs_event(r, QS_BLOCK);
  const char *block_seen = apr_table_get(r->subprocess_env, QS_BLOCK_SEEN);
  int block_dec = get_qs_event(r, QS_BLOCK_DEC);
  qs_conn_ctx *cconf = qos_get_cconf(r->connection);
  qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
  apr_time_t now = apr_time_sec(r->request_time);
  qos_s_entry_t **clientEntry = NULL;
  qos_s_entry_t **clientEntryFromHdr = NULL; // client ip entry from header
  qos_s_entry_t searchE;
  qos_s_entry_t searchEFromHdr;
 
  if(block_seen != NULL) {
    block_event = 0;
  }

  if(sconf->qos_cc_prefer_limit || (sconf->req_rate != -1)) {
    qos_ifctx_t *inctx = qos_get_ifctx(r->connection->input_filters);
    if(inctx) {
      if(inctx->lowrate > QS_PKT_RATE_TH) {
        lowrate = inctx->lowrate;
      }
      if(inctx->lowrate != -1) {
        inctx->lowrate = 0;
      }
      if(inctx->status > QS_CONN_STATE_NEW) {
        inctx->r = NULL;
        inctx->status = QS_CONN_STATE_KEEP;
      }
      if(inctx->shutdown) {
        lowrate++;
        inctx->shutdown = 0;
      }
    }
  }

  if(cconf) {
    // works for real connections only (no HTTP/2)
    searchE.ip6[0] = cconf->ip6[0];
    searchE.ip6[1] = cconf->ip6[1];
  } else {
    // HTTP/2
    apr_uint64_t ci6[2];
    qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), ci6);
    searchE.ip6[0] = ci6[0];
    searchE.ip6[1] = ci6[1];
  }
  qos_get_clientIP(r, sconf, cconf, "logger", searchEFromHdr.ip6);

  apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT19 */
  clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, apr_time_sec(r->request_time));
  if(searchEFromHdr.ip6[0] == searchE.ip6[0] && searchEFromHdr.ip6[1] == searchE.ip6[1]) {
    clientEntryFromHdr = clientEntry; // same as connection
  } else {
    clientEntryFromHdr = qos_cc_getOrSet(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
  }

  if(rctx->cc_event_req_set) {
    /* QS_ClientEventRequestLimit */
    qos_s_entry_t **eEvent = NULL;
    rctx->cc_event_req_set = 0;
    if(rctx->cc_event_ip[0] == searchE.ip6[0] &&
       rctx->cc_event_ip[1] == searchE.ip6[1]) {
      // connection ip
      eEvent = clientEntry;
    } else if(rctx->cc_event_ip[0] == searchEFromHdr.ip6[0] &&
              rctx->cc_event_ip[1] == searchEFromHdr.ip6[1]) {
      // from header
      eEvent = clientEntryFromHdr;
    } else {
      // looks like the header has changed or is no longer available
      qos_s_entry_t searchEvent;
      searchEvent.ip6[0] = rctx->cc_event_ip[0];
      searchEvent.ip6[1] = rctx->cc_event_ip[1];
      eEvent = qos_cc_get0(u->qos_cc, &searchEvent, apr_time_sec(r->request_time));
    }
    if(eEvent) {
      if((*eEvent)->event_req > 0) {
        (*eEvent)->event_req--;
      }
    }
  }

  if(rctx->cc_serialize_set) {
    /* QS_ClientSerialize */
    qos_s_entry_t **eSerialize = NULL;
    rctx->cc_serialize_set = 0;
    if(rctx->cc_serialize_ip[0] == searchE.ip6[0] &&
       rctx->cc_serialize_ip[1] == searchE.ip6[1]) {
      // connection ip
      eSerialize = clientEntry;
    } else if(rctx->cc_serialize_ip[0] == searchEFromHdr.ip6[0] &&
              rctx->cc_serialize_ip[1] == searchEFromHdr.ip6[1]) {
      // from header
      eSerialize = clientEntryFromHdr;
    } else {
      // looks like the header has changed or is no longer available
      qos_s_entry_t searchSerialize;
      searchSerialize.ip6[0] = rctx->cc_serialize_ip[0];
      searchSerialize.ip6[1] = rctx->cc_serialize_ip[1];
      eSerialize = qos_cc_get0(u->qos_cc, &searchSerialize, apr_time_sec(r->request_time));
    }
    if(eSerialize) {
      (*eSerialize)->serialize = 0;
    }
  }
  
  if(sconf->qos_cc_prefer) {
    // QS_ClientPrefer is not enabled
    unusual_behavior = qos_content_type(r, sconf, u->qos_cc, *clientEntry, sconf->qos_cc_prefer_limit);
    if(unusual_behavior == 0) {
      // normal behavior
      (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_BEHAVIOR_OK;
      (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_BAD;
    } else {
      // unknown or bad behavior
      (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_OK;
    }
  }

  if(block_event || block_dec || lowrate || (unusual_behavior > 0)) {
    if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) < now) {
      /* reset expired events */
      if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
        // write remaining log lines
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->connection->base_server,
                     QOS_LOG_PFX(060)"access denied (previously), "
                     "QS_ClientEventBlockCount rule: "
                     "max=%d, current=%hu, "
                     "message repeated %d times, "
                     "c=%s",
                     sconf->qos_cc_block,
                     (*clientEntry)->block,
                     (*clientEntry)->blockMsg % QS_LOG_REPEAT,
                     QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : 
                     QS_CONN_REMOTEIP(r->connection)/*no id here, this r is okay*/);
        QS_INC_EVENT_LOCKED(sconf, 60);
        (*clientEntry)->blockMsg = 0;
      }
      (*clientEntry)->block = 0;
      (*clientEntry)->blockTime = 0;
    }
    /* mark lowpkt client */
    if(lowrate || (unusual_behavior > 0)) {
      (*clientEntry)->lowrate = apr_time_sec(r->request_time);
      if(lowrate) {
        (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_PKGRATE;
      }
      if(unusual_behavior > 1) {
        (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_BEHAVIOR_BAD;
        (*clientEntry)->lowratestatus &= ~QOS_LOW_FLAG_BEHAVIOR_OK;
      }
      qs_set_evmsg(r, "r;"); 
    }
    if(block_dec > 0) {
      if(block_dec >= (*clientEntry)->block) {
        (*clientEntry)->block = 0;
        (*clientEntry)->blockTime = 0;
      } else {
        (*clientEntry)->block = (*clientEntry)->block - block_dec;
      }
    }
    if(block_event) {
      int newValue = (*clientEntry)->block + block_event;
      if((*clientEntry)->block == 0) {
        /* start timer */
        (*clientEntry)->blockTime = now;
      }
      /* ...and increment/increase block event counter */
      (*clientEntry)->block = newValue > USHRT_MAX ? USHRT_MAX : newValue;
    }
  } else if((*clientEntry)->lowrate) {
    /* reset low prio client after 24h (resp. QOS_LOW_TIMEOUT seconds) */
    if(((*clientEntry)->lowrate + QOS_LOW_TIMEOUT) < now) {
      (*clientEntry)->lowrate = 0;
      if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
        (*clientEntry)->lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK;
      } else {
        (*clientEntry)->lowratestatus = 0;
      }
    }
  }

  /* QS_Limit* */
  if(u->qos_cc->limitTable) {
    int limitTableIndex;
    apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
    for(limitTableIndex = 0; 
        limitTableIndex < apr_table_elts(u->qos_cc->limitTable)->nelts;
        limitTableIndex++) {
      int eventSet = 0;
      const char *eventName = limitTableEntry[limitTableIndex].key;
      qos_s_entry_limit_conf_t *eventLimitConf = (qos_s_entry_limit_conf_t *)limitTableEntry[limitTableIndex].val;
      const char *clearEvent = apr_table_get(r->subprocess_env, eventLimitConf->eventClearStr);
      int decEvent = get_qs_event(r, eventLimitConf->eventDecStr);

      /*
       * reset expired events, clear event counter, decrement event counter
       */
      if(clearEvent ||
         (((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) < now)) {
        (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
        (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
      }
      if(decEvent > 0) {
        if(decEvent >= (*clientEntryFromHdr)->limit[limitTableIndex].limit) {
          (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
          (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
        } else {
          (*clientEntryFromHdr)->limit[limitTableIndex].limit = (*clientEntryFromHdr)->limit[limitTableIndex].limit - decEvent;
        }
      }

      /*
       * check for new events
       */
      eventSet = get_qs_event(r, eventName);
      if(eventSet) {
        char *seenEvent;
        if(strcasecmp(eventName, QS_LIMIT_DEFAULT) == 0) {
          // backward compat/event forwarding
          seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, NULL);
        } else {
          seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, eventName, NULL);
        }
        if(apr_table_get(r->subprocess_env, seenEvent) == NULL) {
          int newValue = (*clientEntryFromHdr)->limit[limitTableIndex].limit + eventSet;
          /* only once per request */
          apr_table_set(r->subprocess_env, seenEvent, "");
          if((*clientEntryFromHdr)->limit[limitTableIndex].limit == 0) {
            /* start timer... */
            (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = now;
          }
          /* ... and increase limit event */
          (*clientEntryFromHdr)->limit[limitTableIndex].limit = newValue > USHRT_MAX ? USHRT_MAX : newValue;
        }
      }
    }
  }
  apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT19 */
  if(block_event) {
    /* only once per request */
    apr_table_set(r->subprocess_env, QS_BLOCK_SEEN, "");
    apr_table_set(r->connection->notes, QS_BLOCK_SEEN, "");
  }
}

/**
 * client control rules at header parser
 */
static int qos_hp_cc(request_rec *r, qos_srv_config *sconf, char **msg, char **uid) {
  int ret = DECLINED;
  if(sconf->has_qos_cc) {
    int req_per_sec_block_rate = 0;
    qos_s_entry_t **clientEntry = NULL;
    qos_s_entry_t **clientEntryFromHdr = NULL;
    qos_s_entry_t searchE;
    qos_s_entry_t searchEFromHdr;
    qs_conn_ctx *cconf = qos_get_cconf(r->connection);
    const char *forwardedForLogIP = QS_CONN_REMOTEIP(r->connection);
    qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
    int excludeFromBlock = qos_is_excluded_ip(r->connection, sconf->cc_exclude_ip);
    if(cconf) {
      searchE.ip6[0] = cconf->ip6[0];
      searchE.ip6[1] = cconf->ip6[1];
    } else {
      // HTTP/2
      apr_uint64_t ci6[2];
      qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), ci6);
      searchE.ip6[0] = ci6[0];
      searchE.ip6[1] = ci6[1];
    }

    forwardedForLogIP = qos_get_clientIP(r, sconf, cconf, "hp",
                                         searchEFromHdr.ip6);

    apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT17 */
    clientEntry = qos_cc_get0(u->qos_cc, &searchE, apr_time_sec(r->request_time));
    if(!clientEntry) {
      clientEntry = qos_cc_set(u->qos_cc, &searchE, apr_time_sec(r->request_time));
    } else {
      /* update time */
      (*clientEntry)->time = apr_time_sec(r->request_time);
    }
    if(searchEFromHdr.ip6[0] == searchE.ip6[0] && searchEFromHdr.ip6[1] == searchE.ip6[1]) {
      clientEntryFromHdr = clientEntry; // same as connection
    } else {
      clientEntryFromHdr = qos_cc_get0(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
      if(!clientEntryFromHdr) {
        clientEntryFromHdr = qos_cc_set(u->qos_cc, &searchEFromHdr, apr_time_sec(r->request_time));
      } else {
        /* update time */
        (*clientEntryFromHdr)->time = apr_time_sec(r->request_time);
      }
    }
    if(sconf->qos_cc_event) {
      apr_time_t now = apr_time_sec(r->request_time);
      const char *v = apr_table_get(r->subprocess_env, QS_EVENT);
      if(v) {
        if((*clientEntry)->req < LONG_MAX) {
          (*clientEntry)->req++;
        }
        if(now > (*clientEntry)->interval + QS_BW_SAMPLING_RATE) {
          /* calc req/sec */
          (*clientEntry)->req_per_sec = (*clientEntry)->req / (now - (*clientEntry)->interval);
          (*clientEntry)->req = 0;
          (*clientEntry)->interval = now;
          /* calc block rate */
          if((*clientEntry)->req_per_sec > sconf->qos_cc_event) {
            int factor = (((*clientEntry)->req_per_sec * 100) / sconf->qos_cc_event) - 100;
            (*clientEntry)->req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate + factor;
            if((*clientEntry)->req_per_sec_block_rate > QS_MAX_DELAY/1000) {
              (*clientEntry)->req_per_sec_block_rate = QS_MAX_DELAY/1000;
            }
            /* QS_ClientEventPerSecLimit */
            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
                          QOS_LOG_PFX(061)"request rate limit,"
                          " rule: "QS_EVENT"(%d), req/sec=%ld,"
                          " delay=%dms%s",
                          sconf->qos_cc_event,
                          (*clientEntry)->req_per_sec, (*clientEntry)->req_per_sec_block_rate,
                          (*clientEntry)->req_per_sec_block_rate == QS_MAX_DELAY/1000 ? " (max)" : "");
            QS_INC_EVENT_LOCKED(sconf, 61);
          } else if((*clientEntry)->req_per_sec_block_rate > 0) {
            if((*clientEntry)->req_per_sec_block_rate < 50) {
              (*clientEntry)->req_per_sec_block_rate = 0;
            } else {
              int factor = (*clientEntry)->req_per_sec_block_rate / 4;
              (*clientEntry)->req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate - factor;
            }
            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
                          QOS_LOG_PFX(062)"request rate limit,"
                          " rule: "QS_EVENT"(%d), req/sec=%ld,"
                          " delay=%dms",
                          sconf->qos_cc_event,
                          (*clientEntry)->req_per_sec, (*clientEntry)->req_per_sec_block_rate);
            QS_INC_EVENT_LOCKED(sconf, 62);
          }
        }
        req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate;
      }
    }
    if(sconf->qos_cc_block && !excludeFromBlock) {
      apr_time_t now = apr_time_sec(r->request_time);
      int block_event = get_qs_event(r, QS_BLOCK);
      if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) < now) {
        /* reset expired events */
        if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
          // write remaining log lines
          ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r->connection->base_server,
                       QOS_LOG_PFX(060)"access denied (previously), "
                       "QS_ClientEventBlockCount rule: "
                       "max=%d, current=%hu, "
                       "message repeated %d times, "
                       "c=%s",
                       sconf->qos_cc_block,
                       (*clientEntry)->block,
                       (*clientEntry)->blockMsg % QS_LOG_REPEAT,
                       QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : 
                       QS_CONN_REMOTEIP(r->connection)/*no id here, this r is okay*/);
          QS_INC_EVENT_LOCKED(sconf, 60);
          (*clientEntry)->blockMsg = 0;
        }
        (*clientEntry)->block = 0;
        (*clientEntry)->blockTime = 0;
      }
      if(block_event) {
        if((*clientEntry)->block == 0) {
          /* start timer */
          (*clientEntry)->blockTime = now;
        }
        /* ... and increment block event */
        (*clientEntry)->block += block_event;
        /* only once per request */
        apr_table_set(r->subprocess_env, QS_BLOCK_SEEN, "");
        apr_table_set(r->connection->notes, QS_BLOCK_SEEN, "");
      }
      if((*clientEntry)->block >= sconf->qos_cc_block) {
        *uid = apr_pstrdup(r->connection->pool, "060");
        *msg = apr_psprintf(r->connection->pool, 
                            QOS_LOG_PFX(060)"access denied%s, "
                            "QS_ClientEventBlockCount rule: "
                            "max=%d, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
                            sconf->log_only ? " (log only)" : "",
                            sconf->qos_cc_block,
                            (*clientEntry)->block,
                            now - (*clientEntry)->blockTime,
                            QS_CONN_REMOTEIP(r->connection) == NULL ? "-" : 
                            QS_CONN_REMOTEIP(r->connection));
        QS_INC_EVENT_LOCKED(sconf, 60);
        ret = m_retcode;
        (*clientEntry)->lowrate = apr_time_sec(r->request_time);
        (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_EVENTBLOCK;
        qs_set_evmsg(r, "r;"); 
      }
    }
    if(u->qos_cc->limitTable) {
      apr_time_t now = apr_time_sec(r->request_time);
      int limitTableIndex;
      apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
      for(limitTableIndex = 0; 
          limitTableIndex < apr_table_elts(u->qos_cc->limitTable)->nelts;
          limitTableIndex++) {
        time_t remaining = 0;
        int eventSet = 0;
        const char *eventName = limitTableEntry[limitTableIndex].key;
        qos_s_entry_limit_conf_t *eventLimitConf = (qos_s_entry_limit_conf_t *)limitTableEntry[limitTableIndex].val;
        const char *clearEvent = apr_table_get(r->subprocess_env, eventLimitConf->eventClearStr);

        /*
         * reset expired events or clear event counter
         */
        if(clearEvent ||
           (((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) < now)) {
          (*clientEntryFromHdr)->limit[limitTableIndex].limit = 0;
          (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = 0;
        }

        /*
         * check for new events
         */
        eventSet = get_qs_event(r, eventName);
        if(eventSet) {
          char *seenEvent;
          if(strcasecmp(eventName, QS_LIMIT_DEFAULT) == 0) {
            // backward compat/event forwarding
            seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, NULL);
          } else {
            seenEvent = apr_pstrcat(r->pool, QS_LIMIT_SEEN, eventName, NULL);
          }
          if(apr_table_get(r->subprocess_env, seenEvent) == NULL) {
            int newValue = (*clientEntryFromHdr)->limit[limitTableIndex].limit + eventSet;
            // first occurrence
            apr_table_set(r->subprocess_env, seenEvent, "");
            if((*clientEntryFromHdr)->limit[limitTableIndex].limit == 0) {
              /* .start timer */
              (*clientEntryFromHdr)->limit[limitTableIndex].limitTime = now;
            }
            /* increment limit event */
            (*clientEntryFromHdr)->limit[limitTableIndex].limit = newValue > USHRT_MAX ? USHRT_MAX : newValue;
          }
        }

        /*
         * propagate to env
         */
        apr_table_set(r->subprocess_env,
                      apr_pstrcat(r->pool, QS_LIMIT_NAME_PFX, eventName, NULL),
                      eventName);
        apr_table_set(r->subprocess_env,
                      apr_pstrcat(r->pool, eventName, QS_COUNTER_SUFFIX, NULL),
                      apr_psprintf(r->pool, "%d", (*clientEntryFromHdr)->limit[limitTableIndex].limit));
        
        remaining = ((*clientEntryFromHdr)->limit[limitTableIndex].limitTime + eventLimitConf->limitTime) - now;
        apr_table_set(r->subprocess_env,
                      apr_pstrcat(r->pool, eventName, QS_LIMIT_REMAINING, NULL),
                      apr_psprintf(r->pool, "%"APR_TIME_T_FMT, remaining > 0 ? remaining : 0));

        /*
         * enforce limit
         */
        if((*clientEntryFromHdr)->limit[limitTableIndex].limit >= eventLimitConf->limit) {
          int block = 1;
          char *conditional = "";
          if(eventLimitConf->condStr != NULL) {
            // conditional enforcement...
            const char *condition = apr_table_get(r->subprocess_env, QS_COND);
            conditional = apr_pstrdup(r->pool, "Cond");
            if(condition == NULL) {
              block = 0; // variable not set
            } else {
              if(ap_regexec(eventLimitConf->preg, condition, 0, NULL, 0) != 0) {
                block = 0; // pattern does not match
              }
            }
          }
          if(block) {
            if(ret == DECLINED || clientEntryFromHdr != clientEntry) {
              /* log only one error (either block or limit) */
              *uid = apr_pstrdup(r->connection->pool, "067");
              *msg = apr_psprintf(r->connection->pool, 
                                  QOS_LOG_PFX(067)"access denied%s, "
                                  "QS_%sClientEventLimitCount rule: "
                                  "event=%s, "
                                  "max=%hu, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
                                  sconf->log_only ? " (log only)" : "",
                                  conditional,
                                  eventName,
                                  eventLimitConf->limit,
                                  (*clientEntryFromHdr)->limit[limitTableIndex].limit,
                                  now - (*clientEntryFromHdr)->limit[limitTableIndex].limitTime,
                                  forwardedForLogIP == NULL ? "-" : forwardedForLogIP);
              QS_INC_EVENT_LOCKED(sconf, 67);
              ret = m_retcode;
            }
          }
          if(clientEntryFromHdr == clientEntry) {
            (*clientEntry)->lowrate = apr_time_sec(r->request_time);
            (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_EVENTLIMIT;
            qs_set_evmsg(r, "r;"); 
          }
        }
      }
    }
    
    /* reset low prio client after 24h */
    if(clientEntry && sconf->qos_cc_prefer) { 
      apr_time_t now = apr_time_sec(r->request_time); 
      if(((*clientEntry)->lowrate + QOS_LOW_TIMEOUT) < now) {
        (*clientEntry)->lowrate = 0;
        if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
          (*clientEntry)->lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK;
        } else {
          (*clientEntry)->lowratestatus = 0;
        }
      }
    }
    apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT17 */

    if(req_per_sec_block_rate) {
      qs_set_evmsg(r, "L;"); 
      if(!sconf->log_only) {
        apr_sleep(req_per_sec_block_rate*1000);
      }
    }
  }
  return ret;
}

#if APR_HAS_THREADS
static apr_status_t qos_cleanup_status_thread(void *selfv) {
  qsstatus_t *s = selfv;
  s->exit = 1;
  /* may long up to 100ms */
  if(m_threaded_mpm) {
    apr_status_t status;
    apr_thread_join(&status, s->thread);
    //apr_pool_destroy(s->pool);
  }
  return APR_SUCCESS;
}

// checks if connection counting is enabled (any host)
static int qos_count_connections(qos_srv_config *sconf) {
  server_rec *s = sconf->base_server;
  qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
  if(QS_COUNT_CONNECTIONS(bsconf)) {
    return 1;
  }
  s = s->next;
  while(s) {
    qos_srv_config *sc = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
    if(QS_COUNT_CONNECTIONS(sc)) {
      return 1;
    }
    s = s->next;
  }
  return 0;
}

// total (server/all hosts) connections
static int qos_server_connections(qos_srv_config *sconf) {
  server_rec *s = sconf->base_server;
  qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
  int connections = bsconf->act->conn->connections;
  s = s->next;
  while(s) {
    qos_srv_config *sc = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
    if(sc->act->conn != bsconf->act->conn) {
      // server has either his own counter or the base server's counter
      connections += sc->act->conn->connections;
    }
    s = s->next;
  }
  return connections;
  /*
  int i, j;
  worker_score *ws_record;
  process_score *ps_record;
  for(i = 0; i < sconf->server_limit; ++i) {
    ps_record = ap_get_scoreboard_process(i);
    for(j = 0; j < sconf->thread_limit; ++j) {
      ws_record = ap_get_scoreboard_worker(i, j);
      if(!ps_record->quiescing && ps_record->pid) {
        if(ws_record->status == SERVER_READY && ps_record->generation == qos_my_generation) {
          ready++;
        }
      }
    }
  }
  */ 
}

/**
 * Status logger thread
 *
 * @param thread
 * @param selfv Base server_rec
 */
static void *APR_THREAD_FUNC qos_status_thread(apr_thread_t *thread, void *selfv) {
  qsstatus_t *s = selfv;
  int server_limit, thread_limit;
  ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
  ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
  while(!s->exit) {
    char clientContentTypes[8192];
    char allConn[64];
    int s_busy = 0;
    int s_open = 0;
    int s_ready = 0;
    int s_read = 0;
    int s_write = 0;
    int s_keep = 0;
    int s_start = 0;
    int s_log = 0;
    int s_dns = 0;
    int s_closing = 0;
    int s_usr1 = 0;
    int s_kill = 0;
    worker_score ws_record;
    int c, i, e;
    time_t now = time(NULL);
    int run = 0;
    e = 60 - (now % 60);
    e = e * 10;
    for(c = 0; c < e; c++) {
      apr_sleep(100000);
      if(s->exit) {
        run = 0;
        break;
      }
    }
    if(!s->exit) {
      apr_global_mutex_lock(s->lock);            /* @CRT47 */
      now = time(NULL);
      if(*s->qsstatustimer <= now) {
        // set next and fetch the data
        *s->qsstatustimer = (now/60*60 + 60);
        run = 1;
      } else {
        // another child already did the job
        run = 0;
      }
      apr_global_mutex_unlock(s->lock);          /* @CRT47 */
    }
    if(!s->exit && run) {
      for(i = 0; i < server_limit; ++i) {
        int j;
        for(j = 0; j < thread_limit; ++j) {
          int res;
          ap_copy_scoreboard_worker(&ws_record, i, j);
          res = ws_record.status;
          if(res == SERVER_DEAD) {
            s_open++;
          } else  if(res == SERVER_READY) {
            s_ready++;
          } else if(res == SERVER_BUSY_READ) {
            s_read++;
            s_busy++;
          } else if(res == SERVER_BUSY_WRITE) {
            s_write++;
            s_busy++;
          } else if(res == SERVER_BUSY_KEEPALIVE) {
            s_keep++;
            s_busy++;
          } else if(res == SERVER_STARTING) {
            s_start++;
          } else if(res == SERVER_BUSY_LOG) {
            s_log++;
            s_busy++;
          } else if(res == SERVER_BUSY_DNS) {
            s_dns++;
            s_busy++;
          } else if(res == SERVER_CLOSING) {
            s_closing++;
          } else if(res == SERVER_GRACEFUL) {
            s_usr1++;
          } else if(res == SERVER_IDLE_KILL) {
            s_kill++;
          }
        }
      }
      clientContentTypes[0] = '\0';
      if(s->sconf->qos_cc_prefer) {
        qos_user_t *u = qos_get_user_conf(s->sconf->act->ppool);
        if(u) {
          unsigned long long html;
          unsigned long long cssjs;
          unsigned long long img;
          unsigned long long other;
          unsigned long long notmodified;
          apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT48 */
          html = u->qos_cc->html;
          cssjs = u->qos_cc->cssjs;
          img = u->qos_cc->img;
          other = u->qos_cc->other;
          notmodified = u->qos_cc->notmodified;
          apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT48 */
          snprintf(clientContentTypes, 8191, ", \"clientContentTypes\": { "
                   "\"html\": %llu, \"css/js\": %llu,"
                   " \"images\": %llu, \"other\": %llu, \"304\": %llu }",
                   html, cssjs,
                   img, other, notmodified
                   );
        }
      }
      allConn[0] = '\0';
      if(qos_count_connections(s->sconf)) {
        apr_global_mutex_lock(s->lock);          /* @CRT52 */
        int all_connections = qos_server_connections(s->sconf);
        snprintf(allConn, 64, ", \"QS_AllConn\": %d",
                 all_connections
                 );
        apr_global_mutex_unlock(s->lock);        /* @CRT52 */
      }
      ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s->sconf->base_server,
                   QOS_LOG_PFX(200)"{ \"scoreboard\": { "
                   "\"open\": %d, \"waiting\": %d, \"read\": %d, "
                   "\"write\": %d, \"keepalive\": %d, "
                   "\"start\": %d, \"log\": %d, "
                   "\"dns\": %d, \"closing\": %d, "
                   "\"finishing\": %d, \"idle\": %d }, "
                   "\"maxclients\": { "
                   "\"max\": %d, \"busy\": %d"
                   "%s }"
                   "%s }",
                   s_open, s_ready, s_read,
                   s_write, s_keep,
                   s_start, s_log,
                   s_dns, s_closing,
                   s_usr1, s_kill,
                   s->maxclients, s_busy,
                   allConn,
                   clientContentTypes);
    }
  }
  if(m_threaded_mpm) {
    apr_thread_exit(thread, APR_SUCCESS);
  }
  return NULL;
}

/**
 * Starts the stats logger thread
 */
static void qos_init_status_thread(apr_pool_t *p, qos_srv_config *sconf, int maxclients) {
  qs_actable_t *act = sconf->act;
  apr_pool_t *pool;
  apr_threadattr_t *tattr;
  qsstatus_t *s;
  apr_pool_create(&pool, NULL);
  s = apr_pcalloc(pool, sizeof(qsstatus_t));
  s->exit = 0;
  s->pool = pool;
  s->maxclients = maxclients;
  s->qsstatustimer = act->qsstatustimer;
  s->lock = act->lock;
  s->sconf = sconf;
  if(apr_threadattr_create(&tattr, pool) == APR_SUCCESS) {
    if(apr_thread_create(&s->thread, tattr, qos_status_thread, s, pool) == APR_SUCCESS) {
      apr_pool_pre_cleanup_register(p, s, qos_cleanup_status_thread);
    }
  }
}
#endif

/**
 * client control rules at process connection handler
 */
static int qos_cc_pc_filter(conn_rec *connection, qs_conn_ctx *cconf, qos_user_t *u, char **msg) {
  conn_rec *c = connection;
  int ret = DECLINED;
  if(cconf && cconf->sconf->has_qos_cc) {
    qos_s_entry_t **clientEntry = NULL;
    qos_s_entry_t searchE;
    searchE.ip6[0] = cconf->ip6[0];
    searchE.ip6[1] = cconf->ip6[1];
    apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT14 */
    clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);

    /* early vip detection */
    if((*clientEntry)->vip) {
      cconf->is_vip = 1;
      apr_table_set(c->notes, QS_ISVIPREQ, "yes");
    }

    /* max connections */
    if(cconf->sconf->has_qos_cc && cconf->sconf->qos_cc_prefer) {
      if(m_generation != u->qos_cc->generation_locked) {
        u->qos_cc->connections++;
      } else {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, c->base_server,
                     QOS_LOG_PFX(166)"unexpected connection dispatching, skipping"
                     " connection counter update for QS_ClientPrefer rule, c=%s",
                     QS_CONN_REMOTEIP(cconf->mc) == NULL ? "-" : 
                     QS_CONN_REMOTEIP(cconf->mc));
      }
      if((*clientEntry)->lowrate) {
        if(c->notes) {
          char *flags = apr_psprintf(c->pool, "0x%02x", (*clientEntry)->lowratestatus);
          apr_table_set(c->notes, "QS_ClientLowPrio", flags);
        }
      }
      /* non vip (allow all vip addresses - no restrictions) */
      if(!(*clientEntry)->vip) {
        if(u->qos_cc->connections > cconf->sconf->qos_cc_prefer_limit) {
          int penalty = 4; // 2 to 12 points
          int reqSpare = 0;
          if((*clientEntry)->lowrate) {
            if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_PKGRATE) {
              penalty++;
            }
            if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_BAD) {
              penalty+=2;
            }
            if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_EVENTBLOCK) {
              penalty+=2;
            }
            if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_EVENTLIMIT) {
              penalty+=2;
            }
            if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_TIMEOUT) {
              penalty++;
            }
          }
          if((*clientEntry)->lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK) {
            // normal behavior
            penalty-=2;
          }
          // calculate the min. required free connections allowing us to serve request by this IP
          reqSpare = (cconf->sconf->max_clients - cconf->sconf->qos_cc_prefer_limit) * penalty / 12;
          if((cconf->sconf->max_clients - u->qos_cc->connections) < reqSpare) {
            /* not enough free connections */
            if(u->qos_cc->connections > cconf->sconf->qos_cc_prefer_limit) {
              *msg = apr_psprintf(cconf->mc->pool, 
                                  QOS_LOG_PFX(066)"access denied%s, "
                                  "QS_ClientPrefer rule (penalty=%d 0x%02x): "
                                  "max=%d, concurrent connections=%d, c=%s",
                                  cconf->sconf->log_only ? " (log only)" : "",
                                  penalty, (*clientEntry)->lowratestatus,
                                  cconf->sconf->qos_cc_prefer_limit, u->qos_cc->connections,
                                  QS_CONN_REMOTEIP(cconf->mc) == NULL ? "-" : 
                                  QS_CONN_REMOTEIP(cconf->mc));
              QS_INC_EVENT_LOCKED(cconf->sconf, 66);
              ret = m_retcode;
            }
          }
        }
      }
    }

    apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT14 */
  }
  return ret;
}

/**
 * calculates the current minimal up/download bandwidth
 */
static int qos_req_rate_calc(qos_srv_config *sconf, int *current) {
  int req_rate = sconf->req_rate;
  if(sconf->min_rate_max != -1) {
    int connections = qos_server_connections(sconf);
    if(connections > sconf->req_rate_start) {
      /* keep the minimal rate until reaching the min connections */
      req_rate = req_rate + (sconf->min_rate_max * connections / sconf->max_clients);
      if(connections > sconf->max_clients) {
        // limit the max rate to its max if we have more connections then expected
        if(connections > (sconf->max_clients + QS_DOUBLE_CONN_H)) {
          ap_log_error(APLOG_MARK, APLOG_CRIT, 0, sconf->base_server, 
                       QOS_LOG_PFX(036)"QS_SrvMinDataRate: unexpected connection status!"
                       " connections=%d,"
                       " cal. request rate=%d,"
                       " max. limit=%d."
                       " Check log for unclean child exit and consider"
                       " to do a graceful server restart if this condition persists."
                       " You might also increase the number of supported connections"
                       " using the 'QS_MaxClients' directive.",
                       connections, req_rate, sconf->min_rate_max);
        }
        QS_INC_EVENT(sconf, 36);
        req_rate = sconf->min_rate_max;
      }
    }
    *current = connections;
  }
  return req_rate;
}

qos_s_entry_limit_conf_t *qos_getQSLimitEvent(qos_user_t *u, const char *event,
                                              int *limitTableIndex) {
  int i = 0;
  apr_table_entry_t *limitTableEntry = (apr_table_entry_t *)apr_table_elts(u->qos_cc->limitTable)->elts;
  for(i = 0; i < apr_table_elts(u->qos_cc->limitTable)->nelts; i++) {
    const char *eventName = limitTableEntry[i].key;
    if(strcasecmp(eventName, event) == 0) {
      *limitTableIndex = i;
      return (qos_s_entry_limit_conf_t *)limitTableEntry[i].val;
    }
  }
  return NULL;
}

/************************************************************************
 * "public"
 ***********************************************************************/

/**
 * short status viewer
 */
static void qos_ext_status_short(request_rec *r, apr_table_t *qt) {
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
                                                                &qos_module);
  server_rec *s = sconf->base_server;
  qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config,
                                                                 &qos_module);
  const char *option = apr_table_get(qt, "option");
  const char *all_connections = apr_table_get(r->subprocess_env, "QS_AllConn");
  apr_time_t now = apr_time_sec(r->request_time);
  double av[1];

  qos_user_t *u = qos_get_user_conf(sconf->act->ppool);

#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
  getloadavg(av, 1);
  ap_rprintf(r, "b"QOS_DELIM"system.load: %.2f\n", av[0]);
#endif

  if(u->qos_cc && sconf->qos_cc_prefer) {
    unsigned long long html;
    unsigned long long cssjs;
    unsigned long long img;
    unsigned long long other;
    unsigned long long notmodified;
    char clientContentTypes[8192];
    apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT51 */
    html = u->qos_cc->html;
    cssjs = u->qos_cc->cssjs;
    img = u->qos_cc->img;
    other = u->qos_cc->other;
    notmodified = u->qos_cc->notmodified;
    apr_global_mutex_unlock(u->qos_cc->lock);          /* @CRT51 */
    snprintf(clientContentTypes, 8191,
             "b;clientContentTypes(html,css/js,images,other,304): "
             "%llu %llu %llu %llu %llu",
             html, cssjs, img, other, notmodified
             );
    ap_rprintf(r, "%s\n", clientContentTypes);
  }
  while(s) {
    char *sn = apr_psprintf(r->pool, "%s"QOS_DELIM"%s"QOS_DELIM"%d",
                            s->is_virtual ? "v" : "b",
                            s->server_hostname == NULL ? "-" :
                            ap_escape_html(r->pool, s->server_hostname),
                            s->addrs->host_port);
    sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
    if(all_connections && !s->is_virtual) {
      ap_rprintf(r, "%s"QOS_DELIM"QS_AllConn: %s\n", sn, all_connections);
    }
    if((s->is_virtual && (sconf != bsconf)) || !s->is_virtual) {
      qs_acentry_t *actEntry;
      if(!s->is_virtual && sconf->has_qos_cc && sconf->qos_cc_prefer_limit) {
        int hc = u->qos_cc->connections; /* not synchronized ... */
        ap_rprintf(r, "%s"QOS_DELIM"QS_ClientPrefer"QOS_DELIM"%d[]: %d\n", sn,
                   sconf->qos_cc_prefer_limit, hc);
      }
      /* request level */
      actEntry = sconf->act->entry;
      while(actEntry) {
        if((actEntry->limit > 0) && !actEntry->condition && !actEntry->event) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_LocRequestLimit%s"QOS_DELIM"%d[%s]: %d\n", sn,
                     actEntry->regex == NULL ? "" : "Match", 
                     actEntry->limit,
                     actEntry->url, 
                     actEntry->counter);
        }
        if((actEntry->req_per_sec_limit > 0) && !actEntry->event) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_LocRequestPerSecLimit%s"QOS_DELIM"%ld[%s]: %ld\n", sn,
                     actEntry->regex == NULL ? "" : "Match", 
                     actEntry->req_per_sec_limit,
                     actEntry->url,
                     actEntry->req_per_sec);
        }
        if((actEntry->kbytes_per_sec_limit > 0) && !actEntry->event) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_LocKBytesPerSecLimit%s"QOS_DELIM"%"APR_OFF_T_FMT"[%s]: %"APR_OFF_T_FMT"\n", sn,
                     actEntry->regex == NULL ? "" : "Match",
                     actEntry->kbytes_per_sec_limit,
                     actEntry->url, 
                     actEntry->kbytes_per_sec);
        }
        if(actEntry->condition && !actEntry->event) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_CondLocRequestLimitMatch"QOS_DELIM"%d[%s]: %d\n", sn,
                     actEntry->limit,
                     actEntry->url, 
                     actEntry->counter);
        }
        if(actEntry->event && (actEntry->limit != -1)) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_EventRequestLimit"QOS_DELIM"%d[%s]: %d\n", sn,
                     actEntry->limit,
                     actEntry->url, 
                     actEntry->counter);
        }
        if(actEntry->event && (actEntry->kbytes_per_sec_limit != 0)) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_EventKBytesPerSecLimit"QOS_DELIM"%"APR_OFF_T_FMT"[%s]: %"APR_OFF_T_FMT"\n", sn,
                     actEntry->kbytes_per_sec_limit,
                     actEntry->url, 
                     now > (apr_time_sec(actEntry->kbytes_interval_us) + (QS_BW_SAMPLING_RATE*10)) ? 0 : actEntry->kbytes_per_sec);
        }
        if(actEntry->event && (actEntry->req_per_sec_limit > 0)) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_EventPerSecLimit"QOS_DELIM"%ld[%s]: %ld\n", sn,
                     actEntry->req_per_sec_limit,
                     actEntry->url, 
                     now > (actEntry->interval + (QS_BW_SAMPLING_RATE*3)) ? 0 : actEntry->req_per_sec);
        }
        actEntry = actEntry->next;
      }
      /* event limit */
      if(sconf->event_limit_a->nelts > 0) {
        int ie = 0;
        qos_event_limit_entry_t *event_limit = sconf->act->event_entry;
        for(ie = 0; ie < sconf->event_limit_a->nelts; ie++) {
          int elimit = event_limit->limit;
          if(event_limit->limitTime + event_limit->seconds <= now) {
            elimit = 0;
          }
          if(event_limit->action == QS_EVENT_ACTION_DENY) {
            ap_rprintf(r, "%s"QOS_DELIM"QS_%sEventLimitCount"QOS_DELIM"%d/%d[%s]: %d\n",
                       sn,
                       event_limit->condStr == NULL ? "" : "Cond",
                       event_limit->max,
                       event_limit->seconds,
                       event_limit->env_var,
                       elimit);
          }
          event_limit++;
        }
      }
      if(!s->is_virtual || sconf->act->conn != bsconf->act->conn) {
        if(sconf->max_conn != -1) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConn"QOS_DELIM"%d[]: %d\n", sn,
                     sconf->max_conn,
                     sconf->act->conn->connections);
        }
        if(sconf->max_conn_close != -1) {
          ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConnClose"QOS_DELIM"%d[]: %d\n", sn,
                     sconf->max_conn_close,
                     sconf->act->conn->connections);
        }
        if(option && strstr(option, "ip")) {
          if(sconf->act->conn->connections) {
            apr_table_t *entries = apr_table_make(r->pool, 100);
            int j;
            apr_table_entry_t *entry;
            qos_collect_ip(r, sconf, entries, sconf->max_conn_per_ip, 0);
            entry = (apr_table_entry_t *)apr_table_elts(entries)->elts;
            for(j = 0; j < apr_table_elts(entries)->nelts; j++) {
              ap_rprintf(r, "%s"QOS_DELIM"QS_SrvMaxConnPerIP"QOS_DELIM"%s: %s\n",
                         sn,
                         entry[j].key, entry[j].val);
            }
          }
        }
      }
    }
    s = s->next;
  }
  if(u->qos_cc && sconf->qsevents) {
    int i = 0;
    apr_table_t *eTable = apr_table_make(r->pool, QOS_LOG_MSGCT);
    apr_table_entry_t *entry;
    char buf1[1024];
    char buf2[1024];
    apr_global_mutex_lock(u->qos_cc->lock);        /* @CRT50 */
    while(m_knownEvents[i] > 0) {
      snprintf(buf1, 1023, "e;mod_qos(%03d);new: %llu",
               m_knownEvents[i], u->qos_cc->eventLast[m_knownEvents[i]]);
      snprintf(buf2, 1023, "e;mod_qos(%03d);total: %llu", 
               m_knownEvents[i], u->qos_cc->eventTotal[m_knownEvents[i]]);
      apr_table_add(eTable, buf1, buf2);
      u->qos_cc->eventLast[m_knownEvents[i]] = 0;
      i++;
    }
    apr_global_mutex_unlock(u->qos_cc->lock);      /* @CRT50 */
    entry = (apr_table_entry_t *)apr_table_elts(eTable)->elts;
    for(i = 0; i < apr_table_elts(eTable)->nelts; ++i) {
      ap_rprintf(r, "%s\n%s\n", entry[i].key, entry[i].val);
    }
  }
}

/**
 * Comperator for bsearch function
 */
static int qos_geo_comp(const void *_pA, const void *_pB) {
  unsigned long *pA = (unsigned long *)_pA;
  qos_geo_entry_t *pB = (qos_geo_entry_t *)_pB;
  unsigned long search = *pA;
  if((search >= pB->start) && (search <= pB->end)) return 0;
  if(search > pB->start) return 1;
  if(search < pB->start) return -1;
  return -1; // error
}

/**
 * Translates an IP address (from geo csv) to a numeric value.
 *
 * @param pool To dup the string while parsing.
 * @param ip
 * @return
 */
static unsigned long qos_geo_str2long(apr_pool_t *pool, const char *ip) {
  char *p;
  char *i = apr_pstrdup(pool, ip);
  unsigned long addr = 0;

  p = strchr(i, '.');
  if(!p) return 0;
  p[0] = '\0';
  if(!qos_is_num(i)) return 0;
  addr += (atol(i) * 16777216);
  i = p;
  i++;

  p = strchr(i, '.');
  if(!p) return 0;
  p[0] = '\0';
  if(!qos_is_num(i)) return 0;
  addr += (atol(i) * 65536);
  i = p;
  i++;

  p = strchr(i, '.');
  if(!p) return 0;
  p[0] = '\0';
  if(!qos_is_num(i)) return 0;
  addr += (atol(i) * 256);
  i = p;
  i++;

  if(!qos_is_num(i)) return 0;
  addr += (atol(i));

  return addr;
}

/**
 * Viewer settings about ip address information.
 */
static void qos_show_ip(request_rec *r, qos_srv_config *sconf, apr_table_t *qt) {
  int max_conn_per_ip = 0;
  server_rec *s = sconf->base_server;
  apr_time_t now = apr_time_sec(r->request_time);
  while(s) {
    // enable per client connection search if any server has enabled QS_SrvMaxConnPerIP
    qos_srv_config *conf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
    if(conf->max_conn_per_ip != -1) {
      max_conn_per_ip = 1;
      break;
    }
    s = s->next;
  }
  if(sconf->has_qos_cc || max_conn_per_ip) {
    const char *option = apr_table_get(qt, "option");
    const char *refresh = apr_table_get(qt, "refresh");
    const char *address = apr_table_get(qt, "address");
    if(address) {
      int escerr = 0;
      char *ta = apr_pstrdup(r->pool, address);
      qos_unescaping(ta, QOS_DEC_MODE_FLAGS_URL, &escerr);
      address = ta;
    }
    if(strcmp(r->handler, "qos-viewer") == 0) {
      ap_rputs("<table class=\"btable\"><tbody>\n", r);
      ap_rputs(" <tr class=\"row\"><td>\n", r);
    } else {
      ap_rputs("<table border=\"1\"><tbody>\n", r);
      ap_rputs(" <tr><td>\n", r);
    }
    if(strcmp(r->handler, "qos-viewer") == 0) {
      ap_rputs("  <table border=\"0\" cellpadding=\"2\" "
               "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
    } else {
      ap_rputs("  <table border=\"1\" cellpadding=\"2\" "
               "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
    }
    ap_rputs("    <tr class=\"rowe\">\n", r);
    ap_rputs("      <td colspan=\"9\">viewer settings</td>\n", r);
    ap_rputs("    </tr>\n", r);
    /* auto refresh */
    ap_rputs("    <tr class=\"rows\">\n"
             "      <td colspan=\"1\">auto refresh</td>\n", r);
    ap_rputs("      <td colspan=\"8\">\n", r);
    ap_rprintf(r, "        <form action=\"%s\" method=\"get\">\n",
               ap_escape_html(r->pool, r->parsed_uri.path));
    if(option && strstr(option, "ip")) {
      ap_rprintf(r, "          <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
    }
    if(address) {
      ap_rprintf(r, "          <input name=\"address\" value=\"%s\" type=\"hidden\">\n",
                 ap_escape_html(r->pool, address));
    }
    if(refresh) {
      ap_rprintf(r, "          <input name=\"action\" value=\"disable\" type=\"submit\">\n");
    } else {
      ap_rprintf(r, "          <input name=\"action\" value=\"enable\" type=\"submit\">\n");
      ap_rprintf(r, "          <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
    }
    ap_rputs("        </form>\n", r);
    ap_rputs("      </td>\n", r);
    ap_rputs("    </tr>\n", r);
    /* show ip addresses and their connections */
    ap_rputs("    <tr class=\"rows\">\n"
             "      <td colspan=\"1\">client ip connections</td>\n", r);
    ap_rputs("      <td colspan=\"8\">\n", r);
    ap_rprintf(r, "        <form action=\"%s\" method=\"get\">\n",
               ap_escape_html(r->pool, r->parsed_uri.path));
    if(!option || (option && !strstr(option, "ip")) ) {
      ap_rprintf(r, "          <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
      ap_rprintf(r, "          <input name=\"action\" value=\"enable\" type=\"submit\">\n");
    } else {
      ap_rprintf(r, "          <input name=\"option\" value=\"no\" type=\"hidden\">\n");
      ap_rprintf(r, "          <input name=\"action\" value=\"disable\" type=\"submit\">\n");
    }
    if(address) {
      ap_rprintf(r, "          <input name=\"address\" value=\"%s\" type=\"hidden\">\n",
                 ap_escape_html(r->pool, address));
    }
    if(refresh) {
      ap_rprintf(r, "          <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
    }
    ap_rputs("        </form>\n", r);
    ap_rputs("      </td>\n", r);
    ap_rputs("    </tr>\n", r);
  
    if(sconf->has_qos_cc) {
      ap_rputs("    <tr class=\"rows\">\n"
               "      <td colspan=\"1\">search a client ip entry</td>\n", r); 
      ap_rputs("      <td colspan=\"8\">\n", r);
      ap_rprintf(r, "        <form action=\"%s\" method=\"get\">\n",
                 ap_escape_html(r->pool, r->parsed_uri.path));
      if(option && strstr(option, "ip")) {
        ap_rprintf(r, "          <input name=\"option\" value=\"ip\" type=\"hidden\">\n");
      }
      if(refresh) {
        ap_rprintf(r, "          <input name=\"refresh\" value=\"\" type=\"hidden\">\n");
      }
      ap_rprintf(r, "          <input name=\"address\" value=\"%s\" type=\"text\">\n",
                 address ? ap_escape_html(r->pool, address) : "0.0.0.0");
      ap_rprintf(r, "          <input name=\"action\" value=\"search\" type=\"submit\">\n");
      ap_rputs("          </form>\n", r);
      ap_rputs("      </td>\n", r);
      ap_rputs("    </tr>\n", r);
      if(address) {
        apr_uint64_t ip[2];
        qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
        if(qos_ip_str2long(address, ip)) {
          unsigned long html;
          unsigned long cssjs;
          unsigned long img;
          unsigned long other;
          unsigned long notmodified;
          qos_s_entry_t **clientEntry = NULL;
          qos_s_entry_t searchE;
          int found = 0;
          apr_global_mutex_lock(u->qos_cc->lock);            /* @CRT20 */
          html = u->qos_cc->html;
          cssjs = u->qos_cc->cssjs;
          img = u->qos_cc->img;
          other = u->qos_cc->other;
          notmodified = u->qos_cc->notmodified;
          searchE.ip6[0] = ip[0];
          searchE.ip6[1] = ip[1];
          clientEntry = qos_cc_get0(u->qos_cc, &searchE, 0);
          if(clientEntry) {
            found = 1;
            searchE.vip = (*clientEntry)->vip;
            searchE.lowrate = (*clientEntry)->lowrate;
            searchE.lowratestatus = (*clientEntry)->lowratestatus;
            searchE.time = (*clientEntry)->time;
            searchE.block = (*clientEntry)->block;
            searchE.blockTime = (*clientEntry)->blockTime;
            searchE.limit = (*clientEntry)->limit;
            searchE.req_per_sec = (*clientEntry)->req_per_sec;
            searchE.req_per_sec_block_rate = (*clientEntry)->req_per_sec_block_rate;
            searchE.other = (*clientEntry)->other - 1;
            searchE.html = (*clientEntry)->html - 1;
            searchE.cssjs = (*clientEntry)->cssjs - 1;
            searchE.img = (*clientEntry)->img - 1;
            searchE.notmodified = (*clientEntry)->notmodified - 1;
            searchE.event_req = (*clientEntry)->event_req;
          }
          apr_global_mutex_unlock(u->qos_cc->lock);            /* @CRT20 */
          ap_rputs("    <tr class=\"rowt\">\n", r);
          ap_rputs("      <td colspan=\"1\">IP</td>\n", r);
          ap_rputs("      <td colspan=\"2\">last request</td>\n", r);
          ap_rputs("      <td colspan=\"1\">"
                   "<div title=\"QS_VipHeaderName|QS_VipIPHeaderName\">vip</div></td>\n", r);
          ap_rputs("      <td colspan=\"1\">"
                   "<div title=\"QS_ClientEventBlockCount\">blocked</div></td>\n", r);
          ap_rputs("      <td colspan=\"1\">"
                   "<div title=\"QS_ClientEventLimitCount (QS_Limit)\">limited</div></td>\n", r);
          ap_rputs("      <td colspan=\"2\">"
                   "<div title=\"QS_ClientEventPerSecLimit\">events/sec</div></td>\n", r);
          ap_rputs("      <td colspan=\"1\">"
                   "<div title=\"QS_ClientPrefer&#013;0x01 bad pkg rate&#013;0x02 normal behavior&#013;0x04 bad behavior&#013;0x08 blocked&#013;0x10 limited&#013;0x20 timeout\">low prio</div></td>\n", r);
          ap_rputs("    </tr>\n", r);
          ap_rprintf(r, "    <tr class=\"rows\">"
                     "<td colspan=\"1\">%s</td>", ap_escape_html(r->pool, address));
          if(!found) {
            ap_rputs("<td colspan=\"8\"><i>not found</i></td>\n", r);
          } else {
            char buf[1024];
            struct tm *ptr = localtime(&searchE.time);
            strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M:%S", ptr);
            ap_rprintf(r, "<td colspan=\"2\">%s</td>", buf);
            ap_rprintf(r, "<td colspan=\"1\">%s</td>", searchE.vip ? "yes" : "no");
            if(sconf->qos_cc_blockTime > (time(NULL) - searchE.blockTime)) {
              ap_rprintf(r, "<td colspan=\"1\">%d, %ld&nbsp;sec</td>",
                         searchE.block, time(NULL) - searchE.blockTime);
            } else {
              ap_rprintf(r, "<td colspan=\"1\">no</td>");
            }
            
            if(u->qos_cc->limitTable) {
              int limitTableIndex;
              qos_s_entry_limit_conf_t *eventLimitConf = qos_getQSLimitEvent(u, QS_LIMIT_DEFAULT, &limitTableIndex);
              if(eventLimitConf) {
                if(eventLimitConf->limitTime > (time(NULL) - searchE.limit[limitTableIndex].limitTime)) {
                  ap_rprintf(r, "<td colspan=\"1\">%hu, %ld&nbsp;sec</td>",
                             searchE.limit[limitTableIndex].limit, time(NULL) - searchE.limit[limitTableIndex].limitTime);
                } else {
                  ap_rprintf(r, "<td colspan=\"1\">no</td>");
                }
              } else {
                ap_rprintf(r, "<td colspan=\"1\">off</td>");
              }
            } else {
              ap_rprintf(r, "<td colspan=\"1\">off</td>");
            }
            ap_rprintf(r, "<td colspan=\"1\">%ld</td>", searchE.req_per_sec);
            ap_rprintf(r, "<td colspan=\"1\">%d&nbsp;ms</td>", searchE.req_per_sec_block_rate);
            ap_rprintf(r, "<td colspan=\"1\">%s (0x%02x)</td>\n",
                       (searchE.lowrate + QOS_LOW_TIMEOUT) > now ? "yes" : "no",
                       searchE.lowratestatus);
            ap_rputs("</tr>\n", r);
            ap_rprintf(r, "   <tr class=\"rows\">"
                       "<td colspan=\"6\">&nbsp;</td>"
                       "<td>"
                       "<div title=\"QS_ClientEventRequestLimit\">events:</div></td>"
                       "<td style=\"width:9%%\">%s</td>"
                       "<td colspan=\"1\"></td>"
                       "</tr>\n", (sconf->qos_cc_event_req == -1 ? "off" : apr_psprintf(r->pool, "%d", searchE.event_req)));
          }
          if(sconf->qos_cc_prefer) {
            ap_rprintf(r, "   <tr class=\"rowt\">"
                       "<td colspan=\"4\"></td>"
                       "<td style=\"width:9%%\">html</td>"
                       "<td style=\"width:9%%\">css/js</td>"
                       "<td style=\"width:9%%\">images</td>"
                       "<td style=\"width:9%%\">other</td>"
                       "<td style=\"width:9%%\">304</td>"
                       "</tr>\n");
            if(found) {
              ap_rprintf(r, "   <tr class=\"rows\">"
                         "<td colspan=\"4\"></td>"
                         "<td style=\"width:9%%\">%u</td>"
                         "<td style=\"width:9%%\">%u</td>"
                         "<td style=\"width:9%%\">%u</td>"
                         "<td style=\"width:9%%\">%u</td>"
                         "<td style=\"width:9%%\">%u</td>"
                         "</tr>\n", searchE.html,
                         searchE.cssjs,
                         searchE.img,
                         searchE.other,
                         searchE.notmodified);
            }
            ap_rprintf(r, "   <tr class=\"rows\">"
                       "<td colspan=\"3\"></td>"
                       "<td style=\"width:9%%\"><div title=\"QS_ClientContentTypes\">all clients</div></td>"
                       "<td style=\"width:9%%\">%lu</td>"
                       "<td style=\"width:9%%\">%lu</td>"
                       "<td style=\"width:9%%\">%lu</td>"
                       "<td style=\"width:9%%\">%lu</td>"
                       "<td style=\"width:9%%\">%lu</td>"
                       "</tr>\n", html, cssjs, img, other, notmodified);
            if(sconf->static_on == 1) {
              unsigned long shtml = sconf->static_html;
              unsigned long scssjs = sconf->static_cssjs;
              unsigned long simg = sconf->static_img;
              unsigned long sother = sconf->static_other;
              unsigned long snotmodified = sconf->static_notmodified;
              ap_rprintf(r, "   <tr class=\"rows\">"
                         "<td colspan=\"3\"></td>"
                         "<td style=\"width:9%%\"><div title=\"QS_ClientContentTypes\">configured (global)</div></td>"
                         "<td style=\"width:9%%\">%lu</td>"
                         "<td style=\"width:9%%\">%lu</td>"
                         "<td style=\"width:9%%\">%lu</td>"
                         "<td style=\"width:9%%\">%lu</td>"
                         "<td style=\"width:9%%\">%lu</td>"
                         "</tr>\n", shtml, scssjs,
                         simg, sother, snotmodified);
            }
          }
        }
      }
    }
    ap_rprintf(r, "   <tr class=\"row\">"
               "<td style=\"width:28%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "</tr>\n");
    ap_rputs(" </tbody></table>\n", r);
    ap_rputs(" </tr></td>\n", r);
    ap_rputs("</tbody></table>\n", r);
  }
}

/**
 * Daws the load/connection bars at the top of the status page
 *
 * @param r
 * @param bs
 */
static void qos_bars(request_rec *r, server_rec *bs) {
  server_rec *s = bs;
  qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
  if(bsconf->act && bsconf->act->conn) {
    int connections = -1;
    double av[1];
    int load = 0;
#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
    getloadavg(av, 1);
    load = av[0];
    if(load > 0) {
      load = 100*load/(10+load);
    }
#endif

    ap_rputs("<table class=\"btable\"><tbody>\n", r);
    ap_rputs(" <tr class=\"row\"><td>\n", r);

    ap_rputs("<table border=\"0\" cellpadding=\"2\" "
             "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
    ap_rputs("<tr class=\"rowe\">\n", r);
    ap_rputs("<td colspan=\"2\">overview</td>", r);
    ap_rputs("</tr>\n", r);

    if(bsconf->log_only) {
      ap_rputs("<tr class=\"rowt\">\n", r);
      ap_rputs("<td colspan=\"2\">running in 'log only' mode - rules are NOT enforced</td>", r);
      ap_rputs("</tr>\n", r);
    }
    if(qos_count_connections(bsconf)) {
      connections = qos_server_connections(bsconf);
    }
    if(connections != -1) {
#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
      ap_rprintf(r, "<tr class=\"rowt\">"
                 "<td colspan=\"1\">connections: %d</td>"
                 "<td colspan=\"1\">load: %.2f</td>"
                 "</tr>\n", connections, av[0]);
#else
      ap_rprintf(r, "<tr class=\"rowt\">"
                 "<td colspan=\"1\">connections: %d</td>"
                 "<td colspan=\"1\">load: n/a</td>"
                 "</tr>\n", connections);
#endif
    } else {
#if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
      ap_rprintf(r, "<tr class=\"rowt\">"
                 "<td colspan=\"1\">connections: n/a</td>"
                 "<td colspan=\"1\">load: %.2f</td>"
                 "</tr>\n", av[0]);
#else
      ap_rprintf(r, "<tr class=\"rowt\">"
                 "<td colspan=\"1\">connections: n/a</td>"
                 "<td colspan=\"1\">load: n/a</td>"
                 "</tr>\n");
#endif
    }
    ap_rprintf(r, "<tr class=\"rows\">");
    ap_rprintf(r, "<td>");
    if(connections != -1) {
      int percentage = 100 * connections / bsconf->max_clients;
      if(percentage > 100) percentage = 100;
      ap_rprintf(r, "<div class=\"prog-border\">"
                 "<div class=\"%s\" style=\"width: %d%%;\"></div></div>",
                 percentage < 90 ? "prog-bar" : "prog-bar-limit",
                 percentage);
      ap_rprintf(r, "</td>");
    } else {
      ap_rprintf(r, "&nbsp;");
    }      
    ap_rprintf(r, "<td>");
    ap_rprintf(r, "<div class=\"prog-border\">"
               "<div class=\"prog-bar\" style=\"width: %d%%;\"></div></div>",
               load);
    ap_rprintf(r, "</td>");
    ap_rprintf(r, "</tr>\n");

    ap_rputs("</tbody></table>\n", r);

    ap_rputs(" </tr></td>\n", r);
    ap_rputs("</tbody></table>\n", r);
  }
}

/**
 * (Extendet-)Status viewer, used by internal and mod_status handler.
 */
static int qos_ext_status_hook(request_rec *r, int flags) {
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(r->server->module_config,
                                                                &qos_module);
  server_rec *s = sconf->base_server;
  int i = 0;
  apr_time_t now = apr_time_sec(r->request_time);
  qos_srv_config *bsconf = (qos_srv_config*)ap_get_module_config(s->module_config,
                                                                 &qos_module);
  apr_table_t *qt = qos_get_query_table(r);
  const char *option = apr_table_get(qt, "option");
  if(sconf->disable_handler == 1) {
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                  QOS_LOG_PFX(072)"handler has been disabled for this host, id=%s",
                  qos_unique_id(r, "072"));
    return OK;
  }
  if (flags & AP_STATUS_SHORT) {
    qos_ext_status_short(r, qt);
    return OK;
  }
  if(qt && (apr_table_get(qt, "auto") != NULL)) {
    qos_ext_status_short(r, qt);
    return OK;
  }
  if(strcmp(r->handler, "qos-viewer") != 0) {
    ap_rputs("<hr>\n", r);
    ap_rputs("<table style=\"width:400px\" cellspacing=0 cellpadding=0>\n", r);
    ap_rputs(" <tr><td bgcolor=\"#000000\">\n", r);
    ap_rputs(" <b><font color=\"#ffffff\" face=\"Arial,Helvetica\">", r);
    ap_rprintf(r, "mod_qos&nbsp;%s", ap_escape_html(r->pool, qos_revision(r->pool)));
    ap_rputs(" </font></b>\r", r);
    ap_rputs(" </td></tr>\n", r);
    ap_rputs("</table>\n", r);
    if(sconf->log_only) {
      ap_rputs("<p>running in 'log only' mode - rules are NOT enforced</p>\n", r);
    }      
  }
#ifdef QS_INTERNAL_TEST
  {
    apr_uint64_t remoteip[2];
    qs_conn_ctx *cconf = qos_get_cconf(r->connection);
    qos_ip_str2long(QS_CONN_REMOTEIP(r->connection), remoteip);
    if(cconf) {
      remoteip[0] = cconf->ip6[0];
      remoteip[1] = cconf->ip6[1];
    }
    ap_rputs("<p>TEST BINARY, NOT FOR PRODUCTIVE USE<br>\n", r);
    ap_rprintf(r, "client ip=%s</p>\n", qos_ip_long2str(r->pool, remoteip));
  }
#endif
  if(strcmp(r->handler, "qos-viewer") == 0) {
    qos_bars(r, s);
  }
  qos_show_ip(r, bsconf, qt);
  if(strcmp(r->handler, "qos-viewer") == 0) {
    ap_rputs("<table class=\"btable\"><tbody>\n", r);
    ap_rputs(" <tr class=\"row\"><td>\n", r);
  } else {
    ap_rputs("<table border=\"1\"><tbody>\n", r);
    ap_rputs(" <tr><td>\n", r);
  }
  while(s) {
    qs_acentry_t *actEntry;
    if(strcmp(r->handler, "qos-viewer") == 0) {
      ap_rputs("  <table border=\"0\" cellpadding=\"2\" "
               "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
    } else {
      ap_rputs("  <table border=\"1\" cellpadding=\"2\" "
               "cellspacing=\"2\" style=\"width: 100%\"><tbody>\n",r);
    }
    ap_rputs("    <tr class=\"rowe\">\n", r);
    ap_rprintf(r, "      <td colspan=\"9\">%s:%d (%s)</td>\n",
               s->server_hostname == NULL ? "-" : ap_escape_html(r->pool, s->server_hostname),
               s->addrs->host_port,
               s->is_virtual ? "virtual" : "base");
    ap_rputs("    </tr>\n", r);
    sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);

    if((sconf == bsconf) && s->is_virtual) {
      ap_rputs("    <tr class=\"rows\">\n"
               "     <td colspan=\"9\"><i>uses base server settings and counters</i></td>\n    </tr>\n", r);
    } else {
      if(!s->is_virtual && sconf->has_qos_cc) {
        qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
        int num = 0;
        int max = 0;
        int hc = -1;
        apr_global_mutex_lock(u->qos_cc->lock);           /* @CRT16 */
        hc = u->qos_cc->connections;
        num = u->qos_cc->num;
        max = u->qos_cc->max;
        apr_global_mutex_unlock(u->qos_cc->lock);         /* @CRT16 */
        ap_rputs("<tr class=\"rowt\">"
                 "<td colspan=\"6\">client control</td>"
                 "<td >max</td>"
                 "<td >limit&nbsp;</td>"
                 "<td >current&nbsp;</td>", r);
        ap_rputs("</tr>\n", r);
        ap_rprintf(r, "<tr class=\"rows\">");
        ap_rprintf(r, "<td colspan=\"6\"><div title=\"QS_ClientEntries\">clients in memory</div></td>");
        ap_rprintf(r, "<td >%d</td>", max);
        ap_rprintf(r, "<td >-</td>");
        ap_rprintf(r, "<td >%d</td>", num);
        ap_rputs("</tr>\n", r);
        if(sconf->qos_cc_prefer) {
          ap_rprintf(r, "<tr class=\"rows\">");
          ap_rprintf(r, "<td colspan=\"6\"><div title=\"QS_ClientPrefer\">connections</div></td>");
          ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_prefer);
          ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_prefer_limit);
          ap_rprintf(r, "<td >%d</td>", hc);
          ap_rputs("</tr>\n", r);
        }
        /*
        if(sconf->qos_cc_block) {
          ap_rprintf(r, "<tr class=\"rows\">");
          ap_rprintf(r, "<td colspan=\"6\">block event</td>");
          ap_rprintf(r, "<td >%d</td>", sconf->qos_cc_block);
          ap_rprintf(r, "<td >&nbsp</td>");
          ap_rprintf(r, "<td >%d</td>", blocked);
          ap_rputs("</tr>\n", r);
        }
        */
      }
      /* request level */
      actEntry = sconf->act->entry;
      if(actEntry) {
        ap_rputs("    <tr class=\"rowt\">"
                 "<td colspan=\"1\">rule</td>"
                 "<td colspan=\"2\">"
                 "<div title=\"QS_LocRequestLimitMatch|QS_LocRequestLimit"
                 "|QS_CondLocRequestLimitMatch|QS_EventRequestLimit\">"
                 "concurrent requests</div></td>"
                 "<td colspan=\"3\">"
                 "<div title=\"QS_LocRequestPerSecLimitMatch|"
                 "QS_LocRequestPerSecLimit|QS_EventPerSecLimit\">"
                 "requests/second</div></td>"
                 "<td colspan=\"3\">"
                 "<div title=\"QS_LocKBytesPerSecLimitMatch|QS_LocKBytesPerSecLimit\">"
                 "kbytes/second</div></td>", r);
        ap_rputs("</tr>\n", r);
        ap_rputs("    <tr class=\"rowt\">"
                 "<td >&nbsp;</td>"
                 "<td >limit</td>"
                 "<td >current</td>"
                   "<td >wait rate</td>"
                 "<td >limit</td>"
                 "<td >current</td>"
                 "<td >wait rate</td>"
                 "<td >limit</td>"
                 "<td >current</td>", r);
          ap_rputs("</tr>\n", r);
      }
      while(actEntry) {
        char *red = "style=\"background-color: rgb(240,153,155);\"";
        ap_rputs("    <tr class=\"rows\">", r);
        ap_rprintf(r, "<!--%d--><td>%s%s</a></td>", i,
                   ap_escape_html(r->pool, qos_crline(r, actEntry->url)),
                   actEntry->condition == NULL ? "" : " <small>(conditional)</small>");
        if((actEntry->limit == 0) || (actEntry->limit == -1)) {
          ap_rprintf(r, "<td>-</td>");
          ap_rprintf(r, "<td>-</td>");
        } else {
          ap_rprintf(r, "<td>%d</td>", actEntry->limit);
          ap_rprintf(r, "<td %s>%d</td>",
                     ((actEntry->counter * 100) / actEntry->limit) > 90 ? red : "",
                     actEntry->counter);
        }
        if(actEntry->req_per_sec_limit == 0) {
          ap_rprintf(r, "<td>-</td>");
          ap_rprintf(r, "<td>-</td>");
          ap_rprintf(r, "<td>-</td>");
          } else {
          ap_rprintf(r, "<td %s>%d&nbsp;ms</td>",
                     actEntry->req_per_sec_block_rate ? red : "",
                     actEntry->req_per_sec_block_rate);
          ap_rprintf(r, "<td>%ld</td>", actEntry->req_per_sec_limit);
          ap_rprintf(r, "<td %s>%ld</td>",
                     ((actEntry->req_per_sec * 100) / actEntry->req_per_sec_limit) > 90 ? red : "",
                     now > (actEntry->interval + (QS_BW_SAMPLING_RATE*3)) ? 0 : actEntry->req_per_sec);
        }
        if(actEntry->kbytes_per_sec_limit == 0) {
            ap_rprintf(r, "<td>-</td>");
            ap_rprintf(r, "<td>-</td>");
            ap_rprintf(r, "<td>-</td>");
        } else {
          int hasActualData = now > (apr_time_sec(actEntry->kbytes_interval_us) + QS_BW_SAMPLING_RATE) ? 0 : 1;
          ap_rprintf(r, "<td %s>%"APR_OFF_T_FMT"&nbsp;ms</td>",
                     hasActualData && (actEntry->kbytes_per_sec_block_rate >= 1000) ? red : "",
                     actEntry->kbytes_per_sec_block_rate / 1000);
          ap_rprintf(r, "<td>%"APR_OFF_T_FMT"</td>", actEntry->kbytes_per_sec_limit);
          ap_rprintf(r, "<td %s>%"APR_OFF_T_FMT"</td>",
                     hasActualData && (((actEntry->kbytes_per_sec * 100) / actEntry->kbytes_per_sec_limit) > 90) ? red : "",
                     hasActualData ? actEntry->kbytes_per_sec : 0);
        }
        ap_rputs("</tr>\n", r);
        actEntry = actEntry->next;
      }
      /* event limit */
      if(sconf->event_limit_a->nelts > 0) {
        char *red = "style=\"background-color: rgb(240,153,155);\"";
        int ie = 0;
        qos_event_limit_entry_t *event_limit = sconf->act->event_entry;
        ap_rputs("    <tr class=\"rowt\">"
                 "<td colspan=\"5\"><div title=\"QS_EventLimitCount\">events</div></td>"
                 "<td colspan=\"1\">limit</td>"
                 "<td colspan=\"1\">seconds</td>"
                 "<td colspan=\"2\">current</td>", r);
        ap_rputs("</tr>\n", r);
        for(ie = 0; ie < sconf->event_limit_a->nelts; ie++) {
          int edelta = event_limit->limitTime + event_limit->seconds - now;
          int elimit = event_limit->limit;
          if(event_limit->limitTime + event_limit->seconds <= now) {
            elimit = 0;
            edelta = 0;
          }
          if(event_limit->action == QS_EVENT_ACTION_DENY) {
            ap_rprintf(r, "    <tr class=\"rows\">"
                       "<td colspan=\"5\">%s%s</td>"
                       "<td>%d</td><td>%ds</td><td %s>%d</td><td>%ds</td>"
                       "</tr>\n",
                       event_limit->env_var,
                       event_limit->condStr == NULL ? "" : " <small>(conditional)</small>",
                       event_limit->max,
                       event_limit->seconds,
                       elimit >= event_limit->max ? red : "",
                       elimit,
                       edelta);
          }
          event_limit++;
        }
      }
      /* connection level */
      if(sconf->has_conn_counter == 1 || !s->is_virtual) {
        char *red = "style=\"background-color: rgb(240,153,155);\"";
        int c = qos_count_free_ip(sconf);
        ap_rputs("    <tr class=\"rowt\">"
                 "<td colspan=\"9\">connections</td>", r);
        ap_rputs("</tr>\n", r);
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<!--%d--><td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConnPerIP\">free ip entries</div></td>"
                   "<td colspan=\"3\">%d</td></tr>\n", i, c);
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<!--%d--><td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConn|QS_SrvMaxConnClose\">current connections</div></td>"
                   "<td %s colspan=\"3\">%d</td></tr>\n", i,
                   ( ( (sconf->max_conn_close != -1) &&
                       (sconf->act->conn->connections >= sconf->max_conn_close) )  ||
                     ( (sconf->max_conn != -1) &&
                       (sconf->act->conn->connections >= sconf->max_conn) ) ) ? red : "",
                   sconf->act->conn->connections);

        if(!s->is_virtual) {
          ap_rprintf(r, "    <tr class=\"rows\">"
                     "<!--base--><td colspan=\"6\">"
                     "<div>total connections</div></td>"
                     "<td colspan=\"3\">%d</td></tr>\n",
                     qos_server_connections(sconf));
        }

        if(option && strstr(option, "ip")) {
          apr_table_t *entries = apr_table_make(r->pool, 100);
          int j;
          apr_table_entry_t *entry;
          ap_rputs("    <tr class=\"rowt\">"
                   "<td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConnPerIP\">client ip connections</div></td>"
                   "<td colspan=\"3\">current&nbsp;</td>", r);
          ap_rputs("</tr>\n", r);
          qos_collect_ip(r, sconf, entries, sconf->max_conn_per_ip, 1);
          entry = (apr_table_entry_t *)apr_table_elts(entries)->elts;
          for(j = 0; j < apr_table_elts(entries)->nelts; j++) {
            ap_rputs("     <tr class=\"rows\">", r);
            ap_rputs("<td colspan=\"6\">", r);
            ap_rprintf(r, "%s</td></tr>\n", entry[j].key);
          }
        }

        ap_rputs("    <tr class=\"rowt\">"
                 "<td colspan=\"9\">connection settings</td>", r);
        ap_rputs("</tr>\n", r);
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConn\">max connections</div></td>");
        if(sconf->max_conn == -1) {
          ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
        } else {
          ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn);
        }
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConnClose\">max connections with keep-alive</div></td>");
        if(sconf->max_conn_close == -1) {
          ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
        } else {
          ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn_close);
        }
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<td colspan=\"6\">"
                   "<div title=\"QS_SrvMaxConnPerIP\">max connections per client ip</div></td>");
        if(sconf->max_conn_per_ip == -1) {
          ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
        } else {
          ap_rprintf(r, "<td colspan=\"3\">%d</td></tr>\n", sconf->max_conn_per_ip);
        }
        ap_rprintf(r, "    <tr class=\"rows\">"
                   "<td colspan=\"6\">"
                   "<div title=\"QS_SrvMinDataRate|QS_SrvRequestRate\">"
                   "min. data rate (bytes/sec) (min/max/current)</div></td>");
        if(sconf->req_rate == -1) {
          ap_rprintf(r, "<td colspan=\"3\">-</td></tr>\n");
        } else {
          int connections;
          int rt = qos_req_rate_calc(sconf, &connections);
          ap_rprintf(r, "<td colspan=\"3\">%d/%d/%d</td></tr>\n",
                     sconf->req_rate,
                     sconf->min_rate_max == -1 ? sconf->req_rate : sconf->min_rate_max,
                     rt);
        }
      } else {
        ap_rputs("    <tr class=\"rowt\">"
                 "<td colspan=\"9\">connections</td>", r);
        ap_rputs("</tr>\n", r);
        ap_rputs("    <tr class=\"rows\">\n"
                 "     <td colspan=\"9\"><i>uses base server settings and counters</i></td>\n    </tr>\n", r);
      }
    }
    ap_rprintf(r, "    <tr class=\"row\">"
               "<td style=\"width:28%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "<td style=\"width:9%%\"></td>"
               "</tr>");
    i++;
    s = s->next;
    ap_rputs("</tbody></table>\n", r);
  }
  ap_rputs(" </td></tr>\n", r);
  ap_rputs("</tbody></table>\n", r);
  return OK;
}

/**
 * Disables request rate enforcements for all child processes (at start/fork) if
 * init has failed.
 *
 * @param bs Base server_rec to iterate through all client configurations
 * @param msg Error message to log (reason what has failed @init).
 */
static void qos_disable_req_rate(server_rec *bs, const char *msg) {
  server_rec *s = bs->next;
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
  ap_log_error(APLOG_MARK, APLOG_CRIT, 0, bs,
               QOS_LOG_PFX(008)"could not create supervisor thread (%s),"
               " disable request rate enforcement", msg);
  sconf->req_rate = -1;
  while(s) {
    sconf = (qos_srv_config*)ap_get_module_config(s->module_config, &qos_module);
    sconf->req_rate = -1;
    s = s->next;
  }
}

/** 
 * stores the IP of the connection into the array and
 * increments the array pointer (for QS_Block for connection errors)
 */
static apr_uint64_t *qos_inc_block(conn_rec *connection, qos_srv_config *sconf,
                                   qs_conn_ctx *cconf, apr_uint64_t *ip) {
  conn_rec *c = connection;
 if(sconf->qos_cc_block &&
     apr_table_get(sconf->setenvstatus_t, QS_CLOSE) &&
     !apr_table_get(c->notes, QS_BLOCK_SEEN)) {
    apr_table_set(c->notes, QS_BLOCK_SEEN, "");
    *ip = cconf->ip6[0];
    ip++;
    *ip = cconf->ip6[1];
    ip++;
  }
  return ip;
}

#if APR_HAS_THREADS
/**
 * Supervisior thread monitoring the bandith of registered connections.
 *
 * Connections are closed by a apr_socket_close/shutdown which must be
 * detected by the thread processing the connection in order to 
 * de-register the connection and to terminate the pending request in
 * order to free resources (thread).
 *
 * @param thread
 * @param selfv Base server_rec
 */
static void *APR_THREAD_FUNC qos_req_rate_thread(apr_thread_t *thread, void *selfv) {
  server_rec *bs = selfv;
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
  // list of ip addr. for whose we shall inc. block count
  apr_uint64_t *ips = calloc(sconf->max_clients * 2, APR_ALIGN_DEFAULT(sizeof(apr_uint64_t)));
  while(!sconf->inctx_t->exit) {
    apr_uint64_t *ip = ips;
    int current_con = 0;
    int req_rate = qos_req_rate_calc(sconf, &current_con);
    apr_time_t now = apr_time_sec(apr_time_now());
    apr_time_t interval = now - sconf->qs_req_rate_tm;
    int i;
    apr_table_entry_t *entry;
    // every second
    apr_sleep(APR_USEC_PER_SEC);
    if(sconf->inctx_t->exit) {
      break;
    }
    apr_thread_mutex_lock(sconf->inctx_t->lock);   /* @CRT21 */
    entry = (apr_table_entry_t *)apr_table_elts(sconf->inctx_t->table)->elts;
    for(i = 0; i < apr_table_elts(sconf->inctx_t->table)->nelts; i++) {
      qos_ifctx_t *inctx = (qos_ifctx_t *)entry[i].val;
      if(inctx->status == QS_CONN_STATE_KEEP) {
        /* enforce keep alive */
        apr_interval_time_t current_timeout = 0;
        apr_socket_timeout_get(inctx->clientSocket, &current_timeout);
        /* add 5sec tolerance to receive the request line 
           or let Apache close the connection */
        /* ignored by event MPM ! */
        if(!m_event_mpm && 
           (now > (apr_time_sec(current_timeout) + 5 + inctx->time))) {
          qs_conn_ctx *cconf = qos_get_cconf(inctx->c);
          int level = APLOG_ERR;
          if(cconf) {
            /* disabled by vip priv */
            if(cconf->is_vip && sconf->req_ignore_vip_rate != 1) {
              level = APLOG_DEBUG;
              cconf->has_lowrate = 1; /* mark connection low rate */
            }
            /* disabled for this request/connection */
            if(inctx->disabled) {
              level = APLOG_DEBUG;
              cconf->has_lowrate = 1; /* mark connection low rate */
            }
            /* enable only if min. num of connection reached */
            if(current_con < sconf->req_rate_start) {
              level = APLOG_DEBUG;
              cconf->has_lowrate = 1; /* mark connection low rate */
            }
            ip = qos_inc_block(inctx->c, sconf, cconf, ip);
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|level, 0, inctx->c->base_server,
                         QOS_LOG_PFX(034)"%s,"
                         " QS_SrvMinDataRate rule (enforce keep-alive),"
                         " c=%s",
                         (level == APLOG_DEBUG) || sconf->log_only ? 
                         "log only (allowed)" 
                         : "access denied",
                         QS_CONN_REMOTEIP(inctx->c) == NULL ? "-" : QS_CONN_REMOTEIP(inctx->c));
            QS_INC_EVENT_LOCKED(sconf, 34);
            inctx->time = now;
            inctx->nbytes = 0;
            if((level == APLOG_ERR) &&
               !sconf->log_only) {
              apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_READ);
            }
            /* mark slow clients (QS_ClientPrefer) even they are VIP */
            inctx->shutdown = 1;
          } 
          //else {
          //  // HTTP/2
          //}
        }
      } else {
        if(interval > inctx->time) {
          int rate = inctx->nbytes / sconf->qs_req_rate_tm;
          if(rate < req_rate) {
            if(inctx->clientSocket) {
              qs_conn_ctx *cconf = qos_get_cconf(inctx->c);
              int level = APLOG_ERR;
              int notUsed = 0;
              if(cconf) {
                /* disabled by vip priv */
                if(cconf->is_vip && sconf->req_ignore_vip_rate != 1) {
                  level = APLOG_DEBUG;
                  cconf->has_lowrate = 1; /* mark connection low rate */
                }
                /* disabled for this request/connection */
                if(inctx->disabled) {
                  level = APLOG_DEBUG;
                  cconf->has_lowrate = 1; /* mark connection low rate */
                }
                /* enable only if min. num of connection reached */
                if(current_con < sconf->req_rate_start) {
                  level = APLOG_DEBUG;
                  cconf->has_lowrate = 1; /* mark connection low rate */
                }
                ip = qos_inc_block(inctx->c, sconf, cconf, ip);
                if((inctx->hasBytes == 0) &&
                   (inctx->c->keepalives == 0) &&
                   (inctx->status == QS_CONN_STATE_HEAD)) {
                  // not even received the request line
                  notUsed = 1;
                }
                ap_log_error(APLOG_MARK, APLOG_NOERRNO|level, 0, inctx->c->base_server,
                             QOS_LOG_PFX(034)"%s, QS_SrvMinDataRate rule (%s%s): min=%d,"
                             " this connection=%d,"
                             " c=%s",
                             (level == APLOG_DEBUG) || sconf->log_only ? 
                             "log only (allowed)" 
                             : "access denied",
                             inctx->status == QS_CONN_STATE_RESPONSE ? "out" : "in",
                             notUsed ? ":0" : "",
                             req_rate,
                             rate,
                             QS_CONN_REMOTEIP(inctx->c) == NULL ? "-" : QS_CONN_REMOTEIP(inctx->c));
                QS_INC_EVENT_LOCKED(sconf, 34);
                inctx->time = interval + sconf->qs_req_rate_tm;
                inctx->nbytes = 0;
                if((level == APLOG_ERR) && !sconf->log_only) {
                  if(inctx->status == QS_CONN_STATE_RESPONSE) {
                    apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_WRITE);
                    /* close out socket (the hard way) */
                    apr_socket_close(inctx->clientSocket);
                  } else {
                    apr_socket_shutdown(inctx->clientSocket, APR_SHUTDOWN_READ);
                  }
                }
                /* mark slow clients (QS_ClientPrefer) even they are VIP */
                inctx->shutdown = 1;
              }
            }
            //else {
            //  // HTTP/2
            //}
          } else {
            inctx->time = interval + sconf->qs_req_rate_tm;
            inctx->nbytes = 0;
          }
        }
      }
    }
    apr_thread_mutex_unlock(sconf->inctx_t->lock); /* @CRT21 */
    /* QS_Block for connection errors */
    while(ip != ips) {
      qos_user_t *u = qos_get_user_conf(sconf->act->ppool);    
      qos_s_entry_t **clientEntry = NULL;
      qos_s_entry_t searchE;
      apr_global_mutex_lock(u->qos_cc->lock);          /* @CRT38 */
      ip--;
      searchE.ip6[1] = *ip;
      ip--;
      searchE.ip6[0] = *ip;
      clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);

      /* increment block event */
      if((*clientEntry)->block < USHRT_MAX) {
        (*clientEntry)->block++;
      }
      if((*clientEntry)->block == 1) {
        /* ... and start timer */
        (*clientEntry)->blockTime = apr_time_sec(apr_time_now());
      }
      apr_global_mutex_unlock(u->qos_cc->lock);        /* @CRT38 */
    }
  }
  // apr_thread_mutex_lock(sconf->inctx_t->lock);
  // apr_thread_mutex_unlock(sconf->inctx_t->lock);
  // called via apr_pool_cleanup_register():
  // apr_thread_mutex_destroy(sconf->inctx_t->lock);
  free(ips);
  if(m_threaded_mpm) {
    apr_thread_exit(thread, APR_SUCCESS);
  }
  return NULL;
}

/**
 * Terminates the connection supervisor thread.
 * (works for mpm_worker only)
 *
 * @param selfv The base server_rec
 * @return APR_SUCCESS
 */
static apr_status_t qos_cleanup_req_rate_thread(void *selfv) {
  server_rec *bs = selfv;
  qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(bs->module_config, &qos_module);
  sconf->inctx_t->exit = 1;
  /* may long up to one second */
  if(m_threaded_mpm) {
    apr_status_t status;
    apr_thread_join(&status, sconf->inctx_t->thread);
  }
  return APR_SUCCESS;
}
#endif

/**
 * Writes the query/body to the env variables which may be used
 * for the qsfilter* audit log.
 *
 * @param r
 * @param dconf
 */
static void qos_audit(request_rec *r, qos_dir_config *dconf) {
  const char *q = NULL;
  const char *u = apr_table_get(r->notes, QS_PARP_PATH);
  if(dconf->bodyfilter_p == 1 || dconf->bodyfilter_d == 1) {
    q = apr_table_get(r->notes, QS_PARP_QUERY);
  }
  if(u == NULL) {
    if(r->parsed_uri.path) {
      u = apr_pstrdup(r->pool, r->parsed_uri.path);
    } else {
      u = apr_pstrdup(r->pool, "");
    }
    apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_PATH), u);
  }
  if(q == NULL) {
    if(r->parsed_uri.query) {
      q = apr_pstrcat(r->pool, "?", r->parsed_uri.query, NULL);
    } else {
      q = apr_pstrdup(r->pool, "");
    }
    apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), q);
  }
  apr_table_setn(r->notes, apr_pstrdup(r->pool, QS_PARP_LOC), dconf->path);
  if(r->next) {
    apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_PATH), u);
    apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_QUERY), q);
    apr_table_setn(r->next->notes, apr_pstrdup(r->pool, QS_PARP_LOC), dconf->path);
  }
}

/**
 * Adds the configured (QS_Delay env var) delay to the request
 *
 * @param r
 * @param sconf Do set log-only mode
 */
static void qos_delay(request_rec *r, qos_srv_config *sconf) {
  const char *d = apr_table_get(r->subprocess_env, "QS_Delay");
  if(d) {
    apr_off_t s;
#ifdef ap_http_scheme
    /* Apache 2.2 */
    char *errp = NULL;
    if((APR_SUCCESS == apr_strtoff(&s, d, &errp, 10)) && s > 0)
#else
    if((s = apr_atoi64(d)) > 0)
#endif
      {
        qs_set_evmsg(r, "L;"); 
        if(!sconf->log_only) {
          apr_sleep(s*1000);
        } 
    }
  }
}

/** 
 * Enables mod_deflate
 * QS_DeflateReqBody (if parp has been enabled)
 *
 * @param r
 */
static void qos_deflate(request_rec *r) {
  if(apr_table_get(r->subprocess_env, "QS_DeflateReqBody") && 
     apr_table_get(r->subprocess_env, "parp")) {
    ap_add_input_filter("DEFLATE", NULL, r, r->connection);
  }
}

/**
 * Adjusts the content-length header
 *
 * @param r
 */
static void qos_deflate_contentlength(request_rec *r) {
  if(apr_table_get(r->subprocess_env, "QS_DeflateReqBody") && 
     apr_table_get(r->subprocess_env, "parp")) {
    const char *PARPContentLength = apr_table_get(r->subprocess_env, "PARPContentLength");
    const char *contentLength = apr_table_get(r->headers_in, "Content-Length");
    if(PARPContentLength && contentLength) {
      apr_table_set(r->headers_in, "Content-Length", PARPContentLength);
    }
  }
}

/**
 * Verifies Apache and MPM version and writes error message (notice)
 * for incompatibel version/type.
 *
 * Apache MPM worker binaries is the only configuration which
 * has been fully tested (as mentioned in the documentation, see index.html).
 *
 * - old (2.0.x, 2.2.x) MPM Apache prefork versions do not unload the
 *   DSO properly or child exit may cause a segfault (pool cleanup)
 * - regular expression are only fully supported with Apach 2.4
 * - Apache 2.4 should work (but is not fully tested)
 *   (see CHANGES.txt for more information)
 * - Apache 2.0 does not support all directives (e.g. QS_ClientPrefer) and
 *   we do no longer test against this version (the module does probably
 *   not even compile with version 2.0)
 *
 */
static void qos_version_check(server_rec *bs) {
  ap_version_t version;

  if(strcasecmp(ap_show_mpm(), "event") == 0) {
    ap_directive_t *pdir;
    m_event_mpm = 1; // disable features like keep-alive control
    for(pdir = ap_conftree; pdir != NULL; pdir = pdir->next) {
      if(strcasecmp(pdir->directive, "AsyncRequestWorkerFactor") == 0) {
        double val = 0;
        char *endptr;
        const char *arg = pdir->args;
        if(arg == NULL) {
          continue;
        }
        val = strtod(arg, &endptr);
        if(*endptr) {
          continue;
        }
        if(val < 0) {
          continue;
        }
        m_event_mpm_worker_factor = val;        
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, bs, 
                     QOS_LOGD_PFX"found AsyncRequestWorkerFactor directive '%f'",
                     m_event_mpm_worker_factor);
      }
    }
  } else if(strcasecmp(ap_show_mpm(), "worker") != 0) {
    // mod_qos is fully tested for MPM worker (and "works" with event)
    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs, 
                 QOS_LOG_PFX(009)"loaded MPM is '%s'"
                 " but mod_qos should be used with MPM 'Worker' or 'Event' only.",
                 ap_show_mpm());
  }

  if(strcasecmp(ap_show_mpm(), "prefork") == 0) {
    m_threaded_mpm = 0; // disable child cleanup (if neither worker nor event MPM)
  }
  
  ap_get_server_revision(&version);
  if(version.major == 2 && version.minor == 4 && version.patch == 49) {
    // 2.4.49 compat: prevents Apache segfault on connection close
    m_forced_close = 0;
  }

  if(version.major != 2 || version.minor != 4) {
    // 2.4 should work fine / older or newer versions are not tested
    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, bs, 
                 QOS_LOG_PFX(009)"server version is %d.%d"
                 " but mod_qos should be used with Apache 2.4 only.",
                 version.major, version.minor);
  }
}

/**
 * enforces the QS_RedirectIf variable
 * @param r
 * @param sconf
 * @param rules Rules array
 * @return HTTP_MOVED_TEMPORARILY/HTTP_TEMPORARY_REDIRECT or DECLINED
 */
static int qos_redirectif(request_rec *r, qos_srv_config *sconf, 
                          apr_array_header_t *rules) {
  ap_regmatch_t regm[AP_MAX_REG_MATCH];
  int i;
  qos_redirectif_entry_t *entries = (qos_redirectif_entry_t *)rules->elts;
  for(i = 0; i < rules->nelts; ++i) {
    qos_redirectif_entry_t *entry = &entries[i];
    const char *val = apr_table_get(r->subprocess_env, entry->name);
    if(val) {
      if(ap_regexec(entry->preg, val, AP_MAX_REG_MATCH, regm, 0) == 0) {
        int severity = sconf->log_only ? APLOG_WARNING : APLOG_ERR;
        char *replaced = ap_pregsub(r->pool, entry->url, val, AP_MAX_REG_MATCH, regm);
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|severity, 0, r,
                      QOS_LOG_PFX(049)"redirect to %s,"
                      " var=%s,"
                      " action=%s, c=%s, id=%s",
                      replaced,
                      entry->name,
                      sconf->log_only ? "log only" : "redirect",
                      QS_CONN_REMOTEIP(r->connection) == NULL ? "-" :
                      QS_CONN_REMOTEIP(r->connection),
                      qos_unique_id(r, "049"));
        QS_INC_EVENT(sconf, 49);
        if(!sconf->log_only) {
          apr_table_set(r->headers_out, "Location", replaced);
          return entry->code;
        }
      }
    }
  }
  return DECLINED;
}

static void qos_init_unique_id(apr_pool_t *p, server_rec *bs) {
  char str[APRMAXHOSTLEN + 1];
  apr_sockaddr_t *sockaddr;
  str[APRMAXHOSTLEN] = '\0';
  unsigned int in_addr = 0;
  if(apr_gethostname(str, sizeof(str) - 1, p) == APR_SUCCESS) {
    if(apr_sockaddr_info_get(&sockaddr, str, AF_INET, 0, 0, p) == APR_SUCCESS) {
      in_addr = sockaddr->sa.sin.sin_addr.s_addr;
    }
  }
  pid_t pid = getpid();
  m_unique_id.in_addr = pid^in_addr;
  m_unique_id.unique_id_counter = time(NULL);
}

/**
 * propagates environment variables to sub-requests (for logging)
 */
static void qos_propagate_events(request_rec *r) {
  request_rec *mr = NULL;
  const char **var;
  if(r->prev) {
    mr = r->prev;
  } else if(r->main) {
    mr = r->main;
  } else if(r->next) {
    mr = r->next;
  }
  var = m_env_variables;
  while(*var) {
    int propagated = 0;
    if(mr) {
      const char *p = apr_table_get(mr->subprocess_env, *var);
      if(p) {
        propagated = 1;
        apr_table_set(r->subprocess_env, *var, p);
      }
      if(!propagated) {
        p = apr_table_get(r->subprocess_env, *var);
        if(p) {
          propagated = 1;
          apr_table_set(mr->subprocess_env, *var, p);
        }
      }
    }
    var++;
  }
  if(r->prev) {
    // internal redirect (e.g. error page)
    int i;
    int len = strlen(QS_LIMIT_NAME_PFX);
    request_rec *mp = r->prev;
    apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(mp->subprocess_env)->elts;
    for(i = 0; i < apr_table_elts(mp->subprocess_env)->nelts; ++i) {
      if(strncmp(entry[i].key, QS_LIMIT_NAME_PFX, len) == 0) {
        const char *eventName = entry[i].val;
        const char *name = apr_pstrcat(r->pool, eventName, QS_COUNTER_SUFFIX, NULL);
        const char *value = apr_table_get(mp->subprocess_env, name);
        if(value) {
          apr_table_set(r->subprocess_env, name, value);
        }
        name = eventName;
        value = apr_table_get(mp->subprocess_env, name);
        if(value) {
          apr_table_set(r->subprocess_env, name, value);
        }
        name = apr_pstrcat(r->pool, eventName, QS_LIMIT_REMAINING, NULL);
        value = apr_table_get(mp->subprocess_env, name);
        if(value) {
          apr_table_set(r->subprocess_env, name, value);
        }
        name = apr_pstrcat(r->pool, eventName, QS_LIMIT_SEEN, NULL);
        value = apr_table_get(mp->subprocess_env, name);
        if(value) {
          apr_table_set(r->subprocess_env, name, value);
        }
      }
    }
  }
}

/**
 * ensure that every request record has the error notes to log
 */
static void qos_propagate_notes(request_rec *r) {
  request_rec *mr = NULL;
  const char **var;
  if(r->prev) {
    mr = r->prev;
  } else if(r->main) {
    mr = r->main;
  } else if(r->next) {
    mr = r->next;
  }
  var = m_note_variables;
  while(*var) {
    int propagated = 0;
    if(mr) {
      const char *p = apr_table_get(mr->notes, *var);
      if(p) {
        propagated = 1;
        apr_table_setn(r->notes, *var, p);
      }
      if(!propagated) {
        p = apr_table_get(r->notes, *var);
        if(p) {
          propagated = 1;
          apr_table_setn(mr->notes, *var, p);
        }
      }
    }
    var++;
  }
}


/* QS_UnsetReqHeader */
static void qos_unset_reqheader(request_rec *r, qos_srv_config *sconf) {
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->unsetreqheader_t)->elts;
  for(i = 0; i < apr_table_elts(sconf->unsetreqheader_t)->nelts; i++) {
    apr_table_unset(r->headers_in, entry[i].key);
  }
  return;
}

/* QS_UnsetResHeader */
static void qos_unset_resheader(request_rec *r, qos_srv_config *sconf) {
  int i;
  apr_table_entry_t *entry = (apr_table_entry_t *)apr_table_elts(sconf->unsetresheader_t)->elts;
  for(i = 0; i < apr_table_elts(sconf->unsetresheader_t)->nelts; i++) {
    apr_table_unset(r->headers_out, entry[i].key);
    apr_table_unset(r->err_headers_out, entry[i].key);
  }
  return;
}

/************************************************************************
 * handlers
 ***********************************************************************/

/**
 * Destructor to handle connections which do not have been established
 * successfully resp. have been closed unexpectedly.
 *
 * Increments block counter.
 *
 * @param p Connection base context
 * @return APR_SUCCESS
 */
static apr_status_t qos_base_cleanup_conn(void *p) {
  qs_conn_base_ctx *base = p;
  if(base->sconf->has_qos_cc || base->sconf->qos_cc_prefer) {
    int connRuleViolation = 0;
    const char *type = QS_EMPTY_CON;
    if(base->requests == 0 &&
       apr_table_get(base->sconf->setenvstatus_t, QS_EMPTY_CON) && 
       !apr_table_get(base->c->notes, QS_BLOCK_SEEN)) {
      connRuleViolation = 1;
      apr_table_set(base->c->notes, QS_BLOCK_SEEN, "");
    }
    if(apr_table_get(base->c->notes, QS_BROKEN_CON)) {
      connRuleViolation = 1;
      type = QS_BROKEN_CON;
    }
    if(apr_table_get(base->c->notes, QS_MAXIP)) {
      connRuleViolation = 1;
      type = QS_MAXIP;
    }
    if(connRuleViolation) {
      qos_user_t *u = qos_get_user_conf(base->sconf->act->ppool);
      qos_s_entry_t **clientEntry = NULL;
      qos_s_entry_t searchE;
      qos_ip_str2long(QS_CONN_REMOTEIP(base->c), searchE.ip6); // no ip simulation here
      apr_global_mutex_lock(u->qos_cc->lock);           /* @CRT40 */
      clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);

      /* increment block event */
      if((*clientEntry)->block < USHRT_MAX) {
        (*clientEntry)->block++;
      }
      if((*clientEntry)->block == 1) {
        /* ... and start timer */
        (*clientEntry)->blockTime = apr_time_sec(apr_time_now());
      }
      apr_global_mutex_unlock(u->qos_cc->lock);         /* @CRT40 */
      if(QS_ISDEBUG(base->c->base_server)) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base->c->base_server, 
                     QOS_LOGD_PFX"QS_ClientEventBlockCount rule: "
                     "%s event detected "
                     "c=%s",
                     type,
                     QS_CONN_REMOTEIP(base->c) == NULL ? "-" : QS_CONN_REMOTEIP(base->c));
      }
    }
  }
  return APR_SUCCESS;
}

/**
 * Connection destructor.
 *
 * Updates per IP events and connection counter.
 *
 * @param p Connection context
 * @return APR_SUCCESS
 */
static apr_status_t qos_cleanup_conn(void *p) {
  qs_conn_ctx *cconf = p;
  if(cconf->sconf->has_qos_cc || cconf->sconf->qos_cc_prefer) {
    qos_user_t *u = qos_get_user_conf(cconf->sconf->act->ppool);
    qos_s_entry_t **clientEntry = NULL;
    qos_s_entry_t searchE;
    searchE.ip6[0] = cconf->ip6[0];
    searchE.ip6[1] = cconf->ip6[1];
    apr_global_mutex_lock(u->qos_cc->lock);           /* @CRT15 */
    // generation: check if it has been cleard for this process generation
    if((m_generation != u->qos_cc->generation_locked) && u->qos_cc->connections > 0) {
      u->qos_cc->connections--;
    }
    clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
    if((*clientEntry)->events < UINT_MAX) {
      (*clientEntry)->events++; // update event activity even there is no valid request (logger)
    }
    if(cconf->set_vip_by_header) {
      (*clientEntry)->vip = 1;
    }
    if(cconf->has_lowrate) {
      (*clientEntry)->lowrate = time(NULL);
      (*clientEntry)->lowratestatus |= QOS_LOW_FLAG_PKGRATE;
    }
    apr_global_mutex_unlock(u->qos_cc->lock);         /* @CRT15 */
  }
  /* QS_SrvMaxConn or Geo */
  if(qos_count_connections(cconf->sconf)) {
    apr_global_mutex_lock(cconf->sconf->act->lock);   /* @CRT3 */
    if(cconf->sconf->act->conn && cconf->sconf->act->conn->connections > 0) {
      cconf->sconf->act->conn->connections--;
    }
    apr_global_mutex_unlock(cconf->sconf->act->lock); /* @CRT3 */
  }
  if(cconf->sconf->max_conn_per_ip != -1) {
    qos_dec_ip(cconf);
  }
  return APR_SUCCESS;
}

/**
 * handler to close the connection in the case the abort
 * by the pre-connect hook was ignored (Apache 2.4.28 Event MPM)
 */
static int qos_process_connection(conn_rec *connection) {
#if (AP_SERVER_MINORVERSION_NUMBER == 4)
#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
  if(connection->master) {
    // skip slave connections / process "real" connections only
    return DECLINED;
  }
#endif
#endif
  if(connection->aborted == 1 && apr_table_get(connection->notes, QS_CONN_ABORT)) {
    if(connection->cs) {
      connection->cs->state = CONN_STATE_LINGER;
    }
    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, connection->base_server,
                 QOS_LOG_PFX(167)"closing connection at process connection hook, c=%s",
                 QS_CONN_REMOTEIP(connection) == NULL ? "-" : 
                 QS_CONN_REMOTEIP(connection));

    return HTTP_INTERNAL_SERVER_ERROR;
  }
  return DECLINED;
}

/**
 * Connection constructor. Rules that are applied to established connections.
 *
 * @param c
 * @return
 */
static int qos_pre_process_connection(conn_rec *connection, void *skt) {
  conn_rec *c = connection;
  qs_conn_ctx *cconf;
  int vip = 0;
  apr_socket_t *socket = skt;
  
  if(c->sbh == NULL) {
    // proxy connections do NOT have any relation to the score board, don't handle them
    return DECLINED;
  }

#if (AP_SERVER_MINORVERSION_NUMBER == 4)
#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
  if(connection->master) {
    // skip slave connections / process "real" connections only
    return DECLINED;
  }
#endif
#endif

  cconf = qos_get_cconf(c);
  if(cconf == NULL) {
    int client_control = DECLINED;
    int connections = 0;
    int all_connections = 0;
    int current = 0;
    qs_ip_entry_t *conn_ip = NULL;
    char *msg = NULL;
    qos_srv_config *sconf = (qos_srv_config*)ap_get_module_config(c->base_server->module_config,
                                                                  &qos_module);
    qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
    cconf = qos_create_cconf(c, sconf);

    /* control timeout */
    if(sconf->req_rate != -1) {
      qos_timeout_pc(c, sconf);
    }

    /* packet rate */
    if(sconf->qos_cc_prefer_limit) {
      qos_pktrate_pc(c, sconf);
    }

    /* evaluates client ip */
    qos_ip_str2long(QS_CONN_REMOTEIP(c), cconf->ip6);
#ifdef QS_INTERNAL_TEST
    /* use one of the predefined ip addresses */
    if(cconf->sconf->enable_testip) {
      char *testid = apr_psprintf(c->pool, "%d", rand()%(m_qs_sim_ip_len-1));
      const char *testip = apr_table_get(cconf->sconf->testip, testid);
      qos_ip_str2long(testip, cconf->ip6);
    }
#endif

    /* ------------------------------------------------------------
     * update data
     */
    /* client control */
    client_control = qos_cc_pc_filter(c, cconf, u, &msg);
    /* QS_SrvMaxConn: vhost connections or Geo */
    if(qos_count_connections(sconf)) {
      apr_global_mutex_lock(cconf->sconf->act->lock);    /* @CRT4 */
      if(cconf->sconf->act->conn) {
        cconf->sconf->act->conn->connections++;
        all_connections = qos_server_connections(sconf);
        connections = cconf->sconf->act->conn->connections;
        apr_table_set(c->notes, "QS_SrvConn", apr_psprintf(c->pool, "%d", connections));
        apr_table_set(c->notes, "QS_AllConn", apr_psprintf(c->pool, "%d", all_connections));
      }
      apr_global_mutex_unlock(cconf->sconf->act->lock);  /* @CRT4 */
    }

    /* single source ip */
    if(sconf->max_conn_per_ip != -1) {
      current = qos_inc_ip(sconf, cconf, &conn_ip);
      apr_table_set(c->notes, "QS_IPConn", apr_psprintf(c->pool, "%d", current));
    }
    /* Check for vip (by ip) */
    vip = qos_is_excluded_ip(cconf->mc, sconf->exclude_ip);
    if(vip == 0) {
      // check if qos_cc_pc_filter() got vip status form the cc store
      vip = cconf->is_vip;
    }
    if(vip) {
      /* propagate vip to connection */
      cconf->is_vip = vip;
      if(!cconf->evmsg || !strstr(cconf->evmsg, "S;")) {
        cconf->evmsg = apr_pstrcat(c->pool, "S;", cconf->evmsg, NULL);
      }
    }

    /* ------------------------------------------------------------
     * enforce rules
     */
    /* client control */
    if((client_control != DECLINED) && !vip) {
      ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                   "%s",
                   msg == NULL ? "-" : msg);
      if(!sconf->log_only) {
        return qos_return_error_andclose(c, socket);
      }
    }
    /* Geo */
    if(sconf->geodb) {
      unsigned long ip = qos_geo_str2long(c->pool, QS_CONN_REMOTEIP(c));
      qos_geo_entry_t *pB = bsearch(&ip,
                              sconf->geodb->data,
                              sconf->geodb->size,
                              sizeof(qos_geo_entry_t),
                              qos_geo_comp);
      if(pB) {
        apr_table_set(c->notes, QS_COUNTRY, pB->country);
      }
      if(sconf->geo_limit != -1) {
        if(all_connections >= sconf->geo_limit) {
          if(sconf->geo_excludeUnknown == 1 && (pB == NULL || pB->country[0] == '\0')) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, 
                         QOS_LOGD_PFX"skip QS_ClientGeoCountryPriv enforcement"
                         " for client address %s which could not be mapped to country code",
                         QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN");
          } else if(pB == NULL || apr_table_get(sconf->geo_priv, pB->country) == NULL) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                         QOS_LOG_PFX(101)"access denied%s,"
                         " QS_ClientGeoCountryPriv rule: max=%d,"
                         " concurrent connections=%d,"
                         " c=%s"
                         " country=%s",
                         sconf->log_only ? " (log only)" : "",
                         sconf->geo_limit,
                         all_connections,
                         QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c),
                         pB != NULL ? pB->country : "--");
            QS_INC_EVENT(sconf, 101);
            if(!sconf->log_only) {
              return qos_return_error_andclose(c, socket);
            }
          }
        }
      }
    }
    /* QS_SrvMaxConn: vhost connections */
    if((sconf->max_conn != -1) && !vip) {
      if(connections > sconf->max_conn) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                     QOS_LOG_PFX(030)"access denied%s, QS_SrvMaxConn rule: max=%d,"
                     " concurrent connections=%d,"
                     " c=%s",
                     sconf->log_only ? " (log only)" : "",
                     sconf->max_conn, connections,
                     QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
        QS_INC_EVENT(sconf, 30);
        if(!sconf->log_only) {
          return qos_return_error_andclose(c, socket);
        }
      }
    }
    /* single source ip */
    if((sconf->max_conn_per_ip != -1) && (!vip || sconf->max_conn_per_ip_ignore_vip == 1)) {
      if((current > sconf->max_conn_per_ip) &&
         (all_connections >= sconf->max_conn_per_ip_connections)) {
        conn_ip->error++;
        if(apr_table_get(sconf->setenvstatus_t, QS_MAXIP)) {
          apr_table_set(c->notes, QS_MAXIP, "1");
        }
        /* only print the first 20 messages for this client */
        QS_INC_EVENT(sconf, 31);
        if(conn_ip->error <= QS_LOG_REPEAT) {
          ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                       QOS_LOG_PFX(031)"access denied%s,"
                       " QS_SrvMaxConnPerIP rule: max=%d,"
                       " concurrent connections=%d,"
                       " c=%s",
                       sconf->log_only ? " (log only)" : "",
                       sconf->max_conn_per_ip, current,
                       QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
        } else {
          if((conn_ip->error % QS_LOG_REPEAT) == 0) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                         QOS_LOG_PFX(031)"access denied%s,"
                         " QS_SrvMaxConnPerIP rule: max=%d,"
                         " concurrent connections=%d,"
                         " message repeated %d times,"
                         " c=%s",
                         sconf->log_only ? " (log only)" : "",
                         sconf->max_conn_per_ip, current,
                         QS_LOG_REPEAT,
                         QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
          }
        }
        if(!sconf->log_only) {
          return qos_return_error_andclose(c, socket);
        }
      } else {
        if(conn_ip) {
          if(conn_ip->error > QS_LOG_REPEAT) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                         QOS_LOG_PFX(031)"access denied (previously),"
                         " QS_SrvMaxConnPerIP rule: max=%d,"
                         " concurrent connections=%d,"
                         " message repeated %d times,"
                         " c=%s",
                         sconf->max_conn_per_ip, current,
                         conn_ip->error % QS_LOG_REPEAT,
                         QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
          }
          conn_ip->error = 0;
        }
      }
    }
  }
  return DECLINED;
}

/**
 * Pre connection
 * - constructs the connection ctx (stores socket ref)
 * - enforces block counter (as early as possible)
 */
static int qos_pre_connection(conn_rec *connection, void *skt) {
  conn_rec *c = connection;
  int ret = DECLINED;
  qos_srv_config *sconf;
  qs_conn_base_ctx *base;
  int excludeFromBlock;

  if(c->sbh == NULL) {
    // proxy connections do NOT have any relation to the score board, don't handle them
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, 
                 QOS_LOGD_PFX"skip processing of outgoing/virtual connection %s<->%s",
                 QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN",
                 c->local_ip ? c->local_ip : "UNKNOWN");
    return ret;
  }

#if (AP_SERVER_MINORVERSION_NUMBER == 4)
#if (AP_SERVER_PATCHLEVEL_NUMBER > 17)
  if(connection->master) {
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server, 
                 QOS_LOGD_PFX"skip slave connection %s",
                 QS_CONN_REMOTEIP(c) ? QS_CONN_REMOTEIP(c) : "UNKNOWN");
    return ret;
  }
#endif
#endif

  sconf = (qos_srv_config*)ap_get_module_config(c->base_server->module_config, &qos_module);
  excludeFromBlock = qos_is_excluded_ip(c, sconf->cc_exclude_ip);
  base = qos_get_conn_base_ctx(c);
  if(base == NULL) {
    base = qos_create_conn_base_ctx(c, sconf);
    base->clientSocket = skt;
  }

  if(sconf && (sconf->req_rate != -1)) {
    qos_ifctx_t *inctx = qos_create_ifctx(c, sconf);
    inctx->clientSocket = skt;
    ap_add_input_filter("qos-in-filter", inctx, NULL, c);
  }

  /* blocked by event (block only, no limit) - very aggressive */
  if(sconf->qos_cc_block && !excludeFromBlock) {
    qos_user_t *u = qos_get_user_conf(sconf->act->ppool);
    qos_s_entry_t **clientEntry = NULL;
    qos_s_entry_t searchE;
    qos_ip_str2long(QS_CONN_REMOTEIP(c), searchE.ip6); // no ip simulation here
    apr_global_mutex_lock(u->qos_cc->lock);           /* @CRT39 */
    clientEntry = qos_cc_getOrSet(u->qos_cc, &searchE, 0);
    if((*clientEntry)->block >= sconf->qos_cc_block) {
      apr_time_t now = time(NULL);
      if(((*clientEntry)->blockTime + sconf->qos_cc_blockTime) > now) {
        (*clientEntry)->blockMsg++;;
        // stop logging every event if we have logged it many times
        QS_INC_EVENT_LOCKED(sconf, 60);
        if((*clientEntry)->blockMsg > QS_LOG_REPEAT) {
          if(((*clientEntry)->blockMsg % QS_LOG_REPEAT) == 0) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                         QOS_LOG_PFX(060)"access denied, "
                         "QS_ClientEventBlockCount rule: "
                         "max=%d, current=%hu, "
                         "message repeated %d times, "
                         "c=%s",
                         sconf->qos_cc_block,
                         (*clientEntry)->block,
                         QS_LOG_REPEAT,
                         QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
          }
        } else {
          ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, c->base_server,
                       QOS_LOG_PFX(060)"access denied, QS_ClientEventBlockCount rule: "
                       "max=%d, current=%hu, age=%"APR_TIME_T_FMT", c=%s",
                       sconf->qos_cc_block,
                       (*clientEntry)->block,
                       now - (*clientEntry)->blockTime,
                       QS_CONN_REMOTEIP(c) == NULL ? "-" : QS_CONN_REMOTEIP(c));
        }
        if(!sconf->log_only) {
          apr_table_set(c->notes, QS_BLOCK_SEEN, ""); // suppress NullConnection messages
          c->keepalive = AP_CONN_CLOSE;
          c->aborted = 1;
          if(c->cs) {
            c->cs->state = CONN_STATE_LINGER;
          }
          apr_table_setn(c->notes, "short-lingering-close", "1");
          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 */
};
