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