|  | #! /usr/bin/env python | 
|  |  | 
|  | '''SMTP/ESMTP client class. | 
|  |  | 
|  | This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP | 
|  | Authentication) and RFC 2487 (Secure SMTP over TLS). | 
|  |  | 
|  | Notes: | 
|  |  | 
|  | Please remember, when doing ESMTP, that the names of the SMTP service | 
|  | extensions are NOT the same thing as the option keywords for the RCPT | 
|  | and MAIL commands! | 
|  |  | 
|  | Example: | 
|  |  | 
|  | >>> import smtplib | 
|  | >>> s=smtplib.SMTP("localhost") | 
|  | >>> print s.help() | 
|  | This is Sendmail version 8.8.4 | 
|  | Topics: | 
|  | HELO    EHLO    MAIL    RCPT    DATA | 
|  | RSET    NOOP    QUIT    HELP    VRFY | 
|  | EXPN    VERB    ETRN    DSN | 
|  | For more info use "HELP <topic>". | 
|  | To report bugs in the implementation send email to | 
|  | sendmail-bugs@sendmail.org. | 
|  | For local information send email to Postmaster at your site. | 
|  | End of HELP info | 
|  | >>> s.putcmd("vrfy","someone@here") | 
|  | >>> s.getreply() | 
|  | (250, "Somebody OverHere <somebody@here.my.org>") | 
|  | >>> s.quit() | 
|  | ''' | 
|  |  | 
|  | # Author: The Dragon De Monsyne <dragondm@integral.org> | 
|  | # ESMTP support, test code and doc fixes added by | 
|  | #     Eric S. Raymond <esr@thyrsus.com> | 
|  | # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) | 
|  | #     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers. | 
|  | # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>. | 
|  | # | 
|  | # This was modified from the Python 1.5 library HTTP lib. | 
|  |  | 
|  | import socket | 
|  | import re | 
|  | import email.utils | 
|  | import base64 | 
|  | import hmac | 
|  | from email.base64mime import encode as encode_base64 | 
|  | from sys import stderr | 
|  |  | 
|  | __all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException", | 
|  | "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", | 
|  | "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", | 
|  | "quoteaddr", "quotedata", "SMTP"] | 
|  |  | 
|  | SMTP_PORT = 25 | 
|  | SMTP_SSL_PORT = 465 | 
|  | CRLF = "\r\n" | 
|  |  | 
|  | OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) | 
|  |  | 
|  |  | 
|  | # Exception classes used by this module. | 
|  | class SMTPException(Exception): | 
|  | """Base class for all exceptions raised by this module.""" | 
|  |  | 
|  | class SMTPServerDisconnected(SMTPException): | 
|  | """Not connected to any SMTP server. | 
|  |  | 
|  | This exception is raised when the server unexpectedly disconnects, | 
|  | or when an attempt is made to use the SMTP instance before | 
|  | connecting it to a server. | 
|  | """ | 
|  |  | 
|  | class SMTPResponseException(SMTPException): | 
|  | """Base class for all exceptions that include an SMTP error code. | 
|  |  | 
|  | These exceptions are generated in some instances when the SMTP | 
|  | server returns an error code.  The error code is stored in the | 
|  | `smtp_code' attribute of the error, and the `smtp_error' attribute | 
|  | is set to the error message. | 
|  | """ | 
|  |  | 
|  | def __init__(self, code, msg): | 
|  | self.smtp_code = code | 
|  | self.smtp_error = msg | 
|  | self.args = (code, msg) | 
|  |  | 
|  | class SMTPSenderRefused(SMTPResponseException): | 
|  | """Sender address refused. | 
|  |  | 
|  | In addition to the attributes set by on all SMTPResponseException | 
|  | exceptions, this sets `sender' to the string that the SMTP refused. | 
|  | """ | 
|  |  | 
|  | def __init__(self, code, msg, sender): | 
|  | self.smtp_code = code | 
|  | self.smtp_error = msg | 
|  | self.sender = sender | 
|  | self.args = (code, msg, sender) | 
|  |  | 
|  | class SMTPRecipientsRefused(SMTPException): | 
|  | """All recipient addresses refused. | 
|  |  | 
|  | The errors for each recipient are accessible through the attribute | 
|  | 'recipients', which is a dictionary of exactly the same sort as | 
|  | SMTP.sendmail() returns. | 
|  | """ | 
|  |  | 
|  | def __init__(self, recipients): | 
|  | self.recipients = recipients | 
|  | self.args = (recipients,) | 
|  |  | 
|  |  | 
|  | class SMTPDataError(SMTPResponseException): | 
|  | """The SMTP server didn't accept the data.""" | 
|  |  | 
|  | class SMTPConnectError(SMTPResponseException): | 
|  | """Error during connection establishment.""" | 
|  |  | 
|  | class SMTPHeloError(SMTPResponseException): | 
|  | """The server refused our HELO reply.""" | 
|  |  | 
|  | class SMTPAuthenticationError(SMTPResponseException): | 
|  | """Authentication error. | 
|  |  | 
|  | Most probably the server didn't accept the username/password | 
|  | combination provided. | 
|  | """ | 
|  |  | 
|  |  | 
|  | def quoteaddr(addr): | 
|  | """Quote a subset of the email addresses defined by RFC 821. | 
|  |  | 
|  | Should be able to handle anything rfc822.parseaddr can handle. | 
|  | """ | 
|  | m = (None, None) | 
|  | try: | 
|  | m = email.utils.parseaddr(addr)[1] | 
|  | except AttributeError: | 
|  | pass | 
|  | if m == (None, None):  # Indicates parse failure or AttributeError | 
|  | # something weird here.. punt -ddm | 
|  | return "<%s>" % addr | 
|  | elif m is None: | 
|  | # the sender wants an empty return address | 
|  | return "<>" | 
|  | else: | 
|  | return "<%s>" % m | 
|  |  | 
|  | def _addr_only(addrstring): | 
|  | displayname, addr = email.utils.parseaddr(addrstring) | 
|  | if (displayname, addr) == ('', ''): | 
|  | # parseaddr couldn't parse it, so use it as is. | 
|  | return addrstring | 
|  | return addr | 
|  |  | 
|  | def quotedata(data): | 
|  | """Quote data for email. | 
|  |  | 
|  | Double leading '.', and change Unix newline '\\n', or Mac '\\r' into | 
|  | Internet CRLF end-of-line. | 
|  | """ | 
|  | return re.sub(r'(?m)^\.', '..', | 
|  | re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) | 
|  |  | 
|  |  | 
|  | try: | 
|  | import ssl | 
|  | except ImportError: | 
|  | _have_ssl = False | 
|  | else: | 
|  | class SSLFakeFile: | 
|  | """A fake file like object that really wraps a SSLObject. | 
|  |  | 
|  | It only supports what is needed in smtplib. | 
|  | """ | 
|  | def __init__(self, sslobj): | 
|  | self.sslobj = sslobj | 
|  |  | 
|  | def readline(self): | 
|  | str = "" | 
|  | chr = None | 
|  | while chr != "\n": | 
|  | chr = self.sslobj.read(1) | 
|  | if not chr: | 
|  | break | 
|  | str += chr | 
|  | return str | 
|  |  | 
|  | def close(self): | 
|  | pass | 
|  |  | 
|  | _have_ssl = True | 
|  |  | 
|  | class SMTP: | 
|  | """This class manages a connection to an SMTP or ESMTP server. | 
|  | SMTP Objects: | 
|  | SMTP objects have the following attributes: | 
|  | helo_resp | 
|  | This is the message given by the server in response to the | 
|  | most recent HELO command. | 
|  |  | 
|  | ehlo_resp | 
|  | This is the message given by the server in response to the | 
|  | most recent EHLO command. This is usually multiline. | 
|  |  | 
|  | does_esmtp | 
|  | This is a True value _after you do an EHLO command_, if the | 
|  | server supports ESMTP. | 
|  |  | 
|  | esmtp_features | 
|  | This is a dictionary, which, if the server supports ESMTP, | 
|  | will _after you do an EHLO command_, contain the names of the | 
|  | SMTP service extensions this server supports, and their | 
|  | parameters (if any). | 
|  |  | 
|  | Note, all extension names are mapped to lower case in the | 
|  | dictionary. | 
|  |  | 
|  | See each method's docstrings for details.  In general, there is a | 
|  | method of the same name to perform each SMTP command.  There is also a | 
|  | method called 'sendmail' that will do an entire mail transaction. | 
|  | """ | 
|  | debuglevel = 0 | 
|  | file = None | 
|  | helo_resp = None | 
|  | ehlo_msg = "ehlo" | 
|  | ehlo_resp = None | 
|  | does_esmtp = 0 | 
|  | default_port = SMTP_PORT | 
|  |  | 
|  | def __init__(self, host='', port=0, local_hostname=None, | 
|  | timeout=socket._GLOBAL_DEFAULT_TIMEOUT): | 
|  | """Initialize a new instance. | 
|  |  | 
|  | If specified, `host' is the name of the remote host to which to | 
|  | connect.  If specified, `port' specifies the port to which to connect. | 
|  | By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised | 
|  | if the specified `host' doesn't respond correctly.  If specified, | 
|  | `local_hostname` is used as the FQDN of the local host.  By default, | 
|  | the local hostname is found using socket.getfqdn(). | 
|  |  | 
|  | """ | 
|  | self.timeout = timeout | 
|  | self.esmtp_features = {} | 
|  | if host: | 
|  | (code, msg) = self.connect(host, port) | 
|  | if code != 220: | 
|  | raise SMTPConnectError(code, msg) | 
|  | if local_hostname is not None: | 
|  | self.local_hostname = local_hostname | 
|  | else: | 
|  | # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and | 
|  | # if that can't be calculated, that we should use a domain literal | 
|  | # instead (essentially an encoded IP address like [A.B.C.D]). | 
|  | fqdn = socket.getfqdn() | 
|  | if '.' in fqdn: | 
|  | self.local_hostname = fqdn | 
|  | else: | 
|  | # We can't find an fqdn hostname, so use a domain literal | 
|  | addr = '127.0.0.1' | 
|  | try: | 
|  | addr = socket.gethostbyname(socket.gethostname()) | 
|  | except socket.gaierror: | 
|  | pass | 
|  | self.local_hostname = '[%s]' % addr | 
|  |  | 
|  | def set_debuglevel(self, debuglevel): | 
|  | """Set the debug output level. | 
|  |  | 
|  | A non-false value results in debug messages for connection and for all | 
|  | messages sent to and received from the server. | 
|  |  | 
|  | """ | 
|  | self.debuglevel = debuglevel | 
|  |  | 
|  | def _get_socket(self, port, host, timeout): | 
|  | # This makes it simpler for SMTP_SSL to use the SMTP connect code | 
|  | # and just alter the socket connection bit. | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'connect:', (host, port) | 
|  | return socket.create_connection((port, host), timeout) | 
|  |  | 
|  | def connect(self, host='localhost', port=0): | 
|  | """Connect to a host on a given port. | 
|  |  | 
|  | If the hostname ends with a colon (`:') followed by a number, and | 
|  | there is no port specified, that suffix will be stripped off and the | 
|  | number interpreted as the port number to use. | 
|  |  | 
|  | Note: This method is automatically invoked by __init__, if a host is | 
|  | specified during instantiation. | 
|  |  | 
|  | """ | 
|  | if not port and (host.find(':') == host.rfind(':')): | 
|  | i = host.rfind(':') | 
|  | if i >= 0: | 
|  | host, port = host[:i], host[i + 1:] | 
|  | try: | 
|  | port = int(port) | 
|  | except ValueError: | 
|  | raise socket.error, "nonnumeric port" | 
|  | if not port: | 
|  | port = self.default_port | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'connect:', (host, port) | 
|  | self.sock = self._get_socket(host, port, self.timeout) | 
|  | (code, msg) = self.getreply() | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, "connect:", msg | 
|  | return (code, msg) | 
|  |  | 
|  | def send(self, str): | 
|  | """Send `str' to the server.""" | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'send:', repr(str) | 
|  | if hasattr(self, 'sock') and self.sock: | 
|  | try: | 
|  | self.sock.sendall(str) | 
|  | except socket.error: | 
|  | self.close() | 
|  | raise SMTPServerDisconnected('Server not connected') | 
|  | else: | 
|  | raise SMTPServerDisconnected('please run connect() first') | 
|  |  | 
|  | def putcmd(self, cmd, args=""): | 
|  | """Send a command to the server.""" | 
|  | if args == "": | 
|  | str = '%s%s' % (cmd, CRLF) | 
|  | else: | 
|  | str = '%s %s%s' % (cmd, args, CRLF) | 
|  | self.send(str) | 
|  |  | 
|  | def getreply(self): | 
|  | """Get a reply from the server. | 
|  |  | 
|  | Returns a tuple consisting of: | 
|  |  | 
|  | - server response code (e.g. '250', or such, if all goes well) | 
|  | Note: returns -1 if it can't read response code. | 
|  |  | 
|  | - server response string corresponding to response code (multiline | 
|  | responses are converted to a single, multiline string). | 
|  |  | 
|  | Raises SMTPServerDisconnected if end-of-file is reached. | 
|  | """ | 
|  | resp = [] | 
|  | if self.file is None: | 
|  | self.file = self.sock.makefile('rb') | 
|  | while 1: | 
|  | try: | 
|  | line = self.file.readline() | 
|  | except socket.error as e: | 
|  | self.close() | 
|  | raise SMTPServerDisconnected("Connection unexpectedly closed: " | 
|  | + str(e)) | 
|  | if line == '': | 
|  | self.close() | 
|  | raise SMTPServerDisconnected("Connection unexpectedly closed") | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'reply:', repr(line) | 
|  | resp.append(line[4:].strip()) | 
|  | code = line[:3] | 
|  | # Check that the error code is syntactically correct. | 
|  | # Don't attempt to read a continuation line if it is broken. | 
|  | try: | 
|  | errcode = int(code) | 
|  | except ValueError: | 
|  | errcode = -1 | 
|  | break | 
|  | # Check if multiline response. | 
|  | if line[3:4] != "-": | 
|  | break | 
|  |  | 
|  | errmsg = "\n".join(resp) | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg) | 
|  | return errcode, errmsg | 
|  |  | 
|  | def docmd(self, cmd, args=""): | 
|  | """Send a command, and return its response code.""" | 
|  | self.putcmd(cmd, args) | 
|  | return self.getreply() | 
|  |  | 
|  | # std smtp commands | 
|  | def helo(self, name=''): | 
|  | """SMTP 'helo' command. | 
|  | Hostname to send for this command defaults to the FQDN of the local | 
|  | host. | 
|  | """ | 
|  | self.putcmd("helo", name or self.local_hostname) | 
|  | (code, msg) = self.getreply() | 
|  | self.helo_resp = msg | 
|  | return (code, msg) | 
|  |  | 
|  | def ehlo(self, name=''): | 
|  | """ SMTP 'ehlo' command. | 
|  | Hostname to send for this command defaults to the FQDN of the local | 
|  | host. | 
|  | """ | 
|  | self.esmtp_features = {} | 
|  | self.putcmd(self.ehlo_msg, name or self.local_hostname) | 
|  | (code, msg) = self.getreply() | 
|  | # According to RFC1869 some (badly written) | 
|  | # MTA's will disconnect on an ehlo. Toss an exception if | 
|  | # that happens -ddm | 
|  | if code == -1 and len(msg) == 0: | 
|  | self.close() | 
|  | raise SMTPServerDisconnected("Server not connected") | 
|  | self.ehlo_resp = msg | 
|  | if code != 250: | 
|  | return (code, msg) | 
|  | self.does_esmtp = 1 | 
|  | #parse the ehlo response -ddm | 
|  | resp = self.ehlo_resp.split('\n') | 
|  | del resp[0] | 
|  | for each in resp: | 
|  | # To be able to communicate with as many SMTP servers as possible, | 
|  | # we have to take the old-style auth advertisement into account, | 
|  | # because: | 
|  | # 1) Else our SMTP feature parser gets confused. | 
|  | # 2) There are some servers that only advertise the auth methods we | 
|  | #    support using the old style. | 
|  | auth_match = OLDSTYLE_AUTH.match(each) | 
|  | if auth_match: | 
|  | # This doesn't remove duplicates, but that's no problem | 
|  | self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ | 
|  | + " " + auth_match.groups(0)[0] | 
|  | continue | 
|  |  | 
|  | # RFC 1869 requires a space between ehlo keyword and parameters. | 
|  | # It's actually stricter, in that only spaces are allowed between | 
|  | # parameters, but were not going to check for that here.  Note | 
|  | # that the space isn't present if there are no parameters. | 
|  | m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) | 
|  | if m: | 
|  | feature = m.group("feature").lower() | 
|  | params = m.string[m.end("feature"):].strip() | 
|  | if feature == "auth": | 
|  | self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ | 
|  | + " " + params | 
|  | else: | 
|  | self.esmtp_features[feature] = params | 
|  | return (code, msg) | 
|  |  | 
|  | def has_extn(self, opt): | 
|  | """Does the server support a given SMTP service extension?""" | 
|  | return opt.lower() in self.esmtp_features | 
|  |  | 
|  | def help(self, args=''): | 
|  | """SMTP 'help' command. | 
|  | Returns help text from server.""" | 
|  | self.putcmd("help", args) | 
|  | return self.getreply()[1] | 
|  |  | 
|  | def rset(self): | 
|  | """SMTP 'rset' command -- resets session.""" | 
|  | return self.docmd("rset") | 
|  |  | 
|  | def noop(self): | 
|  | """SMTP 'noop' command -- doesn't do anything :>""" | 
|  | return self.docmd("noop") | 
|  |  | 
|  | def mail(self, sender, options=[]): | 
|  | """SMTP 'mail' command -- begins mail xfer session.""" | 
|  | optionlist = '' | 
|  | if options and self.does_esmtp: | 
|  | optionlist = ' ' + ' '.join(options) | 
|  | self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) | 
|  | return self.getreply() | 
|  |  | 
|  | def rcpt(self, recip, options=[]): | 
|  | """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" | 
|  | optionlist = '' | 
|  | if options and self.does_esmtp: | 
|  | optionlist = ' ' + ' '.join(options) | 
|  | self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) | 
|  | return self.getreply() | 
|  |  | 
|  | def data(self, msg): | 
|  | """SMTP 'DATA' command -- sends message data to server. | 
|  |  | 
|  | Automatically quotes lines beginning with a period per rfc821. | 
|  | Raises SMTPDataError if there is an unexpected reply to the | 
|  | DATA command; the return value from this method is the final | 
|  | response code received when the all data is sent. | 
|  | """ | 
|  | self.putcmd("data") | 
|  | (code, repl) = self.getreply() | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, "data:", (code, repl) | 
|  | if code != 354: | 
|  | raise SMTPDataError(code, repl) | 
|  | else: | 
|  | q = quotedata(msg) | 
|  | if q[-2:] != CRLF: | 
|  | q = q + CRLF | 
|  | q = q + "." + CRLF | 
|  | self.send(q) | 
|  | (code, msg) = self.getreply() | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, "data:", (code, msg) | 
|  | return (code, msg) | 
|  |  | 
|  | def verify(self, address): | 
|  | """SMTP 'verify' command -- checks for address validity.""" | 
|  | self.putcmd("vrfy", _addr_only(address)) | 
|  | return self.getreply() | 
|  | # a.k.a. | 
|  | vrfy = verify | 
|  |  | 
|  | def expn(self, address): | 
|  | """SMTP 'expn' command -- expands a mailing list.""" | 
|  | self.putcmd("expn", _addr_only(address)) | 
|  | return self.getreply() | 
|  |  | 
|  | # some useful methods | 
|  |  | 
|  | def ehlo_or_helo_if_needed(self): | 
|  | """Call self.ehlo() and/or self.helo() if needed. | 
|  |  | 
|  | If there has been no previous EHLO or HELO command this session, this | 
|  | method tries ESMTP EHLO first. | 
|  |  | 
|  | This method may raise the following exceptions: | 
|  |  | 
|  | SMTPHeloError            The server didn't reply properly to | 
|  | the helo greeting. | 
|  | """ | 
|  | if self.helo_resp is None and self.ehlo_resp is None: | 
|  | if not (200 <= self.ehlo()[0] <= 299): | 
|  | (code, resp) = self.helo() | 
|  | if not (200 <= code <= 299): | 
|  | raise SMTPHeloError(code, resp) | 
|  |  | 
|  | def login(self, user, password): | 
|  | """Log in on an SMTP server that requires authentication. | 
|  |  | 
|  | The arguments are: | 
|  | - user:     The user name to authenticate with. | 
|  | - password: The password for the authentication. | 
|  |  | 
|  | If there has been no previous EHLO or HELO command this session, this | 
|  | method tries ESMTP EHLO first. | 
|  |  | 
|  | This method will return normally if the authentication was successful. | 
|  |  | 
|  | This method may raise the following exceptions: | 
|  |  | 
|  | SMTPHeloError            The server didn't reply properly to | 
|  | the helo greeting. | 
|  | SMTPAuthenticationError  The server didn't accept the username/ | 
|  | password combination. | 
|  | SMTPException            No suitable authentication method was | 
|  | found. | 
|  | """ | 
|  |  | 
|  | def encode_cram_md5(challenge, user, password): | 
|  | challenge = base64.decodestring(challenge) | 
|  | response = user + " " + hmac.HMAC(password, challenge).hexdigest() | 
|  | return encode_base64(response, eol="") | 
|  |  | 
|  | def encode_plain(user, password): | 
|  | return encode_base64("\0%s\0%s" % (user, password), eol="") | 
|  |  | 
|  |  | 
|  | AUTH_PLAIN = "PLAIN" | 
|  | AUTH_CRAM_MD5 = "CRAM-MD5" | 
|  | AUTH_LOGIN = "LOGIN" | 
|  |  | 
|  | self.ehlo_or_helo_if_needed() | 
|  |  | 
|  | if not self.has_extn("auth"): | 
|  | raise SMTPException("SMTP AUTH extension not supported by server.") | 
|  |  | 
|  | # Authentication methods the server supports: | 
|  | authlist = self.esmtp_features["auth"].split() | 
|  |  | 
|  | # List of authentication methods we support: from preferred to | 
|  | # less preferred methods. Except for the purpose of testing the weaker | 
|  | # ones, we prefer stronger methods like CRAM-MD5: | 
|  | preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN] | 
|  |  | 
|  | # Determine the authentication method we'll use | 
|  | authmethod = None | 
|  | for method in preferred_auths: | 
|  | if method in authlist: | 
|  | authmethod = method | 
|  | break | 
|  |  | 
|  | if authmethod == AUTH_CRAM_MD5: | 
|  | (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) | 
|  | if code == 503: | 
|  | # 503 == 'Error: already authenticated' | 
|  | return (code, resp) | 
|  | (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) | 
|  | elif authmethod == AUTH_PLAIN: | 
|  | (code, resp) = self.docmd("AUTH", | 
|  | AUTH_PLAIN + " " + encode_plain(user, password)) | 
|  | elif authmethod == AUTH_LOGIN: | 
|  | (code, resp) = self.docmd("AUTH", | 
|  | "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) | 
|  | if code != 334: | 
|  | raise SMTPAuthenticationError(code, resp) | 
|  | (code, resp) = self.docmd(encode_base64(password, eol="")) | 
|  | elif authmethod is None: | 
|  | raise SMTPException("No suitable authentication method found.") | 
|  | if code not in (235, 503): | 
|  | # 235 == 'Authentication successful' | 
|  | # 503 == 'Error: already authenticated' | 
|  | raise SMTPAuthenticationError(code, resp) | 
|  | return (code, resp) | 
|  |  | 
|  | def starttls(self, keyfile=None, certfile=None): | 
|  | """Puts the connection to the SMTP server into TLS mode. | 
|  |  | 
|  | If there has been no previous EHLO or HELO command this session, this | 
|  | method tries ESMTP EHLO first. | 
|  |  | 
|  | If the server supports TLS, this will encrypt the rest of the SMTP | 
|  | session. If you provide the keyfile and certfile parameters, | 
|  | the identity of the SMTP server and client can be checked. This, | 
|  | however, depends on whether the socket module really checks the | 
|  | certificates. | 
|  |  | 
|  | This method may raise the following exceptions: | 
|  |  | 
|  | SMTPHeloError            The server didn't reply properly to | 
|  | the helo greeting. | 
|  | """ | 
|  | self.ehlo_or_helo_if_needed() | 
|  | if not self.has_extn("starttls"): | 
|  | raise SMTPException("STARTTLS extension not supported by server.") | 
|  | (resp, reply) = self.docmd("STARTTLS") | 
|  | if resp == 220: | 
|  | if not _have_ssl: | 
|  | raise RuntimeError("No SSL support included in this Python") | 
|  | self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) | 
|  | self.file = SSLFakeFile(self.sock) | 
|  | # RFC 3207: | 
|  | # The client MUST discard any knowledge obtained from | 
|  | # the server, such as the list of SMTP service extensions, | 
|  | # which was not obtained from the TLS negotiation itself. | 
|  | self.helo_resp = None | 
|  | self.ehlo_resp = None | 
|  | self.esmtp_features = {} | 
|  | self.does_esmtp = 0 | 
|  | return (resp, reply) | 
|  |  | 
|  | def sendmail(self, from_addr, to_addrs, msg, mail_options=[], | 
|  | rcpt_options=[]): | 
|  | """This command performs an entire mail transaction. | 
|  |  | 
|  | The arguments are: | 
|  | - from_addr    : The address sending this mail. | 
|  | - to_addrs     : A list of addresses to send this mail to.  A bare | 
|  | string will be treated as a list with 1 address. | 
|  | - msg          : The message to send. | 
|  | - mail_options : List of ESMTP options (such as 8bitmime) for the | 
|  | mail command. | 
|  | - rcpt_options : List of ESMTP options (such as DSN commands) for | 
|  | all the rcpt commands. | 
|  |  | 
|  | If there has been no previous EHLO or HELO command this session, this | 
|  | method tries ESMTP EHLO first.  If the server does ESMTP, message size | 
|  | and each of the specified options will be passed to it.  If EHLO | 
|  | fails, HELO will be tried and ESMTP options suppressed. | 
|  |  | 
|  | This method will return normally if the mail is accepted for at least | 
|  | one recipient.  It returns a dictionary, with one entry for each | 
|  | recipient that was refused.  Each entry contains a tuple of the SMTP | 
|  | error code and the accompanying error message sent by the server. | 
|  |  | 
|  | This method may raise the following exceptions: | 
|  |  | 
|  | SMTPHeloError          The server didn't reply properly to | 
|  | the helo greeting. | 
|  | SMTPRecipientsRefused  The server rejected ALL recipients | 
|  | (no mail was sent). | 
|  | SMTPSenderRefused      The server didn't accept the from_addr. | 
|  | SMTPDataError          The server replied with an unexpected | 
|  | error code (other than a refusal of | 
|  | a recipient). | 
|  |  | 
|  | Note: the connection will be open even after an exception is raised. | 
|  |  | 
|  | Example: | 
|  |  | 
|  | >>> import smtplib | 
|  | >>> s=smtplib.SMTP("localhost") | 
|  | >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] | 
|  | >>> msg = '''\\ | 
|  | ... From: Me@my.org | 
|  | ... Subject: testin'... | 
|  | ... | 
|  | ... This is a test ''' | 
|  | >>> s.sendmail("me@my.org",tolist,msg) | 
|  | { "three@three.org" : ( 550 ,"User unknown" ) } | 
|  | >>> s.quit() | 
|  |  | 
|  | In the above example, the message was accepted for delivery to three | 
|  | of the four addresses, and one was rejected, with the error code | 
|  | 550.  If all addresses are accepted, then the method will return an | 
|  | empty dictionary. | 
|  |  | 
|  | """ | 
|  | self.ehlo_or_helo_if_needed() | 
|  | esmtp_opts = [] | 
|  | if self.does_esmtp: | 
|  | # Hmmm? what's this? -ddm | 
|  | # self.esmtp_features['7bit']="" | 
|  | if self.has_extn('size'): | 
|  | esmtp_opts.append("size=%d" % len(msg)) | 
|  | for option in mail_options: | 
|  | esmtp_opts.append(option) | 
|  |  | 
|  | (code, resp) = self.mail(from_addr, esmtp_opts) | 
|  | if code != 250: | 
|  | self.rset() | 
|  | raise SMTPSenderRefused(code, resp, from_addr) | 
|  | senderrs = {} | 
|  | if isinstance(to_addrs, basestring): | 
|  | to_addrs = [to_addrs] | 
|  | for each in to_addrs: | 
|  | (code, resp) = self.rcpt(each, rcpt_options) | 
|  | if (code != 250) and (code != 251): | 
|  | senderrs[each] = (code, resp) | 
|  | if len(senderrs) == len(to_addrs): | 
|  | # the server refused all our recipients | 
|  | self.rset() | 
|  | raise SMTPRecipientsRefused(senderrs) | 
|  | (code, resp) = self.data(msg) | 
|  | if code != 250: | 
|  | self.rset() | 
|  | raise SMTPDataError(code, resp) | 
|  | #if we got here then somebody got our mail | 
|  | return senderrs | 
|  |  | 
|  |  | 
|  | def close(self): | 
|  | """Close the connection to the SMTP server.""" | 
|  | if self.file: | 
|  | self.file.close() | 
|  | self.file = None | 
|  | if self.sock: | 
|  | self.sock.close() | 
|  | self.sock = None | 
|  |  | 
|  |  | 
|  | def quit(self): | 
|  | """Terminate the SMTP session.""" | 
|  | res = self.docmd("quit") | 
|  | self.close() | 
|  | return res | 
|  |  | 
|  | if _have_ssl: | 
|  |  | 
|  | class SMTP_SSL(SMTP): | 
|  | """ This is a subclass derived from SMTP that connects over an SSL encrypted | 
|  | socket (to use this class you need a socket module that was compiled with SSL | 
|  | support). If host is not specified, '' (the local host) is used. If port is | 
|  | omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile | 
|  | are also optional - they can contain a PEM formatted private key and | 
|  | certificate chain file for the SSL connection. | 
|  | """ | 
|  |  | 
|  | default_port = SMTP_SSL_PORT | 
|  |  | 
|  | def __init__(self, host='', port=0, local_hostname=None, | 
|  | keyfile=None, certfile=None, | 
|  | timeout=socket._GLOBAL_DEFAULT_TIMEOUT): | 
|  | self.keyfile = keyfile | 
|  | self.certfile = certfile | 
|  | SMTP.__init__(self, host, port, local_hostname, timeout) | 
|  |  | 
|  | def _get_socket(self, host, port, timeout): | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'connect:', (host, port) | 
|  | new_socket = socket.create_connection((host, port), timeout) | 
|  | new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) | 
|  | self.file = SSLFakeFile(new_socket) | 
|  | return new_socket | 
|  |  | 
|  | __all__.append("SMTP_SSL") | 
|  |  | 
|  | # | 
|  | # LMTP extension | 
|  | # | 
|  | LMTP_PORT = 2003 | 
|  |  | 
|  | class LMTP(SMTP): | 
|  | """LMTP - Local Mail Transfer Protocol | 
|  |  | 
|  | The LMTP protocol, which is very similar to ESMTP, is heavily based | 
|  | on the standard SMTP client. It's common to use Unix sockets for LMTP, | 
|  | so our connect() method must support that as well as a regular | 
|  | host:port server. To specify a Unix socket, you must use an absolute | 
|  | path as the host, starting with a '/'. | 
|  |  | 
|  | Authentication is supported, using the regular SMTP mechanism. When | 
|  | using a Unix socket, LMTP generally don't support or require any | 
|  | authentication, but your mileage might vary.""" | 
|  |  | 
|  | ehlo_msg = "lhlo" | 
|  |  | 
|  | def __init__(self, host='', port=LMTP_PORT, local_hostname=None): | 
|  | """Initialize a new instance.""" | 
|  | SMTP.__init__(self, host, port, local_hostname) | 
|  |  | 
|  | def connect(self, host='localhost', port=0): | 
|  | """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" | 
|  | if host[0] != '/': | 
|  | return SMTP.connect(self, host, port) | 
|  |  | 
|  | # Handle Unix-domain sockets. | 
|  | try: | 
|  | self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | 
|  | self.sock.connect(host) | 
|  | except socket.error, msg: | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, 'connect fail:', host | 
|  | if self.sock: | 
|  | self.sock.close() | 
|  | self.sock = None | 
|  | raise socket.error, msg | 
|  | (code, msg) = self.getreply() | 
|  | if self.debuglevel > 0: | 
|  | print>>stderr, "connect:", msg | 
|  | return (code, msg) | 
|  |  | 
|  |  | 
|  | # Test the sendmail method, which tests most of the others. | 
|  | # Note: This always sends to localhost. | 
|  | if __name__ == '__main__': | 
|  | import sys | 
|  |  | 
|  | def prompt(prompt): | 
|  | sys.stdout.write(prompt + ": ") | 
|  | return sys.stdin.readline().strip() | 
|  |  | 
|  | fromaddr = prompt("From") | 
|  | toaddrs = prompt("To").split(',') | 
|  | print "Enter message, end with ^D:" | 
|  | msg = '' | 
|  | while 1: | 
|  | line = sys.stdin.readline() | 
|  | if not line: | 
|  | break | 
|  | msg = msg + line | 
|  | print "Message length is %d" % len(msg) | 
|  |  | 
|  | server = SMTP('localhost') | 
|  | server.set_debuglevel(1) | 
|  | server.sendmail(fromaddr, toaddrs, msg) | 
|  | server.quit() |