blob: 0f8587317c2bbc4c37c79b9112ffc281c67bcebd [file] [log] [blame]
Olivier Deprezf4ef2d02021-04-20 13:36:24 +02001"""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 errno
17import re
18import socket
19import sys
20
21try:
22 import ssl
23 HAVE_SSL = True
24except ImportError:
25 HAVE_SSL = False
26
27__all__ = ["POP3","error_proto"]
28
29# Exception raised when an error or invalid response is received:
30
31class error_proto(Exception): pass
32
33# Standard Port
34POP3_PORT = 110
35
36# POP SSL PORT
37POP3_SSL_PORT = 995
38
39# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
40CR = b'\r'
41LF = b'\n'
42CRLF = CR+LF
43
44# maximal line length when calling readline(). This is to prevent
45# reading arbitrary length lines. RFC 1939 limits POP3 line length to
46# 512 characters, including CRLF. We have selected 2048 just to be on
47# the safe side.
48_MAXLINE = 2048
49
50
51class POP3:
52
53 """This class supports both the minimal and optional command sets.
54 Arguments can be strings or integers (where appropriate)
55 (e.g.: retr(1) and retr('1') both work equally well.
56
57 Minimal Command Set:
58 USER name user(name)
59 PASS string pass_(string)
60 STAT stat()
61 LIST [msg] list(msg = None)
62 RETR msg retr(msg)
63 DELE msg dele(msg)
64 NOOP noop()
65 RSET rset()
66 QUIT quit()
67
68 Optional Commands (some servers support these):
69 RPOP name rpop(name)
70 APOP name digest apop(name, digest)
71 TOP msg n top(msg, n)
72 UIDL [msg] uidl(msg = None)
73 CAPA capa()
74 STLS stls()
75 UTF8 utf8()
76
77 Raises one exception: 'error_proto'.
78
79 Instantiate with:
80 POP3(hostname, port=110)
81
82 NB: the POP protocol locks the mailbox from user
83 authorization until QUIT, so be sure to get in, suck
84 the messages, and quit, each time you access the
85 mailbox.
86
87 POP is a line-based protocol, which means large mail
88 messages consume lots of python cycles reading them
89 line-by-line.
90
91 If it's available on your mail server, use IMAP4
92 instead, it doesn't suffer from the two problems
93 above.
94 """
95
96 encoding = 'UTF-8'
97
98 def __init__(self, host, port=POP3_PORT,
99 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
100 self.host = host
101 self.port = port
102 self._tls_established = False
103 sys.audit("poplib.connect", self, host, port)
104 self.sock = self._create_socket(timeout)
105 self.file = self.sock.makefile('rb')
106 self._debugging = 0
107 self.welcome = self._getresp()
108
109 def _create_socket(self, timeout):
110 if timeout is not None and not timeout:
111 raise ValueError('Non-blocking socket (timeout=0) is not supported')
112 return socket.create_connection((self.host, self.port), timeout)
113
114 def _putline(self, line):
115 if self._debugging > 1: print('*put*', repr(line))
116 sys.audit("poplib.putline", self, line)
117 self.sock.sendall(line + CRLF)
118
119
120 # Internal: send one command to the server (through _putline())
121
122 def _putcmd(self, line):
123 if self._debugging: print('*cmd*', repr(line))
124 line = bytes(line, self.encoding)
125 self._putline(line)
126
127
128 # Internal: return one line from the server, stripping CRLF.
129 # This is where all the CPU time of this module is consumed.
130 # Raise error_proto('-ERR EOF') if the connection is closed.
131
132 def _getline(self):
133 line = self.file.readline(_MAXLINE + 1)
134 if len(line) > _MAXLINE:
135 raise error_proto('line too long')
136
137 if self._debugging > 1: print('*get*', repr(line))
138 if not line: raise error_proto('-ERR EOF')
139 octets = len(line)
140 # server can send any combination of CR & LF
141 # however, 'readline()' returns lines ending in LF
142 # so only possibilities are ...LF, ...CRLF, CR...LF
143 if line[-2:] == CRLF:
144 return line[:-2], octets
145 if line[:1] == CR:
146 return line[1:-1], octets
147 return line[:-1], octets
148
149
150 # Internal: get a response from the server.
151 # Raise 'error_proto' if the response doesn't start with '+'.
152
153 def _getresp(self):
154 resp, o = self._getline()
155 if self._debugging > 1: print('*resp*', repr(resp))
156 if not resp.startswith(b'+'):
157 raise error_proto(resp)
158 return resp
159
160
161 # Internal: get a response plus following text from the server.
162
163 def _getlongresp(self):
164 resp = self._getresp()
165 list = []; octets = 0
166 line, o = self._getline()
167 while line != b'.':
168 if line.startswith(b'..'):
169 o = o-1
170 line = line[1:]
171 octets = octets + o
172 list.append(line)
173 line, o = self._getline()
174 return resp, list, octets
175
176
177 # Internal: send a command and get the response
178
179 def _shortcmd(self, line):
180 self._putcmd(line)
181 return self._getresp()
182
183
184 # Internal: send a command and get the response plus following text
185
186 def _longcmd(self, line):
187 self._putcmd(line)
188 return self._getlongresp()
189
190
191 # These can be useful:
192
193 def getwelcome(self):
194 return self.welcome
195
196
197 def set_debuglevel(self, level):
198 self._debugging = level
199
200
201 # Here are all the POP commands:
202
203 def user(self, user):
204 """Send user name, return response
205
206 (should indicate password required).
207 """
208 return self._shortcmd('USER %s' % user)
209
210
211 def pass_(self, pswd):
212 """Send password, return response
213
214 (response includes message count, mailbox size).
215
216 NB: mailbox is locked by server from here to 'quit()'
217 """
218 return self._shortcmd('PASS %s' % pswd)
219
220
221 def stat(self):
222 """Get mailbox status.
223
224 Result is tuple of 2 ints (message count, mailbox size)
225 """
226 retval = self._shortcmd('STAT')
227 rets = retval.split()
228 if self._debugging: print('*stat*', repr(rets))
229 numMessages = int(rets[1])
230 sizeMessages = int(rets[2])
231 return (numMessages, sizeMessages)
232
233
234 def list(self, which=None):
235 """Request listing, return result.
236
237 Result without a message number argument is in form
238 ['response', ['mesg_num octets', ...], octets].
239
240 Result when a message number argument is given is a
241 single response: the "scan listing" for that message.
242 """
243 if which is not None:
244 return self._shortcmd('LIST %s' % which)
245 return self._longcmd('LIST')
246
247
248 def retr(self, which):
249 """Retrieve whole message number 'which'.
250
251 Result is in form ['response', ['line', ...], octets].
252 """
253 return self._longcmd('RETR %s' % which)
254
255
256 def dele(self, which):
257 """Delete message number 'which'.
258
259 Result is 'response'.
260 """
261 return self._shortcmd('DELE %s' % which)
262
263
264 def noop(self):
265 """Does nothing.
266
267 One supposes the response indicates the server is alive.
268 """
269 return self._shortcmd('NOOP')
270
271
272 def rset(self):
273 """Unmark all messages marked for deletion."""
274 return self._shortcmd('RSET')
275
276
277 def quit(self):
278 """Signoff: commit changes on server, unlock mailbox, close connection."""
279 resp = self._shortcmd('QUIT')
280 self.close()
281 return resp
282
283 def close(self):
284 """Close the connection without assuming anything about it."""
285 try:
286 file = self.file
287 self.file = None
288 if file is not None:
289 file.close()
290 finally:
291 sock = self.sock
292 self.sock = None
293 if sock is not None:
294 try:
295 sock.shutdown(socket.SHUT_RDWR)
296 except OSError as exc:
297 # The server might already have closed the connection.
298 # On Windows, this may result in WSAEINVAL (error 10022):
299 # An invalid operation was attempted.
300 if (exc.errno != errno.ENOTCONN
301 and getattr(exc, 'winerror', 0) != 10022):
302 raise
303 finally:
304 sock.close()
305
306 #__del__ = quit
307
308
309 # optional commands:
310
311 def rpop(self, user):
312 """Not sure what this does."""
313 return self._shortcmd('RPOP %s' % user)
314
315
316 timestamp = re.compile(br'\+OK.[^<]*(<.*>)')
317
318 def apop(self, user, password):
319 """Authorisation
320
321 - only possible if server has supplied a timestamp in initial greeting.
322
323 Args:
324 user - mailbox user;
325 password - mailbox password.
326
327 NB: mailbox is locked by server from here to 'quit()'
328 """
329 secret = bytes(password, self.encoding)
330 m = self.timestamp.match(self.welcome)
331 if not m:
332 raise error_proto('-ERR APOP not supported by server')
333 import hashlib
334 digest = m.group(1)+secret
335 digest = hashlib.md5(digest).hexdigest()
336 return self._shortcmd('APOP %s %s' % (user, digest))
337
338
339 def top(self, which, howmuch):
340 """Retrieve message header of message number 'which'
341 and first 'howmuch' lines of message body.
342
343 Result is in form ['response', ['line', ...], octets].
344 """
345 return self._longcmd('TOP %s %s' % (which, howmuch))
346
347
348 def uidl(self, which=None):
349 """Return message digest (unique id) list.
350
351 If 'which', result contains unique id for that message
352 in the form 'response mesgnum uid', otherwise result is
353 the list ['response', ['mesgnum uid', ...], octets]
354 """
355 if which is not None:
356 return self._shortcmd('UIDL %s' % which)
357 return self._longcmd('UIDL')
358
359
360 def utf8(self):
361 """Try to enter UTF-8 mode (see RFC 6856). Returns server response.
362 """
363 return self._shortcmd('UTF8')
364
365
366 def capa(self):
367 """Return server capabilities (RFC 2449) as a dictionary
368 >>> c=poplib.POP3('localhost')
369 >>> c.capa()
370 {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],
371 'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],
372 'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],
373 'UIDL': [], 'RESP-CODES': []}
374 >>>
375
376 Really, according to RFC 2449, the cyrus folks should avoid
377 having the implementation split into multiple arguments...
378 """
379 def _parsecap(line):
380 lst = line.decode('ascii').split()
381 return lst[0], lst[1:]
382
383 caps = {}
384 try:
385 resp = self._longcmd('CAPA')
386 rawcaps = resp[1]
387 for capline in rawcaps:
388 capnm, capargs = _parsecap(capline)
389 caps[capnm] = capargs
390 except error_proto:
391 raise error_proto('-ERR CAPA not supported by server')
392 return caps
393
394
395 def stls(self, context=None):
396 """Start a TLS session on the active connection as specified in RFC 2595.
397
398 context - a ssl.SSLContext
399 """
400 if not HAVE_SSL:
401 raise error_proto('-ERR TLS support missing')
402 if self._tls_established:
403 raise error_proto('-ERR TLS session already established')
404 caps = self.capa()
405 if not 'STLS' in caps:
406 raise error_proto('-ERR STLS not supported by server')
407 if context is None:
408 context = ssl._create_stdlib_context()
409 resp = self._shortcmd('STLS')
410 self.sock = context.wrap_socket(self.sock,
411 server_hostname=self.host)
412 self.file = self.sock.makefile('rb')
413 self._tls_established = True
414 return resp
415
416
417if HAVE_SSL:
418
419 class POP3_SSL(POP3):
420 """POP3 client class over SSL connection
421
422 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None,
423 context=None)
424
425 hostname - the hostname of the pop3 over ssl server
426 port - port number
427 keyfile - PEM formatted file that contains your private key
428 certfile - PEM formatted certificate chain file
429 context - a ssl.SSLContext
430
431 See the methods of the parent class POP3 for more documentation.
432 """
433
434 def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None,
435 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
436 if context is not None and keyfile is not None:
437 raise ValueError("context and keyfile arguments are mutually "
438 "exclusive")
439 if context is not None and certfile is not None:
440 raise ValueError("context and certfile arguments are mutually "
441 "exclusive")
442 if keyfile is not None or certfile is not None:
443 import warnings
444 warnings.warn("keyfile and certfile are deprecated, use a "
445 "custom context instead", DeprecationWarning, 2)
446 self.keyfile = keyfile
447 self.certfile = certfile
448 if context is None:
449 context = ssl._create_stdlib_context(certfile=certfile,
450 keyfile=keyfile)
451 self.context = context
452 POP3.__init__(self, host, port, timeout)
453
454 def _create_socket(self, timeout):
455 sock = POP3._create_socket(self, timeout)
456 sock = self.context.wrap_socket(sock,
457 server_hostname=self.host)
458 return sock
459
460 def stls(self, keyfile=None, certfile=None, context=None):
461 """The method unconditionally raises an exception since the
462 STLS command doesn't make any sense on an already established
463 SSL/TLS session.
464 """
465 raise error_proto('-ERR TLS session already established')
466
467 __all__.append("POP3_SSL")
468
469if __name__ == "__main__":
470 import sys
471 a = POP3(sys.argv[1])
472 print(a.getwelcome())
473 a.user(sys.argv[2])
474 a.pass_(sys.argv[3])
475 a.list()
476 (numMsgs, totalSize) = a.stat()
477 for i in range(1, numMsgs + 1):
478 (header, msg, octets) = a.retr(i)
479 print("Message %d:" % i)
480 for line in msg:
481 print(' ' + line)
482 print('-----------------------')
483 a.quit()