#!perl -w =head1 NAME bogus_bounce - Check that a bounce message isn't bogus =head1 DESCRIPTION This plugin is designed to reject bogus bounce messages. In our case a bogus bounce message is defined as a bounce message which has more than a single recipient. =head1 CONFIGURATION Only a single argument is recognized and is assumed to be the default action. Valid settings are: =over 8 =item log Merely log the receipt of the bogus bounce (the default behaviour). =item deny Deny with a hard error code. =item denysoft Deny with a soft error code. =back =head1 AUTHOR 2010 - Steve Kemp - http://steve.org.uk/Software/qpsmtpd/ 2013 - Matt Simerson - added Return Path check =cut sub register { my ($self, $qp) = (shift, shift); if ( @_ % 2 ) { $self->{_args}{action} = shift; } else { $self->{_args} = { @_ }; }; if ( ! defined $self->{_args}{reject} ) { $self->{_args}{reject} = 0; # legacy default }; # we only need to check for deferral, default is DENY if ( $self->{_args}{action} =~ /soft/i ) { $self->{_args}{reject_type} = 'temp'; } } sub hook_data_post { my ($self, $transaction) = (@_); # # Find the sender, quit processing if this isn't a bounce. # my $sender = $transaction->sender->address || undef; if ( $sender && $sender ne '<>') { $self->log(LOGINFO, "pass, not a null sender"); return DECLINED; }; # at this point we know it is a bounce, via the null-envelope. # # Count the recipients. Valid bounces have a single recipient # my @to = $transaction->recipients || (); if (scalar @to != 1) { $self->log(LOGINFO, "fail, bogus bounce to: " . join(',', @to)); return $self->get_reject( "fail, this bounce message does not have 1 recipient" ); }; # validate that Return-Path is empty, RFC 3834 my $rp = $transaction->header->get('Return-Path'); if ( $rp && $rp ne '<>' ) { $self->log(LOGINFO, "fail, bounce messages must not have a Return-Path"); return $self->get_reject( "a bounce return path must be empty (RFC 3834)" ); }; $self->log(LOGINFO, "pass, single recipient, empty Return-Path"); return DECLINED; }