blob: a45d95a2988efa768bbf0919bc8848bdae3b44b2 [file] [log] [blame]
Guido van Rossumbbe323e1998-01-29 17:24:40 +00001"""SMTP Client class.
2
3Author: The Dragon De Monsyne <dragondm@integral.org>
4
5(This was modified from the Python 1.5 library HTTP lib.)
6
7This should follow RFC 821. (it dosen't do esmtp (yet))
8
9Example:
10
11>>> import smtplib
12>>> s=smtplib.SMTP("localhost")
13>>> print s.help()
14This is Sendmail version 8.8.4
15Topics:
16 HELO EHLO MAIL RCPT DATA
17 RSET NOOP QUIT HELP VRFY
18 EXPN VERB ETRN DSN
19For more info use "HELP <topic>".
20To report bugs in the implementation send email to
21 sendmail-bugs@sendmail.org.
22For local information send email to Postmaster at your site.
23End of HELP info
24>>> s.putcmd("vrfy","someone@here")
25>>> s.getreply()
26(250, "Somebody OverHere <somebody@here.my.org>")
27>>> s.quit()
28
29"""
30
31import socket
32import string,re
33
34SMTP_PORT = 25
35CRLF="\r\n"
36
37# used for exceptions
Guido van Rossumfc40a831998-01-29 17:26:45 +000038SMTPServerDisconnected="Server not connected"
Guido van Rossumbbe323e1998-01-29 17:24:40 +000039SMTPSenderRefused="Sender address refused"
40SMTPRecipientsRefused="All Recipients refused"
41SMTPDataError="Error transmoitting message data"
42
43class SMTP:
44 """This class manages a connection to an SMTP server."""
45
46 def __init__(self, host = '', port = 0):
47 """Initialize a new instance.
48
49 If specified, `host' is the name of the remote host to which
50 to connect. If specified, `port' specifies the port to which
51 to connect. By default, smtplib.SMTP_PORT is used.
52
53 """
54 self.debuglevel = 0
55 self.file = None
56 self.helo_resp = None
57 if host: self.connect(host, port)
58
59 def set_debuglevel(self, debuglevel):
60 """Set the debug output level.
61
62 A non-false value results in debug messages for connection and
63 for all messages sent to and received from the server.
64
65 """
66 self.debuglevel = debuglevel
67
68 def connect(self, host='localhost', port = 0):
69 """Connect to a host on a given port.
70
71 Note: This method is automatically invoked by __init__,
72 if a host is specified during instantiation.
73
74 """
75 if not port:
76 i = string.find(host, ':')
77 if i >= 0:
78 host, port = host[:i], host[i+1:]
79 try: port = string.atoi(port)
80 except string.atoi_error:
81 raise socket.error, "nonnumeric port"
82 if not port: port = SMTP_PORT
83 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
84 if self.debuglevel > 0: print 'connect:', (host, port)
85 self.sock.connect(host, port)
86 (code,msg)=self.getreply()
87 if self.debuglevel >0 : print "connect:", msg
88 return msg
89
90 def send(self, str):
91 """Send `str' to the server."""
92 if self.debuglevel > 0: print 'send:', `str`
Guido van Rossumfc40a831998-01-29 17:26:45 +000093 if self.sock:
94 self.sock.send(str)
95 else:
96 raise SMTPServerDisconnected
97
Guido van Rossumbbe323e1998-01-29 17:24:40 +000098 def putcmd(self, cmd, args=""):
99 """Send a command to the server.
100 """
101 str = '%s %s%s' % (cmd, args, CRLF)
102 self.send(str)
103
104 def getreply(self):
105 """Get a reply from the server.
106
107 Returns a tuple consisting of:
108 - server response code (e.g. '250', or such, if all goes well)
109 Note: returns -1 if it can't read responce code.
110 - server response string corresponding to response code
Guido van Rossumfc40a831998-01-29 17:26:45 +0000111 (note : multiline responces converted to a single,
112 multiline string)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000113 """
114 resp=[]
115 self.file = self.sock.makefile('rb')
116 while 1:
117 line = self.file.readline()
118 if self.debuglevel > 0: print 'reply:', `line`
119 resp.append(string.strip(line[4:]))
120 code=line[:3]
121 #check if multiline resp
122 if line[3:4]!="-":
123 break
124 try:
125 errcode = string.atoi(code)
126 except(ValueError):
127 errcode = -1
128
129 errmsg = string.join(resp,"\n")
130 if self.debuglevel > 0:
Guido van Rossumfc40a831998-01-29 17:26:45 +0000131 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000132 return errcode, errmsg
133
134 def docmd(self, cmd, args=""):
135 """ Send a command, and return it's responce code """
136
137 self.putcmd(cmd,args)
138 (code,msg)=self.getreply()
139 return code
140# std smtp commands
141
142 def helo(self, name=''):
143 """ SMTP 'helo' command. Hostname to send for this command
144 defaults to the FQDN of the local host """
145 name=string.strip(name)
146 if len(name)==0:
147 name=socket.gethostbyaddr(socket.gethostname())[0]
148 self.putcmd("helo",name)
149 (code,msg)=self.getreply()
150 self.helo_resp=msg
151 return code
152
153 def help(self):
Guido van Rossumfc40a831998-01-29 17:26:45 +0000154 """ SMTP 'help' command. Returns help text from server """
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000155 self.putcmd("help")
156 (code,msg)=self.getreply()
157 return msg
158
159 def rset(self):
160 """ SMTP 'rset' command. Resets session. """
161 code=self.docmd("rset")
162 return code
163
164 def noop(self):
165 """ SMTP 'noop' command. Dosen't do anything :> """
166 code=self.docmd("noop")
167 return code
168
169 def mail(self,sender):
170 """ SMTP 'mail' command. Begins mail xfer session. """
171 self.putcmd("mail","from: %s" % sender)
172 return self.getreply()
173
174 def rcpt(self,recip):
175 """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
176 self.putcmd("rcpt","to: %s" % recip)
177 return self.getreply()
178
179 def data(self,msg):
180 """ SMTP 'DATA' command. Sends message data to server.
181 Automatically quotes lines beginning with a period per rfc821 """
182 #quote periods in msg according to RFC821
183 # ps, I don't know why I have to do it this way... doing:
184 # quotepat=re.compile(r"^[.]",re.M)
185 # msg=re.sub(quotepat,"..",msg)
Guido van Rossumfc40a831998-01-29 17:26:45 +0000186 # should work, but it dosen't (it doubles the number of any
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000187 # contiguous series of .'s at the beginning of a line,
Guido van Rossumfc40a831998-01-29 17:26:45 +0000188 #instead of just adding one. )
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000189 quotepat=re.compile(r"^[.]+",re.M)
190 def m(pat):
191 return "."+pat.group(0)
192 msg=re.sub(quotepat,m,msg)
193 self.putcmd("data")
194 (code,repl)=self.getreply()
195 if self.debuglevel >0 : print "data:", (code,repl)
196 if code <> 354:
197 return -1
198 else:
199 self.send(msg)
200 self.send("\n.\n")
201 (code,msg)=self.getreply()
202 if self.debuglevel >0 : print "data:", (code,msg)
203 return code
204
205#some usefull methods
206 def sendmail(self,from_addr,to_addrs,msg):
207 """ This command performs an entire mail transaction.
208 The arguments are:
209 - from_addr : The address sending this mail.
210 - to_addrs : a list of addresses to send this mail to
211 - msg : the message to send.
212
Guido van Rossumfc40a831998-01-29 17:26:45 +0000213 This method will return normally if the mail is accepted for at least
214 one recipiant.
215 Otherwise it will throw an exception (either SMTPSenderRefused,
216 SMTPRecipientsRefused, or SMTPDataError)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000217
Guido van Rossumfc40a831998-01-29 17:26:45 +0000218 That is, if this method does not throw an exception, then someone
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000219 should get your mail.
220
Guido van Rossumfc40a831998-01-29 17:26:45 +0000221 It returns a dictionary , with one entry for each recipient that was
222 refused.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000223
224 example:
225
226 >>> import smtplib
227 >>> s=smtplib.SMTP("localhost")
Guido van Rossumfc40a831998-01-29 17:26:45 +0000228 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000229 >>> msg = '''
230 ... From: Me@my.org
231 ... Subject: testin'...
232 ...
233 ... This is a test '''
234 >>> s.sendmail("me@my.org",tolist,msg)
235 { "three@three.org" : ( 550 ,"User unknown" ) }
236 >>> s.quit()
237
Guido van Rossumfc40a831998-01-29 17:26:45 +0000238 In the above example, the message was accepted for delivery to
239 three of the four addresses, and one was rejected, with the error
240 code 550. If all addresses are accepted, then the method
241 will return an empty dictionary.
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000242 """
243
244 if not self.helo_resp:
245 self.helo()
246 (code,resp)=self.mail(from_addr)
247 if code <>250:
248 self.rset()
249 raise SMTPSenderRefused
250 senderrs={}
251 for each in to_addrs:
252 (code,resp)=self.rcpt(each)
253 if (code <> 250) and (code <> 251):
Guido van Rossumfc40a831998-01-29 17:26:45 +0000254 senderrs[each]=(code,resp)
Guido van Rossumbbe323e1998-01-29 17:24:40 +0000255 if len(senderrs)==len(to_addrs):
256 #th' server refused all our recipients
257 self.rset()
258 raise SMTPRecipientsRefused
259 code=self.data(msg)
260 if code <>250 :
261 self.rset()
262 raise SMTPDataError
263 #if we got here then somebody got our mail
264 return senderrs
265
266
267 def close(self):
268 """Close the connection to the SMTP server."""
269 if self.file:
270 self.file.close()
271 self.file = None
272 if self.sock:
273 self.sock.close()
274 self.sock = None
275
276
277 def quit(self):
278 self.docmd("quit")
279 self.close()