Skip to content

Instantly share code, notes, and snippets.

Created May 7, 2012 23:23
Show Gist options
  • Save anonymous/2631381 to your computer and use it in GitHub Desktop.
Save anonymous/2631381 to your computer and use it in GitHub Desktop.
package DADA::App::BounceHandler::MessageParser;
use strict;
use lib qw(../../../ ../../../DADA/perllib);
use DADA::Config qw(!:DEFAULT);
use DADA::App::Guts;
use 5.008_001;
use Mail::Verp;
use Carp qw(croak carp);
use vars qw($AUTOLOAD);
my %allowed = ();
sub new {
my $that = shift;
my $class = ref($that) || $that;
my $self = {
_permitted => \%allowed,
%allowed,
};
bless $self, $class;
my %args = (@_);
$self->_init( \%args );
return $self;
}
sub AUTOLOAD {
my $self = shift;
my $type = ref($self)
or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; #strip fully qualifies portion
unless ( exists $self->{_permitted}->{$name} ) {
croak "Can't access '$name' field in object of class $type";
}
if (@_) {
return $self->{$name} = shift;
}
else {
return $self->{$name};
}
}
sub _init {
my $self = shift;
my $args = shift;
}
sub run_all_parses {
my $self = shift;
my ($entity) = shift;
my $email = '';
my $list = '';
my $diagnostics = {};
$email = $self->find_verp($entity);
# Amazon SES is sort of special, since it's very, very easy to understand if
# It's coming from it:
if($self->bounce_from_ses($entity)){
my ( $ses_list, $ses_email, $ses_diagnostics ) =
$self->parse_for_amazon_ses($entity);
$list ||= $ses_list;
$email ||= $ses_email;
%{$diagnostics} = ( %{$diagnostics}, %{$ses_diagnostics} )
if $ses_diagnostics;
}
elsif($self->bounce_from_secureserver_dot_net($entity)){
my ( $ss_list, $ss_email, $ss_diagnostics ) =
$self->parse_for_secureserver_dot_net($entity);
$list ||= $ss_list;
$email ||= $ss_email;
%{$diagnostics} = ( %{$diagnostics}, %{$ss_diagnostics} )
if $ss_diagnostics;
}
else {
# Else, let's try other things:
my ( $gp_list, $gp_email, $gp_diagnostics ) = $self->generic_parse($entity);
if(!$list) {
$list = $gp_list;
}
if(!$email){
$email = $gp_email;
}
%{$diagnostics} = ( %{$diagnostics}, %{$gp_diagnostics} )
if $gp_diagnostics;
}
# This should really do the same thing, first look for tell-tale signs
# that the bounce is a qmail-like bounce, before parsing it out.
# (and along down the line...)
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $qmail_list, $qmail_email, $qmail_diagnostics ) =
$self->parse_for_qmail($entity);
$list ||= $qmail_list;
$email ||= $qmail_email;
%{$diagnostics} = (%{$qmail_diagnostics}, %{$diagnostics})
if $qmail_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $exim_list, $exim_email, $exim_diagnostics ) =
$self->parse_for_exim($entity);
if(!$list){
$list = $exim_list;
}
if(!$email) {
$email = $exim_email;
}
%{$diagnostics} = (%{$exim_diagnostics}, %{$diagnostics} )
if $exim_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $ms_list, $ms_email, $ms_diagnostics ) =
$self->parse_for_f__king_exchange($entity);
$list ||= $ms_list;
$email ||= $ms_email;
%{$diagnostics} = (%{$ms_diagnostics}, %{$diagnostics} )
if $ms_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $nv_list, $nv_email, $nv_diagnostics ) =
$self->parse_for_novell($entity);
$list ||= $nv_list;
$email ||= $nv_email;
%{$diagnostics} = ( %{$nv_diagnostics}, %{$diagnostics} )
if $nv_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $g_list, $g_email, $g_diagnostics ) =
$self->parse_for_gordano($entity);
$list ||= $g_list;
$email ||= $g_email;
%{$diagnostics} = (%{$g_diagnostics}, %{$diagnostics})
if $g_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $y_list, $y_email, $y_diagnostics ) =
$self->parse_for_overquota_yahoo($entity);
$list ||= $y_list;
$email ||= $y_email;
%{$diagnostics} = (%{$y_diagnostics} ,%{$diagnostics} )
if $y_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $el_list, $el_email, $el_diagnostics ) =
$self->parse_for_earthlink($entity);
$list ||= $el_list;
$email ||= $el_email;
%{$diagnostics} = (%{$el_diagnostics}, %{$diagnostics} )
if $el_diagnostics;
}
if ( ( !$list ) || ( !$email ) || !keys %{$diagnostics} ) {
my ( $wl_list, $wl_email, $wl_diagnostics ) =
$self->parse_for_windows_live($entity);
$list ||= $wl_list;
$email ||= $wl_email;
%{$diagnostics} = (%{$wl_diagnostics}, %{$diagnostics} )
if $wl_diagnostics;
}
# This is a special case - since this outside module adds pseudo diagonistic
# reports, we'll say, add them if they're NOT already there:
my ( $bp_list, $bp_email, $bp_diagnostics ) =
$self->parse_using_m_ds_bp($entity);
# There's no test for these in the module itself, so we
# won't even look for them.
#$list ||= $bp_list;
#$email ||= $bp_email;
%{$diagnostics} = ( %{$diagnostics}, %{$bp_diagnostics} )
if $bp_diagnostics;
chomp($email) if $email;
#small hack, turns, %2 into, '-'
$list =~ s/\%2d/\-/g;
$list = strip($list);
if ( !$diagnostics->{'Message-Id'} ) {
$diagnostics->{'Message-Id'} =
$self->find_message_id_in_headers($entity);
if ( !$diagnostics->{'Message-Id'} ) {
$diagnostics->{'Message-Id'} =
$self->find_message_id_in_body($entity);
}
}
if ( $diagnostics->{'Message-Id'} ) {
$diagnostics->{'Simplified-Message-Id'} = $diagnostics->{'Message-Id'};
$diagnostics->{'Simplified-Message-Id'} =~ s/\<|\>//g;
$diagnostics->{'Simplified-Message-Id'} =~ s/\.(.*)//; #greedy
$diagnostics->{'Simplified-Message-Id'} =
strip( $diagnostics->{'Simplified-Message-Id'} );
}
return ( $email, $list, $diagnostics );
}
sub find_verp {
my $self = shift;
my $entity = shift;
my $mv = Mail::Verp->new;
$mv->separator($DADA::Config::MAIL_VERP_SEPARATOR);
if ( $entity->head->count('To') > 0 ) {
my ( $sender, $recipient ) =
$mv->decode( $entity->head->get( 'To', 0 ) );
return $recipient || undef;
}
return undef;
}
sub generic_parse {
my $self = shift;
my $entity = shift;
my ( $email, $list );
my %return = ();
my $headers_diag = {};
$headers_diag = $self->get_orig_headers($entity);
my $diag = {};
( $email, $diag ) = $self->find_delivery_status($entity);
if ( keys %$diag ) {
%return = ( %{$diag}, %{$headers_diag} );
}
else {
%return = %{$headers_diag};
}
$list = $self->find_list_in_list_headers($entity);
$list ||= $self->generic_body_parse_for_list($entity);
$email = DADA::App::Guts::strip($email);
$email =~ s/^\<|\>$//g if $email;
$list = DADA::App::Guts::strip($list) if $list;
return ( $list, $email, \%return );
}
sub get_orig_headers {
my $self = shift;
my $entity = shift;
my $diag = {};
for ( 'From', 'To', 'Subject' ) {
if ( $entity->head->count($_) ) {
my $header = $entity->head->get( $_, 0 );
chomp $header;
$diag->{ 'Bounce_' . $_ } = $header;
}
}
return $diag;
}
sub find_delivery_status {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
if ( !@parts ) {
if ( $entity->head->mime_type eq 'message/delivery-status' ) {
( $email, $diag ) = $self->generic_delivery_status_parse($entity);
return ( $email, $diag );
}
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $email, $diag ) = $self->find_delivery_status($part);
if ( ($email) && ( keys %$diag ) ) {
return ( $email, $diag );
}
}
}
}
sub find_mailer_bounce_headers {
my $self = shift;
my $entity = shift;
my $mailer = $entity->head->get( 'X-Mailer', 0 );
$mailer =~ s/\n//g;
return $mailer if $mailer;
}
sub find_list_in_list_headers {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $list;
my $orig_msg_copy = undef;
if ( $entity->head->mime_type eq 'message/rfc822') {
$orig_msg_copy = $parts[0];
$list = $self->list_in_list_headers($orig_msg_copy);
}
elsif($entity->head->mime_type eq 'text/rfc822-headers'){
require MIME::Parser;
my $parser = new MIME::Parser;
$parser = optimize_mime_parser($parser);
eval {
$orig_msg_copy = $parser->parse_data( $entity->bodyhandle->as_string );
};
if ($@) {
warn "Trouble parsing text/rfc822-headers message. $@";
}
else {
}
$list = $self->list_in_list_headers($orig_msg_copy);
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
$list = $self->find_list_in_list_headers($part);
return $list if $list;
}
}
}
sub list_in_list_headers {
my $self = shift;
my $entity = shift;
my $list = undef;
my $list_header = $entity->head->get( 'List', 0 );
$list = $list_header if $list_header !~ /\:/;
if ( !$list ) {
$list_header = $entity->head->get( 'X-List', 0 );
$list = $list_header if $list_header !~ /\:/;
}
if ( !$list ) {
my $list_id = $entity->head->get( 'List-ID', 0 );
if ( $list_id =~ /\<(.*?)\./ ) {
$list = $1 if $1 !~ /\:/;
}
}
if ( !$list ) {
my $list_sub = $entity->head->get( 'List-Subscribe', 0 );
if ( $list_sub =~ /l\=(.*?)\>/ ) {
$list = $1;
}
}
chomp $list;
return $list;
}
sub find_message_id_in_headers {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $mid;
if ( $entity->head->mime_type eq 'message/rfc822' || $entity->head->mime_type eq 'text/rfc822-headers') {
my $orig_msg_copy = '';
if($entity->head->mime_type eq 'text/rfc822-headers') {
require MIME::Parser;
my $parser = new MIME::Parser;
$parser = optimize_mime_parser($parser);
eval { $orig_msg_copy = $parser->parse_data($entity->bodyhandle->as_string) };
if ( $@ ) {
warn "Trouble parsing text/rfc822-headers message. $@";
}
else {
}
}
else {
$orig_msg_copy = $parts[0];
}
# Amazon SES finds this in the, "X-Message-ID" header:
# Amazon SES will also set its own Message-ID. Maddening!
if($orig_msg_copy->head->get( 'X-Message-ID', 0 )){
$mid = $orig_msg_copy->head->get( 'X-Message-ID', 0 );
}
else {
$mid = $orig_msg_copy->head->get( 'Message-ID', 0 );
}
$mid = strip($mid);
chomp($mid);
return $mid;
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
$mid = $self->find_message_id_in_headers($part);
return $mid if $mid;
}
}
}
sub find_message_id_in_body {
my $self = shift;
my $entity = shift;
my $m_id;
my @parts = $entity->parts;
# for singlepart stuff only.
if ( !@parts ) {
my $body = $entity->bodyhandle;
my $IO;
return undef if !defined($body);
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
chomp($_);
if ( $_ =~ m/^Message\-Id\:(.*?)$/ig ) {
#yeah, sometimes the headers are in the body of
#an attached message. Go figure.
$m_id = $1;
}
}
}
$IO->close;
$m_id = strip($m_id);
return $m_id;
}
else {
return undef;
}
}
sub generic_delivery_status_parse {
my $self = shift;
my $entity = shift;
my $diag = {};
my $email;
# sanity check
#if($delivery_status_entity->head->mime_type eq 'message/delivery-status'){
my $body = $entity->bodyhandle;
my @lines;
my $IO;
my %bodyfields;
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
if ( $_ =~ m/\:/ ) {
my ( $k, $v ) = split( ':', $_ );
chomp($v);
#$bodyfields{$k} = $v;
$diag->{$k} = $v;
}
}
$IO->close;
}
if ( $diag->{'Diagnostic-Code'} =~ /X\-Postfix/ ) {
$diag->{Guessed_MTA} = 'Postfix';
}
my ( $rfc, $remail ) = split( ';', $diag->{'Final-Recipient'} );
if ( $remail eq '<>' ) { #example: Final-Recipient: LOCAL;<>
( $rfc, $remail ) = split( ';', $diag->{'Original-Recipient'} );
}
$email = $remail;
for ( keys %$diag ) {
$diag->{$_} = strip( $diag->{$_} );
}
chomp ($email);
$email =~ s/\n$//g;
$email =~ s/\r$//g;
return ( $email, $diag );
}
sub generic_body_parse_for_list {
my $self = shift;
my $entity = shift;
my $list;
my @parts = $entity->parts;
if ( !@parts ) {
$list = $self->find_list_from_unsub_link($entity);
return $list if $list;
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
$list = $self->generic_body_parse_for_list($part);
if ($list) {
return $list;
}
}
}
}
sub find_list_from_unsub_link {
my $self = shift;
my $entity = shift;
my $list;
my $body = $entity->bodyhandle;
my $IO;
return undef if !defined($body);
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
chomp($_);
# DEV: BUGFIX:
# 2351425 - 3.0.0 - find_list_from_unsub_list sub out-of-date
# https://sourceforge.net/tracker2/?func=detail&aid=2351425&group_id=13002&atid=113002
if ( $_ =~ m/$DADA::Config::PROGRAM_URL\/(u|list)\/(.*?)\// ) {
$list = $2;
if($list =~ m/\"\>/){ # We've picked up a screwy link in HTML.
undef $list;
}
}
# /DEV: BUGFIX
elsif ( $_ =~ m/^List\:(.*?)$/ ) {
#yeah, sometimes the headers are in the body of
#an attached message. Go figure.
$list = $1;
}
elsif ( $_ =~ m/(.*?)\?l\=(.*?)\&f\=u\&e\=/ ) {
$list = $2;
}
elsif ( $_ =~ m/(.*?)\?f\=u\&l\=(.*?)\&e\=/ ) {
$list = $2;
}
}
}
$IO->close;
return $list;
}
sub bounce_from_ses {
my $self = shift;
my $entity = shift;
# As far as I know, it's all from:
my $amazon_ses_from = 'MAILER-DAEMON@email-bounces.amazonses.com';
my $qm_ses = quotemeta($amazon_ses_from);
if($entity->head->get( 'From', 0 ) =~ m/$qm_ses/){
return 1;
}
else {
return 0;
}
}
sub bounce_from_secureserver_dot_net {
my $self = shift;
my $entity = shift;
# As far as I know, it's all from:
my $secure_server_from_fragment = 'secureserver.net';
my $qm = quotemeta($secure_server_from_fragment);
if($entity->head->get( 'From', 0 ) =~ m/$qm/){
return 1;
}
else {
return 0;
}
}
sub parse_for_amazon_ses {
my $self = shift;
my $entity = shift;
my $diag = {};
my $email;
my $list;
my @parts = $entity->parts;
if($parts[1]){
my $mds_entity = $parts[1];
if ( $mds_entity->head->mime_type eq 'message/delivery-status' ) {
( $email, $diag ) = $self->generic_delivery_status_parse($mds_entity);
}
}
if($parts[2]){
my $orig_msg_entity = $parts[2];
if ( $orig_msg_entity->head->mime_type eq 'message/rfc822'
|| $orig_msg_entity->head->mime_type eq 'text/rfc822-headers'
) {
$list = $self->find_list_in_list_headers($orig_msg_entity);
$diag->{'Message-Id'} = $self->find_message_id_in_headers($orig_msg_entity);
}
}
$diag->{Guessed_MTA} = 'Amazon_SES';
return ( $list, $email, $diag );
}
sub parse_for_secureserver_dot_net {
# This seems to be qmail. Sometimes.
my $self = shift;
my $entity = shift;
my $diag = {};
my $email;
my $list;
# <subscriber@example.com>:
# child status 100...The e-mail message could not be delivered because the user's mailfolder is full.
my @parts = $entity->parts;
if(scalar @parts == 0){
my $body = $entity->bodyhandle;
# Your mail message to the following address(es) could not be delivered. This
# is a permanent error. Please verify the addresses and try again. If you are
# still having difficulty sending mail to these addresses, please contact
# Customer Support at 480-624-2500.
# tell me why I'm not using the range operator?
my $begin = quotemeta('Customer Support at 480-624-2500.');
my $begin2 = quotemeta("This is a permanent error; I've given up. Sorry it didn't work out.");
my $end = quotemeta('--- Below this line is a copy of the message.');
my $stuff = '';
my $state = 0;
my $IO;
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
if($data =~ /$begin|$begin2/) {
$state = 1;
next;
}
if($data =~ /$end/){
$state = 0;
last;
}
if ( $state == 1 ) {
$stuff .= $data;
}
}
}
$stuff =~ m/\<(.*?)\>\:(.*)/ms;
$email = $1;
$email = strip($email);
$diag->{'Diagnostic-Code'} = $2;
$diag->{'Diagnostic-Code'} = strip($diag->{'Diagnostic-Code'});
#$list = $self->generic_body_parse_for_list($entity);
# Right now, I only have rules for, mailbox full kin of stuff so if it's not one of those ,
# I'd rather this be parsed with the Qmail stuff,
# Looks like invalid mailboxes are handled by the local mail server ala:
# SMTP error from remote mail server after RCPT TO:<bouncedaddress@example.org>:
# host mail.example.org [...]: 550 sorry, no mailbox here by that name. (#5.7.17)
if($diag->{'Diagnostic-Code'} =~ m/mailfolder is full|Mail quota exceeded/){
$diag->{Guessed_MTA} = 'secureserver_dot_net';
}
}
return ( $list, $email, $diag );
}
sub parse_for_qmail {
my $self = shift;
# When I'm bored
# => http://cr.yp.to/proto/qsbmf.txt
# => http://mikoto.sapporo.iij.ad.jp/cgi-bin/cvsweb.cgi/fmlsrc/fml/lib/Mail/Bounce/Qmail.pm
my $entity = shift;
my ( $email, $list );
my $diag = {};
my @parts = $entity->parts;
my $state = 0;
my $pattern = 'Hi. This is the';
my $pattern2 = 'Your message has been enqueued by';
my $end_pattern = '--- Undelivered message follows ---';
my $end_pattern2 = '--- Below this line is a copy of the message.';
my $end_pattern3 = '--- Enclosed is a copy of the message.';
my $end_pattern4 = 'Your original message headers are included below.';
my ( $addr, $reason );
if ( !@parts ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /$pattern|$pattern2/;
$state = 0
if $data =~ /$end_pattern|$end_pattern2|$end_pattern3/;
if ( $state == 1 ) {
$data =~ s/\n/ /g;
if ( $data =~ /\t(\S+\@\S+)/ ) {
$email = $1;
}
elsif ( $data =~ /\<(\S+\@\S+)\>:\s*(.*)/ ) {
( $addr, $reason ) = ( $1, $2 );
$diag->{Action} = $reason;
my $status = '5.x.y';
if ( $data =~ /\#(\d+\.\d+\.\d+)/ ) {
$status = $1;
}
elsif ( $data =~ /\s+(\d{3})\s+/ ) {
my $code = $1;
$status = '5.x.y' if $code =~ /^5/;
$status = '4.x.y' if $code =~ /^4/;
$diag->{Status} = $status;
$diag->{Action} = $code;
}
$email = $addr;
$diag->{Guessed_MTA} = 'Qmail';
}
elsif ( $data =~ /(.*)\s\(\#(\d+\.\d+\.\d+)\)/ )
{ # Recipient's mailbox is full, message returned to sender. (#5.2.2)
$diag->{'Diagnostic-Code'} = $1;
$diag->{Status} = $2;
$diag->{Guessed_MTA} = 'Qmail';
}
elsif ( $data =~
/Remote host said:\s(\d{3})\s(\d+\.\d+\.\d+)\s\<(\S+\@\S+)\>(.*)/
)
{ # Remote host said: 550 5.1.1 <xxx@xxx>... Account is over quota. Please try again later..[EOF]
$diag->{Status} = $2;
$email = $3;
$diag->{'Diagnostic-Code'} = $4;
$diag->{Action} = 'failed'; #munging this for now...
$diag->{'Final-Recipient'} =
'rfc822'; #munging, again.
}
elsif ( $data =~
/Remote host said:\s(.*?)\s(\S+\@\S+)\s(.*)/ )
{
my $status;
$email ||= $2;
$status ||= $1;
$diag->{Status} ||= '5.x.y' if $status =~ /^5/;
$diag->{Status} ||= '4.x.y' if $status =~ /^4/;
$diag->{'Diagnostic-Code'} = $data;
$diag->{Guessed_MTA} = 'Qmail';
}
elsif ( $data =~ /Remote host said:\s(\d{3}.*)/ ) {
$diag->{'Diagnostic-Code'} = $1;
}
elsif ( $data =~ /\d{3}(\-|\s)\d+\.\d+\.\d+/ )
{ #550-5.1.1 550 5.1.1
if ( !exists( $diag->{'Diagnostic-Code'} ) ) {
$diag->{'Diagnostic-Code'} = '';
}
$diag->{'Diagnostic-Code'} .= $data;
}
elsif ( $data =~ /(.*)\s\(\#(\d+\.\d+\.\d+)\)/ ) {
$diag->{'Diagnostic-Code'} = $1;
$diag->{Status} = $2;
}
elsif ( $data =~ /(No User By That Name)/ ) {
$diag->{'Diagnostic-Code'} = $data;
$diag->{Status} = '5.x.y';
}
elsif (
$data =~ /(This address no longer accepts mail)/ )
{
$diag->{'Diagnostic-Code'} = $data;
}
elsif ( $data =~
/The mail system will continue delivery attempts/ )
{
$diag->{Guessed_MTA} = 'Qmail';
$diag->{'Diagnostic-Code'} = $data;
}
}
}
}
# Not Good:
# if(!defined($diag->{Action})){
# if($diag->{'Diagnostic-Code'} =~ m/The email account that you tried to reach does not exist/){
# $diag->{Action} = 'failed';
# }
# }
$list ||= $self->generic_body_parse_for_list($entity);
return ( $list, $email, $diag );
}
else {
# no body part to parse
return ( undef, undef, {} );
}
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_qmail($part);
if ( ($email) && ( keys %$diag ) ) {
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_exim {
my $self = shift;
my $entity = shift;
my ( $email, $list );
my $diag = {};
my $pattern =
'This message was created automatically by mail delivery software';
my $end_pattern = '------ This is a copy of the message';
my $end_pattern2 = '--- The header of the original message is following.';
my @parts = $entity->parts;
if ( !@parts ) {
if ( $entity->head->mime_type =~ /text/ ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
my $state = 0;
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /\Q$pattern/;
$state = 0 if $data =~ /$end_pattern|$end_pattern2/;
if ( $state == 1 ) {
$diag->{Guessed_MTA} = 'Exim';
$diag->{'Diagnostic-Code'} .= $data;
if ( $data =~ m/unknown local-part/ ) {
$diag->{'Status'} = '5.x.y';
}
# This should probably be moved to the Rules...
# And these are fairly genreal-purpose...
elsif ($data =~ m/This user doesn\'t have a (.*?) account|unknown user|This account has been disabled or discontinued|or discontinued \[\#102\]|User(.*?)does not exist|Invalid mailbox|mailbox unavailable|550\-5\.1\.1|550 5\.1\.1|Recipient does not exist here/) {
$diag->{'Status'} = '5.x.y';
}
else {
}
if ( $data =~ /(\S+\@\S+)/ ) {
$email = $1;
$email = strip($email);
$email =~ s/^\<|\>$//g if $email;
}
}
}
}
$IO->close;
}
if ( $diag->{'Diagnostic-Code'} =~ m/yahoo.com/ )
{ # actually, I guess if the email address is from yahoo...
$diag->{'Remote-MTA'} = 'yahoo.com';
}
if ( $diag->{Guessed_MTA} eq 'Exim' ) {
# well, looks like we got something...
if ( $entity->head->get( 'X-Failed-Recipients', 0 ) ) {
$email = $entity->head->get( 'X-Failed-Recipients', 0 );
$email =~ s/\n//;
$email = strip($email);
}
my $body = $entity->bodyhandle;
my $IO;
my $data = '';
my $copy = '';
my $state = 0;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
if ( $data =~ /$end_pattern|$end_pattern2/ ) {
$state = 1;
next;
}
if ( $state == 1 ) {
$copy .= $data;
}
}
}
$IO->close;
require MIME::Parser;
my $parser = new MIME::Parser;
$parser = optimize_mime_parser($parser);
my $orig_entity;
$copy =~ s/^\r|\n//;
$copy =~ s/^\r|\n//;
eval { $orig_entity = $parser->parse_data($copy) };
if ( !$@ ) {
$list = $self->list_in_list_headers($orig_entity);
}
else {
# print "errors! $@\n";
}
}
}
return ( $list, $email, $diag );
}
else {
return ( undef, undef, {} );
}
}
else {
# no body part to parse
return ( undef, undef, {} );
}
}
sub parse_for_f__king_exchange {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
my $list;
my $state = 0;
my $pattern = 'Your message';
if ( !@parts ) {
if ( $entity->head->mime_type eq 'text/plain' ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /$pattern/;
if ( $state == 1 ) {
$data =~ s/\n/ /g;
if ( $data =~ /\s{2}To:\s{6}(\S+\@\S+)/ ) {
$email = $1;
}
elsif ( $data =~
/(MSEXCH)(.*?)(Unknown\sRecipient|Unknown|)/ )
{ # I know, not perfect.
$diag->{Guessed_MTA} = 'Exchange';
$diag->{'Diagnostic-Code'} =
'Unknown Recipient';
}
else {
#...
#warn "nope: " . $data;
}
}
}
}
}
}
return ( $list, $email, $diag );
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_f__king_exchange($part);
if ( ($email) && ( keys %$diag ) ) {
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_novell { #like, really...
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
my $list;
my $state = 0;
my $pattern = qr/(A|The) message that you sent/;
my $end_pattern =
quotemeta('--- The header of the original message is following. ---');
if ( !@parts ) {
if ( $entity->head->mime_type eq 'text/plain' ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ m/$pattern/;
$state = 0 if $data =~ m/$end_pattern/;
if ( $state == 1 ) {
$data =~ s/\n/ /g;
if ( $data =~ /\s+(\S+\@\S+)\s\((.*?)\)/ ) {
$email = $1;
$diag->{'Diagnostic-Code'} = $2;
}
elsif ( $data =~ m/\<+(\S+\@\S+)\>+/ ) {
$email = $1;
}
else {
#...
}
}
}
}
}
}
return ( $list, $email, $diag );
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_novell($part);
if ( ($email) && ( keys %$diag ) ) {
$diag->{'X-Mailer'} =
$self->find_mailer_bounce_headers($entity);
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_gordano { # what... ever that is there...
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
my $list;
my $state = 0;
my $pattern = 'Your message to';
my $end_pattern = 'The message headers';
if ( !@parts ) {
if ( $entity->head->mime_type eq 'text/plain' ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /$pattern/;
$state = 0 if $data =~ /$end_pattern/;
if ( $state == 1 ) {
$data =~ s/\n/ /g;
if ( $data =~ /RCPT To:\<(\S+\@\S+)\>/ )
{ # RCPT To:<xxx@usnews.com>
$email = $1;
}
elsif ( $data =~ /(.*?)\s(\d+\.\d+\.\d+)\s(.*)/ )
{ # 550 5.1.1 No such mail drop defined.
$diag->{Status} = $2;
$diag->{'Diagnostic-Code'} = $3;
$diag->{'Final-Recipient'} = 'rfc822'; #munge;
$diag->{Action} = 'failed'; #munge;
}
else {
#...
}
}
}
}
}
}
return ( $list, $email, $diag );
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_gordano($part);
if ( ($email) && ( keys %$diag ) ) {
$diag->{'X-Mailer'} =
$self->find_mailer_bounce_headers($entity);
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_overquota_yahoo {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
my $list;
my $state = 0;
my $pattern = 'Message from yahoo.com.';
if ( !@parts ) {
if ( $entity->head->mime_type eq 'text/plain' ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /$pattern/;
$diag->{'Remote-MTA'} = 'yahoo.com';
if ( $state == 1 ) {
$data =~ s/\n/ /g; #what's up with that?
if ( $data =~ /\<(\S+\@\S+)\>\:/ ) {
$email = $1;
}
else {
if ( $data =~ m/(over quota)/ ) {
$diag->{'Diagnostic-Code'} = $data;
}
}
}
}
}
}
}
return ( $list, $email, $diag );
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_overquota_yahoo($part);
if ( ($email) && ( keys %$diag ) ) {
$diag->{'X-Mailer'} =
$self->find_mailer_bounce_headers($entity);
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_earthlink {
my $self = shift;
my $entity = shift;
my @parts = $entity->parts;
my $email;
my $diag = {};
my $list;
my $state = 0;
my $pattern = 'Sorry, unable to deliver your message to';
if ( !@parts ) {
if ( $entity->head->mime_type eq 'text/plain' ) {
my $body = $entity->bodyhandle;
my $IO;
if ($body) {
if ( $IO = $body->open("r") ) { # "r" for reading.
while ( defined( $_ = $IO->getline ) ) {
my $data = $_;
$state = 1 if $data =~ /$pattern/;
if ( $state == 1 ) {
$diag->{'Remote-MTA'} = 'Earthlink';
$data =~ s/\n/ /g; #what's up with that?
if ( $data =~ /(\d{3})\s(.*?)\s(\S+\@\S+)/ )
{ # 552 Quota violation for postmaster@example.com
$diag->{'Diagnostic-Code'} = $1 . ' ' . $2;
$email = $3;
}
}
}
}
}
}
return ( $list, $email, $diag );
}
else {
my $i;
for $i ( 0 .. $#parts ) {
my $part = $parts[$i];
( $list, $email, $diag ) = $self->parse_for_earthlink($part);
if ( ($email) && ( keys %$diag ) ) {
$diag->{'X-Mailer'} =
$self->find_mailer_bounce_headers($entity);
return ( $list, $email, $diag );
}
}
}
}
sub parse_for_windows_live {
my $self = shift;
my $entity = shift;
#
my $email;
my $diag = {};
my $list;
my $state = 0;
if ( defined($entity) ) {
my @parts = $entity->parts;
if ( $parts[0] ) {
my @parts0 = $parts[0]->parts;
if ( $parts0[0] ) {
if ( $parts0[0]->head->count('X-HmXmrOriginalRecipient') ) {
$email =
$parts0[0]->head->get( 'X-HmXmrOriginalRecipient', 0 );
$diag->{'Remote-MTA'} = 'Windows_Live';
return ( $list, $email, $diag );
}
}
}
}
}
sub parse_using_m_ds_bp {
my $self = shift;
eval { require Mail::DeliveryStatus::BounceParser; };
return ( undef, undef, {} ) if $@;
# else, let's get to work;
my $entity = shift;
my $message = $entity->as_string;
my $bounce = eval { Mail::DeliveryStatus::BounceParser->new($message); };
if ($@) {
# couldn't parse.
return ( undef, undef, {} ) if $@;
}
# examples:
# my @addresses = $bounce->addresses; # email address strings
# my @reports = $bounce->reports; # Mail::Header objects
# my $orig_message_id = $bounce->orig_message_id; # <ABCD.1234@mx.example.com>
# my $orig_message = $bounce->orig_message; # Mail::Internet object
return ( undef, undef, {} )
if $bounce->is_bounce != 1;
my ($report) = $bounce->reports;
return ( undef, undef, {} )
if !defined $report;
my $diag = {};
$diag->{'Message-Id'} = $report->get('orig_message_id')
if $report->get('orig_message_id');
$diag->{Action} = $report->get('action')
if $report->get('action');
$diag->{Status} = $report->get('status')
if $report->get('status');
$diag->{'Diagnostic-Code'} = $report->get('diagnostic-code')
if $report->get('diagnostic-code');
$diag->{'Final-Recipient'} = $report->get('final-recipient')
if $report->get('final-recipient');
# these aren't used particularily in Dada Mail, but let's play around with them...
$diag->{std_reason} = $report->get('std_reason')
if $report->get('std_reason');
$diag->{reason} = $report->get('reason')
if $report->get('reason');
$diag->{host} = $report->get('host')
if $report->get('host');
$diag->{smtp_code} = $report->get('smtp_code')
if $report->get('smtp_code');
my $email = $report->get('email') || undef;
return ( undef, $email, $diag );
}
sub DESTROY { }
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment