• 热门专题

记录python2.6.2中SMTPLIB利用SMTP_SSL发邮件过程中的BUG

作者:sding  发布日期:2014-05-21 20:14:20
Tag标签:邮件  过程  
  • 在项目中要加入发送邮件的模块,在测试的过程中使用QQ的邮箱,SMTP的服务器地址:smtp.qq.com 端口465。

    拿出以前写过的一个角本进行测试发送,发送不成功,显示 raise SMTPServerDisconnected('please run connect() first')

    确定已经使用SSL连接上了,一步一步接下来分析。看了下smtplib.py的源码,在使用ssl安全连接发送邮件时,使用SSLFakeFile对socket进行包装,查看SSLFakeFile的源码

     1 try:
     2     import ssl
     3 except ImportError:
     4     _have_ssl = False
     5 else:
     6     class SSLFakeFile:
     7         """A fake file like object that really wraps a SSLObject.
     8 
     9         It only supports what is needed in smtplib.
    10         """
    11         def __init__(self, sslobj):
    12             self.sslobj = sslobj
    13 
    14         def readline(self):
    15             str = ""
    16             chr = None
    17             while chr != "\n":
    18                 chr = self.sslobj.read(1)
    19                 if not chr: break
    20                 str += chr
    21             return str
    22 
    23         def close(self):
    24             pass
    25      
    26     _have_ssl = True

    已经看到在ssl发送邮件的过程中,对于接收数据使用ssl-readline,而发送数据使用的还是socket-send,因而服务器报错,知道问题就好办了,在数据发送时也使用ssl-send

    直接贴出修改之后的smtplib.py的源码供大家使用

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