~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/mail/sendmail.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mfrom: (0.9.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080622211713-fpo2zrq3s5dfecxg
Tags: 1.7.0-3
Simplify /etc/moin/wikilist format: "USER URL" (drop unneeded middle
CONFIG_DIR that was wrongly advertised as DATA_DIR).  Make
moin-mass-migrate handle both formats and warn about deprecation of
the old one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: iso-8859-1 -*-
 
2
"""
 
3
    MoinMoin - email helper functions
 
4
 
 
5
    @copyright: 2003 Juergen Hermann <jh@web.de>,
 
6
                2008 MoinMoin:ThomasWaldmann
 
7
    @license: GNU GPL, see COPYING for details.
 
8
"""
 
9
 
 
10
import os, re
 
11
from email.Header import Header
 
12
 
 
13
from MoinMoin import log
 
14
logging = log.getLogger(__name__)
 
15
 
 
16
from MoinMoin import config
 
17
 
 
18
_transdict = {"AT": "@", "DOT": ".", "DASH": "-"}
 
19
 
 
20
 
 
21
def encodeAddress(address, charset):
 
22
    """ Encode email address to enable non ascii names
 
23
 
 
24
    e.g. '"J�rgen Hermann" <jh@web.de>'. According to the RFC, the name
 
25
    part should be encoded, the address should not.
 
26
 
 
27
    @param address: email address, possibly using '"name" <address>' format
 
28
    @type address: unicode
 
29
    @param charset: specifying both the charset and the encoding, e.g
 
30
                    quoted printable or base64.
 
31
    @type charset: email.Charset.Charset instance
 
32
    @rtype: string
 
33
    @return: encoded address
 
34
    """
 
35
    composite = re.compile(r'(?P<phrase>.+)(?P<angle_addr>\<.*\>)', re.UNICODE)
 
36
    match = composite.match(address)
 
37
    if match:
 
38
        phrase = match.group('phrase').encode(config.charset)
 
39
        phrase = str(Header(phrase, charset))
 
40
        angle_addr = match.group('angle_addr').encode(config.charset)
 
41
        return phrase + angle_addr
 
42
    else:
 
43
        return address.encode(config.charset)
 
44
 
 
45
 
 
46
def sendmail(request, to, subject, text, mail_from=None):
 
47
    """ Create and send a text/plain message
 
48
 
 
49
    Return a tuple of success or error indicator and message.
 
50
 
 
51
    @param request: the request object
 
52
    @param to: recipients (list)
 
53
    @param subject: subject of email (unicode)
 
54
    @param text: email body text (unicode)
 
55
    @param mail_from: override default mail_from
 
56
    @type mail_from: unicode
 
57
    @rtype: tuple
 
58
    @return: (is_ok, Description of error or OK message)
 
59
    """
 
60
    import smtplib, socket
 
61
    from email.Message import Message
 
62
    from email.Charset import Charset, QP
 
63
    from email.Utils import formatdate, make_msgid
 
64
 
 
65
    _ = request.getText
 
66
    cfg = request.cfg
 
67
    mail_from = mail_from or cfg.mail_from
 
68
    subject = subject.encode(config.charset)
 
69
 
 
70
    # Create a text/plain body using CRLF (see RFC2822)
 
71
    text = text.replace(u'\n', u'\r\n')
 
72
    text = text.encode(config.charset)
 
73
 
 
74
    # Create a message using config.charset and quoted printable
 
75
    # encoding, which should be supported better by mail clients.
 
76
    # TODO: check if its really works better for major mail clients
 
77
    msg = Message()
 
78
    charset = Charset(config.charset)
 
79
    charset.header_encoding = QP
 
80
    charset.body_encoding = QP
 
81
    msg.set_charset(charset)
 
82
 
 
83
    # work around a bug in python 2.4.3 and above:
 
84
    msg.set_payload('=')
 
85
    if msg.as_string().endswith('='):
 
86
        text = charset.body_encode(text)
 
87
 
 
88
    msg.set_payload(text)
 
89
 
 
90
    # Create message headers
 
91
    # Don't expose emails addreses of the other subscribers, instead we
 
92
    # use the same mail_from, e.g. u"J�rgen Wiki <noreply@mywiki.org>"
 
93
    address = encodeAddress(mail_from, charset)
 
94
    msg['From'] = address
 
95
    msg['To'] = address
 
96
    msg['Date'] = formatdate()
 
97
    msg['Message-ID'] = make_msgid()
 
98
    msg['Subject'] = Header(subject, charset)
 
99
 
 
100
    if cfg.mail_sendmail:
 
101
        # Set the BCC.  This will be stripped later by sendmail.
 
102
        msg['BCC'] = ','.join(to)
 
103
        # Set Return-Path so that it isn't set (generally incorrectly) for us.
 
104
        msg['Return-Path'] = address
 
105
 
 
106
    # Send the message
 
107
    if not cfg.mail_sendmail:
 
108
        try:
 
109
            logging.debug("trying to send mail (smtp) via smtp server '%s'" % cfg.mail_smarthost)
 
110
            host, port = (cfg.mail_smarthost + ':25').split(':')[:2]
 
111
            server = smtplib.SMTP(host, int(port))
 
112
            try:
 
113
                #server.set_debuglevel(1)
 
114
                if cfg.mail_login:
 
115
                    user, pwd = cfg.mail_login.split()
 
116
                    try: # try to do tls
 
117
                        server.ehlo()
 
118
                        if server.has_extn('starttls'):
 
119
                            server.starttls()
 
120
                            server.ehlo()
 
121
                            logging.debug("tls connection to smtp server established")
 
122
                    except:
 
123
                        logging.debug("could not establish a tls connection to smtp server, continuing without tls")
 
124
                    logging.debug("trying to log in to smtp server using account '%s'" % user)
 
125
                    server.login(user, pwd)
 
126
                server.sendmail(mail_from, to, msg.as_string())
 
127
            finally:
 
128
                try:
 
129
                    server.quit()
 
130
                except AttributeError:
 
131
                    # in case the connection failed, SMTP has no "sock" attribute
 
132
                    pass
 
133
        except smtplib.SMTPException, e:
 
134
            logging.exception("smtp mail failed with an exception.")
 
135
            return (0, str(e))
 
136
        except (os.error, socket.error), e:
 
137
            logging.exception("smtp mail failed with an exception.")
 
138
            return (0, _("Connection to mailserver '%(server)s' failed: %(reason)s") % {
 
139
                'server': cfg.mail_smarthost,
 
140
                'reason': str(e)
 
141
            })
 
142
    else:
 
143
        try:
 
144
            logging.debug("trying to send mail (sendmail)")
 
145
            sendmailp = os.popen(cfg.mail_sendmail, "w")
 
146
            # msg contains everything we need, so this is a simple write
 
147
            sendmailp.write(msg.as_string())
 
148
            sendmail_status = sendmailp.close()
 
149
            if sendmail_status:
 
150
                logging.error("sendmail failed with status: %s" % str(sendmail_status))
 
151
                return (0, str(sendmail_status))
 
152
        except:
 
153
            logging.exception("sendmail failed with an exception.")
 
154
            return (0, _("Mail not sent"))
 
155
 
 
156
    logging.debug("Mail sent OK")
 
157
    return (1, _("Mail sent OK"))
 
158
 
 
159
 
 
160
def decodeSpamSafeEmail(address):
 
161
    """ Decode obfuscated email address to standard email address
 
162
 
 
163
    Decode a spam-safe email address in `address` by applying the
 
164
    following rules:
 
165
 
 
166
    Known all-uppercase words and their translation:
 
167
        "DOT"   -> "."
 
168
        "AT"    -> "@"
 
169
        "DASH"  -> "-"
 
170
 
 
171
    Any unknown all-uppercase words simply get stripped.
 
172
    Use that to make it even harder for spam bots!
 
173
 
 
174
    Blanks (spaces) simply get stripped.
 
175
 
 
176
    @param address: obfuscated email address string
 
177
    @rtype: string
 
178
    @return: decoded email address
 
179
    """
 
180
    email = []
 
181
 
 
182
    # words are separated by blanks
 
183
    for word in address.split():
 
184
        # is it all-uppercase?
 
185
        if word.isalpha() and word == word.upper():
 
186
            # strip unknown CAPS words
 
187
            word = _transdict.get(word, '')
 
188
        email.append(word)
 
189
 
 
190
    # return concatenated parts
 
191
    return ''.join(email)
 
192