blob: 426af19b96ad52912cb7f1ad13264e7ec0f6a057 [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
Guido van Rossumfcfb6321998-08-04 15:29:54 +00006 Eric S. Raymond <esr@thyrsus.com>
Guido van Rossum69a79bc1998-07-13 15:18:49 +00007Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
Guido van Rossumfcfb6321998-08-04 15:29:54 +00008 by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
9
Guido van Rossumbbe323e1998-01-29 17:24:40 +000010(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
Guido van Rossumfcfb6321998-08-04 15:29:54 +000014Notes:
15
16Please remember, when doing ESMTP, that the names of the SMTP service
17extensions are NOT the same thing as the option keyords for the RCPT
18and MAIL commands!
19
Guido van Rossumbbe323e1998-01-29 17:24:40 +000020Example:
21
22>>> import smtplib
23>>> s=smtplib.SMTP("localhost")
24>>> print s.help()
25This is Sendmail version 8.8.4
26Topics:
27 HELO EHLO MAIL RCPT DATA
28 RSET NOOP QUIT HELP VRFY
29 EXPN VERB ETRN DSN
30For more info use "HELP <topic>".
31To report bugs in the implementation send email to
32 sendmail-bugs@sendmail.org.
33For local information send email to Postmaster at your site.
34End of HELP info
35>>> s.putcmd("vrfy","someone@here")
36>>> s.getreply()
37(250, "Somebody OverHere <somebody@here.my.org>")
38>>> s.quit()
39
40"""
41
42import socket
Guido van Rossumfcfb6321998-08-04 15:29:54 +000043import string, re
44import rfc822
Guido van Rossumbbe323e1998-01-29 17:24:40 +000045
46SMTP_PORT = 25
47CRLF="\r\n"
48
49# used for exceptions
Guido van Rossumfc40a831998-01-29 17:26:45 +000050SMTPServerDisconnected="Server not connected"
Guido van Rossumbbe323e1998-01-29 17:24:40 +000051SMTPSenderRefused="Sender address refused"
52SMTPRecipientsRefused="All Recipients refused"
Guido van Rossum95e6f701998-06-25 02:15:50 +000053SMTPDataError="Error transmitting message data"
Guido van Rossumbbe323e1998-01-29 17:24:40 +000054
Guido van Rossum69a79bc1998-07-13 15:18:49 +000055def quoteaddr(addr):
56 """Quote a subset of the email addresses defined by RFC 821.
57
Guido van Rossumfcfb6321998-08-04 15:29:54 +000058 Should be able to handle anything rfc822.parseaddr can handle."""
Guido van Rossum69a79bc1998-07-13 15:18:49 +000059
Guido van Rossumfcfb6321998-08-04 15:29:54 +000060 m=None
Guido van Rossum69a79bc1998-07-13 15:18:49 +000061 try:
Guido van Rossumfcfb6321998-08-04 15:29:54 +000062 m=rfc822.parseaddr(addr)[1]
63 except AttributeError:
64 pass
65 if not m:
66 #something weird here.. punt -ddm
67 return addr
68 else:
69 return "<%s>" % m
Guido van Rossum69a79bc1998-07-13 15:18:49 +000070
71def quotedata(data):
72 """Quote data for email.
73
Guido van Rossumfcfb6321998-08-04 15:29:54 +000074 Double leading '.', and change Unix newline '\n', or Mac '\r' into
Guido van Rossum69a79bc1998-07-13 15:18:49 +000075 Internet CRLF end-of-line."""
76 return re.sub(r'(?m)^\.', '..',
Guido van Rossumfcfb6321998-08-04 15:29:54 +000077 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
Guido van Rossum69a79bc1998-07-13 15:18:49 +000078
Guido van Rossumbbe323e1998-01-29 17:24:40 +000079class SMTP:
Guido van Rossumfcfb6321998-08-04 15:29:54 +000080 """This class manages a connection to an SMTP or ESMTP server.
81 SMTP Objects:
82 SMTP objects have the following attributes:
83 helo_resp
84 This is the message given by the server in responce to the
85 most recent HELO command.
86
87 ehlo_resp
88 This is the message given by the server in responce to the
89 most recent EHLO command. This is usually multiline.
90
91 does_esmtp
92 This is a True value _after you do an EHLO command_, if the
93 server supports ESMTP.
94
95 esmtp_features
96 This is a dictionary, which, if the server supports ESMTP,
97 will _after you do an EHLO command_, contain the names of the
98 SMTP service extentions this server supports, and their
99 parameters (if any).
100 Note, all extention names are mapped to lower case in the
101 dictionary.
102
103 For method docs, see each method's docstrings. In general, there is
104 a method of the same name to preform each SMTP comand, and there
105 is a method called 'sendmail' that will do an entiere mail
106 transaction."""
107
Guido van Rossum95e6f701998-06-25 02:15:50 +0000108 debuglevel = 0
109 file = None
110 helo_resp = None
111 ehlo_resp = None
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000112 does_esmtp = 0
Guido van Rossum95e6f701998-06-25 02:15:50 +0000113
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000114 def __init__(self, host = '', port = 0):
115 """Initialize a new instance.
116
117 If specified, `host' is the name of the remote host to which
118 to connect. If specified, `port' specifies the port to which
119 to connect. By default, smtplib.SMTP_PORT is used.
120
121 """
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000122 self.esmtp_features = {}
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000123 if host: self.connect(host, port)
124
125 def set_debuglevel(self, debuglevel):
126 """Set the debug output level.
127
128 A non-false value results in debug messages for connection and
129 for all messages sent to and received from the server.
130
131 """
132 self.debuglevel = debuglevel
133
134 def connect(self, host='localhost', port = 0):
135 """Connect to a host on a given port.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000136
137 If the hostname ends with a colon (`:') followed by a number,
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000138 and there is no port specified, that suffix will be stripped
139 off and the number interpreted as the port number to use.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000140
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000141 Note: This method is automatically invoked by __init__,
142 if a host is specified during instantiation.
143
144 """
145 if not port:
146 i = string.find(host, ':')
147 if i >= 0:
148 host, port = host[:i], host[i+1:]
149 try: port = string.atoi(port)
150 except string.atoi_error:
151 raise socket.error, "nonnumeric port"
152 if not port: port = SMTP_PORT
153 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
154 if self.debuglevel > 0: print 'connect:', (host, port)
155 self.sock.connect(host, port)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000156 (code,msg)=self.getreply()
157 if self.debuglevel >0 : print "connect:", msg
158 return msg
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000159
160 def send(self, str):
161 """Send `str' to the server."""
162 if self.debuglevel > 0: print 'send:', `str`
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000163 if self.sock:
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000164 try:
165 self.sock.send(str)
166 except socket.error:
167 raise SMTPServerDisconnected
Guido van Rossumfc40a831998-01-29 17:26:45 +0000168 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000169 raise SMTPServerDisconnected
Guido van Rossumfc40a831998-01-29 17:26:45 +0000170
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000171 def putcmd(self, cmd, args=""):
172 """Send a command to the server.
173 """
174 str = '%s %s%s' % (cmd, args, CRLF)
175 self.send(str)
176
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000177 def getreply(self):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000178 """Get a reply from the server.
179
180 Returns a tuple consisting of:
181 - server response code (e.g. '250', or such, if all goes well)
Guido van Rossum95e6f701998-06-25 02:15:50 +0000182 Note: returns -1 if it can't read response code.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000183 - server response string corresponding to response code
Guido van Rossum95e6f701998-06-25 02:15:50 +0000184 (note : multiline responses converted to a single,
Guido van Rossumfc40a831998-01-29 17:26:45 +0000185 multiline string)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000186 """
187 resp=[]
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000188 self.file = self.sock.makefile('rb')
189 while 1:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000190 line = self.file.readline()
191 if self.debuglevel > 0: print 'reply:', `line`
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000192 resp.append(string.strip(line[4:]))
193 code=line[:3]
194 #check if multiline resp
195 if line[3:4]!="-":
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000196 break
197 try:
198 errcode = string.atoi(code)
199 except(ValueError):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000200 errcode = -1
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000201
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000202 errmsg = string.join(resp,"\n")
203 if self.debuglevel > 0:
Guido van Rossumfc40a831998-01-29 17:26:45 +0000204 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000205 return errcode, errmsg
206
207 def docmd(self, cmd, args=""):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000208 """ Send a command, and return its response code """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000209
210 self.putcmd(cmd,args)
211 (code,msg)=self.getreply()
212 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000213# std smtp commands
214
215 def helo(self, name=''):
216 """ SMTP 'helo' command. Hostname to send for this command
217 defaults to the FQDN of the local host """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000218 name=string.strip(name)
219 if len(name)==0:
220 name=socket.gethostbyaddr(socket.gethostname())[0]
221 self.putcmd("helo",name)
222 (code,msg)=self.getreply()
223 self.helo_resp=msg
224 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000225
Guido van Rossum95e6f701998-06-25 02:15:50 +0000226 def ehlo(self, name=''):
227 """ SMTP 'ehlo' command. Hostname to send for this command
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000228 defaults to the FQDN of the local host. """
Guido van Rossum95e6f701998-06-25 02:15:50 +0000229 name=string.strip(name)
230 if len(name)==0:
231 name=socket.gethostbyaddr(socket.gethostname())[0]
232 self.putcmd("ehlo",name)
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000233 (code,msg)=self.getreply()
234 # According to RFC1869 some (badly written)
235 # MTA's will disconnect on an ehlo. Toss an exception if
236 # that happens -ddm
237 if code == -1 and len(msg) == 0:
238 raise SMTPServerDisconnected
Guido van Rossum95e6f701998-06-25 02:15:50 +0000239 self.ehlo_resp=msg
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000240 if code<>250:
241 return code
242 self.does_esmtp=1
243 #parse the ehlo responce -ddm
244 resp=string.split(self.ehlo_resp,'\n')
245 del resp[0]
246 for each in resp:
247 m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
248 if m:
249 feature=string.lower(m.group("feature"))
250 params=string.strip(m.string[m.end("feature"):])
251 self.esmtp_features[feature]=params
Guido van Rossum95e6f701998-06-25 02:15:50 +0000252 return code
253
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000254 def has_extn(self, opt):
255 """Does the server support a given SMTP service extension?"""
256 return self.esmtp_features.has_key(string.lower(opt))
Guido van Rossum95e6f701998-06-25 02:15:50 +0000257
Guido van Rossum18586f41998-04-03 17:03:13 +0000258 def help(self, args=''):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000259 """ SMTP 'help' command. Returns help text from server """
Guido van Rossum18586f41998-04-03 17:03:13 +0000260 self.putcmd("help", args)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000261 (code,msg)=self.getreply()
262 return msg
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000263
264 def rset(self):
265 """ SMTP 'rset' command. Resets session. """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000266 code=self.docmd("rset")
267 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000268
269 def noop(self):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000270 """ SMTP 'noop' command. Doesn't do anything :> """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000271 code=self.docmd("noop")
272 return code
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000273
Guido van Rossum95e6f701998-06-25 02:15:50 +0000274 def mail(self,sender,options=[]):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000275 """ SMTP 'mail' command. Begins mail xfer session. """
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000276 optionlist = ''
277 if options and self.does_esmtp:
278 optionlist = string.join(options, ' ')
279 self.putcmd("mail", "FROM:%s %s" % (quoteaddr(sender) ,optionlist))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000280 return self.getreply()
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000281
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000282 def rcpt(self,recip,options=[]):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000283 """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000284 optionlist = ''
285 if options and self.does_esmtp:
286 optionlist = string.join(options, ' ')
287 self.putcmd("rcpt","TO:%s %s" % (quoteaddr(recip),optionlist))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000288 return self.getreply()
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000289
290 def data(self,msg):
291 """ SMTP 'DATA' command. Sends message data to server.
Guido van Rossum95e6f701998-06-25 02:15:50 +0000292 Automatically quotes lines beginning with a period per rfc821. """
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000293 self.putcmd("data")
294 (code,repl)=self.getreply()
295 if self.debuglevel >0 : print "data:", (code,repl)
296 if code <> 354:
297 return -1
298 else:
Guido van Rossum69a79bc1998-07-13 15:18:49 +0000299 self.send(quotedata(msg))
300 self.send("%s.%s" % (CRLF, CRLF))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000301 (code,msg)=self.getreply()
302 if self.debuglevel >0 : print "data:", (code,msg)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000303 return code
304
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000305 def vrfy(self, address):
306 return self.verify(address)
307
308 def verify(self, address):
309 """ SMTP 'verify' command. Checks for address validity. """
310 self.putcmd("vrfy", quoteaddr(address))
311 return self.getreply()
312
313 def expn(self, address):
314 """ SMTP 'verify' command. Checks for address validity. """
315 self.putcmd("expn", quoteaddr(address))
316 return self.getreply()
317
318
Guido van Rossum95e6f701998-06-25 02:15:50 +0000319#some useful methods
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000320 def sendmail(self,from_addr,to_addrs,msg,mail_options=[],rcpt_options=[]):
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000321 """ This command performs an entire mail transaction.
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000322 The arguments are:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000323 - from_addr : The address sending this mail.
324 - to_addrs : a list of addresses to send this mail to
325 - msg : the message to send.
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000326 - mail_options : list of ESMTP options (such as 8bitmime)
327 for the mail command
328 - rcpt_options : List of ESMTP options (such as DSN commands)
329 for all the rcpt commands
Guido van Rossum95e6f701998-06-25 02:15:50 +0000330 If there has been no previous EHLO or HELO command this session,
331 this method tries ESMTP EHLO first. If the server does ESMTP, message
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000332 size and each of the specified options will be passed to it.
333 If EHLO fails, HELO will be tried and ESMTP options suppressed.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000334
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000335 This method will return normally if the mail is accepted for at least
Guido van Rossum95e6f701998-06-25 02:15:50 +0000336 one recipient. Otherwise it will throw an exception (either
337 SMTPSenderRefused, SMTPRecipientsRefused, or SMTPDataError)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000338 That is, if this method does not throw an exception, then someone
Guido van Rossum95e6f701998-06-25 02:15:50 +0000339 should get your mail. If this method does not throw an exception,
340 it returns a dictionary, with one entry for each recipient that was
Guido van Rossumfc40a831998-01-29 17:26:45 +0000341 refused.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000342
Guido van Rossum95e6f701998-06-25 02:15:50 +0000343 Example:
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000344
345 >>> import smtplib
346 >>> s=smtplib.SMTP("localhost")
Guido van Rossumfc40a831998-01-29 17:26:45 +0000347 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000348 >>> msg = '''
349 ... From: Me@my.org
350 ... Subject: testin'...
351 ...
352 ... This is a test '''
353 >>> s.sendmail("me@my.org",tolist,msg)
354 { "three@three.org" : ( 550 ,"User unknown" ) }
355 >>> s.quit()
356
Guido van Rossumfc40a831998-01-29 17:26:45 +0000357 In the above example, the message was accepted for delivery to
358 three of the four addresses, and one was rejected, with the error
359 code 550. If all addresses are accepted, then the method
360 will return an empty dictionary.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000361 """
Guido van Rossum95e6f701998-06-25 02:15:50 +0000362 if not self.helo_resp and not self.ehlo_resp:
363 if self.ehlo() >= 400:
364 self.helo()
Guido van Rossum95e6f701998-06-25 02:15:50 +0000365 esmtp_opts = []
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000366 if self.does_esmtp:
367 # Hmmm? what's this? -ddm
368 # self.esmtp_features['7bit']=""
369 if self.has_extn('size'):
370 esmtp_opts.append("size=" + `len(msg)`)
371 for option in mail_options:
Guido van Rossum95e6f701998-06-25 02:15:50 +0000372 esmtp_opts.append(option)
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000373
Guido van Rossum95e6f701998-06-25 02:15:50 +0000374 (code,resp) = self.mail(from_addr, esmtp_opts)
375 if code <> 250:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000376 self.rset()
377 raise SMTPSenderRefused
378 senderrs={}
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000379 for each in to_addrs:
Guido van Rossumfcfb6321998-08-04 15:29:54 +0000380 (code,resp)=self.rcpt(each, rcpt_options)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000381 if (code <> 250) and (code <> 251):
Guido van Rossumfc40a831998-01-29 17:26:45 +0000382 senderrs[each]=(code,resp)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000383 if len(senderrs)==len(to_addrs):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000384 # the server refused all our recipients
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000385 self.rset()
386 raise SMTPRecipientsRefused
387 code=self.data(msg)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000388 if code <>250 :
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000389 self.rset()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000390 raise SMTPDataError
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000391 #if we got here then somebody got our mail
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000392 return senderrs
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000393
394
395 def close(self):
396 """Close the connection to the SMTP server."""
397 if self.file:
398 self.file.close()
399 self.file = None
400 if self.sock:
401 self.sock.close()
402 self.sock = None
403
404
405 def quit(self):
Guido van Rossum95e6f701998-06-25 02:15:50 +0000406 """Terminate the SMTP session."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000407 self.docmd("quit")
408 self.close()
Guido van Rossum95e6f701998-06-25 02:15:50 +0000409
410# Test the sendmail method, which tests most of the others.
411# Note: This always sends to localhost.
412if __name__ == '__main__':
413 import sys, rfc822
414
415 def prompt(prompt):
416 sys.stdout.write(prompt + ": ")
417 return string.strip(sys.stdin.readline())
418
419 fromaddr = prompt("From")
420 toaddrs = string.splitfields(prompt("To"), ',')
421 print "Enter message, end with ^D:"
422 msg = ''
423 while 1:
424 line = sys.stdin.readline()
425 if not line:
426 break
427 msg = msg + line
428 print "Message length is " + `len(msg)`
429
430 server = SMTP('localhost')
431 server.set_debuglevel(1)
432 server.sendmail(fromaddr, toaddrs, msg)
433 server.quit()
434
435