1#! /usr/bin/env python3
2
3'''SMTP/ESMTP client class.
4
5This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6Authentication) and RFC 2487 (Secure SMTP over TLS).
7
8Notes:
9
10Please remember, when doing ESMTP, that the names of the SMTP service
11extensions are NOT the same thing as the option keywords for the RCPT
12and MAIL commands!
13
14Example:
15
16  >>> import smtplib
17  >>> s=smtplib.SMTP("localhost")
18  >>> print(s.help())
19  This is Sendmail version 8.8.4
20  Topics:
21      HELO    EHLO    MAIL    RCPT    DATA
22      RSET    NOOP    QUIT    HELP    VRFY
23      EXPN    VERB    ETRN    DSN
24  For more info use "HELP <topic>".
25  To report bugs in the implementation send email to
26      sendmail-bugs@sendmail.org.
27  For local information send email to Postmaster at your site.
28  End of HELP info
29  >>> s.putcmd("vrfy","someone@here")
30  >>> s.getreply()
31  (250, "Somebody OverHere <somebody@here.my.org>")
32  >>> s.quit()
33'''
34
35# Author: The Dragon De Monsyne <dragondm@integral.org>
36# ESMTP support, test code and doc fixes added by
37#     Eric S. Raymond <esr@thyrsus.com>
38# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
39#     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
40# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
41#
42# This was modified from the Python 1.5 library HTTP lib.
43
44import socket
45import io
46import re
47import email.utils
48import email.message
49import email.generator
50import base64
51import hmac
52import copy
53import datetime
54import sys
55from email.base64mime import body_encode as encode_base64
56
57__all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException",
58           "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError",
59           "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError",
60           "quoteaddr", "quotedata", "SMTP"]
61
62SMTP_PORT = 25
63SMTP_SSL_PORT = 465
64CRLF = "\r\n"
65bCRLF = b"\r\n"
66_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
67
68OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
69
70# Exception classes used by this module.
71class SMTPException(OSError):
72    """Base class for all exceptions raised by this module."""
73
74class SMTPNotSupportedError(SMTPException):
75    """The command or option is not supported by the SMTP server.
76
77    This exception is raised when an attempt is made to run a command or a
78    command with an option which is not supported by the server.
79    """
80
81class SMTPServerDisconnected(SMTPException):
82    """Not connected to any SMTP server.
83
84    This exception is raised when the server unexpectedly disconnects,
85    or when an attempt is made to use the SMTP instance before
86    connecting it to a server.
87    """
88
89class SMTPResponseException(SMTPException):
90    """Base class for all exceptions that include an SMTP error code.
91
92    These exceptions are generated in some instances when the SMTP
93    server returns an error code.  The error code is stored in the
94    `smtp_code' attribute of the error, and the `smtp_error' attribute
95    is set to the error message.
96    """
97
98    def __init__(self, code, msg):
99        self.smtp_code = code
100        self.smtp_error = msg
101        self.args = (code, msg)
102
103class SMTPSenderRefused(SMTPResponseException):
104    """Sender address refused.
105
106    In addition to the attributes set by on all SMTPResponseException
107    exceptions, this sets `sender' to the string that the SMTP refused.
108    """
109
110    def __init__(self, code, msg, sender):
111        self.smtp_code = code
112        self.smtp_error = msg
113        self.sender = sender
114        self.args = (code, msg, sender)
115
116class SMTPRecipientsRefused(SMTPException):
117    """All recipient addresses refused.
118
119    The errors for each recipient are accessible through the attribute
120    'recipients', which is a dictionary of exactly the same sort as
121    SMTP.sendmail() returns.
122    """
123
124    def __init__(self, recipients):
125        self.recipients = recipients
126        self.args = (recipients,)
127
128
129class SMTPDataError(SMTPResponseException):
130    """The SMTP server didn't accept the data."""
131
132class SMTPConnectError(SMTPResponseException):
133    """Error during connection establishment."""
134
135class SMTPHeloError(SMTPResponseException):
136    """The server refused our HELO reply."""
137
138class SMTPAuthenticationError(SMTPResponseException):
139    """Authentication error.
140
141    Most probably the server didn't accept the username/password
142    combination provided.
143    """
144
145def quoteaddr(addrstring):
146    """Quote a subset of the email addresses defined by RFC 821.
147
148    Should be able to handle anything email.utils.parseaddr can handle.
149    """
150    displayname, addr = email.utils.parseaddr(addrstring)
151    if (displayname, addr) == ('', ''):
152        # parseaddr couldn't parse it, use it as is and hope for the best.
153        if addrstring.strip().startswith('<'):
154            return addrstring
155        return "<%s>" % addrstring
156    return "<%s>" % addr
157
158def _addr_only(addrstring):
159    displayname, addr = email.utils.parseaddr(addrstring)
160    if (displayname, addr) == ('', ''):
161        # parseaddr couldn't parse it, so use it as is.
162        return addrstring
163    return addr
164
165# Legacy method kept for backward compatibility.
166def quotedata(data):
167    """Quote data for email.
168
169    Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
170    Internet CRLF end-of-line.
171    """
172    return re.sub(r'(?m)^\.', '..',
173        re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
174
175def _quote_periods(bindata):
176    return re.sub(br'(?m)^\.', b'..', bindata)
177
178def _fix_eols(data):
179    return  re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)
180
181try:
182    import ssl
183except ImportError:
184    _have_ssl = False
185else:
186    _have_ssl = True
187
188
189class SMTP:
190    """This class manages a connection to an SMTP or ESMTP server.
191    SMTP Objects:
192        SMTP objects have the following attributes:
193            helo_resp
194                This is the message given by the server in response to the
195                most recent HELO command.
196
197            ehlo_resp
198                This is the message given by the server in response to the
199                most recent EHLO command. This is usually multiline.
200
201            does_esmtp
202                This is a True value _after you do an EHLO command_, if the
203                server supports ESMTP.
204
205            esmtp_features
206                This is a dictionary, which, if the server supports ESMTP,
207                will _after you do an EHLO command_, contain the names of the
208                SMTP service extensions this server supports, and their
209                parameters (if any).
210
211                Note, all extension names are mapped to lower case in the
212                dictionary.
213
214        See each method's docstrings for details.  In general, there is a
215        method of the same name to perform each SMTP command.  There is also a
216        method called 'sendmail' that will do an entire mail transaction.
217        """
218    debuglevel = 0
219    file = None
220    helo_resp = None
221    ehlo_msg = "ehlo"
222    ehlo_resp = None
223    does_esmtp = 0
224    default_port = SMTP_PORT
225
226    def __init__(self, host='', port=0, local_hostname=None,
227                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
228                 source_address=None):
229        """Initialize a new instance.
230
231        If specified, `host' is the name of the remote host to which to
232        connect.  If specified, `port' specifies the port to which to connect.
233        By default, smtplib.SMTP_PORT is used.  If a host is specified the
234        connect method is called, and if it returns anything other than a
235        success code an SMTPConnectError is raised.  If specified,
236        `local_hostname` is used as the FQDN of the local host in the HELO/EHLO
237        command.  Otherwise, the local hostname is found using
238        socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host,
239        port) for the socket to bind to as its source address before
240        connecting. If the host is '' and port is 0, the OS default behavior
241        will be used.
242
243        """
244        self._host = host
245        self.timeout = timeout
246        self.esmtp_features = {}
247        self.command_encoding = 'ascii'
248        self.source_address = source_address
249
250        if host:
251            (code, msg) = self.connect(host, port)
252            if code != 220:
253                raise SMTPConnectError(code, msg)
254        if local_hostname is not None:
255            self.local_hostname = local_hostname
256        else:
257            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
258            # if that can't be calculated, that we should use a domain literal
259            # instead (essentially an encoded IP address like [A.B.C.D]).
260            fqdn = socket.getfqdn()
261            if '.' in fqdn:
262                self.local_hostname = fqdn
263            else:
264                # We can't find an fqdn hostname, so use a domain literal
265                addr = '127.0.0.1'
266                try:
267                    addr = socket.gethostbyname(socket.gethostname())
268                except socket.gaierror:
269                    pass
270                self.local_hostname = '[%s]' % addr
271
272    def __enter__(self):
273        return self
274
275    def __exit__(self, *args):
276        try:
277            code, message = self.docmd("QUIT")
278            if code != 221:
279                raise SMTPResponseException(code, message)
280        except SMTPServerDisconnected:
281            pass
282        finally:
283            self.close()
284
285    def set_debuglevel(self, debuglevel):
286        """Set the debug output level.
287
288        A non-false value results in debug messages for connection and for all
289        messages sent to and received from the server.
290
291        """
292        self.debuglevel = debuglevel
293
294    def _print_debug(self, *args):
295        if self.debuglevel > 1:
296            print(datetime.datetime.now().time(), *args, file=sys.stderr)
297        else:
298            print(*args, file=sys.stderr)
299
300    def _get_socket(self, host, port, timeout):
301        # This makes it simpler for SMTP_SSL to use the SMTP connect code
302        # and just alter the socket connection bit.
303        if self.debuglevel > 0:
304            self._print_debug('connect: to', (host, port), self.source_address)
305        return socket.create_connection((host, port), timeout,
306                                        self.source_address)
307
308    def connect(self, host='localhost', port=0, source_address=None):
309        """Connect to a host on a given port.
310
311        If the hostname ends with a colon (`:') followed by a number, and
312        there is no port specified, that suffix will be stripped off and the
313        number interpreted as the port number to use.
314
315        Note: This method is automatically invoked by __init__, if a host is
316        specified during instantiation.
317
318        """
319
320        if source_address:
321            self.source_address = source_address
322
323        if not port and (host.find(':') == host.rfind(':')):
324            i = host.rfind(':')
325            if i >= 0:
326                host, port = host[:i], host[i + 1:]
327                try:
328                    port = int(port)
329                except ValueError:
330                    raise OSError("nonnumeric port")
331        if not port:
332            port = self.default_port
333        if self.debuglevel > 0:
334            self._print_debug('connect:', (host, port))
335        self.sock = self._get_socket(host, port, self.timeout)
336        self.file = None
337        (code, msg) = self.getreply()
338        if self.debuglevel > 0:
339            self._print_debug('connect:', repr(msg))
340        return (code, msg)
341
342    def send(self, s):
343        """Send `s' to the server."""
344        if self.debuglevel > 0:
345            self._print_debug('send:', repr(s))
346        if hasattr(self, 'sock') and self.sock:
347            if isinstance(s, str):
348                # send is used by the 'data' command, where command_encoding
349                # should not be used, but 'data' needs to convert the string to
350                # binary itself anyway, so that's not a problem.
351                s = s.encode(self.command_encoding)
352            try:
353                self.sock.sendall(s)
354            except OSError:
355                self.close()
356                raise SMTPServerDisconnected('Server not connected')
357        else:
358            raise SMTPServerDisconnected('please run connect() first')
359
360    def putcmd(self, cmd, args=""):
361        """Send a command to the server."""
362        if args == "":
363            str = '%s%s' % (cmd, CRLF)
364        else:
365            str = '%s %s%s' % (cmd, args, CRLF)
366        self.send(str)
367
368    def getreply(self):
369        """Get a reply from the server.
370
371        Returns a tuple consisting of:
372
373          - server response code (e.g. '250', or such, if all goes well)
374            Note: returns -1 if it can't read response code.
375
376          - server response string corresponding to response code (multiline
377            responses are converted to a single, multiline string).
378
379        Raises SMTPServerDisconnected if end-of-file is reached.
380        """
381        resp = []
382        if self.file is None:
383            self.file = self.sock.makefile('rb')
384        while 1:
385            try:
386                line = self.file.readline(_MAXLINE + 1)
387            except OSError as e:
388                self.close()
389                raise SMTPServerDisconnected("Connection unexpectedly closed: "
390                                             + str(e))
391            if not line:
392                self.close()
393                raise SMTPServerDisconnected("Connection unexpectedly closed")
394            if self.debuglevel > 0:
395                self._print_debug('reply:', repr(line))
396            if len(line) > _MAXLINE:
397                self.close()
398                raise SMTPResponseException(500, "Line too long.")
399            resp.append(line[4:].strip(b' \t\r\n'))
400            code = line[:3]
401            # Check that the error code is syntactically correct.
402            # Don't attempt to read a continuation line if it is broken.
403            try:
404                errcode = int(code)
405            except ValueError:
406                errcode = -1
407                break
408            # Check if multiline response.
409            if line[3:4] != b"-":
410                break
411
412        errmsg = b"\n".join(resp)
413        if self.debuglevel > 0:
414            self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg))
415        return errcode, errmsg
416
417    def docmd(self, cmd, args=""):
418        """Send a command, and return its response code."""
419        self.putcmd(cmd, args)
420        return self.getreply()
421
422    # std smtp commands
423    def helo(self, name=''):
424        """SMTP 'helo' command.
425        Hostname to send for this command defaults to the FQDN of the local
426        host.
427        """
428        self.putcmd("helo", name or self.local_hostname)
429        (code, msg) = self.getreply()
430        self.helo_resp = msg
431        return (code, msg)
432
433    def ehlo(self, name=''):
434        """ SMTP 'ehlo' command.
435        Hostname to send for this command defaults to the FQDN of the local
436        host.
437        """
438        self.esmtp_features = {}
439        self.putcmd(self.ehlo_msg, name or self.local_hostname)
440        (code, msg) = self.getreply()
441        # According to RFC1869 some (badly written)
442        # MTA's will disconnect on an ehlo. Toss an exception if
443        # that happens -ddm
444        if code == -1 and len(msg) == 0:
445            self.close()
446            raise SMTPServerDisconnected("Server not connected")
447        self.ehlo_resp = msg
448        if code != 250:
449            return (code, msg)
450        self.does_esmtp = 1
451        #parse the ehlo response -ddm
452        assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp)
453        resp = self.ehlo_resp.decode("latin-1").split('\n')
454        del resp[0]
455        for each in resp:
456            # To be able to communicate with as many SMTP servers as possible,
457            # we have to take the old-style auth advertisement into account,
458            # because:
459            # 1) Else our SMTP feature parser gets confused.
460            # 2) There are some servers that only advertise the auth methods we
461            #    support using the old style.
462            auth_match = OLDSTYLE_AUTH.match(each)
463            if auth_match:
464                # This doesn't remove duplicates, but that's no problem
465                self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
466                        + " " + auth_match.groups(0)[0]
467                continue
468
469            # RFC 1869 requires a space between ehlo keyword and parameters.
470            # It's actually stricter, in that only spaces are allowed between
471            # parameters, but were not going to check for that here.  Note
472            # that the space isn't present if there are no parameters.
473            m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
474            if m:
475                feature = m.group("feature").lower()
476                params = m.string[m.end("feature"):].strip()
477                if feature == "auth":
478                    self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
479                            + " " + params
480                else:
481                    self.esmtp_features[feature] = params
482        return (code, msg)
483
484    def has_extn(self, opt):
485        """Does the server support a given SMTP service extension?"""
486        return opt.lower() in self.esmtp_features
487
488    def help(self, args=''):
489        """SMTP 'help' command.
490        Returns help text from server."""
491        self.putcmd("help", args)
492        return self.getreply()[1]
493
494    def rset(self):
495        """SMTP 'rset' command -- resets session."""
496        self.command_encoding = 'ascii'
497        return self.docmd("rset")
498
499    def _rset(self):
500        """Internal 'rset' command which ignores any SMTPServerDisconnected error.
501
502        Used internally in the library, since the server disconnected error
503        should appear to the application when the *next* command is issued, if
504        we are doing an internal "safety" reset.
505        """
506        try:
507            self.rset()
508        except SMTPServerDisconnected:
509            pass
510
511    def noop(self):
512        """SMTP 'noop' command -- doesn't do anything :>"""
513        return self.docmd("noop")
514
515    def mail(self, sender, options=[]):
516        """SMTP 'mail' command -- begins mail xfer session.
517
518        This method may raise the following exceptions:
519
520         SMTPNotSupportedError  The options parameter includes 'SMTPUTF8'
521                                but the SMTPUTF8 extension is not supported by
522                                the server.
523        """
524        optionlist = ''
525        if options and self.does_esmtp:
526            if any(x.lower()=='smtputf8' for x in options):
527                if self.has_extn('smtputf8'):
528                    self.command_encoding = 'utf-8'
529                else:
530                    raise SMTPNotSupportedError(
531                        'SMTPUTF8 not supported by server')
532            optionlist = ' ' + ' '.join(options)
533        self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
534        return self.getreply()
535
536    def rcpt(self, recip, options=[]):
537        """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
538        optionlist = ''
539        if options and self.does_esmtp:
540            optionlist = ' ' + ' '.join(options)
541        self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist))
542        return self.getreply()
543
544    def data(self, msg):
545        """SMTP 'DATA' command -- sends message data to server.
546
547        Automatically quotes lines beginning with a period per rfc821.
548        Raises SMTPDataError if there is an unexpected reply to the
549        DATA command; the return value from this method is the final
550        response code received when the all data is sent.  If msg
551        is a string, lone '\\r' and '\\n' characters are converted to
552        '\\r\\n' characters.  If msg is bytes, it is transmitted as is.
553        """
554        self.putcmd("data")
555        (code, repl) = self.getreply()
556        if self.debuglevel > 0:
557            self._print_debug('data:', (code, repl))
558        if code != 354:
559            raise SMTPDataError(code, repl)
560        else:
561            if isinstance(msg, str):
562                msg = _fix_eols(msg).encode('ascii')
563            q = _quote_periods(msg)
564            if q[-2:] != bCRLF:
565                q = q + bCRLF
566            q = q + b"." + bCRLF
567            self.send(q)
568            (code, msg) = self.getreply()
569            if self.debuglevel > 0:
570                self._print_debug('data:', (code, msg))
571            return (code, msg)
572
573    def verify(self, address):
574        """SMTP 'verify' command -- checks for address validity."""
575        self.putcmd("vrfy", _addr_only(address))
576        return self.getreply()
577    # a.k.a.
578    vrfy = verify
579
580    def expn(self, address):
581        """SMTP 'expn' command -- expands a mailing list."""
582        self.putcmd("expn", _addr_only(address))
583        return self.getreply()
584
585    # some useful methods
586
587    def ehlo_or_helo_if_needed(self):
588        """Call self.ehlo() and/or self.helo() if needed.
589
590        If there has been no previous EHLO or HELO command this session, this
591        method tries ESMTP EHLO first.
592
593        This method may raise the following exceptions:
594
595         SMTPHeloError            The server didn't reply properly to
596                                  the helo greeting.
597        """
598        if self.helo_resp is None and self.ehlo_resp is None:
599            if not (200 <= self.ehlo()[0] <= 299):
600                (code, resp) = self.helo()
601                if not (200 <= code <= 299):
602                    raise SMTPHeloError(code, resp)
603
604    def auth(self, mechanism, authobject, *, initial_response_ok=True):
605        """Authentication command - requires response processing.
606
607        'mechanism' specifies which authentication mechanism is to
608        be used - the valid values are those listed in the 'auth'
609        element of 'esmtp_features'.
610
611        'authobject' must be a callable object taking a single argument:
612
613                data = authobject(challenge)
614
615        It will be called to process the server's challenge response; the
616        challenge argument it is passed will be a bytes.  It should return
617        bytes data that will be base64 encoded and sent to the server.
618
619        Keyword arguments:
620            - initial_response_ok: Allow sending the RFC 4954 initial-response
621              to the AUTH command, if the authentication methods supports it.
622        """
623        # RFC 4954 allows auth methods to provide an initial response.  Not all
624        # methods support it.  By definition, if they return something other
625        # than None when challenge is None, then they do.  See issue #15014.
626        mechanism = mechanism.upper()
627        initial_response = (authobject() if initial_response_ok else None)
628        if initial_response is not None:
629            response = encode_base64(initial_response.encode('ascii'), eol='')
630            (code, resp) = self.docmd("AUTH", mechanism + " " + response)
631        else:
632            (code, resp) = self.docmd("AUTH", mechanism)
633        # If server responds with a challenge, send the response.
634        if code == 334:
635            challenge = base64.decodebytes(resp)
636            response = encode_base64(
637                authobject(challenge).encode('ascii'), eol='')
638            (code, resp) = self.docmd(response)
639        if code in (235, 503):
640            return (code, resp)
641        raise SMTPAuthenticationError(code, resp)
642
643    def auth_cram_md5(self, challenge=None):
644        """ Authobject to use with CRAM-MD5 authentication. Requires self.user
645        and self.password to be set."""
646        # CRAM-MD5 does not support initial-response.
647        if challenge is None:
648            return None
649        return self.user + " " + hmac.HMAC(
650            self.password.encode('ascii'), challenge, 'md5').hexdigest()
651
652    def auth_plain(self, challenge=None):
653        """ Authobject to use with PLAIN authentication. Requires self.user and
654        self.password to be set."""
655        return "\0%s\0%s" % (self.user, self.password)
656
657    def auth_login(self, challenge=None):
658        """ Authobject to use with LOGIN authentication. Requires self.user and
659        self.password to be set."""
660        if challenge is None:
661            return self.user
662        else:
663            return self.password
664
665    def login(self, user, password, *, initial_response_ok=True):
666        """Log in on an SMTP server that requires authentication.
667
668        The arguments are:
669            - user:         The user name to authenticate with.
670            - password:     The password for the authentication.
671
672        Keyword arguments:
673            - initial_response_ok: Allow sending the RFC 4954 initial-response
674              to the AUTH command, if the authentication methods supports it.
675
676        If there has been no previous EHLO or HELO command this session, this
677        method tries ESMTP EHLO first.
678
679        This method will return normally if the authentication was successful.
680
681        This method may raise the following exceptions:
682
683         SMTPHeloError            The server didn't reply properly to
684                                  the helo greeting.
685         SMTPAuthenticationError  The server didn't accept the username/
686                                  password combination.
687         SMTPNotSupportedError    The AUTH command is not supported by the
688                                  server.
689         SMTPException            No suitable authentication method was
690                                  found.
691        """
692
693        self.ehlo_or_helo_if_needed()
694        if not self.has_extn("auth"):
695            raise SMTPNotSupportedError(
696                "SMTP AUTH extension not supported by server.")
697
698        # Authentication methods the server claims to support
699        advertised_authlist = self.esmtp_features["auth"].split()
700
701        # Authentication methods we can handle in our preferred order:
702        preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']
703
704        # We try the supported authentications in our preferred order, if
705        # the server supports them.
706        authlist = [auth for auth in preferred_auths
707                    if auth in advertised_authlist]
708        if not authlist:
709            raise SMTPException("No suitable authentication method found.")
710
711        # Some servers advertise authentication methods they don't really
712        # support, so if authentication fails, we continue until we've tried
713        # all methods.
714        self.user, self.password = user, password
715        for authmethod in authlist:
716            method_name = 'auth_' + authmethod.lower().replace('-', '_')
717            try:
718                (code, resp) = self.auth(
719                    authmethod, getattr(self, method_name),
720                    initial_response_ok=initial_response_ok)
721                # 235 == 'Authentication successful'
722                # 503 == 'Error: already authenticated'
723                if code in (235, 503):
724                    return (code, resp)
725            except SMTPAuthenticationError as e:
726                last_exception = e
727
728        # We could not login successfully.  Return result of last attempt.
729        raise last_exception
730
731    def starttls(self, keyfile=None, certfile=None, context=None):
732        """Puts the connection to the SMTP server into TLS mode.
733
734        If there has been no previous EHLO or HELO command this session, this
735        method tries ESMTP EHLO first.
736
737        If the server supports TLS, this will encrypt the rest of the SMTP
738        session. If you provide the keyfile and certfile parameters,
739        the identity of the SMTP server and client can be checked. This,
740        however, depends on whether the socket module really checks the
741        certificates.
742
743        This method may raise the following exceptions:
744
745         SMTPHeloError            The server didn't reply properly to
746                                  the helo greeting.
747        """
748        self.ehlo_or_helo_if_needed()
749        if not self.has_extn("starttls"):
750            raise SMTPNotSupportedError(
751                "STARTTLS extension not supported by server.")
752        (resp, reply) = self.docmd("STARTTLS")
753        if resp == 220:
754            if not _have_ssl:
755                raise RuntimeError("No SSL support included in this Python")
756            if context is not None and keyfile is not None:
757                raise ValueError("context and keyfile arguments are mutually "
758                                 "exclusive")
759            if context is not None and certfile is not None:
760                raise ValueError("context and certfile arguments are mutually "
761                                 "exclusive")
762            if keyfile is not None or certfile is not None:
763                import warnings
764                warnings.warn("keyfile and certfile are deprecated, use a"
765                              "custom context instead", DeprecationWarning, 2)
766            if context is None:
767                context = ssl._create_stdlib_context(certfile=certfile,
768                                                     keyfile=keyfile)
769            self.sock = context.wrap_socket(self.sock,
770                                            server_hostname=self._host)
771            self.file = None
772            # RFC 3207:
773            # The client MUST discard any knowledge obtained from
774            # the server, such as the list of SMTP service extensions,
775            # which was not obtained from the TLS negotiation itself.
776            self.helo_resp = None
777            self.ehlo_resp = None
778            self.esmtp_features = {}
779            self.does_esmtp = 0
780        else:
781            # RFC 3207:
782            # 501 Syntax error (no parameters allowed)
783            # 454 TLS not available due to temporary reason
784            raise SMTPResponseException(resp, reply)
785        return (resp, reply)
786
787    def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
788                 rcpt_options=[]):
789        """This command performs an entire mail transaction.
790
791        The arguments are:
792            - from_addr    : The address sending this mail.
793            - to_addrs     : A list of addresses to send this mail to.  A bare
794                             string will be treated as a list with 1 address.
795            - msg          : The message to send.
796            - mail_options : List of ESMTP options (such as 8bitmime) for the
797                             mail command.
798            - rcpt_options : List of ESMTP options (such as DSN commands) for
799                             all the rcpt commands.
800
801        msg may be a string containing characters in the ASCII range, or a byte
802        string.  A string is encoded to bytes using the ascii codec, and lone
803        \\r and \\n characters are converted to \\r\\n characters.
804
805        If there has been no previous EHLO or HELO command this session, this
806        method tries ESMTP EHLO first.  If the server does ESMTP, message size
807        and each of the specified options will be passed to it.  If EHLO
808        fails, HELO will be tried and ESMTP options suppressed.
809
810        This method will return normally if the mail is accepted for at least
811        one recipient.  It returns a dictionary, with one entry for each
812        recipient that was refused.  Each entry contains a tuple of the SMTP
813        error code and the accompanying error message sent by the server.
814
815        This method may raise the following exceptions:
816
817         SMTPHeloError          The server didn't reply properly to
818                                the helo greeting.
819         SMTPRecipientsRefused  The server rejected ALL recipients
820                                (no mail was sent).
821         SMTPSenderRefused      The server didn't accept the from_addr.
822         SMTPDataError          The server replied with an unexpected
823                                error code (other than a refusal of
824                                a recipient).
825         SMTPNotSupportedError  The mail_options parameter includes 'SMTPUTF8'
826                                but the SMTPUTF8 extension is not supported by
827                                the server.
828
829        Note: the connection will be open even after an exception is raised.
830
831        Example:
832
833         >>> import smtplib
834         >>> s=smtplib.SMTP("localhost")
835         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
836         >>> msg = '''\\
837         ... From: Me@my.org
838         ... Subject: testin'...
839         ...
840         ... This is a test '''
841         >>> s.sendmail("me@my.org",tolist,msg)
842         { "three@three.org" : ( 550 ,"User unknown" ) }
843         >>> s.quit()
844
845        In the above example, the message was accepted for delivery to three
846        of the four addresses, and one was rejected, with the error code
847        550.  If all addresses are accepted, then the method will return an
848        empty dictionary.
849
850        """
851        self.ehlo_or_helo_if_needed()
852        esmtp_opts = []
853        if isinstance(msg, str):
854            msg = _fix_eols(msg).encode('ascii')
855        if self.does_esmtp:
856            if self.has_extn('size'):
857                esmtp_opts.append("size=%d" % len(msg))
858            for option in mail_options:
859                esmtp_opts.append(option)
860        (code, resp) = self.mail(from_addr, esmtp_opts)
861        if code != 250:
862            if code == 421:
863                self.close()
864            else:
865                self._rset()
866            raise SMTPSenderRefused(code, resp, from_addr)
867        senderrs = {}
868        if isinstance(to_addrs, str):
869            to_addrs = [to_addrs]
870        for each in to_addrs:
871            (code, resp) = self.rcpt(each, rcpt_options)
872            if (code != 250) and (code != 251):
873                senderrs[each] = (code, resp)
874            if code == 421:
875                self.close()
876                raise SMTPRecipientsRefused(senderrs)
877        if len(senderrs) == len(to_addrs):
878            # the server refused all our recipients
879            self._rset()
880            raise SMTPRecipientsRefused(senderrs)
881        (code, resp) = self.data(msg)
882        if code != 250:
883            if code == 421:
884                self.close()
885            else:
886                self._rset()
887            raise SMTPDataError(code, resp)
888        #if we got here then somebody got our mail
889        return senderrs
890
891    def send_message(self, msg, from_addr=None, to_addrs=None,
892                mail_options=[], rcpt_options={}):
893        """Converts message to a bytestring and passes it to sendmail.
894
895        The arguments are as for sendmail, except that msg is an
896        email.message.Message object.  If from_addr is None or to_addrs is
897        None, these arguments are taken from the headers of the Message as
898        described in RFC 2822 (a ValueError is raised if there is more than
899        one set of 'Resent-' headers).  Regardless of the values of from_addr and
900        to_addr, any Bcc field (or Resent-Bcc field, when the Message is a
901        resent) of the Message object won't be transmitted.  The Message
902        object is then serialized using email.generator.BytesGenerator and
903        sendmail is called to transmit the message.  If the sender or any of
904        the recipient addresses contain non-ASCII and the server advertises the
905        SMTPUTF8 capability, the policy is cloned with utf8 set to True for the
906        serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.
907        If the server does not support SMTPUTF8, an SMTPNotSupported error is
908        raised.  Otherwise the generator is called without modifying the
909        policy.
910
911        """
912        # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
913        # Section 3.6.6). In such a case, we use the 'Resent-*' fields.  However,
914        # if there is more than one 'Resent-' block there's no way to
915        # unambiguously determine which one is the most recent in all cases,
916        # so rather than guess we raise a ValueError in that case.
917        #
918        # TODO implement heuristics to guess the correct Resent-* block with an
919        # option allowing the user to enable the heuristics.  (It should be
920        # possible to guess correctly almost all of the time.)
921
922        self.ehlo_or_helo_if_needed()
923        resent = msg.get_all('Resent-Date')
924        if resent is None:
925            header_prefix = ''
926        elif len(resent) == 1:
927            header_prefix = 'Resent-'
928        else:
929            raise ValueError("message has more than one 'Resent-' header block")
930        if from_addr is None:
931            # Prefer the sender field per RFC 2822:3.6.2.
932            from_addr = (msg[header_prefix + 'Sender']
933                           if (header_prefix + 'Sender') in msg
934                           else msg[header_prefix + 'From'])
935        if to_addrs is None:
936            addr_fields = [f for f in (msg[header_prefix + 'To'],
937                                       msg[header_prefix + 'Bcc'],
938                                       msg[header_prefix + 'Cc'])
939                           if f is not None]
940            to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
941        # Make a local copy so we can delete the bcc headers.
942        msg_copy = copy.copy(msg)
943        del msg_copy['Bcc']
944        del msg_copy['Resent-Bcc']
945        international = False
946        try:
947            ''.join([from_addr, *to_addrs]).encode('ascii')
948        except UnicodeEncodeError:
949            if not self.has_extn('smtputf8'):
950                raise SMTPNotSupportedError(
951                    "One or more source or delivery addresses require"
952                    " internationalized email support, but the server"
953                    " does not advertise the required SMTPUTF8 capability")
954            international = True
955        with io.BytesIO() as bytesmsg:
956            if international:
957                g = email.generator.BytesGenerator(
958                    bytesmsg, policy=msg.policy.clone(utf8=True))
959                mail_options += ['SMTPUTF8', 'BODY=8BITMIME']
960            else:
961                g = email.generator.BytesGenerator(bytesmsg)
962            g.flatten(msg_copy, linesep='\r\n')
963            flatmsg = bytesmsg.getvalue()
964        return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
965                             rcpt_options)
966
967    def close(self):
968        """Close the connection to the SMTP server."""
969        try:
970            file = self.file
971            self.file = None
972            if file:
973                file.close()
974        finally:
975            sock = self.sock
976            self.sock = None
977            if sock:
978                sock.close()
979
980    def quit(self):
981        """Terminate the SMTP session."""
982        res = self.docmd("quit")
983        # A new EHLO is required after reconnecting with connect()
984        self.ehlo_resp = self.helo_resp = None
985        self.esmtp_features = {}
986        self.does_esmtp = False
987        self.close()
988        return res
989
990if _have_ssl:
991
992    class SMTP_SSL(SMTP):
993        """ This is a subclass derived from SMTP that connects over an SSL
994        encrypted socket (to use this class you need a socket module that was
995        compiled with SSL support). If host is not specified, '' (the local
996        host) is used. If port is omitted, the standard SMTP-over-SSL port
997        (465) is used.  local_hostname and source_address have the same meaning
998        as they do in the SMTP class.  keyfile and certfile are also optional -
999        they can contain a PEM formatted private key and certificate chain file
1000        for the SSL connection. context also optional, can contain a
1001        SSLContext, and is an alternative to keyfile and certfile; If it is
1002        specified both keyfile and certfile must be None.
1003
1004        """
1005
1006        default_port = SMTP_SSL_PORT
1007
1008        def __init__(self, host='', port=0, local_hostname=None,
1009                     keyfile=None, certfile=None,
1010                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
1011                     source_address=None, context=None):
1012            if context is not None and keyfile is not None:
1013                raise ValueError("context and keyfile arguments are mutually "
1014                                 "exclusive")
1015            if context is not None and certfile is not None:
1016                raise ValueError("context and certfile arguments are mutually "
1017                                 "exclusive")
1018            if keyfile is not None or certfile is not None:
1019                import warnings
1020                warnings.warn("keyfile and certfile are deprecated, use a"
1021                              "custom context instead", DeprecationWarning, 2)
1022            self.keyfile = keyfile
1023            self.certfile = certfile
1024            if context is None:
1025                context = ssl._create_stdlib_context(certfile=certfile,
1026                                                     keyfile=keyfile)
1027            self.context = context
1028            SMTP.__init__(self, host, port, local_hostname, timeout,
1029                    source_address)
1030
1031        def _get_socket(self, host, port, timeout):
1032            if self.debuglevel > 0:
1033                self._print_debug('connect:', (host, port))
1034            new_socket = socket.create_connection((host, port), timeout,
1035                    self.source_address)
1036            new_socket = self.context.wrap_socket(new_socket,
1037                                                  server_hostname=self._host)
1038            return new_socket
1039
1040    __all__.append("SMTP_SSL")
1041
1042#
1043# LMTP extension
1044#
1045LMTP_PORT = 2003
1046
1047class LMTP(SMTP):
1048    """LMTP - Local Mail Transfer Protocol
1049
1050    The LMTP protocol, which is very similar to ESMTP, is heavily based
1051    on the standard SMTP client. It's common to use Unix sockets for
1052    LMTP, so our connect() method must support that as well as a regular
1053    host:port server.  local_hostname and source_address have the same
1054    meaning as they do in the SMTP class.  To specify a Unix socket,
1055    you must use an absolute path as the host, starting with a '/'.
1056
1057    Authentication is supported, using the regular SMTP mechanism. When
1058    using a Unix socket, LMTP generally don't support or require any
1059    authentication, but your mileage might vary."""
1060
1061    ehlo_msg = "lhlo"
1062
1063    def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1064            source_address=None):
1065        """Initialize a new instance."""
1066        SMTP.__init__(self, host, port, local_hostname=local_hostname,
1067                      source_address=source_address)
1068
1069    def connect(self, host='localhost', port=0, source_address=None):
1070        """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
1071        if host[0] != '/':
1072            return SMTP.connect(self, host, port, source_address=source_address)
1073
1074        # Handle Unix-domain sockets.
1075        try:
1076            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1077            self.file = None
1078            self.sock.connect(host)
1079        except OSError:
1080            if self.debuglevel > 0:
1081                self._print_debug('connect fail:', host)
1082            if self.sock:
1083                self.sock.close()
1084            self.sock = None
1085            raise
1086        (code, msg) = self.getreply()
1087        if self.debuglevel > 0:
1088            self._print_debug('connect:', msg)
1089        return (code, msg)
1090
1091
1092# Test the sendmail method, which tests most of the others.
1093# Note: This always sends to localhost.
1094if __name__ == '__main__':
1095    def prompt(prompt):
1096        sys.stdout.write(prompt + ": ")
1097        sys.stdout.flush()
1098        return sys.stdin.readline().strip()
1099
1100    fromaddr = prompt("From")
1101    toaddrs = prompt("To").split(',')
1102    print("Enter message, end with ^D:")
1103    msg = ''
1104    while 1:
1105        line = sys.stdin.readline()
1106        if not line:
1107            break
1108        msg = msg + line
1109    print("Message length is %d" % len(msg))
1110
1111    server = SMTP('localhost')
1112    server.set_debuglevel(1)
1113    server.sendmail(fromaddr, toaddrs, msg)
1114    server.quit()
1115