~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smtp_connection.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""A convenience class around smtplib."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
from email import Utils
20
22
import errno
21
23
import smtplib
22
24
import socket
23
25
 
24
 
from bzrlib import ui
 
26
from bzrlib import (
 
27
    config,
 
28
    osutils,
 
29
    )
25
30
from bzrlib.errors import (
26
31
    NoDestinationAddress,
27
32
    SMTPError,
30
35
    )
31
36
 
32
37
 
 
38
smtp_password = config.Option('smtp_password', default=None,
 
39
        help='''\
 
40
Password to use for authentication to SMTP server.
 
41
''')
 
42
smtp_server = config.Option('smtp_server', default=None,
 
43
        help='''\
 
44
Hostname of the SMTP server to use for sending email.
 
45
''')
 
46
smtp_username = config.Option('smtp_username', default=None,
 
47
        help='''\
 
48
Username to use for authentication to SMTP server.
 
49
''')
 
50
 
 
51
 
33
52
class SMTPConnection(object):
34
53
    """Connect to an SMTP server and send an email.
35
54
 
45
64
        if self._smtp_factory is None:
46
65
            self._smtp_factory = smtplib.SMTP
47
66
        self._config = config
48
 
        self._config_smtp_server = config.get_user_option('smtp_server')
 
67
        self._config_smtp_server = config.get('smtp_server')
49
68
        self._smtp_server = self._config_smtp_server
50
69
        if self._smtp_server is None:
51
70
            self._smtp_server = self._default_smtp_server
52
71
 
53
 
        self._smtp_username = config.get_user_option('smtp_username')
54
 
        self._smtp_password = config.get_user_option('smtp_password')
 
72
        self._smtp_username = config.get('smtp_username')
 
73
        self._smtp_password = config.get('smtp_password')
55
74
 
56
75
        self._connection = None
57
76
 
61
80
            return
62
81
 
63
82
        self._create_connection()
 
83
        # FIXME: _authenticate() should only be called when the server has
 
84
        # refused unauthenticated access, so it can safely try to authenticate 
 
85
        # with the default username. JRV20090407
64
86
        self._authenticate()
65
87
 
66
88
    def _create_connection(self):
79
101
            else:
80
102
                raise
81
103
 
82
 
        # If this fails, it just returns an error, but it shouldn't raise an
83
 
        # exception unless something goes really wrong (in which case we want
84
 
        # to fail anyway).
85
 
        self._connection.starttls()
 
104
        # Say EHLO (falling back to HELO) to query the server's features.
 
105
        code, resp = self._connection.ehlo()
 
106
        if not (200 <= code <= 299):
 
107
            code, resp = self._connection.helo()
 
108
            if not (200 <= code <= 299):
 
109
                raise SMTPError("server refused HELO: %d %s" % (code, resp))
 
110
 
 
111
        # Use TLS if the server advertised it:
 
112
        if self._connection.has_extn("starttls"):
 
113
            code, resp = self._connection.starttls()
 
114
            if not (200 <= code <= 299):
 
115
                raise SMTPError("server refused STARTTLS: %d %s" % (code, resp))
 
116
            # Say EHLO again, to check for newly revealed features
 
117
            code, resp = self._connection.ehlo()
 
118
            if not (200 <= code <= 299):
 
119
                raise SMTPError("server refused EHLO: %d %s" % (code, resp))
86
120
 
87
121
    def _authenticate(self):
88
122
        """If necessary authenticate yourself to the server."""
 
123
        auth = config.AuthenticationConfig()
89
124
        if self._smtp_username is None:
90
 
            return
 
125
            # FIXME: Since _authenticate gets called even when no authentication
 
126
            # is necessary, it's not possible to use the default username 
 
127
            # here yet.
 
128
            self._smtp_username = auth.get_user('smtp', self._smtp_server)
 
129
            if self._smtp_username is None:
 
130
                return
91
131
 
92
132
        if self._smtp_password is None:
93
 
            self._smtp_password = ui.ui_factory.get_password(
94
 
                'Please enter the SMTP password: %(user)s@%(host)s',
95
 
                user=self._smtp_username,
96
 
                host=self._smtp_server)
97
 
 
98
 
        self._connection.login(self._smtp_username, self._smtp_password)
 
133
            self._smtp_password = auth.get_password(
 
134
                'smtp', self._smtp_server, self._smtp_username)
 
135
 
 
136
        # smtplib requires that the username and password be byte
 
137
        # strings.  The CRAM-MD5 spec doesn't give any guidance on
 
138
        # encodings, but the SASL PLAIN spec says UTF-8, so that's
 
139
        # what we'll use.
 
140
        username = osutils.safe_utf8(self._smtp_username)
 
141
        password = osutils.safe_utf8(self._smtp_password)
 
142
 
 
143
        self._connection.login(username, password)
99
144
 
100
145
    @staticmethod
101
146
    def get_message_addresses(message):