17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ci'''SMTP/ESMTP client class.
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ciThis should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
67db96d56Sopenharmony_ciAuthentication) and RFC 2487 (Secure SMTP over TLS).
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ciNotes:
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ciPlease remember, when doing ESMTP, that the names of the SMTP service
117db96d56Sopenharmony_ciextensions are NOT the same thing as the option keywords for the RCPT
127db96d56Sopenharmony_ciand MAIL commands!
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ciExample:
157db96d56Sopenharmony_ci
167db96d56Sopenharmony_ci  >>> import smtplib
177db96d56Sopenharmony_ci  >>> s=smtplib.SMTP("localhost")
187db96d56Sopenharmony_ci  >>> print(s.help())
197db96d56Sopenharmony_ci  This is Sendmail version 8.8.4
207db96d56Sopenharmony_ci  Topics:
217db96d56Sopenharmony_ci      HELO    EHLO    MAIL    RCPT    DATA
227db96d56Sopenharmony_ci      RSET    NOOP    QUIT    HELP    VRFY
237db96d56Sopenharmony_ci      EXPN    VERB    ETRN    DSN
247db96d56Sopenharmony_ci  For more info use "HELP <topic>".
257db96d56Sopenharmony_ci  To report bugs in the implementation send email to
267db96d56Sopenharmony_ci      sendmail-bugs@sendmail.org.
277db96d56Sopenharmony_ci  For local information send email to Postmaster at your site.
287db96d56Sopenharmony_ci  End of HELP info
297db96d56Sopenharmony_ci  >>> s.putcmd("vrfy","someone@here")
307db96d56Sopenharmony_ci  >>> s.getreply()
317db96d56Sopenharmony_ci  (250, "Somebody OverHere <somebody@here.my.org>")
327db96d56Sopenharmony_ci  >>> s.quit()
337db96d56Sopenharmony_ci'''
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci# Author: The Dragon De Monsyne <dragondm@integral.org>
367db96d56Sopenharmony_ci# ESMTP support, test code and doc fixes added by
377db96d56Sopenharmony_ci#     Eric S. Raymond <esr@thyrsus.com>
387db96d56Sopenharmony_ci# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
397db96d56Sopenharmony_ci#     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
407db96d56Sopenharmony_ci# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
417db96d56Sopenharmony_ci#
427db96d56Sopenharmony_ci# This was modified from the Python 1.5 library HTTP lib.
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_ciimport socket
457db96d56Sopenharmony_ciimport io
467db96d56Sopenharmony_ciimport re
477db96d56Sopenharmony_ciimport email.utils
487db96d56Sopenharmony_ciimport email.message
497db96d56Sopenharmony_ciimport email.generator
507db96d56Sopenharmony_ciimport base64
517db96d56Sopenharmony_ciimport hmac
527db96d56Sopenharmony_ciimport copy
537db96d56Sopenharmony_ciimport datetime
547db96d56Sopenharmony_ciimport sys
557db96d56Sopenharmony_cifrom email.base64mime import body_encode as encode_base64
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ci__all__ = ["SMTPException", "SMTPNotSupportedError", "SMTPServerDisconnected", "SMTPResponseException",
587db96d56Sopenharmony_ci           "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError",
597db96d56Sopenharmony_ci           "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError",
607db96d56Sopenharmony_ci           "quoteaddr", "quotedata", "SMTP"]
617db96d56Sopenharmony_ci
627db96d56Sopenharmony_ciSMTP_PORT = 25
637db96d56Sopenharmony_ciSMTP_SSL_PORT = 465
647db96d56Sopenharmony_ciCRLF = "\r\n"
657db96d56Sopenharmony_cibCRLF = b"\r\n"
667db96d56Sopenharmony_ci_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
677db96d56Sopenharmony_ci_MAXCHALLENGE = 5  # Maximum number of AUTH challenges sent
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ciOLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ci# Exception classes used by this module.
727db96d56Sopenharmony_ciclass SMTPException(OSError):
737db96d56Sopenharmony_ci    """Base class for all exceptions raised by this module."""
747db96d56Sopenharmony_ci
757db96d56Sopenharmony_ciclass SMTPNotSupportedError(SMTPException):
767db96d56Sopenharmony_ci    """The command or option is not supported by the SMTP server.
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci    This exception is raised when an attempt is made to run a command or a
797db96d56Sopenharmony_ci    command with an option which is not supported by the server.
807db96d56Sopenharmony_ci    """
817db96d56Sopenharmony_ci
827db96d56Sopenharmony_ciclass SMTPServerDisconnected(SMTPException):
837db96d56Sopenharmony_ci    """Not connected to any SMTP server.
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ci    This exception is raised when the server unexpectedly disconnects,
867db96d56Sopenharmony_ci    or when an attempt is made to use the SMTP instance before
877db96d56Sopenharmony_ci    connecting it to a server.
887db96d56Sopenharmony_ci    """
897db96d56Sopenharmony_ci
907db96d56Sopenharmony_ciclass SMTPResponseException(SMTPException):
917db96d56Sopenharmony_ci    """Base class for all exceptions that include an SMTP error code.
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_ci    These exceptions are generated in some instances when the SMTP
947db96d56Sopenharmony_ci    server returns an error code.  The error code is stored in the
957db96d56Sopenharmony_ci    `smtp_code' attribute of the error, and the `smtp_error' attribute
967db96d56Sopenharmony_ci    is set to the error message.
977db96d56Sopenharmony_ci    """
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci    def __init__(self, code, msg):
1007db96d56Sopenharmony_ci        self.smtp_code = code
1017db96d56Sopenharmony_ci        self.smtp_error = msg
1027db96d56Sopenharmony_ci        self.args = (code, msg)
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ciclass SMTPSenderRefused(SMTPResponseException):
1057db96d56Sopenharmony_ci    """Sender address refused.
1067db96d56Sopenharmony_ci
1077db96d56Sopenharmony_ci    In addition to the attributes set by on all SMTPResponseException
1087db96d56Sopenharmony_ci    exceptions, this sets `sender' to the string that the SMTP refused.
1097db96d56Sopenharmony_ci    """
1107db96d56Sopenharmony_ci
1117db96d56Sopenharmony_ci    def __init__(self, code, msg, sender):
1127db96d56Sopenharmony_ci        self.smtp_code = code
1137db96d56Sopenharmony_ci        self.smtp_error = msg
1147db96d56Sopenharmony_ci        self.sender = sender
1157db96d56Sopenharmony_ci        self.args = (code, msg, sender)
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ciclass SMTPRecipientsRefused(SMTPException):
1187db96d56Sopenharmony_ci    """All recipient addresses refused.
1197db96d56Sopenharmony_ci
1207db96d56Sopenharmony_ci    The errors for each recipient are accessible through the attribute
1217db96d56Sopenharmony_ci    'recipients', which is a dictionary of exactly the same sort as
1227db96d56Sopenharmony_ci    SMTP.sendmail() returns.
1237db96d56Sopenharmony_ci    """
1247db96d56Sopenharmony_ci
1257db96d56Sopenharmony_ci    def __init__(self, recipients):
1267db96d56Sopenharmony_ci        self.recipients = recipients
1277db96d56Sopenharmony_ci        self.args = (recipients,)
1287db96d56Sopenharmony_ci
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ciclass SMTPDataError(SMTPResponseException):
1317db96d56Sopenharmony_ci    """The SMTP server didn't accept the data."""
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ciclass SMTPConnectError(SMTPResponseException):
1347db96d56Sopenharmony_ci    """Error during connection establishment."""
1357db96d56Sopenharmony_ci
1367db96d56Sopenharmony_ciclass SMTPHeloError(SMTPResponseException):
1377db96d56Sopenharmony_ci    """The server refused our HELO reply."""
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_ciclass SMTPAuthenticationError(SMTPResponseException):
1407db96d56Sopenharmony_ci    """Authentication error.
1417db96d56Sopenharmony_ci
1427db96d56Sopenharmony_ci    Most probably the server didn't accept the username/password
1437db96d56Sopenharmony_ci    combination provided.
1447db96d56Sopenharmony_ci    """
1457db96d56Sopenharmony_ci
1467db96d56Sopenharmony_cidef quoteaddr(addrstring):
1477db96d56Sopenharmony_ci    """Quote a subset of the email addresses defined by RFC 821.
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci    Should be able to handle anything email.utils.parseaddr can handle.
1507db96d56Sopenharmony_ci    """
1517db96d56Sopenharmony_ci    displayname, addr = email.utils.parseaddr(addrstring)
1527db96d56Sopenharmony_ci    if (displayname, addr) == ('', ''):
1537db96d56Sopenharmony_ci        # parseaddr couldn't parse it, use it as is and hope for the best.
1547db96d56Sopenharmony_ci        if addrstring.strip().startswith('<'):
1557db96d56Sopenharmony_ci            return addrstring
1567db96d56Sopenharmony_ci        return "<%s>" % addrstring
1577db96d56Sopenharmony_ci    return "<%s>" % addr
1587db96d56Sopenharmony_ci
1597db96d56Sopenharmony_cidef _addr_only(addrstring):
1607db96d56Sopenharmony_ci    displayname, addr = email.utils.parseaddr(addrstring)
1617db96d56Sopenharmony_ci    if (displayname, addr) == ('', ''):
1627db96d56Sopenharmony_ci        # parseaddr couldn't parse it, so use it as is.
1637db96d56Sopenharmony_ci        return addrstring
1647db96d56Sopenharmony_ci    return addr
1657db96d56Sopenharmony_ci
1667db96d56Sopenharmony_ci# Legacy method kept for backward compatibility.
1677db96d56Sopenharmony_cidef quotedata(data):
1687db96d56Sopenharmony_ci    """Quote data for email.
1697db96d56Sopenharmony_ci
1707db96d56Sopenharmony_ci    Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
1717db96d56Sopenharmony_ci    internet CRLF end-of-line.
1727db96d56Sopenharmony_ci    """
1737db96d56Sopenharmony_ci    return re.sub(r'(?m)^\.', '..',
1747db96d56Sopenharmony_ci        re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
1757db96d56Sopenharmony_ci
1767db96d56Sopenharmony_cidef _quote_periods(bindata):
1777db96d56Sopenharmony_ci    return re.sub(br'(?m)^\.', b'..', bindata)
1787db96d56Sopenharmony_ci
1797db96d56Sopenharmony_cidef _fix_eols(data):
1807db96d56Sopenharmony_ci    return  re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)
1817db96d56Sopenharmony_ci
1827db96d56Sopenharmony_citry:
1837db96d56Sopenharmony_ci    import ssl
1847db96d56Sopenharmony_ciexcept ImportError:
1857db96d56Sopenharmony_ci    _have_ssl = False
1867db96d56Sopenharmony_cielse:
1877db96d56Sopenharmony_ci    _have_ssl = True
1887db96d56Sopenharmony_ci
1897db96d56Sopenharmony_ci
1907db96d56Sopenharmony_ciclass SMTP:
1917db96d56Sopenharmony_ci    """This class manages a connection to an SMTP or ESMTP server.
1927db96d56Sopenharmony_ci    SMTP Objects:
1937db96d56Sopenharmony_ci        SMTP objects have the following attributes:
1947db96d56Sopenharmony_ci            helo_resp
1957db96d56Sopenharmony_ci                This is the message given by the server in response to the
1967db96d56Sopenharmony_ci                most recent HELO command.
1977db96d56Sopenharmony_ci
1987db96d56Sopenharmony_ci            ehlo_resp
1997db96d56Sopenharmony_ci                This is the message given by the server in response to the
2007db96d56Sopenharmony_ci                most recent EHLO command. This is usually multiline.
2017db96d56Sopenharmony_ci
2027db96d56Sopenharmony_ci            does_esmtp
2037db96d56Sopenharmony_ci                This is a True value _after you do an EHLO command_, if the
2047db96d56Sopenharmony_ci                server supports ESMTP.
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_ci            esmtp_features
2077db96d56Sopenharmony_ci                This is a dictionary, which, if the server supports ESMTP,
2087db96d56Sopenharmony_ci                will _after you do an EHLO command_, contain the names of the
2097db96d56Sopenharmony_ci                SMTP service extensions this server supports, and their
2107db96d56Sopenharmony_ci                parameters (if any).
2117db96d56Sopenharmony_ci
2127db96d56Sopenharmony_ci                Note, all extension names are mapped to lower case in the
2137db96d56Sopenharmony_ci                dictionary.
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ci        See each method's docstrings for details.  In general, there is a
2167db96d56Sopenharmony_ci        method of the same name to perform each SMTP command.  There is also a
2177db96d56Sopenharmony_ci        method called 'sendmail' that will do an entire mail transaction.
2187db96d56Sopenharmony_ci        """
2197db96d56Sopenharmony_ci    debuglevel = 0
2207db96d56Sopenharmony_ci
2217db96d56Sopenharmony_ci    sock = None
2227db96d56Sopenharmony_ci    file = None
2237db96d56Sopenharmony_ci    helo_resp = None
2247db96d56Sopenharmony_ci    ehlo_msg = "ehlo"
2257db96d56Sopenharmony_ci    ehlo_resp = None
2267db96d56Sopenharmony_ci    does_esmtp = False
2277db96d56Sopenharmony_ci    default_port = SMTP_PORT
2287db96d56Sopenharmony_ci
2297db96d56Sopenharmony_ci    def __init__(self, host='', port=0, local_hostname=None,
2307db96d56Sopenharmony_ci                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
2317db96d56Sopenharmony_ci                 source_address=None):
2327db96d56Sopenharmony_ci        """Initialize a new instance.
2337db96d56Sopenharmony_ci
2347db96d56Sopenharmony_ci        If specified, `host` is the name of the remote host to which to
2357db96d56Sopenharmony_ci        connect.  If specified, `port` specifies the port to which to connect.
2367db96d56Sopenharmony_ci        By default, smtplib.SMTP_PORT is used.  If a host is specified the
2377db96d56Sopenharmony_ci        connect method is called, and if it returns anything other than a
2387db96d56Sopenharmony_ci        success code an SMTPConnectError is raised.  If specified,
2397db96d56Sopenharmony_ci        `local_hostname` is used as the FQDN of the local host in the HELO/EHLO
2407db96d56Sopenharmony_ci        command.  Otherwise, the local hostname is found using
2417db96d56Sopenharmony_ci        socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host,
2427db96d56Sopenharmony_ci        port) for the socket to bind to as its source address before
2437db96d56Sopenharmony_ci        connecting. If the host is '' and port is 0, the OS default behavior
2447db96d56Sopenharmony_ci        will be used.
2457db96d56Sopenharmony_ci
2467db96d56Sopenharmony_ci        """
2477db96d56Sopenharmony_ci        self._host = host
2487db96d56Sopenharmony_ci        self.timeout = timeout
2497db96d56Sopenharmony_ci        self.esmtp_features = {}
2507db96d56Sopenharmony_ci        self.command_encoding = 'ascii'
2517db96d56Sopenharmony_ci        self.source_address = source_address
2527db96d56Sopenharmony_ci        self._auth_challenge_count = 0
2537db96d56Sopenharmony_ci
2547db96d56Sopenharmony_ci        if host:
2557db96d56Sopenharmony_ci            (code, msg) = self.connect(host, port)
2567db96d56Sopenharmony_ci            if code != 220:
2577db96d56Sopenharmony_ci                self.close()
2587db96d56Sopenharmony_ci                raise SMTPConnectError(code, msg)
2597db96d56Sopenharmony_ci        if local_hostname is not None:
2607db96d56Sopenharmony_ci            self.local_hostname = local_hostname
2617db96d56Sopenharmony_ci        else:
2627db96d56Sopenharmony_ci            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
2637db96d56Sopenharmony_ci            # if that can't be calculated, that we should use a domain literal
2647db96d56Sopenharmony_ci            # instead (essentially an encoded IP address like [A.B.C.D]).
2657db96d56Sopenharmony_ci            fqdn = socket.getfqdn()
2667db96d56Sopenharmony_ci            if '.' in fqdn:
2677db96d56Sopenharmony_ci                self.local_hostname = fqdn
2687db96d56Sopenharmony_ci            else:
2697db96d56Sopenharmony_ci                # We can't find an fqdn hostname, so use a domain literal
2707db96d56Sopenharmony_ci                addr = '127.0.0.1'
2717db96d56Sopenharmony_ci                try:
2727db96d56Sopenharmony_ci                    addr = socket.gethostbyname(socket.gethostname())
2737db96d56Sopenharmony_ci                except socket.gaierror:
2747db96d56Sopenharmony_ci                    pass
2757db96d56Sopenharmony_ci                self.local_hostname = '[%s]' % addr
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci    def __enter__(self):
2787db96d56Sopenharmony_ci        return self
2797db96d56Sopenharmony_ci
2807db96d56Sopenharmony_ci    def __exit__(self, *args):
2817db96d56Sopenharmony_ci        try:
2827db96d56Sopenharmony_ci            code, message = self.docmd("QUIT")
2837db96d56Sopenharmony_ci            if code != 221:
2847db96d56Sopenharmony_ci                raise SMTPResponseException(code, message)
2857db96d56Sopenharmony_ci        except SMTPServerDisconnected:
2867db96d56Sopenharmony_ci            pass
2877db96d56Sopenharmony_ci        finally:
2887db96d56Sopenharmony_ci            self.close()
2897db96d56Sopenharmony_ci
2907db96d56Sopenharmony_ci    def set_debuglevel(self, debuglevel):
2917db96d56Sopenharmony_ci        """Set the debug output level.
2927db96d56Sopenharmony_ci
2937db96d56Sopenharmony_ci        A non-false value results in debug messages for connection and for all
2947db96d56Sopenharmony_ci        messages sent to and received from the server.
2957db96d56Sopenharmony_ci
2967db96d56Sopenharmony_ci        """
2977db96d56Sopenharmony_ci        self.debuglevel = debuglevel
2987db96d56Sopenharmony_ci
2997db96d56Sopenharmony_ci    def _print_debug(self, *args):
3007db96d56Sopenharmony_ci        if self.debuglevel > 1:
3017db96d56Sopenharmony_ci            print(datetime.datetime.now().time(), *args, file=sys.stderr)
3027db96d56Sopenharmony_ci        else:
3037db96d56Sopenharmony_ci            print(*args, file=sys.stderr)
3047db96d56Sopenharmony_ci
3057db96d56Sopenharmony_ci    def _get_socket(self, host, port, timeout):
3067db96d56Sopenharmony_ci        # This makes it simpler for SMTP_SSL to use the SMTP connect code
3077db96d56Sopenharmony_ci        # and just alter the socket connection bit.
3087db96d56Sopenharmony_ci        if timeout is not None and not timeout:
3097db96d56Sopenharmony_ci            raise ValueError('Non-blocking socket (timeout=0) is not supported')
3107db96d56Sopenharmony_ci        if self.debuglevel > 0:
3117db96d56Sopenharmony_ci            self._print_debug('connect: to', (host, port), self.source_address)
3127db96d56Sopenharmony_ci        return socket.create_connection((host, port), timeout,
3137db96d56Sopenharmony_ci                                        self.source_address)
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ci    def connect(self, host='localhost', port=0, source_address=None):
3167db96d56Sopenharmony_ci        """Connect to a host on a given port.
3177db96d56Sopenharmony_ci
3187db96d56Sopenharmony_ci        If the hostname ends with a colon (`:') followed by a number, and
3197db96d56Sopenharmony_ci        there is no port specified, that suffix will be stripped off and the
3207db96d56Sopenharmony_ci        number interpreted as the port number to use.
3217db96d56Sopenharmony_ci
3227db96d56Sopenharmony_ci        Note: This method is automatically invoked by __init__, if a host is
3237db96d56Sopenharmony_ci        specified during instantiation.
3247db96d56Sopenharmony_ci
3257db96d56Sopenharmony_ci        """
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ci        if source_address:
3287db96d56Sopenharmony_ci            self.source_address = source_address
3297db96d56Sopenharmony_ci
3307db96d56Sopenharmony_ci        if not port and (host.find(':') == host.rfind(':')):
3317db96d56Sopenharmony_ci            i = host.rfind(':')
3327db96d56Sopenharmony_ci            if i >= 0:
3337db96d56Sopenharmony_ci                host, port = host[:i], host[i + 1:]
3347db96d56Sopenharmony_ci                try:
3357db96d56Sopenharmony_ci                    port = int(port)
3367db96d56Sopenharmony_ci                except ValueError:
3377db96d56Sopenharmony_ci                    raise OSError("nonnumeric port")
3387db96d56Sopenharmony_ci        if not port:
3397db96d56Sopenharmony_ci            port = self.default_port
3407db96d56Sopenharmony_ci        sys.audit("smtplib.connect", self, host, port)
3417db96d56Sopenharmony_ci        self.sock = self._get_socket(host, port, self.timeout)
3427db96d56Sopenharmony_ci        self.file = None
3437db96d56Sopenharmony_ci        (code, msg) = self.getreply()
3447db96d56Sopenharmony_ci        if self.debuglevel > 0:
3457db96d56Sopenharmony_ci            self._print_debug('connect:', repr(msg))
3467db96d56Sopenharmony_ci        return (code, msg)
3477db96d56Sopenharmony_ci
3487db96d56Sopenharmony_ci    def send(self, s):
3497db96d56Sopenharmony_ci        """Send `s' to the server."""
3507db96d56Sopenharmony_ci        if self.debuglevel > 0:
3517db96d56Sopenharmony_ci            self._print_debug('send:', repr(s))
3527db96d56Sopenharmony_ci        if self.sock:
3537db96d56Sopenharmony_ci            if isinstance(s, str):
3547db96d56Sopenharmony_ci                # send is used by the 'data' command, where command_encoding
3557db96d56Sopenharmony_ci                # should not be used, but 'data' needs to convert the string to
3567db96d56Sopenharmony_ci                # binary itself anyway, so that's not a problem.
3577db96d56Sopenharmony_ci                s = s.encode(self.command_encoding)
3587db96d56Sopenharmony_ci            sys.audit("smtplib.send", self, s)
3597db96d56Sopenharmony_ci            try:
3607db96d56Sopenharmony_ci                self.sock.sendall(s)
3617db96d56Sopenharmony_ci            except OSError:
3627db96d56Sopenharmony_ci                self.close()
3637db96d56Sopenharmony_ci                raise SMTPServerDisconnected('Server not connected')
3647db96d56Sopenharmony_ci        else:
3657db96d56Sopenharmony_ci            raise SMTPServerDisconnected('please run connect() first')
3667db96d56Sopenharmony_ci
3677db96d56Sopenharmony_ci    def putcmd(self, cmd, args=""):
3687db96d56Sopenharmony_ci        """Send a command to the server."""
3697db96d56Sopenharmony_ci        if args == "":
3707db96d56Sopenharmony_ci            s = cmd
3717db96d56Sopenharmony_ci        else:
3727db96d56Sopenharmony_ci            s = f'{cmd} {args}'
3737db96d56Sopenharmony_ci        if '\r' in s or '\n' in s:
3747db96d56Sopenharmony_ci            s = s.replace('\n', '\\n').replace('\r', '\\r')
3757db96d56Sopenharmony_ci            raise ValueError(
3767db96d56Sopenharmony_ci                f'command and arguments contain prohibited newline characters: {s}'
3777db96d56Sopenharmony_ci            )
3787db96d56Sopenharmony_ci        self.send(f'{s}{CRLF}')
3797db96d56Sopenharmony_ci
3807db96d56Sopenharmony_ci    def getreply(self):
3817db96d56Sopenharmony_ci        """Get a reply from the server.
3827db96d56Sopenharmony_ci
3837db96d56Sopenharmony_ci        Returns a tuple consisting of:
3847db96d56Sopenharmony_ci
3857db96d56Sopenharmony_ci          - server response code (e.g. '250', or such, if all goes well)
3867db96d56Sopenharmony_ci            Note: returns -1 if it can't read response code.
3877db96d56Sopenharmony_ci
3887db96d56Sopenharmony_ci          - server response string corresponding to response code (multiline
3897db96d56Sopenharmony_ci            responses are converted to a single, multiline string).
3907db96d56Sopenharmony_ci
3917db96d56Sopenharmony_ci        Raises SMTPServerDisconnected if end-of-file is reached.
3927db96d56Sopenharmony_ci        """
3937db96d56Sopenharmony_ci        resp = []
3947db96d56Sopenharmony_ci        if self.file is None:
3957db96d56Sopenharmony_ci            self.file = self.sock.makefile('rb')
3967db96d56Sopenharmony_ci        while 1:
3977db96d56Sopenharmony_ci            try:
3987db96d56Sopenharmony_ci                line = self.file.readline(_MAXLINE + 1)
3997db96d56Sopenharmony_ci            except OSError as e:
4007db96d56Sopenharmony_ci                self.close()
4017db96d56Sopenharmony_ci                raise SMTPServerDisconnected("Connection unexpectedly closed: "
4027db96d56Sopenharmony_ci                                             + str(e))
4037db96d56Sopenharmony_ci            if not line:
4047db96d56Sopenharmony_ci                self.close()
4057db96d56Sopenharmony_ci                raise SMTPServerDisconnected("Connection unexpectedly closed")
4067db96d56Sopenharmony_ci            if self.debuglevel > 0:
4077db96d56Sopenharmony_ci                self._print_debug('reply:', repr(line))
4087db96d56Sopenharmony_ci            if len(line) > _MAXLINE:
4097db96d56Sopenharmony_ci                self.close()
4107db96d56Sopenharmony_ci                raise SMTPResponseException(500, "Line too long.")
4117db96d56Sopenharmony_ci            resp.append(line[4:].strip(b' \t\r\n'))
4127db96d56Sopenharmony_ci            code = line[:3]
4137db96d56Sopenharmony_ci            # Check that the error code is syntactically correct.
4147db96d56Sopenharmony_ci            # Don't attempt to read a continuation line if it is broken.
4157db96d56Sopenharmony_ci            try:
4167db96d56Sopenharmony_ci                errcode = int(code)
4177db96d56Sopenharmony_ci            except ValueError:
4187db96d56Sopenharmony_ci                errcode = -1
4197db96d56Sopenharmony_ci                break
4207db96d56Sopenharmony_ci            # Check if multiline response.
4217db96d56Sopenharmony_ci            if line[3:4] != b"-":
4227db96d56Sopenharmony_ci                break
4237db96d56Sopenharmony_ci
4247db96d56Sopenharmony_ci        errmsg = b"\n".join(resp)
4257db96d56Sopenharmony_ci        if self.debuglevel > 0:
4267db96d56Sopenharmony_ci            self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg))
4277db96d56Sopenharmony_ci        return errcode, errmsg
4287db96d56Sopenharmony_ci
4297db96d56Sopenharmony_ci    def docmd(self, cmd, args=""):
4307db96d56Sopenharmony_ci        """Send a command, and return its response code."""
4317db96d56Sopenharmony_ci        self.putcmd(cmd, args)
4327db96d56Sopenharmony_ci        return self.getreply()
4337db96d56Sopenharmony_ci
4347db96d56Sopenharmony_ci    # std smtp commands
4357db96d56Sopenharmony_ci    def helo(self, name=''):
4367db96d56Sopenharmony_ci        """SMTP 'helo' command.
4377db96d56Sopenharmony_ci        Hostname to send for this command defaults to the FQDN of the local
4387db96d56Sopenharmony_ci        host.
4397db96d56Sopenharmony_ci        """
4407db96d56Sopenharmony_ci        self.putcmd("helo", name or self.local_hostname)
4417db96d56Sopenharmony_ci        (code, msg) = self.getreply()
4427db96d56Sopenharmony_ci        self.helo_resp = msg
4437db96d56Sopenharmony_ci        return (code, msg)
4447db96d56Sopenharmony_ci
4457db96d56Sopenharmony_ci    def ehlo(self, name=''):
4467db96d56Sopenharmony_ci        """ SMTP 'ehlo' command.
4477db96d56Sopenharmony_ci        Hostname to send for this command defaults to the FQDN of the local
4487db96d56Sopenharmony_ci        host.
4497db96d56Sopenharmony_ci        """
4507db96d56Sopenharmony_ci        self.esmtp_features = {}
4517db96d56Sopenharmony_ci        self.putcmd(self.ehlo_msg, name or self.local_hostname)
4527db96d56Sopenharmony_ci        (code, msg) = self.getreply()
4537db96d56Sopenharmony_ci        # According to RFC1869 some (badly written)
4547db96d56Sopenharmony_ci        # MTA's will disconnect on an ehlo. Toss an exception if
4557db96d56Sopenharmony_ci        # that happens -ddm
4567db96d56Sopenharmony_ci        if code == -1 and len(msg) == 0:
4577db96d56Sopenharmony_ci            self.close()
4587db96d56Sopenharmony_ci            raise SMTPServerDisconnected("Server not connected")
4597db96d56Sopenharmony_ci        self.ehlo_resp = msg
4607db96d56Sopenharmony_ci        if code != 250:
4617db96d56Sopenharmony_ci            return (code, msg)
4627db96d56Sopenharmony_ci        self.does_esmtp = True
4637db96d56Sopenharmony_ci        #parse the ehlo response -ddm
4647db96d56Sopenharmony_ci        assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp)
4657db96d56Sopenharmony_ci        resp = self.ehlo_resp.decode("latin-1").split('\n')
4667db96d56Sopenharmony_ci        del resp[0]
4677db96d56Sopenharmony_ci        for each in resp:
4687db96d56Sopenharmony_ci            # To be able to communicate with as many SMTP servers as possible,
4697db96d56Sopenharmony_ci            # we have to take the old-style auth advertisement into account,
4707db96d56Sopenharmony_ci            # because:
4717db96d56Sopenharmony_ci            # 1) Else our SMTP feature parser gets confused.
4727db96d56Sopenharmony_ci            # 2) There are some servers that only advertise the auth methods we
4737db96d56Sopenharmony_ci            #    support using the old style.
4747db96d56Sopenharmony_ci            auth_match = OLDSTYLE_AUTH.match(each)
4757db96d56Sopenharmony_ci            if auth_match:
4767db96d56Sopenharmony_ci                # This doesn't remove duplicates, but that's no problem
4777db96d56Sopenharmony_ci                self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
4787db96d56Sopenharmony_ci                        + " " + auth_match.groups(0)[0]
4797db96d56Sopenharmony_ci                continue
4807db96d56Sopenharmony_ci
4817db96d56Sopenharmony_ci            # RFC 1869 requires a space between ehlo keyword and parameters.
4827db96d56Sopenharmony_ci            # It's actually stricter, in that only spaces are allowed between
4837db96d56Sopenharmony_ci            # parameters, but were not going to check for that here.  Note
4847db96d56Sopenharmony_ci            # that the space isn't present if there are no parameters.
4857db96d56Sopenharmony_ci            m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
4867db96d56Sopenharmony_ci            if m:
4877db96d56Sopenharmony_ci                feature = m.group("feature").lower()
4887db96d56Sopenharmony_ci                params = m.string[m.end("feature"):].strip()
4897db96d56Sopenharmony_ci                if feature == "auth":
4907db96d56Sopenharmony_ci                    self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
4917db96d56Sopenharmony_ci                            + " " + params
4927db96d56Sopenharmony_ci                else:
4937db96d56Sopenharmony_ci                    self.esmtp_features[feature] = params
4947db96d56Sopenharmony_ci        return (code, msg)
4957db96d56Sopenharmony_ci
4967db96d56Sopenharmony_ci    def has_extn(self, opt):
4977db96d56Sopenharmony_ci        """Does the server support a given SMTP service extension?"""
4987db96d56Sopenharmony_ci        return opt.lower() in self.esmtp_features
4997db96d56Sopenharmony_ci
5007db96d56Sopenharmony_ci    def help(self, args=''):
5017db96d56Sopenharmony_ci        """SMTP 'help' command.
5027db96d56Sopenharmony_ci        Returns help text from server."""
5037db96d56Sopenharmony_ci        self.putcmd("help", args)
5047db96d56Sopenharmony_ci        return self.getreply()[1]
5057db96d56Sopenharmony_ci
5067db96d56Sopenharmony_ci    def rset(self):
5077db96d56Sopenharmony_ci        """SMTP 'rset' command -- resets session."""
5087db96d56Sopenharmony_ci        self.command_encoding = 'ascii'
5097db96d56Sopenharmony_ci        return self.docmd("rset")
5107db96d56Sopenharmony_ci
5117db96d56Sopenharmony_ci    def _rset(self):
5127db96d56Sopenharmony_ci        """Internal 'rset' command which ignores any SMTPServerDisconnected error.
5137db96d56Sopenharmony_ci
5147db96d56Sopenharmony_ci        Used internally in the library, since the server disconnected error
5157db96d56Sopenharmony_ci        should appear to the application when the *next* command is issued, if
5167db96d56Sopenharmony_ci        we are doing an internal "safety" reset.
5177db96d56Sopenharmony_ci        """
5187db96d56Sopenharmony_ci        try:
5197db96d56Sopenharmony_ci            self.rset()
5207db96d56Sopenharmony_ci        except SMTPServerDisconnected:
5217db96d56Sopenharmony_ci            pass
5227db96d56Sopenharmony_ci
5237db96d56Sopenharmony_ci    def noop(self):
5247db96d56Sopenharmony_ci        """SMTP 'noop' command -- doesn't do anything :>"""
5257db96d56Sopenharmony_ci        return self.docmd("noop")
5267db96d56Sopenharmony_ci
5277db96d56Sopenharmony_ci    def mail(self, sender, options=()):
5287db96d56Sopenharmony_ci        """SMTP 'mail' command -- begins mail xfer session.
5297db96d56Sopenharmony_ci
5307db96d56Sopenharmony_ci        This method may raise the following exceptions:
5317db96d56Sopenharmony_ci
5327db96d56Sopenharmony_ci         SMTPNotSupportedError  The options parameter includes 'SMTPUTF8'
5337db96d56Sopenharmony_ci                                but the SMTPUTF8 extension is not supported by
5347db96d56Sopenharmony_ci                                the server.
5357db96d56Sopenharmony_ci        """
5367db96d56Sopenharmony_ci        optionlist = ''
5377db96d56Sopenharmony_ci        if options and self.does_esmtp:
5387db96d56Sopenharmony_ci            if any(x.lower()=='smtputf8' for x in options):
5397db96d56Sopenharmony_ci                if self.has_extn('smtputf8'):
5407db96d56Sopenharmony_ci                    self.command_encoding = 'utf-8'
5417db96d56Sopenharmony_ci                else:
5427db96d56Sopenharmony_ci                    raise SMTPNotSupportedError(
5437db96d56Sopenharmony_ci                        'SMTPUTF8 not supported by server')
5447db96d56Sopenharmony_ci            optionlist = ' ' + ' '.join(options)
5457db96d56Sopenharmony_ci        self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
5467db96d56Sopenharmony_ci        return self.getreply()
5477db96d56Sopenharmony_ci
5487db96d56Sopenharmony_ci    def rcpt(self, recip, options=()):
5497db96d56Sopenharmony_ci        """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
5507db96d56Sopenharmony_ci        optionlist = ''
5517db96d56Sopenharmony_ci        if options and self.does_esmtp:
5527db96d56Sopenharmony_ci            optionlist = ' ' + ' '.join(options)
5537db96d56Sopenharmony_ci        self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist))
5547db96d56Sopenharmony_ci        return self.getreply()
5557db96d56Sopenharmony_ci
5567db96d56Sopenharmony_ci    def data(self, msg):
5577db96d56Sopenharmony_ci        """SMTP 'DATA' command -- sends message data to server.
5587db96d56Sopenharmony_ci
5597db96d56Sopenharmony_ci        Automatically quotes lines beginning with a period per rfc821.
5607db96d56Sopenharmony_ci        Raises SMTPDataError if there is an unexpected reply to the
5617db96d56Sopenharmony_ci        DATA command; the return value from this method is the final
5627db96d56Sopenharmony_ci        response code received when the all data is sent.  If msg
5637db96d56Sopenharmony_ci        is a string, lone '\\r' and '\\n' characters are converted to
5647db96d56Sopenharmony_ci        '\\r\\n' characters.  If msg is bytes, it is transmitted as is.
5657db96d56Sopenharmony_ci        """
5667db96d56Sopenharmony_ci        self.putcmd("data")
5677db96d56Sopenharmony_ci        (code, repl) = self.getreply()
5687db96d56Sopenharmony_ci        if self.debuglevel > 0:
5697db96d56Sopenharmony_ci            self._print_debug('data:', (code, repl))
5707db96d56Sopenharmony_ci        if code != 354:
5717db96d56Sopenharmony_ci            raise SMTPDataError(code, repl)
5727db96d56Sopenharmony_ci        else:
5737db96d56Sopenharmony_ci            if isinstance(msg, str):
5747db96d56Sopenharmony_ci                msg = _fix_eols(msg).encode('ascii')
5757db96d56Sopenharmony_ci            q = _quote_periods(msg)
5767db96d56Sopenharmony_ci            if q[-2:] != bCRLF:
5777db96d56Sopenharmony_ci                q = q + bCRLF
5787db96d56Sopenharmony_ci            q = q + b"." + bCRLF
5797db96d56Sopenharmony_ci            self.send(q)
5807db96d56Sopenharmony_ci            (code, msg) = self.getreply()
5817db96d56Sopenharmony_ci            if self.debuglevel > 0:
5827db96d56Sopenharmony_ci                self._print_debug('data:', (code, msg))
5837db96d56Sopenharmony_ci            return (code, msg)
5847db96d56Sopenharmony_ci
5857db96d56Sopenharmony_ci    def verify(self, address):
5867db96d56Sopenharmony_ci        """SMTP 'verify' command -- checks for address validity."""
5877db96d56Sopenharmony_ci        self.putcmd("vrfy", _addr_only(address))
5887db96d56Sopenharmony_ci        return self.getreply()
5897db96d56Sopenharmony_ci    # a.k.a.
5907db96d56Sopenharmony_ci    vrfy = verify
5917db96d56Sopenharmony_ci
5927db96d56Sopenharmony_ci    def expn(self, address):
5937db96d56Sopenharmony_ci        """SMTP 'expn' command -- expands a mailing list."""
5947db96d56Sopenharmony_ci        self.putcmd("expn", _addr_only(address))
5957db96d56Sopenharmony_ci        return self.getreply()
5967db96d56Sopenharmony_ci
5977db96d56Sopenharmony_ci    # some useful methods
5987db96d56Sopenharmony_ci
5997db96d56Sopenharmony_ci    def ehlo_or_helo_if_needed(self):
6007db96d56Sopenharmony_ci        """Call self.ehlo() and/or self.helo() if needed.
6017db96d56Sopenharmony_ci
6027db96d56Sopenharmony_ci        If there has been no previous EHLO or HELO command this session, this
6037db96d56Sopenharmony_ci        method tries ESMTP EHLO first.
6047db96d56Sopenharmony_ci
6057db96d56Sopenharmony_ci        This method may raise the following exceptions:
6067db96d56Sopenharmony_ci
6077db96d56Sopenharmony_ci         SMTPHeloError            The server didn't reply properly to
6087db96d56Sopenharmony_ci                                  the helo greeting.
6097db96d56Sopenharmony_ci        """
6107db96d56Sopenharmony_ci        if self.helo_resp is None and self.ehlo_resp is None:
6117db96d56Sopenharmony_ci            if not (200 <= self.ehlo()[0] <= 299):
6127db96d56Sopenharmony_ci                (code, resp) = self.helo()
6137db96d56Sopenharmony_ci                if not (200 <= code <= 299):
6147db96d56Sopenharmony_ci                    raise SMTPHeloError(code, resp)
6157db96d56Sopenharmony_ci
6167db96d56Sopenharmony_ci    def auth(self, mechanism, authobject, *, initial_response_ok=True):
6177db96d56Sopenharmony_ci        """Authentication command - requires response processing.
6187db96d56Sopenharmony_ci
6197db96d56Sopenharmony_ci        'mechanism' specifies which authentication mechanism is to
6207db96d56Sopenharmony_ci        be used - the valid values are those listed in the 'auth'
6217db96d56Sopenharmony_ci        element of 'esmtp_features'.
6227db96d56Sopenharmony_ci
6237db96d56Sopenharmony_ci        'authobject' must be a callable object taking a single argument:
6247db96d56Sopenharmony_ci
6257db96d56Sopenharmony_ci                data = authobject(challenge)
6267db96d56Sopenharmony_ci
6277db96d56Sopenharmony_ci        It will be called to process the server's challenge response; the
6287db96d56Sopenharmony_ci        challenge argument it is passed will be a bytes.  It should return
6297db96d56Sopenharmony_ci        an ASCII string that will be base64 encoded and sent to the server.
6307db96d56Sopenharmony_ci
6317db96d56Sopenharmony_ci        Keyword arguments:
6327db96d56Sopenharmony_ci            - initial_response_ok: Allow sending the RFC 4954 initial-response
6337db96d56Sopenharmony_ci              to the AUTH command, if the authentication methods supports it.
6347db96d56Sopenharmony_ci        """
6357db96d56Sopenharmony_ci        # RFC 4954 allows auth methods to provide an initial response.  Not all
6367db96d56Sopenharmony_ci        # methods support it.  By definition, if they return something other
6377db96d56Sopenharmony_ci        # than None when challenge is None, then they do.  See issue #15014.
6387db96d56Sopenharmony_ci        mechanism = mechanism.upper()
6397db96d56Sopenharmony_ci        initial_response = (authobject() if initial_response_ok else None)
6407db96d56Sopenharmony_ci        if initial_response is not None:
6417db96d56Sopenharmony_ci            response = encode_base64(initial_response.encode('ascii'), eol='')
6427db96d56Sopenharmony_ci            (code, resp) = self.docmd("AUTH", mechanism + " " + response)
6437db96d56Sopenharmony_ci            self._auth_challenge_count = 1
6447db96d56Sopenharmony_ci        else:
6457db96d56Sopenharmony_ci            (code, resp) = self.docmd("AUTH", mechanism)
6467db96d56Sopenharmony_ci            self._auth_challenge_count = 0
6477db96d56Sopenharmony_ci        # If server responds with a challenge, send the response.
6487db96d56Sopenharmony_ci        while code == 334:
6497db96d56Sopenharmony_ci            self._auth_challenge_count += 1
6507db96d56Sopenharmony_ci            challenge = base64.decodebytes(resp)
6517db96d56Sopenharmony_ci            response = encode_base64(
6527db96d56Sopenharmony_ci                authobject(challenge).encode('ascii'), eol='')
6537db96d56Sopenharmony_ci            (code, resp) = self.docmd(response)
6547db96d56Sopenharmony_ci            # If server keeps sending challenges, something is wrong.
6557db96d56Sopenharmony_ci            if self._auth_challenge_count > _MAXCHALLENGE:
6567db96d56Sopenharmony_ci                raise SMTPException(
6577db96d56Sopenharmony_ci                    "Server AUTH mechanism infinite loop. Last response: "
6587db96d56Sopenharmony_ci                    + repr((code, resp))
6597db96d56Sopenharmony_ci                )
6607db96d56Sopenharmony_ci        if code in (235, 503):
6617db96d56Sopenharmony_ci            return (code, resp)
6627db96d56Sopenharmony_ci        raise SMTPAuthenticationError(code, resp)
6637db96d56Sopenharmony_ci
6647db96d56Sopenharmony_ci    def auth_cram_md5(self, challenge=None):
6657db96d56Sopenharmony_ci        """ Authobject to use with CRAM-MD5 authentication. Requires self.user
6667db96d56Sopenharmony_ci        and self.password to be set."""
6677db96d56Sopenharmony_ci        # CRAM-MD5 does not support initial-response.
6687db96d56Sopenharmony_ci        if challenge is None:
6697db96d56Sopenharmony_ci            return None
6707db96d56Sopenharmony_ci        return self.user + " " + hmac.HMAC(
6717db96d56Sopenharmony_ci            self.password.encode('ascii'), challenge, 'md5').hexdigest()
6727db96d56Sopenharmony_ci
6737db96d56Sopenharmony_ci    def auth_plain(self, challenge=None):
6747db96d56Sopenharmony_ci        """ Authobject to use with PLAIN authentication. Requires self.user and
6757db96d56Sopenharmony_ci        self.password to be set."""
6767db96d56Sopenharmony_ci        return "\0%s\0%s" % (self.user, self.password)
6777db96d56Sopenharmony_ci
6787db96d56Sopenharmony_ci    def auth_login(self, challenge=None):
6797db96d56Sopenharmony_ci        """ Authobject to use with LOGIN authentication. Requires self.user and
6807db96d56Sopenharmony_ci        self.password to be set."""
6817db96d56Sopenharmony_ci        if challenge is None or self._auth_challenge_count < 2:
6827db96d56Sopenharmony_ci            return self.user
6837db96d56Sopenharmony_ci        else:
6847db96d56Sopenharmony_ci            return self.password
6857db96d56Sopenharmony_ci
6867db96d56Sopenharmony_ci    def login(self, user, password, *, initial_response_ok=True):
6877db96d56Sopenharmony_ci        """Log in on an SMTP server that requires authentication.
6887db96d56Sopenharmony_ci
6897db96d56Sopenharmony_ci        The arguments are:
6907db96d56Sopenharmony_ci            - user:         The user name to authenticate with.
6917db96d56Sopenharmony_ci            - password:     The password for the authentication.
6927db96d56Sopenharmony_ci
6937db96d56Sopenharmony_ci        Keyword arguments:
6947db96d56Sopenharmony_ci            - initial_response_ok: Allow sending the RFC 4954 initial-response
6957db96d56Sopenharmony_ci              to the AUTH command, if the authentication methods supports it.
6967db96d56Sopenharmony_ci
6977db96d56Sopenharmony_ci        If there has been no previous EHLO or HELO command this session, this
6987db96d56Sopenharmony_ci        method tries ESMTP EHLO first.
6997db96d56Sopenharmony_ci
7007db96d56Sopenharmony_ci        This method will return normally if the authentication was successful.
7017db96d56Sopenharmony_ci
7027db96d56Sopenharmony_ci        This method may raise the following exceptions:
7037db96d56Sopenharmony_ci
7047db96d56Sopenharmony_ci         SMTPHeloError            The server didn't reply properly to
7057db96d56Sopenharmony_ci                                  the helo greeting.
7067db96d56Sopenharmony_ci         SMTPAuthenticationError  The server didn't accept the username/
7077db96d56Sopenharmony_ci                                  password combination.
7087db96d56Sopenharmony_ci         SMTPNotSupportedError    The AUTH command is not supported by the
7097db96d56Sopenharmony_ci                                  server.
7107db96d56Sopenharmony_ci         SMTPException            No suitable authentication method was
7117db96d56Sopenharmony_ci                                  found.
7127db96d56Sopenharmony_ci        """
7137db96d56Sopenharmony_ci
7147db96d56Sopenharmony_ci        self.ehlo_or_helo_if_needed()
7157db96d56Sopenharmony_ci        if not self.has_extn("auth"):
7167db96d56Sopenharmony_ci            raise SMTPNotSupportedError(
7177db96d56Sopenharmony_ci                "SMTP AUTH extension not supported by server.")
7187db96d56Sopenharmony_ci
7197db96d56Sopenharmony_ci        # Authentication methods the server claims to support
7207db96d56Sopenharmony_ci        advertised_authlist = self.esmtp_features["auth"].split()
7217db96d56Sopenharmony_ci
7227db96d56Sopenharmony_ci        # Authentication methods we can handle in our preferred order:
7237db96d56Sopenharmony_ci        preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN']
7247db96d56Sopenharmony_ci
7257db96d56Sopenharmony_ci        # We try the supported authentications in our preferred order, if
7267db96d56Sopenharmony_ci        # the server supports them.
7277db96d56Sopenharmony_ci        authlist = [auth for auth in preferred_auths
7287db96d56Sopenharmony_ci                    if auth in advertised_authlist]
7297db96d56Sopenharmony_ci        if not authlist:
7307db96d56Sopenharmony_ci            raise SMTPException("No suitable authentication method found.")
7317db96d56Sopenharmony_ci
7327db96d56Sopenharmony_ci        # Some servers advertise authentication methods they don't really
7337db96d56Sopenharmony_ci        # support, so if authentication fails, we continue until we've tried
7347db96d56Sopenharmony_ci        # all methods.
7357db96d56Sopenharmony_ci        self.user, self.password = user, password
7367db96d56Sopenharmony_ci        for authmethod in authlist:
7377db96d56Sopenharmony_ci            method_name = 'auth_' + authmethod.lower().replace('-', '_')
7387db96d56Sopenharmony_ci            try:
7397db96d56Sopenharmony_ci                (code, resp) = self.auth(
7407db96d56Sopenharmony_ci                    authmethod, getattr(self, method_name),
7417db96d56Sopenharmony_ci                    initial_response_ok=initial_response_ok)
7427db96d56Sopenharmony_ci                # 235 == 'Authentication successful'
7437db96d56Sopenharmony_ci                # 503 == 'Error: already authenticated'
7447db96d56Sopenharmony_ci                if code in (235, 503):
7457db96d56Sopenharmony_ci                    return (code, resp)
7467db96d56Sopenharmony_ci            except SMTPAuthenticationError as e:
7477db96d56Sopenharmony_ci                last_exception = e
7487db96d56Sopenharmony_ci
7497db96d56Sopenharmony_ci        # We could not login successfully.  Return result of last attempt.
7507db96d56Sopenharmony_ci        raise last_exception
7517db96d56Sopenharmony_ci
7527db96d56Sopenharmony_ci    def starttls(self, keyfile=None, certfile=None, context=None):
7537db96d56Sopenharmony_ci        """Puts the connection to the SMTP server into TLS mode.
7547db96d56Sopenharmony_ci
7557db96d56Sopenharmony_ci        If there has been no previous EHLO or HELO command this session, this
7567db96d56Sopenharmony_ci        method tries ESMTP EHLO first.
7577db96d56Sopenharmony_ci
7587db96d56Sopenharmony_ci        If the server supports TLS, this will encrypt the rest of the SMTP
7597db96d56Sopenharmony_ci        session. If you provide the keyfile and certfile parameters,
7607db96d56Sopenharmony_ci        the identity of the SMTP server and client can be checked. This,
7617db96d56Sopenharmony_ci        however, depends on whether the socket module really checks the
7627db96d56Sopenharmony_ci        certificates.
7637db96d56Sopenharmony_ci
7647db96d56Sopenharmony_ci        This method may raise the following exceptions:
7657db96d56Sopenharmony_ci
7667db96d56Sopenharmony_ci         SMTPHeloError            The server didn't reply properly to
7677db96d56Sopenharmony_ci                                  the helo greeting.
7687db96d56Sopenharmony_ci        """
7697db96d56Sopenharmony_ci        self.ehlo_or_helo_if_needed()
7707db96d56Sopenharmony_ci        if not self.has_extn("starttls"):
7717db96d56Sopenharmony_ci            raise SMTPNotSupportedError(
7727db96d56Sopenharmony_ci                "STARTTLS extension not supported by server.")
7737db96d56Sopenharmony_ci        (resp, reply) = self.docmd("STARTTLS")
7747db96d56Sopenharmony_ci        if resp == 220:
7757db96d56Sopenharmony_ci            if not _have_ssl:
7767db96d56Sopenharmony_ci                raise RuntimeError("No SSL support included in this Python")
7777db96d56Sopenharmony_ci            if context is not None and keyfile is not None:
7787db96d56Sopenharmony_ci                raise ValueError("context and keyfile arguments are mutually "
7797db96d56Sopenharmony_ci                                 "exclusive")
7807db96d56Sopenharmony_ci            if context is not None and certfile is not None:
7817db96d56Sopenharmony_ci                raise ValueError("context and certfile arguments are mutually "
7827db96d56Sopenharmony_ci                                 "exclusive")
7837db96d56Sopenharmony_ci            if keyfile is not None or certfile is not None:
7847db96d56Sopenharmony_ci                import warnings
7857db96d56Sopenharmony_ci                warnings.warn("keyfile and certfile are deprecated, use a "
7867db96d56Sopenharmony_ci                              "custom context instead", DeprecationWarning, 2)
7877db96d56Sopenharmony_ci            if context is None:
7887db96d56Sopenharmony_ci                context = ssl._create_stdlib_context(certfile=certfile,
7897db96d56Sopenharmony_ci                                                     keyfile=keyfile)
7907db96d56Sopenharmony_ci            self.sock = context.wrap_socket(self.sock,
7917db96d56Sopenharmony_ci                                            server_hostname=self._host)
7927db96d56Sopenharmony_ci            self.file = None
7937db96d56Sopenharmony_ci            # RFC 3207:
7947db96d56Sopenharmony_ci            # The client MUST discard any knowledge obtained from
7957db96d56Sopenharmony_ci            # the server, such as the list of SMTP service extensions,
7967db96d56Sopenharmony_ci            # which was not obtained from the TLS negotiation itself.
7977db96d56Sopenharmony_ci            self.helo_resp = None
7987db96d56Sopenharmony_ci            self.ehlo_resp = None
7997db96d56Sopenharmony_ci            self.esmtp_features = {}
8007db96d56Sopenharmony_ci            self.does_esmtp = False
8017db96d56Sopenharmony_ci        else:
8027db96d56Sopenharmony_ci            # RFC 3207:
8037db96d56Sopenharmony_ci            # 501 Syntax error (no parameters allowed)
8047db96d56Sopenharmony_ci            # 454 TLS not available due to temporary reason
8057db96d56Sopenharmony_ci            raise SMTPResponseException(resp, reply)
8067db96d56Sopenharmony_ci        return (resp, reply)
8077db96d56Sopenharmony_ci
8087db96d56Sopenharmony_ci    def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
8097db96d56Sopenharmony_ci                 rcpt_options=()):
8107db96d56Sopenharmony_ci        """This command performs an entire mail transaction.
8117db96d56Sopenharmony_ci
8127db96d56Sopenharmony_ci        The arguments are:
8137db96d56Sopenharmony_ci            - from_addr    : The address sending this mail.
8147db96d56Sopenharmony_ci            - to_addrs     : A list of addresses to send this mail to.  A bare
8157db96d56Sopenharmony_ci                             string will be treated as a list with 1 address.
8167db96d56Sopenharmony_ci            - msg          : The message to send.
8177db96d56Sopenharmony_ci            - mail_options : List of ESMTP options (such as 8bitmime) for the
8187db96d56Sopenharmony_ci                             mail command.
8197db96d56Sopenharmony_ci            - rcpt_options : List of ESMTP options (such as DSN commands) for
8207db96d56Sopenharmony_ci                             all the rcpt commands.
8217db96d56Sopenharmony_ci
8227db96d56Sopenharmony_ci        msg may be a string containing characters in the ASCII range, or a byte
8237db96d56Sopenharmony_ci        string.  A string is encoded to bytes using the ascii codec, and lone
8247db96d56Sopenharmony_ci        \\r and \\n characters are converted to \\r\\n characters.
8257db96d56Sopenharmony_ci
8267db96d56Sopenharmony_ci        If there has been no previous EHLO or HELO command this session, this
8277db96d56Sopenharmony_ci        method tries ESMTP EHLO first.  If the server does ESMTP, message size
8287db96d56Sopenharmony_ci        and each of the specified options will be passed to it.  If EHLO
8297db96d56Sopenharmony_ci        fails, HELO will be tried and ESMTP options suppressed.
8307db96d56Sopenharmony_ci
8317db96d56Sopenharmony_ci        This method will return normally if the mail is accepted for at least
8327db96d56Sopenharmony_ci        one recipient.  It returns a dictionary, with one entry for each
8337db96d56Sopenharmony_ci        recipient that was refused.  Each entry contains a tuple of the SMTP
8347db96d56Sopenharmony_ci        error code and the accompanying error message sent by the server.
8357db96d56Sopenharmony_ci
8367db96d56Sopenharmony_ci        This method may raise the following exceptions:
8377db96d56Sopenharmony_ci
8387db96d56Sopenharmony_ci         SMTPHeloError          The server didn't reply properly to
8397db96d56Sopenharmony_ci                                the helo greeting.
8407db96d56Sopenharmony_ci         SMTPRecipientsRefused  The server rejected ALL recipients
8417db96d56Sopenharmony_ci                                (no mail was sent).
8427db96d56Sopenharmony_ci         SMTPSenderRefused      The server didn't accept the from_addr.
8437db96d56Sopenharmony_ci         SMTPDataError          The server replied with an unexpected
8447db96d56Sopenharmony_ci                                error code (other than a refusal of
8457db96d56Sopenharmony_ci                                a recipient).
8467db96d56Sopenharmony_ci         SMTPNotSupportedError  The mail_options parameter includes 'SMTPUTF8'
8477db96d56Sopenharmony_ci                                but the SMTPUTF8 extension is not supported by
8487db96d56Sopenharmony_ci                                the server.
8497db96d56Sopenharmony_ci
8507db96d56Sopenharmony_ci        Note: the connection will be open even after an exception is raised.
8517db96d56Sopenharmony_ci
8527db96d56Sopenharmony_ci        Example:
8537db96d56Sopenharmony_ci
8547db96d56Sopenharmony_ci         >>> import smtplib
8557db96d56Sopenharmony_ci         >>> s=smtplib.SMTP("localhost")
8567db96d56Sopenharmony_ci         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
8577db96d56Sopenharmony_ci         >>> msg = '''\\
8587db96d56Sopenharmony_ci         ... From: Me@my.org
8597db96d56Sopenharmony_ci         ... Subject: testin'...
8607db96d56Sopenharmony_ci         ...
8617db96d56Sopenharmony_ci         ... This is a test '''
8627db96d56Sopenharmony_ci         >>> s.sendmail("me@my.org",tolist,msg)
8637db96d56Sopenharmony_ci         { "three@three.org" : ( 550 ,"User unknown" ) }
8647db96d56Sopenharmony_ci         >>> s.quit()
8657db96d56Sopenharmony_ci
8667db96d56Sopenharmony_ci        In the above example, the message was accepted for delivery to three
8677db96d56Sopenharmony_ci        of the four addresses, and one was rejected, with the error code
8687db96d56Sopenharmony_ci        550.  If all addresses are accepted, then the method will return an
8697db96d56Sopenharmony_ci        empty dictionary.
8707db96d56Sopenharmony_ci
8717db96d56Sopenharmony_ci        """
8727db96d56Sopenharmony_ci        self.ehlo_or_helo_if_needed()
8737db96d56Sopenharmony_ci        esmtp_opts = []
8747db96d56Sopenharmony_ci        if isinstance(msg, str):
8757db96d56Sopenharmony_ci            msg = _fix_eols(msg).encode('ascii')
8767db96d56Sopenharmony_ci        if self.does_esmtp:
8777db96d56Sopenharmony_ci            if self.has_extn('size'):
8787db96d56Sopenharmony_ci                esmtp_opts.append("size=%d" % len(msg))
8797db96d56Sopenharmony_ci            for option in mail_options:
8807db96d56Sopenharmony_ci                esmtp_opts.append(option)
8817db96d56Sopenharmony_ci        (code, resp) = self.mail(from_addr, esmtp_opts)
8827db96d56Sopenharmony_ci        if code != 250:
8837db96d56Sopenharmony_ci            if code == 421:
8847db96d56Sopenharmony_ci                self.close()
8857db96d56Sopenharmony_ci            else:
8867db96d56Sopenharmony_ci                self._rset()
8877db96d56Sopenharmony_ci            raise SMTPSenderRefused(code, resp, from_addr)
8887db96d56Sopenharmony_ci        senderrs = {}
8897db96d56Sopenharmony_ci        if isinstance(to_addrs, str):
8907db96d56Sopenharmony_ci            to_addrs = [to_addrs]
8917db96d56Sopenharmony_ci        for each in to_addrs:
8927db96d56Sopenharmony_ci            (code, resp) = self.rcpt(each, rcpt_options)
8937db96d56Sopenharmony_ci            if (code != 250) and (code != 251):
8947db96d56Sopenharmony_ci                senderrs[each] = (code, resp)
8957db96d56Sopenharmony_ci            if code == 421:
8967db96d56Sopenharmony_ci                self.close()
8977db96d56Sopenharmony_ci                raise SMTPRecipientsRefused(senderrs)
8987db96d56Sopenharmony_ci        if len(senderrs) == len(to_addrs):
8997db96d56Sopenharmony_ci            # the server refused all our recipients
9007db96d56Sopenharmony_ci            self._rset()
9017db96d56Sopenharmony_ci            raise SMTPRecipientsRefused(senderrs)
9027db96d56Sopenharmony_ci        (code, resp) = self.data(msg)
9037db96d56Sopenharmony_ci        if code != 250:
9047db96d56Sopenharmony_ci            if code == 421:
9057db96d56Sopenharmony_ci                self.close()
9067db96d56Sopenharmony_ci            else:
9077db96d56Sopenharmony_ci                self._rset()
9087db96d56Sopenharmony_ci            raise SMTPDataError(code, resp)
9097db96d56Sopenharmony_ci        #if we got here then somebody got our mail
9107db96d56Sopenharmony_ci        return senderrs
9117db96d56Sopenharmony_ci
9127db96d56Sopenharmony_ci    def send_message(self, msg, from_addr=None, to_addrs=None,
9137db96d56Sopenharmony_ci                     mail_options=(), rcpt_options=()):
9147db96d56Sopenharmony_ci        """Converts message to a bytestring and passes it to sendmail.
9157db96d56Sopenharmony_ci
9167db96d56Sopenharmony_ci        The arguments are as for sendmail, except that msg is an
9177db96d56Sopenharmony_ci        email.message.Message object.  If from_addr is None or to_addrs is
9187db96d56Sopenharmony_ci        None, these arguments are taken from the headers of the Message as
9197db96d56Sopenharmony_ci        described in RFC 2822 (a ValueError is raised if there is more than
9207db96d56Sopenharmony_ci        one set of 'Resent-' headers).  Regardless of the values of from_addr and
9217db96d56Sopenharmony_ci        to_addr, any Bcc field (or Resent-Bcc field, when the Message is a
9227db96d56Sopenharmony_ci        resent) of the Message object won't be transmitted.  The Message
9237db96d56Sopenharmony_ci        object is then serialized using email.generator.BytesGenerator and
9247db96d56Sopenharmony_ci        sendmail is called to transmit the message.  If the sender or any of
9257db96d56Sopenharmony_ci        the recipient addresses contain non-ASCII and the server advertises the
9267db96d56Sopenharmony_ci        SMTPUTF8 capability, the policy is cloned with utf8 set to True for the
9277db96d56Sopenharmony_ci        serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.
9287db96d56Sopenharmony_ci        If the server does not support SMTPUTF8, an SMTPNotSupported error is
9297db96d56Sopenharmony_ci        raised.  Otherwise the generator is called without modifying the
9307db96d56Sopenharmony_ci        policy.
9317db96d56Sopenharmony_ci
9327db96d56Sopenharmony_ci        """
9337db96d56Sopenharmony_ci        # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
9347db96d56Sopenharmony_ci        # Section 3.6.6). In such a case, we use the 'Resent-*' fields.  However,
9357db96d56Sopenharmony_ci        # if there is more than one 'Resent-' block there's no way to
9367db96d56Sopenharmony_ci        # unambiguously determine which one is the most recent in all cases,
9377db96d56Sopenharmony_ci        # so rather than guess we raise a ValueError in that case.
9387db96d56Sopenharmony_ci        #
9397db96d56Sopenharmony_ci        # TODO implement heuristics to guess the correct Resent-* block with an
9407db96d56Sopenharmony_ci        # option allowing the user to enable the heuristics.  (It should be
9417db96d56Sopenharmony_ci        # possible to guess correctly almost all of the time.)
9427db96d56Sopenharmony_ci
9437db96d56Sopenharmony_ci        self.ehlo_or_helo_if_needed()
9447db96d56Sopenharmony_ci        resent = msg.get_all('Resent-Date')
9457db96d56Sopenharmony_ci        if resent is None:
9467db96d56Sopenharmony_ci            header_prefix = ''
9477db96d56Sopenharmony_ci        elif len(resent) == 1:
9487db96d56Sopenharmony_ci            header_prefix = 'Resent-'
9497db96d56Sopenharmony_ci        else:
9507db96d56Sopenharmony_ci            raise ValueError("message has more than one 'Resent-' header block")
9517db96d56Sopenharmony_ci        if from_addr is None:
9527db96d56Sopenharmony_ci            # Prefer the sender field per RFC 2822:3.6.2.
9537db96d56Sopenharmony_ci            from_addr = (msg[header_prefix + 'Sender']
9547db96d56Sopenharmony_ci                           if (header_prefix + 'Sender') in msg
9557db96d56Sopenharmony_ci                           else msg[header_prefix + 'From'])
9567db96d56Sopenharmony_ci            from_addr = email.utils.getaddresses([from_addr])[0][1]
9577db96d56Sopenharmony_ci        if to_addrs is None:
9587db96d56Sopenharmony_ci            addr_fields = [f for f in (msg[header_prefix + 'To'],
9597db96d56Sopenharmony_ci                                       msg[header_prefix + 'Bcc'],
9607db96d56Sopenharmony_ci                                       msg[header_prefix + 'Cc'])
9617db96d56Sopenharmony_ci                           if f is not None]
9627db96d56Sopenharmony_ci            to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)]
9637db96d56Sopenharmony_ci        # Make a local copy so we can delete the bcc headers.
9647db96d56Sopenharmony_ci        msg_copy = copy.copy(msg)
9657db96d56Sopenharmony_ci        del msg_copy['Bcc']
9667db96d56Sopenharmony_ci        del msg_copy['Resent-Bcc']
9677db96d56Sopenharmony_ci        international = False
9687db96d56Sopenharmony_ci        try:
9697db96d56Sopenharmony_ci            ''.join([from_addr, *to_addrs]).encode('ascii')
9707db96d56Sopenharmony_ci        except UnicodeEncodeError:
9717db96d56Sopenharmony_ci            if not self.has_extn('smtputf8'):
9727db96d56Sopenharmony_ci                raise SMTPNotSupportedError(
9737db96d56Sopenharmony_ci                    "One or more source or delivery addresses require"
9747db96d56Sopenharmony_ci                    " internationalized email support, but the server"
9757db96d56Sopenharmony_ci                    " does not advertise the required SMTPUTF8 capability")
9767db96d56Sopenharmony_ci            international = True
9777db96d56Sopenharmony_ci        with io.BytesIO() as bytesmsg:
9787db96d56Sopenharmony_ci            if international:
9797db96d56Sopenharmony_ci                g = email.generator.BytesGenerator(
9807db96d56Sopenharmony_ci                    bytesmsg, policy=msg.policy.clone(utf8=True))
9817db96d56Sopenharmony_ci                mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME')
9827db96d56Sopenharmony_ci            else:
9837db96d56Sopenharmony_ci                g = email.generator.BytesGenerator(bytesmsg)
9847db96d56Sopenharmony_ci            g.flatten(msg_copy, linesep='\r\n')
9857db96d56Sopenharmony_ci            flatmsg = bytesmsg.getvalue()
9867db96d56Sopenharmony_ci        return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
9877db96d56Sopenharmony_ci                             rcpt_options)
9887db96d56Sopenharmony_ci
9897db96d56Sopenharmony_ci    def close(self):
9907db96d56Sopenharmony_ci        """Close the connection to the SMTP server."""
9917db96d56Sopenharmony_ci        try:
9927db96d56Sopenharmony_ci            file = self.file
9937db96d56Sopenharmony_ci            self.file = None
9947db96d56Sopenharmony_ci            if file:
9957db96d56Sopenharmony_ci                file.close()
9967db96d56Sopenharmony_ci        finally:
9977db96d56Sopenharmony_ci            sock = self.sock
9987db96d56Sopenharmony_ci            self.sock = None
9997db96d56Sopenharmony_ci            if sock:
10007db96d56Sopenharmony_ci                sock.close()
10017db96d56Sopenharmony_ci
10027db96d56Sopenharmony_ci    def quit(self):
10037db96d56Sopenharmony_ci        """Terminate the SMTP session."""
10047db96d56Sopenharmony_ci        res = self.docmd("quit")
10057db96d56Sopenharmony_ci        # A new EHLO is required after reconnecting with connect()
10067db96d56Sopenharmony_ci        self.ehlo_resp = self.helo_resp = None
10077db96d56Sopenharmony_ci        self.esmtp_features = {}
10087db96d56Sopenharmony_ci        self.does_esmtp = False
10097db96d56Sopenharmony_ci        self.close()
10107db96d56Sopenharmony_ci        return res
10117db96d56Sopenharmony_ci
10127db96d56Sopenharmony_ciif _have_ssl:
10137db96d56Sopenharmony_ci
10147db96d56Sopenharmony_ci    class SMTP_SSL(SMTP):
10157db96d56Sopenharmony_ci        """ This is a subclass derived from SMTP that connects over an SSL
10167db96d56Sopenharmony_ci        encrypted socket (to use this class you need a socket module that was
10177db96d56Sopenharmony_ci        compiled with SSL support). If host is not specified, '' (the local
10187db96d56Sopenharmony_ci        host) is used. If port is omitted, the standard SMTP-over-SSL port
10197db96d56Sopenharmony_ci        (465) is used.  local_hostname and source_address have the same meaning
10207db96d56Sopenharmony_ci        as they do in the SMTP class.  keyfile and certfile are also optional -
10217db96d56Sopenharmony_ci        they can contain a PEM formatted private key and certificate chain file
10227db96d56Sopenharmony_ci        for the SSL connection. context also optional, can contain a
10237db96d56Sopenharmony_ci        SSLContext, and is an alternative to keyfile and certfile; If it is
10247db96d56Sopenharmony_ci        specified both keyfile and certfile must be None.
10257db96d56Sopenharmony_ci
10267db96d56Sopenharmony_ci        """
10277db96d56Sopenharmony_ci
10287db96d56Sopenharmony_ci        default_port = SMTP_SSL_PORT
10297db96d56Sopenharmony_ci
10307db96d56Sopenharmony_ci        def __init__(self, host='', port=0, local_hostname=None,
10317db96d56Sopenharmony_ci                     keyfile=None, certfile=None,
10327db96d56Sopenharmony_ci                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
10337db96d56Sopenharmony_ci                     source_address=None, context=None):
10347db96d56Sopenharmony_ci            if context is not None and keyfile is not None:
10357db96d56Sopenharmony_ci                raise ValueError("context and keyfile arguments are mutually "
10367db96d56Sopenharmony_ci                                 "exclusive")
10377db96d56Sopenharmony_ci            if context is not None and certfile is not None:
10387db96d56Sopenharmony_ci                raise ValueError("context and certfile arguments are mutually "
10397db96d56Sopenharmony_ci                                 "exclusive")
10407db96d56Sopenharmony_ci            if keyfile is not None or certfile is not None:
10417db96d56Sopenharmony_ci                import warnings
10427db96d56Sopenharmony_ci                warnings.warn("keyfile and certfile are deprecated, use a "
10437db96d56Sopenharmony_ci                              "custom context instead", DeprecationWarning, 2)
10447db96d56Sopenharmony_ci            self.keyfile = keyfile
10457db96d56Sopenharmony_ci            self.certfile = certfile
10467db96d56Sopenharmony_ci            if context is None:
10477db96d56Sopenharmony_ci                context = ssl._create_stdlib_context(certfile=certfile,
10487db96d56Sopenharmony_ci                                                     keyfile=keyfile)
10497db96d56Sopenharmony_ci            self.context = context
10507db96d56Sopenharmony_ci            SMTP.__init__(self, host, port, local_hostname, timeout,
10517db96d56Sopenharmony_ci                          source_address)
10527db96d56Sopenharmony_ci
10537db96d56Sopenharmony_ci        def _get_socket(self, host, port, timeout):
10547db96d56Sopenharmony_ci            if self.debuglevel > 0:
10557db96d56Sopenharmony_ci                self._print_debug('connect:', (host, port))
10567db96d56Sopenharmony_ci            new_socket = super()._get_socket(host, port, timeout)
10577db96d56Sopenharmony_ci            new_socket = self.context.wrap_socket(new_socket,
10587db96d56Sopenharmony_ci                                                  server_hostname=self._host)
10597db96d56Sopenharmony_ci            return new_socket
10607db96d56Sopenharmony_ci
10617db96d56Sopenharmony_ci    __all__.append("SMTP_SSL")
10627db96d56Sopenharmony_ci
10637db96d56Sopenharmony_ci#
10647db96d56Sopenharmony_ci# LMTP extension
10657db96d56Sopenharmony_ci#
10667db96d56Sopenharmony_ciLMTP_PORT = 2003
10677db96d56Sopenharmony_ci
10687db96d56Sopenharmony_ciclass LMTP(SMTP):
10697db96d56Sopenharmony_ci    """LMTP - Local Mail Transfer Protocol
10707db96d56Sopenharmony_ci
10717db96d56Sopenharmony_ci    The LMTP protocol, which is very similar to ESMTP, is heavily based
10727db96d56Sopenharmony_ci    on the standard SMTP client. It's common to use Unix sockets for
10737db96d56Sopenharmony_ci    LMTP, so our connect() method must support that as well as a regular
10747db96d56Sopenharmony_ci    host:port server.  local_hostname and source_address have the same
10757db96d56Sopenharmony_ci    meaning as they do in the SMTP class.  To specify a Unix socket,
10767db96d56Sopenharmony_ci    you must use an absolute path as the host, starting with a '/'.
10777db96d56Sopenharmony_ci
10787db96d56Sopenharmony_ci    Authentication is supported, using the regular SMTP mechanism. When
10797db96d56Sopenharmony_ci    using a Unix socket, LMTP generally don't support or require any
10807db96d56Sopenharmony_ci    authentication, but your mileage might vary."""
10817db96d56Sopenharmony_ci
10827db96d56Sopenharmony_ci    ehlo_msg = "lhlo"
10837db96d56Sopenharmony_ci
10847db96d56Sopenharmony_ci    def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
10857db96d56Sopenharmony_ci                 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
10867db96d56Sopenharmony_ci        """Initialize a new instance."""
10877db96d56Sopenharmony_ci        super().__init__(host, port, local_hostname=local_hostname,
10887db96d56Sopenharmony_ci                         source_address=source_address, timeout=timeout)
10897db96d56Sopenharmony_ci
10907db96d56Sopenharmony_ci    def connect(self, host='localhost', port=0, source_address=None):
10917db96d56Sopenharmony_ci        """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
10927db96d56Sopenharmony_ci        if host[0] != '/':
10937db96d56Sopenharmony_ci            return super().connect(host, port, source_address=source_address)
10947db96d56Sopenharmony_ci
10957db96d56Sopenharmony_ci        if self.timeout is not None and not self.timeout:
10967db96d56Sopenharmony_ci            raise ValueError('Non-blocking socket (timeout=0) is not supported')
10977db96d56Sopenharmony_ci
10987db96d56Sopenharmony_ci        # Handle Unix-domain sockets.
10997db96d56Sopenharmony_ci        try:
11007db96d56Sopenharmony_ci            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
11017db96d56Sopenharmony_ci            if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
11027db96d56Sopenharmony_ci                self.sock.settimeout(self.timeout)
11037db96d56Sopenharmony_ci            self.file = None
11047db96d56Sopenharmony_ci            self.sock.connect(host)
11057db96d56Sopenharmony_ci        except OSError:
11067db96d56Sopenharmony_ci            if self.debuglevel > 0:
11077db96d56Sopenharmony_ci                self._print_debug('connect fail:', host)
11087db96d56Sopenharmony_ci            if self.sock:
11097db96d56Sopenharmony_ci                self.sock.close()
11107db96d56Sopenharmony_ci            self.sock = None
11117db96d56Sopenharmony_ci            raise
11127db96d56Sopenharmony_ci        (code, msg) = self.getreply()
11137db96d56Sopenharmony_ci        if self.debuglevel > 0:
11147db96d56Sopenharmony_ci            self._print_debug('connect:', msg)
11157db96d56Sopenharmony_ci        return (code, msg)
11167db96d56Sopenharmony_ci
11177db96d56Sopenharmony_ci
11187db96d56Sopenharmony_ci# Test the sendmail method, which tests most of the others.
11197db96d56Sopenharmony_ci# Note: This always sends to localhost.
11207db96d56Sopenharmony_ciif __name__ == '__main__':
11217db96d56Sopenharmony_ci    def prompt(prompt):
11227db96d56Sopenharmony_ci        sys.stdout.write(prompt + ": ")
11237db96d56Sopenharmony_ci        sys.stdout.flush()
11247db96d56Sopenharmony_ci        return sys.stdin.readline().strip()
11257db96d56Sopenharmony_ci
11267db96d56Sopenharmony_ci    fromaddr = prompt("From")
11277db96d56Sopenharmony_ci    toaddrs = prompt("To").split(',')
11287db96d56Sopenharmony_ci    print("Enter message, end with ^D:")
11297db96d56Sopenharmony_ci    msg = ''
11307db96d56Sopenharmony_ci    while 1:
11317db96d56Sopenharmony_ci        line = sys.stdin.readline()
11327db96d56Sopenharmony_ci        if not line:
11337db96d56Sopenharmony_ci            break
11347db96d56Sopenharmony_ci        msg = msg + line
11357db96d56Sopenharmony_ci    print("Message length is %d" % len(msg))
11367db96d56Sopenharmony_ci
11377db96d56Sopenharmony_ci    server = SMTP('localhost')
11387db96d56Sopenharmony_ci    server.set_debuglevel(1)
11397db96d56Sopenharmony_ci    server.sendmail(fromaddr, toaddrs, msg)
11407db96d56Sopenharmony_ci    server.quit()
1141