Oops -- Lib/Test should be Lib/test, of course!
[python/dscho.git] / Lib / smtplib.py
blob6a12621a24bafb7f8682080ce33317c56e4d4152
1 """SMTP Client class.
3 Author: The Dragon De Monsyne <dragondm@integral.org>
5 (This was modified from the Python 1.5 library HTTP lib.)
7 This should follow RFC 821. (it dosen't do esmtp (yet))
9 Example:
11 >>> import smtplib
12 >>> s=smtplib.SMTP("localhost")
13 >>> print s.help()
14 This is Sendmail version 8.8.4
15 Topics:
16 HELO EHLO MAIL RCPT DATA
17 RSET NOOP QUIT HELP VRFY
18 EXPN VERB ETRN DSN
19 For more info use "HELP <topic>".
20 To report bugs in the implementation send email to
21 sendmail-bugs@sendmail.org.
22 For local information send email to Postmaster at your site.
23 End of HELP info
24 >>> s.putcmd("vrfy","someone@here")
25 >>> s.getreply()
26 (250, "Somebody OverHere <somebody@here.my.org>")
27 >>> s.quit()
29 """
31 import socket
32 import string,re
34 SMTP_PORT = 25
35 CRLF="\r\n"
37 # used for exceptions
38 SMTPServerDisconnected="Server not connected"
39 SMTPSenderRefused="Sender address refused"
40 SMTPRecipientsRefused="All Recipients refused"
41 SMTPDataError="Error transmoitting message data"
43 class SMTP:
44 """This class manages a connection to an SMTP server."""
46 def __init__(self, host = '', port = 0):
47 """Initialize a new instance.
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.
53 """
54 self.debuglevel = 0
55 self.file = None
56 self.helo_resp = None
57 if host: self.connect(host, port)
59 def set_debuglevel(self, debuglevel):
60 """Set the debug output level.
62 A non-false value results in debug messages for connection and
63 for all messages sent to and received from the server.
65 """
66 self.debuglevel = debuglevel
68 def connect(self, host='localhost', port = 0):
69 """Connect to a host on a given port.
71 Note: This method is automatically invoked by __init__,
72 if a host is specified during instantiation.
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
90 def send(self, str):
91 """Send `str' to the server."""
92 if self.debuglevel > 0: print 'send:', `str`
93 if self.sock:
94 self.sock.send(str)
95 else:
96 raise SMTPServerDisconnected
98 def putcmd(self, cmd, args=""):
99 """Send a command to the server.
101 str = '%s %s%s' % (cmd, args, CRLF)
102 self.send(str)
104 def getreply(self):
105 """Get a reply from the server.
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
111 (note : multiline responces converted to a single,
112 multiline string)
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
129 errmsg = string.join(resp,"\n")
130 if self.debuglevel > 0:
131 print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
132 return errcode, errmsg
134 def docmd(self, cmd, args=""):
135 """ Send a command, and return it's responce code """
137 self.putcmd(cmd,args)
138 (code,msg)=self.getreply()
139 return code
140 # std smtp commands
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
153 def help(self, args=''):
154 """ SMTP 'help' command. Returns help text from server """
155 self.putcmd("help", args)
156 (code,msg)=self.getreply()
157 return msg
159 def rset(self):
160 """ SMTP 'rset' command. Resets session. """
161 code=self.docmd("rset")
162 return code
164 def noop(self):
165 """ SMTP 'noop' command. Dosen't do anything :> """
166 code=self.docmd("noop")
167 return code
169 def mail(self,sender):
170 """ SMTP 'mail' command. Begins mail xfer session. """
171 self.putcmd("mail","from: %s" % sender)
172 return self.getreply()
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()
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)
186 # should work, but it dosen't (it doubles the number of any
187 # contiguous series of .'s at the beginning of a line,
188 #instead of just adding one. )
189 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
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.
213 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)
218 That is, if this method does not throw an exception, then someone
219 should get your mail.
221 It returns a dictionary , with one entry for each recipient that was
222 refused.
224 example:
226 >>> import smtplib
227 >>> s=smtplib.SMTP("localhost")
228 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
229 >>> msg = '''
230 ... From: Me@my.org
231 ... Subject: testin'...
233 ... This is a test '''
234 >>> s.sendmail("me@my.org",tolist,msg)
235 { "three@three.org" : ( 550 ,"User unknown" ) }
236 >>> s.quit()
238 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.
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):
254 senderrs[each]=(code,resp)
255 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
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
277 def quit(self):
278 self.docmd("quit")
279 self.close()