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