0001 #! /usr/bin/env python 0002 0003 '''SMTP/ESMTP client class. 0004 0005 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP 0006 Authentication) and RFC 2487 (Secure SMTP over TLS). 0007 0008 Notes: 0009 0010 Please remember, when doing ESMTP, that the names of the SMTP service 0011 extensions are NOT the same thing as the option keywords for the RCPT 0012 and MAIL commands! 0013 0014 Example: 0015 0016 >>> import smtplib 0017 >>> s=smtplib.SMTP("localhost") 0018 >>> print s.help() 0019 This is Sendmail version 8.8.4 0020 Topics: 0021 HELO EHLO MAIL RCPT DATA 0022 RSET NOOP QUIT HELP VRFY 0023 EXPN VERB ETRN DSN 0024 For more info use "HELP <topic>". 0025 To report bugs in the implementation send email to 0026 sendmail-bugs@sendmail.org. 0027 For local information send email to Postmaster at your site. 0028 End of HELP info 0029 >>> s.putcmd("vrfy","someone@here") 0030 >>> s.getreply() 0031 (250, "Somebody OverHere <somebody@here.my.org>") 0032 >>> s.quit() 0033 ''' 0034 0035 # Author: The Dragon De Monsyne <dragondm@integral.org> 0036 # ESMTP support, test code and doc fixes added by 0037 # Eric S. Raymond <esr@thyrsus.com> 0038 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data) 0039 # by Carey Evans <c.evans@clear.net.nz>, for picky mail servers. 0040 # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>. 0041 # 0042 # This was modified from the Python 1.5 library HTTP lib. 0043 0044 import socket 0045 import re 0046 import rfc822 0047 import base64 0048 import hmac 0049 from email.base64MIME import encode as encode_base64 0050 from sys import stderr 0051 0052 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException", 0053 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError", 0054 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError", 0055 "quoteaddr","quotedata","SMTP"] 0056 0057 SMTP_PORT = 25 0058 CRLF="\r\n" 0059 0060 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 0061 0062 # Exception classes used by this module. 0063 class SMTPException(Exception): 0064 """Base class for all exceptions raised by this module.""" 0065 0066 class SMTPServerDisconnected(SMTPException): 0067 """Not connected to any SMTP server. 0068 0069 This exception is raised when the server unexpectedly disconnects, 0070 or when an attempt is made to use the SMTP instance before 0071 connecting it to a server. 0072 """ 0073 0074 class SMTPResponseException(SMTPException): 0075 """Base class for all exceptions that include an SMTP error code. 0076 0077 These exceptions are generated in some instances when the SMTP 0078 server returns an error code. The error code is stored in the 0079 `smtp_code' attribute of the error, and the `smtp_error' attribute 0080 is set to the error message. 0081 """ 0082 0083 def __init__(self, code, msg): 0084 self.smtp_code = code 0085 self.smtp_error = msg 0086 self.args = (code, msg) 0087 0088 class SMTPSenderRefused(SMTPResponseException): 0089 """Sender address refused. 0090 0091 In addition to the attributes set by on all SMTPResponseException 0092 exceptions, this sets `sender' to the string that the SMTP refused. 0093 """ 0094 0095 def __init__(self, code, msg, sender): 0096 self.smtp_code = code 0097 self.smtp_error = msg 0098 self.sender = sender 0099 self.args = (code, msg, sender) 0100 0101 class SMTPRecipientsRefused(SMTPException): 0102 """All recipient addresses refused. 0103 0104 The errors for each recipient are accessible through the attribute 0105 'recipients', which is a dictionary of exactly the same sort as 0106 SMTP.sendmail() returns. 0107 """ 0108 0109 def __init__(self, recipients): 0110 self.recipients = recipients 0111 self.args = ( recipients,) 0112 0113 0114 class SMTPDataError(SMTPResponseException): 0115 """The SMTP server didn't accept the data.""" 0116 0117 class SMTPConnectError(SMTPResponseException): 0118 """Error during connection establishment.""" 0119 0120 class SMTPHeloError(SMTPResponseException): 0121 """The server refused our HELO reply.""" 0122 0123 class SMTPAuthenticationError(SMTPResponseException): 0124 """Authentication error. 0125 0126 Most probably the server didn't accept the username/password 0127 combination provided. 0128 """ 0129 0130 class SSLFakeSocket: 0131 """A fake socket object that really wraps a SSLObject. 0132 0133 It only supports what is needed in smtplib. 0134 """ 0135 def __init__(self, realsock, sslobj): 0136 self.realsock = realsock 0137 self.sslobj = sslobj 0138 0139 def send(self, str): 0140 self.sslobj.write(str) 0141 return len(str) 0142 0143 sendall = send 0144 0145 def close(self): 0146 self.realsock.close() 0147 0148 class SSLFakeFile: 0149 """A fake file like object that really wraps a SSLObject. 0150 0151 It only supports what is needed in smtplib. 0152 """ 0153 def __init__( self, sslobj): 0154 self.sslobj = sslobj 0155 0156 def readline(self): 0157 str = "" 0158 chr = None 0159 while chr != "\n": 0160 chr = self.sslobj.read(1) 0161 str += chr 0162 return str 0163 0164 def close(self): 0165 pass 0166 0167 def quoteaddr(addr): 0168 """Quote a subset of the email addresses defined by RFC 821. 0169 0170 Should be able to handle anything rfc822.parseaddr can handle. 0171 """ 0172 m = (None, None) 0173 try: 0174 m=rfc822.parseaddr(addr)[1] 0175 except AttributeError: 0176 pass 0177 if m == (None, None): # Indicates parse failure or AttributeError 0178 #something weird here.. punt -ddm 0179 return "<%s>" % addr 0180 else: 0181 return "<%s>" % m 0182 0183 def quotedata(data): 0184 """Quote data for email. 0185 0186 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 0187 Internet CRLF end-of-line. 0188 """ 0189 return re.sub(r'(?m)^\.', '..', 0190 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 0191 0192 0193 class SMTP: 0194 """This class manages a connection to an SMTP or ESMTP server. 0195 SMTP Objects: 0196 SMTP objects have the following attributes: 0197 helo_resp 0198 This is the message given by the server in response to the 0199 most recent HELO command. 0200 0201 ehlo_resp 0202 This is the message given by the server in response to the 0203 most recent EHLO command. This is usually multiline. 0204 0205 does_esmtp 0206 This is a True value _after you do an EHLO command_, if the 0207 server supports ESMTP. 0208 0209 esmtp_features 0210 This is a dictionary, which, if the server supports ESMTP, 0211 will _after you do an EHLO command_, contain the names of the 0212 SMTP service extensions this server supports, and their 0213 parameters (if any). 0214 0215 Note, all extension names are mapped to lower case in the 0216 dictionary. 0217 0218 See each method's docstrings for details. In general, there is a 0219 method of the same name to perform each SMTP command. There is also a 0220 method called 'sendmail' that will do an entire mail transaction. 0221 """ 0222 debuglevel = 0 0223 file = None 0224 helo_resp = None 0225 ehlo_resp = None 0226 does_esmtp = 0 0227 0228 def __init__(self, host = '', port = 0, local_hostname = None): 0229 """Initialize a new instance. 0230 0231 If specified, `host' is the name of the remote host to which to 0232 connect. If specified, `port' specifies the port to which to connect. 0233 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised 0234 if the specified `host' doesn't respond correctly. If specified, 0235 `local_hostname` is used as the FQDN of the local host. By default, 0236 the local hostname is found using socket.getfqdn(). 0237 0238 """ 0239 self.esmtp_features = {} 0240 if host: 0241 (code, msg) = self.connect(host, port) 0242 if code != 220: 0243 raise SMTPConnectError(code, msg) 0244 if local_hostname is not None: 0245 self.local_hostname = local_hostname 0246 else: 0247 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and 0248 # if that can't be calculated, that we should use a domain literal 0249 # instead (essentially an encoded IP address like [A.B.C.D]). 0250 fqdn = socket.getfqdn() 0251 if '.' in fqdn: 0252 self.local_hostname = fqdn 0253 else: 0254 # We can't find an fqdn hostname, so use a domain literal 0255 addr = socket.gethostbyname(socket.gethostname()) 0256 self.local_hostname = '[%s]' % addr 0257 0258 def set_debuglevel(self, debuglevel): 0259 """Set the debug output level. 0260 0261 A non-false value results in debug messages for connection and for all 0262 messages sent to and received from the server. 0263 0264 """ 0265 self.debuglevel = debuglevel 0266 0267 def connect(self, host='localhost', port = 0): 0268 """Connect to a host on a given port. 0269 0270 If the hostname ends with a colon (`:') followed by a number, and 0271 there is no port specified, that suffix will be stripped off and the 0272 number interpreted as the port number to use. 0273 0274 Note: This method is automatically invoked by __init__, if a host is 0275 specified during instantiation. 0276 0277 """ 0278 if not port and (host.find(':') == host.rfind(':')): 0279 i = host.rfind(':') 0280 if i >= 0: 0281 host, port = host[:i], host[i+1:] 0282 try: port = int(port) 0283 except ValueError: 0284 raise socket.error, "nonnumeric port" 0285 if not port: port = SMTP_PORT 0286 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) 0287 msg = "getaddrinfo returns an empty list" 0288 self.sock = None 0289 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): 0290 af, socktype, proto, canonname, sa = res 0291 try: 0292 self.sock = socket.socket(af, socktype, proto) 0293 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) 0294 self.sock.connect(sa) 0295 except socket.error, msg: 0296 if self.debuglevel > 0: print>>stderr, 'connect fail:', (host, port) 0297 if self.sock: 0298 self.sock.close() 0299 self.sock = None 0300 continue 0301 break 0302 if not self.sock: 0303 raise socket.error, msg 0304 (code, msg) = self.getreply() 0305 if self.debuglevel > 0: print>>stderr, "connect:", msg 0306 return (code, msg) 0307 0308 def send(self, str): 0309 """Send `str' to the server.""" 0310 if self.debuglevel > 0: print>>stderr, 'send:', repr(str) 0311 if self.sock: 0312 try: 0313 self.sock.sendall(str) 0314 except socket.error: 0315 self.close() 0316 raise SMTPServerDisconnected('Server not connected') 0317 else: 0318 raise SMTPServerDisconnected('please run connect() first') 0319 0320 def putcmd(self, cmd, args=""): 0321 """Send a command to the server.""" 0322 if args == "": 0323 str = '%s%s' % (cmd, CRLF) 0324 else: 0325 str = '%s %s%s' % (cmd, args, CRLF) 0326 self.send(str) 0327 0328 def getreply(self): 0329 """Get a reply from the server. 0330 0331 Returns a tuple consisting of: 0332 0333 - server response code (e.g. '250', or such, if all goes well) 0334 Note: returns -1 if it can't read response code. 0335 0336 - server response string corresponding to response code (multiline 0337 responses are converted to a single, multiline string). 0338 0339 Raises SMTPServerDisconnected if end-of-file is reached. 0340 """ 0341 resp=[] 0342 if self.file is None: 0343 self.file = self.sock.makefile('rb') 0344 while 1: 0345 line = self.file.readline() 0346 if line == '': 0347 self.close() 0348 raise SMTPServerDisconnected("Connection unexpectedly closed") 0349 if self.debuglevel > 0: print>>stderr, 'reply:', repr(line) 0350 resp.append(line[4:].strip()) 0351 code=line[:3] 0352 # Check that the error code is syntactically correct. 0353 # Don't attempt to read a continuation line if it is broken. 0354 try: 0355 errcode = int(code) 0356 except ValueError: 0357 errcode = -1 0358 break 0359 # Check if multiline response. 0360 if line[3:4]!="-": 0361 break 0362 0363 errmsg = "\n".join(resp) 0364 if self.debuglevel > 0: 0365 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg) 0366 return errcode, errmsg 0367 0368 def docmd(self, cmd, args=""): 0369 """Send a command, and return its response code.""" 0370 self.putcmd(cmd,args) 0371 return self.getreply() 0372 0373 # std smtp commands 0374 def helo(self, name=''): 0375 """SMTP 'helo' command. 0376 Hostname to send for this command defaults to the FQDN of the local 0377 host. 0378 """ 0379 self.putcmd("helo", name or self.local_hostname) 0380 (code,msg)=self.getreply() 0381 self.helo_resp=msg 0382 return (code,msg) 0383 0384 def ehlo(self, name=''): 0385 """ SMTP 'ehlo' command. 0386 Hostname to send for this command defaults to the FQDN of the local 0387 host. 0388 """ 0389 self.esmtp_features = {} 0390 self.putcmd("ehlo", name or self.local_hostname) 0391 (code,msg)=self.getreply() 0392 # According to RFC1869 some (badly written) 0393 # MTA's will disconnect on an ehlo. Toss an exception if 0394 # that happens -ddm 0395 if code == -1 and len(msg) == 0: 0396 self.close() 0397 raise SMTPServerDisconnected("Server not connected") 0398 self.ehlo_resp=msg 0399 if code != 250: 0400 return (code,msg) 0401 self.does_esmtp=1 0402 #parse the ehlo response -ddm 0403 resp=self.ehlo_resp.split('\n') 0404 del resp[0] 0405 for each in resp: 0406 # To be able to communicate with as many SMTP servers as possible, 0407 # we have to take the old-style auth advertisement into account, 0408 # because: 0409 # 1) Else our SMTP feature parser gets confused. 0410 # 2) There are some servers that only advertise the auth methods we 0411 # support using the old style. 0412 auth_match = OLDSTYLE_AUTH.match(each) 0413 if auth_match: 0414 # This doesn't remove duplicates, but that's no problem 0415 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 0416 + " " + auth_match.groups(0)[0] 0417 continue 0418 0419 # RFC 1869 requires a space between ehlo keyword and parameters. 0420 # It's actually stricter, in that only spaces are allowed between 0421 # parameters, but were not going to check for that here. Note 0422 # that the space isn't present if there are no parameters. 0423 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each) 0424 if m: 0425 feature=m.group("feature").lower() 0426 params=m.string[m.end("feature"):].strip() 0427 if feature == "auth": 0428 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 0429 + " " + params 0430 else: 0431 self.esmtp_features[feature]=params 0432 return (code,msg) 0433 0434 def has_extn(self, opt): 0435 """Does the server support a given SMTP service extension?""" 0436 return opt.lower() in self.esmtp_features 0437 0438 def help(self, args=''): 0439 """SMTP 'help' command. 0440 Returns help text from server.""" 0441 self.putcmd("help", args) 0442 return self.getreply() 0443 0444 def rset(self): 0445 """SMTP 'rset' command -- resets session.""" 0446 return self.docmd("rset") 0447 0448 def noop(self): 0449 """SMTP 'noop' command -- doesn't do anything :>""" 0450 return self.docmd("noop") 0451 0452 def mail(self,sender,options=[]): 0453 """SMTP 'mail' command -- begins mail xfer session.""" 0454 optionlist = '' 0455 if options and self.does_esmtp: 0456 optionlist = ' ' + ' '.join(options) 0457 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist)) 0458 return self.getreply() 0459 0460 def rcpt(self,recip,options=[]): 0461 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 0462 optionlist = '' 0463 if options and self.does_esmtp: 0464 optionlist = ' ' + ' '.join(options) 0465 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist)) 0466 return self.getreply() 0467 0468 def data(self,msg): 0469 """SMTP 'DATA' command -- sends message data to server. 0470 0471 Automatically quotes lines beginning with a period per rfc821. 0472 Raises SMTPDataError if there is an unexpected reply to the 0473 DATA command; the return value from this method is the final 0474 response code received when the all data is sent. 0475 """ 0476 self.putcmd("data") 0477 (code,repl)=self.getreply() 0478 if self.debuglevel >0 : print>>stderr, "data:", (code,repl) 0479 if code != 354: 0480 raise SMTPDataError(code,repl) 0481 else: 0482 q = quotedata(msg) 0483 if q[-2:] != CRLF: 0484 q = q + CRLF 0485 q = q + "." + CRLF 0486 self.send(q) 0487 (code,msg)=self.getreply() 0488 if self.debuglevel >0 : print>>stderr, "data:", (code,msg) 0489 return (code,msg) 0490 0491 def verify(self, address): 0492 """SMTP 'verify' command -- checks for address validity.""" 0493 self.putcmd("vrfy", quoteaddr(address)) 0494 return self.getreply() 0495 # a.k.a. 0496 vrfy=verify 0497 0498 def expn(self, address): 0499 """SMTP 'verify' command -- checks for address validity.""" 0500 self.putcmd("expn", quoteaddr(address)) 0501 return self.getreply() 0502 0503 # some useful methods 0504 0505 def login(self, user, password): 0506 """Log in on an SMTP server that requires authentication. 0507 0508 The arguments are: 0509 - user: The user name to authenticate with. 0510 - password: The password for the authentication. 0511 0512 If there has been no previous EHLO or HELO command this session, this 0513 method tries ESMTP EHLO first. 0514 0515 This method will return normally if the authentication was successful. 0516 0517 This method may raise the following exceptions: 0518 0519 SMTPHeloError The server didn't reply properly to 0520 the helo greeting. 0521 SMTPAuthenticationError The server didn't accept the username/ 0522 password combination. 0523 SMTPException No suitable authentication method was 0524 found. 0525 """ 0526 0527 def encode_cram_md5(challenge, user, password): 0528 challenge = base64.decodestring(challenge) 0529 response = user + " " + hmac.HMAC(password, challenge).hexdigest() 0530 return encode_base64(response, eol="") 0531 0532 def encode_plain(user, password): 0533 return encode_base64("%s\0%s\0%s" % (user, user, password), eol="") 0534 0535 0536 AUTH_PLAIN = "PLAIN" 0537 AUTH_CRAM_MD5 = "CRAM-MD5" 0538 AUTH_LOGIN = "LOGIN" 0539 0540 if self.helo_resp is None and self.ehlo_resp is None: 0541 if not (200 <= self.ehlo()[0] <= 299): 0542 (code, resp) = self.helo() 0543 if not (200 <= code <= 299): 0544 raise SMTPHeloError(code, resp) 0545 0546 if not self.has_extn("auth"): 0547 raise SMTPException("SMTP AUTH extension not supported by server.") 0548 0549 # Authentication methods the server supports: 0550 authlist = self.esmtp_features["auth"].split() 0551 0552 # List of authentication methods we support: from preferred to 0553 # less preferred methods. Except for the purpose of testing the weaker 0554 # ones, we prefer stronger methods like CRAM-MD5: 0555 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN] 0556 0557 # Determine the authentication method we'll use 0558 authmethod = None 0559 for method in preferred_auths: 0560 if method in authlist: 0561 authmethod = method 0562 break 0563 0564 if authmethod == AUTH_CRAM_MD5: 0565 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5) 0566 if code == 503: 0567 # 503 == 'Error: already authenticated' 0568 return (code, resp) 0569 (code, resp) = self.docmd(encode_cram_md5(resp, user, password)) 0570 elif authmethod == AUTH_PLAIN: 0571 (code, resp) = self.docmd("AUTH", 0572 AUTH_PLAIN + " " + encode_plain(user, password)) 0573 elif authmethod == AUTH_LOGIN: 0574 (code, resp) = self.docmd("AUTH", 0575 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol=""))) 0576 if code != 334: 0577 raise SMTPAuthenticationError(code, resp) 0578 (code, resp) = self.docmd(encode_base64(password, eol="")) 0579 elif authmethod is None: 0580 raise SMTPException("No suitable authentication method found.") 0581 if code not in [235, 503]: 0582 # 235 == 'Authentication successful' 0583 # 503 == 'Error: already authenticated' 0584 raise SMTPAuthenticationError(code, resp) 0585 return (code, resp) 0586 0587 def starttls(self, keyfile = None, certfile = None): 0588 """Puts the connection to the SMTP server into TLS mode. 0589 0590 If the server supports TLS, this will encrypt the rest of the SMTP 0591 session. If you provide the keyfile and certfile parameters, 0592 the identity of the SMTP server and client can be checked. This, 0593 however, depends on whether the socket module really checks the 0594 certificates. 0595 """ 0596 (resp, reply) = self.docmd("STARTTLS") 0597 if resp == 220: 0598 sslobj = socket.ssl(self.sock, keyfile, certfile) 0599 self.sock = SSLFakeSocket(self.sock, sslobj) 0600 self.file = SSLFakeFile(sslobj) 0601 return (resp, reply) 0602 0603 def sendmail(self, from_addr, to_addrs, msg, mail_options=[], 0604 rcpt_options=[]): 0605 """This command performs an entire mail transaction. 0606 0607 The arguments are: 0608 - from_addr : The address sending this mail. 0609 - to_addrs : A list of addresses to send this mail to. A bare 0610 string will be treated as a list with 1 address. 0611 - msg : The message to send. 0612 - mail_options : List of ESMTP options (such as 8bitmime) for the 0613 mail command. 0614 - rcpt_options : List of ESMTP options (such as DSN commands) for 0615 all the rcpt commands. 0616 0617 If there has been no previous EHLO or HELO command this session, this 0618 method tries ESMTP EHLO first. If the server does ESMTP, message size 0619 and each of the specified options will be passed to it. If EHLO 0620 fails, HELO will be tried and ESMTP options suppressed. 0621 0622 This method will return normally if the mail is accepted for at least 0623 one recipient. It returns a dictionary, with one entry for each 0624 recipient that was refused. Each entry contains a tuple of the SMTP 0625 error code and the accompanying error message sent by the server. 0626 0627 This method may raise the following exceptions: 0628 0629 SMTPHeloError The server didn't reply properly to 0630 the helo greeting. 0631 SMTPRecipientsRefused The server rejected ALL recipients 0632 (no mail was sent). 0633 SMTPSenderRefused The server didn't accept the from_addr. 0634 SMTPDataError The server replied with an unexpected 0635 error code (other than a refusal of 0636 a recipient). 0637 0638 Note: the connection will be open even after an exception is raised. 0639 0640 Example: 0641 0642 >>> import smtplib 0643 >>> s=smtplib.SMTP("localhost") 0644 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 0645 >>> msg = '''\\ 0646 ... From: Me@my.org 0647 ... Subject: testin'... 0648 ... 0649 ... This is a test ''' 0650 >>> s.sendmail("me@my.org",tolist,msg) 0651 { "three@three.org" : ( 550 ,"User unknown" ) } 0652 >>> s.quit() 0653 0654 In the above example, the message was accepted for delivery to three 0655 of the four addresses, and one was rejected, with the error code 0656 550. If all addresses are accepted, then the method will return an 0657 empty dictionary. 0658 0659 """ 0660 if self.helo_resp is None and self.ehlo_resp is None: 0661 if not (200 <= self.ehlo()[0] <= 299): 0662 (code,resp) = self.helo() 0663 if not (200 <= code <= 299): 0664 raise SMTPHeloError(code, resp) 0665 esmtp_opts = [] 0666 if self.does_esmtp: 0667 # Hmmm? what's this? -ddm 0668 # self.esmtp_features['7bit']="" 0669 if self.has_extn('size'): 0670 esmtp_opts.append("size=%d" % len(msg)) 0671 for option in mail_options: 0672 esmtp_opts.append(option) 0673 0674 (code,resp) = self.mail(from_addr, esmtp_opts) 0675 if code != 250: 0676 self.rset() 0677 raise SMTPSenderRefused(code, resp, from_addr) 0678 senderrs={} 0679 if isinstance(to_addrs, basestring): 0680 to_addrs = [to_addrs] 0681 for each in to_addrs: 0682 (code,resp)=self.rcpt(each, rcpt_options) 0683 if (code != 250) and (code != 251): 0684 senderrs[each]=(code,resp) 0685 if len(senderrs)==len(to_addrs): 0686 # the server refused all our recipients 0687 self.rset() 0688 raise SMTPRecipientsRefused(senderrs) 0689 (code,resp) = self.data(msg) 0690 if code != 250: 0691 self.rset() 0692 raise SMTPDataError(code, resp) 0693 #if we got here then somebody got our mail 0694 return senderrs 0695 0696 0697 def close(self): 0698 """Close the connection to the SMTP server.""" 0699 if self.file: 0700 self.file.close() 0701 self.file = None 0702 if self.sock: 0703 self.sock.close() 0704 self.sock = None 0705 0706 0707 def quit(self): 0708 """Terminate the SMTP session.""" 0709 self.docmd("quit") 0710 self.close() 0711 0712 0713 # Test the sendmail method, which tests most of the others. 0714 # Note: This always sends to localhost. 0715 if __name__ == '__main__': 0716 import sys 0717 0718 def prompt(prompt): 0719 sys.stdout.write(prompt + ": ") 0720 return sys.stdin.readline().strip() 0721 0722 fromaddr = prompt("From") 0723 toaddrs = prompt("To").split(',') 0724 print "Enter message, end with ^D:" 0725 msg = '' 0726 while 1: 0727 line = sys.stdin.readline() 0728 if not line: 0729 break 0730 msg = msg + line 0731 print "Message length is %d" % len(msg) 0732 0733 server = SMTP('localhost') 0734 server.set_debuglevel(1) 0735 server.sendmail(fromaddr, toaddrs, msg) 0736 server.quit() 0737
Generated by PyXR 0.9.4