push old code

This commit is contained in:
Gregory Colpart 2015-11-26 16:30:00 +01:00
parent 871bd3cf29
commit 2ce7ad8f68
7 changed files with 659 additions and 0 deletions

45
bin/parse-mail Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/php
<?php
/**
* parse-mail
*
* Copyright (c) 2009 Evolix - Tous droits reserves
*
* $Id: index.php 310 2009-10-19 16:04:34Z tmartin $
* vim: expandtab softtabstop=4 tabstop=4 shiftwidth=4 showtabline=2
*
* @author Thomas Martin <tmartin@evolix.fr>
* @author Sebastien Palma <spalma@evolix.fr>
* @version 0.1
*/
require_once('../config/config.php');
require_once('../lib/parser.php');
require_once('../lib/storage.php');
$args = $_SERVER['argv'];
$mailbox = $args[1];
$stdin = fopen('php://stdin', 'r');
$content = '';
while($line = fread($stdin, 1024)) {
$content .= $line;
}
$bounce = new bounceParser($content);
$bounce->parse();
if ($bounce->getErrorCode()) {
$dbstore = new databaseStorage();
$dbstore->store_in_db($bounce, $mailbox);
}
//if ($bounce->getErrorCode()) print($bounce->getErrorCode().'|'.$bounce->getBouncedEmail().'|'.$bounce->getBounceReason()."|".$bounce->getServerAnswer()."\n");
//print($bounce->getErrorCode().'|'.$bounce->getBouncedEmail().'|'.$bounce->getBounceReason()."|".$bounce->getServerAnswer()."\n");
//exit($bounce->getStatus());
?>

67
bin/parse-maildir Executable file
View File

@ -0,0 +1,67 @@
#!/bin/bash
#
# parse-maildir
#
# Copyright (c) 2009 Evolix - Tous droits reserves
#
# $Id: index.php 310 2009-10-19 16:04:34Z tmartin $
# vim: expandtab softtabstop=4 tabstop=4 shiftwidth=4 showtabline=2
#
# @author Thomas Martin <tmartin@evolix.fr>
# @author Sebastien Palma <spalma@evolix.fr>
# @version 0.1
#
SCRIPT_START=`date '+%Y-%m-%d %H:%M:%S'`
TOTAL_BOUNCES=0
# Test the parameters presence
if [ "$#" -lt 1 ]; then
echo "Usage of $0:"
echo "$0 {maildir} {database}"
exit 9;
fi
MAILDIR=`echo $1 | cut -d"/" -f5`
STOCK_DIR=`date '+%Y%m%d'`
if [ ! -d "$1/.$STOCK_DIR" ]; then
mkdir -p $1/.$STOCK_DIR/{tmp,cur,new}
fi
for mail in `ls $1/new/`
do
cat $1/new/$mail | ./parse-mail $MAILDIR
mv $1/new/$mail $1/.$STOCK_DIR/new/
((TOTAL_BOUNCES=$TOTAL_BOUNCES+1))
done
for mail in `ls $1/cur/`
do
cat $1/cur/$mail | ./parse-mail $MAILDIR
mv $1/cur/$mail $1/.$STOCK_DIR/cur/
((TOTAL_BOUNCES=$TOTAL_BOUNCES+1))
done
SCRIPT_END=`date '+%Y-%m-%d %H:%M:%S'`
#if [ "$1" = "/home/vmail/example.com/no-reply" ]; then
cat <<EOT | mail -s "Traitement des bounces" notification@example.com
Traitement des bounces sur la boîte $1
Début du traitement : $SCRIPT_START
Total bounces : $TOTAL_BOUNCES
Fin de traitement : $SCRIPT_END
EOT
#fi

53
config/config.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/**
* Config file
*
* Copyright (c) 2009 Evolix - Tous droits reserves
*
* $Id: config.php 27 2010-01-11 16:33:56Z spalma $
* vim: expandtab softtabstop=4 tabstop=4 shiftwidth=4 showtabline=2
*
* @author Thomas Martin <tmartin@evolix.fr>
* @author Sebastien Palma <spalma@evolix.fr>
* @version 0.1
*/
define('BOUNCE_HARD', 1);
define('BOUNCE_SOFT', 2);
define('BOUNCE_UNKN', 9);
require_once("../config/database.php");
// Maildir path
$config = array (
$search_maildir = '/home/vmail/example.com/no-reply',
);
$bounce_regex = array(
1 => array( // Utilisateur n'existe pas
'User unknown', // wanadoo, orange, 9online, neuf, voila, bluewin.ch
'Unknown user', // tlb.sympatico.ca
'This account has been disabled or discontinued', // yahoo
'The email account .* does not exist', // google
'mailbox unavailable', // hotmail, live, msn
'blocked due to inactivity', // free, aliceadsl
'Recipient address rejected.*User unknown', // laposte
'skynet\.be.*quota exceeded', // Skynet
'MAILBOX NOT FOUND', // AOL
'IS NOT ACCEPTING ANY MAIL', // AOL
'unknown or illegal alias', // Videotron
'Inactive MailBox', // Numericable
'Mailbox has moved' // divers serveurs
),
2 => array( // Erreurs de domaines (mal formulés)
'Operation timed out',
'Connection refused',
'Connection timed out',
'Host not found'
)
);
?>

