blob: 2ffc24843667f3436265926c8e2c76bfe07be22b [file] [log] [blame]
Guido van Rossum95e6f701998-06-25 02:15:50 +00001#!/usr/bin/python
2"""SMTP/ESMTP client class.
Guido van Rossumbbe323e1998-01-29 17:24:40 +00003
4Author: The Dragon De Monsyne <dragondm@integral.org>
Guido van Rossum95e6f701998-06-25 02:15:50 +00005ESMTP support, test code and doc fixes added by
6Eric S. Raymond <esr@thyrsus.com>
Guido van Rossum69a79bc1998-07-13 15:18:49 +00007Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
8by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
Guido van Rossumbbe323e1998-01-29 17:24:40 +00009
10(This was modified from the Python 1.5 library HTTP lib.)
11
Guido van Rossum95e6f701998-06-25 02:15:50 +000012This should follow RFC 821 (SMTP) and RFC 1869 (ESMTP).
Guido van Rossumbbe323e1998-01-29 17:24:40 +000013
14Example:
15
16>>> import smtplib
17>>> s=smtplib.SMTP("localhost")
18>>> print s.help()
19This is Sendmail version 8.8.4
20Topics:
21 HELO EHLO MAIL RCPT DATA
22 RSET NOOP QUIT HELP VRFY
23 EXPN VERB ETRN DSN
24For more info use "HELP <topic>".
25To report bugs in the implementation send email to
26 sendmail-bugs@sendmail.org.
27For local information send email to Postmaster at your site.
28End of HELP info
29>>> s.putcmd("vrfy","someone@here")
30>>> s.getreply()
31(250, "Somebody OverHere <somebody@here.my.org>")
32>>> s.quit()
33
34"""
35
36import socket
37import string,re
38
39SMTP_PORT = 25
40CRLF="\r\n"
41
42# used for exceptions
Guido van Rossumfc40a831998-01-29 17:26:45 +000043SMTPServerDisconnected="Server not connected"
Guido van Rossumbbe323e1998-01-29 17:24:40 +000044SMTPSenderRefused="Sender address refused"
45SMTPRecipientsRefused="All Recipients refused"
Guido van Rossum95e6f701998-06-25 02:15:50 +000046SMTPDataError="Error transmitting message data"
Guido van Rossumbbe323e1998-01-29 17:24:40 +000047
Guido van Rossum69a79bc1998-07-13 15:18:49 +000048def quoteaddr(addr):
49 """Quote a subset of the email addresses defined by RFC 821.
50
51 Technically, only a <mailbox> is allowed. In addition,
52 email addresses without a domain are permitted.
53
54 Addresses will not be modified if they are already quoted
55 (actually if they begin with '<' and end with '>'."""
56 if re.match('(?s)\A<.*>\Z', addr):
57 return addr
58
59 localpart = None
60 domain = ''
61 try:
62 at = string.rindex(addr, '@')
63 localpart = addr[:at]
64 domain = addr[at:]
65 except ValueError:
66 localpart = addr
67
68 pat = re.compile(r'([<>()\[\]\\,;:@\"\001-\037\177])')
69 return '<%s%s>' % (pat.sub(r'\\\1', localpart), domain)
70
71def quotedata(data):
72 """Quote data for email.
73
74 Double leading '.', and change Unix newline '\n' into
75 Internet CRLF end-of-line."""
76 return re.sub(r'(?m)^\.', '..',
77 re.sub(r'\r?\n', CRLF, data))
78
Guido van Rossumbbe323e1998-01-29 17:24:40 +000079class SMTP:
Guido van Rossum95e6f701998-06-25 02:15:50 +000080 """This class manages a connection to an SMTP or ESMTP server."""
81 debuglevel = 0
82 file = None
83 helo_resp = None
84 ehlo_resp = None
85 esmtp_features = []
86
Guido van Rossumbbe323e1998-01-29 17:24:40 +000087 def __init__(self, host = '', port = 0):
88 """Initialize a new instance.
89
90 If specified, `host' is the name of the remote host to which
91 to connect. If specified, `port' specifies the port to which
92 to connect. By default, smtplib.SMTP_PORT is used.
93
94 """
Guido van Rossumbbe323e1998-01-29 17:24:40 +000095 if host: self.connect(host, port)
96
97 def set_debuglevel(self, debuglevel):
98 """Set the debug output level.
99
100 A non-false value results in debug messages for connection and
101 for all messages sent to and received from the server.
102
103 """
104 self.debuglevel = debuglevel
105
Guido van Rossum95e6f701998-06-25 02:15:50 +0000106 def verify(self, address):
107 """ SMTP 'verify' command. Checks for address validity. """
108 self.putcmd("vrfy", address)
109 return self.getreply()
110
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000111 def connect(self, host='localhost', port = 0):
112 """Connect to a host on a given port.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000113
114 If the hostname ends with a colon (`:') followed by a number,
115 that suffix will be stripped off and the number interpreted as
116 the port number to use.
117
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000118 Note: This method is automatically invoked by __init__,
119 if a host is specified during instantiation.
120
121 """
122 if not port:
123 i = string.find(host, ':')
124 if i >= 0:
125 host, port = host[:i], host[i+1:]
126 try: port = string.atoi(port)
127 except string.atoi_error:
128 raise socket.error, "nonnumeric port"
129 if not port: port = SMTP_PORT
130 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
131 if self.debuglevel > 0: print 'connect:', (host, port)
132 self.sock.connect(host, port)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000133 (code,msg)=self.getreply()
134 if self.debuglevel >0 : print "connect:", msg
135 return msg
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000136
137 def send(self, str):
138 """Send `str' to the server."""
139 if self.debuglevel > 0: print 'send:', `str`
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000140 if self.sock:
Guido van Rossumfc40a831998-01-29 17:26:45 +0000141 self.sock.send(str)
142 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 raise SMTPServerDisconnected
Guido van Rossumfc40a831998-01-29 17:26:45 +0000144
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000145 def putcmd(self, cmd, args=""):
146 """Send a command to the server.
147 """
148 str = '%s %s%s' % (cmd, args, CRLF)
149 self.send(str)
150
Guido van Rossum95e6f701998-06-25 02:15:50 +0000151 def getreply(self, linehook=None):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000152 """Get a reply from the server.
153
154 Returns a tuple consisting of:
155 - server response code (e.g. '250', or such, if all goes well)
Guido van Rossum95e6f701998-06-25 02:15:50 +0000156 Note: returns -1 if it can't read response code.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000157 - server response string corresponding to response code
Guido van Rossum95e6f701998-06-25 02:15:50 +0000158 (note : multiline responses converted to a single,
Guido van Rossumfc40a831998-01-29 17:26:45 +0000159 multiline string)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000160 """
161 resp=[]
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000162 self.file = self.sock.makefile('rb')
163 while 1:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000164 line = self.file.readline()
165 if self.debuglevel > 0: print 'reply:', `line`
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000166 resp.append(string.strip(line[4:]))
167 code=line[:3]
168 #check if multiline resp
169 if line[3:4]!="-":
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000170 break
Guido van Rossum95e6f701998-06-25 02:15:50 +0000171 elif linehook:
172 linehook(line)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000173 try:
174 errcode = string.atoi(code)
175 except(ValueError):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000176 errcode = -1
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000177
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000178 errmsg = string.join(resp,"\n")
179 if self.debuglevel > 0:
Guido van Rossumfc40a831998-01-29 17:26:45 +0000180 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000181 return errcode, errmsg
182
183 def docmd(self, cmd, args=""):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000184 """ Send a command, and return its response code """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000185
186 self.putcmd(cmd,args)
187 (code,msg)=self.getreply()
188 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000189# std smtp commands
190
191 def helo(self, name=''):
192 """ SMTP 'helo' command. Hostname to send for this command
193 defaults to the FQDN of the local host """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000194 name=string.strip(name)
195 if len(name)==0:
196 name=socket.gethostbyaddr(socket.gethostname())[0]
197 self.putcmd("helo",name)
198 (code,msg)=self.getreply()
199 self.helo_resp=msg
200 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000201
Guido van Rossum95e6f701998-06-25 02:15:50 +0000202 def ehlo(self, name=''):
203 """ SMTP 'ehlo' command. Hostname to send for this command
204 defaults to the FQDN of the local host """
205 name=string.strip(name)
206 if len(name)==0:
207 name=socket.gethostbyaddr(socket.gethostname())[0]
208 self.putcmd("ehlo",name)
209 (code,msg)=self.getreply(self.ehlo_hook)
210 self.ehlo_resp=msg
211 return code
212
213 def ehlo_hook(self, line):
214 # Interpret EHLO response lines
215 if line[4] in string.uppercase+string.digits:
216 self.esmtp_features.append(string.lower(string.strip(line)[4:]))
217
218 def has_option(self, opt):
219 """Does the server support a given SMTP option?"""
220 return opt in self.esmtp_features
221
Guido van Rossum18586f41998-04-03 17:03:13 +0000222 def help(self, args=''):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000223 """ SMTP 'help' command. Returns help text from server """
Guido van Rossum18586f41998-04-03 17:03:13 +0000224 self.putcmd("help", args)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000225 (code,msg)=self.getreply()
226 return msg
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000227
228 def rset(self):
229 """ SMTP 'rset' command. Resets session. """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000230 code=self.docmd("rset")
231 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000232
233 def noop(self):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000234 """ SMTP 'noop' command. Doesn't do anything :> """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000235 code=self.docmd("noop")
236 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000237
Guido van Rossum95e6f701998-06-25 02:15:50 +0000238 def mail(self,sender,options=[]):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000239 """ SMTP 'mail' command. Begins mail xfer session. """
Guido van Rossum95e6f701998-06-25 02:15:50 +0000240 if options:
241 options = " " + string.joinfields(options, ' ')
242 else:
243 options = ''
Guido van Rossum69a79bc1998-07-13 15:18:49 +0000244 self.putcmd("mail", "from:" + quoteaddr(sender) + options)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000245 return self.getreply()
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000246
247 def rcpt(self,recip):
248 """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
Guido van Rossum69a79bc1998-07-13 15:18:49 +0000249 self.putcmd("rcpt","to:%s" % quoteaddr(recip))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000250 return self.getreply()
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000251
252 def data(self,msg):
253 """ SMTP 'DATA' command. Sends message data to server.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000254 Automatically quotes lines beginning with a period per rfc821. """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000255 self.putcmd("data")
256 (code,repl)=self.getreply()
257 if self.debuglevel >0 : print "data:", (code,repl)
258 if code <> 354:
259 return -1
260 else:
Guido van Rossum69a79bc1998-07-13 15:18:49 +0000261 self.send(quotedata(msg))
262 self.send("%s.%s" % (CRLF, CRLF))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000263 (code,msg)=self.getreply()
264 if self.debuglevel >0 : print "data:", (code,msg)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000265 return code
266
Guido van Rossum95e6f701998-06-25 02:15:50 +0000267#some useful methods
268 def sendmail(self,from_addr,to_addrs,msg,options=[]):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000269 """ This command performs an entire mail transaction.
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000270 The arguments are:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000271 - from_addr : The address sending this mail.
272 - to_addrs : a list of addresses to send this mail to
273 - msg : the message to send.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000274 - encoding : list of ESMTP options (such as 8bitmime)
275
276 If there has been no previous EHLO or HELO command this session,
277 this method tries ESMTP EHLO first. If the server does ESMTP, message
278 size and each of the specified options will be passed to it (if the
279 option is in the feature set the server advertises). If EHLO fails,
280 HELO will be tried and ESMTP options suppressed.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000281
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000282 This method will return normally if the mail is accepted for at least
Guido van Rossum95e6f701998-06-25 02:15:50 +0000283 one recipient. Otherwise it will throw an exception (either
284 SMTPSenderRefused, SMTPRecipientsRefused, or SMTPDataError)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000285 That is, if this method does not throw an exception, then someone
Guido van Rossum95e6f701998-06-25 02:15:50 +0000286 should get your mail. If this method does not throw an exception,
287 it returns a dictionary, with one entry for each recipient that was
Guido van Rossumfc40a831998-01-29 17:26:45 +0000288 refused.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000289
Guido van Rossum95e6f701998-06-25 02:15:50 +0000290 Example:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000291
292 >>> import smtplib
293 >>> s=smtplib.SMTP("localhost")
Guido van Rossumfc40a831998-01-29 17:26:45 +0000294 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000295 >>> msg = '''
296 ... From: Me@my.org
297 ... Subject: testin'...
298 ...
299 ... This is a test '''
300 >>> s.sendmail("me@my.org",tolist,msg)
301 { "three@three.org" : ( 550 ,"User unknown" ) }
302 >>> s.quit()
303
Guido van Rossumfc40a831998-01-29 17:26:45 +0000304 In the above example, the message was accepted for delivery to
305 three of the four addresses, and one was rejected, with the error
306 code 550. If all addresses are accepted, then the method
307 will return an empty dictionary.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000308 """
Guido van Rossum95e6f701998-06-25 02:15:50 +0000309 if not self.helo_resp and not self.ehlo_resp:
310 if self.ehlo() >= 400:
311 self.helo()
312 if self.esmtp_features:
313 self.esmtp_features.append('7bit')
314 esmtp_opts = []
315 if 'size' in self.esmtp_features:
316 esmtp_opts.append("size=" + `len(msg)`)
317 for option in options:
318 if option in self.esmtp_features:
319 esmtp_opts.append(option)
320 (code,resp) = self.mail(from_addr, esmtp_opts)
321 if code <> 250:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000322 self.rset()
323 raise SMTPSenderRefused
324 senderrs={}
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000325 for each in to_addrs:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000326 (code,resp)=self.rcpt(each)
327 if (code <> 250) and (code <> 251):
Guido van Rossumfc40a831998-01-29 17:26:45 +0000328 senderrs[each]=(code,resp)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000329 if len(senderrs)==len(to_addrs):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000330 # the server refused all our recipients
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000331 self.rset()
332 raise SMTPRecipientsRefused
333 code=self.data(msg)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000334 if code <>250 :
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000335 self.rset()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000336 raise SMTPDataError
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000337 #if we got here then somebody got our mail
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000338 return senderrs
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000339
340
341 def close(self):
342 """Close the connection to the SMTP server."""
343 if self.file:
344 self.file.close()
345 self.file = None
346 if self.sock:
347 self.sock.close()
348 self.sock = None
349
350
351 def quit(self):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000352 """Terminate the SMTP session."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000353 self.docmd("quit")
354 self.close()
Guido van Rossum95e6f701998-06-25 02:15:50 +0000355
356# Test the sendmail method, which tests most of the others.
357# Note: This always sends to localhost.
358if __name__ == '__main__':
359 import sys, rfc822
360
361 def prompt(prompt):
362 sys.stdout.write(prompt + ": ")
363 return string.strip(sys.stdin.readline())
364
365 fromaddr = prompt("From")
366 toaddrs = string.splitfields(prompt("To"), ',')
367 print "Enter message, end with ^D:"
368 msg = ''
369 while 1:
370 line = sys.stdin.readline()
371 if not line:
372 break
373 msg = msg + line
374 print "Message length is " + `len(msg)`
375
376 server = SMTP('localhost')
377 server.set_debuglevel(1)
378 server.sendmail(fromaddr, toaddrs, msg)
379 server.quit()
380
381