bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include "lib.h"
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include "ioloop.h"
bdd36cfdba3ff66d25570a9ff568d69e1eb543cfTimo Sirainen#include "net.h"
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen#include "crc32.h"
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen#include "master-service.h"
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include "anvil-client.h"
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include "auth-request.h"
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include "auth-penalty.h"
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen#include <stdio.h>
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen/* We don't want IPv6 hosts being able to flood our penalty
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen tracking with tons of different IPs. */
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen#define PENALTY_IPV6_MASK_BITS 48
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenstruct auth_penalty_request {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_request *auth_request;
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen struct anvil_client *client;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen auth_penalty_callback_t *callback;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen};
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenstruct auth_penalty {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct anvil_client *client;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen bool disabled:1;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen};
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenstruct auth_penalty *auth_penalty_init(const char *path)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_penalty *penalty;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen penalty = i_new(struct auth_penalty, 1);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen penalty->client = anvil_client_init(path, NULL,
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen ANVIL_CLIENT_FLAG_HIDE_ENOENT);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen if (anvil_client_connect(penalty->client, TRUE) < 0)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen penalty->disabled = TRUE;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen else {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen anvil_client_cmd(penalty->client, t_strdup_printf(
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen "PENALTY-SET-EXPIRE-SECS\t%u", AUTH_PENALTY_TIMEOUT));
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen return penalty;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenvoid auth_penalty_deinit(struct auth_penalty **_penalty)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_penalty *penalty = *_penalty;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen *_penalty = NULL;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen anvil_client_deinit(&penalty->client);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen i_free(penalty);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenunsigned int auth_penalty_to_secs(unsigned int penalty)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen unsigned int i, secs = AUTH_PENALTY_INIT_SECS;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen for (i = 0; i < penalty; i++)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen secs *= 2;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen return secs < AUTH_PENALTY_MAX_SECS ? secs : AUTH_PENALTY_MAX_SECS;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenstatic void auth_penalty_anvil_callback(const char *reply, void *context)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_penalty_request *request = context;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen unsigned int penalty = 0;
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen unsigned long last_penalty = 0;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen unsigned int secs, drop_penalty;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen if (reply == NULL) {
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen /* internal failure. */
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen if (!anvil_client_is_connected(request->client)) {
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen /* we probably didn't have permissions to reconnect
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen back to anvil. need to restart ourself. */
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen master_service_stop(master_service);
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen }
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen } else if (sscanf(reply, "%u %lu", &penalty, &last_penalty) != 2) {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen i_error("Invalid PENALTY-GET reply: %s", reply);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen } else {
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen if ((time_t)last_penalty > ioloop_time) {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen /* time moved backwards? */
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen last_penalty = ioloop_time;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen /* update penalty. */
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen drop_penalty = AUTH_PENALTY_MAX_PENALTY;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen while (penalty > 0) {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen secs = auth_penalty_to_secs(drop_penalty);
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen if (ioloop_time - last_penalty < secs)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen break;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen drop_penalty--;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen penalty--;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen request->callback(penalty, request->auth_request);
301740f53f71137a381417be0417418a52a04582Timo Sirainen auth_request_unref(&request->auth_request);
7f1e0e620d58f728e8f197ab2bf1a8baf55b4347Timo Sirainen i_free(request);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainenstatic const char *
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainenauth_penalty_get_ident(struct auth_request *auth_request)
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen{
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen struct ip_addr ip;
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen ip = auth_request->remote_ip;
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen if (IPADDR_IS_V6(&ip)) {
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen memset(ip.u.ip6.s6_addr + PENALTY_IPV6_MASK_BITS/CHAR_BIT, 0,
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen sizeof(ip.u.ip6.s6_addr) -
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen PENALTY_IPV6_MASK_BITS/CHAR_BIT);
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen }
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen return net_ip2addr(&ip);
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen}
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenvoid auth_penalty_lookup(struct auth_penalty *penalty,
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_request *auth_request,
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen auth_penalty_callback_t *callback)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_penalty_request *request;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen const char *ident;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen ident = auth_penalty_get_ident(auth_request);
d8702d15ee7721ed1fcfc8f00a589970bd6b3598Timo Sirainen if (penalty->disabled || ident == NULL || auth_request->no_penalty) {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen callback(0, auth_request);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen return;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen request = i_new(struct auth_penalty_request, 1);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen request->auth_request = auth_request;
e9d9f9071b12a687564c6817f3984cc1cd56d866Timo Sirainen request->client = penalty->client;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen request->callback = callback;
301740f53f71137a381417be0417418a52a04582Timo Sirainen auth_request_ref(auth_request);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen T_BEGIN {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen anvil_client_query(penalty->client,
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen t_strdup_printf("PENALTY-GET\t%s", ident),
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen auth_penalty_anvil_callback, request);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen } T_END;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainenstatic unsigned int
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainenget_userpass_checksum(struct auth_request *auth_request)
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen{
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen return auth_request->mech_password == NULL ? 0 :
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen crc32_str_more(crc32_str(auth_request->mech_password),
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen auth_request->user);
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen}
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainenvoid auth_penalty_update(struct auth_penalty *penalty,
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen struct auth_request *auth_request, unsigned int value)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen{
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen const char *ident;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
120ab73a12c9a752d112db80cd467eea514493c6Timo Sirainen ident = auth_penalty_get_ident(auth_request);
d8702d15ee7721ed1fcfc8f00a589970bd6b3598Timo Sirainen if (penalty->disabled || ident == NULL || auth_request->no_penalty)
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen return;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen if (value > AUTH_PENALTY_MAX_PENALTY) {
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen /* even if the actual value doesn't change, the last_change
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen timestamp does. */
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen value = AUTH_PENALTY_MAX_PENALTY;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen }
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen T_BEGIN {
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen const char *cmd;
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen unsigned int checksum;
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen checksum = value == 0 ? 0 : get_userpass_checksum(auth_request);
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen cmd = t_strdup_printf("PENALTY-INC\t%s\t%u\t%u",
5566faf0b48f0b77b0134f34130bdc7842c844ebTimo Sirainen ident, checksum, value);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen anvil_client_cmd(penalty->client, cmd);
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen } T_END;
e76073ebaf90fa29abfdc364873acf78983949aaTimo Sirainen}