在项目中要加入发送邮件的模块,在测试的过程中使用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 = sslobj13 14 def readline(self):15 str = ""16 chr = None17 while chr != "/n":18 chr = self.sslobj.read(1)19 if not chr: break20 str += chr21 return str22 23 def close(self):24 pass2526 _have_ssl = True
已经看到在ssl发送邮件的过程中,对于接收数据使用ssl-readline,而发送数据使用的还是socket-send,因而服务器报错,知道问题就好办了,在数据发送时也使用ssl-send
直接贴出修改之后的smtplib.py的源码供大家使用
1 #! /usr/bin/env python2 3 '''SMTP/ESMTP client class.4 5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP6 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 HELOEHLOMAILRCPTDATA 22 RSETNOOPQUITHELPVRFY 23 EXPNVERBETRNDSN 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 = sender100 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 attribute106 'recipients', which is a dictionary of exactly the same sort as107 SMTP.sendmail() returns.108 """109 110 def __init__(self, recipients):111 self.recipients = recipients112 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/password128 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 pass141 if m == (None, None): # Indicates parse failure or AttributeError142 # something weird here.. punt -ddm143 return "<%s>" % addr144 elif m is None:145 # the sender wants an empty return address146 return "<>"147 else:148 return "<%s>" % m149 150 def quotedata(data):151 """Quote data for email.152 153 Double leading '.', and change Unix newline '//n', or Mac '//r' into154 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 ssl162 except ImportError:163 _have_ssl = False164 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 = sslobj172 173 def readline(self):174 str = ""175 chr = None176 while chr != "/n":177 chr = self.sslobj.read(1)178 if not chr: break179 str += chr180 return str181 182 def close(self):183 pass184 185 def send(self, str):186 self.sslobj.send(str)187 188 _have_ssl = True189 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_resp195 This is the message given by the server in response to the196 most recent HELO command.197 198 ehlo_resp199 This is the message given by the server in response to the200 most recent EHLO command. This is usually multiline.201 202 does_esmtp203 This is a True value _after you do an EHLO command_, if the204 server supports ESMTP.205 206 esmtp_features207 This is a dictionary, which, if the server supports ESMTP,208 will _after you do an EHLO command_, contain the names of the209 SMTP service extensions this server supports, and their210 parameters (if any).211 212 Note, all extension names are mapped to lower case in the213 dictionary.214 215 See each method's docstrings for details.In general, there is a216 method of the same name to perform each SMTP command.There is also a217 method called 'sendmail' that will do an entire mail transaction.218 """219 debuglevel = 0220 file = None221 helo_resp = None222 ehlo_msg = "ehlo"223 ehlo_resp = None224 does_esmtp = 0225 226 def __init__(self, host='', port=0, local_hostname=None,227timeout=socket._GLOBAL_DEFAULT_TIMEOUT):228 """Initialize a new instance.229 230 If specified, `host' is the name of the remote host to which to231 connect.If specified, `port' specifies the port to which to connect.232 By default, smtplib.SMTP_PORT is used.An SMTPConnectError is raised233 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 = timeout239 self.esmtp_features = {}240 self.default_port = SMTP_PORT241 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_hostname248 else:249 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and250 # if that can't be calculated, that we should use a domain literal251 # instead (essentially an encoded IP address like [A.B.C.D]).252 fqdn = socket.getfqdn()253 if '.' in fqdn:254 self.local_hostname = fqdn255 else:256 # We can't find an fqdn hostname, so use a domain literal257 addr = '127.0.0.1'258 try:259 addr = socket.gethostbyname(socket.gethostname())260 except socket.gaierror:261 pass262 self.local_hostname = '[%s]' % addr263 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 all268 messages sent to and received from the server.269 270 """271 self.debuglevel = debuglevel272 273 def _get_socket(self, port, host, timeout):274 # This makes it simpler for SMTP_SSL to use the SMTP connect code275 # 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, and283 there is no port specified, that suffix will be stripped off and the284 number interpreted as the port number to use.285 286 Note: This method is automatically invoked by __init__, if a host is287 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_port298 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:", msg302 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.sock309 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.sock322 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 (multiline350 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 = -1371 break372 # Check if multiline response.373 if line[3:4]!="-":374 break375 376 errmsg = "/n".join(resp)377 if self.debuglevel > 0:378 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)379 return errcode, errmsg380 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 commands387 def helo(self, name=''):388 """SMTP 'helo' command.389 Hostname to send for this command defaults to the FQDN of the local390 host.391 """392 self.putcmd("helo", name or self.local_hostname)393 (code,msg)=self.getreply()394 self.helo_resp=msg395 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 local400 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 if407 # that happens -ddm408 if code == -1 and len(msg) == 0:409 self.close()410 raise SMTPServerDisconnected("Server not connected")411 self.ehlo_resp=msg412 if code != 250:413 return (code,msg)414 self.does_esmtp=1415 #parse the ehlo response -ddm416 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 we424 #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 problem428 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") /429 + " " + auth_match.groups(0)[0]430 continue431 432 # RFC 1869 requires a space between ehlo keyword and parameters.433 # It's actually stricter, in that only spaces are allowed between434 # parameters, but were not going to check for that here.Note435 # 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 + " " + params443 else:444 self.esmtp_features[feature]=params445 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_features450 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 the486 DATA command; the return value from this method is the final487 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 + CRLF498 q = q + "." + CRLF499 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=verify510 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 methods517 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, this522 method tries ESMTP EHLO first.523 524 This method may raise the following exceptions:525 526 SMTPHeloErrorThe server didn't reply properly to527 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, this543 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 SMTPHeloErrorThe server didn't reply properly to550 the helo greeting.551 SMTPAuthenticationErrorThe server didn't accept the username/552 password combination.553 SMTPExceptionNo suitable authentication method was554 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 to579 # less preferred methods. Except for the purpose of testing the weaker580 # 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 use584 authmethod = None585 for method in preferred_auths:586 if method in authlist:587 authmethod = method588 break589 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 start600 (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, this625 method tries ESMTP EHLO first.626 627 If the server supports TLS, this will encrypt the rest of the SMTP628 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 the631 certificates.632 633 This method may raise the following exceptions:634 635 SMTPHeloErrorThe server didn't reply properly to636 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 from649 # the server, such as the list of SMTP service extensions,650 # which was not obtained from the TLS negotiation itself.651 self.helo_resp = None652 self.ehlo_resp = None653 self.esmtp_features = {}654 self.does_esmtp = 0655 return (resp, reply)656 657 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],658rcpt_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 bare664 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 the667 mail command.668 - rcpt_options : List of ESMTP options (such as DSN commands) for669 all the rcpt commands.670 671 If there has been no previous EHLO or HELO command this session, this672 method tries ESMTP EHLO first.If the server does ESMTP, message size673 and each of the specified options will be passed to it.If EHLO674 fails, HELO will be tried and ESMTP options suppressed.675 676 This method will return normally if the mail is accepted for at least677 one recipient.It returns a dictionary, with one entry for each678 recipient that was refused.Each entry contains a tuple of the SMTP679 error code and the accompanying error message sent by the server.680 681 This method may raise the following exceptions:682 683 SMTPHeloErrorThe server didn't reply properly to684 the helo greeting.685 SMTPRecipientsRefusedThe server rejected ALL recipients686 (no mail was sent).687 SMTPSenderRefusedThe server didn't accept the from_addr.688 SMTPDataErrorThe server replied with an unexpected689 error code (other than a refusal of690 a recipient).691 692 Note: the connection will be open even after an exception is raised.693 694 Example:695 696 >>> import smtplib697 >>> s=smtplib.SMTP("localhost")698 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]699 >>> msg = '''//700 ... From: Me@my.org701 ... 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 three709 of the four addresses, and one was rejected, with the error code710 550.If all addresses are accepted, then the method will return an711 empty dictionary.712 713 """714 self.ehlo_or_helo_if_needed()715 esmtp_opts = []716 if self.does_esmtp:717 # Hmmm? what's this? -ddm718 # 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 recipients737 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 mail744 return senderrs745 746 747 def close(self):748 """Close the connection to the SMTP server."""749 if self.file:750 self.file.close()751 self.file = None752 if self.sock:753 self.sock.close()754 self.sock = None755 756 757 def quit(self):758 """Terminate the SMTP session."""759 res = self.docmd("quit")760 self.close()761 return res762 763 if _have_ssl:764 765 class SMTP_SSL(SMTP):766 """ This is a subclass derived from SMTP that connects over an SSL encrypted767 socket (to use this class you need a socket module that was compiled with SSL768 support). If host is not specified, '' (the local host) is used. If port is769 omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile770 are also optional - they can contain a PEM formatted private key and771 certificate chain file for the SSL connection.772 """773 def __init__(self, host='', port=0, local_hostname=None,774keyfile=None, certfile=None,775timeout=socket._GLOBAL_DEFAULT_TIMEOUT):776 self.keyfile = keyfile777 self.certfile = certfile778 SMTP.__init__(self, host, port, local_hostname, timeout)779 self.default_port = SMTP_SSL_PORT780 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 extension791 #792 LMTP_PORT = 2003793 794 class LMTP(SMTP):795 """LMTP - Local Mail Transfer Protocol796 797 The LMTP protocol, which is very similar to ESMTP, is heavily based798 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 regular800 host:port server. To specify a Unix socket, you must use an absolute801 path as the host, starting with a '/'.802 803 Authentication is supported, using the regular SMTP mechanism. When804 using a Unix socket, LMTP generally don't support or require any805 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:', host824 if self.sock:825 self.sock.close()826 self.sock = None827 raise socket.error, msg828 (code, msg) = self.getreply()829 if self.debuglevel > 0: print>>stderr, "connect:", msg830 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 sys837 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 break850 msg = msg + line851 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()
--转自
该贴由system转至本版2014-10-30 23:18:01