From 73f4759ae7ec76935e0d2518170b9a231a842a0b Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Wed, 13 Mar 2013 03:19:48 -0400 Subject: [PATCH] karma: general improvements skip earlytalker checks for positive senders limit negative karma senders to 1 concurrent connection (hosts_allow) added karma::hook_pre_connection, to make hosts_allow change possible added karma score to log entries --- plugins/earlytalker | 3 ++ plugins/hosts_allow | 11 +++++++ plugins/karma | 70 +++++++++++++++++++++++++++-------------- plugins/virus/clamdscan | 5 +-- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/plugins/earlytalker b/plugins/earlytalker index bcbad95..cb31010 100644 --- a/plugins/earlytalker +++ b/plugins/earlytalker @@ -163,6 +163,9 @@ sub connect_handler { return DECLINED unless $self->{_args}{'check-at'}{CONNECT}; return DECLINED if $self->is_immune(); + my $karma = $self->connection->notes('karma_history'); + return DECLINED if (defined $karma && $karma > 5); + $in->add(\*STDIN) or return DECLINED; if (! $in->can_read($self->{_args}{'wait'})) { return $self->log_and_pass(); diff --git a/plugins/hosts_allow b/plugins/hosts_allow index 6661ec1..d226578 100644 --- a/plugins/hosts_allow +++ b/plugins/hosts_allow @@ -68,6 +68,7 @@ sub hook_pre_connection { my $remote = $args{remote_ip}; my $max = $args{max_conn_ip}; + my $karma = $self->connection->notes('karma_history'); if ( $max ) { my $num_conn = 1; # seed with current value @@ -75,6 +76,7 @@ sub hook_pre_connection { foreach my $rip (@{$args{child_addrs}}) { ++$num_conn if (defined $rip && $rip eq $raddr); } + $max = $self->karma_bump( $karma, $max ) if defined $karma; if ($num_conn > $max ) { my $err_mess = "too many connections from $remote"; $self->log(LOGINFO, "fail: $err_mess ($num_conn > $max)"); @@ -113,3 +115,12 @@ sub in_hosts_allow { return; }; + +sub karma_bump { + my ($self, $karma, $max) = @_; + if ( $karma <= 0 ) { + $self->log(LOGINFO, "limiting max connects to 1 for negative karma ($karma)"); + return 1; + }; + return $max; +}; diff --git a/plugins/karma b/plugins/karma index b5a3a33..723d17c 100644 --- a/plugins/karma +++ b/plugins/karma @@ -6,7 +6,7 @@ karma - reward nice and penalize naughty mail senders =head1 SYNOPSIS -Karma tracks sender history, providing the ability to deliver differing levels +Karma tracks sender history, allowing us to provide differing levels of service to naughty, nice, and unknown senders. =head1 DESCRIPTION @@ -14,7 +14,7 @@ of service to naughty, nice, and unknown senders. Karma records the number of nice, naughty, and total connections from mail senders. After sending a naughty message, if a sender has more naughty than nice connections, they are penalized for I. Connections -from senders in the penalty box are tersely disconnected. +from senders in the penalty box are rejected per the settings in I. Karma provides other plugins with a karma value they can use to be more lenient, strict, or skip processing entirely. @@ -24,10 +24,9 @@ custom connection policies such as these two examples: =over 4 -Hi there, well behaved sender. Please help yourself to TLS, AUTH, greater -concurrency, multiple recipients, no delays, and other privileges. +Hi there, well behaved sender. Please help yourself to greater concurrency, multiple recipients, no delays, and other privileges. -Hi there, naughty sender. Enjoy this poke in the eye with a sharp stick. Bye. +Hi there, naughty sender. You get a max concurrency of 1, and SMTP delays. =back @@ -114,13 +113,7 @@ run before B for that to work. No attempt is made by this plugin to determine what karma is. It is up to other plugins to make that determination and communicate it to this plugin by incrementing or decrementing the transaction note B. Raise it for good -karma and lower it for bad karma. This is best done like so: - - # only if karma plugin loaded - if ( defined $connection->notes('karma') ) { - $connection->notes('karma', $connection->notes('karma') - 1); # bad - $connection->notes('karma', $connection->notes('karma') + 1); # good - }; +karma and lower it for bad karma. See B. After the connection ends, B will record the result. Mail servers whose naughty connections exceed nice ones are sent to the penalty box. Servers in @@ -133,7 +126,7 @@ an example connection from an IP in the penalty box: 73122 (connect) earlytalker: pass: 64.185.226.65 said nothing spontaneous 73122 (connect) relay: skip: no match 73122 (connect) karma: fail - 73122 550 You were naughty. You are penalized for 0.99 more days. + 73122 550 You were naughty. You are cannot connect for 0.99 more days. 73122 click, disconnecting 73122 (post-connection) connection_time: 1.048 s. @@ -211,12 +204,11 @@ karma_tool script. =head1 BUGS & LIMITATIONS -This plugin is reactionary. Like the FBI, it doesn't punish until -after a crime has been committed. It an "abuse me once, shame on you, -abuse me twice, shame on me" policy. +This plugin is reactionary. Like the FBI, it doesn't do anything until +after a crime has been committed. There is little to be gained by listing servers that are already on DNS -blacklists, send to non-existent users, earlytalkers, etc. Those already have +blacklists, send to invalid users, earlytalkers, etc. Those already have very lightweight tests. =head1 AUTHOR @@ -255,6 +247,32 @@ sub register { $self->register_hook('disconnect', 'disconnect_handler'); } +sub hook_pre_connection { + my ($self,$transaction,%args) = @_; + + $self->connection->notes('karma_history', 0); + + my $remote_ip = $args{remote_ip}; + #my $max_conn = $args{max_conn_ip}; + + my $db = $self->get_db_location(); + my $lock = $self->get_db_lock( $db ) or return DECLINED; + my $tied = $self->get_db_tie( $db, $lock ) or return DECLINED; + my $key = $self->get_db_key( $remote_ip ) or do { + $self->log( LOGINFO, "skip, unable to get DB key" ); + return DECLINED; + }; + + if ( ! $tied->{$key} ) { + $self->log(LOGINFO, "pass, no record"); + return $self->cleanup_and_return($tied, $lock ); + }; + + my ($penalty_start_ts, $naughty, $nice, $connects) = $self->parse_value( $tied->{$key} ); + $self->calc_karma($naughty, $nice); + return $self->cleanup_and_return($tied, $lock ); +}; + sub connect_handler { my $self = shift; @@ -294,7 +312,7 @@ sub connect_handler { $self->cleanup_and_return($tied, $lock ); my $left = sprintf "%.2f", $self->{_args}{penalty_days} - $days_old; - my $mess = "You were naughty. You are penalized for $left more days."; + my $mess = "You were naughty. You cannot connect for $left more days."; return $self->get_reject( $mess, $karma ); } @@ -313,11 +331,11 @@ sub disconnect_handler { my $key = $self->get_db_key(); my ($penalty_start_ts, $naughty, $nice, $connects) = $self->parse_value( $tied->{$key} ); + my $history = ($nice || 0) - $naughty; if ( $karma < 0 ) { - $naughty++; + $history--; my $negative_limit = 0 - $self->{_args}{negative}; - my $history = ($nice || 0) - $naughty; if ( $history <= $negative_limit ) { if ( $nice == 0 && $history < -5 ) { $self->log(LOGINFO, "penalty box bonus!"); @@ -326,15 +344,15 @@ sub disconnect_handler { else { $penalty_start_ts = sprintf "%s", time; }; - $self->log(LOGINFO, "negative, sent to penalty box ($history)"); + $self->log(LOGINFO, "negative, sent to penalty box (k: $karma, h: $history)"); } else { - $self->log(LOGINFO, "negative"); + $self->log(LOGINFO, "negative (k: $karma, h: $history)"); }; } elsif ($karma > 1) { $nice++; - $self->log(LOGINFO, "positive"); + $self->log(LOGINFO, "positive (k: $karma, h: $history)"); } $tied->{$key} = join(':', $penalty_start_ts, $naughty, $nice, ++$connects); @@ -375,7 +393,11 @@ sub cleanup_and_return { sub get_db_key { my $self = shift; - my $nip = Net::IP->new( $self->qp->connection->remote_ip ) or return; + my $ip = shift || $self->qp->connection->remote_ip; + my $nip = Net::IP->new( $ip ) or do { + $self->log(LOGERROR, "skip, unable to determine remote IP"); + return; + }; return $nip->intip; # convert IP to an int }; diff --git a/plugins/virus/clamdscan b/plugins/virus/clamdscan index ab35ab0..4148bd8 100644 --- a/plugins/virus/clamdscan +++ b/plugins/virus/clamdscan @@ -168,10 +168,7 @@ sub data_post_handler { $self->log( LOGNOTICE, "fail, found virus $found" ); $self->connection->notes('naughty', 1); # see plugins/naughty - - if ( defined $self->connection->notes('karma') ) { - $self->connection->notes('karma', ($self->connection->notes('karma') - 1)); - }; + $self->adjust_karma( -1 ); if ( $self->{_args}{deny_viruses} ) { return ( DENY, "Virus found: $found" );