This commit was manufactured by cvs2svn to create tag 'r212'.
[python/dscho.git] / Lib / smtplib.py
blobd01641662f2d5bd45c1e1c752758a6b2c72d80c3
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:
212 i = host.find(':')
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 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
220 if self.debuglevel > 0: print 'connect:', (host, port)
221 try:
222 self.sock.connect((host, port))
223 except socket.error:
224 self.close()
225 raise
226 (code,msg)=self.getreply()
227 if self.debuglevel >0 : print "connect:", msg
228 return (code,msg)
230 def send(self, str):
231 """Send `str' to the server."""
232 if self.debuglevel > 0: print 'send:', `str`
233 if self.sock:
234 try:
235 self.sock.sendall(str)
236 except socket.error:
237 raise SMTPServerDisconnected('Server not connected')
238 else:
239 raise SMTPServerDisconnected('please run connect() first')
241 def putcmd(self, cmd, args=""):
242 """Send a command to the server."""
243 if args == "":
244 str = '%s%s' % (cmd, CRLF)
245 else:
246 str = '%s %s%s' % (cmd, args, CRLF)
247 self.send(str)
249 def getreply(self):
250 """Get a reply from the server.
252 Returns a tuple consisting of:
254 - server response code (e.g. '250', or such, if all goes well)
255 Note: returns -1 if it can't read response code.
257 - server response string corresponding to response code (multiline
258 responses are converted to a single, multiline string).
260 Raises SMTPServerDisconnected if end-of-file is reached.
262 resp=[]
263 if self.file is None:
264 self.file = self.sock.makefile('rb')
265 while 1:
266 line = self.file.readline()
267 if line == '':
268 self.close()
269 raise SMTPServerDisconnected("Connection unexpectedly closed")
270 if self.debuglevel > 0: print 'reply:', `line`
271 resp.append(line[4:].strip())
272 code=line[:3]
273 # Check that the error code is syntactically correct.
274 # Don't attempt to read a continuation line if it is broken.
275 try:
276 errcode = int(code)
277 except ValueError:
278 errcode = -1
279 break
280 # Check if multiline response.
281 if line[3:4]!="-":
282 break
284 errmsg = "\n".join(resp)
285 if self.debuglevel > 0:
286 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
287 return errcode, errmsg
289 def docmd(self, cmd, args=""):
290 """Send a command, and return its response code."""
291 self.putcmd(cmd,args)
292 return self.getreply()
294 # std smtp commands
295 def helo(self, name=''):
296 """SMTP 'helo' command.
297 Hostname to send for this command defaults to the FQDN of the local
298 host.
300 if name:
301 self.putcmd("helo", name)
302 else:
303 self.putcmd("helo", socket.getfqdn())
304 (code,msg)=self.getreply()
305 self.helo_resp=msg
306 return (code,msg)
308 def ehlo(self, name=''):
309 """ SMTP 'ehlo' command.
310 Hostname to send for this command defaults to the FQDN of the local
311 host.
313 if name:
314 self.putcmd("ehlo", name)
315 else:
316 self.putcmd("ehlo", socket.getfqdn())
317 (code,msg)=self.getreply()
318 # According to RFC1869 some (badly written)
319 # MTA's will disconnect on an ehlo. Toss an exception if
320 # that happens -ddm
321 if code == -1 and len(msg) == 0:
322 raise SMTPServerDisconnected("Server not connected")
323 self.ehlo_resp=msg
324 if code != 250:
325 return (code,msg)
326 self.does_esmtp=1
327 #parse the ehlo response -ddm
328 resp=self.ehlo_resp.split('\n')
329 del resp[0]
330 for each in resp:
331 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
332 if m:
333 feature=m.group("feature").lower()
334 params=m.string[m.end("feature"):].strip()
335 self.esmtp_features[feature]=params
336 return (code,msg)
338 def has_extn(self, opt):
339 """Does the server support a given SMTP service extension?"""
340 return self.esmtp_features.has_key(opt.lower())
342 def help(self, args=''):
343 """SMTP 'help' command.
344 Returns help text from server."""
345 self.putcmd("help", args)
346 return self.getreply()
348 def rset(self):
349 """SMTP 'rset' command -- resets session."""
350 return self.docmd("rset")
352 def noop(self):
353 """SMTP 'noop' command -- doesn't do anything :>"""
354 return self.docmd("noop")
356 def mail(self,sender,options=[]):
357 """SMTP 'mail' command -- begins mail xfer session."""
358 optionlist = ''
359 if options and self.does_esmtp:
360 optionlist = ' ' + ' '.join(options)
361 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
362 return self.getreply()
364 def rcpt(self,recip,options=[]):
365 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
366 optionlist = ''
367 if options and self.does_esmtp:
368 optionlist = ' ' + ' '.join(options)
369 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
370 return self.getreply()
372 def data(self,msg):
373 """SMTP 'DATA' command -- sends message data to server.
375 Automatically quotes lines beginning with a period per rfc821.
376 Raises SMTPDataError if there is an unexpected reply to the
377 DATA command; the return value from this method is the final
378 response code received when the all data is sent.
380 self.putcmd("data")
381 (code,repl)=self.getreply()
382 if self.debuglevel >0 : print "data:", (code,repl)
383 if code != 354:
384 raise SMTPDataError(code,repl)
385 else:
386 q = quotedata(msg)
387 if q[-2:] != CRLF:
388 q = q + CRLF
389 q = q + "." + CRLF
390 self.send(q)
391 (code,msg)=self.getreply()
392 if self.debuglevel >0 : print "data:", (code,msg)
393 return (code,msg)
395 def verify(self, address):
396 """SMTP 'verify' command -- checks for address validity."""
397 self.putcmd("vrfy", quoteaddr(address))
398 return self.getreply()
399 # a.k.a.
400 vrfy=verify
402 def expn(self, address):
403 """SMTP 'verify' command -- checks for address validity."""
404 self.putcmd("expn", quoteaddr(address))
405 return self.getreply()
407 # some useful methods
408 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
409 rcpt_options=[]):
410 """This command performs an entire mail transaction.
412 The arguments are:
413 - from_addr : The address sending this mail.
414 - to_addrs : A list of addresses to send this mail to. A bare
415 string will be treated as a list with 1 address.
416 - msg : The message to send.
417 - mail_options : List of ESMTP options (such as 8bitmime) for the
418 mail command.
419 - rcpt_options : List of ESMTP options (such as DSN commands) for
420 all the rcpt commands.
422 If there has been no previous EHLO or HELO command this session, this
423 method tries ESMTP EHLO first. If the server does ESMTP, message size
424 and each of the specified options will be passed to it. If EHLO
425 fails, HELO will be tried and ESMTP options suppressed.
427 This method will return normally if the mail is accepted for at least
428 one recipient. It returns a dictionary, with one entry for each
429 recipient that was refused. Each entry contains a tuple of the SMTP
430 error code and the accompanying error message sent by the server.
432 This method may raise the following exceptions:
434 SMTPHeloError The server didn't reply properly to
435 the helo greeting.
436 SMTPRecipientsRefused The server rejected ALL recipients
437 (no mail was sent).
438 SMTPSenderRefused The server didn't accept the from_addr.
439 SMTPDataError The server replied with an unexpected
440 error code (other than a refusal of
441 a recipient).
443 Note: the connection will be open even after an exception is raised.
445 Example:
447 >>> import smtplib
448 >>> s=smtplib.SMTP("localhost")
449 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
450 >>> msg = '''
451 ... From: Me@my.org
452 ... Subject: testin'...
454 ... This is a test '''
455 >>> s.sendmail("me@my.org",tolist,msg)
456 { "three@three.org" : ( 550 ,"User unknown" ) }
457 >>> s.quit()
459 In the above example, the message was accepted for delivery to three
460 of the four addresses, and one was rejected, with the error code
461 550. If all addresses are accepted, then the method will return an
462 empty dictionary.
465 if self.helo_resp is None and self.ehlo_resp is None:
466 if not (200 <= self.ehlo()[0] <= 299):
467 (code,resp) = self.helo()
468 if not (200 <= code <= 299):
469 raise SMTPHeloError(code, resp)
470 esmtp_opts = []
471 if self.does_esmtp:
472 # Hmmm? what's this? -ddm
473 # self.esmtp_features['7bit']=""
474 if self.has_extn('size'):
475 esmtp_opts.append("size=" + `len(msg)`)
476 for option in mail_options:
477 esmtp_opts.append(option)
479 (code,resp) = self.mail(from_addr, esmtp_opts)
480 if code != 250:
481 self.rset()
482 raise SMTPSenderRefused(code, resp, from_addr)
483 senderrs={}
484 if type(to_addrs) == types.StringType:
485 to_addrs = [to_addrs]
486 for each in to_addrs:
487 (code,resp)=self.rcpt(each, rcpt_options)
488 if (code != 250) and (code != 251):
489 senderrs[each]=(code,resp)
490 if len(senderrs)==len(to_addrs):
491 # the server refused all our recipients
492 self.rset()
493 raise SMTPRecipientsRefused(senderrs)
494 (code,resp) = self.data(msg)
495 if code != 250:
496 self.rset()
497 raise SMTPDataError(code, resp)
498 #if we got here then somebody got our mail
499 return senderrs
502 def close(self):
503 """Close the connection to the SMTP server."""
504 if self.file:
505 self.file.close()
506 self.file = None
507 if self.sock:
508 self.sock.close()
509 self.sock = None
512 def quit(self):
513 """Terminate the SMTP session."""
514 self.docmd("quit")
515 self.close()
518 # Test the sendmail method, which tests most of the others.
519 # Note: This always sends to localhost.
520 if __name__ == '__main__':
521 import sys, rfc822
523 def prompt(prompt):
524 sys.stdout.write(prompt + ": ")
525 return sys.stdin.readline().strip()
527 fromaddr = prompt("From")
528 toaddrs = prompt("To").split(',')
529 print "Enter message, end with ^D:"
530 msg = ''
531 while 1:
532 line = sys.stdin.readline()
533 if not line:
534 break
535 msg = msg + line
536 print "Message length is " + `len(msg)`
538 server = SMTP('localhost')
539 server.set_debuglevel(1)
540 server.sendmail(fromaddr, toaddrs, msg)
541 server.quit()