Catch situations where currentframe() returns None. See SF patch #1447410, this is...
[python.git] / Lib / smtplib.py
blob71d25fda268ecc8916171003895c1e7e76693aad
1 #! /usr/bin/env python
3 '''SMTP/ESMTP client class.
5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
6 Authentication) and RFC 2487 (Secure SMTP over TLS).
8 Notes:
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!
14 Example:
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 '''
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>.
42 # This was modified from the Python 1.5 library HTTP lib.
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
52 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
53 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
54 "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
55 "quoteaddr","quotedata","SMTP"]
57 SMTP_PORT = 25
58 CRLF="\r\n"
60 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
62 # Exception classes used by this module.
63 class SMTPException(Exception):
64 """Base class for all exceptions raised by this module."""
66 class SMTPServerDisconnected(SMTPException):
67 """Not connected to any SMTP server.
69 This exception is raised when the server unexpectedly disconnects,
70 or when an attempt is made to use the SMTP instance before
71 connecting it to a server.
72 """
74 class SMTPResponseException(SMTPException):
75 """Base class for all exceptions that include an SMTP error code.
77 These exceptions are generated in some instances when the SMTP
78 server returns an error code. The error code is stored in the
79 `smtp_code' attribute of the error, and the `smtp_error' attribute
80 is set to the error message.
81 """
83 def __init__(self, code, msg):
84 self.smtp_code = code
85 self.smtp_error = msg
86 self.args = (code, msg)
88 class SMTPSenderRefused(SMTPResponseException):
89 """Sender address refused.
91 In addition to the attributes set by on all SMTPResponseException
92 exceptions, this sets `sender' to the string that the SMTP refused.
93 """
95 def __init__(self, code, msg, sender):
96 self.smtp_code = code
97 self.smtp_error = msg
98 self.sender = sender
99 self.args = (code, msg, sender)
101 class SMTPRecipientsRefused(SMTPException):
102 """All recipient addresses refused.
104 The errors for each recipient are accessible through the attribute
105 'recipients', which is a dictionary of exactly the same sort as
106 SMTP.sendmail() returns.
109 def __init__(self, recipients):
110 self.recipients = recipients
111 self.args = ( recipients,)
114 class SMTPDataError(SMTPResponseException):
115 """The SMTP server didn't accept the data."""
117 class SMTPConnectError(SMTPResponseException):
118 """Error during connection establishment."""
120 class SMTPHeloError(SMTPResponseException):
121 """The server refused our HELO reply."""
123 class SMTPAuthenticationError(SMTPResponseException):
124 """Authentication error.
126 Most probably the server didn't accept the username/password
127 combination provided.
130 class SSLFakeSocket:
131 """A fake socket object that really wraps a SSLObject.
133 It only supports what is needed in smtplib.
135 def __init__(self, realsock, sslobj):
136 self.realsock = realsock
137 self.sslobj = sslobj
139 def send(self, str):
140 self.sslobj.write(str)
141 return len(str)
143 sendall = send
145 def close(self):
146 self.realsock.close()
148 class SSLFakeFile:
149 """A fake file like object that really wraps a SSLObject.
151 It only supports what is needed in smtplib.
153 def __init__( self, sslobj):
154 self.sslobj = sslobj
156 def readline(self):
157 str = ""
158 chr = None
159 while chr != "\n":
160 chr = self.sslobj.read(1)
161 str += chr
162 return str
164 def close(self):
165 pass
167 def quoteaddr(addr):
168 """Quote a subset of the email addresses defined by RFC 821.
170 Should be able to handle anything rfc822.parseaddr can handle.
172 m = (None, None)
173 try:
174 m = email.Utils.parseaddr(addr)[1]
175 except AttributeError:
176 pass
177 if m == (None, None): # Indicates parse failure or AttributeError
178 # something weird here.. punt -ddm
179 return "<%s>" % addr
180 elif m is None:
181 # the sender wants an empty return address
182 return "<>"
183 else:
184 return "<%s>" % m
186 def quotedata(data):
187 """Quote data for email.
189 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
190 Internet CRLF end-of-line.
192 return re.sub(r'(?m)^\.', '..',
193 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
196 class SMTP:
197 """This class manages a connection to an SMTP or ESMTP server.
198 SMTP Objects:
199 SMTP objects have the following attributes:
200 helo_resp
201 This is the message given by the server in response to the
202 most recent HELO command.
204 ehlo_resp
205 This is the message given by the server in response to the
206 most recent EHLO command. This is usually multiline.
208 does_esmtp
209 This is a True value _after you do an EHLO command_, if the
210 server supports ESMTP.
212 esmtp_features
213 This is a dictionary, which, if the server supports ESMTP,
214 will _after you do an EHLO command_, contain the names of the
215 SMTP service extensions this server supports, and their
216 parameters (if any).
218 Note, all extension names are mapped to lower case in the
219 dictionary.
221 See each method's docstrings for details. In general, there is a
222 method of the same name to perform each SMTP command. There is also a
223 method called 'sendmail' that will do an entire mail transaction.
225 debuglevel = 0
226 file = None
227 helo_resp = None
228 ehlo_resp = None
229 does_esmtp = 0
231 def __init__(self, host = '', port = 0, local_hostname = None):
232 """Initialize a new instance.
234 If specified, `host' is the name of the remote host to which to
235 connect. If specified, `port' specifies the port to which to connect.
236 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
237 if the specified `host' doesn't respond correctly. If specified,
238 `local_hostname` is used as the FQDN of the local host. By default,
239 the local hostname is found using socket.getfqdn().
242 self.esmtp_features = {}
243 if host:
244 (code, msg) = self.connect(host, port)
245 if code != 220:
246 raise SMTPConnectError(code, msg)
247 if local_hostname is not None:
248 self.local_hostname = local_hostname
249 else:
250 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
251 # if that can't be calculated, that we should use a domain literal
252 # instead (essentially an encoded IP address like [A.B.C.D]).
253 fqdn = socket.getfqdn()
254 if '.' in fqdn:
255 self.local_hostname = fqdn
256 else:
257 # We can't find an fqdn hostname, so use a domain literal
258 addr = socket.gethostbyname(socket.gethostname())
259 self.local_hostname = '[%s]' % addr
261 def set_debuglevel(self, debuglevel):
262 """Set the debug output level.
264 A non-false value results in debug messages for connection and for all
265 messages sent to and received from the server.
268 self.debuglevel = debuglevel
270 def connect(self, host='localhost', port = 0):
271 """Connect to a host on a given port.
273 If the hostname ends with a colon (`:') followed by a number, and
274 there is no port specified, that suffix will be stripped off and the
275 number interpreted as the port number to use.
277 Note: This method is automatically invoked by __init__, if a host is
278 specified during instantiation.
281 if not port and (host.find(':') == host.rfind(':')):
282 i = host.rfind(':')
283 if i >= 0:
284 host, port = host[:i], host[i+1:]
285 try: port = int(port)
286 except ValueError:
287 raise socket.error, "nonnumeric port"
288 if not port: port = SMTP_PORT
289 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
290 msg = "getaddrinfo returns an empty list"
291 self.sock = None
292 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
293 af, socktype, proto, canonname, sa = res
294 try:
295 self.sock = socket.socket(af, socktype, proto)
296 if self.debuglevel > 0: print>>stderr, 'connect:', sa
297 self.sock.connect(sa)
298 except socket.error, msg:
299 if self.debuglevel > 0: print>>stderr, 'connect fail:', msg
300 if self.sock:
301 self.sock.close()
302 self.sock = None
303 continue
304 break
305 if not self.sock:
306 raise socket.error, msg
307 (code, msg) = self.getreply()
308 if self.debuglevel > 0: print>>stderr, "connect:", msg
309 return (code, msg)
311 def send(self, str):
312 """Send `str' to the server."""
313 if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
314 if self.sock:
315 try:
316 self.sock.sendall(str)
317 except socket.error:
318 self.close()
319 raise SMTPServerDisconnected('Server not connected')
320 else:
321 raise SMTPServerDisconnected('please run connect() first')
323 def putcmd(self, cmd, args=""):
324 """Send a command to the server."""
325 if args == "":
326 str = '%s%s' % (cmd, CRLF)
327 else:
328 str = '%s %s%s' % (cmd, args, CRLF)
329 self.send(str)
331 def getreply(self):
332 """Get a reply from the server.
334 Returns a tuple consisting of:
336 - server response code (e.g. '250', or such, if all goes well)
337 Note: returns -1 if it can't read response code.
339 - server response string corresponding to response code (multiline
340 responses are converted to a single, multiline string).
342 Raises SMTPServerDisconnected if end-of-file is reached.
344 resp=[]
345 if self.file is None:
346 self.file = self.sock.makefile('rb')
347 while 1:
348 line = self.file.readline()
349 if line == '':
350 self.close()
351 raise SMTPServerDisconnected("Connection unexpectedly closed")
352 if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
353 resp.append(line[4:].strip())
354 code=line[:3]
355 # Check that the error code is syntactically correct.
356 # Don't attempt to read a continuation line if it is broken.
357 try:
358 errcode = int(code)
359 except ValueError:
360 errcode = -1
361 break
362 # Check if multiline response.
363 if line[3:4]!="-":
364 break
366 errmsg = "\n".join(resp)
367 if self.debuglevel > 0:
368 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
369 return errcode, errmsg
371 def docmd(self, cmd, args=""):
372 """Send a command, and return its response code."""
373 self.putcmd(cmd,args)
374 return self.getreply()
376 # std smtp commands
377 def helo(self, name=''):
378 """SMTP 'helo' command.
379 Hostname to send for this command defaults to the FQDN of the local
380 host.
382 self.putcmd("helo", name or self.local_hostname)
383 (code,msg)=self.getreply()
384 self.helo_resp=msg
385 return (code,msg)
387 def ehlo(self, name=''):
388 """ SMTP 'ehlo' command.
389 Hostname to send for this command defaults to the FQDN of the local
390 host.
392 self.esmtp_features = {}
393 self.putcmd("ehlo", name or self.local_hostname)
394 (code,msg)=self.getreply()
395 # According to RFC1869 some (badly written)
396 # MTA's will disconnect on an ehlo. Toss an exception if
397 # that happens -ddm
398 if code == -1 and len(msg) == 0:
399 self.close()
400 raise SMTPServerDisconnected("Server not connected")
401 self.ehlo_resp=msg
402 if code != 250:
403 return (code,msg)
404 self.does_esmtp=1
405 #parse the ehlo response -ddm
406 resp=self.ehlo_resp.split('\n')
407 del resp[0]
408 for each in resp:
409 # To be able to communicate with as many SMTP servers as possible,
410 # we have to take the old-style auth advertisement into account,
411 # because:
412 # 1) Else our SMTP feature parser gets confused.
413 # 2) There are some servers that only advertise the auth methods we
414 # support using the old style.
415 auth_match = OLDSTYLE_AUTH.match(each)
416 if auth_match:
417 # This doesn't remove duplicates, but that's no problem
418 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
419 + " " + auth_match.groups(0)[0]
420 continue
422 # RFC 1869 requires a space between ehlo keyword and parameters.
423 # It's actually stricter, in that only spaces are allowed between
424 # parameters, but were not going to check for that here. Note
425 # that the space isn't present if there are no parameters.
426 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
427 if m:
428 feature=m.group("feature").lower()
429 params=m.string[m.end("feature"):].strip()
430 if feature == "auth":
431 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
432 + " " + params
433 else:
434 self.esmtp_features[feature]=params
435 return (code,msg)
437 def has_extn(self, opt):
438 """Does the server support a given SMTP service extension?"""
439 return opt.lower() in self.esmtp_features
441 def help(self, args=''):
442 """SMTP 'help' command.
443 Returns help text from server."""
444 self.putcmd("help", args)
445 return self.getreply()[1]
447 def rset(self):
448 """SMTP 'rset' command -- resets session."""
449 return self.docmd("rset")
451 def noop(self):
452 """SMTP 'noop' command -- doesn't do anything :>"""
453 return self.docmd("noop")
455 def mail(self,sender,options=[]):
456 """SMTP 'mail' command -- begins mail xfer session."""
457 optionlist = ''
458 if options and self.does_esmtp:
459 optionlist = ' ' + ' '.join(options)
460 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
461 return self.getreply()
463 def rcpt(self,recip,options=[]):
464 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
465 optionlist = ''
466 if options and self.does_esmtp:
467 optionlist = ' ' + ' '.join(options)
468 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
469 return self.getreply()
471 def data(self,msg):
472 """SMTP 'DATA' command -- sends message data to server.
474 Automatically quotes lines beginning with a period per rfc821.
475 Raises SMTPDataError if there is an unexpected reply to the
476 DATA command; the return value from this method is the final
477 response code received when the all data is sent.
479 self.putcmd("data")
480 (code,repl)=self.getreply()
481 if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
482 if code != 354:
483 raise SMTPDataError(code,repl)
484 else:
485 q = quotedata(msg)
486 if q[-2:] != CRLF:
487 q = q + CRLF
488 q = q + "." + CRLF
489 self.send(q)
490 (code,msg)=self.getreply()
491 if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
492 return (code,msg)
494 def verify(self, address):
495 """SMTP 'verify' command -- checks for address validity."""
496 self.putcmd("vrfy", quoteaddr(address))
497 return self.getreply()
498 # a.k.a.
499 vrfy=verify
501 def expn(self, address):
502 """SMTP 'verify' command -- checks for address validity."""
503 self.putcmd("expn", quoteaddr(address))
504 return self.getreply()
506 # some useful methods
508 def login(self, user, password):
509 """Log in on an SMTP server that requires authentication.
511 The arguments are:
512 - user: The user name to authenticate with.
513 - password: The password for the authentication.
515 If there has been no previous EHLO or HELO command this session, this
516 method tries ESMTP EHLO first.
518 This method will return normally if the authentication was successful.
520 This method may raise the following exceptions:
522 SMTPHeloError The server didn't reply properly to
523 the helo greeting.
524 SMTPAuthenticationError The server didn't accept the username/
525 password combination.
526 SMTPException No suitable authentication method was
527 found.
530 def encode_cram_md5(challenge, user, password):
531 challenge = base64.decodestring(challenge)
532 response = user + " " + hmac.HMAC(password, challenge).hexdigest()
533 return encode_base64(response, eol="")
535 def encode_plain(user, password):
536 return encode_base64("\0%s\0%s" % (user, password), eol="")
539 AUTH_PLAIN = "PLAIN"
540 AUTH_CRAM_MD5 = "CRAM-MD5"
541 AUTH_LOGIN = "LOGIN"
543 if self.helo_resp is None and self.ehlo_resp is None:
544 if not (200 <= self.ehlo()[0] <= 299):
545 (code, resp) = self.helo()
546 if not (200 <= code <= 299):
547 raise SMTPHeloError(code, resp)
549 if not self.has_extn("auth"):
550 raise SMTPException("SMTP AUTH extension not supported by server.")
552 # Authentication methods the server supports:
553 authlist = self.esmtp_features["auth"].split()
555 # List of authentication methods we support: from preferred to
556 # less preferred methods. Except for the purpose of testing the weaker
557 # ones, we prefer stronger methods like CRAM-MD5:
558 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
560 # Determine the authentication method we'll use
561 authmethod = None
562 for method in preferred_auths:
563 if method in authlist:
564 authmethod = method
565 break
567 if authmethod == AUTH_CRAM_MD5:
568 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
569 if code == 503:
570 # 503 == 'Error: already authenticated'
571 return (code, resp)
572 (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
573 elif authmethod == AUTH_PLAIN:
574 (code, resp) = self.docmd("AUTH",
575 AUTH_PLAIN + " " + encode_plain(user, password))
576 elif authmethod == AUTH_LOGIN:
577 (code, resp) = self.docmd("AUTH",
578 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
579 if code != 334:
580 raise SMTPAuthenticationError(code, resp)
581 (code, resp) = self.docmd(encode_base64(password, eol=""))
582 elif authmethod is None:
583 raise SMTPException("No suitable authentication method found.")
584 if code not in (235, 503):
585 # 235 == 'Authentication successful'
586 # 503 == 'Error: already authenticated'
587 raise SMTPAuthenticationError(code, resp)
588 return (code, resp)
590 def starttls(self, keyfile = None, certfile = None):
591 """Puts the connection to the SMTP server into TLS mode.
593 If the server supports TLS, this will encrypt the rest of the SMTP
594 session. If you provide the keyfile and certfile parameters,
595 the identity of the SMTP server and client can be checked. This,
596 however, depends on whether the socket module really checks the
597 certificates.
599 (resp, reply) = self.docmd("STARTTLS")
600 if resp == 220:
601 sslobj = socket.ssl(self.sock, keyfile, certfile)
602 self.sock = SSLFakeSocket(self.sock, sslobj)
603 self.file = SSLFakeFile(sslobj)
604 return (resp, reply)
606 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
607 rcpt_options=[]):
608 """This command performs an entire mail transaction.
610 The arguments are:
611 - from_addr : The address sending this mail.
612 - to_addrs : A list of addresses to send this mail to. A bare
613 string will be treated as a list with 1 address.
614 - msg : The message to send.
615 - mail_options : List of ESMTP options (such as 8bitmime) for the
616 mail command.
617 - rcpt_options : List of ESMTP options (such as DSN commands) for
618 all the rcpt commands.
620 If there has been no previous EHLO or HELO command this session, this
621 method tries ESMTP EHLO first. If the server does ESMTP, message size
622 and each of the specified options will be passed to it. If EHLO
623 fails, HELO will be tried and ESMTP options suppressed.
625 This method will return normally if the mail is accepted for at least
626 one recipient. It returns a dictionary, with one entry for each
627 recipient that was refused. Each entry contains a tuple of the SMTP
628 error code and the accompanying error message sent by the server.
630 This method may raise the following exceptions:
632 SMTPHeloError The server didn't reply properly to
633 the helo greeting.
634 SMTPRecipientsRefused The server rejected ALL recipients
635 (no mail was sent).
636 SMTPSenderRefused The server didn't accept the from_addr.
637 SMTPDataError The server replied with an unexpected
638 error code (other than a refusal of
639 a recipient).
641 Note: the connection will be open even after an exception is raised.
643 Example:
645 >>> import smtplib
646 >>> s=smtplib.SMTP("localhost")
647 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
648 >>> msg = '''\\
649 ... From: Me@my.org
650 ... Subject: testin'...
652 ... This is a test '''
653 >>> s.sendmail("me@my.org",tolist,msg)
654 { "three@three.org" : ( 550 ,"User unknown" ) }
655 >>> s.quit()
657 In the above example, the message was accepted for delivery to three
658 of the four addresses, and one was rejected, with the error code
659 550. If all addresses are accepted, then the method will return an
660 empty dictionary.
663 if self.helo_resp is None and self.ehlo_resp is None:
664 if not (200 <= self.ehlo()[0] <= 299):
665 (code,resp) = self.helo()
666 if not (200 <= code <= 299):
667 raise SMTPHeloError(code, resp)
668 esmtp_opts = []
669 if self.does_esmtp:
670 # Hmmm? what's this? -ddm
671 # self.esmtp_features['7bit']=""
672 if self.has_extn('size'):
673 esmtp_opts.append("size=%d" % len(msg))
674 for option in mail_options:
675 esmtp_opts.append(option)
677 (code,resp) = self.mail(from_addr, esmtp_opts)
678 if code != 250:
679 self.rset()
680 raise SMTPSenderRefused(code, resp, from_addr)
681 senderrs={}
682 if isinstance(to_addrs, basestring):
683 to_addrs = [to_addrs]
684 for each in to_addrs:
685 (code,resp)=self.rcpt(each, rcpt_options)
686 if (code != 250) and (code != 251):
687 senderrs[each]=(code,resp)
688 if len(senderrs)==len(to_addrs):
689 # the server refused all our recipients
690 self.rset()
691 raise SMTPRecipientsRefused(senderrs)
692 (code,resp) = self.data(msg)
693 if code != 250:
694 self.rset()
695 raise SMTPDataError(code, resp)
696 #if we got here then somebody got our mail
697 return senderrs
700 def close(self):
701 """Close the connection to the SMTP server."""
702 if self.file:
703 self.file.close()
704 self.file = None
705 if self.sock:
706 self.sock.close()
707 self.sock = None
710 def quit(self):
711 """Terminate the SMTP session."""
712 self.docmd("quit")
713 self.close()
716 # Test the sendmail method, which tests most of the others.
717 # Note: This always sends to localhost.
718 if __name__ == '__main__':
719 import sys
721 def prompt(prompt):
722 sys.stdout.write(prompt + ": ")
723 return sys.stdin.readline().strip()
725 fromaddr = prompt("From")
726 toaddrs = prompt("To").split(',')
727 print "Enter message, end with ^D:"
728 msg = ''
729 while 1:
730 line = sys.stdin.readline()
731 if not line:
732 break
733 msg = msg + line
734 print "Message length is %d" % len(msg)
736 server = SMTP('localhost')
737 server.set_debuglevel(1)
738 server.sendmail(fromaddr, toaddrs, msg)
739 server.quit()