diff --git a/Changes b/Changes index 3144b11..fa6578b 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,8 @@ 0.26-dev + unrecognized_command hook and a count_unrecognized_commands + plugin. (Rasjid Wilcox) + check_earlytalker plugin. Deny the connection if the client talks before we show our SMTP banner. (From Devin Carraway) diff --git a/README.plugins b/README.plugins index 64704f5..0d1d1f4 100644 --- a/README.plugins +++ b/README.plugins @@ -125,6 +125,14 @@ Called on "helo" from the client. DECLINED - Qpsmtpd will send the standard HELO message +=head2 unrecognized_command + +Called when we get a command that isn't recognized. + + DENY - Return 521 and disconnect the client + DONE - Qpsmtpd won't do anything; the plugin responded + Anything else - Return '500 Unrecognized command' + =head2 disconnect Called just before we shutdown a connection. diff --git a/config.sample/plugins b/config.sample/plugins index 90a48d6..fceff8d 100644 --- a/config.sample/plugins +++ b/config.sample/plugins @@ -9,6 +9,8 @@ quit_fortune #check_earlytalker +count_unrecognized_commands 4 + require_resolvable_fromhost rhsbl diff --git a/lib/Qpsmtpd/SMTP.pm b/lib/Qpsmtpd/SMTP.pm index 2fd1952..031db29 100644 --- a/lib/Qpsmtpd/SMTP.pm +++ b/lib/Qpsmtpd/SMTP.pm @@ -46,8 +46,20 @@ sub dispatch { #$self->respond(553, $state{dnsbl_blocked}), return 1 # if $state{dnsbl_blocked} and ($cmd eq "rcpt"); - $self->respond(500, "Unrecognized command"), return 1 - if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}); + if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) { + my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd); + if ($rc == DENY) { + $self->respond(521, $msg); + $self->disconnect; + } + elsif ($rc == DONE) { + 1; + } + else { + $self->respond(500, "Unrecognized command"); + } + return 1 + } $cmd = $1; if (1 or $self->{_commands}->{$cmd} and $self->can($cmd)) { diff --git a/plugins/count_unrecognized_commands b/plugins/count_unrecognized_commands new file mode 100644 index 0000000..2a1f7e4 --- /dev/null +++ b/plugins/count_unrecognized_commands @@ -0,0 +1,47 @@ +=head1 NAME + +count_unrecognized_commands - Count unrecognized commands and disconnect when we have too many + +=head1 DESCRIPTION + +Disconnect the client if it sends too many unrecognized commands. +Good for rejecting spam sent through open HTTP proxies. + +=head1 CONFIGURATION + +Takes one parameter, the number of allowed unrecognized commands +before we disconnect the client. Defaults to 4. + +=cut + +sub register { + my ($self, $qp, @args) = @_; + $self->register_hook("unrecognized_command", "check_unrec_cmd"); + + if (@args > 0) { + $self->{_unrec_cmd_max} = $args[0]; + $self->log(1, "WARNING: Ignoring additional arguments.") if (@args > 1); + } else { + $self->{_unrec_cmd_max} = 4; + } + + $self->{_unrec_cmd_count} = 0; +} + +sub check_unrec_cmd { + my ($self, $transaction, $cmd) = @_; + + $self->log(5, "Unrecognized command '$cmd'"); + + $self->{_unrec_cmd_count}++; + + my $badcmdcount = $self->{_unrec_cmd_count}; + + if ($badcmdcount >= $self->{_unrec_cmd_max}) { + $self->log(5, "Closing connection. Too many unrecognized commands."); + return (DENY, "Closing connection. $badcmdcount unrecognized commands. Perhaps you should read RFC 2821?"); + } + + return DECLINED; +} +