Exceptions raised during renaming in rotating file handlers are now passed to handleE...
[python.git] / Lib / smtplib.py
blob57605a96dadc5b4757ac3f01c4c4f8a7dd7901b3
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 else:
181 return "<%s>" % m
183 def quotedata(data):
184 """Quote data for email.
186 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
187 Internet CRLF end-of-line.
189 return re.sub(r'(?m)^\.', '..',
190 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
193 class SMTP:
194 """This class manages a connection to an SMTP or ESMTP server.
195 SMTP Objects:
196 SMTP objects have the following attributes:
197 helo_resp
198 This is the message given by the server in response to the
199 most recent HELO command.
201 ehlo_resp
202 This is the message given by the server in response to the
203 most recent EHLO command. This is usually multiline.
205 does_esmtp
206 This is a True value _after you do an EHLO command_, if the
207 server supports ESMTP.
209 esmtp_features
210 This is a dictionary, which, if the server supports ESMTP,
211 will _after you do an EHLO command_, contain the names of the
212 SMTP service extensions this server supports, and their
213 parameters (if any).
215 Note, all extension names are mapped to lower case in the
216 dictionary.
218 See each method's docstrings for details. In general, there is a
219 method of the same name to perform each SMTP command. There is also a
220 method called 'sendmail' that will do an entire mail transaction.
222 debuglevel = 0
223 file = None
224 helo_resp = None
225 ehlo_resp = None
226 does_esmtp = 0
228 def __init__(self, host = '', port = 0, local_hostname = None):
229 """Initialize a new instance.
231 If specified, `host' is the name of the remote host to which to
232 connect. If specified, `port' specifies the port to which to connect.
233 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
234 if the specified `host' doesn't respond correctly. If specified,
235 `local_hostname` is used as the FQDN of the local host. By default,
236 the local hostname is found using socket.getfqdn().
239 self.esmtp_features = {}
240 if host:
241 (code, msg) = self.connect(host, port)
242 if code != 220:
243 raise SMTPConnectError(code, msg)
244 if local_hostname is not None:
245 self.local_hostname = local_hostname
246 else:
247 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
248 # if that can't be calculated, that we should use a domain literal
249 # instead (essentially an encoded IP address like [A.B.C.D]).
250 fqdn = socket.getfqdn()
251 if '.' in fqdn:
252 self.local_hostname = fqdn
253 else:
254 # We can't find an fqdn hostname, so use a domain literal
255 addr = socket.gethostbyname(socket.gethostname())
256 self.local_hostname = '[%s]' % addr
258 def set_debuglevel(self, debuglevel):
259 """Set the debug output level.
261 A non-false value results in debug messages for connection and for all
262 messages sent to and received from the server.
265 self.debuglevel = debuglevel
267 def connect(self, host='localhost', port = 0):
268 """Connect to a host on a given port.
270 If the hostname ends with a colon (`:') followed by a number, and
271 there is no port specified, that suffix will be stripped off and the
272 number interpreted as the port number to use.
274 Note: This method is automatically invoked by __init__, if a host is
275 specified during instantiation.
278 if not port and (host.find(':') == host.rfind(':')):
279 i = host.rfind(':')
280 if i >= 0:
281 host, port = host[:i], host[i+1:]
282 try: port = int(port)
283 except ValueError:
284 raise socket.error, "nonnumeric port"
285 if not port: port = SMTP_PORT
286 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
287 msg = "getaddrinfo returns an empty list"
288 self.sock = None
289 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
290 af, socktype, proto, canonname, sa = res
291 try:
292 self.sock = socket.socket(af, socktype, proto)
293 if self.debuglevel > 0: print>>stderr, 'connect:', sa
294 self.sock.connect(sa)
295 except socket.error, msg:
296 if self.debuglevel > 0: print>>stderr, 'connect fail:', msg
297 if self.sock:
298 self.sock.close()
299 self.sock = None
300 continue
301 break
302 if not self.sock:
303 raise socket.error, msg
304 (code, msg) = self.getreply()
305 if self.debuglevel > 0: print>>stderr, "connect:", msg
306 return (code, msg)
308 def send(self, str):
309 """Send `str' to the server."""
310 if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
311 if self.sock:
312 try:
313 self.sock.sendall(str)
314 except socket.error:
315 self.close()
316 raise SMTPServerDisconnected('Server not connected')
317 else:
318 raise SMTPServerDisconnected('please run connect() first')
320 def putcmd(self, cmd, args=""):
321 """Send a command to the server."""
322 if args == "":
323 str = '%s%s' % (cmd, CRLF)
324 else:
325 str = '%s %s%s' % (cmd, args, CRLF)
326 self.send(str)
328 def getreply(self):
329 """Get a reply from the server.
331 Returns a tuple consisting of:
333 - server response code (e.g. '250', or such, if all goes well)
334 Note: returns -1 if it can't read response code.
336 - server response string corresponding to response code (multiline
337 responses are converted to a single, multiline string).
339 Raises SMTPServerDisconnected if end-of-file is reached.
341 resp=[]
342 if self.file is None:
343 self.file = self.sock.makefile('rb')
344 while 1:
345 line = self.file.readline()
346 if line == '':
347 self.close()
348 raise SMTPServerDisconnected("Connection unexpectedly closed")
349 if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
350 resp.append(line[4:].strip())
351 code=line[:3]
352 # Check that the error code is syntactically correct.
353 # Don't attempt to read a continuation line if it is broken.
354 try:
355 errcode = int(code)
356 except ValueError:
357 errcode = -1
358 break
359 # Check if multiline response.
360 if line[3:4]!="-":
361 break
363 errmsg = "\n".join(resp)
364 if self.debuglevel > 0:
365 print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
366 return errcode, errmsg
368 def docmd(self, cmd, args=""):
369 """Send a command, and return its response code."""
370 self.putcmd(cmd,args)
371 return self.getreply()
373 # std smtp commands
374 def helo(self, name=''):
375 """SMTP 'helo' command.
376 Hostname to send for this command defaults to the FQDN of the local
377 host.
379 self.putcmd("helo", name or self.local_hostname)
380 (code,msg)=self.getreply()
381 self.helo_resp=msg
382 return (code,msg)
384 def ehlo(self, name=''):
385 """ SMTP 'ehlo' command.
386 Hostname to send for this command defaults to the FQDN of the local
387 host.
389 self.esmtp_features = {}
390 self.putcmd("ehlo", name or self.local_hostname)
391 (code,msg)=self.getreply()
392 # According to RFC1869 some (badly written)
393 # MTA's will disconnect on an ehlo. Toss an exception if
394 # that happens -ddm
395 if code == -1 and len(msg) == 0:
396 self.close()
397 raise SMTPServerDisconnected("Server not connected")
398 self.ehlo_resp=msg
399 if code != 250:
400 return (code,msg)
401 self.does_esmtp=1
402 #parse the ehlo response -ddm
403 resp=self.ehlo_resp.split('\n')
404 del resp[0]
405 for each in resp:
406 # To be able to communicate with as many SMTP servers as possible,
407 # we have to take the old-style auth advertisement into account,
408 # because:
409 # 1) Else our SMTP feature parser gets confused.
410 # 2) There are some servers that only advertise the auth methods we
411 # support using the old style.
412 auth_match = OLDSTYLE_AUTH.match(each)
413 if auth_match:
414 # This doesn't remove duplicates, but that's no problem
415 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
416 + " " + auth_match.groups(0)[0]
417 continue
419 # RFC 1869 requires a space between ehlo keyword and parameters.
420 # It's actually stricter, in that only spaces are allowed between
421 # parameters, but were not going to check for that here. Note
422 # that the space isn't present if there are no parameters.
423 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
424 if m:
425 feature=m.group("feature").lower()
426 params=m.string[m.end("feature"):].strip()
427 if feature == "auth":
428 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
429 + " " + params
430 else:
431 self.esmtp_features[feature]=params
432 return (code,msg)
434 def has_extn(self, opt):
435 """Does the server support a given SMTP service extension?"""
436 return opt.lower() in self.esmtp_features
438 def help(self, args=''):
439 """SMTP 'help' command.
440 Returns help text from server."""
441 self.putcmd("help", args)
442 return self.getreply()[1]
444 def rset(self):
445 """SMTP 'rset' command -- resets session."""
446 return self.docmd("rset")
448 def noop(self):
449 """SMTP 'noop' command -- doesn't do anything :>"""
450 return self.docmd("noop")
452 def mail(self,sender,options=[]):
453 """SMTP 'mail' command -- begins mail xfer session."""
454 optionlist = ''
455 if options and self.does_esmtp:
456 optionlist = ' ' + ' '.join(options)
457 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
458 return self.getreply()
460 def rcpt(self,recip,options=[]):
461 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
462 optionlist = ''
463 if options and self.does_esmtp:
464 optionlist = ' ' + ' '.join(options)
465 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
466 return self.getreply()
468 def data(self,msg):
469 """SMTP 'DATA' command -- sends message data to server.
471 Automatically quotes lines beginning with a period per rfc821.
472 Raises SMTPDataError if there is an unexpected reply to the
473 DATA command; the return value from this method is the final
474 response code received when the all data is sent.
476 self.putcmd("data")
477 (code,repl)=self.getreply()
478 if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
479 if code != 354:
480 raise SMTPDataError(code,repl)
481 else:
482 q = quotedata(msg)
483 if q[-2:] != CRLF:
484 q = q + CRLF
485 q = q + "." + CRLF
486 self.send(q)
487 (code,msg)=self.getreply()
488 if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
489 return (code,msg)
491 def verify(self, address):
492 """SMTP 'verify' command -- checks for address validity."""
493 self.putcmd("vrfy", quoteaddr(address))
494 return self.getreply()
495 # a.k.a.
496 vrfy=verify
498 def expn(self, address):
499 """SMTP 'verify' command -- checks for address validity."""
500 self.putcmd("expn", quoteaddr(address))
501 return self.getreply()
503 # some useful methods
505 def login(self, user, password):
506 """Log in on an SMTP server that requires authentication.
508 The arguments are:
509 - user: The user name to authenticate with.
510 - password: The password for the authentication.
512 If there has been no previous EHLO or HELO command this session, this
513 method tries ESMTP EHLO first.
515 This method will return normally if the authentication was successful.
517 This method may raise the following exceptions:
519 SMTPHeloError The server didn't reply properly to
520 the helo greeting.
521 SMTPAuthenticationError The server didn't accept the username/
522 password combination.
523 SMTPException No suitable authentication method was
524 found.
527 def encode_cram_md5(challenge, user, password):
528 challenge = base64.decodestring(challenge)
529 response = user + " " + hmac.HMAC(password, challenge).hexdigest()
530 return encode_base64(response, eol="")
532 def encode_plain(user, password):
533 return encode_base64("\0%s\0%s" % (user, password), eol="")
536 AUTH_PLAIN = "PLAIN"
537 AUTH_CRAM_MD5 = "CRAM-MD5"
538 AUTH_LOGIN = "LOGIN"
540 if self.helo_resp is None and self.ehlo_resp is None:
541 if not (200 <= self.ehlo()[0] <= 299):
542 (code, resp) = self.helo()
543 if not (200 <= code <= 299):
544 raise SMTPHeloError(code, resp)
546 if not self.has_extn("auth"):
547 raise SMTPException("SMTP AUTH extension not supported by server.")
549 # Authentication methods the server supports:
550 authlist = self.esmtp_features["auth"].split()
552 # List of authentication methods we support: from preferred to
553 # less preferred methods. Except for the purpose of testing the weaker
554 # ones, we prefer stronger methods like CRAM-MD5:
555 preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
557 # Determine the authentication method we'll use
558 authmethod = None
559 for method in preferred_auths:
560 if method in authlist:
561 authmethod = method
562 break
564 if authmethod == AUTH_CRAM_MD5:
565 (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
566 if code == 503:
567 # 503 == 'Error: already authenticated'
568 return (code, resp)
569 (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
570 elif authmethod == AUTH_PLAIN:
571 (code, resp) = self.docmd("AUTH",
572 AUTH_PLAIN + " " + encode_plain(user, password))
573 elif authmethod == AUTH_LOGIN:
574 (code, resp) = self.docmd("AUTH",
575 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
576 if code != 334:
577 raise SMTPAuthenticationError(code, resp)
578 (code, resp) = self.docmd(encode_base64(password, eol=""))
579 elif authmethod is None:
580 raise SMTPException("No suitable authentication method found.")
581 if code not in (235, 503):
582 # 235 == 'Authentication successful'
583 # 503 == 'Error: already authenticated'
584 raise SMTPAuthenticationError(code, resp)
585 return (code, resp)
587 def starttls(self, keyfile = None, certfile = None):
588 """Puts the connection to the SMTP server into TLS mode.
590 If the server supports TLS, this will encrypt the rest of the SMTP
591 session. If you provide the keyfile and certfile parameters,
592 the identity of the SMTP server and client can be checked. This,
593 however, depends on whether the socket module really checks the
594 certificates.
596 (resp, reply) = self.docmd("STARTTLS")
597 if resp == 220:
598 sslobj = socket.ssl(self.sock, keyfile, certfile)
599 self.sock = SSLFakeSocket(self.sock, sslobj)
600 self.file = SSLFakeFile(sslobj)
601 return (resp, reply)
603 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
604 rcpt_options=[]):
605 """This command performs an entire mail transaction.
607 The arguments are:
608 - from_addr : The address sending this mail.
609 - to_addrs : A list of addresses to send this mail to. A bare
610 string will be treated as a list with 1 address.
611 - msg : The message to send.
612 - mail_options : List of ESMTP options (such as 8bitmime) for the
613 mail command.
614 - rcpt_options : List of ESMTP options (such as DSN commands) for
615 all the rcpt commands.
617 If there has been no previous EHLO or HELO command this session, this
618 method tries ESMTP EHLO first. If the server does ESMTP, message size
619 and each of the specified options will be passed to it. If EHLO
620 fails, HELO will be tried and ESMTP options suppressed.
622 This method will return normally if the mail is accepted for at least
623 one recipient. It returns a dictionary, with one entry for each
624 recipient that was refused. Each entry contains a tuple of the SMTP
625 error code and the accompanying error message sent by the server.
627 This method may raise the following exceptions:
629 SMTPHeloError The server didn't reply properly to
630 the helo greeting.
631 SMTPRecipientsRefused The server rejected ALL recipients
632 (no mail was sent).
633 SMTPSenderRefused The server didn't accept the from_addr.
634 SMTPDataError The server replied with an unexpected
635 error code (other than a refusal of
636 a recipient).
638 Note: the connection will be open even after an exception is raised.
640 Example:
642 >>> import smtplib
643 >>> s=smtplib.SMTP("localhost")
644 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
645 >>> msg = '''\\
646 ... From: Me@my.org
647 ... Subject: testin'...
649 ... This is a test '''
650 >>> s.sendmail("me@my.org",tolist,msg)
651 { "three@three.org" : ( 550 ,"User unknown" ) }
652 >>> s.quit()
654 In the above example, the message was accepted for delivery to three
655 of the four addresses, and one was rejected, with the error code
656 550. If all addresses are accepted, then the method will return an
657 empty dictionary.
660 if self.helo_resp is None and self.ehlo_resp is None:
661 if not (200 <= self.ehlo()[0] <= 299):
662 (code,resp) = self.helo()
663 if not (200 <= code <= 299):
664 raise SMTPHeloError(code, resp)
665 esmtp_opts = []
666 if self.does_esmtp:
667 # Hmmm? what's this? -ddm
668 # self.esmtp_features['7bit']=""
669 if self.has_extn('size'):
670 esmtp_opts.append("size=%d" % len(msg))
671 for option in mail_options:
672 esmtp_opts.append(option)
674 (code,resp) = self.mail(from_addr, esmtp_opts)
675 if code != 250:
676 self.rset()
677 raise SMTPSenderRefused(code, resp, from_addr)
678 senderrs={}
679 if isinstance(to_addrs, basestring):
680 to_addrs = [to_addrs]
681 for each in to_addrs:
682 (code,resp)=self.rcpt(each, rcpt_options)
683 if (code != 250) and (code != 251):
684 senderrs[each]=(code,resp)
685 if len(senderrs)==len(to_addrs):
686 # the server refused all our recipients
687 self.rset()
688 raise SMTPRecipientsRefused(senderrs)
689 (code,resp) = self.data(msg)
690 if code != 250:
691 self.rset()
692 raise SMTPDataError(code, resp)
693 #if we got here then somebody got our mail
694 return senderrs
697 def close(self):
698 """Close the connection to the SMTP server."""
699 if self.file:
700 self.file.close()
701 self.file = None
702 if self.sock:
703 self.sock.close()
704 self.sock = None
707 def quit(self):
708 """Terminate the SMTP session."""
709 self.docmd("quit")
710 self.close()
713 # Test the sendmail method, which tests most of the others.
714 # Note: This always sends to localhost.
715 if __name__ == '__main__':
716 import sys
718 def prompt(prompt):
719 sys.stdout.write(prompt + ": ")
720 return sys.stdin.readline().strip()
722 fromaddr = prompt("From")
723 toaddrs = prompt("To").split(',')
724 print "Enter message, end with ^D:"
725 msg = ''
726 while 1:
727 line = sys.stdin.readline()
728 if not line:
729 break
730 msg = msg + line
731 print "Message length is %d" % len(msg)
733 server = SMTP('localhost')
734 server.set_debuglevel(1)
735 server.sendmail(fromaddr, toaddrs, msg)
736 server.quit()