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