7
config/database.php Normal file
View File

@ -0,0 +1,7 @@
<?php
define('SERVEUR', '127.0.0.1');
define('NOM', 'bounces');
define('PASSE', 'YOUR-PASSWORD');
define('BASE', 'bounces');

177
lib/Mysql.php Normal file
View File

@ -0,0 +1,177 @@
<?php
/**
* Bibliotheques MySQL (PHP4/PHP5)
*
* $Id: Mysql.php 18 2009-12-21 17:21:08Z spalma $
*
* <pre>A definir :
* define('SERVEUR','localhost');
* define('SERVEURPORT',3306);
* define('BASE','example');
* define('NOM', 'root');
* define('PASSE', 'nopass');</pre>
*
* @author Gregory Colpart <reg@evolix.fr>
* @author Alexandre Anriot <aanriot@evolix.fr>
* @copyright Copyright (c) 2004,2005,2006 Evolix
* @version 1.0
*/
class Mysql
{
/*
* Connexion a une base MySQL
* les constantes SERVEUR, NOM, PASSE et BASE devront etre definies
* il convient de les definir dans un fichier connect.php
*/
function MyConnect()
{
$connexion = mysql_connect(SERVEUR, NOM, PASSE);
if (! $connexion)
{
echo "Une erreur s'est produite : ".mysql_error()."\n";
exit;
}
if (! mysql_select_db(BASE, $connexion))
{
echo "Une erreur s'est produite : ".mysql_error()."\n";
exit;
}
return $connexion;
}
/**
* Executer une requete SQL quelconque
*
* A noter que cette fonction peut s'ecrire en une ligne
* mais reste sous cette forme pour plus de clarté
*
* @param resource $connexion
* @param string $req
* @return mixed (resource or boolean)
*/
function MyReq($connexion,$req) {
if ($query = mysql_query($req,$connexion)) {
return $query;
} else {
// print "erreur requete SQL";
return FALSE;
}
}
/*
* Requete MySQL optimale renvoyant une seule variable
*/
function MyExecReq($connexion,$req) {
if ($r = mysql_fetch_row(mysql_query($req,$connexion))) {
return current($r);
} else {
return FALSE;
}
}
/*
* Requete MySQL renvoyant un objet
*
* Exemple d'utilisation :
* $req = 'SELECT * FROM main';
* $obj = Mysql::MyObjectReq($con,$req);
* print("result = $obj->title");
*/
function MyObjectReq($connexion,$req) {
if ($query = mysql_query($req,$connexion)) {
return mysql_fetch_object($query);
} else {
return FALSE;
}
}
/*
* Requete MySQL renvoyant un tableau associatif
*
* Exemple d'utilisation :
* $req = 'SELECT * FROM main';
* $assoc = Mysql::MyAssocReq($con,$req);
* print($assoc['title']);
*/
function MyAssocReq($connexion,$req) {
if ($query = mysql_query($req,$connexion)) {
return mysql_fetch_assoc($query);
} else {
return FALSE;
}
}
/**
* Requete insertion MySQL (INSERT ou UPDATE)
* renvoie 1 si insertion correcte, 0 sinon
*/
function MyInsertReq($connexion,$req) {
return (mysql_query(Html::sqlclean($req),$connexion)) ? TRUE : FALSE;
}
/**
* Executer une requete SQL renvoyant UNE réponse sur plusieurs lignes
*
* @param resource $connexion
* @param string $req
* @return array
*/
function MyGetArray($connexion,$req) {
$result = array();
$query = Mysql::MyReq($connexion,$req);
while ($res = mysql_fetch_row($query)) {
array_push($result,current($res));
}
return $result;
}
/**
* Executer une requete SQL renvoyant DEUX réponses sur plusieurs lignes
*
* @param resource $connexion
* @param string $req
* @return array
*/
function MyGetHash($connexion,$req) {
$result = array();
$query = Mysql::MyReq($connexion,$req);
while ($res = mysql_fetch_row($query)) {
$result[current($res)] = next($res);
}
return $result;
}
/**
* Parcourir les differentes lignes pour les requetes complexes
* (plusieurs réponses et plusieurs lignes)
*
* Exemple d'utilisation :
* while ($data = Mysql::MyFetchObject($query)) { ...
*
* @param resource
* @return object
*/
function MyFetchObject($query) {
return mysql_fetch_object($query);
}
}
?>

