lighttpd 1.4.x
https://www.lighttpd.net/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
5.1 KiB
179 lines
5.1 KiB
#include "first.h" |
|
|
|
#include "base.h" |
|
#include "log.h" |
|
#include "buffer.h" |
|
#include "http_header.h" |
|
#include "sock_addr.h" |
|
|
|
#include "plugin.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
/** |
|
* mod_evasive |
|
* |
|
* we indent to implement all features the mod_evasive from apache has |
|
* |
|
* - limit of connections per IP |
|
* - provide a list of block-listed ip/networks (no access) |
|
* - provide a white-list of ips/network which is not affected by the limit |
|
* (hmm, conditionals might be enough) |
|
* - provide a bandwidth limiter per IP |
|
* |
|
* started by: |
|
* - w1zzard@techpowerup.com |
|
*/ |
|
|
|
typedef struct { |
|
unsigned short max_conns; |
|
unsigned short silent; |
|
const buffer *location; |
|
} plugin_config; |
|
|
|
typedef struct { |
|
PLUGIN_DATA; |
|
plugin_config defaults; |
|
plugin_config conf; |
|
} plugin_data; |
|
|
|
INIT_FUNC(mod_evasive_init) { |
|
return calloc(1, sizeof(plugin_data)); |
|
} |
|
|
|
static void mod_evasive_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { |
|
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ |
|
case 0: /* evasive.max-conns-per-ip */ |
|
pconf->max_conns = cpv->v.shrt; |
|
break; |
|
case 1: /* evasive.silent */ |
|
pconf->silent = (0 != cpv->v.u); |
|
break; |
|
case 2: /* evasive.location */ |
|
pconf->location = cpv->v.b; |
|
break; |
|
default:/* should not happen */ |
|
return; |
|
} |
|
} |
|
|
|
static void mod_evasive_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { |
|
do { |
|
mod_evasive_merge_config_cpv(pconf, cpv); |
|
} while ((++cpv)->k_id != -1); |
|
} |
|
|
|
static void mod_evasive_patch_config(request_st * const r, plugin_data * const p) { |
|
p->conf = p->defaults; /* copy small struct instead of memcpy() */ |
|
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ |
|
for (int i = 1, used = p->nconfig; i < used; ++i) { |
|
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) |
|
mod_evasive_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); |
|
} |
|
} |
|
|
|
SETDEFAULTS_FUNC(mod_evasive_set_defaults) { |
|
static const config_plugin_keys_t cpk[] = { |
|
{ CONST_STR_LEN("evasive.max-conns-per-ip"), |
|
T_CONFIG_SHORT, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("evasive.silent"), |
|
T_CONFIG_BOOL, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ CONST_STR_LEN("evasive.location"), |
|
T_CONFIG_STRING, |
|
T_CONFIG_SCOPE_CONNECTION } |
|
,{ NULL, 0, |
|
T_CONFIG_UNSET, |
|
T_CONFIG_SCOPE_UNSET } |
|
}; |
|
|
|
plugin_data * const p = p_d; |
|
if (!config_plugin_values_init(srv, p, cpk, "mod_evasive")) |
|
return HANDLER_ERROR; |
|
|
|
/* process and validate config directives |
|
* (init i to 0 if global context; to 1 to skip empty global context) */ |
|
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { |
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; |
|
for (; -1 != cpv->k_id; ++cpv) { |
|
switch (cpv->k_id) { |
|
case 0: /* evasive.max-conns-per-ip */ |
|
case 1: /* evasive.silent */ |
|
break; |
|
case 2: /* evasive.location */ |
|
if (buffer_is_blank(cpv->v.b)) |
|
cpv->v.b = NULL; |
|
break; |
|
default:/* should not happen */ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
/* initialize p->defaults from global config context */ |
|
if (p->nconfig > 0 && p->cvlist->v.u2[1]) { |
|
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; |
|
if (-1 != cpv->k_id) |
|
mod_evasive_merge_config(&p->defaults, cpv); |
|
} |
|
|
|
return HANDLER_GO_ON; |
|
} |
|
|
|
URIHANDLER_FUNC(mod_evasive_uri_handler) { |
|
plugin_data *p = p_d; |
|
|
|
mod_evasive_patch_config(r, p); |
|
|
|
/* no limit set, nothing to block */ |
|
if (p->conf.max_conns == 0) return HANDLER_GO_ON; |
|
|
|
sock_addr * const dst_addr = &r->con->dst_addr; |
|
uint32_t conns_by_ip = 0; |
|
for (const connection *c = r->con->srv->conns; c; c = c->next) { |
|
/* check if other connections are already actively serving data for the same IP |
|
* we can only ban connections which are already behind the 'read request' state |
|
*/ |
|
if (c->request.state <= CON_STATE_REQUEST_END) continue; |
|
|
|
if (!sock_addr_is_addr_eq(&c->dst_addr, dst_addr)) continue; |
|
conns_by_ip++; |
|
|
|
if (conns_by_ip > p->conf.max_conns) { |
|
if (!p->conf.silent) { |
|
log_error(r->conf.errh, __FILE__, __LINE__, |
|
"%s turned away. Too many connections.", |
|
r->con->dst_addr_buf.ptr); |
|
} |
|
|
|
if (p->conf.location) { |
|
http_header_response_set(r, HTTP_HEADER_LOCATION, |
|
CONST_STR_LEN("Location"), |
|
BUF_PTR_LEN(p->conf.location)); |
|
r->http_status = 302; |
|
r->resp_body_finished = 1; |
|
} else { |
|
r->http_status = 403; |
|
} |
|
r->handler_module = NULL; |
|
return HANDLER_FINISHED; |
|
} |
|
} |
|
|
|
return HANDLER_GO_ON; |
|
} |
|
|
|
|
|
int mod_evasive_plugin_init(plugin *p); |
|
int mod_evasive_plugin_init(plugin *p) { |
|
p->version = LIGHTTPD_VERSION_ID; |
|
p->name = "evasive"; |
|
|
|
p->init = mod_evasive_init; |
|
p->set_defaults = mod_evasive_set_defaults; |
|
p->handle_uri_clean = mod_evasive_uri_handler; |
|
|
|
return 0; |
|
}
|
|
|