• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env python3
2
3'''SMTP/ESMTP client class.
4
5This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6Authentication) and RFC 2487 (Secure SMTP over TLS).
7
8Notes:
9
10Please remember, when doing ESMTP, that the names of the SMTP service
11extensions are NOT the same thing as the option keywords for the RCPT
12and MAIL commands!
13
14Example:
15
16  >>> import smtplib
17  >>> s=smtplib.SMTP("localhost")
18  >>> print(s.help())
19  This is Sendmail version 8.8.4
20  Topics:
21      HELO    EHLO    MAIL    RCPT    DATA
22      RSET    NOOP    QUIT    HELP    VRFY
23      EXPN    VERB    ETRN    DSN
24  For more info use "HELP <topic>".
25  To report bugs in the implementation send email to
26      sendmail-bugs@sendmail.org.
27  For local information send email to Postmaster at your site.
28  End of HELP info
29  >>> s.putcmd("vrfy","someone@here")
30  >>> s.getreply()
31  (250, "Somebody OverHere <somebody@here.my.org>")
32  >>> s.quit()
33'''
34
35# Author: The Dragon De Monsyne <dragondm@integral.org>
36# ESMTP support, test code and doc fixes added by
37#     Eric S. Raymond <esr@thyrsus.com>
38# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
39#     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
40# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
41#
42# This was modified from the Python 1.5 library HTTP lib.
43
44import socket
45import io
46import re
47import email.utils
48import email.message
49import email.generator
50import base64
51import hmac
52import copy
53import datetime
54import sys
55from email.base64mime import body_encode as encode_base64
56
57__all__ = ["SMTPException", "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, *, 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 context parameter,
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 None:
778                context = ssl._create_stdlib_context()
779            self.sock = context.wrap_socket(self.sock,
780                                            server_hostname=self._host)
781            self.file = None
782            # RFC 3207:
783            # The client MUST discard any knowledge obtained from
784            # the server, such as the list of SMTP service extensions,
785            # which was not obtained from the TLS negotiation itself.
786            self.helo_resp = None
787            self.ehlo_resp = None
788            self.esmtp_features = {}
789            self.does_esmtp = False
790        else:
791            # RFC 3207:
792            # 501 Syntax error (no parameters allowed)
793            # 454 TLS not available due to temporary reason
794            raise SMTPResponseException(resp, reply)
795        return (resp, reply)
796
797    def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
798                 rcpt_options=()):
799        """This command performs an entire mail transaction.
800
801        The arguments are:
802            - from_addr    : The address sending this mail.
803            - to_addrs     : A list of addresses to send this mail to.  A bare
804                             string will be treated as a list with 1 address.
805            - msg          : The message to send.
806            - mail_options : List of ESMTP options (such as 8bitmime) for the
807                             mail command.
808            - rcpt_options : List of ESMTP options (such as DSN commands) for
809                             all the rcpt commands.
810
811        msg may be a string containing characters in the ASCII range, or a byte
812        string.  A string is encoded to bytes using the ascii codec, and lone
813        \\r and \\n characters are converted to \\r\\n characters.
814
815        If there has been no previous EHLO or HELO command this session, this
816        method tries ESMTP EHLO first.  If the server does ESMTP, message size
817        and each of the specified options will be passed to it.  If EHLO
818        fails, HELO will be tried and ESMTP options suppressed.
819
820        This method will return normally if the mail is accepted for at least
821        one recipient.  It returns a dictionary, with one entry for each
822        recipient that was refused.  Each entry contains a tuple of the SMTP
823        error code and the accompanying error message sent by the server.
824
825        This method may raise the following exceptions:
826
827         SMTPHeloError          The server didn't reply properly to
828                                the helo greeting.
829         SMTPRecipientsRefused  The server rejected ALL recipients
830                                (no mail was sent).
831         SMTPSenderRefused      The server didn't accept the from_addr.
832         SMTPDataError          The server replied with an unexpected
833                                error code (other than a refusal of
834                                a recipient).
835         SMTPNotSupportedError  The mail_options parameter includes 'SMTPUTF8'
836                                but the SMTPUTF8 extension is not supported by
837                                the server.
838
839        Note: the connection will be open even after an exception is raised.
840
841        Example:
842
843         >>> import smtplib
844         >>> s=smtplib.SMTP("localhost")
845         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
846         >>> msg = '''\\
847         ... From: Me@my.org
848         ... Subject: testin'...
849         ...
850         ... This is a test '''
851         >>> s.sendmail("me@my.org",tolist,msg)
852         { "three@three.org" : ( 550 ,"User unknown" ) }
853         >>> s.quit()
854
855        In the above example, the message was accepted for delivery to three
856        of the four addresses, and one was rejected, with the error code
857        550.  If all addresses are accepted, then the method will return an
858        empty dictionary.
859
860        """
861        self.ehlo_or_helo_if_needed()
862        esmtp_opts = []
863        if isinstance(msg, str):
864            msg = _fix_eols(msg).encode('ascii')
865        if self.does_esmtp:
866            if self.has_extn('size'):
867                esmtp_opts.append("size=%d" % len(msg))
868            for option in mail_options:
869                esmtp_opts.append(option)
870        (code, resp) = self.mail(from_addr, esmtp_opts)
871        if code != 250:
872            if code == 421:
873                self.close()
874            else:
875                self._rset()
876            raise SMTPSenderRefused(code, resp, from_addr)
877        senderrs = {}
878        if isinstance(to_addrs, str):
879            to_addrs = [to_addrs]
880        for each in to_addrs:
881            (code, resp) = self.rcpt(each, rcpt_options)
882            if (code != 250) and (code != 251):
883                senderrs[each] = (code, resp)
884            if code == 421:
885                self.close()
886                raise SMTPRecipientsRefused(senderrs)
887        if len(senderrs) == len(to_addrs):
888            # the server refused all our recipients
889            self._rset()
890            raise SMTPRecipientsRefused(senderrs)
891        (code, resp) = self.data(msg)
892        if code != 250:
893            if code == 421:
894                self.close()
895            else:
896                self._rset()
897            raise SMTPDataError(code, resp)
898        #if we got here then somebody got our mail
899        return senderrs
900
901    def send_message(self, msg, from_addr=None, to_addrs=None,
902                     mail_options=(), rcpt_options=()):
903        """Converts message to a bytestring and passes it to sendmail.
904
905        The arguments are as for sendmail, except that msg is an
906        email.message.Message object.  If from_addr is None or to_addrs is
907        None, these arguments are taken from the headers of the Message as
908        described in RFC 2822 (a ValueError is raised if there is more than
909        one set of 'Resent-' headers).  Regardless of the values of from_addr and
910        to_addr, any Bcc field (or Resent-Bcc field, when the Message is a
911        resent) of the Message object won't be transmitted.  The Message
912        object is then serialized using email.generator.BytesGenerator and
913        sendmail is called to transmit the message.  If the sender or any of
914        the recipient addresses contain non-ASCII and the server advertises the
915        SMTPUTF8 capability, the policy is cloned with utf8 set to True for the
916        serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.
917        If the server does not support SMTPUTF8, an SMTPNotSupported error is
918        raised.  Otherwise the generator is called without modifying the
919        policy.
920
921        """
922        # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
923        # Section 3.6.6). In such a case, we use the 'Resent-*' fields.  However,
924        # if there is more than one 'Resent-' block there's no way to
925        # unambiguously determine which one is the most recent in all cases,
926        # so rather than guess we raise a ValueError in that case.
927        #
928        # TODO implement heuristics to guess the correct Resent-* block with an
929        # option allowing the user to enable the heuristics.  (It should be
930        # possible to guess correctly almost all of the time.)
931
932        self.ehlo_or_helo_if_needed()
933        resent = msg.get_all('Resent-Date')
934        if resent is None:
935            header_prefix = ''
936        elif len(resent) == 1:
937            header_prefix = 'Resent-'
938        else:
939            raise ValueError("message has more than one 'Resent-' header block")
940        if from_addr is None:
941            # Prefer the sender field per RFC 2822:3.6.2.
942            from_addr = (msg[header_prefix + 'Sender']
943                           if (header_prefix + 'Sender') in msg
944                           else msg[header_prefix + 'From'])
945            from_addr = email.utils.getaddresses([from_addr])[0][1]
946        if to_addrs is None:
947            addr_fields = [f for f in (msg[header_prefix + 'To'],
948                                       msg[header_prefix + 'Bcc'],
949                                       msg[header_prefix + 'Cc'])
950                           if f is not None]
951            to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
952        # Make a local copy so we can delete the bcc headers.
953        msg_copy = copy.copy(msg)
954        del msg_copy['Bcc']
955        del msg_copy['Resent-Bcc']
956        international = False
957        try:
958            ''.join([from_addr, *to_addrs]).encode('ascii')
959        except UnicodeEncodeError:
960            if not self.has_extn('smtputf8'):
961                raise SMTPNotSupportedError(
962                    "One or more source or delivery addresses require"
963                    " internationalized email support, but the server"
964                    " does not advertise the required SMTPUTF8 capability")
965            international = True
966        with io.BytesIO() as bytesmsg:
967            if international:
968                g = email.generator.BytesGenerator(
969                    bytesmsg, policy=msg.policy.clone(utf8=True))
970                mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME')
971            else:
972                g = email.generator.BytesGenerator(bytesmsg)
973            g.flatten(msg_copy, linesep='\r\n')
974            flatmsg = bytesmsg.getvalue()
975        return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
976                             rcpt_options)
977
978    def close(self):
979        """Close the connection to the SMTP server."""
980        try:
981            file = self.file
982            self.file = None
983            if file:
984                file.close()
985        finally:
986            sock = self.sock
987            self.sock = None
988            if sock:
989                sock.close()
990
991    def quit(self):
992        """Terminate the SMTP session."""
993        res = self.docmd("quit")
994        # A new EHLO is required after reconnecting with connect()
995        self.ehlo_resp = self.helo_resp = None
996        self.esmtp_features = {}
997        self.does_esmtp = False
998        self.close()
999        return res
1000
1001if _have_ssl:
1002
1003    class SMTP_SSL(SMTP):
1004        """ This is a subclass derived from SMTP that connects over an SSL
1005        encrypted socket (to use this class you need a socket module that was
1006        compiled with SSL support). If host is not specified, '' (the local
1007        host) is used. If port is omitted, the standard SMTP-over-SSL port
1008        (465) is used.  local_hostname and source_address have the same meaning
1009        as they do in the SMTP class.  context also optional, can contain a
1010        SSLContext.
1011
1012        """
1013
1014        default_port = SMTP_SSL_PORT
1015
1016        def __init__(self, host='', port=0, local_hostname=None,
1017                     *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
1018                     source_address=None, context=None):
1019            if context is None:
1020                context = ssl._create_stdlib_context()
1021            self.context = context
1022            SMTP.__init__(self, host, port, local_hostname, timeout,
1023                          source_address)
1024
1025        def _get_socket(self, host, port, timeout):
1026            if self.debuglevel > 0:
1027                self._print_debug('connect:', (host, port))
1028            new_socket = super()._get_socket(host, port, timeout)
1029            new_socket = self.context.wrap_socket(new_socket,
1030                                                  server_hostname=self._host)
1031            return new_socket
1032
1033    __all__.append("SMTP_SSL")
1034
1035#
1036# LMTP extension
1037#
1038LMTP_PORT = 2003
1039
1040class LMTP(SMTP):
1041    """LMTP - Local Mail Transfer Protocol
1042
1043    The LMTP protocol, which is very similar to ESMTP, is heavily based
1044    on the standard SMTP client. It's common to use Unix sockets for
1045    LMTP, so our connect() method must support that as well as a regular
1046    host:port server.  local_hostname and source_address have the same
1047    meaning as they do in the SMTP class.  To specify a Unix socket,
1048    you must use an absolute path as the host, starting with a '/'.
1049
1050    Authentication is supported, using the regular SMTP mechanism. When
1051    using a Unix socket, LMTP generally don't support or require any
1052    authentication, but your mileage might vary."""
1053
1054    ehlo_msg = "lhlo"
1055
1056    def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1057                 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
1058        """Initialize a new instance."""
1059        super().__init__(host, port, local_hostname=local_hostname,
1060                         source_address=source_address, timeout=timeout)
1061
1062    def connect(self, host='localhost', port=0, source_address=None):
1063        """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
1064        if host[0] != '/':
1065            return super().connect(host, port, source_address=source_address)
1066
1067        if self.timeout is not None and not self.timeout:
1068            raise ValueError('Non-blocking socket (timeout=0) is not supported')
1069
1070        # Handle Unix-domain sockets.
1071        try:
1072            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1073            if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
1074                self.sock.settimeout(self.timeout)
1075            self.file = None
1076            self.sock.connect(host)
1077        except OSError:
1078            if self.debuglevel > 0:
1079                self._print_debug('connect fail:', host)
1080            if self.sock:
1081                self.sock.close()
1082            self.sock = None
1083            raise
1084        (code, msg) = self.getreply()
1085        if self.debuglevel > 0:
1086            self._print_debug('connect:', msg)
1087        return (code, msg)
1088
1089
1090# Test the sendmail method, which tests most of the others.
1091# Note: This always sends to localhost.
1092if __name__ == '__main__':
1093    def prompt(prompt):
1094        sys.stdout.write(prompt + ": ")
1095        sys.stdout.flush()
1096        return sys.stdin.readline().strip()
1097
1098    fromaddr = prompt("From")
1099    toaddrs = prompt("To").split(',')
1100    print("Enter message, end with ^D:")
1101    msg = ''
1102    while line := sys.stdin.readline():
1103        msg = msg + line
1104    print("Message length is %d" % len(msg))
1105
1106    server = SMTP('localhost')
1107    server.set_debuglevel(1)
1108    server.sendmail(fromaddr, toaddrs, msg)
1109    server.quit()
1110