#!/usr/bin/perl =head1 NAME smtp_callback - verify sender via smtp callback =head1 DESCRIPTION Plugin that checks if the envelope sender exists by contacting the MX of the sender's domain and checking the results of the VRFY and RCPT TO commands. This method was pioneered by Exim in 2000, and is also supported by postfix. It is somewhat controversial as there is some potential for DOS attacks and the callbacks may be mistaken for address harvesting (for example, gmail seems to block IP addresses which test a large number of addresses). Using VRFY seems to be mostly useless currently as almost all MTAs which support the command at all return a 252 (basically "don't know") reply, it might help an MTA distinguishing between address checks and mail delivery attempts. This plugin doesn't do the callbacks itself. Instead it relies on a server (smtpcbd) to perform them. This allows several MXs to share a common database of verified addresses. =head1 CONFIG Configured in the plugins file without any parameters, the smtp_callback plugin will connect to 127.0.0.1:4571 and do callbacks for all recipients. The format goes like smtp_callback option value [option value] Options being those listed below and the values being parameters to the options. Confused yet? :-) =over 4 =item smtpcbd address[:port] Set the adress and optionally port number of th smtpcbd to contact. =item per_recipient 0|1 If set to a non-zero value, the sender will only be checked for recipients which have the smtp_callback note set. Note that for this to work you also need the address_notes plugin and a plugin to set the note for the appropriate recipients (e.g. address_notes_aliases). =back =head1 TODO =cut use Qpsmtpd::DSN; use IO::Socket::INET; use POSIX; sub register { my ($self, $qp, @args) = @_; $self->log(LOGERROR, "Bad parameters for the smtp_callback plugin") if @_ % 2; %{$self->{_args}} = @args; } sub hook_rcpt { my ($self, $transaction, $rcpt) = @_; return DECLINED if ($transaction->sender->format eq '<>'); if ($self->{_args}{per_recipient} && ! $rcpt->notes('smtp_callback')) { $self->log(LOGINFO, 'smtp_callback skipped'); return DECLINED; } my $res = $transaction->notes('smtp_callback'); if ($res) { my ($code, $msg, $addr, $server, $timestamp, $expire) = split(/:/, $res); if ($code eq 'FAIL') { return Qpsmtpd::DSN->addr_bad_from_system("$addr doesn't exist according to $server:\n" . "$msg\n", "Unchanged since " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($timestamp)) . ", next check " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($expire)) ); } else { return DECLINED; } } my $paddr = '127.0.0.1'; my $port = 4571; if ($self->{_args}{smtpcbd}) { if ($self->{_args}{smtpcbd} =~ m/(.*):(.*)/) { $paddr = $1; $port = $2; } elsif ($self->{_args}{smtpcbd} =~ m/^([-.a-z0-9]+)$/i) { $paddr = $1; } else { $self->log(LOGERROR, "Bad parameter smtpcbd $self->{_args}{smtpcbd} for the smtp_callback plugin") } } $self->log(LOGINFO, "connecting to smtpcbd at $paddr:$port"); my $s = IO::Socket::INET->new(PeerAddr => $paddr, PeerPort => $port, Timeout => 60); $s->print($transaction->sender->format, "\n"); $res = $s->getline; $self->log(LOGINFO, "smtpcbd: $res"); return Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT, $transaction->sender->format ." cannot be verified (server unreachable?)") unless ($res); $transaction->notes('smtp_callback', $res); my ($code, $msg, $addr, $server, $timestamp, $expire) = split(/:/, $res); if ($code eq 'FAIL') { my @r = Qpsmtpd::DSN->addr_bad_from_system("$addr doesn't exist according to $server: " . "$msg. " . "Unchanged since " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($timestamp)) . ", next check " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($expire)) ); $self->log(LOGINFO, "failure: returning @r"); return @r; } elsif ($code eq 'TEMPFAIL') { my @r = Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT, "$addr has a temporary problem according to $server: " . "$msg. " . "Unchanged since " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($timestamp)) . ", next check " . strftime("%Y-%m-%dT%H:%M:%S%z", localtime($expire)) ); $self->log(LOGINFO, "temp failure: returning @r"); return @r; } else { $self->log(LOGINFO, "ok"); return DECLINED; } } # vim: tw=0