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