/** * Check an email. * * @param string $email Contents of an email to check * * @return void */ function checkmail($email) { global $total; $bh = new Bouncehandler(); $bounceinfo = $bh->get_the_facts($email); // var_dump($bounceinfo); // var_dump($bh); print " TYPE " . @$bh->type . "\n"; if ($bh->type == 'bounce') { print " ACTION " . $bounceinfo[0]['action'] . "\n"; print " STATUS " . $bounceinfo[0]['status'] . "\n"; print " RECIPIENT " . $bounceinfo[0]['recipient'] . "\n"; } if ($bh->type == 'fbl') { print " ENV FROM " . @$bh->fbl_hash['Original-mail-from'] . "\n"; print " AGENT " . @$bh->fbl_hash['User-agent'] . "\n"; print " IP " . @$bh->fbl_hash['Source-ip'] . "\n"; } if ($bh->type == 'autoresponse') { print " AUTO " . $bounceinfo[0]['autoresponse'] . "\n"; } if ($bh->type) { @$total[$bh->type]++; } else { @$total['unknown']++; } print "\n"; }
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <body> <?php require_once "bounce_driver.class.php"; $bouncehandler = new Bouncehandler(); if ($_GET['testall']) { $files = get_sorted_file_list('eml'); if (is_array($files)) { echo "<P>File Tests:</P>\n"; foreach ($files as $file) { echo "<a href=\"" . $_SERVER['PHP_SELF'] . "?eml=" . urlencode($file) . "\">{$file}</a> "; $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<br>\n"; } else { print "<font color=red> - WRONG</font><br>\n"; print "<pre>\n"; print_r($multiArray[0]); print "</pre>\n"; } } } } ?> <h1>bounce_driver.class.php -- Version 7.0</h1> <P>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <body> <?php //error_reporting(E_ALL); require_once "bounce_driver.class.php"; $bouncehandler = new Bouncehandler(); if (!empty($_GET['testall'])) { $files = get_sorted_file_list('eml'); if (is_array($files)) { echo "<P>File Tests:</P>\n"; foreach ($files as $file) { echo "<a href=\"" . $_SERVER['PHP_SELF'] . "?eml=" . urlencode($file) . "\">{$file}</a> "; $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<br>\n"; } else { print "<font color=red> - WRONG</font><br>\n"; print "<pre>\n"; print_r($multiArray[0]); print "</pre>\n"; } } } } ?> <h1>bounce_driver.class.php -- Version 7.3</h1>
/** * Tests a single email. * * @param string $bounce Contents of the bounce email. * * @return void */ private function _testSingle($bounce) { $multiArray = $this->_bouncehandler->get_the_facts($bounce); echo "<TEXTAREA COLS=100 ROWS=" . count($multiArray) * 8 . ">"; print_r($multiArray); echo "</TEXTAREA>"; $bounce = $this->_bouncehandler->init_bouncehandler($bounce, 'string'); list($head, $body) = preg_split("/\r\n\r\n/", $bounce, 2); echo '<h2>Raw email:</h2><br />'; echo "<TEXTAREA COLS=100 ROWS=12>"; echo htmlspecialchars($bounce); echo "</TEXTAREA><br />"; echo "<h2>Parsed head</h2>\n"; $head_hash = $this->_bouncehandler->parse_head($head); echo "<TEXTAREA COLS=100 ROWS=" . count($head_hash) * 2.7 . ">"; print_r($head_hash); echo "</TEXTAREA><br />"; if ($this->_bouncehandler->is_RFC1892_multipart_report($head_hash)) { echo '<h2 style="color:red;">'; echo 'Looks like an RFC1892 multipart report'; echo '</h2>'; } else { if ($this->_bouncehandler->looks_like_an_FBL) { echo '<h2 style="color:red;">'; echo 'Looks like a feedback loop'; if ($this->_bouncehandler->is_hotmail_fbl) { echo ' in Hotmail Doofus Format (HDF?)'; } else { echo ' in Abuse Feedback Reporting format (ARF)'; } echo '</h2>'; echo "<TEXTAREA COLS=100 ROWS=12>"; print_r($this->_bouncehandler->fbl_hash); echo "</TEXTAREA>"; } else { echo "<h2 style='color:red;'>Not an RFC1892 multipart report</H2>"; echo "<TEXTAREA COLS=100 ROWS=100>"; print_r($body); echo "</TEXTAREA>"; exit; } } echo "<h2>Here is the parsed report</h2>\n"; echo '<p>Postfix adds an appropriate X- header (X-Postfix-Sender:), '; echo 'so you do not need to create one via phpmailer. RFC\'s call '; echo 'for an optional Original-recipient field, but mandatory '; echo 'Final-recipient field is a fair substitute.</p>'; $boundary = $head_hash['Content-type']['boundary']; $mime_sections = $this->_bouncehandler->parse_body_into_mime_sections($body, $boundary); $rpt_hash = $this->_bouncehandler->parse_machine_parsable_body_part($mime_sections['machine_parsable_body_part']); echo "<TEXTAREA COLS=100 ROWS=" . count($rpt_hash) * 16 . ">"; print_r($rpt_hash); echo "</TEXTAREA>"; echo "<h2>Here is the error status code</h2>\n"; echo "<P>It's all in the status code, if you can find one.</P>"; for ($i = 0; $i < count($rpt_hash['per_recipient']); $i++) { echo "<P>Report #" . ($i + 1) . "<BR>\n"; echo $this->_bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]); $scode = $rpt_hash['per_recipient'][$i]['Status']; echo "<PRE>{$scode}</PRE>"; echo $this->_bouncehandler->fetch_status_messages($scode); echo "</P>\n"; } echo '<h2>The Diagnostic-code</h2>'; echo '<p>is not the same as the reported status code, but it seems '; echo 'to be more descriptive, so it should be extracted (if possible).'; for ($i = 0; $i < count($rpt_hash['per_recipient']); $i++) { echo "<P>Report #" . ($i + 1) . " <BR>\n"; echo $this->_bouncehandler->find_recipient($rpt_hash['per_recipient'][$i]); $dcode = $rpt_hash['per_recipient'][$i]['Diagnostic-code']['text']; if ($dcode) { echo "<PRE>{$dcode}</PRE>"; echo $this->_bouncehandler->fetch_status_messages($dcode); } else { echo "<PRE>couldn't decode</PRE>"; } echo "</P>\n"; } echo '<h2>Grab original To: and From:</h2>\\n'; echo '<p>Just in case we don\'t have an Original-recipient: field, or '; echo 'a X-Postfix-Sender: field, we can retrieve information from '; echo 'the (optional) returned message body part</p>' . PHP_EOL; $head = $this->_bouncehandler->get_head_from_returned_message_body_part($mime_sections); echo "<P>From: " . $head['From']; echo "<br>To: " . $head['To']; echo "<br>Subject: " . $head['Subject'] . "</P>"; echo "<h2>Here is the body in RFC1892 parts</h2>\n"; echo '<[>Three parts: [first_body_part], '; echo '[machine_parsable_body_part], and '; echo ' [returned_message_body_part]</p>'; echo "<TEXTAREA cols=100 rows=100>"; print_r($mime_sections); echo "</TEXTAREA>"; }
<?php require_once "extension/jaj_newsletter/lib/bounce_handler/bounce_driver.class.php"; $server = "zmail-01.hikt.no"; $username = "******"; $password = "******"; $bouncehandler = new Bouncehandler(); $pop3 = new ezcMailPop3Transport($server); $pop3->authenticate($username, $password); $pop3->status($num, $size); $cli->output('Bounce messages to check: ' . $num); $messages = $pop3->listMessages(); foreach ($messages as $index => $size) { $set = $pop3->fetchByMessageNr($index); do { $raw_message = ""; $line = ""; while (($line = $set->getNextLine()) !== null) { $raw_message .= $line; } $result = $bouncehandler->get_the_facts($raw_message); $result = $result[0]; $status = $result['status']; $action = $result['action']; $recipient = trim($result['recipient']); if (!in_array($action, array("delayed", "failed", "autoreply"))) { $cli->output("Message index: {$index}, unknown action: {$action}, skipping..."); continue; } if ($action == 'delayed' || $action == 'autoreply') { $cli->output("Deleting message: {$index}, action: {$action}");
function alo_em_handle_bounces($report = false) { global $wpdb; $output = ''; $bounce_settings = alo_em_bounce_settings(); $conn = alo_em_bounce_connect(); if (!$conn) { return FALSE; } $num_msgs = imap_num_msg($conn); // start bounce class require_once 'inc/bouncehandler/bounce_driver.class.php'; $bouncehandler = new Bouncehandler(); // get the failures $email_addresses = array(); $delete_addresses = array(); $max_msgs = min($num_msgs, $bounce_settings['bounce_maxmsg']); if ($report) { $output .= 'Bounces handled in: ' . $bounce_settings['bounce_email']; } for ($n = 1; $n <= $max_msgs; $n++) { $msg_headers = imap_fetchheader($conn, $n); $msg_body = imap_body($conn, $n); $bounce = $msg_headers . $msg_body; //entire message $multiArray = $bouncehandler->get_the_facts($bounce); if (!empty($multiArray[0]['action']) && !empty($multiArray[0]['status']) && !empty($multiArray[0]['recipient'])) { if ($report) { $output .= '<br /> - MSG #' . $n . ' - Bounce response: ' . $multiArray[0]['action']; } // If delivery permanently failed, unsubscribe if ($multiArray[0]['action'] == 'failed') { $email = trim($multiArray[0]['recipient']); // Unsubscribe email address if ($s_id = alo_em_is_subscriber($email)) { alo_em_delete_subscriber_by_id($s_id); do_action('alo_easymail_bounce_email_unsubscribed', $email); // Hook if ($report) { $output .= ' - ' . $email . ' UNSUBSCRIBED'; } } } // If delivery temporary or permanently failed, mark recipient as bounced if ($multiArray[0]['action'] == 'failed' || $multiArray[0]['action'] == 'transient' || $multiArray[0]['action'] == 'autoreply') { // TODO maybe use: $bouncehandler->x_header_search_1 = 'ALO-EM-Newsletter'; // Look fo EasyMail custom headers: Newsletter and Recipient // NOTE: searching in body because IDs are inside original message included in body $newsletter_id = 0; $recipient_id = 0; if (preg_match('/X-ALO-EM-Newsletter: (\\d+)/i', $bounce, $matches)) { if (!empty($matches[1]) && is_numeric($matches[1])) { $newsletter_id = (int) $matches[1]; } } if (preg_match('/X-ALO-EM-Recipient: (\\d+)/i', $bounce, $matches)) { if (!empty($matches[1]) && is_numeric($matches[1])) { $recipient_id = (int) $matches[1]; } } // Mark recipient as bounced only if not a debug to author if ($newsletter_id > 0 && $recipient_id > 0 && strpos($msg_headers, "( DEBUG - TO: ") === false) { $wpdb->update("{$wpdb->prefix}easymail_recipients", array('result' => -3), array('ID' => $recipient_id, 'newsletter' => $newsletter_id, 'email' => $email)); } if ($report) { $output .= ' - Recipient ID #' . $recipient_id . ' marked as not delivered'; } // mark msg for deletion imap_delete($conn, $n); } } else { if ($report) { $output .= '<br /><span class="description"> - MSG #' . $n . ' - Not a bounce</span>'; } } } //for loop // delete messages imap_expunge($conn); // close imap_close($conn); if ($report) { return $output; } }
function get_status_code_from_text($recipient, $arrBody, $index) { for ($i = $index; $i < count($arrBody); $i++) { $line = trim($arrBody[$i]); /******** 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! $output = BounceHandler::get_status_code_from_text($recipient, $arrBody, $i + 1); if ($output) { return $output; } } /******** 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) { return ''; } //if we see an email address other than our current recipient's, if (count(BounceHandler::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. return ''; } /******** pattern matching ********/ if (stristr($line, 'no such address') !== FALSE || stristr($line, 'Recipient address rejected') !== FALSE || stristr($line, 'User unknown in virtual alias table') !== FALSE) { return '5.1.1'; } else { if (stristr($line, 'unrouteable mail domain') !== FALSE || stristr($line, 'Esta casilla ha expirado por falta de uso') !== FALSE) { return '5.1.2'; } else { if (stristr($line, 'mailbox is full') !== FALSE || stristr($line, 'Mailbox quota usage exceeded') !== FALSE || stristr($line, 'User mailbox exceeds allowed size') !== FALSE) { return '4.2.2'; } else { if (stristr($line, 'not yet been delivered') !== FALSE) { return '4.2.0'; } else { if (stristr($line, 'mailbox unavailable') !== FALSE) { return '5.2.0'; } else { if (stristr($line, 'Unrouteable address') !== FALSE) { return '5.4.4'; } else { if (stristr($line, 'retry timeout exceeded') !== FALSE) { return '4.4.7'; } else { if (stristr($line, 'The account or domain may not exist, they may be blacklisted, or missing the proper dns entries.') !== FALSE) { // Kanon added return '5.2.0'; // I guess.... seems like 5.1.1, 5.1.2, or 5.4.4 would fit too, but 5.2.0 seemed most generic } else { if (stristr($line, '554 TRANSACTION FAILED') !== FALSE) { // Kanon added return '5.5.4'; // I think this should be 5.7.1. "SMTP error from remote mail server after end of data: ... (HVU:B1) http://postmaster.info.aol.com/errors/554hvub1.html" -- AOL rejects messages that have links to certain sites in them. } else { if (stristr($line, 'Status: 4.4.1') !== FALSE || stristr($line, 'delivery temporarily suspended') !== FALSE) { // Kanon added return '4.4.1'; } else { if (stristr($line, '550 OU-002') !== FALSE || stristr($line, 'Mail rejected by Windows Live Hotmail for policy reasons') !== FALSE) { // Kanon added return '5.5.0'; // Again, why isn't this 5.7.1 instead? } else { if (stristr($line, 'PERM_FAILURE: DNS Error: Domain name not found') !== FALSE) { // Kanon added return '5.1.2'; // Not sure if this is right code. Just copied from above. } else { if (stristr($line, 'Delivery attempts will continue to be made for') !== FALSE) { // Kanon added. From Symantec_AntiVirus_for_SMTP_Gateways@uqam.ca return '4.2.0'; // I'm not sure why Symantec delayed this message, but x.2.x means something to do with the mailbox, which seemed appropriate. x.5.x (protocol) or x.7.x (security) also seem possibly appropriate. It seems a lot of times it's x.5.x when it seems to me it should be x.7.x, so maybe x.5.x is standard when mail is rejected due to spam-like characteristics instead of x.7.x like I think it should be. } else { if (stristr($line, '554 delivery error:') !== FALSE) { return '5.5.4'; // rogers.com } else { if (strstr($line, '550-5.1.1') !== FALSE || stristr($line, 'This Gmail user does not exist.') !== FALSE) { // Kanon added return '5.1.1'; // Or should it be 5.5.0? } else { // end strstr tests } } } } } } } } } } } } } } } // rfc1893 return code if (preg_match('/([245]\\.[01234567]\\.[012345678])/', $line, $matches)) { $mycode = str_replace('.', '', $matches[1]); $mycode = Bouncehandler::format_status_code($mycode); return implode('.', $mycode['code']); } // search for RFC821 return code // 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)) { $mycode = $matches[1]; // map common codes to new rfc values if ($mycode == '450' || $mycode == '550' || $mycode == '551' || $mycode == '554') { $mycode = '511'; } else { if ($mycode == '452' || $mycode == '552') { $mycode = '422'; } else { if ($mycode == '421') { $mycode = '432'; } } } $mycode = Bouncehandler::format_status_code($mycode); return implode('.', $mycode['code']); } } return ''; }
<?php global $Knews_plugin, $knewsOptions, $wpdb; if (!$Knews_plugin->initialized) { $Knews_plugin->init(); } if ($knewsOptions['bounce_on'] == '1') { require_once KNEWS_DIR . "/includes/knews_util.php"; require_once KNEWS_DIR . "/includes/bouncehandler/bounce_driver.class.php"; $bouncehandler = new Bouncehandler(); if ($inst = knews_pop3_login($knewsOptions['bounce_host'], $knewsOptions['bounce_port'], $knewsOptions['bounce_user'], $knewsOptions['bounce_pass'], "INBOX", $knewsOptions['bounce_ssl'], $knewsOptions['bounce_mode'])) { $stat = knews_pop3_stat($inst); //print_r($stat); //die(); if ($stat['Nmsgs'] > 0) { $messages = $stat['Nmsgs']; if ($messages > 50) { $messages = 50; } $list = knews_pop3_list($inst, $messages); $count = 0; foreach ($list as $row) { $count++; $b = $bouncehandler->get_the_facts($row); $fail_code = 0; $read_blog_id = 0; $posid = strpos($row, 'Knews-Blog-Id:'); $posid = $posid + 14; if ($posid !== false) { $posid2 = strpos($row, '_', $posid); if ($posid2 !== false) {