257
lib/parser.php Normal file
View File

@ -0,0 +1,257 @@
<?php
/**
* bounceParser object
*
* Copyright (c) 2009 Evolix - Tous droits reserves
*
* $Id: parser.php 27 2010-01-11 16:33:56Z spalma $
* vim: expandtab softtabstop=4 tabstop=4 shiftwidth=4 showtabline=2
*
* @author Thomas Martin <tmartin@evolix.fr>
* @author Sebastien Palma <spalma@evolix.fr>
* @version 0.1
*/
class bounceParser {
private $content = NULL;
private $bounced_mail = NULL;
private $smtp_error_code = NULL;
private $status = BOUNCE_UNKN;
private $boundary = NULL;
private $boundaries = array();
private $headers = array();
private $mailer = NULL;
private $bounce_server_answer = NULL;
private $bounce_reason = NULL;
private $message_id = NULL;
private $bounce_official_reasons = array (
'00' => 'Not Applicable',
'10' => 'Other address status',
'11' => 'Bad destination mailbox address',
'12' => 'Bad destination system address',
'13' => 'Bad destination mailbox address syntax',
'14' => 'Destination mailbox address ambiguous',
'15' => 'Destination mailbox address valid',
'16' => 'Mailbox has moved',
'17' => 'Bad sender\'s mailbox address syntax',
'18' => 'Bad sender\'s system address',
'20' => 'Other or undefined mailbox status',
'21' => 'Mailbox disabled, not accepting messages',
'22' => 'Mailbox full',
'23' => 'Message length exceeds administrative limit.',
'24' => 'Mailing list expansion problem',
'30' => 'Other or undefined mail system status',
'31' => 'Mail system full',
'32' => 'System not accepting network messages',
'33' => 'System not capable of selected features',
'34' => 'Message too big for system',
'40' => 'Other or undefined network or routing status',
'41' => 'No answer from host',
'42' => 'Bad connection',
'43' => 'Routing server failure',
'44' => 'Unable to route',
'45' => 'Network congestion',
'46' => 'Routing loop detected',
'47' => 'Delivery time expired',
'50' => 'Other or undefined protocol status',
'51' => 'Invalid command',
'52' => 'Syntax error',
'53' => 'Too many recipients',
'54' => 'Invalid command arguments',
'55' => 'Wrong protocol version',
'60' => 'Other or undefined media error',
'61' => 'Media not supported',
'62' => 'Conversion required and prohibited',
'63' => 'Conversion required but not supported',
'64' => 'Conversion with loss performed',
'65' => 'Conversion failed',
'70' => 'Other or undefined security status',
'71' => 'Delivery not authorized, message refused',
'72' => 'Mailing list expansion prohibited',
'73' => 'Security conversion required but not possible',
'74' => 'Security features not supported',
'75' => 'Cryptographic failure',
'76' => 'Cryptographic algorithm not supported',
'77' => 'Message integrity failure',
);
public function __construct($content) {
$this->content = $content;
}
public function parse() {
$this->sanitize();
$this->message_id = $this->findMessageID();
$this->status = $this->findStatus();
}
private function sanitize() {
// Sépare les parties de mail par boundary
$this->boundary = '--'.$this->findBoundary();
// Nettoie les parties en mettant tout sur une ligne
// pour améliorer la rapidité des regex
$this->boundaries = $this->splitAndCleanBoundaries($this->boundary);
// Décodage des textes
// TODO
}
private function findMessageID() {
if (preg_match('/.*Message-Id: <([^>]*)>.*$/', $this->boundaries[0], $bounce_infos) == 1) {
$message_id = $bounce_infos[1];
}
return $message_id;
}
private function findBoundary() {
$boundary='';
// Recherche du délimiteur
preg_match('/boundary="(.*?)"/', $this->content, $preg_results);
// Si on le trouve, on le renvoie
if ($preg_results[1] != '') {
$boundary = $preg_results[1];
} else {
// TODO: Gestion des erreurs ?
}
return $boundary;
}
private function splitAndCleanBoundaries($boundary_delimiter) {
// Decoupage du mail par boundary
$boundaries = array();
$boundaries_orig = explode($boundary_delimiter, $this->content);
// Nettoyage du boundary
foreach($boundaries_orig as $boundary_part) {
$boundary = $boundary_part;
// On met tout le boundary sur une seule ligne
$boundary = preg_replace('/\n/m', ' ', $boundary);
// On remplace toutes les tabulations par un espace
$boundary = preg_replace('/\t/', ' ', $boundary);
// Suppression des doubles espaces
$boundary = preg_replace('/\s{2,}/', ' ', $boundary);
// "Trim"
$boundary = preg_replace('/^\s*/', '', $boundary);
$boundary = preg_replace('/\s*$/', '', $boundary);
// Et on le stocke dans un tableau
$boundaries[] = $boundary;
}
return $boundaries;
}
private function findStatus () {
// Analyse du bounce
global $bounce_regex;
// Pour le moment, on se sait pas si c'est un bounce hard|soft|spam
$score = 9;
// Mail de bounce classique de Postfix
if (preg_match('/.*-Recipient: rfc822;\s?([^\s]*).*Status: ([\d\.]*).*Diagnostic-Code: (.*)$/', $this->boundaries[2], $bounce_infos) == 1) {
$this->mailer = "Postfix";
if (array_key_exists(1, $bounce_infos)) $this->bounced_mail = $bounce_infos[1];
if (array_key_exists(2, $bounce_infos)) $this->smtp_error_code = $bounce_infos[2];
if (array_key_exists(3, $bounce_infos)) $this->bounce_server_answer = $bounce_infos[3];
// Construction de la regexp qui nous assure que c'est un hardbounce
foreach ($bounce_regex as $severity_id => $severity) {
$bounce_pattern='';
foreach ($severity as $count => $pattern) {
if ($bounce_pattern!="") $bounce_pattern.='|';
$bounce_pattern .= $pattern;
}
$bounce_pattern = "/^.*($bounce_pattern).*/";
//print "Pattern : $bounce_pattern\n\n";
if (preg_match($bounce_pattern, $this->boundaries[2])==1) {
//print "FOUND!!!!\n\n";
$this->smtp_error_code="6.0.".$severity_id;
$this->bounce_reason="HardBounce detected by Bounce-Parser";
$score=6;
}
}
if ($this->smtp_error_code != NULL && $this->smtp_error_code != '' && $score==9) {
$score = substr($this->smtp_error_code, 0, 1);
$this->bounce_reason = $this->bounce_official_reasons[substr(str_replace('.', '', $this->smtp_error_code),1,2)];
}
// Test de reconnaissance d'un Out of Office
} elseif (false) {
// Le reste... Bah on sait pas ce que c'est (SPAM?)
} else {
}
return $score;
}
public function getStatus() {
return $this->status;
}
public function getMessageID() {
return $this->message_id;
}
public function getErrorCode() {
return $this->smtp_error_code;
}
public function getBouncedEmail() {
return $this->bounced_mail;
}
public function getBounceReason() {
return $this->bounce_reason;
}
public function getServerAnswer() {
return $this->bounce_server_answer;
}
public function getBoundary() {
return $this->boundary;
}
public function getBoundaries($boundary_id) {
return $this->boundaries[$boundary_id];
}
}
?>

