poplib.py 14 KB
Newer Older
1
"""A POP3 client class.
2

3
Based on the J. Myers POP3 draft, Jan. 96
4 5
"""

6 7 8
# Author: David Ascher <david_ascher@brown.edu>
#         [heavily stealing from nntplib.py]
# 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 13 14 15
# Example (see the test function at the end of this file)

# Imports

16 17 18
import errno
import re
import socket
19

20 21 22
try:
    import ssl
    HAVE_SSL = True
23
except ImportError:
24 25
    HAVE_SSL = False

26
__all__ = ["POP3","error_proto"]
27

28
# Exception raised when an error or invalid response is received:
29 30

class error_proto(Exception): pass
31 32 33 34

# Standard Port
POP3_PORT = 110

35 36 37
# POP SSL PORT
POP3_SSL_PORT = 995

38
# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
39 40
CR = b'\r'
LF = b'\n'
41
CRLF = CR+LF
42

43
# maximal line length when calling readline(). This is to prevent
44
# reading arbitrary length lines. RFC 1939 limits POP3 line length to
45 46 47 48
# 512 characters, including CRLF. We have selected 2048 just to be on
# the safe side.
_MAXLINE = 2048

49 50

class POP3:
51

Tim Peters's avatar
Tim Peters committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
    """This class supports both the minimal and optional command sets.
    Arguments can be strings or integers (where appropriate)
    (e.g.: retr(1) and retr('1') both work equally well.

    Minimal Command Set:
            USER name               user(name)
            PASS string             pass_(string)
            STAT                    stat()
            LIST [msg]              list(msg = None)
            RETR msg                retr(msg)
            DELE msg                dele(msg)
            NOOP                    noop()
            RSET                    rset()
            QUIT                    quit()

    Optional Commands (some servers support these):
            RPOP name               rpop(name)
            APOP name digest        apop(name, digest)
            TOP msg n               top(msg, n)
            UIDL [msg]              uidl(msg = None)
72
            CAPA                    capa()
73
            STLS                    stls()
Tim Peters's avatar
Tim Peters committed
74 75

    Raises one exception: 'error_proto'.
76

Tim Peters's avatar
Tim Peters committed
77 78
    Instantiate with:
            POP3(hostname, port=110)
79

Tim Peters's avatar
Tim Peters committed
80 81 82 83
    NB:     the POP protocol locks the mailbox from user
            authorization until QUIT, so be sure to get in, suck
            the messages, and quit, each time you access the
            mailbox.
84

Tim Peters's avatar
Tim Peters committed
85 86 87
            POP is a line-based protocol, which means large mail
            messages consume lots of python cycles reading them
            line-by-line.
88

Tim Peters's avatar
Tim Peters committed
89 90 91 92
            If it's available on your mail server, use IMAP4
            instead, it doesn't suffer from the two problems
            above.
    """
93

94
    encoding = 'UTF-8'
95

