blob: e2b33ef10f499fd75ab1d709b1a3c9d7b4ca4437 [file] [log] [blame]
Andrew Hsiehffab9582013-06-18 12:29:14 -07001"""A POP3 client class.
2
3Based on the J. Myers POP3 draft, Jan. 96
4"""
5
6# Author: David Ascher <david_ascher@brown.edu>
7# [heavily stealing from nntplib.py]
8# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
9# String method conversion and test jig improvements by ESR, February 2001.
10# Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003
11
12# Example (see the test function at the end of this file)
13
14# Imports
15
16import re, socket
17
18__all__ = ["POP3","error_proto"]
19
20# Exception raised when an error or invalid response is received:
21
22class error_proto(Exception): pass
23
24# Standard Port
25POP3_PORT = 110
26
27# POP SSL PORT
28POP3_SSL_PORT = 995
29
30# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
31CR = '\r'
32LF = '\n'
33CRLF = CR+LF
34
35
36class POP3:
37
38 """This class supports both the minimal and optional command sets.
39 Arguments can be strings or integers (where appropriate)
40 (e.g.: retr(1) and retr('1') both work equally well.
41
42 Minimal Command Set:
43 USER name user(name)
44 PASS string pass_(string)
45 STAT stat()
46 LIST [msg] list(msg = None)
47 RETR msg retr(msg)
48 DELE msg dele(msg)
49 NOOP noop()
50 RSET rset()
51 QUIT quit()
52
53 Optional Commands (some servers support these):
54 RPOP name rpop(name)
55 APOP name digest apop(name, digest)
56 TOP msg n top(msg, n)
57 UIDL [msg] uidl(msg = None)
58
59 Raises one exception: 'error_proto'.
60
61 Instantiate with:
62 POP3(hostname, port=110)
63
64 NB: the POP protocol locks the mailbox from user
65 authorization until QUIT, so be sure to get in, suck
66 the messages, and quit, each time you access the
67 mailbox.
68
69 POP is a line-based protocol, which means large mail
70 messages consume lots of python cycles reading them
71 line-by-line.
72
73 If it's available on your mail server, use IMAP4
74 instead, it doesn't suffer from the two problems
75 above.
76 """
77
78
79 def __init__(self, host, port=POP3_PORT,
80 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
81 self.host = host
82 self.port = port
83 self.sock = socket.create_connection((host, port), timeout)
84 self.file = self.sock.makefile('rb')
85 self._debugging = 0
86 self.welcome = self._getresp()
87
88
89 def _putline(self, line):
90 if self._debugging > 1: print '*put*', repr(line)
91 self.sock.sendall('%s%s' % (line, CRLF))
92
93
94 # Internal: send one command to the server (through _putline())
95
96 def _putcmd(self, line):
97 if self._debugging: print '*cmd*', repr(line)
98 self._putline(line)
99
100
101 # Internal: return one line from the server, stripping CRLF.
102 # This is where all the CPU time of this module is consumed.
103 # Raise error_proto('-ERR EOF') if the connection is closed.
104
105 def _getline(self):
106 line = self.file.readline()
107 if self._debugging > 1: print '*get*', repr(line)
108 if not line: raise error_proto('-ERR EOF')
109 octets = len(line)
110 # server can send any combination of CR & LF
111 # however, 'readline()' returns lines ending in LF
112 # so only possibilities are ...LF, ...CRLF, CR...LF
113 if line[-2:] == CRLF:
114 return line[:-2], octets
115 if line[0] == CR:
116 return line[1:-1], octets
117 return line[:-1], octets
118
119
120 # Internal: get a response from the server.
121 # Raise 'error_proto' if the response doesn't start with '+'.
122
123 def _getresp(self):
124 resp, o = self._getline()
125 if self._debugging > 1: print '*resp*', repr(resp)
126 c = resp[:1]
127 if c != '+':
128 raise error_proto(resp)
129 return resp
130
131
132 # Internal: get a response plus following text from the server.
133
134 def _getlongresp(self):
135 resp = self._getresp()
136 list = []; octets = 0
137 line, o = self._getline()
138 while line != '.':
139 if line[:2] == '..':
140 o = o-1
141 line = line[1:]
142 octets = octets + o
143 list.append(line)
144 line, o = self._getline()
145 return resp, list, octets
146
147
148 # Internal: send a command and get the response
149
150 def _shortcmd(self, line):
151 self._putcmd(line)
152 return self._getresp()
153
154
155 # Internal: send a command and get the response plus following text
156
157 def _longcmd(self, line):
158 self._putcmd(line)
159 return self._getlongresp()
160
161
162 # These can be useful:
163
164 def getwelcome(self):
165 return self.welcome
166
167
168 def set_debuglevel(self, level):
169 self._debugging = level
170
171
172 # Here are all the POP commands:
173
174 def user(self, user):
175 """Send user name, return response
176
177 (should indicate password required).
178 """
179 return self._shortcmd('USER %s' % user)
180
181
182 def pass_(self, pswd):
183 """Send password, return response
184
185 (response includes message count, mailbox size).
186
187 NB: mailbox is locked by server from here to 'quit()'
188 """
189 return self._shortcmd('PASS %s' % pswd)
190
191
192 def stat(self):
193 """Get mailbox status.
194
195 Result is tuple of 2 ints (message count, mailbox size)
196 """
197 retval = self._shortcmd('STAT')
198 rets = retval.split()
199 if self._debugging: print '*stat*', repr(rets)
200 numMessages = int(rets[1])
201 sizeMessages = int(rets[2])
202 return (numMessages, sizeMessages)
203
204
205 def list(self, which=None):
206 """Request listing, return result.
207
208 Result without a message number argument is in form
209 ['response', ['mesg_num octets', ...], octets].
210
211 Result when a message number argument is given is a
212 single response: the "scan listing" for that message.
213 """
214 if which is not None:
215 return self._shortcmd('LIST %s' % which)
216 return self._longcmd('LIST')
217
218
219 def retr(self, which):
220 """Retrieve whole message number 'which'.
221
222 Result is in form ['response', ['line', ...], octets].
223 """
224 return self._longcmd('RETR %s' % which)
225
226
227 def dele(self, which):
228 """Delete message number 'which'.
229
230 Result is 'response'.
231 """
232 return self._shortcmd('DELE %s' % which)
233
234
235 def noop(self):
236 """Does nothing.
237
238 One supposes the response indicates the server is alive.
239 """
240 return self._shortcmd('NOOP')
241
242
243 def rset(self):
244 """Unmark all messages marked for deletion."""
245 return self._shortcmd('RSET')
246
247
248 def quit(self):
249 """Signoff: commit changes on server, unlock mailbox, close connection."""
250 try:
251 resp = self._shortcmd('QUIT')
252 except error_proto, val:
253 resp = val
254 self.file.close()
255 self.sock.close()
256 del self.file, self.sock
257 return resp
258
259 #__del__ = quit
260
261
262 # optional commands:
263
264 def rpop(self, user):
265 """Not sure what this does."""
266 return self._shortcmd('RPOP %s' % user)
267
268
269 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
270
271 def apop(self, user, secret):
272 """Authorisation
273
274 - only possible if server has supplied a timestamp in initial greeting.
275
276 Args:
277 user - mailbox user;
278 secret - secret shared between client and server.
279
280 NB: mailbox is locked by server from here to 'quit()'
281 """
282 m = self.timestamp.match(self.welcome)
283 if not m:
284 raise error_proto('-ERR APOP not supported by server')
285 import hashlib
286 digest = hashlib.md5(m.group(1)+secret).digest()
287 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
288 return self._shortcmd('APOP %s %s' % (user, digest))
289
290
291 def top(self, which, howmuch):
292 """Retrieve message header of message number 'which'
293 and first 'howmuch' lines of message body.
294
295 Result is in form ['response', ['line', ...], octets].
296 """
297 return self._longcmd('TOP %s %s' % (which, howmuch))
298
299
300 def uidl(self, which=None):
301 """Return message digest (unique id) list.
302
303 If 'which', result contains unique id for that message
304 in the form 'response mesgnum uid', otherwise result is
305 the list ['response', ['mesgnum uid', ...], octets]
306 """
307 if which is not None:
308 return self._shortcmd('UIDL %s' % which)
309 return self._longcmd('UIDL')
310
311try:
312 import ssl
313except ImportError:
314 pass
315else:
316
317 class POP3_SSL(POP3):
318 """POP3 client class over SSL connection
319
320 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
321
322 hostname - the hostname of the pop3 over ssl server
323 port - port number
324 keyfile - PEM formatted file that countains your private key
325 certfile - PEM formatted certificate chain file
326
327 See the methods of the parent class POP3 for more documentation.
328 """
329
330 def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None):
331 self.host = host
332 self.port = port
333 self.keyfile = keyfile
334 self.certfile = certfile
335 self.buffer = ""
336 msg = "getaddrinfo returns an empty list"
337 self.sock = None
338 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
339 af, socktype, proto, canonname, sa = res
340 try:
341 self.sock = socket.socket(af, socktype, proto)
342 self.sock.connect(sa)
343 except socket.error, msg:
344 if self.sock:
345 self.sock.close()
346 self.sock = None
347 continue
348 break
349 if not self.sock:
350 raise socket.error, msg
351 self.file = self.sock.makefile('rb')
352 self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
353 self._debugging = 0
354 self.welcome = self._getresp()
355
356 def _fillBuffer(self):
357 localbuf = self.sslobj.read()
358 if len(localbuf) == 0:
359 raise error_proto('-ERR EOF')
360 self.buffer += localbuf
361
362 def _getline(self):
363 line = ""
364 renewline = re.compile(r'.*?\n')
365 match = renewline.match(self.buffer)
366 while not match:
367 self._fillBuffer()
368 match = renewline.match(self.buffer)
369 line = match.group(0)
370 self.buffer = renewline.sub('' ,self.buffer, 1)
371 if self._debugging > 1: print '*get*', repr(line)
372
373 octets = len(line)
374 if line[-2:] == CRLF:
375 return line[:-2], octets
376 if line[0] == CR:
377 return line[1:-1], octets
378 return line[:-1], octets
379
380 def _putline(self, line):
381 if self._debugging > 1: print '*put*', repr(line)
382 line += CRLF
383 bytes = len(line)
384 while bytes > 0:
385 sent = self.sslobj.write(line)
386 if sent == bytes:
387 break # avoid copy
388 line = line[sent:]
389 bytes = bytes - sent
390
391 def quit(self):
392 """Signoff: commit changes on server, unlock mailbox, close connection."""
393 try:
394 resp = self._shortcmd('QUIT')
395 except error_proto, val:
396 resp = val
397 self.sock.close()
398 del self.sslobj, self.sock
399 return resp
400
401 __all__.append("POP3_SSL")
402
403if __name__ == "__main__":
404 import sys
405 a = POP3(sys.argv[1])
406 print a.getwelcome()
407 a.user(sys.argv[2])
408 a.pass_(sys.argv[3])
409 a.list()
410 (numMsgs, totalSize) = a.stat()
411 for i in range(1, numMsgs + 1):
412 (header, msg, octets) = a.retr(i)
413 print "Message %d:" % i
414 for line in msg:
415 print ' ' + line
416 print '-----------------------'
417 a.quit()