From b6799fd00aaf1e9d8ab6f4f4e8c0c32162931505 Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Wed, 30 Jun 2010 08:58:45 +0000 Subject: [PATCH] Rewrite sendMailToERP5 Previous version used CMFMailIn and didn't work anymore. Remove useless sendMailToZope.py script. Like old sendMailToERP5.py, it can be used directly to deliver mails from postfix to ERP5. Main improvements over previous implementation are: - fix bug losing mail silently if ERP5 instance returns a HTTP status >= 300 - ingestion map to get options according to the recipient git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@36717 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/bin/sendMailToERP5 | 172 +++++++++++++++++++++++++++++ product/ERP5/bin/sendMailToERP5.py | 54 --------- product/ERP5/bin/sendMailToZope.py | 61 ---------- 3 files changed, 172 insertions(+), 115 deletions(-) create mode 100755 product/ERP5/bin/sendMailToERP5 delete mode 100644 product/ERP5/bin/sendMailToERP5.py delete mode 100755 product/ERP5/bin/sendMailToZope.py diff --git a/product/ERP5/bin/sendMailToERP5 b/product/ERP5/bin/sendMailToERP5 new file mode 100755 index 0000000000..3afae366c1 --- /dev/null +++ b/product/ERP5/bin/sendMailToERP5 @@ -0,0 +1,172 @@ +#!/usr/bin/python +import os, subprocess, sys, textwrap, traceback, urllib, urlparse + +# Example of configuration of postfix to deliver to ERP5: +# - Add the following lines to master.cf: +# erp5 unix - n n - - pipe +# flags=FR user=erp5 argv=/path_to/sendMailToERP5 --ingestion_map=... +# recipient=${recipient} +# - Tell smtpd service to use the new filter, by adding: +# -o content_filter=erp5: + +class HTTPError(IOError): + + def __init__(self, errcode, errmsg, result): + self.__dict__.update(errcode=errcode, errmsg=errmsg, result=result) + + def __str__(self): + return '%s %s' % (self.errcode, self.errmsg) + + +class urlopen(urllib.FancyURLopener, object): + """Open a network object denoted by a URL for reading + + Raise a HTTPError exception if HTTP error code is not 200. + """ + def __new__(cls, *args, **kw): + self = object.__new__(cls) + self.__init__() + return self.open(*args, **kw) + + def http_error(self, url, fp, errcode, errmsg, headers, data=None): + raise HTTPError(errcode, errmsg, + self.http_error_default(url, fp, errcode, errmsg, headers)) + + +class Message(object): + + def __init__(self, *args): + for arg in args: + k, v = arg.split('=', 1) + if not k.islower(): + raise ValueError + old = getattr(self, k, None) + if old is not None: + if type(old) is list: + old.append(v) + continue + v = [old, v] + setattr(self, k, v) + recipient_list = self.__dict__.pop('recipient', []) + if isinstance(recipient_list, basestring): + recipient_list = [recipient_list] + self.recipient_list = recipient_list + + def __call__(self, portal=None, **kw): + if portal is not None: + scheme, netloc, path, query, fragment = urlparse.urlsplit(portal) + if query or fragment: + raise ValueError + user, host = urllib.splituser(netloc) + if user is None: + password = None + else: + user, password = urllib.splitpasswd(user) + user = kw.pop('user', user) + if user is not None: + password = kw.pop('password', password) + if password is not None: + user = '%s:%s' % (user, password) + host = '%s@%s' % (user, host) + url = urlparse.urlunsplit((scheme, host, path.rstrip('/'), '', '')) + \ + '/portal_contributions/newContent' + kw['data'] = sys.stdin.read() + try: + result = urlopen(url, urllib.urlencode(kw)) + except HTTPError, e: + if e.errcode >= 300: + raise + result = e.result + result.read() # ERP5 does not return useful information + # Now, we could reinject the message to postfix for local delivery, + # using /usr/sbin/sendmail, depending on a 'sendmail' option. However, + # we would get duplicate mails if either ERP5 or sendmail fail. + # It is better to do this from the ERP5 instance itself, by activity. + + +class SimpleIngestionMap(object): + """Simple implementation of ingestion map, using a Python file as database + + This class maps recipients to parameters for portal_contributions/newContent + """ + def __init__(self, ingestion_map_filename): + fd = file(ingestion_map_filename) + g = {} + try: + exec fd in g + finally: + fd.close() + self._map = g['ingestion_map'] + + def __call__(self, message, **kw): + for recipient in message.recipient_list: + recipient = self._map.get(recipient) + if recipient: + kw.update(recipient) + break + return message(**kw) + + +def getOptionParser(): + from optparse import IndentedHelpFormatter, OptionGroup, OptionParser + class Formatter(IndentedHelpFormatter): + """Subclass IndentedHelpFormatter to preserve line breaks in description""" + def format_description(self, description): + return ''.join(IndentedHelpFormatter.format_description(self, x) + for x in description.split('\n')) + parser = OptionParser(usage="%prog [options] [<key>=<value>]...", + formatter=Formatter(), description="""Positional \ +arguments defines variables that are used by ingestion maps to determine \ +options to send to ERP5. Currently, only 'recipient' key is used. +This tool can be used directly to deliver mails from postfix to ERP5, \ +by using it as a filter (cf document of /etc/postfix/master.cf).""") + _ = parser.add_option + _("--portal", help="URL of ERP5 instance to connect to") + _("--user", help="use this user to connect to ERP5") + _("--password", help="use this password to connect to ERP5") + _("--file_name", help="ERP5 requires a file name to guess content type") + _("--container_path", help="define where to contribute the content" + " (by default, it is guessed by ERP5)") + #_("--portal_type", default="Mail Message") + group = OptionGroup(parser, "Ingestion map", """Above options can be \ +overridden according to recipients, using a Python module as ingestion map \ +database. The module must define an 'ingestion_map' variable implementing \ +'get(recipient) -> option_dict'. Example: + ingestion_map = { + 'foo@bar.com': dict(user='foo', password='12345'), + 'patches@prj1.org': dict(file_name='prj1.patch'), + 'spam@example.invalid': dict(portal=None), # drop + }""") + group.add_option("--ingestion_map", help="get options from this file," + " according to recipients") + parser.add_option_group(group) + _ = group.add_option + parser.set_defaults(file_name="unnamed.eml") + return parser + + +def main(): + parser = getOptionParser() + options, args = parser.parse_args() + message = Message(*args) + default = {} + for option in parser.option_list: + dest = option.dest + if dest not in (None, 'ingestion_map'): + value = getattr(options, dest) + if value is not None: + default[dest] = value + if options.ingestion_map: + SimpleIngestionMap(options.ingestion_map)(message, **default) + else: + message(**default) + + +if __name__ == '__main__': + try: + main() + except SystemExit: + raise + except: + traceback.print_exc() + sys.exit(os.EX_TEMPFAIL) diff --git a/product/ERP5/bin/sendMailToERP5.py b/product/ERP5/bin/sendMailToERP5.py deleted file mode 100644 index 785864d1e3..0000000000 --- a/product/ERP5/bin/sendMailToERP5.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/python - -# This python module will send a mail message to a ERP5 site -# Taken from sendMailToZope.py in CMFMailin. - -# $Id: sendMailToZope.py,v 1.1.1.1 2002/05/31 09:13:06 andyd Exp $ - -__version__='$Revision: 1.1.1.1 $'[11:-2] - -import sys, urllib -import rfc822, StringIO, string - -def sendMail(url, messageText): - if url: - if not url[-len('/postUTF8MailMessage'):] == '/postUTF8MailMessage': - url = url + '/postUTF8MailMessage' - - try: - result = urllib.urlopen(url, urllib.urlencode({'file':messageText})).read() - except (IOError,EOFError),e: - print "ZMailIn Error: Problem Connecting to server",e - sys.exit(73) - - # if the ZMailIn Client's method returned anything, then 'something bad' happened. - if result: - print result - sys.exit(1) - - sys.exit(0) - - print "ZMailIn Error: No ZMailIn Client URL found or specified." - sys.exit(1) - - -if __name__ == '__main__': - # This gets called by the MTA when a new message arrives. - # The mail message file gets passed in on the stdin - - # First get a handle on the message file - f = sys.stdin - messageText = f.read() - - url = '' - if len(sys.argv)>1: - url = sys.argv[1] - - if not url: - print "ZMailIn Error: You must specify the URL" \ - " to the ERP5 instance in the First arguement. " \ - "i.e. python sendMailToERP5.py http://www.myserver.com/erp5/" - sys.exit(1) - - sendMail(url, messageText) - diff --git a/product/ERP5/bin/sendMailToZope.py b/product/ERP5/bin/sendMailToZope.py deleted file mode 100755 index 9e85752ddf..0000000000 --- a/product/ERP5/bin/sendMailToZope.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python -############################################################################## -# -# Copyright (c) 2007 Nexedi SA and Contributors. All Rights Reserved. -# Jean-Paul Smets <jp@nexedi.com> -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# 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. -# -############################################################################## - -import sys -import urllib - -# Configurable parameters -USER = '' -PASSWORD = '' -CONTRIBUTION_TOOL_URL = 'http://%s:%s@localhost:9080/erp5/portal_contributions/newContent' % (USER, PASSWORD) -PORTAL_TYPE = 'Mail Message' -FILE_NAME = 'postfix_mail.eml' -CONTAINER_PATH = 'event_module' - -# Main program -if __name__ == '__main__': - f = sys.stdin - message_text = f.read() - try: - result = urllib.urlopen(CONTRIBUTION_TOOL_URL, urllib.urlencode( - {'data': message_text, - 'portal_type': PORTAL_TYPE, - 'container_path': CONTAINER_PATH, - 'file_name': FILE_NAME, - } - )).read() - except (IOError,EOFError), e: - print "Zope Email Ingestion Error: Problem Connecting to server", e - sys.exit(73) - - if result: - print result - sys.exit(1) - - sys.exit(0) \ No newline at end of file -- 2.30.9