Georg Brandl's avatar
Georg Brandl committed
96 97
    def __init__(self, host, port=POP3_PORT,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
98 99
        self.host = host
        self.port = port
100
        self._tls_established = False
101
        self.sock = self._create_socket(timeout)
102 103 104
        self.file = self.sock.makefile('rb')
        self._debugging = 0
        self.welcome = self._getresp()
105

106 107
    def _create_socket(self, timeout):
        return socket.create_connection((self.host, self.port), timeout)
108

Tim Peters's avatar
Tim Peters committed
109
    def _putline(self, line):
110
        if self._debugging > 1: print('*put*', repr(line))
111
        self.sock.sendall(line + CRLF)
112

113

Tim Peters's avatar
Tim Peters committed
114
    # Internal: send one command to the server (through _putline())
115

Tim Peters's avatar
Tim Peters committed
116
    def _putcmd(self, line):
117
        if self._debugging: print('*cmd*', repr(line))
118
        line = bytes(line, self.encoding)
Tim Peters's avatar
Tim Peters committed
119
        self._putline(line)
120

121

Tim Peters's avatar
Tim Peters committed
122 123 124
    # Internal: return one line from the server, stripping CRLF.
    # This is where all the CPU time of this module is consumed.
    # Raise error_proto('-ERR EOF') if the connection is closed.
125

Tim Peters's avatar
Tim Peters committed
126
    def _getline(self):
127 128 129 130
        line = self.file.readline(_MAXLINE + 1)
        if len(line) > _MAXLINE:
            raise error_proto('line too long')

131
        if self._debugging > 1: print('*get*', repr(line))
Tim Peters's avatar
Tim Peters committed
132 133 134 135 136 137 138
        if not line: raise error_proto('-ERR EOF')
        octets = len(line)
        # server can send any combination of CR & LF
        # however, 'readline()' returns lines ending in LF
        # so only possibilities are ...LF, ...CRLF, CR...LF
        if line[-2:] == CRLF:
            return line[:-2], octets
139
        if line[:1] == CR:
Tim Peters's avatar
Tim Peters committed
140 141
            return line[1:-1], octets
        return line[:-1], octets
142

143

Tim Peters's avatar
Tim Peters committed
144 145
    # Internal: get a response from the server.
    # Raise 'error_proto' if the response doesn't start with '+'.
146

Tim Peters's avatar
Tim Peters committed
147 148
    def _getresp(self):
        resp, o = self._getline()
149
        if self._debugging > 1: print('*resp*', repr(resp))
150
        if not resp.startswith(b'+'):
Tim Peters's avatar
Tim Peters committed
151 152
            raise error_proto(resp)
        return resp
153

154

Tim Peters's avatar
Tim Peters committed
155
    # Internal: get a response plus following text from the server.
156

Tim Peters's avatar
Tim Peters committed
157 158 159 160
    def _getlongresp(self):
        resp = self._getresp()
        list = []; octets = 0
        line, o = self._getline()
161
        while line != b'.':
162
            if line.startswith(b'..'):
Tim Peters's avatar
Tim Peters committed
163 164 165 166 167 168
                o = o-1
                line = line[1:]
            octets = octets + o
            list.append(line)
            line, o = self._getline()
        return resp, list, octets
169

170

Tim Peters's avatar
Tim Peters committed
171
    # Internal: send a command and get the response
172

Tim Peters's avatar
Tim Peters committed
173 174 175
    def _shortcmd(self, line):
        self._putcmd(line)
        return self._getresp()
176

177

Tim Peters's avatar
Tim Peters committed
178
    # Internal: send a command and get the response plus following text
179

Tim Peters's avatar
Tim Peters committed
180 181 182
    def _longcmd(self, line):
        self._putcmd(line)
        return self._getlongresp()
183

184

Tim Peters's avatar
Tim Peters committed
185
    # These can be useful:
186

Tim Peters's avatar
Tim Peters committed
187 188
    def getwelcome(self):
        return self.welcome
189

190

Tim Peters's avatar
Tim Peters committed
191 192
    def set_debuglevel(self, level):
        self._debugging = level
193

194

Tim Peters's avatar
Tim Peters committed
195
    # Here are all the POP commands:
196

Tim Peters's avatar
Tim Peters committed
197 198
    def user(self, user):
        """Send user name, return response
199

Tim Peters's avatar
Tim Peters committed
200 201 202
        (should indicate password required).
        """
        return self._shortcmd('USER %s' % user)
203

204

Tim Peters's avatar
Tim Peters committed
205 206
    def pass_(self, pswd):
        """Send password, return response
207

Tim Peters's avatar
Tim Peters committed
208
        (response includes message count, mailbox size).
209

Tim Peters's avatar
Tim Peters committed
210 211 212
        NB: mailbox is locked by server from here to 'quit()'
        """
        return self._shortcmd('PASS %s' % pswd)
213

214

Tim Peters's avatar
Tim Peters committed
215 216
    def stat(self):
        """Get mailbox status.
217

Tim Peters's avatar
Tim Peters committed
218 219 220
        Result is tuple of 2 ints (message count, mailbox size)
        """
        retval = self._shortcmd('STAT')
221
        rets = retval.split()
222
        if self._debugging: print('*stat*', repr(rets))
223 224
        numMessages = int(rets[1])
        sizeMessages = int(rets[2])
Tim Peters's avatar
Tim Peters committed
225
        return (numMessages, sizeMessages)
226

227

Tim Peters's avatar
Tim Peters committed
228 229
    def list(self, which=None):
        """Request listing, return result.
230

Tim Peters's avatar
Tim Peters committed
231
        Result without a message number argument is in form
232
        ['response', ['mesg_num octets', ...], octets].
233

Tim Peters's avatar
Tim Peters committed
234 235 236
        Result when a message number argument is given is a
        single response: the "scan listing" for that message.
        """
237
        if which is not None:
Tim Peters's avatar
Tim Peters committed
238 239
            return self._shortcmd('LIST %s' % which)
        return self._longcmd('LIST')
240

241

Tim Peters's avatar
Tim Peters committed
242 243
    def retr(self, which):
        """Retrieve whole message number 'which'.
244

Tim Peters's avatar
Tim Peters committed
245 246 247
        Result is in form ['response', ['line', ...], octets].
        """
        return self._longcmd('RETR %s' % which)
248

249

Tim Peters's avatar
Tim Peters committed
250 251
    def dele(self, which):
        """Delete message number 'which'.
252

Tim Peters's avatar
Tim Peters committed
253 254 255
        Result is 'response'.
        """
        return self._shortcmd('DELE %s' % which)
256

257

Tim Peters's avatar
Tim Peters committed
258 259
    def noop(self):
        """Does nothing.
260

Tim Peters's avatar
Tim Peters committed
261 262 263
        One supposes the response indicates the server is alive.
        """
        return self._shortcmd('NOOP')
264

265

Tim Peters's avatar
Tim Peters committed
266
    def rset(self):
Benjamin Peterson's avatar
Benjamin Peterson committed
267
        """Unmark all messages marked for deletion."""
Tim Peters's avatar
Tim Peters committed
268
        return self._shortcmd('RSET')
269 270


Tim Peters's avatar
Tim Peters committed
271 272
    def quit(self):
        """Signoff: commit changes on server, unlock mailbox, close connection."""
273 274
        resp = self._shortcmd('QUIT')
        self.close()
Tim Peters's avatar
Tim Peters committed
275
        return resp
276

277 278
    def close(self):
        """Close the connection without assuming anything about it."""
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        try:
            file = self.file
            self.file = None
            if file is not None:
                file.close()
        finally:
            sock = self.sock
            self.sock = None
            if sock is not None:
                try:
                    sock.shutdown(socket.SHUT_RDWR)
                except OSError as e:
                    # The server might already have closed the connection
                    if e.errno != errno.ENOTCONN:
                        raise
                finally:
                    sock.close()
296

Tim Peters's avatar
Tim Peters committed
297
    #__del__ = quit
298 299


Tim Peters's avatar
Tim Peters committed
300
    # optional commands:
301

Tim Peters's avatar
Tim Peters committed
302 303 304
    def rpop(self, user):
        """Not sure what this does."""
        return self._shortcmd('RPOP %s' % user)
305 306


307
    timestamp = re.compile(br'\+OK.*(<[^>]+>)')
308

309
    def apop(self, user, password):
Tim Peters's avatar
Tim Peters committed
310
        """Authorisation
311

Tim Peters's avatar
Tim Peters committed
312
        - only possible if server has supplied a timestamp in initial greeting.
313

Tim Peters's avatar
Tim Peters committed
314
        Args:
315 316
                user     - mailbox user;
                password - mailbox password.
317

Tim Peters's avatar
Tim Peters committed
318 319
        NB: mailbox is locked by server from here to 'quit()'
        """
320
        secret = bytes(password, self.encoding)
321 322
        m = self.timestamp.match(self.welcome)
        if not m:
Tim Peters's avatar
Tim Peters committed
323
            raise error_proto('-ERR APOP not supported by server')
324
        import hashlib
325 326
        digest = m.group(1)+secret
        digest = hashlib.md5(digest).hexdigest()
Tim Peters's avatar
Tim Peters committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
        return self._shortcmd('APOP %s %s' % (user, digest))


    def top(self, which, howmuch):
        """Retrieve message header of message number 'which'
        and first 'howmuch' lines of message body.

        Result is in form ['response', ['line', ...], octets].
        """
        return self._longcmd('TOP %s %s' % (which, howmuch))


    def uidl(self, which=None):
        """Return message digest (unique id) list.

        If 'which', result contains unique id for that message
        in the form 'response mesgnum uid', otherwise result is
        the list ['response', ['mesgnum uid', ...], octets]
        """
346
        if which is not None:
Tim Peters's avatar
Tim Peters committed
347 348
            return self._shortcmd('UIDL %s' % which)
        return self._longcmd('UIDL')
349

350 351 352 353 354 355 356 357 358 359 360 361

    def capa(self):
        """Return server capabilities (RFC 2449) as a dictionary
        >>> c=poplib.POP3('localhost')
        >>> c.capa()
        {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],
         'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],
         'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],
         'UIDL': [], 'RESP-CODES': []}
        >>>

        Really, according to RFC 2449, the cyrus folks should avoid
Ezio Melotti's avatar
Ezio Melotti committed
362
        having the implementation split into multiple arguments...
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
        """
        def _parsecap(line):
            lst = line.decode('ascii').split()
            return lst[0], lst[1:]

        caps = {}
        try:
            resp = self._longcmd('CAPA')
            rawcaps = resp[1]
            for capline in rawcaps:
                capnm, capargs = _parsecap(capline)
                caps[capnm] = capargs
        except error_proto as _err:
            raise error_proto('-ERR CAPA not supported by server')
        return caps

379 380 381 382 383 384 385 386 387 388 389 390 391 392

    def stls(self, context=None):
        """Start a TLS session on the active connection as specified in RFC 2595.

                context - a ssl.SSLContext
        """
        if not HAVE_SSL:
            raise error_proto('-ERR TLS support missing')
        if self._tls_established:
            raise error_proto('-ERR TLS session already established')
        caps = self.capa()
        if not 'STLS' in caps:
            raise error_proto('-ERR STLS not supported by server')
        if context is None:
393
            context = ssl._create_stdlib_context()
394
        resp = self._shortcmd('STLS')
395
        self.sock = context.wrap_socket(self.sock,
396
                                        server_hostname=self.host)
397 398 399 400 401 402
        self.file = self.sock.makefile('rb')
        self._tls_established = True
        return resp


if HAVE_SSL:
403

404 405
    class POP3_SSL(POP3):
        """POP3 client class over SSL connection
406

407 408
        Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None,
                                   context=None)
409

410 411
               hostname - the hostname of the pop3 over ssl server
               port - port number
412
               keyfile - PEM formatted file that contains your private key
413
               certfile - PEM formatted certificate chain file
414
               context - a ssl.SSLContext
415

416
        See the methods of the parent class POP3 for more documentation.
417
        """
418

419 420 421 422 423 424 425 426
        def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None,
                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
            if context is not None and keyfile is not None:
                raise ValueError("context and keyfile arguments are mutually "
                                 "exclusive")
            if context is not None and certfile is not None:
                raise ValueError("context and certfile arguments are mutually "
                                 "exclusive")
427 428
            self.keyfile = keyfile
            self.certfile = certfile
429 430 431
            if context is None:
                context = ssl._create_stdlib_context(certfile=certfile,
                                                     keyfile=keyfile)
432
            self.context = context
433 434 435 436
            POP3.__init__(self, host, port, timeout)

        def _create_socket(self, timeout):
            sock = POP3._create_socket(self, timeout)
437
            sock = self.context.wrap_socket(sock,
438
                                            server_hostname=self.host)
439
            return sock
440

441 442 443 444 445 446 447
        def stls(self, keyfile=None, certfile=None, context=None):
            """The method unconditionally raises an exception since the
            STLS command doesn't make any sense on an already established
            SSL/TLS session.
            """
            raise error_proto('-ERR TLS session already established')

448
    __all__.append("POP3_SSL")
449

450
if __name__ == "__main__":
451 452
    import sys
    a = POP3(sys.argv[1])
453
    print(a.getwelcome())
454 455
    a.user(sys.argv[2])
    a.pass_(sys.argv[3])
Tim Peters's avatar
Tim Peters committed
456 457 458 459
    a.list()
    (numMsgs, totalSize) = a.stat()
    for i in range(1, numMsgs + 1):
        (header, msg, octets) = a.retr(i)
460
        print("Message %d:" % i)
Tim Peters's avatar
Tim Peters committed
461
        for line in msg:
462 463
            print('   ' + line)
        print('-----------------------')
Tim Peters's avatar
Tim Peters committed
464
    a.quit()