diff options
Diffstat (limited to 'src/auth/auth-penalty.c')
-rw-r--r-- | src/auth/auth-penalty.c | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/auth/auth-penalty.c b/src/auth/auth-penalty.c new file mode 100644 index 0000000..3816902 --- /dev/null +++ b/src/auth/auth-penalty.c @@ -0,0 +1,176 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "net.h" +#include "crc32.h" +#include "master-service.h" +#include "anvil-client.h" +#include "auth-request.h" +#include "auth-penalty.h" + +#include <stdio.h> + +/* We don't want IPv6 hosts being able to flood our penalty + tracking with tons of different IPs. */ +#define PENALTY_IPV6_MASK_BITS 48 + +struct auth_penalty_request { + struct auth_request *auth_request; + struct anvil_client *client; + auth_penalty_callback_t *callback; +}; + +struct auth_penalty { + struct anvil_client *client; + + bool disabled:1; +}; + +struct auth_penalty *auth_penalty_init(const char *path) +{ + struct auth_penalty *penalty; + + penalty = i_new(struct auth_penalty, 1); + penalty->client = anvil_client_init(path, NULL, + ANVIL_CLIENT_FLAG_HIDE_ENOENT); + if (anvil_client_connect(penalty->client, TRUE) < 0) + penalty->disabled = TRUE; + else { + anvil_client_cmd(penalty->client, t_strdup_printf( + "PENALTY-SET-EXPIRE-SECS\t%u", AUTH_PENALTY_TIMEOUT)); + } + return penalty; +} + +void auth_penalty_deinit(struct auth_penalty **_penalty) +{ + struct auth_penalty *penalty = *_penalty; + + *_penalty = NULL; + anvil_client_deinit(&penalty->client); + i_free(penalty); +} + +unsigned int auth_penalty_to_secs(unsigned int penalty) +{ + unsigned int i, secs = AUTH_PENALTY_INIT_SECS; + + for (i = 0; i < penalty; i++) + secs *= 2; + return secs < AUTH_PENALTY_MAX_SECS ? secs : AUTH_PENALTY_MAX_SECS; +} + +static void auth_penalty_anvil_callback(const char *reply, void *context) +{ + struct auth_penalty_request *request = context; + unsigned int penalty = 0; + unsigned long last_penalty = 0; + unsigned int secs, drop_penalty; + + if (reply == NULL) { + /* internal failure. */ + if (!anvil_client_is_connected(request->client)) { + /* we probably didn't have permissions to reconnect + back to anvil. need to restart ourself. */ + master_service_stop(master_service); + } + } else if (sscanf(reply, "%u %lu", &penalty, &last_penalty) != 2) { + e_error(request->auth_request->event, + "Invalid PENALTY-GET reply: %s", reply); + } else { + if ((time_t)last_penalty > ioloop_time) { + /* time moved backwards? */ + last_penalty = ioloop_time; + } + + /* update penalty. */ + drop_penalty = AUTH_PENALTY_MAX_PENALTY; + while (penalty > 0) { + secs = auth_penalty_to_secs(drop_penalty); + if (ioloop_time - last_penalty < secs) + break; + drop_penalty--; + penalty--; + } + } + + request->callback(penalty, request->auth_request); + auth_request_unref(&request->auth_request); + i_free(request); +} + +static const char * +auth_penalty_get_ident(struct auth_request *auth_request) +{ + struct ip_addr ip; + + ip = auth_request->fields.remote_ip; + if (IPADDR_IS_V6(&ip)) { + memset(ip.u.ip6.s6_addr + PENALTY_IPV6_MASK_BITS/CHAR_BIT, 0, + sizeof(ip.u.ip6.s6_addr) - + PENALTY_IPV6_MASK_BITS/CHAR_BIT); + } + return net_ip2addr(&ip); +} + +void auth_penalty_lookup(struct auth_penalty *penalty, + struct auth_request *auth_request, + auth_penalty_callback_t *callback) +{ + struct auth_penalty_request *request; + const char *ident; + + ident = auth_penalty_get_ident(auth_request); + if (penalty->disabled || ident == NULL || + auth_request->fields.no_penalty) { + callback(0, auth_request); + return; + } + + request = i_new(struct auth_penalty_request, 1); + request->auth_request = auth_request; + request->client = penalty->client; + request->callback = callback; + auth_request_ref(auth_request); + + T_BEGIN { + anvil_client_query(penalty->client, + t_strdup_printf("PENALTY-GET\t%s", ident), + auth_penalty_anvil_callback, request); + } T_END; +} + +static unsigned int +get_userpass_checksum(struct auth_request *auth_request) +{ + return auth_request->mech_password == NULL ? 0 : + crc32_str_more(crc32_str(auth_request->mech_password), + auth_request->fields.user); +} + +void auth_penalty_update(struct auth_penalty *penalty, + struct auth_request *auth_request, unsigned int value) +{ + const char *ident; + + ident = auth_penalty_get_ident(auth_request); + if (penalty->disabled || ident == NULL || + auth_request->fields.no_penalty) + return; + + if (value > AUTH_PENALTY_MAX_PENALTY) { + /* even if the actual value doesn't change, the last_change + timestamp does. */ + value = AUTH_PENALTY_MAX_PENALTY; + } + T_BEGIN { + const char *cmd; + unsigned int checksum; + + checksum = value == 0 ? 0 : get_userpass_checksum(auth_request); + cmd = t_strdup_printf("PENALTY-INC\t%s\t%u\t%u", + ident, checksum, value); + anvil_client_cmd(penalty->client, cmd); + } T_END; +} |