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