/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
7479.2.1 by Jelmer Vernooij
Drop python2 support.
19
from email.utils import getaddresses, parseaddr
6791.2.3 by Jelmer Vernooij
Fix more imports.
20
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
21
import errno
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
22
import smtplib
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
23
import socket
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
24
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
25
from . import (
2900.2.11 by Vincent Ladeuil
Make smtp aware of authentication config.
26
    config,
4147.1.3 by James Henstridge
Switch to using osutils.safe_utf8() as suggested by Vincent. Also
27
    osutils,
2900.2.11 by Vincent Ladeuil
Make smtp aware of authentication config.
28
    )
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
29
from .errors import (
6734.1.1 by Jelmer Vernooij
Fix more imports.
30
    BzrError,
31
    InternalBzrError,
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
32
    )
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
33
34
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
35
smtp_password = config.Option('smtp_password', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
36
                              help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
37
Password to use for authentication to SMTP server.
38
''')
39
smtp_server = config.Option('smtp_server', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
40
                            help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
41
Hostname of the SMTP server to use for sending email.
42
''')
43
smtp_username = config.Option('smtp_username', default=None,
7143.15.2 by Jelmer Vernooij
Run autopep8.
44
                              help='''\
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
45
Username to use for authentication to SMTP server.
46
''')
47
48
6734.1.1 by Jelmer Vernooij
Fix more imports.
49
class SMTPError(BzrError):
50
51
    _fmt = "SMTP error: %(error)s"
52
53
    def __init__(self, error):
54
        self.error = error
55
56
57
class SMTPConnectionRefused(SMTPError):
58
59
    _fmt = "SMTP connection to %(host)s refused"
60
61
    def __init__(self, error, host):
62
        self.error = error
63
        self.host = host
64
65
66
class DefaultSMTPConnectionRefused(SMTPConnectionRefused):
67
68
    _fmt = "Please specify smtp_server.  No server at default %(host)s."
69
70
71
class NoDestinationAddress(InternalBzrError):
72
73
    _fmt = "Message does not have a destination address."
74
75
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
76
class SMTPConnection(object):
77
    """Connect to an SMTP server and send an email.
78
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
79
    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.
80
    understands the basic bzr SMTP configuration information: smtp_server,
81
    smtp_username, and smtp_password.
82
    """
83
84
    _default_smtp_server = 'localhost'
85
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
86
    def __init__(self, config, _smtp_factory=None):
87
        self._smtp_factory = _smtp_factory
88
        if self._smtp_factory is None:
89
            self._smtp_factory = smtplib.SMTP
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
90
        self._config = config
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
91
        self._config_smtp_server = config.get('smtp_server')
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
92
        self._smtp_server = self._config_smtp_server
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
93
        if self._smtp_server is None:
94
            self._smtp_server = self._default_smtp_server
95
6379.8.1 by Jelmer Vernooij
Register smtp options in bzrlib.config, and use config stacks in bzrlib.smtp_conection.
96
        self._smtp_username = config.get('smtp_username')
97
        self._smtp_password = config.get('smtp_password')
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
98
99
        self._connection = None
100
101
    def _connect(self):
102
        """If we haven't connected, connect and authenticate."""
103
        if self._connection is not None:
104
            return
105
106
        self._create_connection()
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
107
        # FIXME: _authenticate() should only be called when the server has
7143.15.2 by Jelmer Vernooij
Run autopep8.
108
        # 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.
109
        # with the default username. JRV20090407
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
110
        self._authenticate()
111
112
    def _create_connection(self):
113
        """Create an SMTP connection."""
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
114
        self._connection = self._smtp_factory()
115
        try:
116
            self._connection.connect(self._smtp_server)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
117
        except socket.error as e:
2694.2.1 by Aaron Bentley
Make error handling nicer when SMTP server not working
118
            if e.args[0] == errno.ECONNREFUSED:
119
                if self._config_smtp_server is None:
120
                    raise DefaultSMTPConnectionRefused(socket.error,
121
                                                       self._smtp_server)
122
                else:
123
                    raise SMTPConnectionRefused(socket.error,
124
                                                self._smtp_server)
125
            else:
126
                raise
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
127
2898.2.3 by James Henstridge
* Fix merge-directive blackbox test.
128
        # 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
129
        code, resp = self._connection.ehlo()
130
        if not (200 <= code <= 299):
131
            code, resp = self._connection.helo()
132
            if not (200 <= code <= 299):
133
                raise SMTPError("server refused HELO: %d %s" % (code, resp))
134
135
        # Use TLS if the server advertised it:
