Home | History | Annotate | Download | only in Lib
      1 #! /usr/bin/env python
      2 
      3 '''SMTP/ESMTP client class.
      4 
      5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
      6 Authentication) and RFC 2487 (Secure SMTP over TLS).
      7 
      8 Notes:
      9 
     10 Please remember, when doing ESMTP, that the names of the SMTP service
     11 extensions are NOT the same thing as the option keywords for the RCPT
     12 and MAIL commands!
     13 
     14 Example:
     15 
     16   >>> import smtplib
     17   >>> s=smtplib.SMTP("localhost")
     18   >>> print s.help()
     19   This is Sendmail version 8.8.4
     20   Topics:
     21       HELO    EHLO    MAIL    RCPT    DATA
     22       RSET    NOOP    QUIT    HELP    VRFY
     23       EXPN    VERB    ETRN    DSN
     24   For more info use "HELP <topic>".
     25   To report bugs in the implementation send email to
     26       sendmail-bugs (at] sendmail.org.
     27   For local information send email to Postmaster at your site.
     28   End of HELP info
     29   >>> s.putcmd("vrfy","someone@here")
     30   >>> s.getreply()
     31   (250, "Somebody OverHere <somebody (at] here.my.org>")
     32   >>> s.quit()
     33 '''
     34 
     35 # Author: The Dragon De Monsyne <dragondm (at] integral.org>
     36 # ESMTP support, test code and doc fixes added by
     37 #     Eric S. Raymond <esr (at] thyrsus.com>
     38 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
     39 #     by Carey Evans <c.evans (at] clear.net.nz>, for picky mail servers.
     40 # RFC 2554 (authentication) support by Gerhard Haering <gerhard (at] bigfoot.de>.
     41 #
     42 # This was modified from the Python 1.5 library HTTP lib.
     43 
     44 import socket
     45 import re
     46 import email.utils
     47 import base64
     48 import hmac
     49 from email.base64mime import encode as encode_base64
     50 from sys import stderr
     51 
     52 __all__ = ["SMTPException", "SMTPServerDisconnected", "SMTPResponseException",
     53            "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError",
     54            "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError",
     55            "quoteaddr", "quotedata", "SMTP"]
     56 
     57 SMTP_PORT = 25
     58 SMTP_SSL_PORT = 465
     59 CRLF = "\r\n"
     60 _MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
     61 
     62 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
     63 
     64 
     65 # Exception classes used by this module.
     66 class SMTPException(Exception):
     67     """Base class for all exceptions raised by this module."""
     68 
     69 class SMTPServerDisconnected(SMTPException):
     70     """Not connected to any SMTP server.
     71 
     72     This exception is raised when the server unexpectedly disconnects,
     73     or when an attempt is made to use the SMTP instance before
     74     connecting it to a server.
     75     """
     76 
     77 class SMTPResponseException(SMTPException):
     78     """Base class for all exceptions that include an SMTP error code.
     79 
     80     These exceptions are generated in some instances when the SMTP
     81     server returns an error code.  The error code is stored in the
     82     `smtp_code' attribute of the error, and the `smtp_error' attribute
     83     is set to the error message.
     84     """
     85 
     86     def __init__(self, code, msg):
     87         self.smtp_code = code
     88         self.smtp_error = msg
     89         self.args = (code, msg)
     90 
     91 class SMTPSenderRefused(SMTPResponseException):
     92     """Sender address refused.
     93 
     94     In addition to the attributes set by on all SMTPResponseException
     95     exceptions, this sets `sender' to the string that the SMTP refused.
     96     """
     97 
     98     def __init__(self, code, msg, sender):
     99         self.smtp_code = code
    100         self.smtp_error = msg
    101         self.sender = sender
    102         self.args = (code, msg, sender)
    103 
    104 class SMTPRecipientsRefused(SMTPException):
    105     """All recipient addresses refused.
    106 
    107     The errors for each recipient are accessible through the attribute
    108     'recipients', which is a dictionary of exactly the same sort as
    109     SMTP.sendmail() returns.
    110     """
    111 
    112     def __init__(self, recipients):
    113         self.recipients = recipients
    114         self.args = (recipients,)
    115 
    116 
    117 class SMTPDataError(SMTPResponseException):
    118     """The SMTP server didn't accept the data."""
    119 
    120 class SMTPConnectError(SMTPResponseException):
    121     """Error during connection establishment."""
    122 
    123 class SMTPHeloError(SMTPResponseException):
    124     """The server refused our HELO reply."""
    125 
    126 class SMTPAuthenticationError(SMTPResponseException):
    127     """Authentication error.
    128 
    129     Most probably the server didn't accept the username/password
    130     combination provided.
    131     """
    132 
    133 
    134 def quoteaddr(addr):
    135     """Quote a subset of the email addresses defined by RFC 821.
    136 
    137     Should be able to handle anything rfc822.parseaddr can handle.
    138     """
    139     m = (None, None)
    140     try:
    141         m = email.utils.parseaddr(addr)[1]
    142     except AttributeError:
    143         pass
    144     if m == (None, None):  # Indicates parse failure or AttributeError
    145         # something weird here.. punt -ddm
    146         return "<%s>" % addr
    147     elif m is None:
    148         # the sender wants an empty return address
    149         return "<>"
    150     else:
    151         return "<%s>" % m
    152 
    153 def _addr_only(addrstring):
    154     displayname, addr = email.utils.parseaddr(addrstring)
    155     if (displayname, addr) == ('', ''):
    156         # parseaddr couldn't parse it, so use it as is.
    157         return addrstring
    158     return addr
    159 
    160 def quotedata(data):
    161     """Quote data for email.
    162 
    163     Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
    164     Internet CRLF end-of-line.
    165     """
    166     return re.sub(r'(?m)^\.', '..',
    167         re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
    168 
    169 
    170 try:
    171     import ssl
    172 except ImportError:
    173     _have_ssl = False
    174 else:
    175     class SSLFakeFile:
    176         """A fake file like object that really wraps a SSLObject.
    177 
    178         It only supports what is needed in smtplib.
    179         """
    180         def __init__(self, sslobj):
    181             self.sslobj = sslobj
    182 
    183         def readline(self, size=-1):
    184             if size < 0:
    185                 size = None
    186             str = ""
    187             chr = None
    188             while chr != "\n":
    189                 if size is not None and len(str) >= size:
    190                     break
    191                 chr = self.sslobj.read(1)
    192                 if not chr:
    193                     break
    194                 str += chr
    195             return str
    196 
    197         def close(self):
    198             pass
    199 
    200     _have_ssl = True
    201 
    202 class SMTP:
    203     """This class manages a connection to an SMTP or ESMTP server.
    204     SMTP Objects:
    205         SMTP objects have the following attributes:
    206             helo_resp
    207                 This is the message given by the server in response to the
    208                 most recent HELO command.
    209 
    210             ehlo_resp
    211                 This is the message given by the server in response to the
    212                 most recent EHLO command. This is usually multiline.
    213 
    214             does_esmtp
    215                 This is a True value _after you do an EHLO command_, if the
    216                 server supports ESMTP.
    217 
    218             esmtp_features
    219                 This is a dictionary, which, if the server supports ESMTP,
    220                 will _after you do an EHLO command_, contain the names of the
    221                 SMTP service extensions this server supports, and their
    222                 parameters (if any).
    223 
    224                 Note, all extension names are mapped to lower case in the
    225                 dictionary.
    226 
    227         See each method's docstrings for details.  In general, there is a
    228         method of the same name to perform each SMTP command.  There is also a
    229         method called 'sendmail' that will do an entire mail transaction.
    230         """
    231     debuglevel = 0
    232     file = None
    233     helo_resp = None
    234     ehlo_msg = "ehlo"
    235     ehlo_resp = None
    236     does_esmtp = 0
    237     default_port = SMTP_PORT
    238 
    239     def __init__(self, host='', port=0, local_hostname=None,
    240                  timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
    241         """Initialize a new instance.
    242 
    243         If specified, `host' is the name of the remote host to which to
    244         connect.  If specified, `port' specifies the port to which to connect.
    245         By default, smtplib.SMTP_PORT is used.  If a host is specified the
    246         connect method is called, and if it returns anything other than a
    247         success code an SMTPConnectError is raised.  If specified,
    248         `local_hostname` is used as the FQDN of the local host for the
    249         HELO/EHLO command.  Otherwise, the local hostname is found using
    250         socket.getfqdn().
    251 
    252         """
    253         self.timeout = timeout
    254         self.esmtp_features = {}
    255         if host:
    256             (code, msg) = self.connect(host, port)
    257             if code != 220:
    258                 raise SMTPConnectError(code, msg)
    259         if local_hostname is not None:
    260             self.local_hostname = local_hostname
    261         else:
    262             # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
    263             # if that can't be calculated, that we should use a domain literal
    264             # instead (essentially an encoded IP address like [A.B.C.D]).
    265             fqdn = socket.getfqdn()
    266             if '.' in fqdn:
    267                 self.local_hostname = fqdn
    268             else:
    269                 # We can't find an fqdn hostname, so use a domain literal
    270                 addr = '127.0.0.1'
    271                 try:
    272                     addr = socket.gethostbyname(socket.gethostname())
    273                 except socket.gaierror:
    274                     pass
    275                 self.local_hostname = '[%s]' % addr
    276 
    277     def set_debuglevel(self, debuglevel):
    278         """Set the debug output level.
    279 
    280         A non-false value results in debug messages for connection and for all
    281         messages sent to and received from the server.
    282 
    283         """
    284         self.debuglevel = debuglevel
    285 
    286     def _get_socket(self, host, port, timeout):
    287         # This makes it simpler for SMTP_SSL to use the SMTP connect code
    288         # and just alter the socket connection bit.
    289         if self.debuglevel > 0:
    290             print>>stderr, 'connect:', (host, port)
    291         return socket.create_connection((host, port), timeout)
    292 
    293     def connect(self, host='localhost', port=0):
    294         """Connect to a host on a given port.
    295 
    296         If the hostname ends with a colon (`:') followed by a number, and
    297         there is no port specified, that suffix will be stripped off and the
    298         number interpreted as the port number to use.
    299 
    300         Note: This method is automatically invoked by __init__, if a host is
    301         specified during instantiation.
    302 
    303         """
    304         if not port and (host.find(':') == host.rfind(':')):
    305             i = host.rfind(':')
    306             if i >= 0:
    307                 host, port = host[:i], host[i + 1:]
    308                 try:
    309                     port = int(port)
    310                 except ValueError:
    311                     raise socket.error, "nonnumeric port"
    312         if not port:
    313             port = self.default_port
    314         if self.debuglevel > 0:
    315             print>>stderr, 'connect:', (host, port)
    316         self.sock = self._get_socket(host, port, self.timeout)
    317         (code, msg) = self.getreply()
    318         if self.debuglevel > 0:
    319             print>>stderr, "connect:", msg
    320         return (code, msg)
    321 
    322     def send(self, str):
    323         """Send `str' to the server."""
    324         if self.debuglevel > 0:
    325             print>>stderr, 'send:', repr(str)
    326         if hasattr(self, 'sock') and self.sock:
    327             try:
    328                 self.sock.sendall(str)
    329             except socket.error:
    330                 self.close()
    331                 raise SMTPServerDisconnected('Server not connected')
    332         else:
    333             raise SMTPServerDisconnected('please run connect() first')
    334 
    335     def putcmd(self, cmd, args=""):
    336         """Send a command to the server."""
    337         if args == "":
    338             str = '%s%s' % (cmd, CRLF)
    339         else:
    340             str = '%s %s%s' % (cmd, args, CRLF)
    341         self.send(str)
    342 
    343     def getreply(self):
    344         """Get a reply from the server.
    345 
    346         Returns a tuple consisting of:
    347 
    348           - server response code (e.g. '250', or such, if all goes well)
    349             Note: returns -1 if it can't read response code.
    350 
    351           - server response string corresponding to response code (multiline
    352             responses are converted to a single, multiline string).
    353 
    354         Raises SMTPServerDisconnected if end-of-file is reached.
    355         """
    356         resp = []
    357         if self.file is None:
    358             self.file = self.sock.makefile('rb')
    359         while 1:
    360             try:
    361                 line = self.file.readline(_MAXLINE + 1)
    362             except socket.error as e:
    363                 self.close()
    364                 raise SMTPServerDisconnected("Connection unexpectedly closed: "
    365                                              + str(e))
    366             if line == '':
    367                 self.close()
    368                 raise SMTPServerDisconnected("Connection unexpectedly closed")
    369             if self.debuglevel > 0:
    370                 print>>stderr, 'reply:', repr(line)
    371             if len(line) > _MAXLINE:
    372                 raise SMTPResponseException(500, "Line too long.")
    373             resp.append(line[4:].strip())
    374             code = line[:3]
    375             # Check that the error code is syntactically correct.
    376             # Don't attempt to read a continuation line if it is broken.
    377             try:
    378                 errcode = int(code)
    379             except ValueError:
    380                 errcode = -1
    381                 break
    382             # Check if multiline response.
    383             if line[3:4] != "-":
    384                 break
    385 
    386         errmsg = "\n".join(resp)
    387         if self.debuglevel > 0:
    388             print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg)
    389         return errcode, errmsg
    390 
    391     def docmd(self, cmd, args=""):
    392         """Send a command, and return its response code."""
    393         self.putcmd(cmd, args)
    394         return self.getreply()
    395 
    396     # std smtp commands
    397     def helo(self, name=''):
    398         """SMTP 'helo' command.
    399         Hostname to send for this command defaults to the FQDN of the local
    400         host.
    401         """
    402         self.putcmd("helo", name or self.local_hostname)
    403         (code, msg) = self.getreply()
    404         self.helo_resp = msg
    405         return (code, msg)
    406 
    407     def ehlo(self, name=''):
    408         """ SMTP 'ehlo' command.
    409         Hostname to send for this command defaults to the FQDN of the local
    410         host.
    411         """
    412         self.esmtp_features = {}
    413         self.putcmd(self.ehlo_msg, name or self.local_hostname)
    414         (code, msg) = self.getreply()
    415         # According to RFC1869 some (badly written)
    416         # MTA's will disconnect on an ehlo. Toss an exception if
    417         # that happens -ddm
    418         if code == -1 and len(msg) == 0:
    419             self.close()
    420             raise SMTPServerDisconnected("Server not connected")
    421         self.ehlo_resp = msg
    422         if code != 250:
    423             return (code, msg)
    424         self.does_esmtp = 1
    425         #parse the ehlo response -ddm
    426         resp = self.ehlo_resp.split('\n')
    427         del resp[0]
    428         for each in resp:
    429             # To be able to communicate with as many SMTP servers as possible,
    430             # we have to take the old-style auth advertisement into account,
    431             # because:
    432             # 1) Else our SMTP feature parser gets confused.
    433             # 2) There are some servers that only advertise the auth methods we
    434             #    support using the old style.
    435             auth_match = OLDSTYLE_AUTH.match(each)
    436             if auth_match:
    437                 # This doesn't remove duplicates, but that's no problem
    438                 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
    439                         + " " + auth_match.groups(0)[0]
    440                 continue
    441 
    442             # RFC 1869 requires a space between ehlo keyword and parameters.
    443             # It's actually stricter, in that only spaces are allowed between
    444             # parameters, but were not going to check for that here.  Note
    445             # that the space isn't present if there are no parameters.
    446             m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
    447             if m:
    448                 feature = m.group("feature").lower()
    449                 params = m.string[m.end("feature"):].strip()
    450                 if feature == "auth":
    451                     self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
    452                             + " " + params
    453                 else:
    454                     self.esmtp_features[feature] = params
    455         return (code, msg)
    456 
    457     def has_extn(self, opt):
    458         """Does the server support a given SMTP service extension?"""
    459         return opt.lower() in self.esmtp_features
    460 
    461     def help(self, args=''):
    462         """SMTP 'help' command.
    463         Returns help text from server."""
    464         self.putcmd("help", args)
    465         return self.getreply()[1]
    466 
    467     def rset(self):
    468         """SMTP 'rset' command -- resets session."""
    469         return self.docmd("rset")
    470 
    471     def noop(self):
    472         """SMTP 'noop' command -- doesn't do anything :>"""
    473         return self.docmd("noop")
    474 
    475     def mail(self, sender, options=[]):
    476         """SMTP 'mail' command -- begins mail xfer session."""
    477         optionlist = ''
    478         if options and self.does_esmtp:
    479             optionlist = ' ' + ' '.join(options)
    480         self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
    481         return self.getreply()
    482 
    483     def rcpt(self, recip, options=[]):
    484         """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
    485         optionlist = ''
    486         if options and self.does_esmtp:
    487             optionlist = ' ' + ' '.join(options)
    488         self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist))
    489         return self.getreply()
    490 
    491     def data(self, msg):
    492         """SMTP 'DATA' command -- sends message data to server.
    493 
    494         Automatically quotes lines beginning with a period per rfc821.
    495         Raises SMTPDataError if there is an unexpected reply to the
    496         DATA command; the return value from this method is the final
    497         response code received when the all data is sent.
    498         """
    499         self.putcmd("data")
    500         (code, repl) = self.getreply()
    501         if self.debuglevel > 0:
    502             print>>stderr, "data:", (code, repl)
    503         if code != 354:
    504             raise SMTPDataError(code, repl)
    505         else:
    506             q = quotedata(msg)
    507             if q[-2:] != CRLF:
    508                 q = q + CRLF
    509             q = q + "." + CRLF
    510             self.send(q)
    511             (code, msg) = self.getreply()
    512             if self.debuglevel > 0:
    513                 print>>stderr, "data:", (code, msg)
    514             return (code, msg)
    515 
    516     def verify(self, address):
    517         """SMTP 'verify' command -- checks for address validity."""
    518         self.putcmd("vrfy", _addr_only(address))
    519         return self.getreply()
    520     # a.k.a.
    521     vrfy = verify
    522 
    523     def expn(self, address):
    524         """SMTP 'expn' command -- expands a mailing list."""
    525         self.putcmd("expn", _addr_only(address))
    526         return self.getreply()
    527 
    528     # some useful methods
    529 
    530     def ehlo_or_helo_if_needed(self):
    531         """Call self.ehlo() and/or self.helo() if needed.
    532 
    533         If there has been no previous EHLO or HELO command this session, this
    534         method tries ESMTP EHLO first.
    535 
    536         This method may raise the following exceptions:
    537 
    538          SMTPHeloError            The server didn't reply properly to
    539                                   the helo greeting.
    540         """
    541         if self.helo_resp is None and self.ehlo_resp is None:
    542             if not (200 <= self.ehlo()[0] <= 299):
    543                 (code, resp) = self.helo()
    544                 if not (200 <= code <= 299):
    545                     raise SMTPHeloError(code, resp)
    546 
    547     def login(self, user, password):
    548         """Log in on an SMTP server that requires authentication.
    549 
    550         The arguments are:
    551             - user:     The user name to authenticate with.
    552             - password: The password for the authentication.
    553 
    554         If there has been no previous EHLO or HELO command this session, this
    555         method tries ESMTP EHLO first.
    556 
    557         This method will return normally if the authentication was successful.
    558 
    559         This method may raise the following exceptions:
    560 
    561          SMTPHeloError            The server didn't reply properly to
    562                                   the helo greeting.
    563          SMTPAuthenticationError  The server didn't accept the username/
    564                                   password combination.
    565          SMTPException            No suitable authentication method was
    566                                   found.
    567         """
    568 
    569         def encode_cram_md5(challenge, user, password):
    570             challenge = base64.decodestring(challenge)
    571             response = user + " " + hmac.HMAC(password, challenge).hexdigest()
    572             return encode_base64(response, eol="")
    573 
    574         def encode_plain(user, password):
    575             return encode_base64("\0%s\0%s" % (user, password), eol="")
    576 
    577 
    578         AUTH_PLAIN = "PLAIN"
    579         AUTH_CRAM_MD5 = "CRAM-MD5"
    580         AUTH_LOGIN = "LOGIN"
    581 
    582         self.ehlo_or_helo_if_needed()
    583 
    584         if not self.has_extn("auth"):
    585             raise SMTPException("SMTP AUTH extension not supported by server.")
    586 
    587         # Authentication methods the server supports:
    588         authlist = self.esmtp_features["auth"].split()
    589 
    590         # List of authentication methods we support: from preferred to
    591         # less preferred methods. Except for the purpose of testing the weaker
    592         # ones, we prefer stronger methods like CRAM-MD5:
    593         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
    594 
    595         # Determine the authentication method we'll use
    596         authmethod = None
    597         for method in preferred_auths:
    598             if method in authlist:
    599                 authmethod = method
    600                 break
    601 
    602         if authmethod == AUTH_CRAM_MD5:
    603             (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
    604             if code == 503:
    605                 # 503 == 'Error: already authenticated'
    606                 return (code, resp)
    607             (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
    608         elif authmethod == AUTH_PLAIN:
    609             (code, resp) = self.docmd("AUTH",
    610                 AUTH_PLAIN + " " + encode_plain(user, password))
    611         elif authmethod == AUTH_LOGIN:
    612             (code, resp) = self.docmd("AUTH",
    613                 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
    614             if code != 334:
    615                 raise SMTPAuthenticationError(code, resp)
    616             (code, resp) = self.docmd(encode_base64(password, eol=""))
    617         elif authmethod is None:
    618             raise SMTPException("No suitable authentication method found.")
    619         if code not in (235, 503):
    620             # 235 == 'Authentication successful'
    621             # 503 == 'Error: already authenticated'
    622             raise SMTPAuthenticationError(code, resp)
    623         return (code, resp)
    624 
    625     def starttls(self, keyfile=None, certfile=None):
    626         """Puts the connection to the SMTP server into TLS mode.
    627 
    628         If there has been no previous EHLO or HELO command this session, this
    629         method tries ESMTP EHLO first.
    630 
    631         If the server supports TLS, this will encrypt the rest of the SMTP
    632         session. If you provide the keyfile and certfile parameters,
    633         the identity of the SMTP server and client can be checked. This,
    634         however, depends on whether the socket module really checks the
    635         certificates.
    636 
    637         This method may raise the following exceptions:
    638 
    639          SMTPHeloError            The server didn't reply properly to
    640                                   the helo greeting.
    641         """
    642         self.ehlo_or_helo_if_needed()
    643         if not self.has_extn("starttls"):
    644             raise SMTPException("STARTTLS extension not supported by server.")
    645         (resp, reply) = self.docmd("STARTTLS")
    646         if resp == 220:
    647             if not _have_ssl:
    648                 raise RuntimeError("No SSL support included in this Python")
    649             self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
    650             self.file = SSLFakeFile(self.sock)
    651             # RFC 3207:
    652             # The client MUST discard any knowledge obtained from
    653             # the server, such as the list of SMTP service extensions,
    654             # which was not obtained from the TLS negotiation itself.
    655             self.helo_resp = None
    656             self.ehlo_resp = None
    657             self.esmtp_features = {}
    658             self.does_esmtp = 0
    659         else:
    660             # RFC 3207:
    661             # 501 Syntax error (no parameters allowed)
    662             # 454 TLS not available due to temporary reason
    663             raise SMTPResponseException(resp, reply)
    664         return (resp, reply)
    665 
    666     def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
    667                  rcpt_options=[]):
    668         """This command performs an entire mail transaction.
    669 
    670         The arguments are:
    671             - from_addr    : The address sending this mail.
    672             - to_addrs     : A list of addresses to send this mail to.  A bare
    673                              string will be treated as a list with 1 address.
    674             - msg          : The message to send.
    675             - mail_options : List of ESMTP options (such as 8bitmime) for the
    676                              mail command.
    677             - rcpt_options : List of ESMTP options (such as DSN commands) for
    678                              all the rcpt commands.
    679 
    680         If there has been no previous EHLO or HELO command this session, this
    681         method tries ESMTP EHLO first.  If the server does ESMTP, message size
    682         and each of the specified options will be passed to it.  If EHLO
    683         fails, HELO will be tried and ESMTP options suppressed.
    684 
    685         This method will return normally if the mail is accepted for at least
    686         one recipient.  It returns a dictionary, with one entry for each
    687         recipient that was refused.  Each entry contains a tuple of the SMTP
    688         error code and the accompanying error message sent by the server.
    689 
    690         This method may raise the following exceptions:
    691 
    692          SMTPHeloError          The server didn't reply properly to
    693                                 the helo greeting.
    694          SMTPRecipientsRefused  The server rejected ALL recipients
    695                                 (no mail was sent).
    696          SMTPSenderRefused      The server didn't accept the from_addr.
    697          SMTPDataError          The server replied with an unexpected
    698                                 error code (other than a refusal of
    699                                 a recipient).
    700 
    701         Note: the connection will be open even after an exception is raised.
    702 
    703         Example:
    704 
    705          >>> import smtplib
    706          >>> s=smtplib.SMTP("localhost")
    707          >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
    708          >>> msg = '''\\
    709          ... From: Me (at] my.org
    710          ... Subject: testin'...
    711          ...
    712          ... This is a test '''
    713          >>> s.sendmail("me@my.org",tolist,msg)
    714          { "three@three.org" : ( 550 ,"User unknown" ) }
    715          >>> s.quit()
    716 
    717         In the above example, the message was accepted for delivery to three
    718         of the four addresses, and one was rejected, with the error code
    719         550.  If all addresses are accepted, then the method will return an
    720         empty dictionary.
    721 
    722         """
    723         self.ehlo_or_helo_if_needed()
    724         esmtp_opts = []
    725         if self.does_esmtp:
    726             # Hmmm? what's this? -ddm
    727             # self.esmtp_features['7bit']=""
    728             if self.has_extn('size'):
    729                 esmtp_opts.append("size=%d" % len(msg))
    730             for option in mail_options:
    731                 esmtp_opts.append(option)
    732 
    733         (code, resp) = self.mail(from_addr, esmtp_opts)
    734         if code != 250:
    735             self.rset()
    736             raise SMTPSenderRefused(code, resp, from_addr)
    737         senderrs = {}
    738         if isinstance(to_addrs, basestring):
    739             to_addrs = [to_addrs]
    740         for each in to_addrs:
    741             (code, resp) = self.rcpt(each, rcpt_options)
    742             if (code != 250) and (code != 251):
    743                 senderrs[each] = (code, resp)
    744         if len(senderrs) == len(to_addrs):
    745             # the server refused all our recipients
    746             self.rset()
    747             raise SMTPRecipientsRefused(senderrs)
    748         (code, resp) = self.data(msg)
    749         if code != 250:
    750             self.rset()
    751             raise SMTPDataError(code, resp)
    752         #if we got here then somebody got our mail
    753         return senderrs
    754 
    755 
    756     def close(self):
    757         """Close the connection to the SMTP server."""
    758         try:
    759             file = self.file
    760             self.file = None
    761             if file:
    762                 file.close()
    763         finally:
    764             sock = self.sock
    765             self.sock = None
    766             if sock:
    767                 sock.close()
    768 
    769 
    770     def quit(self):
    771         """Terminate the SMTP session."""
    772         res = self.docmd("quit")
    773         # A new EHLO is required after reconnecting with connect()
    774         self.ehlo_resp = self.helo_resp = None
    775         self.esmtp_features = {}
    776         self.does_esmtp = False
    777         self.close()
    778         return res
    779 
    780 if _have_ssl:
    781 
    782     class SMTP_SSL(SMTP):
    783         """ This is a subclass derived from SMTP that connects over an SSL
    784         encrypted socket (to use this class you need a socket module that was
    785         compiled with SSL support). If host is not specified, '' (the local
    786         host) is used. If port is omitted, the standard SMTP-over-SSL port
    787         (465) is used.  local_hostname has the same meaning as it does in the
    788         SMTP class.  keyfile and certfile are also optional - they can contain
    789         a PEM formatted private key and certificate chain file for the SSL
    790         connection.
    791 
    792         """
    793 
    794         default_port = SMTP_SSL_PORT
    795 
    796         def __init__(self, host='', port=0, local_hostname=None,
    797                      keyfile=None, certfile=None,
    798                      timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
    799             self.keyfile = keyfile
    800             self.certfile = certfile
    801             SMTP.__init__(self, host, port, local_hostname, timeout)
    802 
    803         def _get_socket(self, host, port, timeout):
    804             if self.debuglevel > 0:
    805                 print>>stderr, 'connect:', (host, port)
    806             new_socket = socket.create_connection((host, port), timeout)
    807             new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
    808             self.file = SSLFakeFile(new_socket)
    809             return new_socket
    810 
    811     __all__.append("SMTP_SSL")
    812 
    813 #
    814 # LMTP extension
    815 #
    816 LMTP_PORT = 2003
    817 
    818 class LMTP(SMTP):
    819     """LMTP - Local Mail Transfer Protocol
    820 
    821     The LMTP protocol, which is very similar to ESMTP, is heavily based
    822     on the standard SMTP client. It's common to use Unix sockets for
    823     LMTP, so our connect() method must support that as well as a regular
    824     host:port server.  local_hostname has the same meaning as it does in
    825     the SMTP class.  To specify a Unix socket, you must use an absolute
    826     path as the host, starting with a '/'.
    827 
    828     Authentication is supported, using the regular SMTP mechanism. When
    829     using a Unix socket, LMTP generally don't support or require any
    830     authentication, but your mileage might vary."""
    831 
    832     ehlo_msg = "lhlo"
    833 
    834     def __init__(self, host='', port=LMTP_PORT, local_hostname=None):
    835         """Initialize a new instance."""
    836         SMTP.__init__(self, host, port, local_hostname)
    837 
    838     def connect(self, host='localhost', port=0):
    839         """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
    840         if host[0] != '/':
    841             return SMTP.connect(self, host, port)
    842 
    843         # Handle Unix-domain sockets.
    844         try:
    845             self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    846             self.sock.connect(host)
    847         except socket.error:
    848             if self.debuglevel > 0:
    849                 print>>stderr, 'connect fail:', host
    850             if self.sock:
    851                 self.sock.close()
    852             self.sock = None
    853             raise
    854         (code, msg) = self.getreply()
    855         if self.debuglevel > 0:
    856             print>>stderr, "connect:", msg
    857         return (code, msg)
    858 
    859 
    860 # Test the sendmail method, which tests most of the others.
    861 # Note: This always sends to localhost.
    862 if __name__ == '__main__':
    863     import sys
    864 
    865     def prompt(prompt):
    866         sys.stdout.write(prompt + ": ")
    867         return sys.stdin.readline().strip()
    868 
    869     fromaddr = prompt("From")
    870     toaddrs = prompt("To").split(',')
    871     print "Enter message, end with ^D:"
    872     msg = ''
    873     while 1:
    874         line = sys.stdin.readline()
    875         if not line:
    876             break
    877         msg = msg + line
    878     print "Message length is %d" % len(msg)
    879 
    880     server = SMTP('localhost')
    881     server.set_debuglevel(1)
    882     server.sendmail(fromaddr, toaddrs, msg)
    883     server.quit()
    884