| 1 | #include <ctype.h> |
|---|
| 2 | #include <stdlib.h> |
|---|
| 3 | #include <string.h> |
|---|
| 4 | |
|---|
| 5 | #include "base.h" |
|---|
| 6 | #include "log.h" |
|---|
| 7 | #include "buffer.h" |
|---|
| 8 | |
|---|
| 9 | #include "plugin.h" |
|---|
| 10 | |
|---|
| 11 | #include "inet_ntop_cache.h" |
|---|
| 12 | |
|---|
| 13 | /** |
|---|
| 14 | * mod_evasive |
|---|
| 15 | * |
|---|
| 16 | * we indent to implement all features the mod_evasive from apache has |
|---|
| 17 | * |
|---|
| 18 | * - limit of connections per IP |
|---|
| 19 | * - provide a list of block-listed ip/networks (no access) |
|---|
| 20 | * - provide a white-list of ips/network which is not affected by the limit |
|---|
| 21 | * (hmm, conditionals might be enough) |
|---|
| 22 | * - provide a bandwidth limiter per IP |
|---|
| 23 | * |
|---|
| 24 | * started by: |
|---|
| 25 | * - w1zzard@techpowerup.com |
|---|
| 26 | */ |
|---|
| 27 | |
|---|
| 28 | typedef struct { |
|---|
| 29 | unsigned short max_conns; |
|---|
| 30 | } plugin_config; |
|---|
| 31 | |
|---|
| 32 | typedef struct { |
|---|
| 33 | PLUGIN_DATA; |
|---|
| 34 | |
|---|
| 35 | plugin_config **config_storage; |
|---|
| 36 | |
|---|
| 37 | plugin_config conf; |
|---|
| 38 | } plugin_data; |
|---|
| 39 | |
|---|
| 40 | INIT_FUNC(mod_evasive_init) { |
|---|
| 41 | plugin_data *p; |
|---|
| 42 | |
|---|
| 43 | UNUSED(srv); |
|---|
| 44 | |
|---|
| 45 | p = calloc(1, sizeof(*p)); |
|---|
| 46 | |
|---|
| 47 | return p; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | FREE_FUNC(mod_evasive_free) { |
|---|
| 51 | plugin_data *p = p_d; |
|---|
| 52 | |
|---|
| 53 | UNUSED(srv); |
|---|
| 54 | |
|---|
| 55 | if (!p) return HANDLER_GO_ON; |
|---|
| 56 | |
|---|
| 57 | if (p->config_storage) { |
|---|
| 58 | size_t i; |
|---|
| 59 | for (i = 0; i < srv->config_context->used; i++) { |
|---|
| 60 | plugin_config *s = p->config_storage[i]; |
|---|
| 61 | |
|---|
| 62 | free(s); |
|---|
| 63 | } |
|---|
| 64 | free(p->config_storage); |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | free(p); |
|---|
| 68 | |
|---|
| 69 | return HANDLER_GO_ON; |
|---|
| 70 | } |
|---|
| 71 | |
|---|
| 72 | SETDEFAULTS_FUNC(mod_evasive_set_defaults) { |
|---|
| 73 | plugin_data *p = p_d; |
|---|
| 74 | size_t i = 0; |
|---|
| 75 | |
|---|
| 76 | config_values_t cv[] = { |
|---|
| 77 | { "evasive.max-conns-per-ip", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, |
|---|
| 78 | { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } |
|---|
| 79 | }; |
|---|
| 80 | |
|---|
| 81 | p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); |
|---|
| 82 | |
|---|
| 83 | for (i = 0; i < srv->config_context->used; i++) { |
|---|
| 84 | plugin_config *s; |
|---|
| 85 | |
|---|
| 86 | s = calloc(1, sizeof(plugin_config)); |
|---|
| 87 | s->max_conns = 0; |
|---|
| 88 | |
|---|
| 89 | cv[0].destination = &(s->max_conns); |
|---|
| 90 | |
|---|
| 91 | p->config_storage[i] = s; |
|---|
| 92 | |
|---|
| 93 | if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { |
|---|
| 94 | return HANDLER_ERROR; |
|---|
| 95 | } |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | return HANDLER_GO_ON; |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | static int mod_evasive_patch_connection(server *srv, connection *con, plugin_data *p) { |
|---|
| 102 | size_t i, j; |
|---|
| 103 | plugin_config *s = p->config_storage[0]; |
|---|
| 104 | |
|---|
| 105 | PATCH_OPTION(max_conns); |
|---|
| 106 | |
|---|
| 107 | /* skip the first, the global context */ |
|---|
| 108 | for (i = 1; i < srv->config_context->used; i++) { |
|---|
| 109 | data_config *dc = (data_config *)srv->config_context->data[i]; |
|---|
| 110 | s = p->config_storage[i]; |
|---|
| 111 | |
|---|
| 112 | /* condition didn't match */ |
|---|
| 113 | if (!config_check_cond(srv, con, dc)) continue; |
|---|
| 114 | |
|---|
| 115 | /* merge config */ |
|---|
| 116 | for (j = 0; j < dc->value->used; j++) { |
|---|
| 117 | data_unset *du = dc->value->data[j]; |
|---|
| 118 | |
|---|
| 119 | if (buffer_is_equal_string(du->key, CONST_STR_LEN("evasive.max-conns-per-ip"))) { |
|---|
| 120 | PATCH_OPTION(max_conns); |
|---|
| 121 | } |
|---|
| 122 | } |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | return 0; |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | URIHANDLER_FUNC(mod_evasive_uri_handler) { |
|---|
| 129 | plugin_data *p = p_d; |
|---|
| 130 | size_t conns_by_ip = 0; |
|---|
| 131 | size_t j; |
|---|
| 132 | |
|---|
| 133 | if (con->uri.path->used == 0) return HANDLER_GO_ON; |
|---|
| 134 | |
|---|
| 135 | mod_evasive_patch_connection(srv, con, p); |
|---|
| 136 | |
|---|
| 137 | /* no limit set, nothing to block */ |
|---|
| 138 | if (p->conf.max_conns == 0) return HANDLER_GO_ON; |
|---|
| 139 | |
|---|
| 140 | switch (con->dst_addr.plain.sa_family) { |
|---|
| 141 | case AF_INET: |
|---|
| 142 | #ifdef HAVE_IPV6 |
|---|
| 143 | case AF_INET6: |
|---|
| 144 | #endif |
|---|
| 145 | break; |
|---|
| 146 | default: // Address family not supported |
|---|
| 147 | return HANDLER_GO_ON; |
|---|
| 148 | }; |
|---|
| 149 | |
|---|
| 150 | for (j = 0; j < srv->conns->used; j++) { |
|---|
| 151 | connection *c = srv->conns->ptr[j]; |
|---|
| 152 | |
|---|
| 153 | /* check if other connections are already actively serving data for the same IP |
|---|
| 154 | * we can only ban connections which are already behind the 'read request' state |
|---|
| 155 | * */ |
|---|
| 156 | if (c->dst_addr.plain.sa_family != con->dst_addr.plain.sa_family) continue; |
|---|
| 157 | if (c->state <= CON_STATE_HANDLE_REQUEST_HEADER) continue; |
|---|
| 158 | |
|---|
| 159 | switch (con->dst_addr.plain.sa_family) { |
|---|
| 160 | case AF_INET: |
|---|
| 161 | if (c->dst_addr.ipv4.sin_addr.s_addr != con->dst_addr.ipv4.sin_addr.s_addr) continue; |
|---|
| 162 | break; |
|---|
| 163 | #ifdef HAVE_IPV6 |
|---|
| 164 | case AF_INET6: |
|---|
| 165 | if (0 != memcmp(c->dst_addr.ipv6.sin6_addr.s6_addr, con->dst_addr.ipv6.sin6_addr.s6_addr, 16)) continue; |
|---|
| 166 | break; |
|---|
| 167 | #endif |
|---|
| 168 | default: // Address family not supported, should never be reached |
|---|
| 169 | continue; |
|---|
| 170 | }; |
|---|
| 171 | conns_by_ip++; |
|---|
| 172 | |
|---|
| 173 | if (conns_by_ip > p->conf.max_conns) { |
|---|
| 174 | log_error_write(srv, __FILE__, __LINE__, "ss", |
|---|
| 175 | inet_ntop_cache_get_ip(srv, &(con->dst_addr)), |
|---|
| 176 | "turned away. Too many connections."); |
|---|
| 177 | |
|---|
| 178 | con->http_status = 403; |
|---|
| 179 | return HANDLER_FINISHED; |
|---|
| 180 | } |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | return HANDLER_GO_ON; |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | |
|---|
| 187 | LI_EXPORT int mod_evasive_plugin_init(plugin *p) { |
|---|
| 188 | p->version = LIGHTTPD_VERSION_ID; |
|---|
| 189 | p->name = buffer_init_string("evasive"); |
|---|
| 190 | |
|---|
| 191 | p->init = mod_evasive_init; |
|---|
| 192 | p->set_defaults = mod_evasive_set_defaults; |
|---|
| 193 | p->handle_uri_clean = mod_evasive_uri_handler; |
|---|
| 194 | p->cleanup = mod_evasive_free; |
|---|
| 195 | |
|---|
| 196 | p->data = NULL; |
|---|
| 197 | |
|---|
| 198 | return 0; |
|---|
| 199 | } |
|---|