136
        if self._connection.has_extn("starttls"):
137
            code, resp = self._connection.starttls()
138
            if not (200 <= code <= 299):
7143.15.2 by Jelmer Vernooij
Run autopep8.
139
                raise SMTPError("server refused STARTTLS: %d %s" %
140
                                (code, resp))
2898.2.1 by James Henstridge
Update SMTPConnection._create_connection to better follow the SMTP
141
            # Say EHLO again, to check for newly revealed features
142
            code, resp = self._connection.ehlo()
143
            if not (200 <= code <= 299):
144
                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.
145
146
    def _authenticate(self):
147
        """If necessary authenticate yourself to the server."""
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
148
        auth = config.AuthenticationConfig()
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
149
        if self._smtp_username is None:
4222.3.10 by Jelmer Vernooij
Avoid using the default username in the case of SMTP.
150
            # FIXME: Since _authenticate gets called even when no authentication
7143.15.2 by Jelmer Vernooij
Run autopep8.
151
            # 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.
152
            # here yet.
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
153
            self._smtp_username = auth.get_user('smtp', self._smtp_server)
154
            if self._smtp_username is None:
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
155
                return
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
156
2900.2.12 by Vincent Ladeuil
Since all schemes query AuthenticationConfig then prompt user, make that
157
        if self._smtp_password is None:
158
            self._smtp_password = auth.get_password(
159
                'smtp', self._smtp_server, self._smtp_username)
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
160
4147.1.1 by James Henstridge
Ensure that byte strings are passed to SMTP.login(), as passing unicode
161
        # 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
162
        # 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
163
        # encodings, but the SASL PLAIN spec says UTF-8, so that's
164
        # what we'll use.
165
        username = osutils.safe_utf8(self._smtp_username)
166
        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
167
4147.1.3 by James Henstridge
Switch to using osutils.safe_utf8() as suggested by Vincent. Also
168
        self._connection.login(username, password)
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
169
170
    @staticmethod
171
    def get_message_addresses(message):
172
        """Get the origin and destination addresses of a message.
173
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
174
        :param message: A message object supporting get() to access its
6791.2.3 by Jelmer Vernooij
Fix more imports.
175
            headers, like email.message.Message or
176
            breezy.email_message.EmailMessage.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
177
        :return: A pair (from_email, to_emails), where from_email is the email
178
            address in the From header, and to_emails a list of all the
179
            addresses in the To, Cc, and Bcc headers.
180
        """
6797 by Jelmer Vernooij
Merge lp:~jelmer/brz/fix-imports.
181
        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.
182
        to_full_addresses = []
183
        for header in ['To', 'Cc', 'Bcc']:
2625.6.1 by Adeodato Simó
New EmailMessage class, façade around email.Message and MIMEMultipart.
184
            value = message.get(header, None)
185
            if value:
186
                to_full_addresses.append(value)
7143.15.2 by Jelmer Vernooij
Run autopep8.
187
        to_emails = [pair[1] for pair in
188
                     getaddresses(to_full_addresses)]
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
189
190
        return from_email, to_emails
191
192
    def send_email(self, message):
193
        """Send an email message.
194
2547.1.1 by Aaron Bentley
Add SMTPConnection class (Adeodato Simó)
195
        The message will be sent to all addresses in the To, Cc and Bcc
196
        headers.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
197
6791.2.3 by Jelmer Vernooij
Fix more imports.
198
        :param message: An email.message.Message or
199
            email.mime.multipart.MIMEMultipart object.
2535.2.1 by Adeodato Simó
New SMTPConnection class, a reduced version of that in bzr-email.
200
        :return: None
201
        """
202
        from_email, to_emails = self.get_message_addresses(message)
203
204
        if not to_emails:
205
            raise NoDestinationAddress
206
207
        try:
208
            self._connect()
2547.1.1 by Aaron Bentley
Add SMTPConnection class (Adeodato Simó)
209
            self._connection.sendmail(from_email, to_emails,
210
                                      message.as_string())
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
211
        except smtplib.SMTPRecipientsRefused as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
212
            raise SMTPError('server refused recipient: %d %s' %
7143.15.2 by Jelmer Vernooij
Run autopep8.
213
                            next(iter(e.recipients.values())))
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
214
        except smtplib.SMTPResponseException as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
215
            raise SMTPError('%d %s' % (e.smtp_code, e.smtp_error))
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
216
        except smtplib.SMTPException as e:
2535.2.4 by Adeodato Simó
Don't use BzrCommandError in non-UI code; create and use an SMTPError
217
            raise SMTPError(str(e))