1 #include "first.h"
2
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "http_header.h"
7 #include "sock_addr.h"
8
9 #include "plugin.h"
10
11 #include <stdlib.h>
12 #include <string.h>
13
14 /**
15 * mod_evasive
16 *
17 * A combination of lighttpd modules provides similar features
18 * to those in (old) Apache mod_evasive
19 *
20 * - limit of connections per IP
21 * ==> mod_evasive
22 * - provide a list of block-listed ip/networks (no access)
23 * ==> block at firewall
24 * ==> block using lighttpd.conf conditionals and mod_access
25 * ==> block using mod_magnet and an external (updatable) constant database
26 * https://wiki.lighttpd.net/AbsoLUAtion#Fight-DDoS
27 * - provide a white-list of ips/network which is not affected by the limit
28 * ==> allow using lighttpd.conf conditionals
29 * and configure evasive.max-conns-per-ip = 0 for whitelist
30 * - provide a bandwidth limiter per IP
31 * ==> set using lighttpd.conf conditionals
32 * and configure connection.kbytes-per-second
33 * - enforce additional policy using mod_magnet and libmodsecurity
34 * ==> https://wiki.lighttpd.net/AbsoLUAtion#Mod_Security
35 *
36 * started by:
37 * - w1zzard@techpowerup.com
38 */
39
40 typedef struct {
41 unsigned short max_conns;
42 unsigned short silent;
43 const buffer *location;
44 } plugin_config;
45
46 typedef struct {
47 PLUGIN_DATA;
48 plugin_config defaults;
49 plugin_config conf;
50 } plugin_data;
51
INIT_FUNC(mod_evasive_init)52 INIT_FUNC(mod_evasive_init) {
53 return calloc(1, sizeof(plugin_data));
54 }
55
mod_evasive_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)56 static void mod_evasive_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
57 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
58 case 0: /* evasive.max-conns-per-ip */
59 pconf->max_conns = cpv->v.shrt;
60 break;
61 case 1: /* evasive.silent */
62 pconf->silent = (0 != cpv->v.u);
63 break;
64 case 2: /* evasive.location */
65 pconf->location = cpv->v.b;
66 break;
67 default:/* should not happen */
68 return;
69 }
70 }
71
mod_evasive_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)72 static void mod_evasive_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
73 do {
74 mod_evasive_merge_config_cpv(pconf, cpv);
75 } while ((++cpv)->k_id != -1);
76 }
77
mod_evasive_patch_config(request_st * const r,plugin_data * const p)78 static void mod_evasive_patch_config(request_st * const r, plugin_data * const p) {
79 p->conf = p->defaults; /* copy small struct instead of memcpy() */
80 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
81 for (int i = 1, used = p->nconfig; i < used; ++i) {
82 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
83 mod_evasive_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]);
84 }
85 }
86
SETDEFAULTS_FUNC(mod_evasive_set_defaults)87 SETDEFAULTS_FUNC(mod_evasive_set_defaults) {
88 static const config_plugin_keys_t cpk[] = {
89 { CONST_STR_LEN("evasive.max-conns-per-ip"),
90 T_CONFIG_SHORT,
91 T_CONFIG_SCOPE_CONNECTION }
92 ,{ CONST_STR_LEN("evasive.silent"),
93 T_CONFIG_BOOL,
94 T_CONFIG_SCOPE_CONNECTION }
95 ,{ CONST_STR_LEN("evasive.location"),
96 T_CONFIG_STRING,
97 T_CONFIG_SCOPE_CONNECTION }
98 ,{ NULL, 0,
99 T_CONFIG_UNSET,
100 T_CONFIG_SCOPE_UNSET }
101 };
102
103 plugin_data * const p = p_d;
104 if (!config_plugin_values_init(srv, p, cpk, "mod_evasive"))
105 return HANDLER_ERROR;
106
107 /* process and validate config directives
108 * (init i to 0 if global context; to 1 to skip empty global context) */
109 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
110 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
111 for (; -1 != cpv->k_id; ++cpv) {
112 switch (cpv->k_id) {
113 case 0: /* evasive.max-conns-per-ip */
114 case 1: /* evasive.silent */
115 break;
116 case 2: /* evasive.location */
117 if (buffer_is_blank(cpv->v.b))
118 cpv->v.b = NULL;
119 break;
120 default:/* should not happen */
121 break;
122 }
123 }
124 }
125
126 /* initialize p->defaults from global config context */
127 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
128 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
129 if (-1 != cpv->k_id)
130 mod_evasive_merge_config(&p->defaults, cpv);
131 }
132
133 return HANDLER_GO_ON;
134 }
135
136 __attribute_cold__
137 __attribute_noinline__
138 static handler_t
mod_evasive_reached_per_ip_limit(request_st * const r,const plugin_data * const p)139 mod_evasive_reached_per_ip_limit (request_st * const r, const plugin_data * const p)
140 {
141 if (!p->conf.silent) {
142 log_error(r->conf.errh, __FILE__, __LINE__,
143 "%s turned away. Too many connections.",
144 r->con->dst_addr_buf.ptr);
145 }
146
147 if (p->conf.location) {
148 http_header_response_set(r, HTTP_HEADER_LOCATION,
149 CONST_STR_LEN("Location"),
150 BUF_PTR_LEN(p->conf.location));
151 r->http_status = 302;
152 r->resp_body_finished = 1;
153 } else {
154 r->http_status = 403;
155 }
156 r->handler_module = NULL;
157 return HANDLER_FINISHED;
158 }
159
160 static handler_t
mod_evasive_check_per_ip_limit(request_st * const r,const plugin_data * const p,const connection * c)161 mod_evasive_check_per_ip_limit (request_st * const r, const plugin_data * const p, const connection *c)
162 {
163 const sock_addr * const dst_addr = &r->con->dst_addr;
164 for (uint_fast32_t conns_by_ip = 0; c; c = c->next) {
165 /* count connections already actively serving data for the same IP
166 * (only count connections already behind the 'read request' state) */
167 if (c->request.state > CON_STATE_REQUEST_END
168 && sock_addr_is_addr_eq(&c->dst_addr, dst_addr)
169 && ++conns_by_ip > p->conf.max_conns)
170 return mod_evasive_reached_per_ip_limit(r, p);/* HANDLER_FINISHED */
171 }
172 return HANDLER_GO_ON;
173 }
174
URIHANDLER_FUNC(mod_evasive_uri_handler)175 URIHANDLER_FUNC(mod_evasive_uri_handler) {
176 plugin_data * const p = p_d;
177 mod_evasive_patch_config(r, p);
178 return (p->conf.max_conns == 0) /* no limit set, nothing to block */
179 ? HANDLER_GO_ON
180 : mod_evasive_check_per_ip_limit(r, p, r->con->srv->conns);
181 }
182
183
184 int mod_evasive_plugin_init(plugin *p);
mod_evasive_plugin_init(plugin * p)185 int mod_evasive_plugin_init(plugin *p) {
186 p->version = LIGHTTPD_VERSION_ID;
187 p->name = "evasive";
188
189 p->init = mod_evasive_init;
190 p->set_defaults = mod_evasive_set_defaults;
191 p->handle_uri_clean = mod_evasive_uri_handler;
192
193 return 0;
194 }
195