53
lib/storage.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/**
* database object
*
* Copyright (c) 2009 Evolix - Tous droits reserves
*
* $Id: storage.php 26 2010-01-11 10:01:45Z spalma $
* vim: expandtab softtabstop=4 tabstop=4 shiftwidth=4 showtabline=2
*
* @author Thomas Martin <tmartin@evolix.fr>
* @author Sebastien Palma <spalma@evolix.fr>
* @version 0.1
*/
require_once('../lib/Mysql.php');
class databaseStorage {
private $mysql=NULL;
private $connexion=NULL;
public function __construct() {
$this->mysql = new Mysql();
$this->connexion = $this->mysql->MyConnect();
}
public function store_in_db($bounce, $mailbox='') {
$sql = "INSERT INTO `bounces";
if ($mailbox!='') $sql .= "_$mailbox";
$sql.= "` VALUES(NULL,";
$sql.= "'".$bounce->getStatus()."',";
$sql.= "'".$bounce->getMessageID()."',";
$sql.= "'".$bounce->getErrorCode()."',";
$sql.= "'".$bounce->getBounceReason()."',";
$sql.= "'".$bounce->getServerAnswer()."',";
$sql.= "'".date('Y-m-d H:i:s')."',";
$sql.= "'".$bounce->getBouncedEmail()."',";
$domain='';
$email_parts = explode('@', $bounce->getBouncedEmail());
if (is_array($email_parts) && array_key_exists(1, $email_parts)) $domain=$email_parts[1];
$sql.= "'".$domain."'";
$sql.= ")";
//echo "$sql\n";
$this->mysql->MyReq($this->connexion, $sql);
}
}