Merged release21-maint changes.
[python/dscho.git] / Lib / smtplib.py
blob3d28b0d56682ffa4d10595a4e0d5a75398d696fb
1 #! /usr/bin/env python
3 '''SMTP/ESMTP client class.
5 This should follow RFC 821 (SMTP) and RFC 1869 (ESMTP).
7 Notes:
9 Please remember, when doing ESMTP, that the names of the SMTP service
10 extensions are NOT the same thing as the option keywords for the RCPT
11 and MAIL commands!
13 Example:
15 >>> import smtplib
16 >>> s=smtplib.SMTP("localhost")
17 >>> print s.help()
18 This is Sendmail version 8.8.4
19 Topics:
20 HELO EHLO MAIL RCPT DATA
21 RSET NOOP QUIT HELP VRFY
22 EXPN VERB ETRN DSN
23 For more info use "HELP <topic>".
24 To report bugs in the implementation send email to
25 sendmail-bugs@sendmail.org.
26 For local information send email to Postmaster at your site.
27 End of HELP info
28 >>> s.putcmd("vrfy","someone@here")
29 >>> s.getreply()
30 (250, "Somebody OverHere <somebody@here.my.org>")
31 >>> s.quit()
32 '''
34 # Author: The Dragon De Monsyne <dragondm@integral.org>
35 # ESMTP support, test code and doc fixes added by
36 # Eric S. Raymond <esr@thyrsus.com>
37 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
38 # by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
40 # This was modified from the Python 1.5 library HTTP lib.
42 import socket
43 import re
44 import rfc822
45 import types
47 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
48 "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
49 "SMTPConnectError","SMTPHeloError","quoteaddr","quotedata",
50 "SMTP"]
52 SMTP_PORT = 25
53 CRLF="\r\n"
55 # Exception classes used by this module.
56 class SMTPException(Exception):
57 """Base class for all exceptions raised by this module."""
59 class SMTPServerDisconnected(SMTPException):
60 """Not connected to any SMTP server.
62 This exception is raised when the server unexpectedly disconnects,
63 or when an attempt is made to use the SMTP instance before
64 connecting it to a server.
65 """
67 class SMTPResponseException(SMTPException):
68 """Base class for all exceptions that include an SMTP error code.
70 These exceptions are generated in some instances when the SMTP
71 server returns an error code. The error code is stored in the
72 `smtp_code' attribute of the error, and the `smtp_error' attribute
73 is set to the error message.
74 """
76 def __init__(self, code, msg):
77 self.smtp_code = code
78 self.smtp_error = msg
79 self.args = (code, msg)
81 class SMTPSenderRefused(SMTPResponseException):
82 """Sender address refused.
83 In addition to the attributes set by on all SMTPResponseException
84 exceptions, this sets `sender' to the string that the SMTP refused.
85 """
87 def __init__(self, code, msg, sender):
88 self.smtp_code = code
89 self.smtp_error = msg
90 self.sender = sender
91 self.args = (code, msg, sender)
93 class SMTPRecipientsRefused(SMTPException):
94 """All recipient addresses refused.
95 The errors for each recipient are accessible through the attribute
96 'recipients', which is a dictionary of exactly the same sort as
97 SMTP.sendmail() returns.
98 """
100 def __init__(self, recipients):
101 self.recipients = recipients
102 self.args = ( recipients,)
105 class SMTPDataError(SMTPResponseException):
106 """The SMTP server didn't accept the data."""
108 class SMTPConnectError(SMTPResponseException):
109 """Error during connection establishment."""
111 class SMTPHeloError(SMTPResponseException):
112 """The server refused our HELO reply."""
115 def quoteaddr(addr):
116 """Quote a subset of the email addresses defined by RFC 821.
118 Should be able to handle anything rfc822.parseaddr can handle.
120 m=None
121 try:
122 m=rfc822.parseaddr(addr)[1]
123 except AttributeError:
124 pass
125 if not m:
126 #something weird here.. punt -ddm
127 return addr
128 else:
129 return "<%s>" % m
131 def quotedata(data):
132 """Quote data for email.
134 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
135 Internet CRLF end-of-line.
137 return re.sub(r'(?m)^\.', '..',
138 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
141 class SMTP:
142 """This class manages a connection to an SMTP or ESMTP server.
143 SMTP Objects:
144 SMTP objects have the following attributes:
145 helo_resp
146 This is the message given by the server in response to the
147 most recent HELO command.
149 ehlo_resp
150 This is the message given by the server in response to the
151 most recent EHLO command. This is usually multiline.
153 does_esmtp
154 This is a True value _after you do an EHLO command_, if the
155 server supports ESMTP.
157 esmtp_features
158 This is a dictionary, which, if the server supports ESMTP,
159 will _after you do an EHLO command_, contain the names of the
160 SMTP service extensions this server supports, and their
161 parameters (if any).
163 Note, all extension names are mapped to lower case in the
164 dictionary.
166 See each method's docstrings for details. In general, there is a
167 method of the same name to perform each SMTP command. There is also a
168 method called 'sendmail' that will do an entire mail transaction.
170 debuglevel = 0
171 file = None
172 helo_resp = None
173 ehlo_resp = None
174 does_esmtp = 0
176 def __init__(self, host = '', port = 0):
177 """Initialize a new instance.
179 If specified, `host' is the name of the remote host to which to
180 connect. If specified, `port' specifies the port to which to connect.
181 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
182 if the specified `host' doesn't respond correctly.
185 self.esmtp_features = {}
186 if host:
187 (code, msg) = self.connect(host, port)
188 if code != 220:
189 raise SMTPConnectError(code, msg)
191 def set_debuglevel(self, debuglevel):
192 """Set the debug output level.
194 A non-false value results in debug messages for connection and for all
195 messages sent to and received from the server.
198 self.debuglevel = debuglevel
200 def connect(self, host='localhost', port = 0):
201 """Connect to a host on a given port.
203 If the hostname ends with a colon (`:') followed by a number, and
204 there is no port specified, that suffix will be stripped off and the
205 number interpreted as the port number to use.
207 Note: This method is automatically invoked by __init__, if a host is
208 specified during instantiation.
211 if not port and (host.find(':') == host.rfind(':')):
212 i = host.rfind(':')
213 if i >= 0:
214 host, port = host[:i], host[i+1:]
215 try: port = int(port)
216 except ValueError:
217 raise socket.error, "nonnumeric port"
218 if not port: port = SMTP_PORT
219 if self.debuglevel > 0: print 'connect:', (host, port)
220 msg = "getaddrinfo returns an empty list"
221 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
222 af, socktype, proto, canonname, sa = res
223 try:
224 self.sock = socket.socket(af, socktype, proto)
225 if self.debuglevel > 0: print 'connect:', (host, port)
226 self.sock.connect(sa)
227 except socket.error, msg:
228 if self.debuglevel > 0: print 'connect fail:', (host, port)
229 self.sock.close()
230 self.sock = None
231 continue
232 break
233 if not self.sock:
234 raise socket.error, msg
235 (code, msg) = self.getreply()
236 if self.debuglevel > 0: print "connect:", msg
237 return (code, msg)
239 def send(self, str):
240 """Send `str' to the server."""
241 if self.debuglevel > 0: print 'send:', `str`
242 if self.sock:
243 try:
244 sendptr = 0
245 while sendptr < len(str):
246 sendptr = sendptr + self.sock.send(str[sendptr:])
247 except socket.error:
248 raise SMTPServerDisconnected('Server not connected')
249 else:
250 raise SMTPServerDisconnected('please run connect() first')
252 def putcmd(self, cmd, args=""):
253 """Send a command to the server."""
254 if args == "":
255 str = '%s%s' % (cmd, CRLF)
256 else:
257 str = '%s %s%s' % (cmd, args, CRLF)
258 self.send(str)
260 def getreply(self):
261 """Get a reply from the server.
263 Returns a tuple consisting of:
265 - server response code (e.g. '250', or such, if all goes well)
266 Note: returns -1 if it can't read response code.
268 - server response string corresponding to response code (multiline
269 responses are converted to a single, multiline string).
271 Raises SMTPServerDisconnected if end-of-file is reached.
273 resp=[]
274 if self.file is None:
275 self.file = self.sock.makefile('rb')
276 while 1:
277 line = self.file.readline()
278 if line == '':
279 self.close()
280 raise SMTPServerDisconnected("Connection unexpectedly closed")
281 if self.debuglevel > 0: print 'reply:', `line`
282 resp.append(line[4:].strip())
283 code=line[:3]
284 # Check that the error code is syntactically correct.
285 # Don't attempt to read a continuation line if it is broken.
286 try:
287 errcode = int(code)
288 except ValueError:
289 errcode = -1
290 break
291 # Check if multiline response.
292 if line[3:4]!="-":
293 break
295 errmsg = "\n".join(resp)
296 if self.debuglevel > 0:
297 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
298 return errcode, errmsg
300 def docmd(self, cmd, args=""):
301 """Send a command, and return its response code."""
302 self.putcmd(cmd,args)
303 return self.getreply()
305 # std smtp commands
306 def helo(self, name=''):
307 """SMTP 'helo' command.
308 Hostname to send for this command defaults to the FQDN of the local
309 host.
311 if name:
312 self.putcmd("helo", name)
313 else:
314 self.putcmd("helo", socket.getfqdn())
315 (code,msg)=self.getreply()
316 self.helo_resp=msg
317 return (code,msg)
319 def ehlo(self, name=''):
320 """ SMTP 'ehlo' command.
321 Hostname to send for this command defaults to the FQDN of the local
322 host.
324 if name:
325 self.putcmd("ehlo", name)
326 else:
327 self.putcmd("ehlo", socket.getfqdn())
328 (code,msg)=self.getreply()
329 # According to RFC1869 some (badly written)
330 # MTA's will disconnect on an ehlo. Toss an exception if
331 # that happens -ddm
332 if code == -1 and len(msg) == 0:
333 raise SMTPServerDisconnected("Server not connected")
334 self.ehlo_resp=msg
335 if code != 250:
336 return (code,msg)
337 self.does_esmtp=1
338 #parse the ehlo response -ddm
339 resp=self.ehlo_resp.split('\n')
340 del resp[0]
341 for each in resp:
342 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
343 if m:
344 feature=m.group("feature").lower()
345 params=m.string[m.end("feature"):].strip()
346 self.esmtp_features[feature]=params
347 return (code,msg)
349 def has_extn(self, opt):
350 """Does the server support a given SMTP service extension?"""
351 return self.esmtp_features.has_key(opt.lower())
353 def help(self, args=''):
354 """SMTP 'help' command.
355 Returns help text from server."""
356 self.putcmd("help", args)
357 return self.getreply()
359 def rset(self):
360 """SMTP 'rset' command -- resets session."""
361 return self.docmd("rset")
363 def noop(self):
364 """SMTP 'noop' command -- doesn't do anything :>"""
365 return self.docmd("noop")
367 def mail(self,sender,options=[]):
368 """SMTP 'mail' command -- begins mail xfer session."""
369 optionlist = ''
370 if options and self.does_esmtp:
371 optionlist = ' ' + ' '.join(options)
372 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
373 return self.getreply()
375 def rcpt(self,recip,options=[]):
376 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
377 optionlist = ''
378 if options and self.does_esmtp:
379 optionlist = ' ' + ' '.join(options)
380 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
381 return self.getreply()
383 def data(self,msg):
384 """SMTP 'DATA' command -- sends message data to server.
386 Automatically quotes lines beginning with a period per rfc821.
387 Raises SMTPDataError if there is an unexpected reply to the
388 DATA command; the return value from this method is the final
389 response code received when the all data is sent.
391 self.putcmd("data")
392 (code,repl)=self.getreply()
393 if self.debuglevel >0 : print "data:", (code,repl)
394 if code != 354:
395 raise SMTPDataError(code,repl)
396 else:
397 q = quotedata(msg)
398 if q[-2:] != CRLF:
399 q = q + CRLF
400 q = q + "." + CRLF
401 self.send(q)
402 (code,msg)=self.getreply()
403 if self.debuglevel >0 : print "data:", (code,msg)
404 return (code,msg)
406 def verify(self, address):
407 """SMTP 'verify' command -- checks for address validity."""
408 self.putcmd("vrfy", quoteaddr(address))
409 return self.getreply()
410 # a.k.a.
411 vrfy=verify
413 def expn(self, address):
414 """SMTP 'verify' command -- checks for address validity."""
415 self.putcmd("expn", quoteaddr(address))
416 return self.getreply()
418 # some useful methods
419 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
420 rcpt_options=[]):
421 """This command performs an entire mail transaction.
423 The arguments are:
424 - from_addr : The address sending this mail.
425 - to_addrs : A list of addresses to send this mail to. A bare
426 string will be treated as a list with 1 address.
427 - msg : The message to send.
428 - mail_options : List of ESMTP options (such as 8bitmime) for the
429 mail command.
430 - rcpt_options : List of ESMTP options (such as DSN commands) for
431 all the rcpt commands.
433 If there has been no previous EHLO or HELO command this session, this
434 method tries ESMTP EHLO first. If the server does ESMTP, message size
435 and each of the specified options will be passed to it. If EHLO
436 fails, HELO will be tried and ESMTP options suppressed.
438 This method will return normally if the mail is accepted for at least
439 one recipient. It returns a dictionary, with one entry for each
440 recipient that was refused. Each entry contains a tuple of the SMTP
441 error code and the accompanying error message sent by the server.
443 This method may raise the following exceptions:
445 SMTPHeloError The server didn't reply properly to
446 the helo greeting.
447 SMTPRecipientsRefused The server rejected ALL recipients
448 (no mail was sent).
449 SMTPSenderRefused The server didn't accept the from_addr.
450 SMTPDataError The server replied with an unexpected
451 error code (other than a refusal of
452 a recipient).
454 Note: the connection will be open even after an exception is raised.
456 Example:
458 >>> import smtplib
459 >>> s=smtplib.SMTP("localhost")
460 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
461 >>> msg = '''
462 ... From: Me@my.org
463 ... Subject: testin'...
465 ... This is a test '''
466 >>> s.sendmail("me@my.org",tolist,msg)
467 { "three@three.org" : ( 550 ,"User unknown" ) }
468 >>> s.quit()
470 In the above example, the message was accepted for delivery to three
471 of the four addresses, and one was rejected, with the error code
472 550. If all addresses are accepted, then the method will return an
473 empty dictionary.
476 if self.helo_resp is None and self.ehlo_resp is None:
477 if not (200 <= self.ehlo()[0] <= 299):
478 (code,resp) = self.helo()
479 if not (200 <= code <= 299):
480 raise SMTPHeloError(code, resp)
481 esmtp_opts = []
482 if self.does_esmtp:
483 # Hmmm? what's this? -ddm
484 # self.esmtp_features['7bit']=""
485 if self.has_extn('size'):
486 esmtp_opts.append("size=" + `len(msg)`)
487 for option in mail_options:
488 esmtp_opts.append(option)
490 (code,resp) = self.mail(from_addr, esmtp_opts)
491 if code != 250:
492 self.rset()
493 raise SMTPSenderRefused(code, resp, from_addr)
494 senderrs={}
495 if type(to_addrs) == types.StringType:
496 to_addrs = [to_addrs]
497 for each in to_addrs:
498 (code,resp)=self.rcpt(each, rcpt_options)
499 if (code != 250) and (code != 251):
500 senderrs[each]=(code,resp)
501 if len(senderrs)==len(to_addrs):
502 # the server refused all our recipients
503 self.rset()
504 raise SMTPRecipientsRefused(senderrs)
505 (code,resp) = self.data(msg)
506 if code != 250:
507 self.rset()
508 raise SMTPDataError(code, resp)
509 #if we got here then somebody got our mail
510 return senderrs
513 def close(self):
514 """Close the connection to the SMTP server."""
515 if self.file:
516 self.file.close()
517 self.file = None
518 if self.sock:
519 self.sock.close()
520 self.sock = None
523 def quit(self):
524 """Terminate the SMTP session."""
525 self.docmd("quit")
526 self.close()
529 # Test the sendmail method, which tests most of the others.
530 # Note: This always sends to localhost.
531 if __name__ == '__main__':
532 import sys, rfc822
534 def prompt(prompt):
535 sys.stdout.write(prompt + ": ")
536 return sys.stdin.readline().strip()
538 fromaddr = prompt("From")
539 toaddrs = prompt("To").split(',')
540 print "Enter message, end with ^D:"
541 msg = ''
542 while 1:
543 line = sys.stdin.readline()
544 if not line:
545 break
546 msg = msg + line
547 print "Message length is " + `len(msg)`
549 server = SMTP('localhost')
550 server.set_debuglevel(1)
551 server.sendmail(fromaddr, toaddrs, msg)
552 server.quit()