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