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