=head1 NAME count_denies - Count denies and disconnect when we have too many =head1 DESCRIPTION Disconnect the client if it receives too many denies. Good for thwarting dictionary attacks. =head1 CONFIGURATION Takes one parameter, the number of allowed denies before we disconnect the client. Defaults to 4. =cut sub register { my ($self, $qp, @args) = @_; $self->register_hook("deny", "check_deny"); $self->register_hook("rcpt", "check_rcpt"); if (@args > 0) { $self->{_deny_max} = $args[0]; $self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args > 1); } else { $self->{_deny_max} = 4; } $qp->connection->notes('deny_count', 0); } sub check_deny { my ($self, $transaction, $plugin, $result, $message) = @_; # Qpsmtpd::Plugin::count_denies=HASH(0x8264290) Qpsmtpd::Transaction=HASH(0x8892a1c) aliases 901 no such user $self->log(LOGDEBUG, "check_deny: @_"); $self->log(LOGDEBUG, "check_deny: result=$result"); if ($result == DENY) { my $deny_count = $self->qp->connection->notes('deny_count'); $self->log(LOGDEBUG, "check_deny: Deny count $deny_count"); $self->qp->connection->notes('deny_count', $deny_count+1); } return DECLINED; } sub check_rcpt { my ($self, $transaction, $rcpt) = @_; my $deny_count = $self->qp->connection->notes('deny_count'); $self->log(LOGDEBUG, "check_rcpt: Deny count $deny_count"); if ($deny_count >= $self->{_deny_max}) { $self->log(LOGNOTICE, "Closing connection. $deny_count denied commands."); return (DENYSOFT_DISCONNECT, "Closing connection. $deny_count denied commands."); } return DECLINED; }