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