~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smtp_connection.py

  • Committer: Patch Queue Manager
  • Date: 2012-04-10 10:26:46 UTC
  • mfrom: (6519.1.2 responsefile-readline)
  • Revision ID: pqm@pqm.ubuntu.com-20120410102646-rv7zpi85e168o1k4
(jelmer) Implement ResponseFile.readline. (Jelmer Vernooij)

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
 
22
import errno
20
23
import smtplib
21
 
 
22
 
from bzrlib import ui
23
 
from bzrlib.errors import NoDestinationAddress, SMTPError
 
24
import socket
 
25
 
 
26
from bzrlib import (
 
27
    config,
 
28
    osutils,
 
29
    )
 
30
from bzrlib.errors import (
 
31
    NoDestinationAddress,
 
32
    SMTPError,
 
33
    DefaultSMTPConnectionRefused,
 
34
    SMTPConnectionRefused,
 
35
    )
 
36
 
 
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
''')
24
50
 
25
51
 
26
52
class SMTPConnection(object):
33
59
 
34
60
    _default_smtp_server = 'localhost'
35
61
 
36
 
    def __init__(self, config):
 
62
    def __init__(self, config, _smtp_factory=None):
 
63
        self._smtp_factory = _smtp_factory
 
64
        if self._smtp_factory is None:
 
65
            self._smtp_factory = smtplib.SMTP
37
66
        self._config = config
38
 
        self._smtp_server = config.get_user_option('smtp_server')
 
67
        self._config_smtp_server = config.get('smtp_server')
 
68
        self._smtp_server = self._config_smtp_server
39
69
        if self._smtp_server is None:
40
70
            self._smtp_server = self._default_smtp_server
41
71
 
42
 
        self._smtp_username = config.get_user_option('smtp_username')
43
 
        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')
44
74
 
45
75
        self._connection = None
46
76
 
50
80
            return
51
81
 
52
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
53
86
        self._authenticate()
54
87
 
55
88
    def _create_connection(self):
56
89
        """Create an SMTP connection."""
57
 
        self._connection = smtplib.SMTP()
58
 
        self._connection.connect(self._smtp_server)
59
 
 
60
 
        # If this fails, it just returns an error, but it shouldn't raise an
61
 
        # exception unless something goes really wrong (in which case we want
62
 
        # to fail anyway).
63
 
        self._connection.starttls()
 
90
        self._connection = self._smtp_factory()
 
91
        try:
 
92
            self._connection.connect(self._smtp_server)
 
93
        except socket.error, e:
 
94
            if e.args[0] == errno.ECONNREFUSED:
 
95
                if self._config_smtp_server is None:
 
96
                    raise DefaultSMTPConnectionRefused(socket.error,
 
97
                                                       self._smtp_server)
 
98
                else:
 
99
                    raise SMTPConnectionRefused(socket.error,
 
100
                                                self._smtp_server)
 
101
            else:
 
102
                raise
 
103
 
 
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))
64
120
 
65
121
    def _authenticate(self):
66
122
        """If necessary authenticate yourself to the server."""
 
123
        auth = config.AuthenticationConfig()
67
124
        if self._smtp_username is None:
68
 
            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
69
131
 
70
132
        if self._smtp_password is None:
71
 
            self._smtp_password = ui.ui_factory.get_password(
72
 
                'Please enter the SMTP password: %(user)s@%(host)s',
73
 
                user=self._smtp_username,
74
 
                host=self._smtp_server)
75
 
 
76
 
        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)
77
144
 
78
145
    @staticmethod
79
146
    def get_message_addresses(message):
80
147
        """Get the origin and destination addresses of a message.
81
148
 
82
 
        :param message: An email.Message or email.MIMEMultipart object.
 
149
        :param message: A message object supporting get() to access its
 
150
            headers, like email.Message or bzrlib.email_message.EmailMessage.
83
151
        :return: A pair (from_email, to_emails), where from_email is the email
84
152
            address in the From header, and to_emails a list of all the
85
153
            addresses in the To, Cc, and Bcc headers.
86
154
        """
87
 
        from_email = Utils.parseaddr(message['From'])[1]
 
155
        from_email = Utils.parseaddr(message.get('From', None))[1]
88
156
        to_full_addresses = []
89
157
        for header in ['To', 'Cc', 'Bcc']:
90
 
            to_full_addresses += message.get_all(header, [])
 
158
            value = message.get(header, None)
 
159
            if value:
 
160
                to_full_addresses.append(value)
91
161
        to_emails = [ pair[1] for pair in
92
162
                Utils.getaddresses(to_full_addresses) ]
93
163