Bump version to 0.9.1.
[python/dscho.git] / Lib / smtplib.py
blobf898a2fe1e26d8efbdbd2f3664d314a0e6e2af71
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 string
44 import re
45 import rfc822
46 import types
48 SMTP_PORT = 25
49 CRLF="\r\n"
51 # Exception classes used by this module.
52 class SMTPException(Exception):
53 """Base class for all exceptions raised by this module."""
55 class SMTPServerDisconnected(SMTPException):
56 """Not connected to any SMTP server.
58 This exception is raised when the server unexpectedly disconnects,
59 or when an attempt is made to use the SMTP instance before
60 connecting it to a server.
61 """
63 class SMTPResponseException(SMTPException):
64 """Base class for all exceptions that include an SMTP error code.
66 These exceptions are generated in some instances when the SMTP
67 server returns an error code. The error code is stored in the
68 `smtp_code' attribute of the error, and the `smtp_error' attribute
69 is set to the error message.
70 """
72 def __init__(self, code, msg):
73 self.smtp_code = code
74 self.smtp_error = msg
75 self.args = (code, msg)
77 class SMTPSenderRefused(SMTPResponseException):
78 """Sender address refused.
79 In addition to the attributes set by on all SMTPResponseException
80 exceptions, this sets `sender' to the string that the SMTP refused.
81 """
83 def __init__(self, code, msg, sender):
84 self.smtp_code = code
85 self.smtp_error = msg
86 self.sender = sender
87 self.args = (code, msg, sender)
89 class SMTPRecipientsRefused(SMTPException):
90 """All recipient addresses refused.
91 The errors for each recipient are accessible through the attribute
92 'recipients', which is a dictionary of exactly the same sort as
93 SMTP.sendmail() returns.
94 """
96 def __init__(self, recipients):
97 self.recipients = recipients
98 self.args = ( recipients,)
101 class SMTPDataError(SMTPResponseException):
102 """The SMTP server didn't accept the data."""
104 class SMTPConnectError(SMTPResponseException):
105 """Error during connection establishment."""
107 class SMTPHeloError(SMTPResponseException):
108 """The server refused our HELO reply."""
111 def quoteaddr(addr):
112 """Quote a subset of the email addresses defined by RFC 821.
114 Should be able to handle anything rfc822.parseaddr can handle.
116 m=None
117 try:
118 m=rfc822.parseaddr(addr)[1]
119 except AttributeError:
120 pass
121 if not m:
122 #something weird here.. punt -ddm
123 return addr
124 else:
125 return "<%s>" % m
127 def quotedata(data):
128 """Quote data for email.
130 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
131 Internet CRLF end-of-line.
133 return re.sub(r'(?m)^\.', '..',
134 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
136 def _get_fqdn_hostname(name):
137 name = string.strip(name)
138 if len(name) == 0:
139 name = socket.gethostname()
140 try:
141 hostname, aliases, ipaddrs = socket.gethostbyaddr(name)
142 except socket.error:
143 pass
144 else:
145 aliases.insert(0, hostname)
146 for name in aliases:
147 if '.' in name:
148 break
149 else:
150 name = hostname
151 return name
154 class SMTP:
155 """This class manages a connection to an SMTP or ESMTP server.
156 SMTP Objects:
157 SMTP objects have the following attributes:
158 helo_resp
159 This is the message given by the server in response to the
160 most recent HELO command.
162 ehlo_resp
163 This is the message given by the server in response to the
164 most recent EHLO command. This is usually multiline.
166 does_esmtp
167 This is a True value _after you do an EHLO command_, if the
168 server supports ESMTP.
170 esmtp_features
171 This is a dictionary, which, if the server supports ESMTP,
172 will _after you do an EHLO command_, contain the names of the
173 SMTP service extensions this server supports, and their
174 parameters (if any).
176 Note, all extension names are mapped to lower case in the
177 dictionary.
179 See each method's docstrings for details. In general, there is a
180 method of the same name to perform each SMTP command. There is also a
181 method called 'sendmail' that will do an entire mail transaction.
183 debuglevel = 0
184 file = None
185 helo_resp = None
186 ehlo_resp = None
187 does_esmtp = 0
189 def __init__(self, host = '', port = 0):
190 """Initialize a new instance.
192 If specified, `host' is the name of the remote host to which to
193 connect. If specified, `port' specifies the port to which to connect.
194 By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
195 if the specified `host' doesn't respond correctly.
198 self.esmtp_features = {}
199 if host:
200 (code, msg) = self.connect(host, port)
201 if code != 220:
202 raise SMTPConnectError(code, msg)
204 def set_debuglevel(self, debuglevel):
205 """Set the debug output level.
207 A non-false value results in debug messages for connection and for all
208 messages sent to and received from the server.
211 self.debuglevel = debuglevel
213 def connect(self, host='localhost', port = 0):
214 """Connect to a host on a given port.
216 If the hostname ends with a colon (`:') followed by a number, and
217 there is no port specified, that suffix will be stripped off and the
218 number interpreted as the port number to use.
220 Note: This method is automatically invoked by __init__, if a host is
221 specified during instantiation.
224 if not port:
225 i = string.find(host, ':')
226 if i >= 0:
227 host, port = host[:i], host[i+1:]
228 try: port = string.atoi(port)
229 except string.atoi_error:
230 raise socket.error, "nonnumeric port"
231 if not port: port = SMTP_PORT
232 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
233 if self.debuglevel > 0: print 'connect:', (host, port)
234 self.sock.connect((host, port))
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 self.sock.send(str)
245 except socket.error:
246 raise SMTPServerDisconnected('Server not connected')
247 else:
248 raise SMTPServerDisconnected('please run connect() first')
250 def putcmd(self, cmd, args=""):
251 """Send a command to the server."""
252 if args == "":
253 str = '%s%s' % (cmd, CRLF)
254 else:
255 str = '%s %s%s' % (cmd, args, CRLF)
256 self.send(str)
258 def getreply(self):
259 """Get a reply from the server.
261 Returns a tuple consisting of:
263 - server response code (e.g. '250', or such, if all goes well)
264 Note: returns -1 if it can't read response code.
266 - server response string corresponding to response code (multiline
267 responses are converted to a single, multiline string).
269 Raises SMTPServerDisconnected if end-of-file is reached.
271 resp=[]
272 if self.file is None:
273 self.file = self.sock.makefile('rb')
274 while 1:
275 line = self.file.readline()
276 if line == '':
277 self.close()
278 raise SMTPServerDisconnected("Connection unexpectedly closed")
279 if self.debuglevel > 0: print 'reply:', `line`
280 resp.append(string.strip(line[4:]))
281 code=line[:3]
282 # Check that the error code is syntactically correct.
283 # Don't attempt to read a continuation line if it is broken.
284 try:
285 errcode = string.atoi(code)
286 except ValueError:
287 errcode = -1
288 break
289 # Check if multiline response.
290 if line[3:4]!="-":
291 break
293 errmsg = string.join(resp,"\n")
294 if self.debuglevel > 0:
295 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
296 return errcode, errmsg
298 def docmd(self, cmd, args=""):
299 """Send a command, and return its response code."""
300 self.putcmd(cmd,args)
301 return self.getreply()
303 # std smtp commands
304 def helo(self, name=''):
305 """SMTP 'helo' command.
306 Hostname to send for this command defaults to the FQDN of the local
307 host.
309 self.putcmd("helo", _get_fqdn_hostname(name))
310 (code,msg)=self.getreply()
311 self.helo_resp=msg
312 return (code,msg)
314 def ehlo(self, name=''):
315 """ SMTP 'ehlo' command.
316 Hostname to send for this command defaults to the FQDN of the local
317 host.
319 self.putcmd("ehlo", _get_fqdn_hostname(name))
320 (code,msg)=self.getreply()
321 # According to RFC1869 some (badly written)
322 # MTA's will disconnect on an ehlo. Toss an exception if
323 # that happens -ddm
324 if code == -1 and len(msg) == 0:
325 raise SMTPServerDisconnected("Server not connected")
326 self.ehlo_resp=msg
327 if code<>250:
328 return (code,msg)
329 self.does_esmtp=1
330 #parse the ehlo response -ddm
331 resp=string.split(self.ehlo_resp,'\n')
332 del resp[0]
333 for each in resp:
334 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
335 if m:
336 feature=string.lower(m.group("feature"))
337 params=string.strip(m.string[m.end("feature"):])
338 self.esmtp_features[feature]=params
339 return (code,msg)
341 def has_extn(self, opt):
342 """Does the server support a given SMTP service extension?"""
343 return self.esmtp_features.has_key(string.lower(opt))
345 def help(self, args=''):
346 """SMTP 'help' command.
347 Returns help text from server."""
348 self.putcmd("help", args)
349 return self.getreply()
351 def rset(self):
352 """SMTP 'rset' command -- resets session."""
353 return self.docmd("rset")
355 def noop(self):
356 """SMTP 'noop' command -- doesn't do anything :>"""
357 return self.docmd("noop")
359 def mail(self,sender,options=[]):
360 """SMTP 'mail' command -- begins mail xfer session."""
361 optionlist = ''
362 if options and self.does_esmtp:
363 optionlist = ' ' + string.join(options, ' ')
364 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
365 return self.getreply()
367 def rcpt(self,recip,options=[]):
368 """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
369 optionlist = ''
370 if options and self.does_esmtp:
371 optionlist = ' ' + string.join(options, ' ')
372 self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
373 return self.getreply()
375 def data(self,msg):
376 """SMTP 'DATA' command -- sends message data to server.
378 Automatically quotes lines beginning with a period per rfc821.
379 Raises SMTPDataError if there is an unexpected reply to the
380 DATA command; the return value from this method is the final
381 response code received when the all data is sent.
383 self.putcmd("data")
384 (code,repl)=self.getreply()
385 if self.debuglevel >0 : print "data:", (code,repl)
386 if code <> 354:
387 raise SMTPDataError(code,repl)
388 else:
389 q = quotedata(msg)
390 if q[-2:] != CRLF:
391 q = q + CRLF
392 q = q + "." + CRLF
393 self.send(q)
394 (code,msg)=self.getreply()
395 if self.debuglevel >0 : print "data:", (code,msg)
396 return (code,msg)
398 def verify(self, address):
399 """SMTP 'verify' command -- checks for address validity."""
400 self.putcmd("vrfy", quoteaddr(address))
401 return self.getreply()
402 # a.k.a.
403 vrfy=verify
405 def expn(self, address):
406 """SMTP 'verify' command -- checks for address validity."""
407 self.putcmd("expn", quoteaddr(address))
408 return self.getreply()
410 # some useful methods
411 def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
412 rcpt_options=[]):
413 """This command performs an entire mail transaction.
415 The arguments are:
416 - from_addr : The address sending this mail.
417 - to_addrs : A list of addresses to send this mail to. A bare
418 string will be treated as a list with 1 address.
419 - msg : The message to send.
420 - mail_options : List of ESMTP options (such as 8bitmime) for the
421 mail command.
422 - rcpt_options : List of ESMTP options (such as DSN commands) for
423 all the rcpt commands.
425 If there has been no previous EHLO or HELO command this session, this
426 method tries ESMTP EHLO first. If the server does ESMTP, message size
427 and each of the specified options will be passed to it. If EHLO
428 fails, HELO will be tried and ESMTP options suppressed.
430 This method will return normally if the mail is accepted for at least
431 one recipient. It returns a dictionary, with one entry for each
432 recipient that was refused. Each entry contains a tuple of the SMTP
433 error code and the accompanying error message sent by the server.
435 This method may raise the following exceptions:
437 SMTPHeloError The server didn't reply properly to
438 the helo greeting.
439 SMTPRecipientsRefused The server rejected ALL recipients
440 (no mail was sent).
441 SMTPSenderRefused The server didn't accept the from_addr.
442 SMTPDataError The server replied with an unexpected
443 error code (other than a refusal of
444 a recipient).
446 Note: the connection will be open even after an exception is raised.
448 Example:
450 >>> import smtplib
451 >>> s=smtplib.SMTP("localhost")
452 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
453 >>> msg = '''
454 ... From: Me@my.org
455 ... Subject: testin'...
457 ... This is a test '''
458 >>> s.sendmail("me@my.org",tolist,msg)
459 { "three@three.org" : ( 550 ,"User unknown" ) }
460 >>> s.quit()
462 In the above example, the message was accepted for delivery to three
463 of the four addresses, and one was rejected, with the error code
464 550. If all addresses are accepted, then the method will return an
465 empty dictionary.
468 if self.helo_resp is None and self.ehlo_resp is None:
469 if not (200 <= self.ehlo()[0] <= 299):
470 (code,resp) = self.helo()
471 if not (200 <= code <= 299):
472 raise SMTPHeloError(code, resp)
473 esmtp_opts = []
474 if self.does_esmtp:
475 # Hmmm? what's this? -ddm
476 # self.esmtp_features['7bit']=""
477 if self.has_extn('size'):
478 esmtp_opts.append("size=" + `len(msg)`)
479 for option in mail_options:
480 esmtp_opts.append(option)
482 (code,resp) = self.mail(from_addr, esmtp_opts)
483 if code <> 250:
484 self.rset()
485 raise SMTPSenderRefused(code, resp, from_addr)
486 senderrs={}
487 if type(to_addrs) == types.StringType:
488 to_addrs = [to_addrs]
489 for each in to_addrs:
490 (code,resp)=self.rcpt(each, rcpt_options)
491 if (code <> 250) and (code <> 251):
492 senderrs[each]=(code,resp)
493 if len(senderrs)==len(to_addrs):
494 # the server refused all our recipients
495 self.rset()
496 raise SMTPRecipientsRefused(senderrs)
497 (code,resp)=self.data(msg)
498 if code <> 250:
499 self.rset()
500 raise SMTPDataError(code, resp)
501 #if we got here then somebody got our mail
502 return senderrs
505 def close(self):
506 """Close the connection to the SMTP server."""
507 if self.file:
508 self.file.close()
509 self.file = None
510 if self.sock:
511 self.sock.close()
512 self.sock = None
515 def quit(self):
516 """Terminate the SMTP session."""
517 self.docmd("quit")
518 self.close()
521 # Test the sendmail method, which tests most of the others.
522 # Note: This always sends to localhost.
523 if __name__ == '__main__':
524 import sys, rfc822
526 def prompt(prompt):
527 sys.stdout.write(prompt + ": ")
528 return string.strip(sys.stdin.readline())
530 fromaddr = prompt("From")
531 toaddrs = string.splitfields(prompt("To"), ',')
532 print "Enter message, end with ^D:"
533 msg = ''
534 while 1:
535 line = sys.stdin.readline()
536 if not line:
537 break
538 msg = msg + line
539 print "Message length is " + `len(msg)`
541 server = SMTP('localhost')
542 server.set_debuglevel(1)
543 server.sendmail(fromaddr, toaddrs, msg)
544 server.quit()