Back to index

moin  1.9.0~rc2
sendmail.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - email helper functions
00004 
00005     @copyright: 2003 Juergen Hermann <jh@web.de>,
00006                 2008-2009 MoinMoin:ThomasWaldmann
00007     @license: GNU GPL, see COPYING for details.
00008 """
00009 
00010 import os, re
00011 from email.Header import Header
00012 
00013 from MoinMoin import log
00014 logging = log.getLogger(__name__)
00015 
00016 from MoinMoin import config
00017 
00018 _transdict = {"AT": "@", "DOT": ".", "DASH": "-"}
00019 
00020 
00021 def encodeAddress(address, charset):
00022     """ Encode email address to enable non ascii names
00023 
00024     e.g. '"Jürgen Hermann" <jh@web.de>'. According to the RFC, the name
00025     part should be encoded, the address should not.
00026 
00027     @param address: email address, possibly using '"name" <address>' format
00028     @type address: unicode
00029     @param charset: specifying both the charset and the encoding, e.g
00030                     quoted printable or base64.
00031     @type charset: email.Charset.Charset instance
00032     @rtype: string
00033     @return: encoded address
00034     """
00035     assert isinstance(address, unicode)
00036     composite = re.compile(r'(?P<phrase>.*?)(?P<blanks>\s*)<(?P<addr>.*)>', re.UNICODE)
00037     match = composite.match(address)
00038     if match:
00039         phrase = match.group('phrase')
00040         try:
00041             str(phrase)  # is it pure ascii?
00042         except UnicodeEncodeError:
00043             phrase = phrase.encode(config.charset)
00044             phrase = Header(phrase, charset)
00045         blanks = match.group('blanks')
00046         addr = match.group('addr')
00047         if phrase:
00048             return "%s%s<%s>" % (str(phrase), str(blanks), str(addr))
00049         else:
00050             return str(addr)
00051     else:
00052         # a pure email address, should encode to ascii without problem
00053         return str(address)
00054 
00055 
00056 def sendmail(request, to, subject, text, mail_from=None):
00057     """ Create and send a text/plain message
00058 
00059     Return a tuple of success or error indicator and message.
00060 
00061     @param request: the request object
00062     @param to: recipients (list)
00063     @param subject: subject of email (unicode)
00064     @param text: email body text (unicode)
00065     @param mail_from: override default mail_from
00066     @type mail_from: unicode
00067     @rtype: tuple
00068     @return: (is_ok, Description of error or OK message)
00069     """
00070     import smtplib, socket
00071     from email.Message import Message
00072     from email.Charset import Charset, QP
00073     from email.Utils import formatdate, make_msgid
00074 
00075     _ = request.getText
00076     cfg = request.cfg
00077     mail_from = mail_from or cfg.mail_from
00078 
00079     logging.debug("send mail, from: %r, subj: %r" % (mail_from, subject))
00080     logging.debug("send mail, to: %r" % (to, ))
00081 
00082     if not to:
00083         return (1, _("No recipients, nothing to do"))
00084 
00085     subject = subject.encode(config.charset)
00086 
00087     # Create a text/plain body using CRLF (see RFC2822)
00088     text = text.replace(u'\n', u'\r\n')
00089     text = text.encode(config.charset)
00090 
00091     # Create a message using config.charset and quoted printable
00092     # encoding, which should be supported better by mail clients.
00093     # TODO: check if its really works better for major mail clients
00094     msg = Message()
00095     charset = Charset(config.charset)
00096     charset.header_encoding = QP
00097     charset.body_encoding = QP
00098     msg.set_charset(charset)
00099 
00100     # work around a bug in python 2.4.3 and above:
00101     msg.set_payload('=')
00102     if msg.as_string().endswith('='):
00103         text = charset.body_encode(text)
00104 
00105     msg.set_payload(text)
00106 
00107     # Create message headers
00108     # Don't expose emails addreses of the other subscribers, instead we
00109     # use the same mail_from, e.g. u"Jürgen Wiki <noreply@mywiki.org>"
00110     address = encodeAddress(mail_from, charset)
00111     msg['From'] = address
00112     msg['To'] = address
00113     msg['Date'] = formatdate()
00114     msg['Message-ID'] = make_msgid()
00115     msg['Subject'] = Header(subject, charset)
00116     # See RFC 3834 section 5:
00117     msg['Auto-Submitted'] = 'auto-generated'
00118 
00119     if cfg.mail_sendmail:
00120         # Set the BCC.  This will be stripped later by sendmail.
00121         msg['BCC'] = ','.join(to)
00122         # Set Return-Path so that it isn't set (generally incorrectly) for us.
00123         msg['Return-Path'] = address
00124 
00125     # Send the message
00126     if not cfg.mail_sendmail:
00127         try:
00128             logging.debug("trying to send mail (smtp) via smtp server '%s'" % cfg.mail_smarthost)
00129             host, port = (cfg.mail_smarthost + ':25').split(':')[:2]
00130             server = smtplib.SMTP(host, int(port))
00131             try:
00132                 #server.set_debuglevel(1)
00133                 if cfg.mail_login:
00134                     user, pwd = cfg.mail_login.split()
00135                     try: # try to do tls
00136                         server.ehlo()
00137                         if server.has_extn('starttls'):
00138                             server.starttls()
00139                             server.ehlo()
00140                             logging.debug("tls connection to smtp server established")
00141                     except:
00142                         logging.debug("could not establish a tls connection to smtp server, continuing without tls")
00143                     logging.debug("trying to log in to smtp server using account '%s'" % user)
00144                     server.login(user, pwd)
00145                 server.sendmail(mail_from, to, msg.as_string())
00146             finally:
00147                 try:
00148                     server.quit()
00149                 except AttributeError:
00150                     # in case the connection failed, SMTP has no "sock" attribute
00151                     pass
00152         except smtplib.SMTPException, e:
00153             logging.exception("smtp mail failed with an exception.")
00154             return (0, str(e))
00155         except (os.error, socket.error), e:
00156             logging.exception("smtp mail failed with an exception.")
00157             return (0, _("Connection to mailserver '%(server)s' failed: %(reason)s") % {
00158                 'server': cfg.mail_smarthost,
00159                 'reason': str(e)
00160             })
00161     else:
00162         try:
00163             logging.debug("trying to send mail (sendmail)")
00164             sendmailp = os.popen(cfg.mail_sendmail, "w")
00165             # msg contains everything we need, so this is a simple write
00166             sendmailp.write(msg.as_string())
00167             sendmail_status = sendmailp.close()
00168             if sendmail_status:
00169                 logging.error("sendmail failed with status: %s" % str(sendmail_status))
00170                 return (0, str(sendmail_status))
00171         except:
00172             logging.exception("sendmail failed with an exception.")
00173             return (0, _("Mail not sent"))
00174 
00175     logging.debug("Mail sent OK")
00176     return (1, _("Mail sent OK"))
00177 
00178 def encodeSpamSafeEmail(email_address, obfuscation_text=''):
00179     """ Encodes a standard email address to an obfuscated address
00180     @param email_address: mail address to encode.
00181                           Known characters and their all-uppercase words translation:
00182                           "." -> " DOT "
00183                           "@" -> " AT "
00184                           "-" -> " DASH "
00185     @param obfuscation_text: optional text to obfuscate the email.
00186                              All characters in the string must be alphabetic
00187                              and they will be added in uppercase.
00188     """
00189     address = email_address.lower()
00190     # uppercase letters will be stripped by decodeSpamSafeEmail
00191     for word, sign in _transdict.items():
00192         address = address.replace(sign, ' %s ' % word)
00193     if obfuscation_text.isalpha():
00194         # is the obfuscation_text alphabetic
00195         address = address.replace(' AT ', ' AT %s ' % obfuscation_text.upper())
00196 
00197     return address
00198 
00199 def decodeSpamSafeEmail(address):
00200     """ Decode obfuscated email address to standard email address
00201 
00202     Decode a spam-safe email address in `address` by applying the
00203     following rules:
00204 
00205     Known all-uppercase words and their translation:
00206         "DOT"   -> "."
00207         "AT"    -> "@"
00208         "DASH"  -> "-"
00209 
00210     Any unknown all-uppercase words or an uppercase letter simply get stripped.
00211     Use that to make it even harder for spam bots!
00212 
00213     Blanks (spaces) simply get stripped.
00214 
00215     @param address: obfuscated email address string
00216     @rtype: string
00217     @return: decoded email address
00218     """
00219     email = []
00220 
00221     # words are separated by blanks
00222     for word in address.split():
00223         # is it all-uppercase?
00224         if word.isalpha() and word == word.upper():
00225             # strip unknown CAPS words
00226             word = _transdict.get(word, '')
00227         email.append(word)
00228 
00229     # return concatenated parts
00230     return ''.join(email)
00231