2025-03-24 13:01:01 +01:00
/* -*-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 .
*
2025-03-24 14:50:13 +01:00
* Copyright ( C ) 2025 Pascal Buchbinder
2025-03-24 13:01:01 +01:00
*
* 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
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2025-03-24 14:50:13 +01:00
static const char revision [ ] = " $Id: mod_qos.c 2724 2025-01-03 15:05:21Z pbuchbinder $ " ;
static const char g_revision [ ] = " 11.76 " ;
2025-03-24 13:01:01 +01:00
/************************************************************************
* 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 ;
}
2025-03-24 14:50:13 +01:00
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 ;
}
2025-03-24 13:01:01 +01:00
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 ] = = ' # ' ) {
2025-03-24 14:50:13 +01:00
if ( strcmp ( " USERAGENT_IP " , & header [ 1 ] ) = = 0 ) {
return qos_forwardedfor_fromUserAgentIP ( r ) ;
}
return qos_pseudoip ( r , & header [ 1 ] ) ;
2025-03-24 13:01:01 +01:00
} else {
2025-03-24 14:50:13 +01:00
return qos_forwardedfor_fromHeader ( r , header ) ;
2025-03-24 13:01:01 +01:00
}
}
/**
* 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 ( 06 9 ) " 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 ( 06 9 ) " 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 ;
}
2025-03-24 14:50:13 +01:00
apr_table_setn ( c - > notes , " short-lingering-close " , " 1 " ) ;
2025-03-24 13:01:01 +01:00
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 ( 04 8 ) " 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 ( 06 8 ) " 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 ( 06 8 ) " QS_ClientSerialize exceeds limit of 5 minutes, "
" c=%s, id=%s " ,
forwardedForLogIP = = NULL ? " - " : forwardedForLogIP ,
qos_unique_id ( r , " 068 " ) ) ;
QS_INC_EVENT ( sconf , 68 ) ;
/* remove this request from the queue resp. clear the queue
to avoid a deadlock */
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT36.1 */
clientEntry = qos_cc_getOrSet ( u - > qos_cc , & searchE , apr_time_sec ( r - > request_time ) ) ;
( * clientEntry ) - > serializeQueue = 0 ;
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT36.1 */
break ;
}
loops + + ;
}
}
}
/*
* QS_ClientEventRequestLimit
*/
static int qos_hp_cc_event_count ( request_rec * r , qos_srv_config * sconf ,
qs_req_ctx * rctx ) {
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
qs_conn_ctx * cconf = qos_get_cconf ( r - > connection ) ;
if ( ! rctx ) {
rctx = qos_rctx_config_get ( r ) ;
}
if ( u & & cconf & &
r - > subprocess_env & & apr_table_get ( r - > subprocess_env , " QS_EventRequest " ) ) {
int vip = 0 ;
int count = 0 ;
qos_s_entry_t * * clientEntry = NULL ;
qos_s_entry_t searchE ;
const char * forwardedForLogIP = qos_get_clientIP ( r , sconf , cconf ,
" hp " , rctx - > cc_event_ip ) ;
rctx - > cc_event_req_set = 1 ;
searchE . ip6 [ 0 ] = rctx - > cc_event_ip [ 0 ] ;
searchE . ip6 [ 1 ] = rctx - > cc_event_ip [ 1 ] ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT33 */
clientEntry = qos_cc_getOrSet ( u - > qos_cc , & searchE , apr_time_sec ( r - > request_time ) ) ;
if ( ( * clientEntry ) - > event_req < INT_MAX ) {
( * clientEntry ) - > event_req + + ;
}
count = ( * clientEntry ) - > event_req ;
if ( ( * clientEntry ) - > vip | | rctx - > is_vip ) {
vip = 1 ;
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT33 */
if ( vip ) {
apr_table_set ( r - > subprocess_env , QS_ISVIPREQ , " yes " ) ;
}
if ( count > sconf - > qos_cc_event_req ) {
if ( vip ) {
qs_set_evmsg ( r , " S; " ) ;
} else {
int rc ;
const char * error_page = sconf - > error_page ;
ap_log_rerror ( APLOG_MARK , APLOG_NOERRNO | APLOG_ERR , 0 , r ,
QOS_LOG_PFX ( 065 ) " access denied%s, "
" QS_ClientEventRequestLimit rule: "
" max=%d, current=%d, c=%s, id=%s " ,
sconf - > log_only ? " (log only) " : " " ,
sconf - > qos_cc_event_req ,
count ,
forwardedForLogIP = = NULL ? " - " :
forwardedForLogIP ,
qos_unique_id ( r , " 065 " ) ) ;
QS_INC_EVENT ( sconf , 65 ) ;
qs_set_evmsg ( r , " D; " ) ;
if ( ! sconf - > log_only ) {
rc = qos_error_response ( r , error_page ) ;
if ( ( rc = = DONE ) | | ( rc = = HTTP_MOVED_TEMPORARILY ) ) {
return rc ;
}
return m_retcode ;
}
}
}
}
return DECLINED ;
}
/* QS_SetEnvIfCmp */
static void qos_setenvifcmp ( request_rec * r , apr_array_header_t * cmps ) {
int i ;
qos_cmp_entry_t * entries = ( qos_cmp_entry_t * ) cmps - > elts ;
for ( i = 0 ; i < cmps - > nelts ; + + i ) {
qos_cmp_entry_t * b = & entries [ i ] ;
const char * leftStr = apr_table_get ( r - > subprocess_env , b - > left ) ;
const char * rightStr = apr_table_get ( r - > subprocess_env , b - > right ) ;
if ( leftStr ! = NULL & & rightStr ! = NULL ) {
int set = 0 ;
if ( qos_isnum ( leftStr ) & & qos_isnum ( rightStr ) ) {
int left = atoi ( leftStr ) ;
int right = atoi ( rightStr ) ;
switch ( b - > cmp ) {
case QS_CMP_EQ :
if ( left = = right ) {
set = 1 ;
}
break ;
case QS_CMP_NE :
if ( left ! = right ) {
set = 1 ;
}
break ;
case QS_CMP_GT :
if ( left > right ) {
set = 1 ;
}
break ;
case QS_CMP_LT :
if ( left < right ) {
set = 1 ;
}
break ;
}
} else {
int c = strcasecmp ( leftStr , rightStr ) ;
switch ( b - > cmp ) {
case QS_CMP_EQ :
if ( c = = 0 ) {
set = 1 ;
}
break ;
case QS_CMP_NE :
if ( c ! = 0 ) {
set = 1 ;
}
break ;
case QS_CMP_GT :
if ( c < 0 ) {
set = 1 ;
}
break ;
case QS_CMP_LT :
if ( c > 0 ) {
set = 1 ;
}
break ;
}
}
if ( set ) {
if ( b - > variable [ 0 ] = = ' ! ' ) {
apr_table_unset ( r - > subprocess_env , & b - > variable [ 1 ] ) ;
} else {
apr_table_set ( r - > subprocess_env , b - > variable , b - > value ) ;
}
}
}
}
return ;
}
/*
* QS_EventPerSecLimit / QS_EventKBytesPerSecLimit
* returns the max req_per_sec_block_rate / kbytes_per_sec_limit and the event
* with the lowest kbytes_per_sec_limit .
*/
static qs_acentry_t * qos_hp_event_count ( request_rec * r ,
int * req_per_sec_block ,
apr_off_t * kbytes_per_sec_limit ) {
qos_srv_config * sconf = ( qos_srv_config * ) ap_get_module_config ( r - > server - > module_config ,
& qos_module ) ;
qs_actable_t * act = sconf - > act ;
qs_acentry_t * event_kbytes_limit = NULL ;
* req_per_sec_block = 0 ;
* kbytes_per_sec_limit = 0 ;
if ( act - > has_events ) {
qs_acentry_t * actEntry = act - > entry ;
if ( actEntry ) {
apr_global_mutex_lock ( act - > lock ) ; /* @CRT12 */
while ( actEntry ) {
if ( actEntry - > event & & ( actEntry - > limit = = - 1 ) ) {
if ( ( ( actEntry - > event [ 0 ] ! = ' ! ' ) & & apr_table_get ( r - > subprocess_env , actEntry - > event ) ) | |
( ( actEntry - > event [ 0 ] = = ' ! ' ) & & ! apr_table_get ( r - > subprocess_env , & actEntry - > event [ 1 ] ) ) ) {
if ( actEntry - > req_per_sec_limit ) {
/* QS_EventPerSecLimit */
if ( actEntry - > req_per_sec_block_rate > * req_per_sec_block ) {
* req_per_sec_block = actEntry - > req_per_sec_block_rate ;
}
} else {
/* QS_EventKBytesPerSecLimit */
if ( actEntry - > kbytes_per_sec_limit ) {
if ( ( * kbytes_per_sec_limit = = 0 ) | |
( actEntry - > kbytes_per_sec_limit < * kbytes_per_sec_limit ) ) {
* kbytes_per_sec_limit = actEntry - > kbytes_per_sec_limit ;
event_kbytes_limit = actEntry ;
}
}
}
}
}
actEntry = actEntry - > next ;
}
apr_global_mutex_unlock ( act - > lock ) ; /* @CRT12 */
}
}
return event_kbytes_limit ;
}
static apr_size_t qos_packet_rate ( qos_ifctx_t * inctx , apr_bucket_brigade * bb ) {
apr_bucket * b ;
apr_size_t total = 0 ;
for ( b = APR_BRIGADE_FIRST ( bb ) ; b ! = APR_BRIGADE_SENTINEL ( bb ) ; b = APR_BUCKET_NEXT ( b ) ) {
if ( b - > length ) {
total = total + b - > length ;
}
}
return total ;
}
/**
* start packet rate measure ( if filter has not already been inserted )
*/
static void qos_pktrate_pc ( conn_rec * connection , qos_srv_config * sconf ) {
conn_rec * c = connection ;
qos_ifctx_t * inctx = qos_get_ifctx ( c - > input_filters ) ;
if ( inctx = = NULL ) {
inctx = qos_create_ifctx ( c , sconf ) ;
ap_add_input_filter ( " qos-in-filter " , inctx , NULL , c ) ;
}
inctx - > lowrate = 0 ;
}
/**
* timeout control at process connection handler
*/
static void qos_timeout_pc ( conn_rec * connection , qos_srv_config * sconf ) {
conn_rec * c = connection ;
qos_ifctx_t * inctx = qos_get_ifctx ( c - > input_filters ) ;
if ( inctx ) {
inctx - > status = QS_CONN_STATE_HEAD ;
inctx - > time = time ( NULL ) ;
inctx - > nbytes = 0 ;
# if APR_HAS_THREADS
if ( sconf - > inctx_t & & ! sconf - > inctx_t - > exit & & sconf - > min_rate_off = = 0 ) {
apr_thread_mutex_lock ( sconf - > inctx_t - > lock ) ; /* @CRT22 */
apr_table_setn ( sconf - > inctx_t - > table ,
QS_INCTX_ID ,
( char * ) inctx ) ;
apr_thread_mutex_unlock ( sconf - > inctx_t - > lock ) ; /* @CRT22 */
}
# endif
}
}
/**
* determines client behavior based on accessed content types
*
* @ return 0 = normal , - 1 = unknown ( not enough data ) , > = 1 abnormal
*/
static int qos_content_type ( request_rec * r , qos_srv_config * sconf ,
qos_s_t * s , qos_s_entry_t * e , int limit ) {
int penalty = - 1 ;
const char * ct = apr_table_get ( r - > headers_out , " Content-Type " ) ;
e - > events + + ; // events counts requests and connections
if ( r - > status = = 304 ) {
e - > notmodified + + ;
s - > notmodified + + ;
}
if ( ct ) {
if ( ap_strcasestr ( ct , " html " ) ) {
e - > events + + ; // learn faster if user requests HTML content (main pages)
e - > html + + ;
s - > html + + ;
goto end ;
} else if ( ap_strcasestr ( ct , " image " ) ) {
e - > img + + ;
s - > img + + ;
goto end ;
} else if ( ap_strcasestr ( ct , " css " ) ) {
e - > cssjs + + ;
s - > cssjs + + ;
goto end ;
} else if ( ap_strcasestr ( ct , " javascript " ) ) {
e - > cssjs + + ;
s - > cssjs + + ;
goto end ;
}
}
e - > other + + ;
s - > other + + ;
end :
/* compare this client with other clients */
if ( e - > events > QOS_CC_BEHAVIOR_THR_SINGLE ) {
penalty = 0 ;
if ( limit & &
( ( sconf - > static_on = = 1 ) | |
( s - > html > QOS_CC_BEHAVIOR_THR & & s - > html & & s - > img & & s - > cssjs & & s - > other & & s - > notmodified ) ) ) {
int i ;
unsigned int server [ 5 ] ;
unsigned int client [ 5 ] ;
// note: all e->* variables are initialized by "1" to avoid FPE
if ( sconf - > static_on = = 1 ) {
/* use predefined value */
unsigned long e_all = e - > html + e - > img + e - > cssjs + e - > other + e - > notmodified ;
server [ 0 ] = sconf - > static_html ;
server [ 1 ] = sconf - > static_cssjs ;
server [ 2 ] = sconf - > static_img ;
server [ 3 ] = sconf - > static_other ;
server [ 4 ] = sconf - > static_notmodified ;
client [ 0 ] = 100 * e - > html / e_all ;
client [ 1 ] = 100 * e - > cssjs / e_all ;
client [ 2 ] = 100 * e - > img / e_all ;
client [ 3 ] = 100 * e - > other / e_all ;
client [ 4 ] = 100 * e - > notmodified / e_all ;
} else {
/* learn average */
unsigned long long s_all = s - > html + s - > img + s - > cssjs + s - > other + s - > notmodified ;
unsigned long e_all = e - > html + e - > img + e - > cssjs + e - > other + e - > notmodified ;
server [ 0 ] = 100 * s - > html / s_all ;
server [ 1 ] = 100 * s - > cssjs / s_all ;
server [ 2 ] = 100 * s - > img / s_all ;
server [ 3 ] = 100 * s - > other / s_all ;
server [ 4 ] = 100 * s - > notmodified / s_all ;
client [ 0 ] = 100 * e - > html / e_all ;
client [ 1 ] = 100 * e - > cssjs / e_all ;
client [ 2 ] = 100 * e - > img / e_all ;
client [ 3 ] = 100 * e - > other / e_all ;
client [ 4 ] = 100 * e - > notmodified / e_all ;
}
for ( i = 0 ; i < 5 ; i + + ) {
if ( client [ i ] > ( server [ i ] + sconf - > cc_tolerance ) ) {
penalty + + ;
} else {
if ( ( server [ i ] > sconf - > cc_tolerance ) & &
( client [ i ] < ( server [ i ] - sconf - > cc_tolerance ) ) ) {
penalty + + ;
}
}
}
}
}
return penalty ;
}
//static void qos_error_log(const char *file, int line, int level,
// apr_status_t status, const server_rec *s,
// const request_rec *r, apr_pool_t *pool,
// const char *errstr) {
// return;
//}
/**
* QS_EventLimitCount , detect / update only
*/
static void qos_logger_event_limit ( request_rec * r , qos_srv_config * sconf ) {
qs_actable_t * act = sconf - > act ;
if ( act - > event_entry & & ( sconf - > event_limit_a - > nelts > 0 ) ) {
apr_time_t now = apr_time_sec ( r - > request_time ) ;
int i ;
qos_event_limit_entry_t * actEntry = act - > event_entry ;
apr_global_mutex_lock ( act - > lock ) ; /* @CRT42 */
for ( i = 0 ; i < sconf - > event_limit_a - > nelts ; i + + ) {
if ( actEntry - > action = = QS_EVENT_ACTION_DENY ) {
int decEvent = get_qs_event ( r , actEntry - > eventDecStr ) ;
if ( decEvent > 0 ) {
if ( decEvent > = actEntry - > limit ) {
actEntry - > limit = 0 ;
actEntry - > limitTime = 0 ;
} else {
actEntry - > limit = actEntry - > limit - decEvent ;
}
}
if ( apr_table_get ( r - > subprocess_env , actEntry - > env_var ) ! = NULL ) {
// increment only once
char * eventLimitId = apr_pstrcat ( r - > pool , QS_R013_ALREADY_BLOCKED , actEntry - > env_var , NULL ) ;
if ( apr_table_get ( r - > notes , eventLimitId ) = = NULL ) {
// reset required (expired)?
if ( actEntry - > limitTime + actEntry - > seconds < now ) {
actEntry - > limit = 0 ;
actEntry - > limitTime = 0 ;
}
/* increment limit event */
if ( actEntry - > limit < INT_MAX ) {
actEntry - > limit + + ;
}
if ( actEntry - > limit = = 1 ) {
/* ... and start timer */
actEntry - > limitTime = now ;
}
}
}
}
// next rule
actEntry + + ;
}
apr_global_mutex_unlock ( act - > lock ) ; /* @CRT42 */
}
}
/**
* client control rules at log transaction
*/
static void qos_logger_cc ( request_rec * r , qos_srv_config * sconf , qs_req_ctx * rctx ) {
int lowrate = 0 ;
int unusual_behavior = - 1 ;
int block_event = get_qs_event ( r , QS_BLOCK ) ;
const char * block_seen = apr_table_get ( r - > subprocess_env , QS_BLOCK_SEEN ) ;
int block_dec = get_qs_event ( r , QS_BLOCK_DEC ) ;
qs_conn_ctx * cconf = qos_get_cconf ( r - > connection ) ;
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
apr_time_t now = apr_time_sec ( r - > request_time ) ;
qos_s_entry_t * * clientEntry = NULL ;
qos_s_entry_t * * clientEntryFromHdr = NULL ; // client ip entry from header
qos_s_entry_t searchE ;
qos_s_entry_t searchEFromHdr ;
if ( block_seen ! = NULL ) {
block_event = 0 ;
}
if ( sconf - > qos_cc_prefer_limit | | ( sconf - > req_rate ! = - 1 ) ) {
qos_ifctx_t * inctx = qos_get_ifctx ( r - > connection - > input_filters ) ;
if ( inctx ) {
if ( inctx - > lowrate > QS_PKT_RATE_TH ) {
lowrate = inctx - > lowrate ;
}
if ( inctx - > lowrate ! = - 1 ) {
inctx - > lowrate = 0 ;
}
if ( inctx - > status > QS_CONN_STATE_NEW ) {
inctx - > r = NULL ;
inctx - > status = QS_CONN_STATE_KEEP ;
}
if ( inctx - > shutdown ) {
lowrate + + ;
inctx - > shutdown = 0 ;
}
}
}
if ( cconf ) {
// works for real connections only (no HTTP/2)
searchE . ip6 [ 0 ] = cconf - > ip6 [ 0 ] ;
searchE . ip6 [ 1 ] = cconf - > ip6 [ 1 ] ;
} else {
// HTTP/2
apr_uint64_t ci6 [ 2 ] ;
qos_ip_str2long ( QS_CONN_REMOTEIP ( r - > connection ) , ci6 ) ;
searchE . ip6 [ 0 ] = ci6 [ 0 ] ;
searchE . ip6 [ 1 ] = ci6 [ 1 ] ;
}
qos_get_clientIP ( r , sconf , cconf , " logger " , searchEFromHdr . ip6 ) ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT19 */
clientEntry = qos_cc_getOrSet ( u - > qos_cc , & searchE , apr_time_sec ( r - > request_time ) ) ;
if ( searchEFromHdr . ip6 [ 0 ] = = searchE . ip6 [ 0 ] & & searchEFromHdr . ip6 [ 1 ] = = searchE . ip6 [ 1 ] ) {
clientEntryFromHdr = clientEntry ; // same as connection
} else {
clientEntryFromHdr = qos_cc_getOrSet ( u - > qos_cc , & searchEFromHdr , apr_time_sec ( r - > request_time ) ) ;
}
if ( rctx - > cc_event_req_set ) {
/* QS_ClientEventRequestLimit */
qos_s_entry_t * * eEvent = NULL ;
rctx - > cc_event_req_set = 0 ;
if ( rctx - > cc_event_ip [ 0 ] = = searchE . ip6 [ 0 ] & &
rctx - > cc_event_ip [ 1 ] = = searchE . ip6 [ 1 ] ) {
// connection ip
eEvent = clientEntry ;
} else if ( rctx - > cc_event_ip [ 0 ] = = searchEFromHdr . ip6 [ 0 ] & &
rctx - > cc_event_ip [ 1 ] = = searchEFromHdr . ip6 [ 1 ] ) {
// from header
eEvent = clientEntryFromHdr ;
} else {
// looks like the header has changed or is no longer available
qos_s_entry_t searchEvent ;
searchEvent . ip6 [ 0 ] = rctx - > cc_event_ip [ 0 ] ;
searchEvent . ip6 [ 1 ] = rctx - > cc_event_ip [ 1 ] ;
eEvent = qos_cc_get0 ( u - > qos_cc , & searchEvent , apr_time_sec ( r - > request_time ) ) ;
}
if ( eEvent ) {
if ( ( * eEvent ) - > event_req > 0 ) {
( * eEvent ) - > event_req - - ;
}
}
}
if ( rctx - > cc_serialize_set ) {
/* QS_ClientSerialize */
qos_s_entry_t * * eSerialize = NULL ;
rctx - > cc_serialize_set = 0 ;
if ( rctx - > cc_serialize_ip [ 0 ] = = searchE . ip6 [ 0 ] & &
rctx - > cc_serialize_ip [ 1 ] = = searchE . ip6 [ 1 ] ) {
// connection ip
eSerialize = clientEntry ;
} else if ( rctx - > cc_serialize_ip [ 0 ] = = searchEFromHdr . ip6 [ 0 ] & &
rctx - > cc_serialize_ip [ 1 ] = = searchEFromHdr . ip6 [ 1 ] ) {
// from header
eSerialize = clientEntryFromHdr ;
} else {
// looks like the header has changed or is no longer available
qos_s_entry_t searchSerialize ;
searchSerialize . ip6 [ 0 ] = rctx - > cc_serialize_ip [ 0 ] ;
searchSerialize . ip6 [ 1 ] = rctx - > cc_serialize_ip [ 1 ] ;
eSerialize = qos_cc_get0 ( u - > qos_cc , & searchSerialize , apr_time_sec ( r - > request_time ) ) ;
}
if ( eSerialize ) {
( * eSerialize ) - > serialize = 0 ;
}
}
if ( sconf - > qos_cc_prefer ) {
// QS_ClientPrefer is not enabled
unusual_behavior = qos_content_type ( r , sconf , u - > qos_cc , * clientEntry , sconf - > qos_cc_prefer_limit ) ;
if ( unusual_behavior = = 0 ) {
// normal behavior
( * clientEntry ) - > lowratestatus | = QOS_LOW_FLAG_BEHAVIOR_OK ;
( * clientEntry ) - > lowratestatus & = ~ QOS_LOW_FLAG_BEHAVIOR_BAD ;
} else {
// unknown or bad behavior
( * clientEntry ) - > lowratestatus & = ~ QOS_LOW_FLAG_BEHAVIOR_OK ;
}
}
if ( block_event | | block_dec | | lowrate | | ( unusual_behavior > 0 ) ) {
if ( ( ( * clientEntry ) - > blockTime + sconf - > qos_cc_blockTime ) < now ) {
/* reset expired events */
if ( ( * clientEntry ) - > blockMsg > QS_LOG_REPEAT ) {
// write remaining log lines
ap_log_error ( APLOG_MARK , APLOG_NOERRNO | APLOG_ERR , 0 , r - > connection - > base_server ,
QOS_LOG_PFX ( 060 ) " access denied (previously), "
" QS_ClientEventBlockCount rule: "
" max=%d, current=%hu, "
" message repeated %d times, "
" c=%s " ,
sconf - > qos_cc_block ,
( * clientEntry ) - > block ,
( * clientEntry ) - > blockMsg % QS_LOG_REPEAT ,
QS_CONN_REMOTEIP ( r - > connection ) = = NULL ? " - " :
QS_CONN_REMOTEIP ( r - > connection ) /*no id here, this r is okay*/ ) ;
QS_INC_EVENT_LOCKED ( sconf , 60 ) ;
( * clientEntry ) - > blockMsg = 0 ;
}
( * clientEntry ) - > block = 0 ;
( * clientEntry ) - > blockTime = 0 ;
}
/* mark lowpkt client */
if ( lowrate | | ( unusual_behavior > 0 ) ) {
( * clientEntry ) - > lowrate = apr_time_sec ( r - > request_time ) ;
if ( lowrate ) {
( * clientEntry ) - > lowratestatus | = QOS_LOW_FLAG_PKGRATE ;
}
if ( unusual_behavior > 1 ) {
( * clientEntry ) - > lowratestatus | = QOS_LOW_FLAG_BEHAVIOR_BAD ;
( * clientEntry ) - > lowratestatus & = ~ QOS_LOW_FLAG_BEHAVIOR_OK ;
}
qs_set_evmsg ( r , " r; " ) ;
}
if ( block_dec > 0 ) {
if ( block_dec > = ( * clientEntry ) - > block ) {
( * clientEntry ) - > block = 0 ;
( * clientEntry ) - > blockTime = 0 ;
} else {
( * clientEntry ) - > block = ( * clientEntry ) - > block - block_dec ;
}
}
if ( block_event ) {
int newValue = ( * clientEntry ) - > block + block_event ;
if ( ( * clientEntry ) - > block = = 0 ) {
/* start timer */
( * clientEntry ) - > blockTime = now ;
}
/* ...and increment/increase block event counter */
( * clientEntry ) - > block = newValue > USHRT_MAX ? USHRT_MAX : newValue ;
}
} else if ( ( * clientEntry ) - > lowrate ) {
/* reset low prio client after 24h (resp. QOS_LOW_TIMEOUT seconds) */
if ( ( ( * clientEntry ) - > lowrate + QOS_LOW_TIMEOUT ) < now ) {
( * clientEntry ) - > lowrate = 0 ;
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK ) {
( * clientEntry ) - > lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK ;
} else {
( * clientEntry ) - > lowratestatus = 0 ;
}
}
}
/* QS_Limit* */
if ( u - > qos_cc - > limitTable ) {
int limitTableIndex ;
apr_table_entry_t * limitTableEntry = ( apr_table_entry_t * ) apr_table_elts ( u - > qos_cc - > limitTable ) - > elts ;
for ( limitTableIndex = 0 ;
limitTableIndex < apr_table_elts ( u - > qos_cc - > limitTable ) - > nelts ;
limitTableIndex + + ) {
int eventSet = 0 ;
const char * eventName = limitTableEntry [ limitTableIndex ] . key ;
qos_s_entry_limit_conf_t * eventLimitConf = ( qos_s_entry_limit_conf_t * ) limitTableEntry [ limitTableIndex ] . val ;
const char * clearEvent = apr_table_get ( r - > subprocess_env , eventLimitConf - > eventClearStr ) ;
int decEvent = get_qs_event ( r , eventLimitConf - > eventDecStr ) ;
/*
* reset expired events , clear event counter , decrement event counter
*/
if ( clearEvent | |
( ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime + eventLimitConf - > limitTime ) < now ) ) {
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = 0 ;
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime = 0 ;
}
if ( decEvent > 0 ) {
if ( decEvent > = ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit ) {
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = 0 ;
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime = 0 ;
} else {
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit - decEvent ;
}
}
/*
* check for new events
*/
eventSet = get_qs_event ( r , eventName ) ;
if ( eventSet ) {
char * seenEvent ;
if ( strcasecmp ( eventName , QS_LIMIT_DEFAULT ) = = 0 ) {
// backward compat/event forwarding
seenEvent = apr_pstrcat ( r - > pool , QS_LIMIT_SEEN , NULL ) ;
} else {
seenEvent = apr_pstrcat ( r - > pool , QS_LIMIT_SEEN , eventName , NULL ) ;
}
if ( apr_table_get ( r - > subprocess_env , seenEvent ) = = NULL ) {
int newValue = ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit + eventSet ;
/* only once per request */
apr_table_set ( r - > subprocess_env , seenEvent , " " ) ;
if ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = = 0 ) {
/* start timer... */
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime = now ;
}
/* ... and increase limit event */
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = newValue > USHRT_MAX ? USHRT_MAX : newValue ;
}
}
}
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT19 */
if ( block_event ) {
/* only once per request */
apr_table_set ( r - > subprocess_env , QS_BLOCK_SEEN , " " ) ;
apr_table_set ( r - > connection - > notes , QS_BLOCK_SEEN , " " ) ;
}
}
/**
* client control rules at header parser
*/
static int qos_hp_cc ( request_rec * r , qos_srv_config * sconf , char * * msg , char * * uid ) {
int ret = DECLINED ;
if ( sconf - > has_qos_cc ) {
int req_per_sec_block_rate = 0 ;
qos_s_entry_t * * clientEntry = NULL ;
qos_s_entry_t * * clientEntryFromHdr = NULL ;
qos_s_entry_t searchE ;
qos_s_entry_t searchEFromHdr ;
qs_conn_ctx * cconf = qos_get_cconf ( r - > connection ) ;
const char * forwardedForLogIP = QS_CONN_REMOTEIP ( r - > connection ) ;
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
int excludeFromBlock = qos_is_excluded_ip ( r - > connection , sconf - > cc_exclude_ip ) ;
if ( cconf ) {
searchE . ip6 [ 0 ] = cconf - > ip6 [ 0 ] ;
searchE . ip6 [ 1 ] = cconf - > ip6 [ 1 ] ;
} else {
// HTTP/2
apr_uint64_t ci6 [ 2 ] ;
qos_ip_str2long ( QS_CONN_REMOTEIP ( r - > connection ) , ci6 ) ;
searchE . ip6 [ 0 ] = ci6 [ 0 ] ;
searchE . ip6 [ 1 ] = ci6 [ 1 ] ;
}
forwardedForLogIP = qos_get_clientIP ( r , sconf , cconf , " hp " ,
searchEFromHdr . ip6 ) ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT17 */
clientEntry = qos_cc_get0 ( u - > qos_cc , & searchE , apr_time_sec ( r - > request_time ) ) ;
if ( ! clientEntry ) {
clientEntry = qos_cc_set ( u - > qos_cc , & searchE , apr_time_sec ( r - > request_time ) ) ;
} else {
/* update time */
( * clientEntry ) - > time = apr_time_sec ( r - > request_time ) ;
}
if ( searchEFromHdr . ip6 [ 0 ] = = searchE . ip6 [ 0 ] & & searchEFromHdr . ip6 [ 1 ] = = searchE . ip6 [ 1 ] ) {
clientEntryFromHdr = clientEntry ; // same as connection
} else {
clientEntryFromHdr = qos_cc_get0 ( u - > qos_cc , & searchEFromHdr , apr_time_sec ( r - > request_time ) ) ;
if ( ! clientEntryFromHdr ) {
clientEntryFromHdr = qos_cc_set ( u - > qos_cc , & searchEFromHdr , apr_time_sec ( r - > request_time ) ) ;
} else {
/* update time */
( * clientEntryFromHdr ) - > time = apr_time_sec ( r - > request_time ) ;
}
}
if ( sconf - > qos_cc_event ) {
apr_time_t now = apr_time_sec ( r - > request_time ) ;
const char * v = apr_table_get ( r - > subprocess_env , QS_EVENT ) ;
if ( v ) {
if ( ( * clientEntry ) - > req < LONG_MAX ) {
( * clientEntry ) - > req + + ;
}
if ( now > ( * clientEntry ) - > interval + QS_BW_SAMPLING_RATE ) {
/* calc req/sec */
( * clientEntry ) - > req_per_sec = ( * clientEntry ) - > req / ( now - ( * clientEntry ) - > interval ) ;
( * clientEntry ) - > req = 0 ;
( * clientEntry ) - > interval = now ;
/* calc block rate */
if ( ( * clientEntry ) - > req_per_sec > sconf - > qos_cc_event ) {
int factor = ( ( ( * clientEntry ) - > req_per_sec * 100 ) / sconf - > qos_cc_event ) - 100 ;
( * clientEntry ) - > req_per_sec_block_rate = ( * clientEntry ) - > req_per_sec_block_rate + factor ;
if ( ( * clientEntry ) - > req_per_sec_block_rate > QS_MAX_DELAY / 1000 ) {
( * clientEntry ) - > req_per_sec_block_rate = QS_MAX_DELAY / 1000 ;
}
/* QS_ClientEventPerSecLimit */
ap_log_rerror ( APLOG_MARK , APLOG_NOERRNO | APLOG_WARNING , 0 , r ,
QOS_LOG_PFX ( 061 ) " request rate limit, "
" rule: " QS_EVENT " (%d), req/sec=%ld, "
" delay=%dms%s " ,
sconf - > qos_cc_event ,
( * clientEntry ) - > req_per_sec , ( * clientEntry ) - > req_per_sec_block_rate ,
( * clientEntry ) - > req_per_sec_block_rate = = QS_MAX_DELAY / 1000 ? " (max) " : " " ) ;
QS_INC_EVENT_LOCKED ( sconf , 61 ) ;
} else if ( ( * clientEntry ) - > req_per_sec_block_rate > 0 ) {
if ( ( * clientEntry ) - > req_per_sec_block_rate < 50 ) {
( * clientEntry ) - > req_per_sec_block_rate = 0 ;
} else {
int factor = ( * clientEntry ) - > req_per_sec_block_rate / 4 ;
( * clientEntry ) - > req_per_sec_block_rate = ( * clientEntry ) - > req_per_sec_block_rate - factor ;
}
ap_log_rerror ( APLOG_MARK , APLOG_NOERRNO | APLOG_INFO , 0 , r ,
QOS_LOG_PFX ( 062 ) " request rate limit, "
" rule: " QS_EVENT " (%d), req/sec=%ld, "
" delay=%dms " ,
sconf - > qos_cc_event ,
( * clientEntry ) - > req_per_sec , ( * clientEntry ) - > req_per_sec_block_rate ) ;
QS_INC_EVENT_LOCKED ( sconf , 62 ) ;
}
}
req_per_sec_block_rate = ( * clientEntry ) - > req_per_sec_block_rate ;
}
}
if ( sconf - > qos_cc_block & & ! excludeFromBlock ) {
apr_time_t now = apr_time_sec ( r - > request_time ) ;
int block_event = get_qs_event ( r , QS_BLOCK ) ;
if ( ( ( * clientEntry ) - > blockTime + sconf - > qos_cc_blockTime ) < now ) {
/* reset expired events */
if ( ( * clientEntry ) - > blockMsg > QS_LOG_REPEAT ) {
// write remaining log lines
ap_log_error ( APLOG_MARK , APLOG_NOERRNO | APLOG_ERR , 0 , r - > connection - > base_server ,
QOS_LOG_PFX ( 060 ) " access denied (previously), "
" QS_ClientEventBlockCount rule: "
" max=%d, current=%hu, "
" message repeated %d times, "
" c=%s " ,
sconf - > qos_cc_block ,
( * clientEntry ) - > block ,
( * clientEntry ) - > blockMsg % QS_LOG_REPEAT ,
QS_CONN_REMOTEIP ( r - > connection ) = = NULL ? " - " :
QS_CONN_REMOTEIP ( r - > connection ) /*no id here, this r is okay*/ ) ;
QS_INC_EVENT_LOCKED ( sconf , 60 ) ;
( * clientEntry ) - > blockMsg = 0 ;
}
( * clientEntry ) - > block = 0 ;
( * clientEntry ) - > blockTime = 0 ;
}
if ( block_event ) {
if ( ( * clientEntry ) - > block = = 0 ) {
/* start timer */
( * clientEntry ) - > blockTime = now ;
}
/* ... and increment block event */
( * clientEntry ) - > block + = block_event ;
/* only once per request */
apr_table_set ( r - > subprocess_env , QS_BLOCK_SEEN , " " ) ;
apr_table_set ( r - > connection - > notes , QS_BLOCK_SEEN , " " ) ;
}
if ( ( * clientEntry ) - > block > = sconf - > qos_cc_block ) {
* uid = apr_pstrdup ( r - > connection - > pool , " 060 " ) ;
* msg = apr_psprintf ( r - > connection - > pool ,
QOS_LOG_PFX ( 060 ) " access denied%s, "
" QS_ClientEventBlockCount rule: "
" max=%d, current=%hu, age=% " APR_TIME_T_FMT " , c=%s " ,
sconf - > log_only ? " (log only) " : " " ,
sconf - > qos_cc_block ,
( * clientEntry ) - > block ,
now - ( * clientEntry ) - > blockTime ,
QS_CONN_REMOTEIP ( r - > connection ) = = NULL ? " - " :
QS_CONN_REMOTEIP ( r - > connection ) ) ;
QS_INC_EVENT_LOCKED ( sconf , 60 ) ;
ret = m_retcode ;
( * clientEntry ) - > lowrate = apr_time_sec ( r - > request_time ) ;
( * clientEntry ) - > lowratestatus | = QOS_LOW_FLAG_EVENTBLOCK ;
qs_set_evmsg ( r , " r; " ) ;
}
}
if ( u - > qos_cc - > limitTable ) {
apr_time_t now = apr_time_sec ( r - > request_time ) ;
int limitTableIndex ;
apr_table_entry_t * limitTableEntry = ( apr_table_entry_t * ) apr_table_elts ( u - > qos_cc - > limitTable ) - > elts ;
for ( limitTableIndex = 0 ;
limitTableIndex < apr_table_elts ( u - > qos_cc - > limitTable ) - > nelts ;
limitTableIndex + + ) {
time_t remaining = 0 ;
int eventSet = 0 ;
const char * eventName = limitTableEntry [ limitTableIndex ] . key ;
qos_s_entry_limit_conf_t * eventLimitConf = ( qos_s_entry_limit_conf_t * ) limitTableEntry [ limitTableIndex ] . val ;
const char * clearEvent = apr_table_get ( r - > subprocess_env , eventLimitConf - > eventClearStr ) ;
/*
* reset expired events or clear event counter
*/
if ( clearEvent | |
( ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime + eventLimitConf - > limitTime ) < now ) ) {
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = 0 ;
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime = 0 ;
}
/*
* check for new events
*/
eventSet = get_qs_event ( r , eventName ) ;
if ( eventSet ) {
char * seenEvent ;
if ( strcasecmp ( eventName , QS_LIMIT_DEFAULT ) = = 0 ) {
// backward compat/event forwarding
seenEvent = apr_pstrcat ( r - > pool , QS_LIMIT_SEEN , NULL ) ;
} else {
seenEvent = apr_pstrcat ( r - > pool , QS_LIMIT_SEEN , eventName , NULL ) ;
}
if ( apr_table_get ( r - > subprocess_env , seenEvent ) = = NULL ) {
int newValue = ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit + eventSet ;
// first occurrence
apr_table_set ( r - > subprocess_env , seenEvent , " " ) ;
if ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = = 0 ) {
/* .start timer */
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime = now ;
}
/* increment limit event */
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit = newValue > USHRT_MAX ? USHRT_MAX : newValue ;
}
}
/*
* propagate to env
*/
apr_table_set ( r - > subprocess_env ,
apr_pstrcat ( r - > pool , QS_LIMIT_NAME_PFX , eventName , NULL ) ,
eventName ) ;
apr_table_set ( r - > subprocess_env ,
apr_pstrcat ( r - > pool , eventName , QS_COUNTER_SUFFIX , NULL ) ,
apr_psprintf ( r - > pool , " %d " , ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit ) ) ;
remaining = ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime + eventLimitConf - > limitTime ) - now ;
apr_table_set ( r - > subprocess_env ,
apr_pstrcat ( r - > pool , eventName , QS_LIMIT_REMAINING , NULL ) ,
apr_psprintf ( r - > pool , " % " APR_TIME_T_FMT , remaining > 0 ? remaining : 0 ) ) ;
/*
* enforce limit
*/
if ( ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit > = eventLimitConf - > limit ) {
int block = 1 ;
char * conditional = " " ;
if ( eventLimitConf - > condStr ! = NULL ) {
// conditional enforcement...
const char * condition = apr_table_get ( r - > subprocess_env , QS_COND ) ;
conditional = apr_pstrdup ( r - > pool , " Cond " ) ;
if ( condition = = NULL ) {
block = 0 ; // variable not set
} else {
if ( ap_regexec ( eventLimitConf - > preg , condition , 0 , NULL , 0 ) ! = 0 ) {
block = 0 ; // pattern does not match
}
}
}
if ( block ) {
if ( ret = = DECLINED | | clientEntryFromHdr ! = clientEntry ) {
/* log only one error (either block or limit) */
* uid = apr_pstrdup ( r - > connection - > pool , " 067 " ) ;
* msg = apr_psprintf ( r - > connection - > pool ,
QOS_LOG_PFX ( 067 ) " access denied%s, "
" QS_%sClientEventLimitCount rule: "
" event=%s, "
" max=%hu, current=%hu, age=% " APR_TIME_T_FMT " , c=%s " ,
sconf - > log_only ? " (log only) " : " " ,
conditional ,
eventName ,
eventLimitConf - > limit ,
( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limit ,
now - ( * clientEntryFromHdr ) - > limit [ limitTableIndex ] . limitTime ,
forwardedForLogIP = = NULL ? " - " : forwardedForLogIP ) ;
QS_INC_EVENT_LOCKED ( sconf , 67 ) ;
ret = m_retcode ;
}
}
if ( clientEntryFromHdr = = clientEntry ) {
( * clientEntry ) - > lowrate = apr_time_sec ( r - > request_time ) ;
( * clientEntry ) - > lowratestatus | = QOS_LOW_FLAG_EVENTLIMIT ;
qs_set_evmsg ( r , " r; " ) ;
}
}
}
}
/* reset low prio client after 24h */
if ( clientEntry & & sconf - > qos_cc_prefer ) {
apr_time_t now = apr_time_sec ( r - > request_time ) ;
if ( ( ( * clientEntry ) - > lowrate + QOS_LOW_TIMEOUT ) < now ) {
( * clientEntry ) - > lowrate = 0 ;
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK ) {
( * clientEntry ) - > lowratestatus = QOS_LOW_FLAG_BEHAVIOR_OK ;
} else {
( * clientEntry ) - > lowratestatus = 0 ;
}
}
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT17 */
if ( req_per_sec_block_rate ) {
qs_set_evmsg ( r , " L; " ) ;
if ( ! sconf - > log_only ) {
apr_sleep ( req_per_sec_block_rate * 1000 ) ;
}
}
}
return ret ;
}
# if APR_HAS_THREADS
static apr_status_t qos_cleanup_status_thread ( void * selfv ) {
qsstatus_t * s = selfv ;
s - > exit = 1 ;
/* may long up to 100ms */
if ( m_threaded_mpm ) {
apr_status_t status ;
apr_thread_join ( & status , s - > thread ) ;
//apr_pool_destroy(s->pool);
}
return APR_SUCCESS ;
}
// checks if connection counting is enabled (any host)
static int qos_count_connections ( qos_srv_config * sconf ) {
server_rec * s = sconf - > base_server ;
qos_srv_config * bsconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( QS_COUNT_CONNECTIONS ( bsconf ) ) {
return 1 ;
}
s = s - > next ;
while ( s ) {
qos_srv_config * sc = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( QS_COUNT_CONNECTIONS ( sc ) ) {
return 1 ;
}
s = s - > next ;
}
return 0 ;
}
// total (server/all hosts) connections
static int qos_server_connections ( qos_srv_config * sconf ) {
server_rec * s = sconf - > base_server ;
qos_srv_config * bsconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
int connections = bsconf - > act - > conn - > connections ;
s = s - > next ;
while ( s ) {
qos_srv_config * sc = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( sc - > act - > conn ! = bsconf - > act - > conn ) {
// server has either his own counter or the base server's counter
connections + = sc - > act - > conn - > connections ;
}
s = s - > next ;
}
return connections ;
/*
int i , j ;
worker_score * ws_record ;
process_score * ps_record ;
for ( i = 0 ; i < sconf - > server_limit ; + + i ) {
ps_record = ap_get_scoreboard_process ( i ) ;
for ( j = 0 ; j < sconf - > thread_limit ; + + j ) {
ws_record = ap_get_scoreboard_worker ( i , j ) ;
if ( ! ps_record - > quiescing & & ps_record - > pid ) {
if ( ws_record - > status = = SERVER_READY & & ps_record - > generation = = qos_my_generation ) {
ready + + ;
}
}
}
}
*/
}
/**
* Status logger thread
*
* @ param thread
* @ param selfv Base server_rec
*/
static void * APR_THREAD_FUNC qos_status_thread ( apr_thread_t * thread , void * selfv ) {
qsstatus_t * s = selfv ;
int server_limit , thread_limit ;
ap_mpm_query ( AP_MPMQ_HARD_LIMIT_THREADS , & thread_limit ) ;
ap_mpm_query ( AP_MPMQ_HARD_LIMIT_DAEMONS , & server_limit ) ;
while ( ! s - > exit ) {
char clientContentTypes [ 8192 ] ;
char allConn [ 64 ] ;
int s_busy = 0 ;
int s_open = 0 ;
int s_ready = 0 ;
int s_read = 0 ;
int s_write = 0 ;
int s_keep = 0 ;
int s_start = 0 ;
int s_log = 0 ;
int s_dns = 0 ;
int s_closing = 0 ;
int s_usr1 = 0 ;
int s_kill = 0 ;
worker_score ws_record ;
int c , i , e ;
time_t now = time ( NULL ) ;
int run = 0 ;
e = 60 - ( now % 60 ) ;
e = e * 10 ;
for ( c = 0 ; c < e ; c + + ) {
apr_sleep ( 100000 ) ;
if ( s - > exit ) {
run = 0 ;
break ;
}
}
if ( ! s - > exit ) {
apr_global_mutex_lock ( s - > lock ) ; /* @CRT47 */
now = time ( NULL ) ;
if ( * s - > qsstatustimer < = now ) {
// set next and fetch the data
* s - > qsstatustimer = ( now / 60 * 60 + 60 ) ;
run = 1 ;
} else {
// another child already did the job
run = 0 ;
}
apr_global_mutex_unlock ( s - > lock ) ; /* @CRT47 */
}
if ( ! s - > exit & & run ) {
for ( i = 0 ; i < server_limit ; + + i ) {
int j ;
for ( j = 0 ; j < thread_limit ; + + j ) {
int res ;
ap_copy_scoreboard_worker ( & ws_record , i , j ) ;
res = ws_record . status ;
if ( res = = SERVER_DEAD ) {
s_open + + ;
} else if ( res = = SERVER_READY ) {
s_ready + + ;
} else if ( res = = SERVER_BUSY_READ ) {
s_read + + ;
s_busy + + ;
} else if ( res = = SERVER_BUSY_WRITE ) {
s_write + + ;
s_busy + + ;
} else if ( res = = SERVER_BUSY_KEEPALIVE ) {
s_keep + + ;
s_busy + + ;
} else if ( res = = SERVER_STARTING ) {
s_start + + ;
} else if ( res = = SERVER_BUSY_LOG ) {
s_log + + ;
s_busy + + ;
} else if ( res = = SERVER_BUSY_DNS ) {
s_dns + + ;
s_busy + + ;
} else if ( res = = SERVER_CLOSING ) {
s_closing + + ;
} else if ( res = = SERVER_GRACEFUL ) {
s_usr1 + + ;
} else if ( res = = SERVER_IDLE_KILL ) {
s_kill + + ;
}
}
}
clientContentTypes [ 0 ] = ' \0 ' ;
if ( s - > sconf - > qos_cc_prefer ) {
qos_user_t * u = qos_get_user_conf ( s - > sconf - > act - > ppool ) ;
if ( u ) {
unsigned long long html ;
unsigned long long cssjs ;
unsigned long long img ;
unsigned long long other ;
unsigned long long notmodified ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT48 */
html = u - > qos_cc - > html ;
cssjs = u - > qos_cc - > cssjs ;
img = u - > qos_cc - > img ;
other = u - > qos_cc - > other ;
notmodified = u - > qos_cc - > notmodified ;
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT48 */
snprintf ( clientContentTypes , 8191 , " , \" clientContentTypes \" : { "
" \" html \" : %llu, \" css/js \" : %llu, "
" \" images \" : %llu, \" other \" : %llu, \" 304 \" : %llu } " ,
html , cssjs ,
img , other , notmodified
) ;
}
}
allConn [ 0 ] = ' \0 ' ;
if ( qos_count_connections ( s - > sconf ) ) {
apr_global_mutex_lock ( s - > lock ) ; /* @CRT52 */
int all_connections = qos_server_connections ( s - > sconf ) ;
snprintf ( allConn , 64 , " , \" QS_AllConn \" : %d " ,
all_connections
) ;
apr_global_mutex_unlock ( s - > lock ) ; /* @CRT52 */
}
ap_log_error ( APLOG_MARK , APLOG_NOTICE , 0 , s - > sconf - > base_server ,
QOS_LOG_PFX ( 200 ) " { \" scoreboard \" : { "
" \" open \" : %d, \" waiting \" : %d, \" read \" : %d, "
" \" write \" : %d, \" keepalive \" : %d, "
" \" start \" : %d, \" log \" : %d, "
" \" dns \" : %d, \" closing \" : %d, "
" \" finishing \" : %d, \" idle \" : %d }, "
" \" maxclients \" : { "
" \" max \" : %d, \" busy \" : %d "
" %s } "
" %s } " ,
s_open , s_ready , s_read ,
s_write , s_keep ,
s_start , s_log ,
s_dns , s_closing ,
s_usr1 , s_kill ,
s - > maxclients , s_busy ,
allConn ,
clientContentTypes ) ;
}
}
if ( m_threaded_mpm ) {
apr_thread_exit ( thread , APR_SUCCESS ) ;
}
return NULL ;
}
/**
* Starts the stats logger thread
*/
static void qos_init_status_thread ( apr_pool_t * p , qos_srv_config * sconf , int maxclients ) {
qs_actable_t * act = sconf - > act ;
apr_pool_t * pool ;
apr_threadattr_t * tattr ;
qsstatus_t * s ;
apr_pool_create ( & pool , NULL ) ;
s = apr_pcalloc ( pool , sizeof ( qsstatus_t ) ) ;
s - > exit = 0 ;
s - > pool = pool ;
s - > maxclients = maxclients ;
s - > qsstatustimer = act - > qsstatustimer ;
s - > lock = act - > lock ;
s - > sconf = sconf ;
if ( apr_threadattr_create ( & tattr , pool ) = = APR_SUCCESS ) {
if ( apr_thread_create ( & s - > thread , tattr , qos_status_thread , s , pool ) = = APR_SUCCESS ) {
apr_pool_pre_cleanup_register ( p , s , qos_cleanup_status_thread ) ;
}
}
}
# endif
/**
* client control rules at process connection handler
*/
static int qos_cc_pc_filter ( conn_rec * connection , qs_conn_ctx * cconf , qos_user_t * u , char * * msg ) {
conn_rec * c = connection ;
int ret = DECLINED ;
if ( cconf & & cconf - > sconf - > has_qos_cc ) {
qos_s_entry_t * * clientEntry = NULL ;
qos_s_entry_t searchE ;
searchE . ip6 [ 0 ] = cconf - > ip6 [ 0 ] ;
searchE . ip6 [ 1 ] = cconf - > ip6 [ 1 ] ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT14 */
clientEntry = qos_cc_getOrSet ( u - > qos_cc , & searchE , 0 ) ;
/* early vip detection */
if ( ( * clientEntry ) - > vip ) {
cconf - > is_vip = 1 ;
apr_table_set ( c - > notes , QS_ISVIPREQ , " yes " ) ;
}
/* max connections */
if ( cconf - > sconf - > has_qos_cc & & cconf - > sconf - > qos_cc_prefer ) {
if ( m_generation ! = u - > qos_cc - > generation_locked ) {
u - > qos_cc - > connections + + ;
} else {
ap_log_error ( APLOG_MARK , APLOG_NOERRNO | APLOG_WARNING , 0 , c - > base_server ,
QOS_LOG_PFX ( 166 ) " unexpected connection dispatching, skipping "
" connection counter update for QS_ClientPrefer rule, c=%s " ,
QS_CONN_REMOTEIP ( cconf - > mc ) = = NULL ? " - " :
QS_CONN_REMOTEIP ( cconf - > mc ) ) ;
}
if ( ( * clientEntry ) - > lowrate ) {
if ( c - > notes ) {
char * flags = apr_psprintf ( c - > pool , " 0x%02x " , ( * clientEntry ) - > lowratestatus ) ;
apr_table_set ( c - > notes , " QS_ClientLowPrio " , flags ) ;
}
}
/* non vip (allow all vip addresses - no restrictions) */
if ( ! ( * clientEntry ) - > vip ) {
if ( u - > qos_cc - > connections > cconf - > sconf - > qos_cc_prefer_limit ) {
int penalty = 4 ; // 2 to 12 points
int reqSpare = 0 ;
if ( ( * clientEntry ) - > lowrate ) {
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_PKGRATE ) {
penalty + + ;
}
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_BEHAVIOR_BAD ) {
penalty + = 2 ;
}
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_EVENTBLOCK ) {
penalty + = 2 ;
}
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_EVENTLIMIT ) {
penalty + = 2 ;
}
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_TIMEOUT ) {
penalty + + ;
}
}
if ( ( * clientEntry ) - > lowratestatus & QOS_LOW_FLAG_BEHAVIOR_OK ) {
// normal behavior
penalty - = 2 ;
}
// calculate the min. required free connections allowing us to serve request by this IP
reqSpare = ( cconf - > sconf - > max_clients - cconf - > sconf - > qos_cc_prefer_limit ) * penalty / 12 ;
if ( ( cconf - > sconf - > max_clients - u - > qos_cc - > connections ) < reqSpare ) {
/* not enough free connections */
if ( u - > qos_cc - > connections > cconf - > sconf - > qos_cc_prefer_limit ) {
* msg = apr_psprintf ( cconf - > mc - > pool ,
QOS_LOG_PFX ( 066 ) " access denied%s, "
" QS_ClientPrefer rule (penalty=%d 0x%02x): "
" max=%d, concurrent connections=%d, c=%s " ,
cconf - > sconf - > log_only ? " (log only) " : " " ,
penalty , ( * clientEntry ) - > lowratestatus ,
cconf - > sconf - > qos_cc_prefer_limit , u - > qos_cc - > connections ,
QS_CONN_REMOTEIP ( cconf - > mc ) = = NULL ? " - " :
QS_CONN_REMOTEIP ( cconf - > mc ) ) ;
QS_INC_EVENT_LOCKED ( cconf - > sconf , 66 ) ;
ret = m_retcode ;
}
}
}
}
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT14 */
}
return ret ;
}
/**
* calculates the current minimal up / download bandwidth
*/
static int qos_req_rate_calc ( qos_srv_config * sconf , int * current ) {
int req_rate = sconf - > req_rate ;
if ( sconf - > min_rate_max ! = - 1 ) {
int connections = qos_server_connections ( sconf ) ;
if ( connections > sconf - > req_rate_start ) {
/* keep the minimal rate until reaching the min connections */
req_rate = req_rate + ( sconf - > min_rate_max * connections / sconf - > max_clients ) ;
if ( connections > sconf - > max_clients ) {
// limit the max rate to its max if we have more connections then expected
if ( connections > ( sconf - > max_clients + QS_DOUBLE_CONN_H ) ) {
ap_log_error ( APLOG_MARK , APLOG_CRIT , 0 , sconf - > base_server ,
QOS_LOG_PFX ( 036 ) " QS_SrvMinDataRate: unexpected connection status! "
" connections=%d, "
" cal. request rate=%d, "
" max. limit=%d. "
" Check log for unclean child exit and consider "
" to do a graceful server restart if this condition persists. "
" You might also increase the number of supported connections "
" using the 'QS_MaxClients' directive. " ,
connections , req_rate , sconf - > min_rate_max ) ;
}
QS_INC_EVENT ( sconf , 36 ) ;
req_rate = sconf - > min_rate_max ;
}
}
* current = connections ;
}
return req_rate ;
}
qos_s_entry_limit_conf_t * qos_getQSLimitEvent ( qos_user_t * u , const char * event ,
int * limitTableIndex ) {
int i = 0 ;
apr_table_entry_t * limitTableEntry = ( apr_table_entry_t * ) apr_table_elts ( u - > qos_cc - > limitTable ) - > elts ;
for ( i = 0 ; i < apr_table_elts ( u - > qos_cc - > limitTable ) - > nelts ; i + + ) {
const char * eventName = limitTableEntry [ i ] . key ;
if ( strcasecmp ( eventName , event ) = = 0 ) {
* limitTableIndex = i ;
return ( qos_s_entry_limit_conf_t * ) limitTableEntry [ i ] . val ;
}
}
return NULL ;
}
/************************************************************************
* " public "
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* short status viewer
*/
static void qos_ext_status_short ( request_rec * r , apr_table_t * qt ) {
qos_srv_config * sconf = ( qos_srv_config * ) ap_get_module_config ( r - > server - > module_config ,
& qos_module ) ;
server_rec * s = sconf - > base_server ;
qos_srv_config * bsconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config ,
& qos_module ) ;
const char * option = apr_table_get ( qt , " option " ) ;
const char * all_connections = apr_table_get ( r - > subprocess_env , " QS_AllConn " ) ;
apr_time_t now = apr_time_sec ( r - > request_time ) ;
double av [ 1 ] ;
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
# if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
getloadavg ( av , 1 ) ;
ap_rprintf ( r , " b " QOS_DELIM " system.load: %.2f \n " , av [ 0 ] ) ;
# endif
if ( u - > qos_cc & & sconf - > qos_cc_prefer ) {
unsigned long long html ;
unsigned long long cssjs ;
unsigned long long img ;
unsigned long long other ;
unsigned long long notmodified ;
char clientContentTypes [ 8192 ] ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT51 */
html = u - > qos_cc - > html ;
cssjs = u - > qos_cc - > cssjs ;
img = u - > qos_cc - > img ;
other = u - > qos_cc - > other ;
notmodified = u - > qos_cc - > notmodified ;
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT51 */
snprintf ( clientContentTypes , 8191 ,
" b;clientContentTypes(html,css/js,images,other,304): "
" %llu %llu %llu %llu %llu " ,
html , cssjs , img , other , notmodified
) ;
ap_rprintf ( r , " %s \n " , clientContentTypes ) ;
}
while ( s ) {
char * sn = apr_psprintf ( r - > pool , " %s " QOS_DELIM " %s " QOS_DELIM " %d " ,
s - > is_virtual ? " v " : " b " ,
s - > server_hostname = = NULL ? " - " :
ap_escape_html ( r - > pool , s - > server_hostname ) ,
s - > addrs - > host_port ) ;
sconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( all_connections & & ! s - > is_virtual ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_AllConn: %s \n " , sn , all_connections ) ;
}
if ( ( s - > is_virtual & & ( sconf ! = bsconf ) ) | | ! s - > is_virtual ) {
qs_acentry_t * actEntry ;
if ( ! s - > is_virtual & & sconf - > has_qos_cc & & sconf - > qos_cc_prefer_limit ) {
int hc = u - > qos_cc - > connections ; /* not synchronized ... */
ap_rprintf ( r , " %s " QOS_DELIM " QS_ClientPrefer " QOS_DELIM " %d[]: %d \n " , sn ,
sconf - > qos_cc_prefer_limit , hc ) ;
}
/* request level */
actEntry = sconf - > act - > entry ;
while ( actEntry ) {
if ( ( actEntry - > limit > 0 ) & & ! actEntry - > condition & & ! actEntry - > event ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_LocRequestLimit%s " QOS_DELIM " %d[%s]: %d \n " , sn ,
actEntry - > regex = = NULL ? " " : " Match " ,
actEntry - > limit ,
actEntry - > url ,
actEntry - > counter ) ;
}
if ( ( actEntry - > req_per_sec_limit > 0 ) & & ! actEntry - > event ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_LocRequestPerSecLimit%s " QOS_DELIM " %ld[%s]: %ld \n " , sn ,
actEntry - > regex = = NULL ? " " : " Match " ,
actEntry - > req_per_sec_limit ,
actEntry - > url ,
actEntry - > req_per_sec ) ;
}
if ( ( actEntry - > kbytes_per_sec_limit > 0 ) & & ! actEntry - > event ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_LocKBytesPerSecLimit%s " QOS_DELIM " % " APR_OFF_T_FMT " [%s]: % " APR_OFF_T_FMT " \n " , sn ,
actEntry - > regex = = NULL ? " " : " Match " ,
actEntry - > kbytes_per_sec_limit ,
actEntry - > url ,
actEntry - > kbytes_per_sec ) ;
}
if ( actEntry - > condition & & ! actEntry - > event ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_CondLocRequestLimitMatch " QOS_DELIM " %d[%s]: %d \n " , sn ,
actEntry - > limit ,
actEntry - > url ,
actEntry - > counter ) ;
}
if ( actEntry - > event & & ( actEntry - > limit ! = - 1 ) ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_EventRequestLimit " QOS_DELIM " %d[%s]: %d \n " , sn ,
actEntry - > limit ,
actEntry - > url ,
actEntry - > counter ) ;
}
if ( actEntry - > event & & ( actEntry - > kbytes_per_sec_limit ! = 0 ) ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_EventKBytesPerSecLimit " QOS_DELIM " % " APR_OFF_T_FMT " [%s]: % " APR_OFF_T_FMT " \n " , sn ,
actEntry - > kbytes_per_sec_limit ,
actEntry - > url ,
now > ( apr_time_sec ( actEntry - > kbytes_interval_us ) + ( QS_BW_SAMPLING_RATE * 10 ) ) ? 0 : actEntry - > kbytes_per_sec ) ;
}
if ( actEntry - > event & & ( actEntry - > req_per_sec_limit > 0 ) ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_EventPerSecLimit " QOS_DELIM " %ld[%s]: %ld \n " , sn ,
actEntry - > req_per_sec_limit ,
actEntry - > url ,
now > ( actEntry - > interval + ( QS_BW_SAMPLING_RATE * 3 ) ) ? 0 : actEntry - > req_per_sec ) ;
}
actEntry = actEntry - > next ;
}
/* event limit */
if ( sconf - > event_limit_a - > nelts > 0 ) {
int ie = 0 ;
qos_event_limit_entry_t * event_limit = sconf - > act - > event_entry ;
for ( ie = 0 ; ie < sconf - > event_limit_a - > nelts ; ie + + ) {
int elimit = event_limit - > limit ;
if ( event_limit - > limitTime + event_limit - > seconds < = now ) {
elimit = 0 ;
}
if ( event_limit - > action = = QS_EVENT_ACTION_DENY ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_%sEventLimitCount " QOS_DELIM " %d/%d[%s]: %d \n " ,
sn ,
event_limit - > condStr = = NULL ? " " : " Cond " ,
event_limit - > max ,
event_limit - > seconds ,
event_limit - > env_var ,
elimit ) ;
}
event_limit + + ;
}
}
if ( ! s - > is_virtual | | sconf - > act - > conn ! = bsconf - > act - > conn ) {
if ( sconf - > max_conn ! = - 1 ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_SrvMaxConn " QOS_DELIM " %d[]: %d \n " , sn ,
sconf - > max_conn ,
sconf - > act - > conn - > connections ) ;
}
if ( sconf - > max_conn_close ! = - 1 ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_SrvMaxConnClose " QOS_DELIM " %d[]: %d \n " , sn ,
sconf - > max_conn_close ,
sconf - > act - > conn - > connections ) ;
}
if ( option & & strstr ( option , " ip " ) ) {
if ( sconf - > act - > conn - > connections ) {
apr_table_t * entries = apr_table_make ( r - > pool , 100 ) ;
int j ;
apr_table_entry_t * entry ;
qos_collect_ip ( r , sconf , entries , sconf - > max_conn_per_ip , 0 ) ;
entry = ( apr_table_entry_t * ) apr_table_elts ( entries ) - > elts ;
for ( j = 0 ; j < apr_table_elts ( entries ) - > nelts ; j + + ) {
ap_rprintf ( r , " %s " QOS_DELIM " QS_SrvMaxConnPerIP " QOS_DELIM " %s: %s \n " ,
sn ,
entry [ j ] . key , entry [ j ] . val ) ;
}
}
}
}
}
s = s - > next ;
}
if ( u - > qos_cc & & sconf - > qsevents ) {
int i = 0 ;
apr_table_t * eTable = apr_table_make ( r - > pool , QOS_LOG_MSGCT ) ;
apr_table_entry_t * entry ;
char buf1 [ 1024 ] ;
char buf2 [ 1024 ] ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT50 */
while ( m_knownEvents [ i ] > 0 ) {
snprintf ( buf1 , 1023 , " e;mod_qos(%03d);new: %llu " ,
m_knownEvents [ i ] , u - > qos_cc - > eventLast [ m_knownEvents [ i ] ] ) ;
snprintf ( buf2 , 1023 , " e;mod_qos(%03d);total: %llu " ,
m_knownEvents [ i ] , u - > qos_cc - > eventTotal [ m_knownEvents [ i ] ] ) ;
apr_table_add ( eTable , buf1 , buf2 ) ;
u - > qos_cc - > eventLast [ m_knownEvents [ i ] ] = 0 ;
i + + ;
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT50 */
entry = ( apr_table_entry_t * ) apr_table_elts ( eTable ) - > elts ;
for ( i = 0 ; i < apr_table_elts ( eTable ) - > nelts ; + + i ) {
ap_rprintf ( r , " %s \n %s \n " , entry [ i ] . key , entry [ i ] . val ) ;
}
}
}
/**
* Comperator for bsearch function
*/
static int qos_geo_comp ( const void * _pA , const void * _pB ) {
unsigned long * pA = ( unsigned long * ) _pA ;
qos_geo_entry_t * pB = ( qos_geo_entry_t * ) _pB ;
unsigned long search = * pA ;
if ( ( search > = pB - > start ) & & ( search < = pB - > end ) ) return 0 ;
if ( search > pB - > start ) return 1 ;
if ( search < pB - > start ) return - 1 ;
return - 1 ; // error
}
/**
* Translates an IP address ( from geo csv ) to a numeric value .
*
* @ param pool To dup the string while parsing .
* @ param ip
* @ return
*/
static unsigned long qos_geo_str2long ( apr_pool_t * pool , const char * ip ) {
char * p ;
char * i = apr_pstrdup ( pool , ip ) ;
unsigned long addr = 0 ;
p = strchr ( i , ' . ' ) ;
if ( ! p ) return 0 ;
p [ 0 ] = ' \0 ' ;
if ( ! qos_is_num ( i ) ) return 0 ;
addr + = ( atol ( i ) * 16777216 ) ;
i = p ;
i + + ;
p = strchr ( i , ' . ' ) ;
if ( ! p ) return 0 ;
p [ 0 ] = ' \0 ' ;
if ( ! qos_is_num ( i ) ) return 0 ;
addr + = ( atol ( i ) * 65536 ) ;
i = p ;
i + + ;
p = strchr ( i , ' . ' ) ;
if ( ! p ) return 0 ;
p [ 0 ] = ' \0 ' ;
if ( ! qos_is_num ( i ) ) return 0 ;
addr + = ( atol ( i ) * 256 ) ;
i = p ;
i + + ;
if ( ! qos_is_num ( i ) ) return 0 ;
addr + = ( atol ( i ) ) ;
return addr ;
}
/**
* Viewer settings about ip address information .
*/
static void qos_show_ip ( request_rec * r , qos_srv_config * sconf , apr_table_t * qt ) {
int max_conn_per_ip = 0 ;
server_rec * s = sconf - > base_server ;
apr_time_t now = apr_time_sec ( r - > request_time ) ;
while ( s ) {
// enable per client connection search if any server has enabled QS_SrvMaxConnPerIP
qos_srv_config * conf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( conf - > max_conn_per_ip ! = - 1 ) {
max_conn_per_ip = 1 ;
break ;
}
s = s - > next ;
}
if ( sconf - > has_qos_cc | | max_conn_per_ip ) {
const char * option = apr_table_get ( qt , " option " ) ;
const char * refresh = apr_table_get ( qt , " refresh " ) ;
const char * address = apr_table_get ( qt , " address " ) ;
if ( address ) {
int escerr = 0 ;
char * ta = apr_pstrdup ( r - > pool , address ) ;
qos_unescaping ( ta , QOS_DEC_MODE_FLAGS_URL , & escerr ) ;
address = ta ;
}
if ( strcmp ( r - > handler , " qos-viewer " ) = = 0 ) {
ap_rputs ( " <table class= \" btable \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr class= \" row \" ><td> \n " , r ) ;
} else {
ap_rputs ( " <table border= \" 1 \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr><td> \n " , r ) ;
}
if ( strcmp ( r - > handler , " qos-viewer " ) = = 0 ) {
ap_rputs ( " <table border= \" 0 \" cellpadding= \" 2 \" "
" cellspacing= \" 2 \" style= \" width: 100% \" ><tbody> \n " , r ) ;
} else {
ap_rputs ( " <table border= \" 1 \" cellpadding= \" 2 \" "
" cellspacing= \" 2 \" style= \" width: 100% \" ><tbody> \n " , r ) ;
}
ap_rputs ( " <tr class= \" rowe \" > \n " , r ) ;
ap_rputs ( " <td colspan= \" 9 \" >viewer settings</td> \n " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
/* auto refresh */
ap_rputs ( " <tr class= \" rows \" > \n "
" <td colspan= \" 1 \" >auto refresh</td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 8 \" > \n " , r ) ;
ap_rprintf ( r , " <form action= \" %s \" method= \" get \" > \n " ,
ap_escape_html ( r - > pool , r - > parsed_uri . path ) ) ;
if ( option & & strstr ( option , " ip " ) ) {
ap_rprintf ( r , " <input name= \" option \" value= \" ip \" type= \" hidden \" > \n " ) ;
}
if ( address ) {
ap_rprintf ( r , " <input name= \" address \" value= \" %s \" type= \" hidden \" > \n " ,
ap_escape_html ( r - > pool , address ) ) ;
}
if ( refresh ) {
ap_rprintf ( r , " <input name= \" action \" value= \" disable \" type= \" submit \" > \n " ) ;
} else {
ap_rprintf ( r , " <input name= \" action \" value= \" enable \" type= \" submit \" > \n " ) ;
ap_rprintf ( r , " <input name= \" refresh \" value= \" \" type= \" hidden \" > \n " ) ;
}
ap_rputs ( " </form> \n " , r ) ;
ap_rputs ( " </td> \n " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
/* show ip addresses and their connections */
ap_rputs ( " <tr class= \" rows \" > \n "
" <td colspan= \" 1 \" >client ip connections</td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 8 \" > \n " , r ) ;
ap_rprintf ( r , " <form action= \" %s \" method= \" get \" > \n " ,
ap_escape_html ( r - > pool , r - > parsed_uri . path ) ) ;
if ( ! option | | ( option & & ! strstr ( option , " ip " ) ) ) {
ap_rprintf ( r , " <input name= \" option \" value= \" ip \" type= \" hidden \" > \n " ) ;
ap_rprintf ( r , " <input name= \" action \" value= \" enable \" type= \" submit \" > \n " ) ;
} else {
ap_rprintf ( r , " <input name= \" option \" value= \" no \" type= \" hidden \" > \n " ) ;
ap_rprintf ( r , " <input name= \" action \" value= \" disable \" type= \" submit \" > \n " ) ;
}
if ( address ) {
ap_rprintf ( r , " <input name= \" address \" value= \" %s \" type= \" hidden \" > \n " ,
ap_escape_html ( r - > pool , address ) ) ;
}
if ( refresh ) {
ap_rprintf ( r , " <input name= \" refresh \" value= \" \" type= \" hidden \" > \n " ) ;
}
ap_rputs ( " </form> \n " , r ) ;
ap_rputs ( " </td> \n " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
if ( sconf - > has_qos_cc ) {
ap_rputs ( " <tr class= \" rows \" > \n "
" <td colspan= \" 1 \" >search a client ip entry</td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 8 \" > \n " , r ) ;
ap_rprintf ( r , " <form action= \" %s \" method= \" get \" > \n " ,
ap_escape_html ( r - > pool , r - > parsed_uri . path ) ) ;
if ( option & & strstr ( option , " ip " ) ) {
ap_rprintf ( r , " <input name= \" option \" value= \" ip \" type= \" hidden \" > \n " ) ;
}
if ( refresh ) {
ap_rprintf ( r , " <input name= \" refresh \" value= \" \" type= \" hidden \" > \n " ) ;
}
ap_rprintf ( r , " <input name= \" address \" value= \" %s \" type= \" text \" > \n " ,
address ? ap_escape_html ( r - > pool , address ) : " 0.0.0.0 " ) ;
ap_rprintf ( r , " <input name= \" action \" value= \" search \" type= \" submit \" > \n " ) ;
ap_rputs ( " </form> \n " , r ) ;
ap_rputs ( " </td> \n " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
if ( address ) {
apr_uint64_t ip [ 2 ] ;
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
if ( qos_ip_str2long ( address , ip ) ) {
unsigned long html ;
unsigned long cssjs ;
unsigned long img ;
unsigned long other ;
unsigned long notmodified ;
qos_s_entry_t * * clientEntry = NULL ;
qos_s_entry_t searchE ;
int found = 0 ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT20 */
html = u - > qos_cc - > html ;
cssjs = u - > qos_cc - > cssjs ;
img = u - > qos_cc - > img ;
other = u - > qos_cc - > other ;
notmodified = u - > qos_cc - > notmodified ;
searchE . ip6 [ 0 ] = ip [ 0 ] ;
searchE . ip6 [ 1 ] = ip [ 1 ] ;
clientEntry = qos_cc_get0 ( u - > qos_cc , & searchE , 0 ) ;
if ( clientEntry ) {
found = 1 ;
searchE . vip = ( * clientEntry ) - > vip ;
searchE . lowrate = ( * clientEntry ) - > lowrate ;
searchE . lowratestatus = ( * clientEntry ) - > lowratestatus ;
searchE . time = ( * clientEntry ) - > time ;
searchE . block = ( * clientEntry ) - > block ;
searchE . blockTime = ( * clientEntry ) - > blockTime ;
searchE . limit = ( * clientEntry ) - > limit ;
searchE . req_per_sec = ( * clientEntry ) - > req_per_sec ;
searchE . req_per_sec_block_rate = ( * clientEntry ) - > req_per_sec_block_rate ;
searchE . other = ( * clientEntry ) - > other - 1 ;
searchE . html = ( * clientEntry ) - > html - 1 ;
searchE . cssjs = ( * clientEntry ) - > cssjs - 1 ;
searchE . img = ( * clientEntry ) - > img - 1 ;
searchE . notmodified = ( * clientEntry ) - > notmodified - 1 ;
searchE . event_req = ( * clientEntry ) - > event_req ;
}
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT20 */
ap_rputs ( " <tr class= \" rowt \" > \n " , r ) ;
ap_rputs ( " <td colspan= \" 1 \" >IP</td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 2 \" >last request</td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 1 \" > "
" <div title= \" QS_VipHeaderName|QS_VipIPHeaderName \" >vip</div></td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 1 \" > "
" <div title= \" QS_ClientEventBlockCount \" >blocked</div></td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 1 \" > "
" <div title= \" QS_ClientEventLimitCount (QS_Limit) \" >limited</div></td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 2 \" > "
" <div title= \" QS_ClientEventPerSecLimit \" >events/sec</div></td> \n " , r ) ;
ap_rputs ( " <td colspan= \" 1 \" > "
" <div title= \" QS_ClientPrefer
0x01 bad pkg rate
0x02 normal behavior
0x04 bad behavior
0x08 blocked
0x10 limited
0x20 timeout \" >low prio</div></td> \n " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 1 \" >%s</td> " , ap_escape_html ( r - > pool , address ) ) ;
if ( ! found ) {
ap_rputs ( " <td colspan= \" 8 \" ><i>not found</i></td> \n " , r ) ;
} else {
char buf [ 1024 ] ;
struct tm * ptr = localtime ( & searchE . time ) ;
strftime ( buf , sizeof ( buf ) , " %d.%m.%Y %H:%M:%S " , ptr ) ;
ap_rprintf ( r , " <td colspan= \" 2 \" >%s</td> " , buf ) ;
ap_rprintf ( r , " <td colspan= \" 1 \" >%s</td> " , searchE . vip ? " yes " : " no " ) ;
if ( sconf - > qos_cc_blockTime > ( time ( NULL ) - searchE . blockTime ) ) {
ap_rprintf ( r , " <td colspan= \" 1 \" >%d, %ld sec</td> " ,
searchE . block , time ( NULL ) - searchE . blockTime ) ;
} else {
ap_rprintf ( r , " <td colspan= \" 1 \" >no</td> " ) ;
}
if ( u - > qos_cc - > limitTable ) {
int limitTableIndex ;
qos_s_entry_limit_conf_t * eventLimitConf = qos_getQSLimitEvent ( u , QS_LIMIT_DEFAULT , & limitTableIndex ) ;
if ( eventLimitConf ) {
if ( eventLimitConf - > limitTime > ( time ( NULL ) - searchE . limit [ limitTableIndex ] . limitTime ) ) {
ap_rprintf ( r , " <td colspan= \" 1 \" >%hu, %ld sec</td> " ,
searchE . limit [ limitTableIndex ] . limit , time ( NULL ) - searchE . limit [ limitTableIndex ] . limitTime ) ;
} else {
ap_rprintf ( r , " <td colspan= \" 1 \" >no</td> " ) ;
}
} else {
ap_rprintf ( r , " <td colspan= \" 1 \" >off</td> " ) ;
}
} else {
ap_rprintf ( r , " <td colspan= \" 1 \" >off</td> " ) ;
}
ap_rprintf ( r , " <td colspan= \" 1 \" >%ld</td> " , searchE . req_per_sec ) ;
ap_rprintf ( r , " <td colspan= \" 1 \" >%d ms</td> " , searchE . req_per_sec_block_rate ) ;
ap_rprintf ( r , " <td colspan= \" 1 \" >%s (0x%02x)</td> \n " ,
( searchE . lowrate + QOS_LOW_TIMEOUT ) > now ? " yes " : " no " ,
searchE . lowratestatus ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 6 \" > </td> "
" <td> "
" <div title= \" QS_ClientEventRequestLimit \" >events:</div></td> "
" <td style= \" width:9%% \" >%s</td> "
" <td colspan= \" 1 \" ></td> "
" </tr> \n " , ( sconf - > qos_cc_event_req = = - 1 ? " off " : apr_psprintf ( r - > pool , " %d " , searchE . event_req ) ) ) ;
}
if ( sconf - > qos_cc_prefer ) {
ap_rprintf ( r , " <tr class= \" rowt \" > "
" <td colspan= \" 4 \" ></td> "
" <td style= \" width:9%% \" >html</td> "
" <td style= \" width:9%% \" >css/js</td> "
" <td style= \" width:9%% \" >images</td> "
" <td style= \" width:9%% \" >other</td> "
" <td style= \" width:9%% \" >304</td> "
" </tr> \n " ) ;
if ( found ) {
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 4 \" ></td> "
" <td style= \" width:9%% \" >%u</td> "
" <td style= \" width:9%% \" >%u</td> "
" <td style= \" width:9%% \" >%u</td> "
" <td style= \" width:9%% \" >%u</td> "
" <td style= \" width:9%% \" >%u</td> "
" </tr> \n " , searchE . html ,
searchE . cssjs ,
searchE . img ,
searchE . other ,
searchE . notmodified ) ;
}
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 3 \" ></td> "
" <td style= \" width:9%% \" ><div title= \" QS_ClientContentTypes \" >all clients</div></td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" </tr> \n " , html , cssjs , img , other , notmodified ) ;
if ( sconf - > static_on = = 1 ) {
unsigned long shtml = sconf - > static_html ;
unsigned long scssjs = sconf - > static_cssjs ;
unsigned long simg = sconf - > static_img ;
unsigned long sother = sconf - > static_other ;
unsigned long snotmodified = sconf - > static_notmodified ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 3 \" ></td> "
" <td style= \" width:9%% \" ><div title= \" QS_ClientContentTypes \" >configured (global)</div></td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" <td style= \" width:9%% \" >%lu</td> "
" </tr> \n " , shtml , scssjs ,
simg , sother , snotmodified ) ;
}
}
}
}
}
ap_rprintf ( r , " <tr class= \" row \" > "
" <td style= \" width:28%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" </tr> \n " ) ;
ap_rputs ( " </tbody></table> \n " , r ) ;
ap_rputs ( " </tr></td> \n " , r ) ;
ap_rputs ( " </tbody></table> \n " , r ) ;
}
}
/**
* Daws the load / connection bars at the top of the status page
*
* @ param r
* @ param bs
*/
static void qos_bars ( request_rec * r , server_rec * bs ) {
server_rec * s = bs ;
qos_srv_config * bsconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( bsconf - > act & & bsconf - > act - > conn ) {
int connections = - 1 ;
double av [ 1 ] ;
int load = 0 ;
# if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64) && !defined(CYGWIN))
getloadavg ( av , 1 ) ;
load = av [ 0 ] ;
if ( load > 0 ) {
load = 100 * load / ( 10 + load ) ;
}
# endif
ap_rputs ( " <table class= \" btable \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr class= \" row \" ><td> \n " , r ) ;
ap_rputs ( " <table border= \" 0 \" cellpadding= \" 2 \" "
" cellspacing= \" 2 \" style= \" width: 100% \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr class= \" rowe \" > \n " , r ) ;
ap_rputs ( " <td colspan= \" 2 \" >overview</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
if ( bsconf - > log_only ) {
ap_rputs ( " <tr class= \" rowt \" > \n " , r ) ;
ap_rputs ( " <td colspan= \" 2 \" >running in 'log only' mode - rules are NOT enforced</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
}
if ( qos_count_connections ( bsconf ) ) {
connections = qos_server_connections ( bsconf ) ;
}
if ( connections ! = - 1 ) {
# if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
ap_rprintf ( r , " <tr class= \" rowt \" > "
" <td colspan= \" 1 \" >connections: %d</td> "
" <td colspan= \" 1 \" >load: %.2f</td> "
" </tr> \n " , connections , av [ 0 ] ) ;
# else
ap_rprintf ( r , " <tr class= \" rowt \" > "
" <td colspan= \" 1 \" >connections: %d</td> "
" <td colspan= \" 1 \" >load: n/a</td> "
" </tr> \n " , connections ) ;
# endif
} else {
# if (!defined(WIN32) && !defined(__MINGW32__) && !defined(_WIN64))
ap_rprintf ( r , " <tr class= \" rowt \" > "
" <td colspan= \" 1 \" >connections: n/a</td> "
" <td colspan= \" 1 \" >load: %.2f</td> "
" </tr> \n " , av [ 0 ] ) ;
# else
ap_rprintf ( r , " <tr class= \" rowt \" > "
" <td colspan= \" 1 \" >connections: n/a</td> "
" <td colspan= \" 1 \" >load: n/a</td> "
" </tr> \n " ) ;
# endif
}
ap_rprintf ( r , " <tr class= \" rows \" > " ) ;
ap_rprintf ( r , " <td> " ) ;
if ( connections ! = - 1 ) {
int percentage = 100 * connections / bsconf - > max_clients ;
if ( percentage > 100 ) percentage = 100 ;
ap_rprintf ( r , " <div class= \" prog-border \" > "
" <div class= \" %s \" style= \" width: %d%%; \" ></div></div> " ,
percentage < 90 ? " prog-bar " : " prog-bar-limit " ,
percentage ) ;
ap_rprintf ( r , " </td> " ) ;
} else {
ap_rprintf ( r , " " ) ;
}
ap_rprintf ( r , " <td> " ) ;
ap_rprintf ( r , " <div class= \" prog-border \" > "
" <div class= \" prog-bar \" style= \" width: %d%%; \" ></div></div> " ,
load ) ;
ap_rprintf ( r , " </td> " ) ;
ap_rprintf ( r , " </tr> \n " ) ;
ap_rputs ( " </tbody></table> \n " , r ) ;
ap_rputs ( " </tr></td> \n " , r ) ;
ap_rputs ( " </tbody></table> \n " , r ) ;
}
}
/**
* ( Extendet - ) Status viewer , used by internal and mod_status handler .
*/
static int qos_ext_status_hook ( request_rec * r , int flags ) {
qos_srv_config * sconf = ( qos_srv_config * ) ap_get_module_config ( r - > server - > module_config ,
& qos_module ) ;
server_rec * s = sconf - > base_server ;
int i = 0 ;
apr_time_t now = apr_time_sec ( r - > request_time ) ;
qos_srv_config * bsconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config ,
& qos_module ) ;
apr_table_t * qt = qos_get_query_table ( r ) ;
const char * option = apr_table_get ( qt , " option " ) ;
if ( sconf - > disable_handler = = 1 ) {
ap_log_rerror ( APLOG_MARK , APLOG_NOERRNO | APLOG_ERR , 0 , r ,
QOS_LOG_PFX ( 072 ) " handler has been disabled for this host, id=%s " ,
qos_unique_id ( r , " 072 " ) ) ;
return OK ;
}
if ( flags & AP_STATUS_SHORT ) {
qos_ext_status_short ( r , qt ) ;
return OK ;
}
if ( qt & & ( apr_table_get ( qt , " auto " ) ! = NULL ) ) {
qos_ext_status_short ( r , qt ) ;
return OK ;
}
if ( strcmp ( r - > handler , " qos-viewer " ) ! = 0 ) {
ap_rputs ( " <hr> \n " , r ) ;
ap_rputs ( " <table style= \" width:400px \" cellspacing=0 cellpadding=0> \n " , r ) ;
ap_rputs ( " <tr><td bgcolor= \" #000000 \" > \n " , r ) ;
ap_rputs ( " <b><font color= \" #ffffff \" face= \" Arial,Helvetica \" > " , r ) ;
ap_rprintf ( r , " mod_qos %s " , ap_escape_html ( r - > pool , qos_revision ( r - > pool ) ) ) ;
ap_rputs ( " </font></b> \r " , r ) ;
ap_rputs ( " </td></tr> \n " , r ) ;
ap_rputs ( " </table> \n " , r ) ;
if ( sconf - > log_only ) {
ap_rputs ( " <p>running in 'log only' mode - rules are NOT enforced</p> \n " , r ) ;
}
}
# ifdef QS_INTERNAL_TEST
{
apr_uint64_t remoteip [ 2 ] ;
qs_conn_ctx * cconf = qos_get_cconf ( r - > connection ) ;
qos_ip_str2long ( QS_CONN_REMOTEIP ( r - > connection ) , remoteip ) ;
if ( cconf ) {
remoteip [ 0 ] = cconf - > ip6 [ 0 ] ;
remoteip [ 1 ] = cconf - > ip6 [ 1 ] ;
}
ap_rputs ( " <p>TEST BINARY, NOT FOR PRODUCTIVE USE<br> \n " , r ) ;
ap_rprintf ( r , " client ip=%s</p> \n " , qos_ip_long2str ( r - > pool , remoteip ) ) ;
}
# endif
if ( strcmp ( r - > handler , " qos-viewer " ) = = 0 ) {
qos_bars ( r , s ) ;
}
qos_show_ip ( r , bsconf , qt ) ;
if ( strcmp ( r - > handler , " qos-viewer " ) = = 0 ) {
ap_rputs ( " <table class= \" btable \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr class= \" row \" ><td> \n " , r ) ;
} else {
ap_rputs ( " <table border= \" 1 \" ><tbody> \n " , r ) ;
ap_rputs ( " <tr><td> \n " , r ) ;
}
while ( s ) {
qs_acentry_t * actEntry ;
if ( strcmp ( r - > handler , " qos-viewer " ) = = 0 ) {
ap_rputs ( " <table border= \" 0 \" cellpadding= \" 2 \" "
" cellspacing= \" 2 \" style= \" width: 100% \" ><tbody> \n " , r ) ;
} else {
ap_rputs ( " <table border= \" 1 \" cellpadding= \" 2 \" "
" cellspacing= \" 2 \" style= \" width: 100% \" ><tbody> \n " , r ) ;
}
ap_rputs ( " <tr class= \" rowe \" > \n " , r ) ;
ap_rprintf ( r , " <td colspan= \" 9 \" >%s:%d (%s)</td> \n " ,
s - > server_hostname = = NULL ? " - " : ap_escape_html ( r - > pool , s - > server_hostname ) ,
s - > addrs - > host_port ,
s - > is_virtual ? " virtual " : " base " ) ;
ap_rputs ( " </tr> \n " , r ) ;
sconf = ( qos_srv_config * ) ap_get_module_config ( s - > module_config , & qos_module ) ;
if ( ( sconf = = bsconf ) & & s - > is_virtual ) {
ap_rputs ( " <tr class= \" rows \" > \n "
" <td colspan= \" 9 \" ><i>uses base server settings and counters</i></td> \n </tr> \n " , r ) ;
} else {
if ( ! s - > is_virtual & & sconf - > has_qos_cc ) {
qos_user_t * u = qos_get_user_conf ( sconf - > act - > ppool ) ;
int num = 0 ;
int max = 0 ;
int hc = - 1 ;
apr_global_mutex_lock ( u - > qos_cc - > lock ) ; /* @CRT16 */
hc = u - > qos_cc - > connections ;
num = u - > qos_cc - > num ;
max = u - > qos_cc - > max ;
apr_global_mutex_unlock ( u - > qos_cc - > lock ) ; /* @CRT16 */
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 6 \" >client control</td> "
" <td >max</td> "
" <td >limit </td> "
" <td >current </td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rprintf ( r , " <tr class= \" rows \" > " ) ;
ap_rprintf ( r , " <td colspan= \" 6 \" ><div title= \" QS_ClientEntries \" >clients in memory</div></td> " ) ;
ap_rprintf ( r , " <td >%d</td> " , max ) ;
ap_rprintf ( r , " <td >-</td> " ) ;
ap_rprintf ( r , " <td >%d</td> " , num ) ;
ap_rputs ( " </tr> \n " , r ) ;
if ( sconf - > qos_cc_prefer ) {
ap_rprintf ( r , " <tr class= \" rows \" > " ) ;
ap_rprintf ( r , " <td colspan= \" 6 \" ><div title= \" QS_ClientPrefer \" >connections</div></td> " ) ;
ap_rprintf ( r , " <td >%d</td> " , sconf - > qos_cc_prefer ) ;
ap_rprintf ( r , " <td >%d</td> " , sconf - > qos_cc_prefer_limit ) ;
ap_rprintf ( r , " <td >%d</td> " , hc ) ;
ap_rputs ( " </tr> \n " , r ) ;
}
/*
if ( sconf - > qos_cc_block ) {
ap_rprintf ( r , " <tr class= \" rows \" > " ) ;
ap_rprintf ( r , " <td colspan= \" 6 \" >block event</td> " ) ;
ap_rprintf ( r , " <td >%d</td> " , sconf - > qos_cc_block ) ;
ap_rprintf ( r , " <td > </td> " ) ;
ap_rprintf ( r , " <td >%d</td> " , blocked ) ;
ap_rputs ( " </tr> \n " , r ) ;
}
*/
}
/* request level */
actEntry = sconf - > act - > entry ;
if ( actEntry ) {
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 1 \" >rule</td> "
" <td colspan= \" 2 \" > "
" <div title= \" QS_LocRequestLimitMatch|QS_LocRequestLimit "
" |QS_CondLocRequestLimitMatch|QS_EventRequestLimit \" > "
" concurrent requests</div></td> "
" <td colspan= \" 3 \" > "
" <div title= \" QS_LocRequestPerSecLimitMatch| "
" QS_LocRequestPerSecLimit|QS_EventPerSecLimit \" > "
" requests/second</div></td> "
" <td colspan= \" 3 \" > "
" <div title= \" QS_LocKBytesPerSecLimitMatch|QS_LocKBytesPerSecLimit \" > "
" kbytes/second</div></td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rputs ( " <tr class= \" rowt \" > "
" <td > </td> "
" <td >limit</td> "
" <td >current</td> "
" <td >wait rate</td> "
" <td >limit</td> "
" <td >current</td> "
" <td >wait rate</td> "
" <td >limit</td> "
" <td >current</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
}
while ( actEntry ) {
char * red = " style= \" background-color: rgb(240,153,155); \" " ;
ap_rputs ( " <tr class= \" rows \" > " , r ) ;
ap_rprintf ( r , " <!--%d--><td>%s%s</a></td> " , i ,
ap_escape_html ( r - > pool , qos_crline ( r , actEntry - > url ) ) ,
actEntry - > condition = = NULL ? " " : " <small>(conditional)</small> " ) ;
if ( ( actEntry - > limit = = 0 ) | | ( actEntry - > limit = = - 1 ) ) {
ap_rprintf ( r , " <td>-</td> " ) ;
ap_rprintf ( r , " <td>-</td> " ) ;
} else {
ap_rprintf ( r , " <td>%d</td> " , actEntry - > limit ) ;
ap_rprintf ( r , " <td %s>%d</td> " ,
( ( actEntry - > counter * 100 ) / actEntry - > limit ) > 90 ? red : " " ,
actEntry - > counter ) ;
}
if ( actEntry - > req_per_sec_limit = = 0 ) {
ap_rprintf ( r , " <td>-</td> " ) ;
ap_rprintf ( r , " <td>-</td> " ) ;
ap_rprintf ( r , " <td>-</td> " ) ;
} else {
ap_rprintf ( r , " <td %s>%d ms</td> " ,
actEntry - > req_per_sec_block_rate ? red : " " ,
actEntry - > req_per_sec_block_rate ) ;
ap_rprintf ( r , " <td>%ld</td> " , actEntry - > req_per_sec_limit ) ;
ap_rprintf ( r , " <td %s>%ld</td> " ,
( ( actEntry - > req_per_sec * 100 ) / actEntry - > req_per_sec_limit ) > 90 ? red : " " ,
now > ( actEntry - > interval + ( QS_BW_SAMPLING_RATE * 3 ) ) ? 0 : actEntry - > req_per_sec ) ;
}
if ( actEntry - > kbytes_per_sec_limit = = 0 ) {
ap_rprintf ( r , " <td>-</td> " ) ;
ap_rprintf ( r , " <td>-</td> " ) ;
ap_rprintf ( r , " <td>-</td> " ) ;
} else {
int hasActualData = now > ( apr_time_sec ( actEntry - > kbytes_interval_us ) + QS_BW_SAMPLING_RATE ) ? 0 : 1 ;
ap_rprintf ( r , " <td %s>% " APR_OFF_T_FMT " ms</td> " ,
hasActualData & & ( actEntry - > kbytes_per_sec_block_rate > = 1000 ) ? red : " " ,
actEntry - > kbytes_per_sec_block_rate / 1000 ) ;
ap_rprintf ( r , " <td>% " APR_OFF_T_FMT " </td> " , actEntry - > kbytes_per_sec_limit ) ;
ap_rprintf ( r , " <td %s>% " APR_OFF_T_FMT " </td> " ,
hasActualData & & ( ( ( actEntry - > kbytes_per_sec * 100 ) / actEntry - > kbytes_per_sec_limit ) > 90 ) ? red : " " ,
hasActualData ? actEntry - > kbytes_per_sec : 0 ) ;
}
ap_rputs ( " </tr> \n " , r ) ;
actEntry = actEntry - > next ;
}
/* event limit */
if ( sconf - > event_limit_a - > nelts > 0 ) {
char * red = " style= \" background-color: rgb(240,153,155); \" " ;
int ie = 0 ;
qos_event_limit_entry_t * event_limit = sconf - > act - > event_entry ;
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 5 \" ><div title= \" QS_EventLimitCount \" >events</div></td> "
" <td colspan= \" 1 \" >limit</td> "
" <td colspan= \" 1 \" >seconds</td> "
" <td colspan= \" 2 \" >current</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
for ( ie = 0 ; ie < sconf - > event_limit_a - > nelts ; ie + + ) {
int edelta = event_limit - > limitTime + event_limit - > seconds - now ;
int elimit = event_limit - > limit ;
if ( event_limit - > limitTime + event_limit - > seconds < = now ) {
elimit = 0 ;
edelta = 0 ;
}
if ( event_limit - > action = = QS_EVENT_ACTION_DENY ) {
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 5 \" >%s%s</td> "
" <td>%d</td><td>%ds</td><td %s>%d</td><td>%ds</td> "
" </tr> \n " ,
event_limit - > env_var ,
event_limit - > condStr = = NULL ? " " : " <small>(conditional)</small> " ,
event_limit - > max ,
event_limit - > seconds ,
elimit > = event_limit - > max ? red : " " ,
elimit ,
edelta ) ;
}
event_limit + + ;
}
}
/* connection level */
if ( sconf - > has_conn_counter = = 1 | | ! s - > is_virtual ) {
char * red = " style= \" background-color: rgb(240,153,155); \" " ;
int c = qos_count_free_ip ( sconf ) ;
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 9 \" >connections</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <!--%d--><td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConnPerIP \" >free ip entries</div></td> "
" <td colspan= \" 3 \" >%d</td></tr> \n " , i , c ) ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <!--%d--><td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConn|QS_SrvMaxConnClose \" >current connections</div></td> "
" <td %s colspan= \" 3 \" >%d</td></tr> \n " , i ,
( ( ( sconf - > max_conn_close ! = - 1 ) & &
( sconf - > act - > conn - > connections > = sconf - > max_conn_close ) ) | |
( ( sconf - > max_conn ! = - 1 ) & &
( sconf - > act - > conn - > connections > = sconf - > max_conn ) ) ) ? red : " " ,
sconf - > act - > conn - > connections ) ;
if ( ! s - > is_virtual ) {
ap_rprintf ( r , " <tr class= \" rows \" > "
" <!--base--><td colspan= \" 6 \" > "
" <div>total connections</div></td> "
" <td colspan= \" 3 \" >%d</td></tr> \n " ,
qos_server_connections ( sconf ) ) ;
}
if ( option & & strstr ( option , " ip " ) ) {
apr_table_t * entries = apr_table_make ( r - > pool , 100 ) ;
int j ;
apr_table_entry_t * entry ;
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConnPerIP \" >client ip connections</div></td> "
" <td colspan= \" 3 \" >current </td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
qos_collect_ip ( r , sconf , entries , sconf - > max_conn_per_ip , 1 ) ;
entry = ( apr_table_entry_t * ) apr_table_elts ( entries ) - > elts ;
for ( j = 0 ; j < apr_table_elts ( entries ) - > nelts ; j + + ) {
ap_rputs ( " <tr class= \" rows \" > " , r ) ;
ap_rputs ( " <td colspan= \" 6 \" > " , r ) ;
ap_rprintf ( r , " %s</td></tr> \n " , entry [ j ] . key ) ;
}
}
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 9 \" >connection settings</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConn \" >max connections</div></td> " ) ;
if ( sconf - > max_conn = = - 1 ) {
ap_rprintf ( r , " <td colspan= \" 3 \" >-</td></tr> \n " ) ;
} else {
ap_rprintf ( r , " <td colspan= \" 3 \" >%d</td></tr> \n " , sconf - > max_conn ) ;
}
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConnClose \" >max connections with keep-alive</div></td> " ) ;
if ( sconf - > max_conn_close = = - 1 ) {
ap_rprintf ( r , " <td colspan= \" 3 \" >-</td></tr> \n " ) ;
} else {
ap_rprintf ( r , " <td colspan= \" 3 \" >%d</td></tr> \n " , sconf - > max_conn_close ) ;
}
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 6 \" > "
" <div title= \" QS_SrvMaxConnPerIP \" >max connections per client ip</div></td> " ) ;
if ( sconf - > max_conn_per_ip = = - 1 ) {
ap_rprintf ( r , " <td colspan= \" 3 \" >-</td></tr> \n " ) ;
} else {
ap_rprintf ( r , " <td colspan= \" 3 \" >%d</td></tr> \n " , sconf - > max_conn_per_ip ) ;
}
ap_rprintf ( r , " <tr class= \" rows \" > "
" <td colspan= \" 6 \" > "
" <div title= \" QS_SrvMinDataRate|QS_SrvRequestRate \" > "
" min. data rate (bytes/sec) (min/max/current)</div></td> " ) ;
if ( sconf - > req_rate = = - 1 ) {
ap_rprintf ( r , " <td colspan= \" 3 \" >-</td></tr> \n " ) ;
} else {
int connections ;
int rt = qos_req_rate_calc ( sconf , & connections ) ;
ap_rprintf ( r , " <td colspan= \" 3 \" >%d/%d/%d</td></tr> \n " ,
sconf - > req_rate ,
sconf - > min_rate_max = = - 1 ? sconf - > req_rate : sconf - > min_rate_max ,
rt ) ;
}
} else {
ap_rputs ( " <tr class= \" rowt \" > "
" <td colspan= \" 9 \" >connections</td> " , r ) ;
ap_rputs ( " </tr> \n " , r ) ;
ap_rputs ( " <tr class= \" rows \" > \n "
" <td colspan= \" 9 \" ><i>uses base server settings and counters</i></td> \n </tr> \n " , r ) ;
}
}
ap_rprintf ( r , " <tr class= \" row \" > "
" <td style= \" width:28%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" <td style= \" width:9%% \" ></td> "
" </tr> " ) ;
i + + ;
s = s - > next ;
ap_rputs ( " </tbody></table> \n " , r ) ;
}
ap_rputs ( " </td></tr> \n " , r ) ;
ap_rputs ( " </tbody></table> \n " , r ) ;
return OK ;
}
/**
* Disables request rate enforcements for all child processes ( at start / fork ) if
* init has failed .
*
* @ param bs Base server_rec to iterate through all client configurations
* @ param msg Error message to log ( reason what has failed @ init ) .
*/
static void qos_disable_req_rate ( server_rec * bs , const char * msg ) {
server_rec * s = bs - > next ;
qos_srv_config * sconf = ( qos_srv_config * ) ap_get_module_config ( bs - > module_config , & qos_module ) ;
ap_log_error ( APLOG_MARK , APLOG_CRIT , 0 , bs ,
QOS_LOG_PFX ( 00 8 ) " 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 ( 00 9 ) " 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 ( 00 9 ) " 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 ( 04 9 ) " 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 ;
}
2025-03-24 14:50:13 +01:00
apr_table_setn ( c - > notes , " short-lingering-close " , " 1 " ) ;
2025-03-24 13:01:01 +01:00
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 ( 06 9 ) " 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 ( 06 9 ) " 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 ( 03 8 ) " 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 ( 03 8 ) 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 ( 00 9 ) " 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 ( 00 9 ) " 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 ( 00 9 ) " 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 ( 00 9 ) " 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 ( 00 9 ) " 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 ( 00 9 ) " 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 , " \n QS_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 , " \n QS_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 , " \n mod_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 : 1 px solid ; padding : 0 px ; \ n \
margin : 6 px ; width : 920 px ; \ n \
font - weight : normal ; \ n \
border - collapse : collapse ; \ n \
} \ n \
. rowts { \ n \
background - color : rgb ( 150 , 165 , 158 ) ; \ n \
vertical - align : top ; \ n \
border : 1 px solid ; \ n \
border - color : black ; \ n \
font - weight : normal ; \ n \
padding : 0 px ; \ n \
margin : 0 px ; \ n \
} \ n \
. rowt { \ n \
background - color : rgb ( 210 , 220 , 215 ) ; \ n \
vertical - align : top ; \ n \
border : 1 px solid ; \ n \
border - color : black ; \ n \
font - weight : normal ; \ n \
padding : 0 px ; \ n \
margin : 0 px ; \ n \
} \ n \
. rows { \ n \
background - color : rgb ( 228 , 235 , 230 ) ; \ n \
vertical - align : top ; \ n \
border : 1 px solid ; \ n \
border - color : black ; \ n \
font - weight : normal ; \ n \
padding : 0 px ; \ n \
margin : 0 px ; \ n \
} \ n \
. row { \ n \
background - color : white ; \ n \
vertical - align : top ; \ n \
border : 1 px solid ; \ n \
border - color : black ; \ n \
font - weight : normal ; \ n \
padding : 0 px ; \ n \
margin : 0 px ; \ n \
} \ n \
. rowe { \ n \
background - color : rgb ( 186 , 200 , 190 ) ; \ n \
vertical - align : top ; \ n \
border : 1 px solid ; \ n \
border - color : black ; \ n \
font - weight : normal ; \ n \
padding : 0 px ; \ n \
margin : 0 px ; \ n \
} \ n \
. small { \ n \
font - size : 0.75 em ; \ n \
font - family : courier ; \ n \
} \ n \
. prog - border { \ n \
height : 10 px ; \ n \
width : 150 px ; \ n \
background : # eee ; \ n \
border : 1 px solid # 000 ; \ n \
padding : 2 px ; \ n \
font - family : arial , helvetica , verdana , sans - serif ; font - size : 10 px ; color : # 000 ; \ n \
} \ n \
. prog - bar { \ n \
height : 10 px ; \ n \
padding : 0 ; \ n \
background : # 339900 ; \ n \
font - family : arial , helvetica , verdana , sans - serif ; font - size : 10 px ; color : # 000 ; \ n \
} \ n \
. prog - bar - limit { \ n \
height : 10 px ; \ n \
padding : 0 ; \ n \
background : # 993300 ; \ n \
font - family : arial , helvetica , verdana , sans - serif ; font - size : 10 px ; 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 */
} ;