14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""A POP3 client class.
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmBased on the J. Myers POP3 draft, Jan. 96
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Author: David Ascher <david_ascher@brown.edu>
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm#         [heavily stealing from nntplib.py]
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# String method conversion and test jig improvements by ESR, February 2001.
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Example (see the test function at the end of this file)
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Imports
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport re, socket
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm__all__ = ["POP3","error_proto"]
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Exception raised when an error or invalid response is received:
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass error_proto(Exception): pass
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Standard Port
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmPOP3_PORT = 110
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# POP SSL PORT
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmPOP3_SSL_PORT = 995
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmCR = '\r'
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLF = '\n'
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmCRLF = CR+LF
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass POP3:
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """This class supports both the minimal and optional command sets.
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Arguments can be strings or integers (where appropriate)
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    (e.g.: retr(1) and retr('1') both work equally well.
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Minimal Command Set:
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            USER name               user(name)
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            PASS string             pass_(string)
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            STAT                    stat()
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            LIST [msg]              list(msg = None)
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            RETR msg                retr(msg)
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            DELE msg                dele(msg)
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            NOOP                    noop()
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            RSET                    rset()
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            QUIT                    quit()
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Optional Commands (some servers support these):
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            RPOP name               rpop(name)
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            APOP name digest        apop(name, digest)
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            TOP msg n               top(msg, n)
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            UIDL [msg]              uidl(msg = None)
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Raises one exception: 'error_proto'.
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Instantiate with:
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            POP3(hostname, port=110)
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    NB:     the POP protocol locks the mailbox from user
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            authorization until QUIT, so be sure to get in, suck
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            the messages, and quit, each time you access the
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            mailbox.
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            POP is a line-based protocol, which means large mail
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            messages consume lots of python cycles reading them
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line-by-line.
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            If it's available on your mail server, use IMAP4
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            instead, it doesn't suffer from the two problems
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            above.
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, host, port=POP3_PORT,
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.host = host
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.port = port
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.sock = socket.create_connection((host, port), timeout)
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.file = self.sock.makefile('rb')
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._debugging = 0
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.welcome = self._getresp()
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _putline(self, line):
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self._debugging > 1: print '*put*', repr(line)
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.sock.sendall('%s%s' % (line, CRLF))
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: send one command to the server (through _putline())
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _putcmd(self, line):
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self._debugging: print '*cmd*', repr(line)
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._putline(line)
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: return one line from the server, stripping CRLF.
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # This is where all the CPU time of this module is consumed.
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Raise error_proto('-ERR EOF') if the connection is closed.
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _getline(self):
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        line = self.file.readline()
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self._debugging > 1: print '*get*', repr(line)
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not line: raise error_proto('-ERR EOF')
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        octets = len(line)
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # server can send any combination of CR & LF
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # however, 'readline()' returns lines ending in LF
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # so only possibilities are ...LF, ...CRLF, CR...LF
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line[-2:] == CRLF:
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return line[:-2], octets
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line[0] == CR:
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return line[1:-1], octets
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return line[:-1], octets
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: get a response from the server.
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Raise 'error_proto' if the response doesn't start with '+'.
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _getresp(self):
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        resp, o = self._getline()
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self._debugging > 1: print '*resp*', repr(resp)
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        c = resp[:1]
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if c != '+':
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            raise error_proto(resp)
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return resp
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: get a response plus following text from the server.
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _getlongresp(self):
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        resp = self._getresp()
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        list = []; octets = 0
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        line, o = self._getline()
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while line != '.':
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if line[:2] == '..':
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                o = o-1
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                line = line[1:]
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            octets = octets + o
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            list.append(line)
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line, o = self._getline()
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return resp, list, octets
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: send a command and get the response
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _shortcmd(self, line):
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._putcmd(line)
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._getresp()
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Internal: send a command and get the response plus following text
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def _longcmd(self, line):
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._putcmd(line)
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._getlongresp()
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # These can be useful:
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getwelcome(self):
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self.welcome
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def set_debuglevel(self, level):
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self._debugging = level
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Here are all the POP commands:
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def user(self, user):
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Send user name, return response
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        (should indicate password required).
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('USER %s' % user)
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def pass_(self, pswd):
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Send password, return response
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        (response includes message count, mailbox size).
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        NB: mailbox is locked by server from here to 'quit()'
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('PASS %s' % pswd)
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def stat(self):
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Get mailbox status.
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result is tuple of 2 ints (message count, mailbox size)
1964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
1974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        retval = self._shortcmd('STAT')
1984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        rets = retval.split()
1994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self._debugging: print '*stat*', repr(rets)
2004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        numMessages = int(rets[1])
2014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sizeMessages = int(rets[2])
2024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return (numMessages, sizeMessages)
2034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def list(self, which=None):
2064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Request listing, return result.
2074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result without a message number argument is in form
2094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        ['response', ['mesg_num octets', ...], octets].
2104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result when a message number argument is given is a
2124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        single response: the "scan listing" for that message.
2134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if which is not None:
2154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self._shortcmd('LIST %s' % which)
2164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._longcmd('LIST')
2174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def retr(self, which):
2204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Retrieve whole message number 'which'.
2214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result is in form ['response', ['line', ...], octets].
2234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._longcmd('RETR %s' % which)
2254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def dele(self, which):
2284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Delete message number 'which'.
2294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result is 'response'.
2314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('DELE %s' % which)
2334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def noop(self):
2364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Does nothing.
2374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        One supposes the response indicates the server is alive.
2394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('NOOP')
2414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def rset(self):
2444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Unmark all messages marked for deletion."""
2454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('RSET')
2464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def quit(self):
2494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Signoff: commit changes on server, unlock mailbox, close connection."""
2504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
2514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            resp = self._shortcmd('QUIT')
2524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except error_proto, val:
2534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            resp = val
2544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.file.close()
2554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.sock.close()
2564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        del self.file, self.sock
2574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return resp
2584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    #__del__ = quit
2604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # optional commands:
2634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def rpop(self, user):
2654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Not sure what this does."""
2664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('RPOP %s' % user)
2674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    timestamp = re.compile(r'\+OK.*(<[^>]+>)')
2704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def apop(self, user, secret):
2724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Authorisation
2734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        - only possible if server has supplied a timestamp in initial greeting.
2754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Args:
2774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                user    - mailbox user;
2784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                secret  - secret shared between client and server.
2794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        NB: mailbox is locked by server from here to 'quit()'
2814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        m = self.timestamp.match(self.welcome)
2834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not m:
2844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            raise error_proto('-ERR APOP not supported by server')
2854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        import hashlib
2864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        digest = hashlib.md5(m.group(1)+secret).digest()
2874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
2884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._shortcmd('APOP %s %s' % (user, digest))
2894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def top(self, which, howmuch):
2924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Retrieve message header of message number 'which'
2934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        and first 'howmuch' lines of message body.
2944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Result is in form ['response', ['line', ...], octets].
2964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._longcmd('TOP %s %s' % (which, howmuch))
2984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def uidl(self, which=None):
3014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Return message digest (unique id) list.
3024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        If 'which', result contains unique id for that message
3044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        in the form 'response mesgnum uid', otherwise result is
3054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        the list ['response', ['mesgnum uid', ...], octets]
3064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
3074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if which is not None:
3084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return self._shortcmd('UIDL %s' % which)
3094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self._longcmd('UIDL')
3104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtry:
3124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import ssl
3134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmexcept ImportError:
3144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    pass
3154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmelse:
3164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    class POP3_SSL(POP3):
3184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """POP3 client class over SSL connection
3194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
3214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               hostname - the hostname of the pop3 over ssl server
3234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               port - port number
3244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               keyfile - PEM formatted file that countains your private key
3254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               certfile - PEM formatted certificate chain file
3264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            See the methods of the parent class POP3 for more documentation.
3284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
3294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None):
3314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.host = host
3324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.port = port
3334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.keyfile = keyfile
3344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.certfile = certfile
3354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.buffer = ""
3364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            msg = "getaddrinfo returns an empty list"
3374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.sock = None
3384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
3394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                af, socktype, proto, canonname, sa = res
3404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                try:
3414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    self.sock = socket.socket(af, socktype, proto)
3424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    self.sock.connect(sa)
3434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                except socket.error, msg:
3444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    if self.sock:
3454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                        self.sock.close()
3464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    self.sock = None
3474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    continue
3484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not self.sock:
3504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                raise socket.error, msg
3514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.file = self.sock.makefile('rb')
3524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
3534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self._debugging = 0
3544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.welcome = self._getresp()
3554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def _fillBuffer(self):
3574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            localbuf = self.sslobj.read()
3584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if len(localbuf) == 0:
3594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                raise error_proto('-ERR EOF')
3604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.buffer += localbuf
3614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def _getline(self):
3634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = ""
3644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            renewline = re.compile(r'.*?\n')
3654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            match = renewline.match(self.buffer)
3664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while not match:
3674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self._fillBuffer()
3684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                match = renewline.match(self.buffer)
3694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = match.group(0)
3704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.buffer = renewline.sub('' ,self.buffer, 1)
3714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self._debugging > 1: print '*get*', repr(line)
3724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            octets = len(line)
3744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if line[-2:] == CRLF:
3754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return line[:-2], octets
3764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if line[0] == CR:
3774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return line[1:-1], octets
3784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return line[:-1], octets
3794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def _putline(self, line):
3814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self._debugging > 1: print '*put*', repr(line)
3824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line += CRLF
3834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            bytes = len(line)
3844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while bytes > 0:
3854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                sent = self.sslobj.write(line)
3864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                if sent == bytes:
3874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    break    # avoid copy
3884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                line = line[sent:]
3894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                bytes = bytes - sent
3904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        def quit(self):
3924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            """Signoff: commit changes on server, unlock mailbox, close connection."""
3934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
3944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                resp = self._shortcmd('QUIT')
3954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except error_proto, val:
3964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                resp = val
3974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.sock.close()
3984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            del self.sslobj, self.sock
3994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return resp
4004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    __all__.append("POP3_SSL")
4024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmif __name__ == "__main__":
4044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    import sys
4054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    a = POP3(sys.argv[1])
4064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    print a.getwelcome()
4074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    a.user(sys.argv[2])
4084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    a.pass_(sys.argv[3])
4094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    a.list()
4104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    (numMsgs, totalSize) = a.stat()
4114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    for i in range(1, numMsgs + 1):
4124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        (header, msg, octets) = a.retr(i)
4134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print "Message %d:" % i
4144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for line in msg:
4154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '   ' + line
4164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print '-----------------------'
4174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    a.quit()
418