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