From 6ad699ffc5feadb773d3a2d41fc173d30662ec87 Mon Sep 17 00:00:00 2001 From: "Pierre Habouzit (MadCoder" Date: Mon, 20 Dec 2004 22:31:37 +0000 Subject: [PATCH] some progress on the smtp bounce proxy we don't want to make distinction between permanent or temporary errors, because we should add a "unknown" level, and that will be too complicated for really to little gain. so we have basicaly : - ignore (drop the mails) - notice (forward the "bounce" as is - default) - error (do the broken leg + forge a new bounce) the errors levels are regexp-driven (read from a mysql db) the only thing left is the treatment of the ERROR level (others are fine, and the web interface to edit the regexps too) git-archimport-id: opensource@polytechnique.org--2005/platal--mainline--0.9--patch-117 --- bin/smtp_bounce_proxy.py | 123 +++++++++++++++++++++++++--------- htdocs/admin/emails_bounces_re.php | 36 ++++++++++ templates/admin/emails_bounces_re.tpl | 97 +++++++++++++++++++++++++++ upgrade/0.9.3/85_bounces_proxy.sql | 12 ++++ 4 files changed, 235 insertions(+), 33 deletions(-) create mode 100644 htdocs/admin/emails_bounces_re.php create mode 100644 templates/admin/emails_bounces_re.tpl create mode 100644 upgrade/0.9.3/85_bounces_proxy.sql diff --git a/bin/smtp_bounce_proxy.py b/bin/smtp_bounce_proxy.py index 01f48b8..28ae641 100755 --- a/bin/smtp_bounce_proxy.py +++ b/bin/smtp_bounce_proxy.py @@ -3,21 +3,59 @@ 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' @@ -90,9 +128,32 @@ class BounceMessage(email.Message.Message): 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 @@ -104,22 +165,15 @@ class BounceMessage(email.Message.Message): 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): @@ -138,19 +192,22 @@ class BounceProxy(PureProxy): 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() diff --git a/htdocs/admin/emails_bounces_re.php b/htdocs/admin/emails_bounces_re.php new file mode 100644 index 0000000..d5537d0 --- /dev/null +++ b/htdocs/admin/emails_bounces_re.php @@ -0,0 +1,36 @@ +$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(); +?> diff --git a/templates/admin/emails_bounces_re.tpl b/templates/admin/emails_bounces_re.tpl new file mode 100644 index 0000000..bf09bee --- /dev/null +++ b/templates/admin/emails_bounces_re.tpl @@ -0,0 +1,97 @@ +{*************************************************************************** + * 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 * + ***************************************************************************} + + +

Regexps pour les détections de bounces

+ +

+Rappel sur les niveaux : +

+ + +{dynamic} + +
+ + + + + + {if $smarty.get.new} + + + + + + + + + {else} + + + + {/if} + {foreach from=$bre item=re} + + + + + + + + + {/foreach} + + + +
Position/NiveauRegexp/Raison
+ + + +
+ + + + + +
+ nouveau +
+ + + +
+ + + + +
+
+ +
+
+ +{/dynamic} + +{* vim:set et sw=2 sts=2 sws=2: *} diff --git a/upgrade/0.9.3/85_bounces_proxy.sql b/upgrade/0.9.3/85_bounces_proxy.sql new file mode 100644 index 0000000..97eba0b --- /dev/null +++ b/upgrade/0.9.3/85_bounces_proxy.sql @@ -0,0 +1,12 @@ +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); -- 2.1.4