| 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 | unsigned short enable_logging; |
|---|
| 31 | unsigned short error_code; |
|---|
| 32 | } plugin_config; |
|---|
| 33 | |
|---|
| 34 | typedef struct { |
|---|
| 35 | PLUGIN_DATA; |
|---|
| 36 | |
|---|
| 37 | plugin_config **config_storage; |
|---|
| 38 | |
|---|
| 39 | plugin_config conf; |
|---|
| 40 | } plugin_data; |
|---|
| 41 | |
|---|
| 42 | INIT_FUNC(mod_evasive_init) { |
|---|
| 43 | plugin_data *p; |
|---|
| 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 | { "evasive.enable-logging", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, |
|---|
| 79 | { "evasive.error-code", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, |
|---|
| 80 | { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } |
|---|
| 81 | }; |
|---|
| 82 | |
|---|
| 83 | p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); |
|---|
| 84 | |
|---|
| 85 | for (i = 0; i < srv->config_context->used; i++) { |
|---|
| 86 | plugin_config *s; |
|---|
| 87 | |
|---|
| 88 | s = calloc(1, sizeof(plugin_config)); |
|---|
| 89 | s->max_conns = 0; |
|---|
| 90 | s->enable_logging = 0; |
|---|
| 91 | s->error_code = 503; |
|---|
| 92 | |
|---|
| 93 | cv[0].destination = &(s->max_conns); |
|---|
| 94 | cv[1].destination = &(s->enable_logging); |
|---|
| 95 | cv[2].destination = &(s->error_code); |
|---|
| 96 | |
|---|
| 97 | p->config_storage[i] = s; |
|---|
| 98 | |
|---|
| 99 | if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { |
|---|
| 100 | return HANDLER_ERROR; |
|---|
| 101 | } |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | return HANDLER_GO_ON; |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | #define PATCH(x) \ |
|---|
| 108 | p->conf.x = s->x; |
|---|
| 109 | static int mod_evasive_patch_connection(server *srv, connection *con, plugin_data *p) { |
|---|
| 110 | size_t i, j; |
|---|
| 111 | plugin_config *s = p->config_storage[0]; |
|---|
| 112 | |
|---|
| 113 | PATCH(max_conns); |
|---|
| 114 | PATCH(enable_logging); |
|---|
| 115 | PATCH(error_code); |
|---|
| 116 | |
|---|
| 117 | /* skip the first, the global context */ |
|---|
| 118 | for (i = 1; i < srv->config_context->used; i++) { |
|---|
| 119 | data_config *dc = (data_config *)srv->config_context->data[i]; |
|---|
| 120 | s = p->config_storage[i]; |
|---|
| 121 | |
|---|
| 122 | /* condition didn't match */ |
|---|
| 123 | if (!config_check_cond(srv, con, dc)) continue; |
|---|
| 124 | |
|---|
| 125 | /* merge config */ |
|---|
| 126 | for (j = 0; j < dc->value->used; j++) { |
|---|
| 127 | data_unset *du = dc->value->data[j]; |
|---|
| 128 | |
|---|
| 129 | if (buffer_is_equal_string(du->key, CONST_STR_LEN("evasive.max-conns-per-ip"))) { |
|---|
| 130 | PATCH(max_conns); |
|---|
| 131 | } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("evasive.enable-logging"))) { |
|---|
| 132 | PATCH(enable_logging); |
|---|
| 133 | } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("evasive.error-code"))) { |
|---|
| 134 | PATCH(error_code); |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | return 0; |
|---|
| 140 | } |
|---|
| 141 | #undef PATCH |
|---|
| 142 | |
|---|
| 143 | URIHANDLER_FUNC(mod_evasive_uri_handler) { |
|---|
| 144 | plugin_data *p = p_d; |
|---|
| 145 | size_t conns_by_ip = 0; |
|---|
| 146 | size_t j; |
|---|
| 147 | |
|---|
| 148 | if (con->uri.path->used == 0) return HANDLER_GO_ON; |
|---|
| 149 | |
|---|
| 150 | mod_evasive_patch_connection(srv, con, p); |
|---|
| 151 | |
|---|
| 152 | /* no limit set, nothing to block */ |
|---|
| 153 | if (p->conf.max_conns == 0) return HANDLER_GO_ON; |
|---|
| 154 | |
|---|
| 155 | for (j = 0; j < srv->conns->used; j++) { |
|---|
| 156 | connection *c = srv->conns->ptr[j]; |
|---|
| 157 | |
|---|
| 158 | /* check if other connections are already actively serving data for the same IP |
|---|
| 159 | * we can only ban connections which are already behind the 'read request' state |
|---|
| 160 | * */ |
|---|
| 161 | if (c->dst_addr.ipv4.sin_addr.s_addr == con->dst_addr.ipv4.sin_addr.s_addr && |
|---|
| 162 | c->state > CON_STATE_REQUEST_END) { |
|---|
| 163 | conns_by_ip++; |
|---|
| 164 | |
|---|
| 165 | if (conns_by_ip > p->conf.max_conns) { |
|---|
| 166 | if(p->conf.enable_logging) { |
|---|
| 167 | log_error_write(srv, __FILE__, __LINE__, "ss", |
|---|
| 168 | inet_ntop_cache_get_ip(srv, &(con->dst_addr)), |
|---|
| 169 | "turned away. Too many connections."); |
|---|
| 170 | } |
|---|
| 171 | if(p->conf.error_code > 99 && p->conf.error_code < 506) { |
|---|
| 172 | con->http_status = p->conf.error_code; |
|---|
| 173 | } else { |
|---|
| 174 | con->http_status = 503; |
|---|
| 175 | } |
|---|
| 176 | return HANDLER_FINISHED; |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | return HANDLER_GO_ON; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | |
|---|
| 185 | int mod_evasive_plugin_init(plugin *p) { |
|---|
| 186 | p->version = LIGHTTPD_VERSION_ID; |
|---|
| 187 | p->name = buffer_init_string("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 | p->cleanup = mod_evasive_free; |
|---|
| 193 | |
|---|
| 194 | p->data = NULL; |
|---|
| 195 | |
|---|
| 196 | return 0; |
|---|
| 197 | } |
|---|