diff --git a/.coveragerc b/.coveragerc
index 26a251fa1..b0b8fe681 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,5 +1,5 @@
[run]
-omit = wcs/ctl/Bouncers/*.py wcs/qommon/vendor/*.py
+omit = wcs/qommon/vendor/*.py
[report]
-omit = wcs/ctl/Bouncers/*.py wcs/qommon/vendor/*.py
+omit = wcs/qommon/vendor/*.py
diff --git a/README b/README
index ef79e624d..944e4ee29 100644
--- a/README
+++ b/README
@@ -41,14 +41,6 @@ AUTHORS file for additional credits.
w.c.s. incorporates some other pieces of code, with their own authors and
copyright notices :
-Email bounce detection code (wcs/ctl/Bounces/*) from Mailman:
- # http://www.gnu.org/software/mailman/
- #
- # 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.
-
Some artwork from GTK+:
# http://www.gtk.org/
#
diff --git a/jenkins.sh b/jenkins.sh
index 85a726777..5e14afc5b 100755
--- a/jenkins.sh
+++ b/jenkins.sh
@@ -16,10 +16,10 @@ rm -f coverage.xml
rm -f test_results.xml
cat << _EOF_ > .coveragerc
[run]
-omit = wcs/ctl/Bouncers/*.py wcs/qommon/vendor/*.py
+omit = wcs/qommon/vendor/*.py
[report]
-omit = wcs/ctl/Bouncers/*.py wcs/qommon/vendor/*.py
+omit = wcs/qommon/vendor/*.py
_EOF_
# $PIP_BIN install --upgrade 'pip<8'
diff --git a/tests/test_admin_pages.py b/tests/test_admin_pages.py
index 4e7c859cc..de86c3947 100644
--- a/tests/test_admin_pages.py
+++ b/tests/test_admin_pages.py
@@ -32,7 +32,6 @@ from wcs.qommon.form import UploadedFile
from wcs.qommon.ident.password_accounts import PasswordAccount
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.template import get_current_theme
-from wcs.qommon.bounces import Bounce
from wcs.admin.settings import UserFieldsFormDef
from wcs.categories import Category
from wcs.data_sources import NamedDataSource
@@ -5260,44 +5259,6 @@ def test_settings_theme_download_upload(pub):
assert app.get('/themes/alto/../', status=404)
assert app.get('/themes/xxx/../', status=404)
-def test_bounces(pub):
- create_superuser(pub)
- app = login(get_app(pub))
- resp = app.get('/backoffice/')
- assert not 'bounces' in resp.body
-
- resp = app.get('/backoffice/settings/emails/options')
- resp.form['from'] = 'noreply@localhost'
- resp.form['bounce_handler'].checked = True
- resp = resp.form.submit('submit')
- pub.reload_cfg()
- assert pub.cfg['emails']['bounce_handler']
-
- bounce = Bounce()
- bounce.arrival_time = time.time()
- bounce.bounce_message = 'foobar'
- bounce.addrs = ['foo@localhost']
- bounce.email_type = 'change-password-request'
- bounce.original_rcpts = ['foo@localhost', 'bar@localhost']
- msg = MIMEText('hello world')
- msg['Subject'] = 'hello world'
- bounce.original_message = msg.as_string()
- bounce.store()
-
- resp = app.get('/backoffice/')
- assert 'bounces' in resp.body
- resp = resp.click('Bounces')
- resp = resp.click(href='%s/' % bounce.id, index=0)
- assert 'hello world' in resp.body
-
- resp = app.get('/backoffice/bounces/')
- resp = resp.click(href='%s/delete' % bounce.id, index=0)
- resp = resp.form.submit('cancel').follow()
- assert Bounce.count() == 1
- resp = resp.click(href='%s/delete' % bounce.id, index=0)
- resp = resp.form.submit('delete')
- assert Bounce.count() == 0
-
def test_postgresql_settings(pub):
if not pub.is_using_postgresql():
pytest.skip('this requires SQL')
diff --git a/tests/test_bounce_processing.py b/tests/test_bounce_processing.py
deleted file mode 100644
index 35a680daf..000000000
--- a/tests/test_bounce_processing.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import email
-
-from wcs.ctl.process_bounce import CmdProcessBounce
-
-def test_normal_email():
- msg = email.message_from_string('test')
- msg['From'] = 'bar@example.net'
- msg['To'] = 'foo@example.net'
- addrs = CmdProcessBounce.get_bounce_addrs(msg)
- assert addrs is None
-
-def test_bounce_email():
- msg = email.message_from_string('test')
- msg['From'] = 'bar@example.net'
- msg['To'] = 'foo@example.net'
-
- # this is how exim adds failed recipients in its outgoing bounce emails
- msg['x-failed-recipients'] = 'baz@example.net'
-
- addrs = CmdProcessBounce.get_bounce_addrs(msg)
- assert addrs == ['baz@example.net']
diff --git a/tests/test_ctl.py b/tests/test_ctl.py
index a14418566..5f6721f2f 100644
--- a/tests/test_ctl.py
+++ b/tests/test_ctl.py
@@ -16,7 +16,6 @@ from wcs.qommon.management.commands.collectstatic import Command as CmdCollectSt
from wcs.qommon.management.commands.migrate import Command as CmdMigrate
from wcs.qommon.management.commands.migrate_schemas import Command as CmdMigrateSchemas
from wcs.qommon.management.commands.makemessages import Command as CmdMakeMessages
-from wcs.ctl.process_bounce import CmdProcessBounce
from wcs.ctl.rebuild_indexes import rebuild_vhost_indexes
from wcs.ctl.wipe_data import CmdWipeData
from wcs.ctl.management.commands.runscript import Command as CmdRunScript
@@ -67,24 +66,6 @@ def test_migrate(two_pubs):
def test_migrate_schemas(two_pubs):
CmdMigrateSchemas().handle()
-def test_get_bounce_addrs():
- msg = MIMEText('Hello world')
- assert CmdProcessBounce.get_bounce_addrs(msg) is None
-
- msg = MIMEMultipart(_subtype='mixed')
- msg.attach(MIMEText('Hello world'))
- msg.attach(MIMEText('
Hello world
', _subtype='html'))
- assert CmdProcessBounce.get_bounce_addrs(msg) is None
-
- msg = MIMEText('Hello world')
- msg['x-failed-recipients'] = 'foobar@localhost'
- assert CmdProcessBounce.get_bounce_addrs(msg) == ['foobar@localhost']
-
- msg = MIMEText('''failed addresses follow:
- foobar@localhost
- message text follows:''')
- assert CmdProcessBounce.get_bounce_addrs(msg) == ['foobar@localhost']
-
def test_wipe_formdata(pub):
form_1 = FormDef()
form_1.name = 'example'
diff --git a/wcs/admin/bounces.py b/wcs/admin/bounces.py
deleted file mode 100644
index 78b434c5a..000000000
--- a/wcs/admin/bounces.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2010 Entr'ouvert
-#
-# 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, see .
-
-import time
-import email.Parser
-
-from django.utils.encoding import force_text
-
-from quixote import get_response, redirect
-from quixote.directory import Directory
-from quixote.html import htmltext, TemplateIO
-
-from ..qommon import _
-from ..qommon import errors
-from ..qommon import misc
-from ..qommon.bounces import Bounce
-from ..qommon.backoffice.menu import html_top
-from ..qommon.admin.menu import command_icon
-
-from ..qommon.form import *
-from ..qommon.misc import get_cfg
-
-def get_email_type_label(type):
- from .settings import EmailsDirectory
- return EmailsDirectory.emails_dict.get(type, {}).get('description')
-
-class BouncePage(Directory):
- _q_exports = ['', 'delete']
-
- def __init__(self, component):
- self.bounce = Bounce.get(component)
- get_response().breadcrumb.append((component + '/', _('bounce')))
-
-
- def _q_index(self):
- html_top('bounces', title = _('Bounce'))
- r = TemplateIO(html=True)
-
- r += htmltext('')
-
- if self.bounce.email_type:
- r += htmltext('
%s
') % _('Email Type')
- r += htmltext('
')
- r += _(get_email_type_label(self.bounce.email_type))
- r += htmltext('
')
-
- r += htmltext('
%s
') % _('Arrival Time')
- r += htmltext('
')
- r += misc.localstrftime(time.localtime(self.bounce.arrival_time))
- r += htmltext('
')
-
- if self.bounce.addrs:
- r += htmltext('
%s
') % _('Failed Addresses')
- r += htmltext('
')
- r += ', '.join(self.bounce.addrs)
- r += htmltext('
')
-
- if self.bounce.original_rcpts:
- r += htmltext('
%s
') % _('Original Recipients')
- r += htmltext('
')
- r += ', '.join(self.bounce.original_rcpts)
- r += htmltext('
')
-
- r += htmltext('
%s
') % _('Bounce Message')
- r += htmltext('
')
- r += htmltext('
')
- r += self.bounce.bounce_message
- r += htmltext('
')
- r += htmltext('
')
-
- if self.bounce.original_message:
- parser = email.Parser.Parser()
-
- msg = parser.parsestr(self.bounce.original_message)
- if msg.is_multipart():
- for m in msg.get_payload():
- if m.get_content_type() == 'text/plain':
- break
- else:
- m = None
- elif msg.get_content_type() == 'text/plain':
- m = msg
- else:
- m = None
-
- r += htmltext('
%s
') % _('Original Message')
- r += htmltext('
')
- r += _('Subject: ')
- subject, charset = email.Header.decode_header(msg['Subject'])[0]
- if charset:
- encoding = get_publisher().site_charset
- r += force_text(subject, charset).encode(encoding)
- else:
- r += subject
- r += htmltext('
')
-
- if m:
- r += htmltext('
')
- r += htmltext('
')
- r += m.get_payload()
- r += htmltext('
')
- r += htmltext('
')
-
- r += htmltext('
') # form
- return r.getvalue()
-
-
- def delete(self):
- form = Form(enctype='multipart/form-data')
- form.widgets.append(HtmlWidget('%s
' % _(
- 'You are about to irrevocably delete this bounce.')))
- form.add_submit('delete', _('Delete'))
- form.add_submit('cancel', _('Cancel'))
- if form.get_widget('cancel').parse():
- return redirect('..')
- if not form.is_submitted() or form.has_errors():
- get_response().breadcrumb.append(('delete', _('Delete')))
- html_top('bounces', title = _('Delete Bounce'))
- r = TemplateIO(html=True)
- r += htmltext('%s
') % _('Deleting Bounce')
- r += form.render()
- return r.getvalue()
- else:
- self.bounce.remove_self()
- return redirect('..')
-
-
-
-class BouncesDirectory(Directory):
- _q_exports = ['']
-
- def _q_traverse(self, path):
- get_response().breadcrumb.append( ('bounces/', _('Bounces')) )
- return Directory._q_traverse(self, path)
-
- def is_visible(self, *args):
- return bool(get_cfg('emails', {}).get('bounce_handler') is True)
-
- def _q_index(self):
- html_top('bounces', title = _('Bounces'))
-
- bounces = Bounce.select(ignore_errors=True)
- bounces.sort(lambda x,y: cmp(x.arrival_time, y.arrival_time))
-
- r = TemplateIO(html=True)
- r += htmltext('')
- for bounce in bounces:
- r += htmltext('- ')
- r += htmltext('')
- r += misc.localstrftime(time.localtime(bounce.arrival_time))
- if bounce.email_type:
- r += ' - '
- r += _(get_email_type_label(bounce.email_type))
- r += htmltext('')
- r += htmltext('
')
- if bounce.addrs:
- r += ', '.join(bounce.addrs)
- r += htmltext('
')
-
- r += htmltext('')
- r += command_icon('%s/' % bounce.id, 'view')
- r += command_icon('%s/delete' % bounce.id, 'remove', popup = True)
- r += htmltext('
')
- r += htmltext('
')
- return r.getvalue()
-
- def _q_lookup(self, component):
- try:
- return BouncePage(component)
- except KeyError:
- raise errors.TraversalError()
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index 6230b4ab1..66fec0c04 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -554,7 +554,6 @@ class SettingsDirectory(QommonSettingsDirectory):
('users', N_('Users')),
('roles', N_('Roles')),
('categories', N_('Categories')),
- ('bounces', N_('Bounces')),
('settings', N_('Settings')),
]
for k, v in admin_sections:
diff --git a/wcs/backoffice/root.py b/wcs/backoffice/root.py
index 2163738db..8ed1192b0 100644
--- a/wcs/backoffice/root.py
+++ b/wcs/backoffice/root.py
@@ -29,7 +29,6 @@ from ..qommon.form import *
from wcs.formdef import FormDef
-import wcs.admin.bounces
import wcs.admin.categories
import wcs.admin.forms
import wcs.admin.roles
@@ -47,7 +46,6 @@ from . import data_management
class RootDirectory(BackofficeRootDirectory):
_q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json')]
- bounces = wcs.admin.bounces.BouncesDirectory()
forms = wcs.admin.forms.FormsDirectory()
roles = wcs.admin.roles.RolesDirectory()
settings = wcs.admin.settings.SettingsDirectory()
@@ -69,7 +67,6 @@ class RootDirectory(BackofficeRootDirectory):
('workflows/', N_('Workflows Workshop'), {'sub': True}),
('users/', N_('Users'), {'check_display_function': roles.is_visible}),
('roles/', N_('Roles'), {'check_display_function': roles.is_visible}),
- ('bounces/', N_('Bounces'), {'check_display_function': bounces.is_visible}),
('settings/', N_('Settings')),
]
diff --git a/wcs/ctl/Bouncers/BouncerAPI.py b/wcs/ctl/Bouncers/BouncerAPI.py
deleted file mode 100644
index fc51597ec..000000000
--- a/wcs/ctl/Bouncers/BouncerAPI.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Contains all the common functionality for msg bounce scanning API.
-
-This module can also be used as the basis for a bounce detection testing
-framework. When run as a script, it expects two arguments, the listname and
-the filename containing the bounce message.
-
-"""
-
-import sys
-
-
-# If a bounce detector returns Stop, that means to just discard the message.
-# An example is warning messages for temporary delivery problems. These
-# shouldn't trigger a bounce notification, but we also don't want to send them
-# on to the list administrator.
-class _Stop:
- pass
-Stop = _Stop()
-
-
-BOUNCE_PIPELINE = [
- 'DSN',
- 'Qmail',
- 'Postfix',
- 'Yahoo',
- 'Caiwireless',
- 'Exchange',
- 'Exim',
- 'Netscape',
- 'Compuserve',
- 'Microsoft',
- 'GroupWise',
- 'SMTP32',
- 'SimpleMatch',
- 'SimpleWarning',
- 'Yale',
- 'LLNL',
- ]
-
-
-
-# msg must be a mimetools.Message
-def ScanMessages(mlist, msg):
- for module in BOUNCE_PIPELINE:
- modname = 'Mailman.Bouncers.' + module
- __import__(modname)
- addrs = sys.modules[modname].process(msg)
- if addrs:
- # Return addrs even if it is Stop. BounceRunner needs this info.
- return addrs
- return []
diff --git a/wcs/ctl/Bouncers/Caiwireless.py b/wcs/ctl/Bouncers/Caiwireless.py
deleted file mode 100644
index b565f29be..000000000
--- a/wcs/ctl/Bouncers/Caiwireless.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Parse mystery style generated by MTA at caiwireless.net."""
-
-import re
-import email
-from cStringIO import StringIO
-
-tcre = re.compile(r'the following recipients did not receive this message:',
- re.IGNORECASE)
-acre = re.compile(r'<(?P[^>]*)>')
-
-
-
-def process(msg):
- if msg.get_content_type() <> 'multipart/mixed':
- return None
- # simple state machine
- # 0 == nothing seen
- # 1 == tag line seen
- state = 0
- # This format thinks it's a MIME, but it really isn't
- for line in email.Iterators.body_line_iterator(msg):
- line = line.strip()
- if state == 0 and tcre.match(line):
- state = 1
- elif state == 1 and line:
- mo = acre.match(line)
- if not mo:
- return None
- return [mo.group('addr')]
diff --git a/wcs/ctl/Bouncers/Compuserve.py b/wcs/ctl/Bouncers/Compuserve.py
deleted file mode 100644
index 89085d2c3..000000000
--- a/wcs/ctl/Bouncers/Compuserve.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Compuserve has its own weird format for bounces."""
-
-import re
-import email
-
-dcre = re.compile(r'your message could not be delivered', re.IGNORECASE)
-acre = re.compile(r'Invalid receiver address: (?P.*)')
-
-
-
-def process(msg):
- # simple state machine
- # 0 = nothing seen yet
- # 1 = intro line seen
- state = 0
- addrs = []
- for line in email.Iterators.body_line_iterator(msg):
- if state == 0:
- mo = dcre.search(line)
- if mo:
- state = 1
- elif state == 1:
- mo = dcre.search(line)
- if mo:
- break
- mo = acre.search(line)
- if mo:
- addrs.append(mo.group('addr'))
- return addrs
diff --git a/wcs/ctl/Bouncers/DSN.py b/wcs/ctl/Bouncers/DSN.py
deleted file mode 100644
index 3e8e4fd99..000000000
--- a/wcs/ctl/Bouncers/DSN.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Parse RFC 3464 (i.e. DSN) bounce formats.
-
-RFC 3464 obsoletes 1894 which was the old DSN standard. This module has not
-been audited for differences between the two.
-"""
-
-from email.Iterators import typed_subpart_iterator
-from email.Utils import parseaddr
-from cStringIO import StringIO
-
-from BouncerAPI import Stop
-
-try:
- True, False
-except NameError:
- True = 1
- False = 0
-
-
-
-def check(msg):
- # Iterate over each message/delivery-status subpart
- addrs = []
- for part in typed_subpart_iterator(msg, 'message', 'delivery-status'):
- if not part.is_multipart():
- # Huh?
- continue
- # Each message/delivery-status contains a list of Message objects
- # which are the header blocks. Iterate over those too.
- for msgblock in part.get_payload():
- # We try to dig out the Original-Recipient (which is optional) and
- # Final-Recipient (which is mandatory, but may not exactly match
- # an address on our list). Some MTA's also use X-Actual-Recipient
- # as a synonym for Original-Recipient, but some apparently use
- # that for other purposes :(
- #
- # Also grok out Action so we can do something with that too.
- action = msgblock.get('action', '').lower()
- # Some MTAs have been observed that put comments on the action.
- if action.startswith('delayed'):
- return Stop
- if not action.startswith('fail'):
- # Some non-permanent failure, so ignore this block
- continue
- params = []
- foundp = False
- for header in ('original-recipient', 'final-recipient'):
- for k, v in msgblock.get_params([], header):
- if k.lower() == 'rfc822':
- foundp = True
- else:
- params.append(k)
- if foundp:
- # Note that params should already be unquoted.
- addrs.extend(params)
- break
- else:
- # MAS: This is a kludge, but SMTP-GATEWAY01.intra.home.dk
- # has a final-recipient with an angle-addr and no
- # address-type parameter at all. Non-compliant, but ...
- for param in params:
- if param.startswith('<') and param.endswith('>'):
- addrs.append(param[1:-1])
- # Uniquify
- rtnaddrs = {}
- for a in addrs:
- if a is not None:
- realname, a = parseaddr(a)
- rtnaddrs[a] = True
- return rtnaddrs.keys()
-
-
-
-def process(msg):
- # A DSN has been seen wrapped with a "legal disclaimer" by an outgoing MTA
- # in a multipart/mixed outer part.
- if msg.is_multipart() and msg.get_content_subtype() == 'mixed':
- msg = msg.get_payload()[0]
- # The report-type parameter should be "delivery-status", but it seems that
- # some DSN generating MTAs don't include this on the Content-Type: header,
- # so let's relax the test a bit.
- if not msg.is_multipart() or msg.get_content_subtype() <> 'report':
- return None
- return check(msg)
diff --git a/wcs/ctl/Bouncers/Exchange.py b/wcs/ctl/Bouncers/Exchange.py
deleted file mode 100644
index 2f6eeda09..000000000
--- a/wcs/ctl/Bouncers/Exchange.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Recognizes (some) Microsoft Exchange formats."""
-
-import re
-import email.Iterators
-
-scre = re.compile('did not reach the following recipient')
-ecre = re.compile('MSEXCH:')
-a1cre = re.compile('SMTP=(?P[^;]+); on ')
-a2cre = re.compile('(?P[^ ]+) on ')
-
-
-
-def process(msg):
- addrs = {}
- it = email.Iterators.body_line_iterator(msg)
- # Find the start line
- for line in it:
- if scre.search(line):
- break
- else:
- return []
- # Search each line until we hit the end line
- for line in it:
- if ecre.search(line):
- break
- mo = a1cre.search(line)
- if not mo:
- mo = a2cre.search(line)
- if mo:
- addrs[mo.group('addr')] = 1
- return addrs.keys()
diff --git a/wcs/ctl/Bouncers/Exim.py b/wcs/ctl/Bouncers/Exim.py
deleted file mode 100644
index ce677385c..000000000
--- a/wcs/ctl/Bouncers/Exim.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Parse bounce messages generated by Exim.
-
-Exim adds an X-Failed-Recipients: header to bounce messages containing
-an `addresslist' of failed addresses.
-
-"""
-
-from email.Utils import getaddresses
-
-
-
-def process(msg):
- all = msg.get_all('x-failed-recipients', [])
- return [a for n, a in getaddresses(all)]
diff --git a/wcs/ctl/Bouncers/GroupWise.py b/wcs/ctl/Bouncers/GroupWise.py
deleted file mode 100644
index eb4075f1d..000000000
--- a/wcs/ctl/Bouncers/GroupWise.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""This appears to be the format for Novell GroupWise and NTMail
-
-X-Mailer: Novell GroupWise Internet Agent 5.5.3.1
-X-Mailer: NTMail v4.30.0012
-X-Mailer: Internet Mail Service (5.5.2653.19)
-"""
-
-import re
-from email.Message import Message
-from cStringIO import StringIO
-
-acre = re.compile(r'<(?P[^>]*)>')
-
-
-
-def find_textplain(msg):
- if msg.get_content_type() == 'text/plain':
- return msg
- if msg.is_multipart:
- for part in msg.get_payload():
- if not isinstance(part, Message):
- continue
- ret = find_textplain(part)
- if ret:
- return ret
- return None
-
-
-
-def process(msg):
- if msg.get_content_type() <> 'multipart/mixed' or not msg['x-mailer']:
- return None
- addrs = {}
- # find the first text/plain part in the message
- textplain = find_textplain(msg)
- if not textplain:
- return None
- body = StringIO(textplain.get_payload())
- while 1:
- line = body.readline()
- if not line:
- break
- mo = acre.search(line)
- if mo:
- addrs[mo.group('addr')] = 1
- elif '@' in line:
- i = line.find(' ')
- if i == 0:
- continue
- if i < 0:
- addrs[line] = 1
- else:
- addrs[line[:i]] = 1
- return addrs.keys()
diff --git a/wcs/ctl/Bouncers/LLNL.py b/wcs/ctl/Bouncers/LLNL.py
deleted file mode 100644
index 97c56e222..000000000
--- a/wcs/ctl/Bouncers/LLNL.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""LLNL's custom Sendmail bounce message."""
-
-import re
-import email
-
-acre = re.compile(r',\s*(?P\S+@[^,]+),', re.IGNORECASE)
-
-
-
-def process(msg):
- for line in email.Iterators.body_line_iterator(msg):
- mo = acre.search(line)
- if mo:
- return [mo.group('addr')]
- return []
diff --git a/wcs/ctl/Bouncers/Microsoft.py b/wcs/ctl/Bouncers/Microsoft.py
deleted file mode 100644
index f5adcf5ce..000000000
--- a/wcs/ctl/Bouncers/Microsoft.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Microsoft's `SMTPSVC' nears I kin tell."""
-
-import re
-from cStringIO import StringIO
-from types import ListType
-
-scre = re.compile(r'transcript of session follows', re.IGNORECASE)
-
-
-
-def process(msg):
- if msg.get_content_type() <> 'multipart/mixed':
- return None
- # Find the first subpart, which has no MIME type
- try:
- subpart = msg.get_payload(0)
- except IndexError:
- # The message *looked* like a multipart but wasn't
- return None
- data = subpart.get_payload()
- if isinstance(data, ListType):
- # The message is a multi-multipart, so not a matching bounce
- return None
- body = StringIO(data)
- state = 0
- addrs = []
- while 1:
- line = body.readline()
- if not line:
- break
- if state == 0:
- if scre.search(line):
- state = 1
- if state == 1:
- if '@' in line:
- addrs.append(line)
- return addrs
diff --git a/wcs/ctl/Bouncers/Netscape.py b/wcs/ctl/Bouncers/Netscape.py
deleted file mode 100644
index b7f29e81b..000000000
--- a/wcs/ctl/Bouncers/Netscape.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Netscape Messaging Server bounce formats.
-
-I've seen at least one NMS server version 3.6 (envy.gmp.usyd.edu.au) bounce
-messages of this format. Bounces come in DSN MIME format, but don't include
-any -Recipient: headers. Gotta just parse the text :(
-
-NMS 4.1 (dfw-smtpin1.email.verio.net) seems even worse, but we'll try to
-decipher the format here too.
-
-"""
-
-import re
-from cStringIO import StringIO
-
-pcre = re.compile(
- r'This Message was undeliverable due to the following reason:',
- re.IGNORECASE)
-
-acre = re.compile(
- r'(?Pplease reply to)?.*<(?P[^>]*)>',
- re.IGNORECASE)
-
-
-
-def flatten(msg, leaves):
- # give us all the leaf (non-multipart) subparts
- if msg.is_multipart():
- for part in msg.get_payload():
- flatten(part, leaves)
- else:
- leaves.append(msg)
-
-
-
-def process(msg):
- # Sigh. Some show NMS 3.6's show
- # multipart/report; report-type=delivery-status
- # and some show
- # multipart/mixed;
- if not msg.is_multipart():
- return None
- # We're looking for a text/plain subpart occuring before a
- # message/delivery-status subpart.
- plainmsg = None
- leaves = []
- flatten(msg, leaves)
- for i, subpart in zip(range(len(leaves)-1), leaves):
- if subpart.get_content_type() == 'text/plain':
- plainmsg = subpart
- break
- if not plainmsg:
- return None
- # Total guesswork, based on captured examples...
- body = StringIO(plainmsg.get_payload())
- addrs = []
- while 1:
- line = body.readline()
- if not line:
- break
- mo = pcre.search(line)
- if mo:
- # We found a bounce section, but I have no idea what the official
- # format inside here is. :( We'll just search for
- # strings.
- while 1:
- line = body.readline()
- if not line:
- break
- mo = acre.search(line)
- if mo and not mo.group('reply'):
- addrs.append(mo.group('addr'))
- return addrs
diff --git a/wcs/ctl/Bouncers/Postfix.py b/wcs/ctl/Bouncers/Postfix.py
deleted file mode 100644
index 1d5e638f3..000000000
--- a/wcs/ctl/Bouncers/Postfix.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Parse bounce messages generated by Postfix.
-
-This also matches something called `Keftamail' which looks just like Postfix
-bounces with the word Postfix scratched out and the word `Keftamail' written
-in in crayon.
-
-It also matches something claiming to be `The BNS Postfix program', and
-`SMTP_Gateway'. Everybody's gotta be different, huh?
-"""
-
-import re
-from cStringIO import StringIO
-
-
-
-def flatten(msg, leaves):
- # give us all the leaf (non-multipart) subparts
- if msg.is_multipart():
- for part in msg.get_payload():
- flatten(part, leaves)
- else:
- leaves.append(msg)
-
-
-
-# are these heuristics correct or guaranteed?
-pcre = re.compile(r'[ \t]*the\s*(bns)?\s*(postfix|keftamail|smtp_gateway)',
- re.IGNORECASE)
-rcre = re.compile(r'failure reason:$', re.IGNORECASE)
-acre = re.compile(r'<(?P[^>]*)>:')
-
-def findaddr(msg):
- addrs = []
- body = StringIO(msg.get_payload())
- # simple state machine
- # 0 == nothing found
- # 1 == salutation found
- state = 0
- while 1:
- line = body.readline()
- if not line:
- break
- # preserve leading whitespace
- line = line.rstrip()
- # yes use match to match at beginning of string
- if state == 0 and (pcre.match(line) or rcre.match(line)):
- state = 1
- elif state == 1 and line:
- mo = acre.search(line)
- if mo:
- addrs.append(mo.group('addr'))
- # probably a continuation line
- return addrs
-
-
-
-def process(msg):
- if msg.get_content_type() not in ('multipart/mixed', 'multipart/report'):
- return None
- # We're looking for the plain/text subpart with a Content-Description: of
- # `notification'.
- leaves = []
- flatten(msg, leaves)
- for subpart in leaves:
- if subpart.get_content_type() == 'text/plain' and \
- subpart.get('content-description', '').lower() == 'notification':
- # then...
- return findaddr(subpart)
- return None
diff --git a/wcs/ctl/Bouncers/Qmail.py b/wcs/ctl/Bouncers/Qmail.py
deleted file mode 100644
index def4abfa0..000000000
--- a/wcs/ctl/Bouncers/Qmail.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Parse bounce messages generated by qmail.
-
-Qmail actually has a standard, called QSBMF (qmail-send bounce message
-format), as described in
-
- http://cr.yp.to/proto/qsbmf.txt
-
-This module should be conformant.
-
-"""
-
-import re
-import email.Iterators
-
-# Other (non-standard?) intros have been observed in the wild.
-introtags = [
- 'Hi. This is the',
- "We're sorry. There's a problem",
- 'Check your send e-mail address.'
- ]
-acre = re.compile(r'<(?P[^>]*)>:')
-
-
-
-def process(msg):
- addrs = []
- # simple state machine
- # 0 = nothing seen yet
- # 1 = intro paragraph seen
- # 2 = recip paragraphs seen
- state = 0
- for line in email.Iterators.body_line_iterator(msg):
- line = line.strip()
- if state == 0:
- for introtag in introtags:
- if line.startswith(introtag):
- state = 1
- break
- elif state == 1 and not line:
- # Looking for the end of the intro paragraph
- state = 2
- elif state == 2:
- if line.startswith('-'):
- # We're looking at the break paragraph, so we're done
- break
- # At this point we know we must be looking at a recipient
- # paragraph
- mo = acre.match(line)
- if mo:
- addrs.append(mo.group('addr'))
- # Otherwise, it must be a continuation line, so just ignore it
- # Not looking at anything in particular
- return addrs
diff --git a/wcs/ctl/Bouncers/SMTP32.py b/wcs/ctl/Bouncers/SMTP32.py
deleted file mode 100644
index 3ad7ce44c..000000000
--- a/wcs/ctl/Bouncers/SMTP32.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Something which claims
-X-Mailer:
-
-What the heck is this thing? Here's a recent host:
-
-% telnet 207.51.255.218 smtp
-Trying 207.51.255.218...
-Connected to 207.51.255.218.
-Escape character is '^]'.
-220 X1 NT-ESMTP Server 208.24.118.205 (IMail 6.00 45595-15)
-
-"""
-
-import re
-import email
-
-ecre = re.compile('original message follows', re.IGNORECASE)
-acre = re.compile(r'''
- ( # several different prefixes
- user\ mailbox[^:]*: # have been spotted in the
- |delivery\ failed[^:]*: # wild...
- |unknown\ user[^:]*:
- |undeliverable\ +to
- )
- \s* # space separator
- (?P.*) # and finally, the address
- ''', re.IGNORECASE | re.VERBOSE)
-
-
-
-def process(msg):
- mailer = msg.get('x-mailer', '')
- if not mailer.startswith('[^>]*)>')),
- # sz-sb.de, corridor.com, nfg.nl
- (_c('the following addresses had'),
- _c('transcript of session follows'),
- _c(r'<(?P[^>]*)>|\(expanded from: (?P[^>)]*)>?\)')),
- # robanal.demon.co.uk
- (_c('this message was created automatically by mail delivery software'),
- _c('original message follows'),
- _c('rcpt to:\s*<(?P[^>]*)>')),
- # s1.com (InterScan E-Mail VirusWall NT ???)
- (_c('message from interscan e-mail viruswall nt'),
- _c('end of message'),
- _c('rcpt to:\s*<(?P[^>]*)>')),
- # Smail
- (_c('failed addresses follow:'),
- _c('message text follows:'),
- _c(r'\s*(?P\S+@\S+)')),
- # newmail.ru
- (_c('This is the machine generated message from mail service.'),
- _c('--- Below the next line is a copy of the message.'),
- _c('<(?P[^>]*)>')),
- # turbosport.com runs something called `MDaemon 3.5.2' ???
- (_c('The following addresses did NOT receive a copy of your message:'),
- _c('--- Session Transcript ---'),
- _c('[>]\s*(?P.*)$')),
- # usa.net
- (_c('Intended recipient:\s*(?P.*)$'),
- _c('--------RETURNED MAIL FOLLOWS--------'),
- _c('Intended recipient:\s*(?P.*)$')),
- # hotpop.com
- (_c('Undeliverable Address:\s*(?P.*)$'),
- _c('Original message attached'),
- _c('Undeliverable Address:\s*(?P.*)$')),
- # Another demon.co.uk format
- (_c('This message was created automatically by mail delivery'),
- _c('^---- START OF RETURNED MESSAGE ----'),
- _c("addressed to '(?P[^']*)'")),
- # Prodigy.net full mailbox
- (_c("User's mailbox is full:"),
- _c('Unable to deliver mail.'),
- _c("User's mailbox is full:\s*<(?P[^>]*)>")),
- # Microsoft SMTPSVC
- (_c('The email below could not be delivered to the following user:'),
- _c('Old message:'),
- _c('<(?P[^>]*)>')),
- # Yahoo on behalf of other domains like sbcglobal.net
- (_c('Unable to deliver message to the following address\(es\)\.'),
- _c('--- Original message follows\.'),
- _c('<(?P[^>]*)>:')),
- # kundenserver.de
- (_c('A message that you sent could not be delivered'),
- _c('^---'),
- _c('<(?P[^>]*)>')),
- # another kundenserver.de
- (_c('A message that you sent could not be delivered'),
- _c('^---'),
- _c('^(?P[^\s@]+@[^\s@:]+):')),
- # thehartford.com
- (_c('Delivery to the following recipients failed'),
- _c("Bogus - there actually isn't anything"),
- _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')),
- # and another thehartfod.com/hartfordlife.com
- (_c('^Your message\s*$'),
- _c('^because:'),
- _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')),
- # kviv.be (NTMail)
- (_c('^Unable to deliver message to'),
- _c(r'\*+\s+End of message\s+\*+'),
- _c('<(?P[^>]*)>')),
- # earthlink.net supported domains
- (_c('^Sorry, unable to deliver your message to'),
- _c('^A copy of the original message'),
- _c('\s*(?P[^\s@]+@[^\s@]+)\s+')),
- # ademe.fr
- (_c('^A message could not be delivered to:'),
- _c('^Subject:'),
- _c('^\s*(?P[^\s@]+@[^\s@]+)\s*$')),
- # andrew.ac.jp
- (_c('^Invalid final delivery userid:'),
- _c('^Original message follows.'),
- _c('\s*(?P[^\s@]+@[^\s@]+)\s*$')),
- # E500_SMTP_Mail_Service@lerctr.org
- (_c('------ Failed Recipients ------'),
- _c('-------- Returned Mail --------'),
- _c('<(?P[^>]*)>')),
- # cynergycom.net
- (_c('A message that you sent could not be delivered'),
- _c('^---'),
- _c('(?P[^\s@]+@[^\s@)]+)')),
- # Next one goes here...
- ]
-
-
-
-def process(msg, patterns=None):
- if patterns is None:
- patterns = PATTERNS
- # simple state machine
- # 0 = nothing seen yet
- # 1 = intro seen
- addrs = {}
- # MAS: This is a mess. The outer loop used to be over the message
- # so we only looped through the message once. Looping through the
- # message for each set of patterns is obviously way more work, but
- # if we don't do it, problems arise because scre from the wrong
- # pattern set matches first and then acre doesn't match. The
- # alternative is to split things into separate modules, but then
- # we process the message multiple times anyway.
- for scre, ecre, acre in patterns:
- state = 0
- for line in email.Iterators.body_line_iterator(msg):
- if state == 0:
- if scre.search(line):
- state = 1
- if state == 1:
- mo = acre.search(line)
- if mo:
- addr = mo.group('addr')
- if addr:
- addrs[mo.group('addr')] = 1
- elif ecre.search(line):
- break
- if addrs:
- break
- return addrs.keys()
diff --git a/wcs/ctl/Bouncers/SimpleWarning.py b/wcs/ctl/Bouncers/SimpleWarning.py
deleted file mode 100644
index 0053766bc..000000000
--- a/wcs/ctl/Bouncers/SimpleWarning.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Recognizes simple heuristically delimited warnings."""
-
-from BouncerAPI import Stop
-from SimpleMatch import _c
-from SimpleMatch import process as _process
-
-
-
-# This is a list of tuples of the form
-#
-# (start cre, end cre, address cre)
-#
-# where `cre' means compiled regular expression, start is the line just before
-# the bouncing address block, end is the line just after the bouncing address
-# block, and address cre is the regexp that will recognize the addresses. It
-# must have a group called `addr' which will contain exactly and only the
-# address that bounced.
-patterns = [
- # pop3.pta.lia.net
- (_c('The address to which the message has not yet been delivered is'),
- _c('No action is required on your part'),
- _c(r'\s*(?P\S+@\S+)\s*')),
- # Next one goes here...
- ]
-
-
-
-def process(msg):
- if _process(msg, patterns):
- # It's a recognized warning so stop now
- return Stop
- else:
- return []
diff --git a/wcs/ctl/Bouncers/Yahoo.py b/wcs/ctl/Bouncers/Yahoo.py
deleted file mode 100644
index b3edf4fa7..000000000
--- a/wcs/ctl/Bouncers/Yahoo.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Yahoo! has its own weird format for bounces."""
-
-import re
-import email
-from email.Utils import parseaddr
-
-tcre = re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE)
-acre = re.compile(r'<(?P[^>]*)>:')
-ecre = re.compile(r'--- Original message follows')
-
-
-
-def process(msg):
- # Yahoo! bounces seem to have a known subject value and something called
- # an x-uidl: header, the value of which seems unimportant.
- sender = parseaddr(msg.get('from', '').lower())[1] or ''
- if not sender.startswith('mailer-daemon@yahoo'):
- return None
- addrs = []
- # simple state machine
- # 0 == nothing seen
- # 1 == tag line seen
- state = 0
- for line in email.Iterators.body_line_iterator(msg):
- line = line.strip()
- if state == 0 and tcre.match(line):
- state = 1
- elif state == 1:
- mo = acre.match(line)
- if mo:
- addrs.append(mo.group('addr'))
- continue
- mo = ecre.match(line)
- if mo:
- # we're at the end of the error response
- break
- return addrs
diff --git a/wcs/ctl/Bouncers/Yale.py b/wcs/ctl/Bouncers/Yale.py
deleted file mode 100644
index 6b4aa3ee1..000000000
--- a/wcs/ctl/Bouncers/Yale.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) 2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""Yale's mail server is pretty dumb.
-
-Its reports include the end user's name, but not the full domain. I think we
-can usually guess it right anyway. This is completely based on examination of
-the corpse, and is subject to failure whenever Yale even slightly changes
-their MTA. :(
-
-"""
-
-import re
-from cStringIO import StringIO
-from email.Utils import getaddresses
-
-scre = re.compile(r'Message not delivered to the following', re.IGNORECASE)
-ecre = re.compile(r'Error Detail', re.IGNORECASE)
-acre = re.compile(r'\s+(?P\S+)\s+')
-
-
-
-def process(msg):
- if msg.is_multipart():
- return None
- try:
- whofrom = getaddresses([msg.get('from', '')])[0][1]
- if not whofrom:
- return None
- username, domain = whofrom.split('@', 1)
- except (IndexError, ValueError):
- return None
- if username.lower() <> 'mailer-daemon':
- return None
- parts = domain.split('.')
- parts.reverse()
- for part1, part2 in zip(parts, ('edu', 'yale')):
- if part1 <> part2:
- return None
- # Okay, we've established that the bounce came from the mailer-daemon at
- # yale.edu. Let's look for a name, and then guess the relevant domains.
- names = {}
- body = StringIO(msg.get_payload())
- state = 0
- # simple state machine
- # 0 == init
- # 1 == intro found
- while 1:
- line = body.readline()
- if not line:
- break
- if state == 0 and scre.search(line):
- state = 1
- elif state == 1 and ecre.search(line):
- break
- elif state == 1:
- mo = acre.search(line)
- if mo:
- names[mo.group('addr')] = 1
- # Now we have a bunch of names, these are either @yale.edu or
- # @cs.yale.edu. Add them both.
- addrs = []
- for name in names.keys():
- addrs.append(name + '@yale.edu')
- addrs.append(name + '@cs.yale.edu')
- return addrs
diff --git a/wcs/ctl/Bouncers/__init__.py b/wcs/ctl/Bouncers/__init__.py
deleted file mode 100644
index f569e43f4..000000000
--- a/wcs/ctl/Bouncers/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
diff --git a/wcs/ctl/process_bounce.py b/wcs/ctl/process_bounce.py
deleted file mode 100644
index 32ab89dc8..000000000
--- a/wcs/ctl/process_bounce.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2010 Entr'ouvert
-#
-# 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, see .
-
-import sys
-import time
-import os
-import email.Parser
-
-from Bouncers import BouncerAPI
-
-from ..qommon.ctl import Command
-
-COMMA_SPACE = ', '
-
-class CmdProcessBounce(Command):
- name = 'process_bounce'
-
- def execute(self, base_options, sub_options, args):
- from ..qommon.tokens import Token
- from ..qommon.bounces import Bounce
-
- from .. import publisher
-
- try:
- publisher.WcsPublisher.configure(self.config)
- pub = publisher.WcsPublisher.create_publisher(
- register_tld_names=False)
- except:
- # not much we can do if we don't have a publisher object :/
- return
-
- try:
- parser = email.Parser.Parser()
- msg = parser.parse(sys.stdin)
- addrs = self.get_bounce_addrs(msg)
- if addrs is None:
- # not a bounce
- return
-
- try:
- to = msg['To']
- local_part, server_part = to.split('@')
- token_id = local_part.split('+')[1]
- except (IndexError, KeyError):
- return
-
- pub.app_dir = os.path.join(pub.app_dir, server_part)
- if not os.path.exists(pub.app_dir):
- return
-
- try:
- token = Token.get(token_id)
- except KeyError:
- return
-
- if token.type != 'email-bounce':
- return
-
- token.remove_self()
-
- bounce = Bounce()
- bounce.arrival_time = time.time()
- bounce.bounce_message = msg.as_string()
- bounce.addrs = addrs
- bounce.original_message = token.email_message
- bounce.original_rcpts = token.email_rcpts
- bounce.email_type = token.email_type
- bounce.store()
- except:
- pub.notify_of_exception(sys.exc_info(), context='[BOUNCE]')
- sys.exit(1)
-
- @classmethod
- def get_bounce_addrs(cls, msg):
- bouncers_dir = os.path.join(os.path.dirname(__file__), 'Bouncers')
- sys.path.append(bouncers_dir)
- for modname in BouncerAPI.BOUNCE_PIPELINE:
- __import__(modname)
- addrs = sys.modules[modname].process(msg)
- if addrs is BouncerAPI.Stop:
- return None # Stop means to ignore message
- if addrs:
- return addrs
- return None # didn't find any match
-
-CmdProcessBounce.register()
diff --git a/wcs/qommon/admin/emails.py b/wcs/qommon/admin/emails.py
index 879d25e8c..b6aa636e2 100644
--- a/wcs/qommon/admin/emails.py
+++ b/wcs/qommon/admin/emails.py
@@ -111,8 +111,6 @@ class EmailsDirectory(Directory):
required = False, value = emails.get('reply_to'))
form.add(TextWidget, 'footer', title=_('Email Footer'), cols=70, rows=5,
required=False, value=emails.get('footer'))
- form.add(CheckboxWidget, 'bounce_handler', title = _('Handle Bounces'),
- value = emails.get('bounce_handler'))
form.add(CheckboxWidget, 'check_domain_with_dns',
title = _('Check DNS for domain name'),
value = emails.get('check_domain_with_dns', True),
@@ -140,7 +138,7 @@ class EmailsDirectory(Directory):
return r.getvalue()
else:
cfg_submit(form, 'emails', [ 'smtp_server', 'smtp_login',
- 'smtp_password', 'from', 'reply_to', 'footer', 'bounce_handler',
+ 'smtp_password', 'from', 'reply_to', 'footer',
'check_domain_with_dns'])
return redirect('.')
diff --git a/wcs/qommon/bounces.py b/wcs/qommon/bounces.py
deleted file mode 100644
index 120c25802..000000000
--- a/wcs/qommon/bounces.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2010 Entr'ouvert
-#
-# 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, see .
-
-from .storage import StorableObject
-
-class Bounce(StorableObject):
- _names = 'bounces'
-
- arrival_time = None
- bounce_message = None
- addrs = None
- original_message = None
- original_rcpts = None
- email_type = None
-
diff --git a/wcs/qommon/emails.py b/wcs/qommon/emails.py
index 3e592732d..1555e3779 100644
--- a/wcs/qommon/emails.py
+++ b/wcs/qommon/emails.py
@@ -302,19 +302,6 @@ def email(subject, mail_body, email_rcpt, replyto=None, bcc=None,
# to that address instead of the real recipients.
rcpts = [os.environ.get('QOMMON_MAIL_REDIRECTION')]
- if emails_cfg.get('bounce_handler'):
- if get_request():
- server_name = get_request().get_server().split(':')[0]
- else:
- server_name = email_from.split('@')[1]
- token = tokens.Token(7 * 86400)
- token.type = 'email-bounce'
- token.email_rcpts = [str(x) for x in rcpts]
- token.email_message = msg.as_string()
- token.email_type = str(email_type)
- token.store()
- email_from = '%s-bounces+%s@%s' % (get_publisher().APP_NAME, token.id, server_name)
-
if not fire_and_forget:
s = create_smtp_server(emails_cfg, smtp_timeout=smtp_timeout)
try: