diff --git a/README.txt b/README.txt
index 1cf6991..c6598a4 100644
--- a/README.txt
+++ b/README.txt
@@ -2,14 +2,30 @@ PHP Bounce Handler
INSTALL
-------
+Download php.bouncehandler.vX.Y.zip (where X.Y match latest version)
Upload to a website, and open testdriver1.php in a web browser
-for normal operation only
- bounce_driver_class.php and bounce_statuscodes.php are required
+for normal operation only bounce_driver_class.php and bounce_statuscodes.php are required
RELEASE HISTORY
---------------
+v7.8 VP August 10, 2015
+- minor fixes to eliminate notices
+
+v7.7 VP April 10, 2015
+- fixed incorrect original letter discovery in some cases
+
+v7.6 VP April 9, 2015
+- added discovery of original letters from weird FBLs
+
+v7.5 VP April 6, 2015
+
+- now it's stable
+- fixed tons of errors - warnings, notices etc...
+- some code optimizations
+- get_the_facts() result now looks slightly like bouncehammer's one
+
v7.4 SB June 19, 2014
- make auto-responder identification table driven
diff --git a/bounce_driver.class.php b/bounce_driver.class.php
index d9a140b..e32a9a3 100644
--- a/bounce_driver.class.php
+++ b/bounce_driver.class.php
@@ -1,14 +1,14 @@
@@ -64,74 +67,98 @@ class BounceHandler{
public $feedback_type = "";
public $x_header_beacon_1 = "";
public $x_header_beacon_2 = "";
-
+
// these accessors are useful only for FBL's
// or if the output array has only one index
public $action = "";
public $status = "";
public $subject = "";
public $recipient = "";
+ public $original_letter = '';
+ public $original_letter_header = [];
+ public $original_letter_body = '';
// the raw data set, a multiArray
public $output = array();
-
+
/**** INSTANTIATION *******************************************************/
- public function __construct(){
- $this->output[0]['action'] = "";
- $this->output[0]['status'] = "";
- $this->output[0]['recipient'] = "";
+ public function __construct() {
require('bounce_responses.php');
$this->bouncelist = $bouncelist;
$this->autorespondlist = $autorespondlist;
$this->bouncesubj = $bouncesubj;
}
-
+
/**** METHODS *************************************************************/
// this is the most commonly used public method
// quick and dirty
// useage: $multiArray = $this->get_the_facts($strEmail);
- public function parse_email($eml){
+ public function parse_email($eml) {
return $this->get_the_facts($eml);
}
- public function get_the_facts($eml){
+
+ public function get_the_facts($eml) {
// fluff up the email
$bounce = $this->init_bouncehandler($eml);
- if (strpos($bounce, "\r\n\r\n") !== FALSE)
- list($head, $body) = preg_split("/\r\n\r\n/", $bounce, 2);
- else
- list($head, $body) = array($bounce, '');
+ list($head, $body) = $this->splitHeadAndBody($bounce);
+
$this->head_hash = $this->parse_head($head);
// parse the email into data structures
$boundary = isset($this->head_hash['Content-type']['boundary']) ? $this->head_hash['Content-type']['boundary'] : '';
$mime_sections = $this->parse_body_into_mime_sections($body, $boundary);
- $this->body_hash = split("\r\n", $body);
- $this->first_body_hash = isset($mime_sections['first_body_part']) ? $this->parse_head($mime_sections['first_body_part']) : array();
+ // recover the original letter
+ if (!empty($mime_sections['returned_message_body_part'])) {
+ list($ct, $this->original_letter) = $this->splitHeadAndBody($mime_sections['returned_message_body_part']);
+ } elseif (strpos($body, '------ This is a copy of your message, including all the headers. ------') !== false) {
+ list($_, $this->original_letter) = preg_split("/\r\n\r\n------ This is a copy of your message, including all the headers. ------\r\n\r\n/", $body, 2);
+ } elseif (strpos($body, '------ This is a copy of the message, including all the headers. ------') !== false) {
+ list($_, $this->original_letter) = preg_split("/\r\n\r\n------ This is a copy of the message, including all the headers. ------\r\n\r\n/", $body, 2);
+ } else {
+ $letters = preg_split("/\nReturn-path:[^\n]*\n/i", $bounce, 3, PREG_SPLIT_NO_EMPTY);
+ if (!empty($letters[2])) {
+ $this->original_letter = $letters[2];
+ }
+ }
+ if (!empty($this->original_letter)) {
+ list($original_head, $this->original_letter_body) = $this->splitHeadAndBody($this->original_letter);
+ $this->original_letter_header = $this->parse_head($original_head);
+ }
+
+ $this->body_hash = explode("\r\n", $body);
+ $this->first_body_hash = isset($mime_sections['first_body_part']) ? $this->parse_head($mime_sections['first_body_part']) : [];
$this->looks_like_a_bounce = $this->is_a_bounce();
$this->looks_like_an_FBL = $this->is_an_ARF();
$this->looks_like_an_autoresponse = !$this->looks_like_a_bounce && !$this->looks_like_an_FBL && $this->is_an_autoresponse();
-
- /* If you are trying to save processing power, and don't care much
- * about accuracy then uncomment this statement in order to skip the
- * heroic text parsing below.
- */
- //if(!$this->looks_like_a_bounce && !$this->looks_like_an_FBL && !$this->looks_like_an_autoresponse){
- // return "unknown";
- //}
-
/*** now we try all our weird text parsing methods (E-mail is weird!) ******************************/
// is it a Feedback Loop, in Abuse Feedback Reporting Format (ARF)?
// http://en.wikipedia.org/wiki/Abuse_Reporting_Format#Abuse_Feedback_Reporting_Format_.28ARF.29
- if($this->looks_like_an_FBL){
+ if ($this->looks_like_an_FBL) {
$this->output[0]['action'] = 'failed';
- $this->output[0]['status'] = "5.7.1";
+ $this->output[0]['deliverystatus'] = "5.7.1";
$this->subject = trim(str_ireplace("Fw:", "", $this->head_hash['Subject']));
- if ($this->is_hotmail_fbl === true){
+
+ // Sometimes one mail server uses senderscore.net for fbl
+ // So original message may be hidden in attachment
+ if (!empty($this->original_letter) && !empty($this->original_letter_header)) {
+ $ol_boundary = isset($this->original_letter_header['Content-type']['boundary']) ? $this->original_letter_header['Content-type']['boundary'] : '';
+ if (!empty($ol_boundary)) {
+ $ol_mime_sections = $this->parse_body_into_mime_sections($this->original_letter_body, $ol_boundary);
+ $ol_mpbp = isset($ol_mime_sections['machine_parsable_body_part']) ? $this->parse_head($ol_mime_sections['machine_parsable_body_part']) : [];
+ if (!empty($ol_mpbp['Content-type']['type']) && $ol_mpbp['Content-type']['type'] == 'message/feedback-report' && !empty($ol_mime_sections['returned_message_body_part'])) {
+ list($ct, $this->original_letter) = $this->splitHeadAndBody($ol_mime_sections['returned_message_body_part']);
+ list($original_head, $this->original_letter_body) = $this->splitHeadAndBody($this->original_letter);
+ $this->original_letter_header = $this->parse_head($original_head);
+ }
+ }
+ }
+
+ if ($this->is_hotmail_fbl === true) {
// fill in the fbl_hash with sensible values
$this->fbl_hash['Source-ip'] = '';
$this->fbl_hash['Original-mail-from'] = '';
@@ -140,138 +167,131 @@ public function get_the_facts($eml){
$this->fbl_hash['Content-disposition'] = 'inline';
$this->fbl_hash['Content-type'] = 'message/feedback-report';
$this->fbl_hash['User-agent'] = 'Hotmail FBL';
- if (isset($this->first_body_hash['Date']))
- $this->fbl_hash['Received-date'] = $this->first_body_hash['Date'];
- if (isset($this->head_hash['Subject']) && preg_match('/complaint about message from ([0-9.]+)/', $this->head_hash['Subject'], $matches))
- $this->fbl_hash['Source-ip'] = $matches[1];
- if (!empty($this->recipient))
- $this->fbl_hash['Original-rcpt-to'] = $this->recipient;
- if (isset($this->first_body_hash['X-sid-pra']))
- $this->fbl_hash['Original-mail-from'] = $this->first_body_hash['X-sid-pra'];
- }
- else {
+
+ if (isset($this->first_body_hash['Date'])) $this->fbl_hash['Received-date'] = $this->first_body_hash['Date'];
+ if (isset($this->head_hash['Subject']) && preg_match('/complaint about message from ([0-9.]+)/', $this->head_hash['Subject'], $matches)) $this->fbl_hash['Source-ip'] = $matches[1];
+ if (!empty($this->recipient)) $this->fbl_hash['Original-rcpt-to'] = $this->recipient;
+ if (isset($this->first_body_hash['X-sid-pra'])) $this->fbl_hash['Original-mail-from'] = $this->first_body_hash['X-sid-pra'];
+ } else {
$this->fbl_hash = $this->standard_parser($mime_sections['machine_parsable_body_part']);
$returnedhash = $this->standard_parser($mime_sections['returned_message_body_part']);
- if (!empty($returnedhash['Return-path']))
+
+ if (!empty($returnedhash['Return-path'])) {
$this->fbl_hash['Original-mail-from'] = $returnedhash['Return-path'];
- elseif (empty($this->fbl_hash['Original-mail-from']) && !empty($returnedhash['From']))
+ } elseif (empty($this->fbl_hash['Original-mail-from']) && !empty($returnedhash['From'])) {
$this->fbl_hash['Original-mail-from'] = $returnedhash['From'];
- if (empty($this->fbl_hash['Original-rcpt-to']) && !empty($this->fbl_hash['Removal-recipient']) )
+ }
+
+ if (empty($this->fbl_hash['Original-rcpt-to']) && !empty($this->fbl_hash['Removal-recipient'])) {
$this->fbl_hash['Original-rcpt-to'] = $this->fbl_hash['Removal-recipient'];
- elseif (isset($returnedhash['To']))
+ } elseif (isset($returnedhash['To'])) {
$this->fbl_hash['Original-rcpt-to'] = $returnedhash['To'];
- else
+ } else {
$this->fbl_hash['Original-rcpt-to'] = '';
- if (!isset($this->fbl_hash['Source-ip']))
- if (!empty($returnedhash['X-originating-ip']))
+ }
+
+ if (!isset($this->fbl_hash['Source-ip'])) {
+ if (!empty($returnedhash['X-originating-ip'])) {
$this->fbl_hash['Source-ip'] = $this->strip_angle_brackets($returnedhash['X-originating-ip']);
- else
+ } else {
$this->fbl_hash['Source-ip'] = '';
+ }
+ }
}
+
// warning, some servers will remove the name of the original intended recipient from the FBL report,
// replacing it with redacted@rcpt-hostname.com, making it utterly useless, of course (unless you used a web-beacon).
// here we try our best to give you the actual intended recipient, if possible.
- if (preg_match('/Undisclosed|redacted/i', $this->fbl_hash['Original-rcpt-to']) && isset($this->fbl_hash['Removal-recipient']) ) {
+ if (preg_match('/Undisclosed|redacted/i', $this->fbl_hash['Original-rcpt-to']) && isset($this->fbl_hash['Removal-recipient'])) {
$this->fbl_hash['Original-rcpt-to'] = @$this->fbl_hash['Removal-recipient'];
}
- if (empty($this->fbl_hash['Received-date']) && !empty($this->fbl_hash[@'Arrival-date']) ) {
- $this->fbl_hash['Received-date'] = @$this->fbl_hash['Arrival-date'];
+
+ if (empty($this->fbl_hash['Received-date']) && !empty($this->fbl_hash['Arrival-date'])) {
+ $this->fbl_hash['Received-date'] = $this->fbl_hash['Arrival-date'];
}
$this->fbl_hash['Original-mail-from'] = $this->strip_angle_brackets(@$this->fbl_hash['Original-mail-from']);
- $this->fbl_hash['Original-rcpt-to'] = $this->strip_angle_brackets(@$this->fbl_hash['Original-rcpt-to']);
+ $this->fbl_hash['Original-rcpt-to'] = $this->strip_angle_brackets(@$this->fbl_hash['Original-rcpt-to']);
$this->output[0]['recipient'] = $this->fbl_hash['Original-rcpt-to'];
- }
-
-#??? else if (preg_match("/auto.{0,20}reply|vacation|(out|away|on holiday).*office/i", $this->head_hash['Subject'])){
-# // looks like a vacation autoreply, ignoring
-# $this->output[0]['action'] = 'autoreply';
-# }
-
- // is this an autoresponse ?
- elseif ($this->looks_like_an_autoresponse) {
+ } else if ($this->looks_like_an_autoresponse) {
+ // is this an autoresponse ?
$this->output[0]['action'] = 'autoresponse'; #??? 'transient' 'autoreply' ??
$this->output[0]['autoresponse'] = $this->autoresponse; #??? 4.3.2
// grab the first recipient and break
$this->output[0]['recipient'] = isset($this->head_hash['Return-path']) ? $this->strip_angle_brackets($this->head_hash['Return-path']) : '';
- if(empty($this->output[0]['recipient'])){
+ if (empty($this->output[0]['recipient'])) {
$arrFailed = $this->find_email_addresses($body);
- for($j=0; $joutput[$j]['recipient'] = trim($arrFailed[$j]);
- break;
+ break;
}
}
- }
-
- else if ($this->is_RFC1892_multipart_report() === TRUE){
+ } elseif ($this->is_RFC1892_multipart_report() === TRUE) {
$rpt_hash = $this->parse_machine_parsable_body_part($mime_sections['machine_parsable_body_part']);
if (isset($rpt_hash['per_recipient'])) {
- for($i=0; $ioutput[$i]['recipient'] = $this->find_recipient($rpt_hash['per_recipient'][$i]);
$mycode = @$this->format_status_code($rpt_hash['per_recipient'][$i]['Status']);
- $this->output[$i]['status'] = @$mycode['code'];
+ $this->output[$i]['deliverystatus'] = @$mycode['code'];
$this->output[$i]['action'] = @$rpt_hash['per_recipient'][$i]['Action'];
}
- }
- else {
+ } else {
$arrFailed = $this->find_email_addresses($mime_sections['first_body_part']);
- for($j=0; $joutput[$j]['recipient'] = trim($arrFailed[$j]);
- $this->output[$j]['status'] = $this->get_status_code_from_text($this->output[$j]['recipient'],0);
- $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['status']);
+ $this->output[$j]['deliverystatus'] = $this->get_status_code_from_text($this->output[$j]['recipient'], 0);
+ $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['deliverystatus']);
}
- }
- }
-
- else if(isset($this->head_hash['X-failed-recipients'])) {
+ }
+ } elseif (isset($this->head_hash['X-failed-recipients'])) {
// Busted Exim MTA
// Up to 50 email addresses can be listed on each header.
// There can be multiple X-Failed-Recipients: headers. - (not supported)
- $arrFailed = split(',', $this->head_hash['X-failed-recipients']);
- for($j=0; $jhead_hash['X-failed-recipients']);
+ for ($j = 0; $j < count($arrFailed); $j++) {
$this->output[$j]['recipient'] = trim($arrFailed[$j]);
- $this->output[$j]['status'] = $this->get_status_code_from_text($this->output[$j]['recipient'],0);
- $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['status']);
+ $this->output[$j]['deliverystatus'] = $this->get_status_code_from_text($this->output[$j]['recipient'], 0);
+ $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['deliverystatus']);
}
- }
-
- else if(!empty($boundary) && $this->looks_like_a_bounce){
+ } else if (!empty($boundary) && $this->looks_like_a_bounce) {
// oh god it could be anything, but at least it has mime parts, so let's try anyway
$arrFailed = $this->find_email_addresses($mime_sections['first_body_part']);
- for($j=0; $joutput[$j]['recipient'] = trim($arrFailed[$j]);
- $this->output[$j]['status'] = $this->get_status_code_from_text($this->output[$j]['recipient'],0);
- $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['status']);
+ $this->output[$j]['deliverystatus'] = $this->get_status_code_from_text($this->output[$j]['recipient'], 0);
+ $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['deliverystatus']);
}
- }
-
- else if($this->looks_like_a_bounce){
+ } else if ($this->looks_like_a_bounce) {
// last ditch attempt
// could possibly produce erroneous output, or be very resource consuming,
// so be careful. You should comment out this section if you are very concerned
// about 100% accuracy or if you want very fast performance.
// Leave it turned on if you know that all messages to be analyzed are bounces.
$arrFailed = $this->find_email_addresses($body);
- for($j=0; $joutput[$j]['recipient'] = trim($arrFailed[$j]);
- $this->output[$j]['status'] = $this->get_status_code_from_text($this->output[$j]['recipient'],0);
- $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['status']);
+ $this->output[$j]['deliverystatus'] = $this->get_status_code_from_text($this->output[$j]['recipient'], 0);
+ $this->output[$j]['action'] = $this->get_action_from_status_code($this->output[$j]['deliverystatus']);
}
}
// else if()..... add a parser for your busted-ass MTA here
-
+
+ // fill additional and dependant fields
+ $this->output[0]['reason'] = $this->get_reason_by_statuscode($this->output[0]['deliverystatus']);
+ $this->output[0]['messageid'] = empty($this->original_letter_header['Message-id']) ? '' : $this->original_letter_header['Message-id'];
+ $this->output[0]['subject'] = empty($this->original_letter_header['Subject']) ? '' : $this->original_letter_header['Subject'];
+
// remove empty array indices
$tmp = array();
- foreach($this->output as $arr){
- if(empty($arr['recipient']) && empty($arr['status']) && empty($arr['action']) ){
- continue;
- }
+ foreach ($this->output as $arr) {
+ if (empty($arr['recipient']) && empty($arr['status']) && empty($arr['action'])) continue;
$tmp[] = $arr;
}
$this->output = $tmp;
+
// accessors
/*if it is an FBL, you could use the class variables to access the
- data (Unlike Multipart-reports, FBL's report only one bounce)
- */
+ data (Unlike Multipart-reports, FBL's report only one bounce)
+ */
$this->type = $this->find_type();
$this->action = isset($this->output[0]['action']) ? $this->output[0]['action'] : '';
$this->status = isset($this->output[0]['status']) ? $this->output[0]['status'] : '';
@@ -280,24 +300,22 @@ public function get_the_facts($eml){
$this->feedback_type = (isset($this->fbl_hash['Feedback-type'])) ? $this->fbl_hash['Feedback-type'] : "";
// sniff out any web beacons
- if($this->web_beacon_preg_1)
- $this->web_beacon_1 = $this->find_web_beacon($body, $this->web_beacon_preg_1);
- if($this->web_beacon_preg_2)
- $this->web_beacon_2 = $this->find_web_beacon($body, $this->web_beacon_preg_2);
- if($this->x_header_search_1)
- $this->x_header_beacon_1 = $this->find_x_header ($this->x_header_search_1);
- if($this->x_header_search_2)
- $this->x_header_beacon_2 = $this->find_x_header ($this->x_header_search_2);
+ if ($this->web_beacon_preg_1) $this->web_beacon_1 = $this->find_web_beacon($body, $this->web_beacon_preg_1);
+ if ($this->web_beacon_preg_2) $this->web_beacon_2 = $this->find_web_beacon($body, $this->web_beacon_preg_2);
+ if ($this->x_header_search_1) $this->x_header_beacon_1 = $this->find_x_header($this->x_header_search_1);
+ if ($this->x_header_search_2) $this->x_header_beacon_2 = $this->find_x_header($this->x_header_search_2);
return $this->output;
}
-
+ private function splitHeadAndBody($letter) {
+ return strpos($letter, "\r\n\r\n") !== false ? preg_split("/\r\n\r\n/", $letter, 2) : [$letter, ''];
+ }
- function init_bouncehandler($blob, $format='string'){
+ function init_bouncehandler($blob) {
$this->head_hash = array();
$this->fbl_hash = array();
- $this->body_hash = array();
+ $this->body_hash = array();
$this->looks_like_a_bounce = false;
$this->looks_like_an_FBL = false;
$this->is_hotmail_fbl = false;
@@ -307,321 +325,294 @@ function init_bouncehandler($blob, $format='string'){
$this->status = "";
$this->subject = "";
$this->recipient = "";
- $this->output = array();
- $this->output[0]['action'] = '';
- $this->output[0]['status'] = '';
- $this->output[0]['recipient'] = '';
-
- // TODO: accept several formats (XML, string, array)
- // currently accepts only string
- //if($format=='xml_array'){
- // $strEmail = "";
- // $out = "";
- // for($i=0; $i<$blob; $i++){
- // $out = preg_replace("//i", "", $blob[$i]);
- // $out = preg_replace("//i", "", $out);
- // $out = preg_replace("//i", "", $out);
- // $out = preg_replace("//i", "", $out);
- // $out = rtrim($out) . "\r\n";
- // $strEmail .= $out;
- // }
- //}
- //else if($format=='string'){
+ $this->output[0] = [
+ 'action' => '',
+ 'deliverystatus' => '',
+ 'recipient' => '',
+ 'reason' => '',
+ 'messageid' => '',
+ 'subject' => '',
+ ];
$strEmail = str_replace("\r\n", "\n", $blob); // line returns 1
$strEmail = str_replace("\n", "\r\n", $strEmail);// line returns 2
-# $strEmail = str_replace("=\r\n", "", $strEmail); // remove MIME line breaks (would never exist as #1 above would have dealt with)
-# $strEmail = str_replace("=3D", "=", $strEmail); // equals sign - dealt with in the MIME decode section now
-# $strEmail = str_replace("=09", " ", $strEmail); // tabs
-
- //}
- //else if($format=='array'){
- // $strEmail = "";
- // for($i=0; $i<$blob; $i++){
- // $strEmail .= rtrim($blob[$i]) . "\r\n";
- // }
- //}
return $strEmail;
}
// general purpose recursive heuristic function
// to try to extract useful info from the bounces produced by busted MTAs
- function get_status_code_from_text($recipient, $index){
- for($i=$index; $ibody_hash); $i++){
+ function get_status_code_from_text($recipient, $index) {
+ for ($i = $index; $i < count($this->body_hash); $i++) {
$line = trim($this->body_hash[$i]);
- //skip Message-ID lines
- if (stripos($line, 'Message-ID') !== FALSE)
- continue;
-
- /******** recurse into the email if you find the recipient ********/
- if(stristr($line, $recipient)!==FALSE){
- // the status code MIGHT be in the next few lines after the recipient line,
- // depending on the message from the foreign host... What a laugh riot!
- $status_code = $this->get_status_code_from_text($recipient, $i+1);
- if($status_code){
- return $status_code;
- }
-
- }
+ // skip empty and Message-ID lines
+ if (empty($line)) continue;
+ if (stripos($line, 'Message-ID') !== false) continue;
+ /******** recurse into the email if you find the recipient ********/
+ /*
+ if (stristr($line, $recipient) !== false) {
+ // the status code MIGHT be in the next few lines after the recipient line,
+ // depending on the message from the foreign host... What a laugh riot!
+ $status_code = $this->get_status_code_from_text($recipient, $i + 1);
+ if ($status_code) return $status_code;
+ }
+ */
/******** exit conditions ********/
// if it's the end of the human readable part in this stupid bounce
- if(stristr($line, '------ This is a copy of the message')!==FALSE){
- break;
- }
- //if we see an email address other than our current recipient's,
- if(count($this->find_email_addresses($line))>=1
- && stristr($line, $recipient) === FALSE
- && strstr($line, 'FROM:<') === FALSE) { // Kanon added this line because Hotmail puts the e-mail address too soon and there actually is error message stuff after it.
- break;
- }
-
+ if (stristr($line, '------ This is a copy of the message') !== false) break;
+ if (stristr($line, 'Mensaje original adjunto') !== false) break;
+
+ // if we see an email address other than our current recipient's,
+ // Kanon added this line because Hotmail puts the e-mail address too soon and there actually is error message stuff after it.
+ if (count($this->find_email_addresses($line)) >= 1 && stristr($line, $recipient) === false && strstr($line, 'FROM:<') === false) break;
+
//******** pattern matching ********/
foreach ($this->bouncelist as $bouncetext => $bouncecode) {
- if (preg_match("/$bouncetext/i", $line, $matches))
- return (isset($matches[1])) ? $matches[1] : $bouncecode;
+ if (preg_match("/$bouncetext/i", $line, $matches)) return (isset($matches[1])) ? $matches[1] : $bouncecode;
}
// Search for a rfc3463 style return code
- if (preg_match('/\W([245]\.[01234567]\.[0-9]{1,2})\W/', $line, $matches)) {
- return $matches[1];
-#??? this seems somewhat redundant
-# $mycode = str_replace('.', '', $matches[1]);
-# $mycode = $this->format_status_code($mycode);
-# return implode('.', $mycode['code']); #x.y.z format
- }
+ if (preg_match('/\W([245]\.[01234567]\.[0-9]{1,2})\W/', $line, $matches)) return $matches[1];
// search for RFC2821 return code
- // thanks to mark.tolman@gmail.com
+ // thanks to mark.tolman@gmail.com
// Maybe at some point it should have it's own place within the main parsing scheme (at line 88)
if (preg_match('/\]?: ([45][01257][012345]) /', $line, $matches) ||
- preg_match('/^([45][01257][012345]) (?:.*?)(?:denied|inactive|deactivated|rejected|disabled|unknown|no such|not (?:our|activated|a valid))+/i', $line, $matches))
- {
+ preg_match('/^([45][01257][012345]) (?:.*?)(?:denied|inactive|deactivated|rejected|disabled|unknown|no such|not (?:our|activated|a valid))+/i', $line, $matches)
+ ) {
$mycode = $matches[1];
// map RFC2821 -> RFC3463 codes
- if ($mycode == '550' || $mycode == '551' || $mycode == '553' || $mycode == '554')
+ if ($mycode == '550' || $mycode == '551' || $mycode == '553' || $mycode == '554') {
return '5.1.1'; #perm error
- elseif ($mycode == '452' || $mycode == '552')
+ } elseif ($mycode == '452' || $mycode == '552') {
return '4.2.2'; #mailbox full
- elseif ($mycode == '450' || $mycode == '421')
+ } elseif ($mycode == '450' || $mycode == '421') {
return '4.3.2'; #temp unavailable
- #???$mycode = $this->format_status_code($mycode);
- #???return implode('.', $mycode['code']);
+ }
}
-
}
return '5.5.0'; #other or unknown status
}
- function is_RFC1892_multipart_report(){
- return @$this->head_hash['Content-type']['type']=='multipart/report'
- && @$this->head_hash['Content-type']['report-type']=='delivery-status'
- && @$this->head_hash['Content-type'][boundary]!=='';
+ function is_RFC1892_multipart_report() {
+ return @$this->head_hash['Content-type']['type'] == 'multipart/report'
+ && @$this->head_hash['Content-type']['report-type'] == 'delivery-status'
+ && @$this->head_hash['Content-type']['boundary'] !== '';
}
- function parse_head($headers){
- if(!is_array($headers))
- $headers = explode("\r\n", $headers);
+ function parse_head($headers) {
+ if (!is_array($headers)) $headers = explode("\r\n", $headers);
$hash = $this->standard_parser($headers);
- if(isset($hash['Content-type'])) {//preg_match('/Multipart\/Report/i', $hash['Content-type'])){
- $multipart_report = explode (';', $hash['Content-type']);
- $hash['Content-type']='';
+ if (isset($hash['Content-type'])) {
+ $multipart_report = explode(';', $hash['Content-type']);
+ $hash['Content-type'] = [];
$hash['Content-type']['type'] = strtolower($multipart_report[0]);
- foreach($multipart_report as $mr){
- if(preg_match('/([^=.]*?)=(.*)/i', $mr, $matches)){
- // didn't work when the content-type boundary ID contained an equal sign,
- // that exists in bounces from many Exchange servers
- //if(preg_match('/([a-z]*)=(.*)?/i', $mr, $matches)){
- $hash['Content-type'][strtolower(trim($matches[1]))]= str_replace('"','',$matches[2]);
+ foreach ($multipart_report as $mr) {
+ if (preg_match('/([^=.]*?)=(.*)/i', $mr, $matches)) {
+ // didn't work when the content-type boundary ID contained an equal sign,
+ // that exists in bounces from many Exchange servers
+ //if(preg_match('/([a-z]*)=(.*)?/i', $mr, $matches)){
+ $hash['Content-type'][strtolower(trim($matches[1]))] = str_replace('"', '', $matches[2]);
}
}
}
return $hash;
}
- function contenttype_decode ($mimepart) {
+ function contenttype_decode($mimepart) {
$encoding = '7bit';
$decoded = '';
foreach (explode("\r\n", $mimepart) as $line) {
if (preg_match("/^Content-Transfer-Encoding:\s*(\S+)/", $line, $match)) {
$encoding = $match[1];
$decoded .= $line . "\r\n";
- }
- else switch ($encoding) {
+ } else switch ($encoding) {
case 'quoted-printable': {
if (substr($line, -1) == '=')
$line = substr($line, 0, -1);
else
$line .= "\r\n";
- $decoded .= preg_replace("/=([0-9A-F][0-9A-F])/e", 'chr(hexdec("$1"))', $line);
+ $decoded .= quoted_printable_decode($line);
+ break;
}
case 'base64': {
$decoded .= base64_decode($line);
break;
}
default: # 7bit, 8bit, binary
- $decoded .= $line."\r\n";
+ $decoded .= $line . "\r\n";
}
}
return $decoded;
}
- function parse_body_into_mime_sections($body, $boundary){
- if(!$boundary) return array();
- if (is_array($body))
- $body = implode("\r\n", $body);
+ function parse_body_into_mime_sections($body, $boundary) {
+ if (!$boundary) return array();
+ if (is_array($body)) $body = implode("\r\n", $body);
$body = explode($boundary, $body);
- $mime_sections['first_body_part'] = isset($body[1]) ? $this->contenttype_decode($body[1]) : ''; #proper MIME decode
+ $mime_sections['first_body_part'] = isset($body[1]) ? $this->contenttype_decode($body[1]) : ''; #proper MIME decode
$mime_sections['machine_parsable_body_part'] = isset($body[2]) ? $this->contenttype_decode($body[2]) : '';
$mime_sections['returned_message_body_part'] = isset($body[3]) ? $this->contenttype_decode($body[3]) : '';
return $mime_sections;
}
-
- function standard_parser($content){ // associative array orstr
- // receives email head as array of lines
- // simple parse (Entity: value\n)
- $hash = array('Received'=>'');
- if(!is_array($content)) $content = explode("\r\n", $content);
- foreach($content as $line){
- if(preg_match('/^([^\s.]*):\s*(.*)\s*/', $line, $array)){
+ function standard_parser($content) { // associative array orstr
+ $hash = [];
+ if (!is_array($content)) $content = explode("\r\n", $content);
+ foreach ($content as $line) {
+ if (empty($line)) continue;
+ if (preg_match('/^([^\s.]*):\s*(.*)\s*/', $line, $array)) {
+ // Important data in Key: Value format
$entity = ucfirst(strtolower($array[1]));
- if (isset($array[2]) && strpos($array[2], '=?') !== FALSE) // decode MIME Header encoding (subject lines etc)
- $array[2] = iconv_mime_decode($array[2], ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
- if(empty($hash[$entity])){
- $hash[$entity] = trim($array[2]);
+ if (isset($array[2]) && strpos($array[2], '=?') !== FALSE) {
+ // decode MIME Header encoding (subject lines etc)
+ $array[2] = @iconv_mime_decode($array[2], ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
}
- else if($hash['Received']){
+
+ if (empty($hash[$entity])) {
+ $hash[$entity] = trim($array[2]);
+ } elseif (isset($hash['Received'])) {
// grab extra Received headers :(
// pile it on with pipe delimiters,
// oh well, SMTP is broken in this way
- if ($entity and $array[2] and $array[2] != $hash[$entity]){
+ if ($entity and $array[2] and $array[2] != $hash[$entity]) {
$hash[$entity] .= "|" . trim($array[2]);
}
}
- }
- elseif (isset($line) && isset($entity) && preg_match('/^\s+(.+)\s*/', $line) && $entity) {
- $line = trim($line);
- if (strpos($array[2], '=?') !== FALSE)
- $line = iconv_mime_decode($array[2], ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
- $hash[$entity] .= ' '. $line;
+ } else {
+ // ordinary line
+ if (preg_match('/^\s+(.+)\s*/', $line, $array) && !empty($entity)) {
+ // but if it starts with space or tab and we have previousely discovered entity
+ // it should be ending of the sentence
+ if (strpos($array[1], '=?') !== FALSE) {
+ // decode MIME Header encoding (subject lines etc)
+ $array[1] = @iconv_mime_decode($array[1], ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
+ }
+ $hash[$entity] .= ' ' . $array[1];
+ } else {
+ unset($entity);
+ }
}
}
// special formatting
- $hash['Received']= @explode('|', $hash['Received']);
- $hash['Subject'] = isset($hash['Subject']) ? : '';
+ if (isset($hash['Received'])) $hash['Received'] = explode('|', $hash['Received']);
return $hash;
}
- function parse_machine_parsable_body_part($str){
- //Per-Message DSN fields
+ function parse_machine_parsable_body_part($str) {
$hash = $this->parse_dsn_fields($str);
+
$hash['mime_header'] = $this->standard_parser($hash['mime_header']);
+
+ // Per-Message DSN fields
$hash['per_message'] = isset($hash['per_message']) ? $this->standard_parser($hash['per_message']) : array();
- if(isset($hash['per_message']['X-postfix-sender'])){
- $arr = explode (';', $hash['per_message']['X-postfix-sender']);
- $hash['per_message']['X-postfix-sender']='';
- $hash['per_message']['X-postfix-sender']['type'] = @trim($arr[0]);
- $hash['per_message']['X-postfix-sender']['addr'] = @trim($arr[1]);
- }
- if(isset($hash['per_message']['Reporting-mta'])){
- $arr = explode (';', $hash['per_message']['Reporting-mta']);
- $hash['per_message']['Reporting-mta']='';
- $hash['per_message']['Reporting-mta']['type'] = @trim($arr[0]);
- $hash['per_message']['Reporting-mta']['addr'] = @trim($arr[1]);
+ foreach(['X-postfix-sender', 'Reporting-mta', 'Received-from-mta'] as $key) {
+ if (isset($hash['per_message'][$key])) {
+ $arr = explode(';', $hash['per_message'][$key]);
+ $hash['per_message'][$key] = [
+ 'type' => @trim($arr[0]),
+ 'addr' => @trim($arr[1]),
+ ];
+ }
}
- //Per-Recipient DSN fields
- if(isset($hash['per_recipient'])) {
- for($i=0; $istandard_parser(explode("\r\n", $hash['per_recipient'][$i]));
- $arr = isset($temp['Final-recipient']) ? explode (';', $temp['Final-recipient']) : array();
+
+ // Per-Recipient DSN fields
+ if (isset($hash['per_recipient'])) {
+ foreach($hash['per_recipient'] as $i => $rcpt) {
+ $temp = $this->standard_parser(explode("\r\n", $rcpt));
+
+ $arr = isset($temp['Final-recipient']) ? explode(';', $temp['Final-recipient']) : array();
$temp['Final-recipient'] = $this->format_final_recipient_array($arr);
- //$temp['Final-recipient']['type'] = trim($arr[0]);
- //$temp['Final-recipient']['addr'] = trim($arr[1]);
- $temp['Original-recipient']= array();
- $temp['Original-recipient']['type'] = isset($arr[0]) ? trim($arr[0]) : '';
- $temp['Original-recipient']['addr'] = isset($arr[1]) ? trim($arr[1]) : '';
- $arr = isset($temp['Diagnostic-code']) ? explode (';', $temp['Diagnostic-code']) : array();
- $temp['Diagnostic-code'] = array();
- $temp['Diagnostic-code']['type'] = isset($arr[0]) ? trim($arr[0]) : '';
- $temp['Diagnostic-code']['text'] = isset($arr[1]) ? trim($arr[1]) : '';
+
+ foreach(['Original-recipient', 'Diagnostic-code', 'Remote-mta'] as $key) {
+ if (isset($temp[$key])) {
+ $arr = explode(';', $temp[$key]);
+ $temp[$key] = [
+ 'type' => @trim($arr[0]),
+ 'addr' => @trim($arr[1]),
+ ];
+ }
+ }
+
// now this is wierd: plenty of times you see the status code is a permanent failure,
// but the diagnostic code is a temporary failure. So we will assert the most general
// temporary failure in this case.
- $ddc=''; $judgement='';
- $ddc = $this->decode_diagnostic_code($temp['Diagnostic-code']['text']);
- $judgement = $this->get_action_from_status_code($ddc);
- if($judgement == 'transient'){
- if(stristr($temp['Action'],'failed')!==FALSE){
- $temp['Action']='transient';
- $temp['Status']='4.3.0';
+ if (isset($temp['Diagnostic-code']['text'])) {
+ $ddc = $this->decode_diagnostic_code($temp['Diagnostic-code']['text']);
+ $judgement = $this->get_action_from_status_code($ddc);
+ if ($judgement == 'transient') {
+ if (stristr($temp['Action'], 'failed') !== FALSE) {
+ $temp['Action'] = 'transient';
+ $temp['Status'] = '4.3.0';
+ }
}
}
- $hash['per_recipient'][$i]='';
- $hash['per_recipient'][$i]=$temp;
+ $hash['per_recipient'][$i] = $temp;
}
}
return $hash;
}
- function get_head_from_returned_message_body_part($mime_sections){
- $temp = explode("\r\n\r\n", $mime_sections[returned_message_body_part]);
+ function get_head_from_returned_message_body_part($mime_sections) {
+ $temp = explode("\r\n\r\n", $mime_sections['returned_message_body_part']);
$head = $this->standard_parser($temp[1]);
$head['From'] = $this->extract_address($head['From']);
$head['To'] = $this->extract_address($head['To']);
return $head;
}
- function extract_address($str){
+ function extract_address($str) {
+ $from = '';
$from_stuff = preg_split('/[ \"\'\<\>:\(\)\[\]]/', $str);
- foreach ($from_stuff as $things){
- if (strpos($things, '@')!==FALSE){$from = $things;}
+ foreach ($from_stuff as $things) {
+ if (strpos($things, '@') !== false) {
+ $from = $things;
+ }
}
return $from;
}
- function find_recipient($per_rcpt){
+ function find_recipient($per_rcpt) {
$recipient = '';
- if($per_rcpt['Original-recipient']['addr'] !== ''){
+ if (isset($per_rcpt['Original-recipient']['addr']) && $per_rcpt['Original-recipient']['addr'] !== '') {
$recipient = $per_rcpt['Original-recipient']['addr'];
- }
- else if($per_rcpt['Final-recipient']['addr'] !== ''){
+ } else if (isset($per_rcpt['Final-recipient']['addr']) && $per_rcpt['Final-recipient']['addr'] !== '') {
$recipient = $per_rcpt['Final-recipient']['addr'];
}
$recipient = $this->strip_angle_brackets($recipient);
return $recipient;
}
- function find_type(){
- if($this->looks_like_a_bounce)
+ function find_type() {
+ if ($this->looks_like_a_bounce) {
return "bounce";
- elseif ($this->looks_like_an_FBL)
+ } else if ($this->looks_like_an_FBL) {
return "fbl";
- elseif ($this->looks_like_an_autoresponse)
+ } elseif ($this->looks_like_an_autoresponse) {
return "autoresponse";
- else
+ } else {
return false;
+ }
}
- function parse_dsn_fields($dsn_fields){
- if(!is_array($dsn_fields)) $dsn_fields = explode("\r\n\r\n", $dsn_fields);
+ function parse_dsn_fields($dsn_fields) {
+ if (!is_array($dsn_fields)) $dsn_fields = explode("\r\n\r\n", $dsn_fields);
$j = 0;
- reset($dsn_fields);
- for($i=0; $iformat_status_code($code);
$arr = explode('.', $ret['code']);
- $str = "". $status_code_classes[$arr[0]]['title'] . " - " .$status_code_classes[$arr[0]]['descr']. " ". $status_code_subclasses[$arr[1].".".$arr[2]]['title'] . " - " .$status_code_subclasses[$arr[1].".".$arr[2]]['descr']. "
";
+ $str = "" . $status_code_classes[$arr[0]]['title'] . " - " . $status_code_classes[$arr[0]]['descr'] . " " . $status_code_subclasses[$arr[1] . "." . $arr[2]]['title'] . " - " . $status_code_subclasses[$arr[1] . "." . $arr[2]]['descr'] . "
";
return $str;
}
- function get_action_from_status_code($code){
- if($code=='')
- return '';
+ function get_action_from_status_code($code) {
+ if ($code == '') return '';
$ret = $this->format_status_code($code);
switch (isset($ret['code']) ? $ret['code'][0] : '') {
- case(2):
- return 'success';
- break;
- case(4):
- return 'transient';
- break;
- case(5):
- return 'failed';
- break;
- default:
- return '';
- break;
+ case 2 : return 'success'; break;
+ case 4 : return 'transient'; break;
+ case 5 : return 'failed'; break;
+ default: return ''; break;
}
}
- function decode_diagnostic_code($dcode){
- if(preg_match("/(\d\.\d\.\d)\s/", $dcode, $array)){
+ function decode_diagnostic_code($dcode) {
+ if (preg_match('/(\d\.\d\.\d)\s/', $dcode, $array)) {
return $array[1];
- }
- else if(preg_match("/(\d\d\d)\s/", $dcode, $array)){
+ } else if (preg_match('/(\d\d\d)\s/', $dcode, $array)) {
return $array[1];
}
+ return '';
}
- function is_a_bounce(){
- foreach ($this->bouncesubj as $s)
- if (preg_match("/^$s/i", $this->head_hash['Subject']))
- return true;
- #if(@preg_match('/auto_reply/',$this->head_hash['Precedence'])) return true; # autoresponse, not bounce
- if (isset($this->head_hash['From']) &&
- preg_match("/^(postmaster|mailer-daemon)\@?/i", $this->head_hash['From']))
- return true;
+ function is_a_bounce() {
+ foreach ($this->bouncesubj as $s) {
+ if (preg_match("/^$s/ui", $this->head_hash['Subject'])) return true;
+ }
+ if (isset($this->head_hash['From']) && preg_match("/^(postmaster|mailer-daemon)\@?/i", $this->head_hash['From'])) return true;
return false;
}
-
- function find_email_addresses($first_body_part){
+
+ function find_email_addresses($first_body_part) {
// not finished yet. This finds only one address.
- if (preg_match("/\b([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b/i", $first_body_part, $matches)){
- return array($matches[1]);
- }
- else
- return array();
+ return preg_match('/\b([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b/i', $first_body_part, $matches) ? [$matches[1]] : [];
}
-
// these functions are for feedback loops
- function is_an_ARF(){
- if(isset($this->head_hash['Content-type']['report-type']) && preg_match('/feedback-report/',$this->head_hash['Content-type']['report-type']))
- return true;
- if(isset($this->head_hash['X-loop']) && preg_match('/scomp/',$this->head_hash['X-loop']))
- return true;
- if(isset($this->head_hash['X-hmxmroriginalrecipient'])) {
- $this->is_hotmail_fbl = TRUE;
+ function is_an_ARF() {
+ if (isset($this->head_hash['Content-type']['report-type']) && preg_match('/feedback-report/', $this->head_hash['Content-type']['report-type'])) return true;
+ if (isset($this->head_hash['X-loop']) && preg_match('/scomp/', $this->head_hash['X-loop'])) return true;
+ if (isset($this->head_hash['X-hmxmroriginalrecipient'])) {
+ $this->is_hotmail_fbl = true;
$this->recipient = $this->head_hash['X-hmxmroriginalrecipient'];
return true;
}
- if(isset($this->first_body_hash['X-hmxmroriginalrecipient']) ) {
- $this->is_hotmail_fbl = TRUE;
+ if (isset($this->first_body_hash['X-hmxmroriginalrecipient'])) {
+ $this->is_hotmail_fbl = true;
$this->recipient = $this->first_body_hash['X-hmxmroriginalrecipient'];
return true;
}
return false;
}
-
+
// look for common auto-responders
function is_an_autoresponse() {
- foreach (array ('Auto-submitted', 'X-autorespond') as $a) {
+ foreach (array('Auto-submitted', 'X-autorespond') as $a) {
if (isset($this->head_hash[$a])) {
- $this->autoresponse = "$a: ". $this->head_hash[$a];
+ $this->autoresponse = "$a: " . $this->head_hash[$a];
return TRUE;
}
}
- foreach (array ('Precedence', 'X-precedence') as $a) {
+ foreach (array('Precedence', 'X-precedence') as $a) {
if (isset($this->head_hash[$a]) && preg_match('/^(auto|junk)/i', $this->head_hash[$a])) {
- $this->autoresponse = "$a: ". $this->head_hash[$a];
+ $this->autoresponse = "$a: " . $this->head_hash[$a];
return TRUE;
}
}
-
+
$subj = isset($this->head_hash['Subject']) ? $this->head_hash['Subject'] : '';
foreach ($this->autorespondlist as $a) {
- if (preg_match("/$a/i", $subj)) {
+ if (preg_match("/$a/ui", $subj)) {
$this->autoresponse = $this->head_hash['Subject'];
return TRUE;
}
}
return FALSE;
}
-
-
-
+
// use a perl regular expression to find the web beacon
- public function find_web_beacon($body, $preg){
- if(!isset($preg) || !$preg)
- return "";
- if(preg_match($preg, $body, $matches))
- return $matches[1];
+ public function find_web_beacon($body, $preg) {
+ if (!isset($preg) || !$preg) return "";
+ return preg_match($preg, $body, $matches) ? $matches[1] : '';
}
-
- public function find_x_header($xheader){
+
+ public function find_x_header($xheader) {
$xheader = ucfirst(strtolower($xheader));
+
// check the header
- if(isset($this->head_hash[$xheader])){
- return $this->head_hash[$xheader];
- }
+ if (isset($this->head_hash[$xheader])) return $this->head_hash[$xheader];
+
// check the body too
$tmp_body_hash = $this->standard_parser($this->body_hash);
- if(isset($tmp_body_hash[$xheader])){
- return $tmp_body_hash[$xheader];
- }
+ if (isset($tmp_body_hash[$xheader])) return $tmp_body_hash[$xheader];
+
return "";
}
-
- private function find_fbl_recipients($fbl){
- if(isset($fbl['Original-rcpt-to'])){
+
+ private function find_fbl_recipients($fbl) {
+ if (isset($fbl['Original-rcpt-to'])) {
return $fbl['Original-rcpt-to'];
- }
- else if(isset($fbl['Removal-recipient'])){
+ } elseif (isset($fbl['Removal-recipient'])) {
return trim(str_replace('--', '', $fbl['Removal-recipient']));
}
+ return '';
}
- private function strip_angle_brackets($recipient){
- if (preg_match('/[<[](.*)[>\]]/', $recipient, $matches))
- return trim($matches[1]);
- else
- return trim($recipient);
+ private function strip_angle_brackets($recipient) {
+ return preg_match('/[<[](.*)[>\]]/', $recipient, $matches) ? trim($matches[1]) : trim($recipient);
}
-
- /*The syntax of the final-recipient field is as follows:
- "Final-Recipient" ":" address-type ";" generic-address
- */
- private function format_final_recipient_array($arr){
- $output = array('addr'=>'',
- 'type'=>'');
- if (isset($arr[1])) {
- if (strpos($arr[0], '@') !== FALSE){
- $output['addr'] = $this->strip_angle_brackets($arr[0]);
- $output['type'] = (!empty($arr[1])) ? trim($arr[1]) : 'unknown';
- }
- else {
- $output['type'] = trim($arr[0]);
- $output['addr'] = $this->strip_angle_brackets($arr[1]);
- }
+ /*
+ * The syntax of the final-recipient field is as follows:
+ * "Final-Recipient" ":" address-type ";" generic-address
+ */
+ private function format_final_recipient_array($arr) {
+ $output = array('addr' => '', 'type' => '');
+ if (count($arr) > 1) {
+ $output['type'] = trim($arr[0]);
+ $output['addr'] = $this->strip_angle_brackets($arr[1]);
+ } else {
+ $output['addr'] = $this->strip_angle_brackets($arr[0]);
+ $output['type'] = 'unknown';
}
return $output;
}
-}/** END class BounceHandler **/
-?>
+
+ public function get_reason_by_statuscode($code) {
+ if ($code == '5.7.1') return 'filtered';
+ if (in_array($code, ['4.2.0', '4.2.2', '4.3.2'])) return 'notaccept';
+ return 'userunknown';
+ }
+}
\ No newline at end of file
diff --git a/bounce_responses.php b/bounce_responses.php
index 37b1bf9..a3ff15e 100644
--- a/bounce_responses.php
+++ b/bounce_responses.php
@@ -264,6 +264,7 @@
'undeliverable',
'undelivered mail',
'warning: message',
+ 'Ваше сообщение не доставлено'
);
#
@@ -276,4 +277,3 @@
# }
# }
#}
-?>
\ No newline at end of file
diff --git a/php.bouncehandler.v7.4.zip b/php.bouncehandler.v7.4.zip
deleted file mode 100644
index 9417828..0000000
Binary files a/php.bouncehandler.v7.4.zip and /dev/null differ
diff --git a/php.bouncehandler.v7.8.zip b/php.bouncehandler.v7.8.zip
new file mode 100644
index 0000000..d09a947
Binary files /dev/null and b/php.bouncehandler.v7.8.zip differ
diff --git a/testdriver1.php b/testdriver1.php
index 12419dc..441b62b 100644
--- a/testdriver1.php
+++ b/testdriver1.php
@@ -7,98 +7,112 @@
require_once("bounce_driver.class.php");
$bouncehandler = new Bouncehandler();
-if(!empty($_GET['testall'])){
- $files = get_sorted_file_list('eml');
- if (is_array($files)) {
- echo "File Tests:
\n";
- foreach($files as $file) {
- echo "$file ";
- $bounce = file_get_contents("eml/".$file);
- $multiArray = $bouncehandler->get_the_facts($bounce);
- if( !empty($multiArray[0]['action'])
- && !empty($multiArray[0]['status'])
- && !empty($multiArray[0]['recipient']) ){
- print " - Passed
\n";
- }
- else{
- print " - WRONG
\n";
- print "\n";
- print_r($multiArray[0]);
- print "
\n";
- }
- }
- }
+if (!empty($_GET['testall'])) {
+ $files = get_sorted_file_list('eml');
+ if (is_array($files)) {
+ echo "File Tests:
\n";
+ foreach ($files as $file) {
+ echo "$file ";
+ $bounce = file_get_contents("eml/" . $file);
+ $multiArray = $bouncehandler->get_the_facts($bounce);
+ if (!empty($multiArray[0]['action'])
+ && !empty($multiArray[0]['status'])
+ && !empty($multiArray[0]['recipient'])) {
+ print " - Passed
\n";
+ } else {
+ print " - WRONG
\n";
+ print "\n";
+ print_r($multiArray[0]);
+ print "
\n";
+ }
+ }
+ }
}
?>
bounce_driver.class.php -- Version 7.4
- Chris Fortune ~ http://cfortune.kics.bc.ca
+ Chris Fortune ~ http://cfortune.kics.bc.ca
June 19, 2014
-- make auto-responder identification table driven
-- make bounce_statuscodes.php (prev rfc1893_status_codes.php) generated from IANA list
- php Make_statuscodes.php >bounce_statuscodes.php
-- allow for rfc status codes with 2 digits in the 3rd paramater
-- more supression for php notifications on undefined data
-- better detection and field definition for FBL handling
-- remove spaces in joined header lines
-- remove invalid/redundant implode operations
-- add two new sample emails 61,62
-- add command line test tool (cmdline_test.php)
-
-
-July 4, 2013
-
-
-Replaced deprecated split() function.
-Added auto-responder identification filter.
-Suppressed php Notice errors.
-
-
-
-Download source code
-
-
-
-
-Feb 3, 2011
-
-
-Hey! Class is no longer static, it is rewritten in dynamic $this-> notation. It's much easier to customize now. If you are upgrading from a previous version, you will need to rewrite your method invocation code.
-
-
-
-Download source code
-
-
-
-This bounce handler Attempts to parse Multipart reports for hard bounces, according to RFC1892 (RFC 1892 - The Multipart/Report Content Type for the Reporting of Mail System Administrative Messages) and RFC1894 (RFC 1894 - An Extensible Message Format for Delivery Status Notifications). We can reuse this for any well-formed bounces.
-
-It handles FBL (Feedback Loop) emails, if they are in Abuse Feedback Reporting Format, ARF (It even handles Hotmail's ridiculous attempts at FBL). DKIM parsing is not yet implemented.
-
-
-You can configure custom regular expressions to find any web beacons you may have put in your outgoing mails, in either the mail body or an x-header field. (see source code for examples). You can use it to track data (eg, recipient, list, mail run, etc...) by sending out unique ids, then parsing them from the bounces. This is especially useful when parsing FBL's, because usually all recipient fields have been removed (redacted).
-
-
-If the bounce is not well formed, it tries to extract some useful information anyway. Currently Postfix and Exim are supported, partially. You can edit the function get_the_facts() if you want to add a parser for your own busted MTA. Please forward any useful & reuseable code to the keeper of this class. Chris Fortune
-
-
-// a perl regular expression to find a web beacon in the email body
-$bouncehandler->web_beacon_preg_1 = "/u=([0-9a-fA-F]{32})/";
-$bouncehandler->web_beacon_preg_2 = "/m=(\d*)/";
-
-// find a web beacon in an X-header field (in the head section)
-$bouncehandler->x_header_search_1 = "X-ctnlist-suid";
-//$bouncehandler->x_header_search_2 = "X-sumthin-sumpin";
-
-if($_GET['eml']){
- echo "
".$_GET['eml']." -- ";
- echo "View a different bounce
";
- $bounce = file_get_contents("eml/".$_GET['eml']);
- echo "Quick and dirty bounce handler:
+
- make auto-responder identification table driven
+ - make bounce_statuscodes.php (prev rfc1893_status_codes.php) generated from IANA list
+ php Make_statuscodes.php >bounce_statuscodes.php
+
+ - allow for rfc status codes with 2 digits in the 3rd paramater
+ - more supression for php notifications on undefined data
+ - better detection and field definition for FBL handling
+ - remove spaces in joined header lines
+ - remove invalid/redundant implode operations
+ - add two new sample emails 61,62
+ - add command line test tool (cmdline_test.php)
+
+
+ July 4, 2013
+
+
+ Replaced deprecated split() function.
+ Added auto-responder identification filter.
+ Suppressed php Notice errors.
+
+
+
+ Download source code
+
+
+
+
+ Feb 3, 2011
+
+
+ Hey! Class is no longer static, it is rewritten in dynamic $this-> notation. It's much easier to
+ customize now. If you are upgrading from a previous version, you will need to rewrite your method invocation
+ code.
+
+
+
+ Download source code
+
+
+
+ This bounce handler Attempts to parse Multipart reports for hard bounces, according to RFC1892 (RFC 1892 - The Multipart/Report Content Type
+ for the Reporting of Mail System Administrative Messages) and RFC1894
+ (RFC 1894 - An Extensible Message Format for Delivery Status Notifications). We can reuse this for any
+ well-formed bounces.
+
+ It handles FBL (Feedback Loop) emails, if they are in Abuse Feedback Reporting Format, ARF
+ (It even handles Hotmail's ridiculous attempts at FBL). DKIM parsing is not yet implemented.
+
+
+ You can configure custom regular expressions to find any web beacons you may have put in your outgoing mails, in
+ either the mail body or an x-header field. (see source code for examples). You can use it to track data (eg,
+ recipient, list, mail run, etc...) by sending out unique ids, then parsing them from the bounces. This is
+ especially useful when parsing FBL's, because usually all recipient fields have been removed (redacted).
+
+
+ If the bounce is not well formed, it tries to extract some useful information anyway. Currently Postfix and Exim
+ are supported, partially. You can edit the function get_the_facts() if you want to add a parser for
+ your own busted MTA. Please forward any useful & reuseable code to the keeper of this class. Chris Fortune
+
+
+ // a perl regular expression to find a web beacon in the email body
+ $bouncehandler->web_beacon_preg_1 = "/u=([0-9a-fA-F]{32})/";
+ $bouncehandler->web_beacon_preg_2 = "/m=(\d*)/";
+
+ // find a web beacon in an X-header field (in the head section)
+ $bouncehandler->x_header_search_1 = "X-ctnlist-suid";
+ //$bouncehandler->x_header_search_2 = "X-sumthin-sumpin";
+
+ if ($_GET['eml']) {
+ echo "
" . $_GET['eml'] . " -- ";
+ echo "View a different bounce
";
+ $bounce = file_get_contents("eml/" . $_GET['eml']);
+ echo "Quick and dirty bounce handler:
useage:
require_once(\"bounce_driver.class.php\");
@@ -110,39 +124,38 @@
print_r(\$multiArray);
";
- $multiArray = $bouncehandler->get_the_facts($bounce);
- echo "";
+
+ $bounce = $bouncehandler->init_bouncehandler($bounce, 'string');
+ list($head, $body) = preg_split("/\r\n\r\n/", $bounce, 2);
+ } else {
+ print "
- Test All Sample Bounce E-mails\n\n";
+ print "
- Or, select a bounce email to view the parsed results:
\n";
+
+ $files = get_sorted_file_list('eml');
+ if (is_array($files)) {
+ reset($files);
+ echo "Files:
\n";
+ foreach ($files as $file) {
+ echo "$file
\n";
+ }
+ }
+ exit;
+ }
+
+ echo "Will return recipient's email address, the RFC1893 error code, and the action. Action can be one of the following:
- 'transient'(temporary problem),
- 'failed' (permanent problem),
- 'autoreply' (a vacation auto-response), or
- '' (nothing -- not classified).
";
-echo "You could dereference the \$multiArray in a 'for loop', for example...
+ echo "You could dereference the \$multiArray in a 'for loop', for example...
foreach(\$multiArray as \$the){
switch(\$the['action']){
@@ -194,111 +207,107 @@
";
-echo "That's all you need to know, but if you want to get more complicated you can. All methods are public. See source code.
";
-
-
-echo "
Here is the parsed head
\n";
-$head_hash = $bouncehandler->parse_head($head);
-echo "";
-
-if($bouncehandler->is_hotmail_fbl) echo "RRRRRR".$bouncehandler->recipient ;
-exit;
-
-if ($bouncehandler->is_RFC1892_multipart_report($head_hash)){
- print "Looks like an RFC1892 multipart report
";
-}
-else if($bouncehandler->looks_like_an_FBL){
- print "It's a Feedback Loop, ";
- if($bouncehandler->is_hotmail_fbl){
- print " in Hotmail Doofus Format (HDF?)
";
- }else{
- print " in Abuse Feedback Reporting format (ARF)";
- echo "";
- }
-}
-else {
- print "Not an RFC1892 multipart report
";
- echo "";
- exit;
-}
-
-
-echo "Here is the parsed report
\n";
-echo "Postfix adds an appropriate X- header (X-Postfix-Sender:), so you do not need to create one via phpmailer. RFC's call for an optional Original-recipient field, but mandatory Final-recipient field is a fair substitute.
";
-$boundary = $head_hash['Content-type']['boundary'];
-$mime_sections = $bouncehandler->parse_body_into_mime_sections($body, $boundary);
-$rpt_hash = $bouncehandler->parse_machine_parsable_body_part($mime_sections['machine_parsable_body_part']);
-echo "";
-
-
-
-echo "Here is the error status code
\n";
-echo "It's all in the status code, if you can find one.
";
-for($i=0; $iReport #".($i+1)."
\n";
- echo $bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]);
- $scode = $rpt_hash['per_recipient'][$i]['Status'];
- echo "$scode
";
- echo $bouncehandler->fetch_status_messages($scode);
- echo "
\n";
-}
-
-echo "The Diagnostic-code
is not the same as the reported status code, but it seems to be more descriptive, so it should be extracted (if possible).";
-for($i=0; $iReport #".($i+1)."
\n";
- echo $bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]);
- $dcode = $rpt_hash['per_recipient'][$i]['Diagnostic-code']['text'];
- if($dcode){
- echo "$dcode
";
- echo $bouncehandler->fetch_status_messages($dcode);
- }
- else{
- echo "couldn't decode
";
- }
- echo "
\n";
-}
-
-echo "Grab original To: and From:
\n";
-echo "Just in case we don't have an Original-recipient: field, or a X-Postfix-Sender: field, we can retrieve information from the (optional) returned message body part
\n";
-$head = $bouncehandler->get_head_from_returned_message_body_part($mime_sections);
-echo "From: ".$head['From']."
To: ".$head['To']."
Subject: ".$head['Subject']."
";
-
-
-echo "Here is the body in RFC1892 parts
\n";
-echo "Three parts: [first_body_part], [machine_parsable_body_part], and [returned_message_body_part]
";
-echo "";
-
-
-/*
- $status_code = $bouncehandler->format_status_code($rpt_hash['per_recipient'][$i]['Status']);
- $status_code_msg = $bouncehandler->fetch_status_messages($status_code['code']);
- $status_code_remote_msg = $status_code['text'];
- $diag_code = $bouncehandler->format_status_code($rpt_hash['per_recipient'][$i]['Diagnostic-code']['text']);
- $diag_code_msg = $bouncehandler->fetch_status_messages($diag_code['code']);
- $diag_code_remote_msg = $diag_code['text'];
-*/
-
-function get_sorted_file_list($d){
- $fs = array();
- if ($h = opendir($d)) {
- while (false !== ($f = readdir($h))) {
- if($f=='.' || $f=='..') continue;
- $fs[] = $f;
- }
- closedir($h);
- sort($fs, SORT_STRING);//
- }
- return $fs;
-}
-
-?>
+ echo "That's all you need to know, but if you want to get more complicated you can. All methods are public. See source code.
";
+
+
+ echo "
Here is the parsed head
\n";
+ $head_hash = $bouncehandler->parse_head($head);
+ echo "";
+
+ if ($bouncehandler->is_hotmail_fbl) echo "RRRRRR" . $bouncehandler->recipient;
+ exit;
+
+ if ($bouncehandler->is_RFC1892_multipart_report($head_hash)) {
+ print "Looks like an RFC1892 multipart report
";
+ } else if ($bouncehandler->looks_like_an_FBL) {
+ print "It's a Feedback Loop, ";
+ if ($bouncehandler->is_hotmail_fbl) {
+ print " in Hotmail Doofus Format (HDF?)
";
+ } else {
+ print " in Abuse Feedback Reporting format (ARF)";
+ echo "";
+ }
+ } else {
+ print "Not an RFC1892 multipart report
";
+ echo "";
+ exit;
+ }
+
+
+ echo "Here is the parsed report
\n";
+ echo "Postfix adds an appropriate X- header (X-Postfix-Sender:), so you do not need to create one via phpmailer. RFC's call for an optional Original-recipient field, but mandatory Final-recipient field is a fair substitute.
";
+ $boundary = $head_hash['Content-type']['boundary'];
+ $mime_sections = $bouncehandler->parse_body_into_mime_sections($body, $boundary);
+ $rpt_hash = $bouncehandler->parse_machine_parsable_body_part($mime_sections['machine_parsable_body_part']);
+ echo "";
+
+
+ echo "Here is the error status code
\n";
+ echo "It's all in the status code, if you can find one.
";
+ for ($i = 0; $i < count($rpt_hash['per_recipient']); $i++) {
+ echo "Report #" . ($i + 1) . "
\n";
+ echo $bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]);
+ $scode = $rpt_hash['per_recipient'][$i]['Status'];
+ echo "
$scode
";
+ echo $bouncehandler->fetch_status_messages($scode);
+ echo "\n";
+ }
+
+ echo "The Diagnostic-code
is not the same as the reported status code, but it seems to be more descriptive, so it should be extracted (if possible).";
+ for ($i = 0; $i < count($rpt_hash['per_recipient']); $i++) {
+ echo "
Report #" . ($i + 1) . "
\n";
+ echo $bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]);
+ $dcode = $rpt_hash['per_recipient'][$i]['Diagnostic-code']['text'];
+ if ($dcode) {
+ echo "
$dcode
";
+ echo $bouncehandler->fetch_status_messages($dcode);
+ } else {
+ echo "couldn't decode
";
+ }
+ echo "\n";
+ }
+
+ echo "Grab original To: and From:
\n";
+ echo "Just in case we don't have an Original-recipient: field, or a X-Postfix-Sender: field, we can retrieve information from the (optional) returned message body part
\n";
+ $head = $bouncehandler->get_head_from_returned_message_body_part($mime_sections);
+ echo "From: " . $head['From'] . "
To: " . $head['To'] . "
Subject: " . $head['Subject'] . "
";
+
+
+ echo "Here is the body in RFC1892 parts
\n";
+ echo "Three parts: [first_body_part], [machine_parsable_body_part], and [returned_message_body_part]
";
+ echo "";
+
+
+ /*
+ $status_code = $bouncehandler->format_status_code($rpt_hash['per_recipient'][$i]['Status']);
+ $status_code_msg = $bouncehandler->fetch_status_messages($status_code['code']);
+ $status_code_remote_msg = $status_code['text'];
+ $diag_code = $bouncehandler->format_status_code($rpt_hash['per_recipient'][$i]['Diagnostic-code']['text']);
+ $diag_code_msg = $bouncehandler->fetch_status_messages($diag_code['code']);
+ $diag_code_remote_msg = $diag_code['text'];
+ */
+
+ function get_sorted_file_list($d) {
+ $fs = array();
+ if ($h = opendir($d)) {
+ while (false !== ($f = readdir($h))) {
+ if ($f == '.' || $f == '..') continue;
+ $fs[] = $f;
+ }
+ closedir($h);
+ sort($fs, SORT_STRING);//
+ }
+ return $fs;
+ }
+
+ ?>