import asyncore
import email
import email.Message
-from email.Iterators import typed_subpart_iterator, _structure
-import re
+import os, re, sys
+from email.Iterators import typed_subpart_iterator, _structure
from smtpd import PureProxy
+import ConfigParser
+import MySQLdb
+
IGNORE = 0
NOTICE = 1
-TEMPORARY = 2
-PERMANENT = 3
+ERROR = 2
FROM_PORT = 20024
-TO_PORT = 20025
+TO_HOST = 'olympe.madism.org'
+TO_PORT = 25
+
+
+################################################################################
+#
+# Functions
+#
+#-------------------------------------------------------------------------------
+
+config = ConfigParser.ConfigParser()
+config.read(os.path.dirname(__file__)+'/../configs/platal.conf')
+
+def get_config(sec,val,default=None):
+ try:
+ return config.get(sec, val)[1:-1]
+ except ConfigParser.NoOptionError, e:
+ if default is None:
+ print e
+ sys.exit(1)
+ else:
+ return default
+
+def connectDB():
+ db = MySQLdb.connect(
+ db = 'x4dat',
+ user = get_config('Core', 'dbuser'),
+ passwd = get_config('Core', 'dbpwd'),
+ unix_socket='/var/run/mysqld/mysqld.sock')
+ db.ping()
+ return db.cursor()
def msg_of_str(data): return email.message_from_string(data, _class=BounceMessage)
+################################################################################
+#
+# Classes
+#
+#-------------------------------------------------------------------------------
+
class BounceMessage(email.Message.Message):
def body(self):
"""this method returns the part that is commonely designed as the 'body'
def error_level(self):
"""determine the level of an error:
- NOTICE == vacation, or any informative message we want to forward as is
- TEMPORARY == temporary failure, fixable (e.g. over quota)
- PERMANENT == permanent failure, broken for good (e.g. account do not exists)
+ IGNORE == drop the mail
+ NOTICE == vacation, or any informative message we want to forward as is
+ ERROR == errors, that we want to handle
+ """
+
+ body = self.body()
+ if not body:
+ return (IGNORE, '')
+
+ mysql.execute ( "SELECT lvl,re,text FROM emails_bounces_re ORDER BY pos" )
+ nb = int(mysql.rowcount)
+ for x in range(0,nb):
+ row = mysql.fetchone()
+ rxp = re.compile(str(row[1]))
+ if rxp.match(body):
+ return (int(row[0]), str(row[2]))
+
+ return (NOTICE, '')
+
+ def forge_error(self, txt):
+ """we have to do our little treatments for the broken mail,
+ and then we create an informative message for the original SENDER to :
+ - explain to him what happened (the detailed error)
+ - try to guess if the user may or may not have had the mail (by another leg)
+ - if no other leg, give an information to the SENDER on how he can give to us a real good leg
+ and attach any sensible information about the original mail (@see attached_mail)
"""
raise NotImplementedError
Case 0: the error is IGNORE : return None
Case 1: the error is NOTICE : we just return self
-
- Case 2: we have to do our little treatments for the broken mail,
- and then we create an informative message for the original SENDER to :
- - explain to him what happened (the detailed error)
- - try to guess if the user may or may not have had the mail (by another leg)
- - if no other leg, give an information to the SENDER on how he can give to us a real good leg
- and attach any sensible information about the original mail (@see attached_mail)
+ Case 2: we have a REAL error: use forge_error
"""
- if self.error_level() is IGNORE:
- return None
- elif self.error_level() is NOTICE:
- return self
- elif self.error_level() in [ TEMPORARY , PERMANENT ] :
- raise NotImplementedError
- else:
- raise
+ lvl, txt = self.error_level()
+
+ if lvl is IGNORE: return None
+ elif lvl is NOTICE: return self
+ elif lvl is ERROR : return self.forge_error(txt)
+ else: raise
+
class BounceProxy(PureProxy):
def __init__(self, localaddr, remoteaddr):
def process_message(self, peer, mailfrom, rcpttos, data):
try:
alias, sender, dest = self.process_rcpt(rcpttos)
+ bounce = msg_of_str(data).to_bounce(alias, dest)
+ if bounce is not None:
+ self._deliver("MAILER-DAEMON@bounces.m4x.org", [sender], bounce.as_string())
except:
+ pass
# SPAM or broken msg, we just drop it
- # if we want to return an error uncomment this line :
- #return { int_code: "some error message" }
- return { }
-
- bounce = msg_of_str(data).to_bounce(alias, dest)
- if bounce is None:
- return { }
- else:
- return self._deliver("MAILER-DAEMON@bounces.m4x.org", sender, bounce)
+ return None
+
+################################################################################
+#
+# Main
+#
+#-------------------------------------------------------------------------------
-Proxy = BounceProxy(('127.0.0.1', FROM_PORT), ('127.0.0.1',TO_PORT))
+mysql = connectDB()
+Proxy = BounceProxy(('127.0.0.1', FROM_PORT), (TO_HOST, TO_PORT))
asyncore.loop()
--- /dev/null
+<?php
+/***************************************************************************
+ * Copyright (C) 2003-2004 Polytechnique.org *
+ * http://opensource.polytechnique.org/ *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
+ ***************************************************************************/
+
+require_once('xorg.inc.php');
+new_admin_page('admin/emails_bounces_re.tpl');
+
+if (Post::has('submit')) {
+ foreach (Env::getMixed('lvl') as $id=>$val) {
+ $globals->db->query("REPLACE INTO emails_bounces_re (id,pos,lvl,re,text)
+ VALUES ($id, '{$_POST['pos'][$id]}', '{$_POST['lvl'][$id]}',
+ '{$_POST['re'][$id]}', '{$_POST['text'][$id]}')");
+ }
+}
+
+$page->mysql_assign("SELECT * FROM emails_bounces_re ORDER BY pos", 'bre');
+
+$page->run();
+?>
--- /dev/null
+{***************************************************************************
+ * Copyright (C) 2003-2004 Polytechnique.org *
+ * http://opensource.polytechnique.org/ *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software *
+ * Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
+ ***************************************************************************}
+
+
+<h1>Regexps pour les détections de bounces</h1>
+
+<p>
+Rappel sur les niveaux :
+</p>
+<ul>
+ <li>0: IGNORE == ignorer le bounce</li>
+ <li>1: NOTICE == forwarder le bounce (typiquement vacation)</li>
+ <li>2: ERREUR == erreur</li>
+</ul>
+
+{dynamic}
+
+<form action="{$smarty.server.PHP_SELF}" method="post">
+ <table class="bicol" cellpadding='0' cellspacing='0'>
+ <tr>
+ <th>Position/Niveau</th>
+ <th>Regexp/Raison</th>
+ </tr>
+ {if $smarty.get.new}
+ <tr class="impair">
+ <td>
+ <input type='text' name='pos[NULL]' value='' size='4' maxlength='4' />
+ </td>
+ <td>
+ <input type="text" size="82" name='re[NULL]' value="{$re.re}" />
+ </td>
+ </tr>
+ <tr class="impair">
+ <td style="white-space: nowrap">
+ <input type='radio' name='lvl[NULL]' value='0' {if $re.lvl eq 0}checked="checked"{/if} />
+ <input type='radio' name='lvl[NULL]' value='1' {if $re.lvl eq 1}checked="checked"{/if} />
+ <input type='radio' name='lvl[NULL]' value='2' {if $re.lvl eq 2}checked="checked"{/if} />
+ </td>
+ <td>
+ <input type="text" size="32" name='text[NULL]' value="{$re.text}" />
+ </td>
+ </tr>
+ {else}
+ <tr class="impair">
+ <td colspan="2" class="right action">
+ <a href="?new=1">nouveau</a>
+ </td>
+ </tr>
+ {/if}
+ {foreach from=$bre item=re}
+ <tr class="{cycle values="pair,pair,impair,impair"}">
+ <td>
+ <input type='text' name='pos[{$re.id}]' value='{$re.pos}' size='4' maxlength='4' />
+ </td>
+ <td>
+ <input type="text" size="82" name='re[{$re.id}]' value="{$re.re}" />
+ </td>
+ </tr>
+ <tr class="{cycle values="pair,pair,impair,impair"}">
+ <td style="white-space: nowrap">
+ <input type='radio' name='lvl[{$re.id}]' value='0' {if $re.lvl eq 0}checked="checked"{/if} />
+ <input type='radio' name='lvl[{$re.id}]' value='1' {if $re.lvl eq 1}checked="checked"{/if} />
+ <input type='radio' name='lvl[{$re.id}]' value='2' {if $re.lvl eq 2}checked="checked"{/if} />
+ </td>
+ <td>
+ <input type="text" size="32" name='text[{$re.id}]' value="{$re.text}" /><br />
+ </td>
+ </tr>
+ {/foreach}
+ <tr class="{cycle values="pair,impair"}">
+ <td colspan="2" class="center">
+ <input type="submit" value="valider" name="submit" />
+ </td>
+ </tr>
+ </table>
+</form>
+
+{/dynamic}
+
+{* vim:set et sw=2 sts=2 sws=2: *}
--- /dev/null
+create table emails_bounces_re (
+ id int not null auto_increment,
+ pos smallint unsigned not null default 0,
+ lvl tinyint unsigned not null default 0,
+ re text not null,
+ text varchar(255) not null,
+ primary key (id),
+ index (lvl),
+ index (pos)
+);
+
+insert into admin_a values(1,'Regexps Bounces', 'admin/emails_bounces_re.php', 30);