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