#!/usr/bin/perl use strict; #---------------------------------------------------------------------# # dada_bounce_handler.pl (Mystery Girl) # For instructions, see the pod of this file. try: # pod2text ./dada_bounce_handler.pl | less # # Or try online: # http://mojo.skazat.com/support/documentation/dada_bounce_handler.pl.html # #---------------------------------------------------------------------# # Required: #Change! the lib paths use lib qw( ../ ../DADA ../DADA/perllib /home/account/www/cgi-bin/dada /home/account/www/cgi-bin/dada/DADA /home/account/www/cgi-bin/dada/DADA/perllib /usr/local/lib/perl5/site_perl/5.8.0/mach /usr/local/lib/perl5/site_perl/5.8.0 /usr/local/lib/perl5/site_perl /usr/local/lib/perl5/5.8.0/BSDPAN /usr/local/lib/perl5/5.8.0/mach /usr/local/lib/perl5/5.8.0 ); my $Server = ''; my $Username = ''; my $Password = ''; use DADA::Config; #---------------------------------------------------------------------# my $Log = $LOGS . '/bounces.txt'; # leave blank to be sent to the list's list owner. my $Send_Messages_To = undef; my $MessagesAtOnce = 100; my $Rules = [ { qmail_delivery_delay_notification => { Examine => { Message_Fields => { Guessed_MTA => [qw(Qmail)], 'Diagnostic-Code_regex' => [qr/The mail system will continue delivery attempts/], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { #nothing! } } }, { over_quota => { Examine => { Message_Fields => { Action => [qw(failed Failed)], Status => [qw(5.2.2 4.2.2 5.0.0 5.1.1)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/552|exceeded storage allocation|over quota|mailbox full|disk quota exceeded|Mail quota exceeded|Quota violation/)] }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { hotmail_over_quota => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.2.3)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/larger than the current system limit/)] }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { over_quota_obscure_mta => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.0.0)], 'Final-Recipient_regex' => [(qr/LOCAL\;\<\>/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { over_quota_obscure_mta_two => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(4.2.2)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { yahoo_over_quota => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.0.0)], 'Remote-MTA_regex' => [(qr/yahoo.com/)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/over quota/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { yahoo_over_quota_two => { Examine => { Message_Fields => { 'Remote-MTA' => [qw(yahoo.com)], 'Diagnostic-Code_regex' => [(qr/over quota/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { qmail_over_quota => { Examine => { Message_Fields => { Guessed_MTA => [qw(Qmail)], Status => [qw(5.2.2 5.x.y)], 'Diagnostic-Code_regex' => [(qr/mailbox is full|Exceeded storage allocation|mailbox full|storage full/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { qmail_tmp_disabled => { Examine => { Message_Fields => { Guessed_MTA => [qw(Qmail)], Status => [qw(4.x.y)], 'Diagnostic-Code_regex' => [(qr/temporarily disabled/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { # TODO: # Not sure what to put here ATM. } } }, { delivery_time_expired => { Examine => { Message_Fields => { Status_regex => [qr(/4.4.7|delivery time expired/)], Action_regex => [qr(/Failed|failed/)], 'Final-Recipient_regex' => [qr(/822/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { # TODO: # Not sure what to put here ATM. } } }, { status_over_quota => { Examine => { Message_Fields => { Action => [qw(Failed failed)], #originally Failed Status =>[qr/mailbox full/], # like, wtf? }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { earthlink_over_quota => { Examine => { Message_Fields => { 'Diagnostic-Code_regex' => [qr/522|Quota violation/], 'Remote-MTA' => [qw(Earthlink)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { mail_list_owner => 'over_quota_message', } } }, { qmail_error_5dot5dot1 => { Examine => { Message_Fields => { Guessed_MTA => [qw(Qmail)], #Status => [qw(5.1.1)], 'Diagnostic-Code_regex' => [(qr/551/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', } } }, { qmail2_error_5dot5dot1 => { Examine => { Message_Fields => { Guessed_MTA => [qw(Qmail)], Status => [qw(5.1.1)], 'Diagnostic-Code_regex' => [(qr/no mailbox here by that name/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', } } }, { # AOL, apple.com, mac.com, altavista.net, pobox.com... delivery_error_550 => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.1.1)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/SMTP\; 550|550 MAILBOX NOT FOUND|550 5\.1\.1 unknown or illegal alias|User unknown|No such mail drop/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { # same as above, but without the Diagnostic_Code_regex. delivery_error_5dot5dot1_status => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.1.1)], 'Final-Recipient_regex' => [(qr/822/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { # Yahoo! delivery_error_554 => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.0.0)], 'Diagnostic-Code_regex' => [(qr/554 delivery error/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { qmail_user_unknown => { Examine => { Message_Fields => { Status => [qw(5.x.y)], Guessed_MTA => [qw(Qmail)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', } } }, { qmail_error_554 => { Examine => { Message_Fields => { 'Diagnostic-Code_regex' => [(qr/554/)], Guessed_MTA => [qw(Qmail)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { qmail_error_550 => { Examine => { Message_Fields => { 'Diagnostic-Code_regex' => [(qr/550/)], Guessed_MTA => [qw(Qmail)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { qmail_unknown_domain => { Examine => { Message_Fields => { Status => [qw(5.1.2)], Guessed_MTA => [qw(Qmail)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { # more info: # http://www.qmail.org/man/man1/bouncesaying.html qmail_bounce_saying => { Examine => { Message_Fields => { 'Diagnostic-Code_regex' => [qr/This address no longer accepts mail./], Guessed_MTA => [qw(Qmail)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', } } }, { exim_user_unknown => { Examine => { Message_Fields => { Status => [qw(5.x.y)], Guessed_MTA => [qw(Exim)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', }, } }, { exchange_user_unknown => { Examine => { Message_Fields => { #Status => [qw(5.x.y)], Guessed_MTA => [qw(Exchange)], 'Diagnostic-Code_regex' => [(qr/Unknown Recipient/)], }, Data => { Email => 'is_valid', List => 'is_valid', }, }, Action => { unsubscribe_bounced_email => 'from_list', } } }, #{ #novell_access_denied => { # Examine => { # Message_Fields => { # #Status => [qw(5.x.y)], # 'X-Mailer_regex' => [qw(Novell)], # 'Diagnostic-Code_regex' => [(qr/access denied/)], # }, # Data => { # Email => 'is_valid', # List => 'is_valid', # }, # # }, # Action => { # unsubscribe_bounced_email => 'from_list', # } # } #}, { # note! this should really make no sense, but I believe this is a bounce.... aol_user_unknown => { Examine => { Message_Fields => { Status => [qw(2.0.0)], Action => [qw(failed)], 'Reporting-MTA_regex' => [(qr/aol\.com/)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/250 OK/)], # no for real, everything's "OK" # }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', }, } }, { user_unknown_5dot3dot0_status => { Examine => { Message_Fields => { Action => [qw(failed)], Status => [qw(5.3.0)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/No such user|Addressee unknown/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', #mail_list_owner => 'user_unknown_message', } } }, { user_inactive => { Examine => { Message_Fields => { Status_regex => [(qr/5\.0\.0/)], Action => [qw(failed)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/user inactive|Bad destination|bad destination/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', }, } }, { postfix_5dot0dot0_error => { Examine => { Message_Fields => { Status => [qw(5.0.0)], Guessed_MTA => [qw(Postfix)], Action => [qw(failed)], #said_regex => [(qr/550\-Mailbox unknown/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', }, } }, { permanent_move_failure => { Examine => { Message_Fields => { Status => [qw(5.1.6)], Action => [qw(failed)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/551 not our customer|User unknown|ecipient no longer/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', }, } }, { unknown_domain => { Examine => { Message_Fields => { Status => [qw(5.1.2)], Action => [qw(failed)], 'Final-Recipient_regex' => [(qr/822/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { unsubscribe_bounced_email => 'from_list', }, } }, { relaying_denied => { Examine => { Message_Fields => { Status => [qw( 5.7.1)], Action => [qw(failed)], 'Final-Recipient_regex' => [(qr/822/)], 'Diagnostic-Code_regex' => [(qr/Relaying denied|relaying denied/)], }, Data => { Email => 'is_valid', List => 'is_valid', } }, Action => { # TODO # Again, not sure quite what to put here - will be silently ignored. # NOTE: Sometimes this message is sent by servers of spammers. }, } }, #{ # Supposively permanent error. #access_denied => { # Examine => { # Message_Fields => { # # Status => [qw(5.7.1)], # Action => [qw(failed)], # 'Final-Recipient_regex' => [(qr/822/)], # 'Diagnostic-Code_regex' => [(qr/ccess denied/)], # # }, # Data => { # Email => 'is_valid', # List => 'is_valid', # } # }, # Action => { # unsubscribe_bounced_email => 'from_list', # }, # } #}, { unknown_bounce_type => { Examine => { Data => { Email => 'is_valid', List => 'is_valid', }, }, Action => { mail_list_owner => 'unknown_bounce_type_message', #append_message_to_file => $Log, } } }, { email_not_found => { Examine => { Data => { Email => 'is_invalid', List => 'is_valid', }, }, Action => { mail_list_owner => 'email_not_found_message', } } }, #{ #who_knows => { # Examine => { # Message_Fields => {}, # }, # Action => {append_message_to_file => $Log}, # }, #}, ]; my $Bounce_Handler_Name = 'Mystery Girl'; my $Over_Quota_Subject = "Bounce Handler - warning user over quota"; my $Over_Quota_Message = qq{ Hello, This is $Bounce_Handler_Name, the bounce handler for $PROGRAM_NAME I received a message and it needs your attention. It seems that the user, [subscriber_email] is over their email quota. This is probably a * temporary * problem, but if the problem persists, you may want to unbsubscribe this address. I've attached what I was sent, if you're curious (or bored, what have you). You can remove this address from your list by clicking this link: [list_unsubscribe_link] Below is the nerdy diagnostic report: ----------------------------------------------------------------------- [report] [status_report] ----------------------------------------------------------------------- - $Bounce_Handler_Name }; my $User_Unknown_Subject = "Bounce Handler - warning user doesn't exist"; my $User_Unknown_Message = qq{ Hello, This is $Bounce_Handler_Name, the bounce handler for $PROGRAM_NAME I received a message and it needs your attention. It seems that the user, [subscriber_email] doesn't exist, was deleted from the system, kicked the big can, etc. This is probably a * permanent * problem and I suggest you unsubscribe the email address, but I'll let you have the last judgement. I've attached what I was sent, if you're curious (or bored, what have you). You can remove this address from your list by clicking this link: [list_unsubscribe_link] Below is the nerdy diagnostic report: ----------------------------------------------------------------------- [report] [status_report] ----------------------------------------------------------------------- - $Bounce_Handler_Name }; my $Email_Not_Found_Subject = "Bounce Handler - warning"; my $Email_Not_Found_Message = qq{ Hello, This is $Bounce_Handler_Name, the bounce handler for $PROGRAM_NAME I received a message and it needs your attention. The message was bounced, but I cannot find the email associated with the bounce. Either I can't understand the bounced report, or there's a bug in my sourcecode. Internet time is lighting fast and I fear I may already be reduced to wasted 1's and 0's, *sigh*. I've attached what I was sent, if you're curious (or bored, what have you). Below is the nerdy diagnostic report: ----------------------------------------------------------------------- [report] [status_report] ----------------------------------------------------------------------- - $Bounce_Handler_Name }; my $Email_Unknown_Bounce_Type_Subject = "Bounce Handler - warning"; my $Email_Unknown_Bounce_Type_Message = qq{ Hello, This is $Bounce_Handler_Name, the bounce handler for $PROGRAM_NAME I received a message and it needs your attention. The message was bounced, but I dont know for what reason. Either I can't understand the bounced report, or there's a bug in my sourcecode. Internet time is lighting fast and I fear I may already be reduced to wasted 1's and 0's, *sigh*. I've attached what I was sent, if you're curious (or bored, what have you). You can remove this address from your list by clicking this link: [list_unsubscribe_link] Below is the nerdy diagnostic report: ----------------------------------------------------------------------- [report] [status_report] ----------------------------------------------------------------------- - $Bounce_Handler_Name }; #---------------------------------------------------------------------# # Nothing else to be configured. # my $App_Version = '1.5'; use DADA::App::Guts; use DADA::Mail::Send; use DADA::MailingList::Subscribers; use DADA::MailingList::Settings; use DADA::Template::HTML; use CGI; my $q = new CGI; my %Global_Template_Options = ( #debug => 1, path => [$TEMPLATES], die_on_bad_params => 0, ); use Getopt::Long; use Mail::Verp; use MIME::Parser; use MIME::Entity; use Net::POP3; my $parser = new MIME::Parser; $parser = optimize_mime_parser($parser); my $Remove_List = {}; my $Bounce_History = {}; my $Rules_To_Carry_Out = []; my $debug = 0; my $help = 0; my $test; my $server; my $username; my $password; my $verbose = 0; my $log; my $Have_Log = 0; my $messages = 0; my $version; my $list; my $admin_list; my $root_login; GetOptions("help" => \$help, "test=s" => \$test, "server=s" => \$server, "username=s" => \$username, "password=s" => \$password, "verbose" => \$verbose, "log=s" => \$log, "messages=i" => \$messages, "version" => \$version, ); &main; sub main { if(!$ENV{GATEWAY_INTERFACE}){ &cl_main(); }else{ &cgi_main(); } } sub cgi_main { ($admin_list, $root_login) = check_list_security(-cgi_obj => $q, -Function => 'dada_bounce_handler'); $list = $admin_list; my $ls = DADA::MailingList::Settings->new(-List => $list); my $li = $ls->get(); my $flavor = $q->param('flavor') || 'cgi_default'; my %Mode = ( 'cgi_default' => \&cgi_default, 'cgi_parse_bounce' => \&cgi_parse_bounce, ); if(exists($Mode{$flavor})) { $Mode{$flavor}->(); #call the correct subroutine }else{ &cgi_default; } } sub cgi_default { require HTML::Template; my $ls = DADA::MailingList::Settings->new(-List => $list); my $li = $ls->get(); my $tmpl = default_cgi_template(); my @amount = (1,2,3,4,5,6,7,8,9,10,25,50,100,150,200, 250,300,350, 400,450, 500,550,600,650,700, 750,800,850,900,950,1000 ); my $parse_amount_widget = $q->popup_menu(-name => 'parse_amount', -id => 'parse_amount', '-values' => [@amount], -default => $MessagesAtOnce, -label => '', ); print(admin_html_header( -Title => "Bounce Handling", -List => $list, -Form => 0, -Root_Login => $root_login, )); my $template = HTML::Template->new(%Global_Template_Options, scalarref => \$tmpl, ); $template->param( Username => $Username ? $Username : "Not Set!", Server => $Server ? $Server : "Not Set!", self_url => $q->url, parse_amount_widget => $parse_amount_widget, send_via_smtp => $li->{send_via_smtp}, add_sendmail_f_flag => $li->{add_sendmail_f_flag}, print_return_path_header => $li->{print_return_path_header}, set_smtp_sender => $li->{set_smtp_sender}, admin_email => $li->{admin_email},, list_owner_email => $li->{list_owner_email}, MAIL_SETTINGS => $MAIL_SETTINGS, ); print $template->output(); print admin_html_footer(-Form => 0, -List => $list, ); } sub cgi_parse_bounce { print(admin_html_header( -Title => "Parsing Bounces...", -List => $list, -Form => 0, -Root_Login => $root_login )); $test = $q->param('test') if $q->param('test'); $MessagesAtOnce ||= $q->param('parse_amount') if $q->param('parse_amount'); $verbose = 1; print '
'; cl_main(); print ''; print ''; print admin_html_footer(-Form => 0, -List => $list, ); } sub cl_main { &init; if($help == 1){ show_help(); }elsif(defined($test) && $test ne 'bounces'){ test_script(); }elsif(defined($version)){ &version(); } print "Making POP3 Connection...\n" if $verbose; my $pop = Net::POP3->new($Server) or warn "Connection to '$Server' wasn't successful: $!"; my $messagecount; eval {require Digest::MD5}; if(!$@){ print "Trying secure login...\n" if $verbose; $messagecount = $pop->apop($Username,$Password); if(!$messagecount){ print "Hmm, secure login failed, switching to regular login...\n" if $verbose; $pop = Net::POP3->new($Server) or warn "Connection to '$Server' wasn't successful: $!"; $messagecount = $pop->login($Username,$Password); } }else{ $messagecount = $pop->login($Username,$Password); } if(($messagecount ne '') && ($messagecount >= 0)){ print "POP3 Connection worked!\n" if $verbose; if($verbose){ print "Mailbox is empty, no bounces to handle.\n\n" if $messagecount == 0; } my $i; my $end = $messagecount; $end = $MessagesAtOnce if $MessagesAtOnce < $end; for($i = 1; $i <= $end; $i++){ my $message_array_ref = []; $message_array_ref = $pop->get($i); my $m_message; foreach(@$message_array_ref){ $m_message .= $_; } parse_bounce(-message => $m_message); } for($i = 1; $i <= $end; $i++){ $pop->delete($i) if ! $debug; } } $pop->quit(); remove_bounces($Remove_List) if ! $debug; &close_log; } sub init { $Server = $server if $server; $Username = $username if $username; $Password = $password if $password; $Log = $log if $log; $MessagesAtOnce = $messages if $messages > 0; if($test){ $debug = 1 if $test eq 'bounces'; } $verbose = 1 if $debug == 1; # init a hashref of hashrefs # for unsub optimization my @a_Lists = DADA::App::Guts::available_lists(); foreach(@a_Lists){ $Remove_List->{$_} = {}; } open_log($Log); } sub parse_bounce { my %args = (-message => undef, @_); my $message = $args{-message}; my $email = ''; my $list = ''; my $diagnostics = {}; my $entity; eval { $entity = $parser->parse_data($message) }; if(!$entity){ warn "No MIME entity found, this message could be garbage, skipping"; }else{ if($verbose){ print '-' x 72 . "\n"; $entity->dump_skeleton; print '-' x 72 . "\n"; } $email = find_verp($entity); my ($gp_list, $gp_email, $gp_diagnostics) = generic_parse($entity); $list = $gp_list if $gp_list; $email ||= $gp_email; $diagnostics = $gp_diagnostics if $gp_diagnostics; if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($qmail_list, $qmail_email, $qmail_diagnostics) = parse_for_qmail($entity); $list ||= $qmail_list; $email ||= $qmail_email; %{$diagnostics} = (%{$diagnostics}, %{$qmail_diagnostics}) if $qmail_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($exim_list, $exim_email, $exim_diagnostics) = parse_for_exim($entity); $list ||= $exim_list; $email ||= $exim_email; %{$diagnostics} = (%{$diagnostics}, %{$exim_diagnostics}) if $exim_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($ms_list, $ms_email, $ms_diagnostics) = parse_for_f__king_exchange($entity); $list ||= $ms_list; $email ||= $ms_email; %{$diagnostics} = (%{$diagnostics}, %{$ms_diagnostics}) if $ms_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($nv_list, $nv_email, $nv_diagnostics) = parse_for_novell($entity); $list ||= $nv_list; $email ||= $nv_email; %{$diagnostics} = (%{$diagnostics}, %{$nv_diagnostics}) if $nv_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($g_list, $g_email, $g_diagnostics) = parse_for_gordano($entity); $list ||= $g_list; $email ||= $g_email; %{$diagnostics} = (%{$diagnostics}, %{$g_diagnostics}) if $g_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($y_list, $y_email, $y_diagnostics) = parse_for_overquota_yahoo($entity); $list ||= $y_list; $email ||= $y_email; %{$diagnostics} = (%{$diagnostics}, %{$y_diagnostics}) if $y_diagnostics; } if((!$list) || (!$email) || !keys %{$diagnostics}){ my ($el_list, $el_email, $el_diagnostics) = parse_for_earthlink($entity); $list ||= $el_list; $email ||= $el_email; %{$diagnostics} = (%{$diagnostics}, %{$el_diagnostics}) if $el_diagnostics; } chomp($email) if $email; #small hack, turns, %2 into, '-' $list =~ s/\%2d/\-/g; $list = trim($list); if(!$diagnostics->{'Message-Id'}){ $diagnostics->{'Message-Id'} = find_message_id_in_headers($entity); if(!$diagnostics->{'Message-Id'}){ $diagnostics->{'Message-Id'} = 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 } #print $entity->as_string; print generate_nerd_report($list, $email, $diagnostics) if $verbose; my $rule = find_rule_to_use($list, $email, $diagnostics); print "\nUsing Rule: $rule\n\n" if $verbose; if(!bounce_from_me($entity)){ if(!$debug){ #push(@$Rules_To_Carry_Out, [$rule, $list, $email, $diagnostics, $message]); carry_out_rule($rule, $list, $email, $diagnostics, $message); } }else{ warn "Whoop! Bounced message was sent by myself... kinda going to ignore and delete..."; } } #sleep(1); } sub bounce_from_me(){ my $entity = shift; my $bh = $entity->head->get('X-BounceHandler', 0); $bh =~ s/\n//g; $bh = trim($bh); $bh eq $Bounce_Handler_Name ? return 1 : return 0; } sub carry_out_rule { my ($title, $list, $email, $diagnostics, $message) = @_; my $actions = {}; my $i = 0; foreach my $rule(@$Rules){ if((keys %$rule)[0] eq $title){ $actions = $Rules->[$i]->{$title}->{Action}; # wooo that was fun. } $i++; } foreach my $action(keys %$actions){ if($action eq 'unsubscribe_bounced_email'){ unsubscribe_bounced_email($list, $email, $diagnostics, $actions->{$action}); }elsif($action eq 'mail_list_owner'){ mail_list_owner($list, $email, $diagnostics, $actions->{$action}, $message); }elsif($action eq 'append_message_to_file'){ append_message_to_file($list, $email, $diagnostics, $actions->{$action}, $message); }elsif($action eq 'default'){ default_action($list, $email, $diagnostics, $actions->{$action}, $message); }else{ warn "unknown rule trying to be carried out, ignoring"; } log_action($list, $email, $diagnostics, "$action $actions->{$action}"); } } sub default_action { warn "Parsing... really didn't work. Ignoring and deleting bounce."; } sub unsubscribe_bounced_email { my ($list, $email, $diagnostics, $action) = @_; my @delete_list; if($action eq 'from_list'){ $delete_list[0] = $list; }elsif($action eq 'from_all_lists'){ @delete_list = DADA::App::Guts::available_lists(); }else{ warn "unknown action: '$action', no unsubscription will be made from this email!"; } $Bounce_History->{$list}->{$email} = [$diagnostics, $action]; foreach(@delete_list){ $Remove_List->{$_}->{$email} = 1; print "$email to be deleted off of: '$_'\n" if $verbose; } } sub mail_list_owner { my ($list, $email, $diagnostics, $action, $message) = @_; my $Body; my $Subject; if($action eq 'over_quota_message'){ $Subject = $Over_Quota_Subject; $Body = $Over_Quota_Message; }elsif($action eq 'user_unknown_message'){ $Subject = $User_Unknown_Subject; $Body = $User_Unknown_Message; }elsif($action eq 'email_not_found_message'){ $Subject = $Email_Not_Found_Subject; $Body = $Email_Not_Found_Message; }elsif($action eq 'unknown_bounce_type_message'){ $Subject = $Email_Unknown_Bounce_Type_Subject; $Body = $Email_Unknown_Bounce_Type_Message; }else{ warn "There's been a misconfiguration somewhere, $Bounce_Handler_Name is about to die..., "; warn "AARRGGGGH!"; } my $ls = DADA::MailingList::Settings->new(-List => $list); my $lh = DADA::MailingList::Subscribers->new(-List => $list); my $li = $ls->get; my ($sub_status, $sub_errors) = $lh->unsubscription_check(-Email => $email); # A little sanity check... if($email eq $li->{admin_email}){ warn "Bounce is from bounce handler, stopping '$action'"; }elsif(($sub_errors->{not_subscribed} == 1) && (($action ne 'user_unknown_message') || ($action ne 'over_quota_message')|| ($action ne 'email_not_found_message')) ){ warn "parsed message contains an email ($email) that's not even subscribed. No reason to tell list owner"; }else{ my $report = generate_nerd_report($list, $email, $diagnostics); my $status_report = rfc1893_status($diagnostics->{Status}); my $unsub_link = DADA::App::Guts::unsubscribe_link(-email => $email, -list => $list, -make_pin => 1); $Body =~ s/\[report\]/$report/i; $Body =~ s/\[status_report\]/$status_report/i; $Body =~ s/\[list_unsubscribe_link\]/$unsub_link/g; #} $Body = DADA::App::Guts::interpolate_string(-String => $Body, -List_Db_Ref => $li, -Email => $email); my $mh = DADA::Mail::Send->new($li); my $to = $Send_Messages_To || $li->{list_owner_email}; my $msg = MIME::Entity->build(To => $to, From => $li->{admin_email}, Subject => $Subject, Type => 'multipart/mixed', ); $msg->attach(Type => 'text/plain', Disposition => 'inline', Data => $Body, Encoding => $li->{plaintext_encoding} ); $msg->attach(Type => 'message/rfc822', Disposition => "attachment", Data => $message); $mh->send( # Trust me on these :) $mh->return_headers($msg->stringify_header), 'X-BounceHandler' => $Bounce_Handler_Name, Body => $msg->stringify_body ); print "mail for: $action is on its way!\n" if $verbose; } } sub append_message_to_file { my ($list, $email, $diagnostics, $action, $message) = @_; print "Appending Email to '$action'\n" if $verbose; $action = DADA::App::Guts::make_safer($action); open(APPENDLOG, ">>$action") or die $!; chmod($FILE_CHMOD, $action); print APPENDLOG $message; close(APPENDLOG) or die $!; } sub generate_nerd_report { my ($list, $email, $diagnostics) = @_; my $report; $report = "List: $list\nEmail: $email\n\n"; foreach(keys %$diagnostics){ $report .= "$_: " . $diagnostics->{$_} . "\n"; } return $report; } sub find_rule_to_use { my ($list, $email, $diagnostics) = @_; my $ir = 0; RULES: for ($ir = 0; $ir <= $#$Rules; $ir++){ my $rule = $Rules->[$ir]; my $title = (keys %$rule)[0]; next if $title eq 'default'; my $match = {}; my $examine = $Rules->[$ir]->{$title}->{Examine}; my $message_fields = $examine->{Message_Fields}; my %ThingsToMatch; foreach my $m_field(keys %$message_fields){ my $is_regex = 0; my $real_field = $m_field; $ThingsToMatch{$m_field} = 0; if($m_field =~ m/_regex$/){ $is_regex = 1; $real_field = $m_field; $real_field =~ s/_regex$//; } MESSAGEFIELD: foreach my $pos_match(@{$message_fields->{$m_field}}){ if($is_regex == 1){ if($diagnostics->{$real_field} =~ m/$pos_match/){ $ThingsToMatch{$m_field} = 1; next MESSAGEFIELD; } }else{ if($diagnostics->{$real_field} eq $pos_match){ $ThingsToMatch{$m_field} = 1; next MESSAGEFIELD; } } } } # If we miss one, the rule doesn't work, # All or nothin', just like life. foreach(keys %ThingsToMatch){ if($ThingsToMatch{$_} == 0){ next RULES; } } if(keys %{$examine->{Data}}){ if($examine->{Data}->{Email}){ my $valid_email = 0; my $email_match; if(DADA::App::Guts::check_for_valid_email($email) == 0){ $valid_email = 1; } if((($examine->{Data}->{Email} eq 'is_valid') && ($valid_email == 1)) || (($examine->{Data}->{Email} eq 'is_invalid') && ($valid_email == 0))){ $email_match = 1; }else{ next RULES; } } if($examine->{Data}->{List}){ my $valid_list = 0; my $list_match; if(DADA::App::Guts::check_if_list_exists(-List=>$list) != 0){ $valid_list = 1; } if((($examine->{Data}->{List} eq 'is_valid') && ($valid_list == 1)) || (($examine->{Data}->{List} eq 'is_invalid') && ($valid_list == 0))){ $list_match = 1; }else{ next RULES; } } } return $title; } return 'default'; } sub find_verp { my $entity = shift; my $mv = Mail::Verp->new; $mv->separator($MAIL_VERP_SEPARATOR); my ($sender, $recipient) = $mv->decode($entity->head->get('To', 0)); return $recipient || undef; } sub generic_parse { my $entity = shift; my ($email, $list); my $diag = {}; ($email, $diag) = find_delivery_status($entity); $list = find_list_in_list_headers($entity); $list ||= 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, $diag); } sub find_delivery_status { my $entity = shift; my @parts = $entity->parts; my $email; my $diag = {}; if(!@parts){ if($entity->head->mime_type eq 'message/delivery-status'){ ($email, $diag) = generic_delivery_status_parse($entity); return ($email, $diag); } }else{ my $i; foreach $i (0 .. $#parts) { my $part = $parts[$i]; ($email, $diag) = find_delivery_status($part); if(($email) && (keys %$diag)){ return ($email, $diag); } } } } sub find_mailer_bounce_headers { 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 $entity = shift; my @parts = $entity->parts; my $list; if($entity->head->mime_type eq 'message/rfc822'){ my $orig_msg_copy = $parts[0]; my $list_header = $orig_msg_copy->head->get('List', 0); $list = $list_header if $list_header !~ /\:/; if(!$list){ my $list_id = $orig_msg_copy->head->get('List-ID', 0); if($list_id =~ /\<(.*?)\./){ $list = $1 if $1 !~ /\:/; } } if(!$list){ my $list_sub = $orig_msg_copy->head->get('List-Subscribe', 0); if($list_sub =~ /l\=(.*?)\>/){ $list = $1; } } return $list; }else{ my $i; foreach $i (0 .. $#parts) { my $part = $parts[$i]; $list = find_list_in_list_headers($part); return $list if $list; } } } sub find_message_id_in_headers { my $entity = shift; my @parts = $entity->parts; my $m_id; if($entity->head->mime_type eq 'message/rfc822'){ my $orig_msg_copy = $parts[0]; $m_id = $orig_msg_copy->head->get('Message-ID', 0); chomp($m_id); return $m_id; }else{ my $i; foreach $i (0 .. $#parts) { my $part = $parts[$i]; $m_id = find_message_id_in_headers($part); return $m_id if $m_id; } } } sub find_message_id_in_body { 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; return $m_id; }else{ return undef; } } sub generic_delivery_status_parse { 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; foreach(keys %$diag){ $diag->{$_} = DADA::App::Guts::strip($diag->{$_}); } return ($email, $diag); } sub generic_body_parse_for_list { my $entity = shift; my $list; my @parts = $entity->parts; if(!@parts){ $list = find_list_from_unsub_list($entity); return $list if $list; }else{ my $i; foreach $i (0 .. $#parts) { my $part = $parts[$i]; $list = generic_body_parse_for_list($part); if($list){ return $list; } } } } sub find_list_from_unsub_list { 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($_); if($_ =~ 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 parse_for_qmail { # 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
Note! Parsing Bounces will parse bounces for all lists.
|
Your Bounce Handler POP3 Username: |
|
|
On: |
|
Mailing is being sent via: SMTP.
The SMTP Sender is being set to: . This should be the same address as the above Bounce Handler POP3 Username
The SMTP Sender has not be explicitly set. Bounces may go to the list owner () or to a server default address.
Mailing is being sent via the sendmail command '-f' flagged added:
}; } END { $parser->filer->purge; } =pod =head1 NAME Mystery Girl - A Bounce Handler For Dada Mail =head1 DESCRIPTION Mystery Girl intelligently handles bounces from Dada Mail list messages. Each message is first B-f