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