I implemented QPSMTPD last week on several servers and REALLY LIKE it!
I did find a couple of issues that I'm working through.
One is that I was still getting forged mail through SPF and TMDA. It was
happening like so:
MAIL FROM: <[EMAIL PROTECTED]>
RCPT TO: <[EMAIL PROTECTED]>
DATA
From: <[EMAIL PROTECTED]>
To: <[EMAIL PROTECTED]>
Since the MAIL FROM is valid it was getting through SPF.
Since the From: was a valid address on my machine then it was getting
through TMDA.
I implemented the enclosed plugin to check the From: address ONLY WHEN it
is in my rcpthosts file and if the From: host does not equal one of the
rcpthosts then DENY the mail at the SMTP transaction.
This *seems* to work beautifully for me for the last week but I'm a
mail-hacking newbie and maybe haven't thought of something.
Please (gently!) critique it and see if you think it is OK.
This is my first attempt at a QSMTPD plugin so I may have missed
something.
Thanks!
- David
#!/usr/bin/perl
=head1 NAME
verify_not_forged -
Check headers to make sure that mail can't get through SPF and TMDA when it
says it is coming from our host but it is really coming from some other
host. This works with SPF to check messages to our host and make sure
mail is getting through the SPF check by sending a valid Return-Path:
address but spoof the From: address thereby getting passed TMDA.
=head1 DESCRIPTION
First, if we are relaying then DECLINE to other plugins.
If we don't have DATA or From: then DENY appropriately.
The main test is to check the "mail from (sender, Return-Path) host" and
the "From: header host" against rcpthosts.
If ( SenderHost ne rcpthost and FromHost eq rcpthost ) then DENY mail.
=head1 CONFIGURATION
None.
=head1 AUTHOR
Written by David Summers.
=head1 LICENSE
Released to the public domain, 2007-10-29.
=cut
use Mail::Field;
sub hook_data_post {
my ($self, $transaction) = @_;
# At this point we should have gotten past the SPF checks.
# If we are relaying then decline and pass on to other plugins.
if ( $self->qp->connection->relay_client( ) )
{
$self->log( LOGINFO, "verify_not_forged: Relaying: DECLINED" );
return DECLINED;
}
# Check to make sure we have a sender address.
my $sender = $transaction->sender;
if ( ! $sender )
{
return ( DENY, "verify_not_forged: No sender address" );
}
# Check to make sure we have data.
if ( $transaction->data_size == 0 )
{
return (DENY, "verify_not_forged: You have to send some data first")
}
# Check to make sure we have a From: address.
my $fromheader = $transaction->header->get( 'From:' );
$self->log( LOGDEBUG, "verify_not_forged: ORIG From: $fromheader" );
my $fromaddresses = Mail::Field->new( 'From', $fromheader );
my @fromaddresses = $fromaddresses->addresses();
my $from = pop @fromaddresses;
$self->log( LOGDEBUG, "verify_not_forged: PULL From: \"$from\"" );
$from = Qpsmtpd::Address->new( "$from" );
$self->log( LOGDEBUG, "verify_not_forged: ADDRESS From: \"$from\"" );
if ( ! $from )
{
return (DENY, "verify_not_forged: Mail with no From: header not accepted "
. "here" );
}
my $senderhost = $sender->host( );
my $fromuser = $from->user( );
my $fromhost = $from->host( );
$self->log( LOGDEBUG, "verify_not_forged: fromuser=$fromuser "
. "fromhost=$fromhost" );
if ( ! $fromhost )
{
$self->log( LOGINFO, "verify_not_forged: no from host. DECLINED" );
return DECLINED;
}
my $myhost;
my @myhosts = $self->qp->config( 'rcpthosts' );
#push @myhosts, $self->qp->config( 'me' );
#my @locals = $self->qp->config( 'locals' );
#push @myhosts, @locals;
$self->log( LOGDEBUG, "verify_not_forged: myhost [EMAIL PROTECTED]" );
for $myhost ( @myhosts )
{
$self->log( LOGDEBUG, "verify_not_forged: CHECKING: myhost=$myhost, "
. "senderhost=$senderhost, fromhost=$fromhost" );
if ( $fromhost eq $myhost and $senderhost ne $myhost )
{
$self->log( LOGDEBUG, "verify_not_forged: senderhost=$senderhost "
. "NOT EQUAL myhost=$myhost AND fromhost=$fromhost EQUAL "
. "myhost=$myhost: DENY" );
return( DENY, "Can't send mail From: $from when it really came from "
. "Return-path: $sender" );
}
}
$self->log( LOGDEBUG, "verify_not_forged: NO PROBLEM: senderhost=$senderhost "
. "EQUAL [EMAIL PROTECTED] OR senderhost=$senderhost NOT EQUAL "
. "fromhost=$fromhost: DECLINED" );
return (DECLINED);
}
--
David Wayne Summers "Linux: Because reboots are for hardware upgrades!"
[EMAIL PROTECTED] PGP Key: http://summersoft.fay.ar.us/~david/pgp.txt
PGP Key fingerprint = 0B44 B118 85CC F4EC 7021 1ED4 1516 5B78 E320 2001