• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""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()
484