/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
16
6379.6.7 by Jelmer Vernooij
Move importing from future until after doc string, otherwise the doc string will disappear.
17
"""A convenience class around smtplib."""
18
6379.6.3 by Jelmer Vernooij
Use absolute_import.
19
from __future__ import absolute_import
20
6791.2.3 by Jelmer Vernooij
Fix more imports.
21
try:
6797 by Jelmer Vernooij
Merge lp:~jelmer/brz/fix-imports.
22
    from email.utils import getaddresses, parseaddr
6791.2.3 by Jelmer Vernooij
Fix more imports.
23
except ImportError:  # python < 3
6797 by Jelmer Vernooij
Merge lp:~jelmer/brz/fix-imports.
24
    from email.Utils import getaddresses, parseaddr
6791.2.3 by Jelmer Vernooij
Fix more imports.
25
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
26
import errno
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
27
import smtplib
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
28
import socket
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
29
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
30
from . import (
2900.2.11 by Vincent Ladeuil
Make smtp aware of authentication config.
31
    config,
4147.1.3 by James Henstridge
Switch to using osutils.safe_utf8() as suggested by Vincent. Also
32
    osutils,
2900.2.11 by Vincent Ladeuil
Make smtp aware of authentication config.
33
    )
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
34
from .errors import (
6734.1.1 by Jelmer Vernooij
Fix more imports.
35
    BzrError,
36
    InternalBzrError,
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
37
    )
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
38
39
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
40
smtp_password = config.Option('smtp_password', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
41
                              help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
42
Password to use for authentication to SMTP server.
43
''')
44
smtp_server = config.Option('smtp_server', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
45
                            help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
46
Hostname of the SMTP server to use for sending email.
47
''')
48
smtp_username = config.Option('smtp_username', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
49
                              help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
50
Username to use for authentication to SMTP server.
51
''')
52
53
6734.1.1 by Jelmer Vernooij
Fix more imports.
54
class SMTPError(BzrError):
55
56
    _fmt = "SMTP error: %(error)s"
57
58
    def __init__(self, error):
59
        self.error = error
60
61
62
class SMTPConnectionRefused(SMTPError):
63
64
    _fmt = "SMTP connection to %(host)s refused"
65
66
    def __init__(self, error, host):
67
        self.error = error
68
        self.host = host
69
70
71
class DefaultSMTPConnectionRefused(SMTPConnectionRefused):
72
73
    _fmt = "Please specify smtp_server.  No server at default %(host)s."
74
75
76
class NoDestinationAddress(InternalBzrError):
77
78
    _fmt = "Message does not have a destination address."
79
80
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
81
class SMTPConnection(object):
82
    """Connect to an SMTP server and send an email.
83
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
84
    This is a gateway between breezy.config.Config and smtplib.SMTP. It
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
85
    understands the basic bzr SMTP configuration information: smtp_server,
86
    smtp_username, and smtp_password.
87
    """
88
89
    _default_smtp_server = 'localhost'
90
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
91
    def __init__(self, config, _smtp_factory=None):
92
        self._smtp_factory = _smtp_factory
93
        if self._smtp_factory is None:
94
            self._smtp_factory = smtplib.SMTP
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
95
        self._config = config
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
96
        self._config_smtp_server = config.get('smtp_server')
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
97
        self._smtp_server = self._config_smtp_server
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
98
        if self._smtp_server is None:
99
            self._smtp_server = self._default_smtp_server
100
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
101
        self._smtp_username = config.get('smtp_username')
102
        self._smtp_password = config.get('smtp_password')
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
103
104
        self._connection = None
105
106
    def _connect(self):
107
        """If we haven't connected, connect and authenticate."""
108
        if self._connection is not None:
109
            return
110
111
        self._create_connection()
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
112
        # FIXME: _authenticate() should only be called when the server has
7143.15.2 by Jelmer Vernooij
Run autopep8.
113
        # refused unauthenticated access, so it can safely try to authenticate
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
114
        # with the default username. JRV20090407
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
115
        self._authenticate()
116
117
    def _create_connection(self):
118
        """Create an SMTP connection."""
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
119
        self._connection = self._smtp_factory()
120
        try:
121
            self._connection.connect(self._smtp_server)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
122
        except socket.error as e:
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
123
            if e.args[0] == errno.ECONNREFUSED:
124
                if self._config_smtp_server is None:
125
                    raise DefaultSMTPConnectionRefused(socket.error,
126
                                                       self._smtp_server)
127
                else:
128
                    raise SMTPConnectionRefused(socket.error,
129
                                                self._smtp_server)
130
            else:
131
                raise
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
132
2898.2.3 by James Henstridge
* Fix merge-directive blackbox test.
133
        # Say EHLO (falling back to HELO) to query the server's features.
2898.2.1 by James Henstridge
Update SMTPConnection._create_connection to better follow the SMTP
134
        code, resp = self._connection.ehlo()
135
        if not (200 <= code <= 299):
136
            code, resp = self._connection.helo()
137
            if not (200 <= code <= 299):
138
                raise SMTPError("server refused HELO: %d %s" % (code, resp))
139
140
        # Use TLS if the server advertised it:
141
        if self._connection.has_extn("starttls"):
142
            code, resp = self._connection.starttls()
143
            if not (200 <= code <= 299):
7143.15.2 by Jelmer Vernooij
Run autopep8.
144
                raise SMTPError("server refused STARTTLS: %d %s" %
145
                                (code, resp))
2898.2.1 by James Henstridge
Update SMTPConnection._create_connection to better follow the SMTP
146
            # Say EHLO again, to check for newly revealed features
147
            code, resp = self._connection.ehlo()
148
            if not (200 <= code <= 299):
149
                raise SMTPError("server refused EHLO: %d %s" % (code, resp))
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
150
151
    def _authenticate(self):
152
        """If necessary authenticate yourself to the server."""
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
153
        auth = config.AuthenticationConfig()
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
154
        if self._smtp_username is None:
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
155
            # FIXME: Since _authenticate gets called even when no authentication
7143.15.2 by Jelmer Vernooij
Run autopep8.
156
            # is necessary, it's not possible to use the default username
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
157
            # here yet.
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
158
            self._smtp_username = auth.get_user('smtp', self._smtp_server)
159
            if self._smtp_username is None:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
160
                return
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
161
2900.2.12 by Vincent Ladeuil
Since all schemes query AuthenticationConfig then prompt user, make that
162
        if self._smtp_password is None:
163
            self._smtp_password = auth.get_password(
164
                'smtp', self._smtp_server, self._smtp_username)
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
165
4147.1.1 by James Henstridge
Ensure that byte strings are passed to SMTP.login(), as passing unicode
166
        # smtplib requires that the username and password be byte
4147.1.2 by James Henstridge
Encode usernames and passwords as UTF-8 rather than ASCII. While
167
        # strings.  The CRAM-MD5 spec doesn't give any guidance on
4147.1.3 by James Henstridge
Switch to using osutils.safe_utf8() as suggested by Vincent. Also
168
        # encodings, but the SASL PLAIN spec says UTF-8, so that's
169
        # what we'll use.
170
        username = osutils.safe_utf8(self._smtp_username)
171
        password = osutils.safe_utf8(self._smtp_password)
4147.1.1 by James Henstridge
Ensure that byte strings are passed to SMTP.login(), as passing unicode
172
4147.1.3 by James Henstridge
Switch to using osutils.safe_utf8() as suggested by Vincent. Also
173
        self._connection.login(username, password)
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
174
175
    @staticmethod
176
    def get_message_addresses(message):
177
        """Get the origin and destination addresses of a message.
178
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
179
        :param message: A message object supporting get() to access its
6791.2.3 by Jelmer Vernooij
Fix more imports.
180
            headers, like email.message.Message or
181
            breezy.email_message.EmailMessage.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
182
        :return: A pair (from_email, to_emails), where from_email is the email
183
            address in the From header, and to_emails a list of all the
184
            addresses in the To, Cc, and Bcc headers.
185
        """
6797 by Jelmer Vernooij
Merge lp:~jelmer/brz/fix-imports.
186
        from_email = parseaddr(message.get('From', None))[1]
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
187
        to_full_addresses = []
188
        for header in ['To', 'Cc', 'Bcc']:
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
189
            value = message.get(header, None)
190
            if value:
191
                to_full_addresses.append(value)
7143.15.2 by Jelmer Vernooij
Run autopep8.
192
        to_emails = [pair[1] for pair in
193
                     getaddresses(to_full_addresses)]
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
194
195
        return from_email, to_emails
196
197
    def send_email(self, message):
198
        """Send an email message.
199
2547.1.1 by Aaron Bentley
Add SMTPConnection class (Adeodato Simó)
200
        The message will be sent to all addresses in the To, Cc and Bcc
201
        headers.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
202
6791.2.3 by Jelmer Vernooij
Fix more imports.
203
        :param message: An email.message.Message or
204
            email.mime.multipart.MIMEMultipart object.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
205
        :return: None
206
        """
207
        from_email, to_emails = self.get_message_addresses(message)
208
209
        if not to_emails:
210
            raise NoDestinationAddress
211
212
        try:
213
            self._connect()
2547.1.1 by Aaron Bentley
Add SMTPConnection class (Adeodato Simó)
214
            self._connection.sendmail(from_email, to_emails,
215
                                      message.as_string())
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
216
        except smtplib.SMTPRecipientsRefused as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
217
            raise SMTPError('server refused recipient: %d %s' %
7143.15.2 by Jelmer Vernooij
Run autopep8.
218
                            next(iter(e.recipients.values())))
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
219
        except smtplib.SMTPResponseException as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
220
            raise SMTPError('%d %s' % (e.smtp_code, e.smtp_error))
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
221
        except smtplib.SMTPException as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
222
            raise SMTPError(str(e))