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 
     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 quotedata(data):
    153     """Quote data for email.
    154 
    155     Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
    156     Internet CRLF end-of-line.
    157     """
    158     return re.sub(r'(?m)^\.', '..',
    159         re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
    160 
    161 
    162 try:
    163     import ssl
    164 except ImportError:
    165     _have_ssl = False
    166 else:
    167     class SSLFakeFile:
    168         """A fake file like object that really wraps a SSLObject.
    169 
    170         It only supports what is needed in smtplib.
    171         """
    172         def __init__(self, sslobj):
    173             self.sslobj = sslobj
    174 
    175         def readline(self):
    176             str = ""
    177             chr = None
    178             while chr != "\n":
    179                 chr = self.sslobj.read(1)
    180                 if not chr:
    181                     break
    182                 str += chr
    183             return str
    184 
    185         def close(self):
    186             pass
    187 
    188     _have_ssl = True
    189 
    190 class SMTP:
    191     """This class manages a connection to an SMTP or ESMTP server.
    192     SMTP Objects:
    193         SMTP objects have the following attributes:
    194             helo_resp
    195                 This is the message given by the server in response to the
    196                 most recent HELO command.
    197 
    198             ehlo_resp
    199                 This is the message given by the server in response to the
    200                 most recent EHLO command. This is usually multiline.
    201 
    202             does_esmtp
    203                 This is a True value _after you do an EHLO command_, if the
    204                 server supports ESMTP.
    205 
    206             esmtp_features
    207                 This is a dictionary, which, if the server supports ESMTP,
    208                 will _after you do an EHLO command_, contain the names of the
    209                 SMTP service extensions this server supports, and their
    210                 parameters (if any).
    211 
    212                 Note, all extension names are mapped to lower case in the
    213                 dictionary.
    214 
    215         See each method's docstrings for details.  In general, there is a
    216         method of the same name to perform each SMTP command.  There is also a
    217         method called 'sendmail' that will do an entire mail transaction.
    218         """
    219     debuglevel = 0
    220     file = None
    221     helo_resp = None
    222     ehlo_msg = "ehlo"
    223     ehlo_resp = None
    224     does_esmtp = 0
    225     default_port = SMTP_PORT
    226 
    227     def __init__(self, host='', port=0, local_hostname=None,
    228                  timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
    229         """Initialize a new instance.
    230 
    231         If specified, `host' is the name of the remote host to which to
    232         connect.  If specified, `port' specifies the port to which to connect.
    233         By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
    234         if the specified `host' doesn't respond correctly.  If specified,
    235         `local_hostname` is used as the FQDN of the local host.  By default,
    236         the local hostname is found using socket.getfqdn().
    237 
    238         """
    239         self.timeout = timeout
    240         self.esmtp_features = {}
    241         if host:
    242             (code, msg) = self.connect(host, port)
    243             if code != 220:
    244                 raise SMTPConnectError(code, msg)
    245         if local_hostname is not None:
    246             self.local_hostname = local_hostname
    247         else:
    248             # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and

    249             # if that can't be calculated, that we should use a domain literal

    250             # instead (essentially an encoded IP address like [A.B.C.D]).

    251             fqdn = socket.getfqdn()
    252             if '.' in fqdn:
    253                 self.local_hostname = fqdn
    254             else:
    255                 # We can't find an fqdn hostname, so use a domain literal

    256                 addr = '127.0.0.1'
    257                 try:
    258                     addr = socket.gethostbyname(socket.gethostname())
    259                 except socket.gaierror:
    260                     pass
    261                 self.local_hostname = '[%s]' % addr
    262 
    263     def set_debuglevel(self, debuglevel):
    264         """Set the debug output level.
    265 
    266         A non-false value results in debug messages for connection and for all
    267         messages sent to and received from the server.
    268 
    269         """
    270         self.debuglevel = debuglevel
    271 
    272     def _get_socket(self, port, host, timeout):
    273         # This makes it simpler for SMTP_SSL to use the SMTP connect code

    274         # and just alter the socket connection bit.

    275         if self.debuglevel > 0:
    276             print>>stderr, 'connect:', (host, port)
    277         return socket.create_connection((port, host), timeout)
    278 
    279     def connect(self, host='localhost', port=0):
    280         """Connect to a host on a given port.
    281 
    282         If the hostname ends with a colon (`:') followed by a number, and
    283         there is no port specified, that suffix will be stripped off and the
    284         number interpreted as the port number to use.
    285 
    286         Note: This method is automatically invoked by __init__, if a host is
    287         specified during instantiation.
    288 
    289         """
    290         if not port and (host.find(':') == host.rfind(':')):
    291             i = host.rfind(':')
    292             if i >= 0:
    293                 host, port = host[:i], host[i + 1:]
    294                 try:
    295                     port = int(port)
    296                 except ValueError:
    297                     raise socket.error, "nonnumeric port"
    298         if not port:
    299             port = self.default_port
    300         if self.debuglevel > 0:
    301             print>>stderr, 'connect:', (host, port)
    302         self.sock = self._get_socket(host, port, self.timeout)
    303         (code, msg) = self.getreply()
    304         if self.debuglevel > 0:
    305             print>>stderr, "connect:", msg
    306         return (code, msg)
    307 
    308     def send(self, str):
    309         """Send `str' to the server."""
    310         if self.debuglevel > 0:
    311             print>>stderr, 'send:', repr(str)
    312         if hasattr(self, 'sock') and self.sock:
    313             try:
    314                 self.sock.sendall(str)
    315             except socket.error:
    316                 self.close()
    317                 raise SMTPServerDisconnected('Server not connected')
    318         else:
    319             raise SMTPServerDisconnected('please run connect() first')
    320 
    321     def putcmd(self, cmd, args=""):
    322         """Send a command to the server."""
    323         if args == "":
    324             str = '%s%s' % (cmd, CRLF)
    325         else:
    326             str = '%s %s%s' % (cmd, args, CRLF)
    327         self.send(str)
    328 
    329     def getreply(self):
    330         """Get a reply from the server.
    331 
    332         Returns a tuple consisting of:
    333 
    334           - server response code (e.g. '250', or such, if all goes well)
    335             Note: returns -1 if it can't read response code.
    336 
    337           - server response string corresponding to response code (multiline
    338             responses are converted to a single, multiline string).
    339 
    340         Raises SMTPServerDisconnected if end-of-file is reached.
    341         """
    342         resp = []
    343         if self.file is None:
    344             self.file = self.sock.makefile('rb')
    345         while 1:
    346             try:
    347                 line = self.file.readline()
    348             except socket.error:
    349                 line = ''
    350             if line == '':
    351                 self.close()
    352                 raise SMTPServerDisconnected("Connection unexpectedly closed")
    353             if self.debuglevel > 0:
    354                 print>>stderr, 'reply:', repr(line)
    355             resp.append(line[4:].strip())
    356             code = line[:3]
    357             # Check that the error code is syntactically correct.
    358             # Don't attempt to read a continuation line if it is broken.
    359             try:
    360                 errcode = int(code)
    361             except ValueError:
    362                 errcode = -1
    363                 break
    364             # Check if multiline response.

    365             if line[3:4] != "-":
    366                 break
    367 
    368         errmsg = "\n".join(resp)
    369         if self.debuglevel > 0:
    370             print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode, errmsg)
    371         return errcode, errmsg
    372 
    373     def docmd(self, cmd, args=""):
    374         """Send a command, and return its response code."""
    375         self.putcmd(cmd, args)
    376         return self.getreply()
    377 
    378     # std smtp commands

    379     def helo(self, name=''):
    380         """SMTP 'helo' command.
    381         Hostname to send for this command defaults to the FQDN of the local
    382         host.
    383         """
    384         self.putcmd("helo", name or self.local_hostname)
    385         (code, msg) = self.getreply()
    386         self.helo_resp = msg
    387         return (code, msg)
    388 
    389     def ehlo(self, name=''):
    390         """ SMTP 'ehlo' command.
    391         Hostname to send for this command defaults to the FQDN of the local
    392         host.
    393         """
    394         self.esmtp_features = {}
    395         self.putcmd(self.ehlo_msg, name or self.local_hostname)
    396         (code, msg) = self.getreply()
    397         # According to RFC1869 some (badly written)

    398         # MTA's will disconnect on an ehlo. Toss an exception if

    399         # that happens -ddm

    400         if code == -1 and len(msg) == 0:
    401             self.close()
    402             raise SMTPServerDisconnected("Server not connected")
    403         self.ehlo_resp = msg
    404         if code != 250:
    405             return (code, msg)
    406         self.does_esmtp = 1
    407         #parse the ehlo response -ddm

    408         resp = self.ehlo_resp.split('\n')
    409         del resp[0]
    410         for each in resp:
    411             # To be able to communicate with as many SMTP servers as possible,

    412             # we have to take the old-style auth advertisement into account,

    413             # because:

    414             # 1) Else our SMTP feature parser gets confused.

    415             # 2) There are some servers that only advertise the auth methods we

    416             #    support using the old style.

    417             auth_match = OLDSTYLE_AUTH.match(each)
    418             if auth_match:
    419                 # This doesn't remove duplicates, but that's no problem

    420                 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
    421                         + " " + auth_match.groups(0)[0]
    422                 continue
    423 
    424             # RFC 1869 requires a space between ehlo keyword and parameters.

    425             # It's actually stricter, in that only spaces are allowed between

    426             # parameters, but were not going to check for that here.  Note

    427             # that the space isn't present if there are no parameters.

    428             m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each)
    429             if m:
    430                 feature = m.group("feature").lower()
    431                 params = m.string[m.end("feature"):].strip()
    432                 if feature == "auth":
    433                     self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
    434                             + " " + params
    435                 else:
    436                     self.esmtp_features[feature] = params
    437         return (code, msg)
    438 
    439     def has_extn(self, opt):
    440         """Does the server support a given SMTP service extension?"""
    441         return opt.lower() in self.esmtp_features
    442 
    443     def help(self, args=''):
    444         """SMTP 'help' command.
    445         Returns help text from server."""
    446         self.putcmd("help", args)
    447         return self.getreply()[1]
    448 
    449     def rset(self):
    450         """SMTP 'rset' command -- resets session."""
    451         return self.docmd("rset")
    452 
    453     def noop(self):
    454         """SMTP 'noop' command -- doesn't do anything :>"""
    455         return self.docmd("noop")
    456 
    457     def mail(self, sender, options=[]):
    458         """SMTP 'mail' command -- begins mail xfer session."""
    459         optionlist = ''
    460         if options and self.does_esmtp:
    461             optionlist = ' ' + ' '.join(options)
    462         self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
    463         return self.getreply()
    464 
    465     def rcpt(self, recip, options=[]):
    466         """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
    467         optionlist = ''
    468         if options and self.does_esmtp:
    469             optionlist = ' ' + ' '.join(options)
    470         self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist))
    471         return self.getreply()
    472 
    473     def data(self, msg):
    474         """SMTP 'DATA' command -- sends message data to server.
    475 
    476         Automatically quotes lines beginning with a period per rfc821.
    477         Raises SMTPDataError if there is an unexpected reply to the
    478         DATA command; the return value from this method is the final
    479         response code received when the all data is sent.
    480         """
    481         self.putcmd("data")
    482         (code, repl) = self.getreply()
    483         if self.debuglevel > 0:
    484             print>>stderr, "data:", (code, repl)
    485         if code != 354:
    486             raise SMTPDataError(code, repl)
    487         else:
    488             q = quotedata(msg)
    489             if q[-2:] != CRLF:
    490                 q = q + CRLF
    491             q = q + "." + CRLF
    492             self.send(q)
    493             (code, msg) = self.getreply()
    494             if self.debuglevel > 0:
    495                 print>>stderr, "data:", (code, msg)
    496             return (code, msg)
    497 
    498     def verify(self, address):
    499         """SMTP 'verify' command -- checks for address validity."""
    500         self.putcmd("vrfy", quoteaddr(address))
    501         return self.getreply()
    502     # a.k.a.

    503     vrfy = verify
    504 
    505     def expn(self, address):
    506         """SMTP 'expn' command -- expands a mailing list."""
    507         self.putcmd("expn", quoteaddr(address))
    508         return self.getreply()
    509 
    510     # some useful methods

    511 
    512     def ehlo_or_helo_if_needed(self):
    513         """Call self.ehlo() and/or self.helo() if needed.
    514 
    515         If there has been no previous EHLO or HELO command this session, this
    516         method tries ESMTP EHLO first.
    517 
    518         This method may raise the following exceptions:
    519 
    520          SMTPHeloError            The server didn't reply properly to
    521                                   the helo greeting.
    522         """
    523         if self.helo_resp is None and self.ehlo_resp is None:
    524             if not (200 <= self.ehlo()[0] <= 299):
    525                 (code, resp) = self.helo()
    526                 if not (200 <= code <= 299):
    527                     raise SMTPHeloError(code, resp)
    528 
    529     def login(self, user, password):
    530         """Log in on an SMTP server that requires authentication.
    531 
    532         The arguments are:
    533             - user:     The user name to authenticate with.
    534             - password: The password for the authentication.
    535 
    536         If there has been no previous EHLO or HELO command this session, this
    537         method tries ESMTP EHLO first.
    538 
    539         This method will return normally if the authentication was successful.
    540 
    541         This method may raise the following exceptions:
    542 
    543          SMTPHeloError            The server didn't reply properly to
    544                                   the helo greeting.
    545          SMTPAuthenticationError  The server didn't accept the username/
    546                                   password combination.
    547          SMTPException            No suitable authentication method was
    548                                   found.
    549         """
    550 
    551         def encode_cram_md5(challenge, user, password):
    552             challenge = base64.decodestring(challenge)
    553             response = user + " " + hmac.HMAC(password, challenge).hexdigest()
    554             return encode_base64(response, eol="")
    555 
    556         def encode_plain(user, password):
    557             return encode_base64("\0%s\0%s" % (user, password), eol="")
    558 
    559 
    560         AUTH_PLAIN = "PLAIN"
    561         AUTH_CRAM_MD5 = "CRAM-MD5"
    562         AUTH_LOGIN = "LOGIN"
    563 
    564         self.ehlo_or_helo_if_needed()
    565 
    566         if not self.has_extn("auth"):
    567             raise SMTPException("SMTP AUTH extension not supported by server.")
    568 
    569         # Authentication methods the server supports:

    570         authlist = self.esmtp_features["auth"].split()
    571 
    572         # List of authentication methods we support: from preferred to

    573         # less preferred methods. Except for the purpose of testing the weaker

    574         # ones, we prefer stronger methods like CRAM-MD5:

    575         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
    576 
    577         # Determine the authentication method we'll use

    578         authmethod = None
    579         for method in preferred_auths:
    580             if method in authlist:
    581                 authmethod = method
    582                 break
    583 
    584         if authmethod == AUTH_CRAM_MD5:
    585             (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
    586             if code == 503:
    587                 # 503 == 'Error: already authenticated'

    588                 return (code, resp)
    589             (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
    590         elif authmethod == AUTH_PLAIN:
    591             (code, resp) = self.docmd("AUTH",
    592                 AUTH_PLAIN + " " + encode_plain(user, password))
    593         elif authmethod == AUTH_LOGIN:
    594             (code, resp) = self.docmd("AUTH",
    595                 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
    596             if code != 334:
    597                 raise SMTPAuthenticationError(code, resp)
    598             (code, resp) = self.docmd(encode_base64(password, eol=""))
    599         elif authmethod is None:
    600             raise SMTPException("No suitable authentication method found.")
    601         if code not in (235, 503):
    602             # 235 == 'Authentication successful'

    603             # 503 == 'Error: already authenticated'

    604             raise SMTPAuthenticationError(code, resp)
    605         return (code, resp)
    606 
    607     def starttls(self, keyfile=None, certfile=None):
    608         """Puts the connection to the SMTP server into TLS mode.
    609 
    610         If there has been no previous EHLO or HELO command this session, this
    611         method tries ESMTP EHLO first.
    612 
    613         If the server supports TLS, this will encrypt the rest of the SMTP
    614         session. If you provide the keyfile and certfile parameters,
    615         the identity of the SMTP server and client can be checked. This,
    616         however, depends on whether the socket module really checks the
    617         certificates.
    618 
    619         This method may raise the following exceptions:
    620 
    621          SMTPHeloError            The server didn't reply properly to
    622                                   the helo greeting.
    623         """
    624         self.ehlo_or_helo_if_needed()
    625         if not self.has_extn("starttls"):
    626             raise SMTPException("STARTTLS extension not supported by server.")
    627         (resp, reply) = self.docmd("STARTTLS")
    628         if resp == 220:
    629             if not _have_ssl:
    630                 raise RuntimeError("No SSL support included in this Python")
    631             self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
    632             self.file = SSLFakeFile(self.sock)
    633             # RFC 3207:

    634             # The client MUST discard any knowledge obtained from

    635             # the server, such as the list of SMTP service extensions,

    636             # which was not obtained from the TLS negotiation itself.

    637             self.helo_resp = None
    638             self.ehlo_resp = None
    639             self.esmtp_features = {}
    640             self.does_esmtp = 0
    641         return (resp, reply)
    642 
    643     def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
    644                  rcpt_options=[]):
    645         """This command performs an entire mail transaction.
    646 
    647         The arguments are:
    648             - from_addr    : The address sending this mail.
    649             - to_addrs     : A list of addresses to send this mail to.  A bare
    650                              string will be treated as a list with 1 address.
    651             - msg          : The message to send.
    652             - mail_options : List of ESMTP options (such as 8bitmime) for the
    653                              mail command.
    654             - rcpt_options : List of ESMTP options (such as DSN commands) for
    655                              all the rcpt commands.
    656 
    657         If there has been no previous EHLO or HELO command this session, this
    658         method tries ESMTP EHLO first.  If the server does ESMTP, message size
    659         and each of the specified options will be passed to it.  If EHLO
    660         fails, HELO will be tried and ESMTP options suppressed.
    661 
    662         This method will return normally if the mail is accepted for at least
    663         one recipient.  It returns a dictionary, with one entry for each
    664         recipient that was refused.  Each entry contains a tuple of the SMTP
    665         error code and the accompanying error message sent by the server.
    666 
    667         This method may raise the following exceptions:
    668 
    669          SMTPHeloError          The server didn't reply properly to
    670                                 the helo greeting.
    671          SMTPRecipientsRefused  The server rejected ALL recipients
    672                                 (no mail was sent).
    673          SMTPSenderRefused      The server didn't accept the from_addr.
    674          SMTPDataError          The server replied with an unexpected
    675                                 error code (other than a refusal of
    676                                 a recipient).
    677 
    678         Note: the connection will be open even after an exception is raised.
    679 
    680         Example:
    681 
    682          >>> import smtplib
    683          >>> s=smtplib.SMTP("localhost")
    684          >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
    685          >>> msg = '''\\
    686          ... From: Me (at] my.org
    687          ... Subject: testin'...
    688          ...
    689          ... This is a test '''
    690          >>> s.sendmail("me@my.org",tolist,msg)
    691          { "three@three.org" : ( 550 ,"User unknown" ) }
    692          >>> s.quit()
    693 
    694         In the above example, the message was accepted for delivery to three
    695         of the four addresses, and one was rejected, with the error code
    696         550.  If all addresses are accepted, then the method will return an
    697         empty dictionary.
    698 
    699         """
    700         self.ehlo_or_helo_if_needed()
    701         esmtp_opts = []
    702         if self.does_esmtp:
    703             # Hmmm? what's this? -ddm

    704             # self.esmtp_features['7bit']=""

    705             if self.has_extn('size'):
    706                 esmtp_opts.append("size=%d" % len(msg))
    707             for option in mail_options:
    708                 esmtp_opts.append(option)
    709 
    710         (code, resp) = self.mail(from_addr, esmtp_opts)
    711         if code != 250:
    712             self.rset()
    713             raise SMTPSenderRefused(code, resp, from_addr)
    714         senderrs = {}
    715         if isinstance(to_addrs, basestring):
    716             to_addrs = [to_addrs]
    717         for each in to_addrs:
    718             (code, resp) = self.rcpt(each, rcpt_options)
    719             if (code != 250) and (code != 251):
    720                 senderrs[each] = (code, resp)
    721         if len(senderrs) == len(to_addrs):
    722             # the server refused all our recipients

    723             self.rset()
    724             raise SMTPRecipientsRefused(senderrs)
    725         (code, resp) = self.data(msg)
    726         if code != 250:
    727             self.rset()
    728             raise SMTPDataError(code, resp)
    729         #if we got here then somebody got our mail

    730         return senderrs
    731 
    732 
    733     def close(self):
    734         """Close the connection to the SMTP server."""
    735         if self.file:
    736             self.file.close()
    737         self.file = None
    738         if self.sock:
    739             self.sock.close()
    740         self.sock = None
    741 
    742 
    743     def quit(self):
    744         """Terminate the SMTP session."""
    745         res = self.docmd("quit")
    746         self.close()
    747         return res
    748 
    749 if _have_ssl:
    750 
    751     class SMTP_SSL(SMTP):
    752         """ This is a subclass derived from SMTP that connects over an SSL encrypted
    753         socket (to use this class you need a socket module that was compiled with SSL
    754         support). If host is not specified, '' (the local host) is used. If port is
    755         omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile
    756         are also optional - they can contain a PEM formatted private key and
    757         certificate chain file for the SSL connection.
    758         """
    759 
    760         default_port = SMTP_SSL_PORT
    761 
    762         def __init__(self, host='', port=0, local_hostname=None,
    763                      keyfile=None, certfile=None,
    764                      timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
    765             self.keyfile = keyfile
    766             self.certfile = certfile
    767             SMTP.__init__(self, host, port, local_hostname, timeout)
    768 
    769         def _get_socket(self, host, port, timeout):
    770             if self.debuglevel > 0:
    771                 print>>stderr, 'connect:', (host, port)
    772             new_socket = socket.create_connection((host, port), timeout)
    773             new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
    774             self.file = SSLFakeFile(new_socket)
    775             return new_socket
    776 
    777     __all__.append("SMTP_SSL")
    778 
    779 #

    780 # LMTP extension

    781 #

    782 LMTP_PORT = 2003
    783 
    784 class LMTP(SMTP):
    785     """LMTP - Local Mail Transfer Protocol
    786 
    787     The LMTP protocol, which is very similar to ESMTP, is heavily based
    788     on the standard SMTP client. It's common to use Unix sockets for LMTP,
    789     so our connect() method must support that as well as a regular
    790     host:port server. To specify a Unix socket, you must use an absolute
    791     path as the host, starting with a '/'.
    792 
    793     Authentication is supported, using the regular SMTP mechanism. When
    794     using a Unix socket, LMTP generally don't support or require any
    795     authentication, but your mileage might vary."""
    796 
    797     ehlo_msg = "lhlo"
    798 
    799     def __init__(self, host='', port=LMTP_PORT, local_hostname=None):
    800         """Initialize a new instance."""
    801         SMTP.__init__(self, host, port, local_hostname)
    802 
    803     def connect(self, host='localhost', port=0):
    804         """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
    805         if host[0] != '/':
    806             return SMTP.connect(self, host, port)
    807 
    808         # Handle Unix-domain sockets.

    809         try:
    810             self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    811             self.sock.connect(host)
    812         except socket.error, msg:
    813             if self.debuglevel > 0:
    814                 print>>stderr, 'connect fail:', host
    815             if self.sock:
    816                 self.sock.close()
    817             self.sock = None
    818             raise socket.error, msg
    819         (code, msg) = self.getreply()
    820         if self.debuglevel > 0:
    821             print>>stderr, "connect:", msg
    822         return (code, msg)
    823 
    824 
    825 # Test the sendmail method, which tests most of the others.

    826 # Note: This always sends to localhost.

    827 if __name__ == '__main__':
    828     import sys
    829 
    830     def prompt(prompt):
    831         sys.stdout.write(prompt + ": ")
    832         return sys.stdin.readline().strip()
    833 
    834     fromaddr = prompt("From")
    835     toaddrs = prompt("To").split(',')
    836     print "Enter message, end with ^D:"
    837     msg = ''
    838     while 1:
    839         line = sys.stdin.readline()
    840         if not line:
    841             break
    842         msg = msg + line
    843     print "Message length is %d" % len(msg)
    844 
    845     server = SMTP('localhost')
    846     server.set_debuglevel(1)
    847     server.sendmail(fromaddr, toaddrs, msg)
    848     server.quit()
    849