+++ /dev/null
-"""A POP3 client class.\r
-\r
-Based on the J. Myers POP3 draft, Jan. 96\r
-"""\r
-\r
-# Author: David Ascher <david_ascher@brown.edu>\r
-# [heavily stealing from nntplib.py]\r
-# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]\r
-# String method conversion and test jig improvements by ESR, February 2001.\r
-# Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003\r
-\r
-# Example (see the test function at the end of this file)\r
-\r
-# Imports\r
-\r
-import re, socket\r
-\r
-__all__ = ["POP3","error_proto"]\r
-\r
-# Exception raised when an error or invalid response is received:\r
-\r
-class error_proto(Exception): pass\r
-\r
-# Standard Port\r
-POP3_PORT = 110\r
-\r
-# POP SSL PORT\r
-POP3_SSL_PORT = 995\r
-\r
-# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)\r
-CR = '\r'\r
-LF = '\n'\r
-CRLF = CR+LF\r
-\r
-\r
-class POP3:\r
-\r
- """This class supports both the minimal and optional command sets.\r
- Arguments can be strings or integers (where appropriate)\r
- (e.g.: retr(1) and retr('1') both work equally well.\r
-\r
- Minimal Command Set:\r
- USER name user(name)\r
- PASS string pass_(string)\r
- STAT stat()\r
- LIST [msg] list(msg = None)\r
- RETR msg retr(msg)\r
- DELE msg dele(msg)\r
- NOOP noop()\r
- RSET rset()\r
- QUIT quit()\r
-\r
- Optional Commands (some servers support these):\r
- RPOP name rpop(name)\r
- APOP name digest apop(name, digest)\r
- TOP msg n top(msg, n)\r
- UIDL [msg] uidl(msg = None)\r
-\r
- Raises one exception: 'error_proto'.\r
-\r
- Instantiate with:\r
- POP3(hostname, port=110)\r
-\r
- NB: the POP protocol locks the mailbox from user\r
- authorization until QUIT, so be sure to get in, suck\r
- the messages, and quit, each time you access the\r
- mailbox.\r
-\r
- POP is a line-based protocol, which means large mail\r
- messages consume lots of python cycles reading them\r
- line-by-line.\r
-\r
- If it's available on your mail server, use IMAP4\r
- instead, it doesn't suffer from the two problems\r
- above.\r
- """\r
-\r
-\r
- def __init__(self, host, port=POP3_PORT,\r
- timeout=socket._GLOBAL_DEFAULT_TIMEOUT):\r
- self.host = host\r
- self.port = port\r
- self.sock = socket.create_connection((host, port), timeout)\r
- self.file = self.sock.makefile('rb')\r
- self._debugging = 0\r
- self.welcome = self._getresp()\r
-\r
-\r
- def _putline(self, line):\r
- if self._debugging > 1: print '*put*', repr(line)\r
- self.sock.sendall('%s%s' % (line, CRLF))\r
-\r
-\r
- # Internal: send one command to the server (through _putline())\r
-\r
- def _putcmd(self, line):\r
- if self._debugging: print '*cmd*', repr(line)\r
- self._putline(line)\r
-\r
-\r
- # Internal: return one line from the server, stripping CRLF.\r
- # This is where all the CPU time of this module is consumed.\r
- # Raise error_proto('-ERR EOF') if the connection is closed.\r
-\r
- def _getline(self):\r
- line = self.file.readline()\r
- if self._debugging > 1: print '*get*', repr(line)\r
- if not line: raise error_proto('-ERR EOF')\r
- octets = len(line)\r
- # server can send any combination of CR & LF\r
- # however, 'readline()' returns lines ending in LF\r
- # so only possibilities are ...LF, ...CRLF, CR...LF\r
- if line[-2:] == CRLF:\r
- return line[:-2], octets\r
- if line[0] == CR:\r
- return line[1:-1], octets\r
- return line[:-1], octets\r
-\r
-\r
- # Internal: get a response from the server.\r
- # Raise 'error_proto' if the response doesn't start with '+'.\r
-\r
- def _getresp(self):\r
- resp, o = self._getline()\r
- if self._debugging > 1: print '*resp*', repr(resp)\r
- c = resp[:1]\r
- if c != '+':\r
- raise error_proto(resp)\r
- return resp\r
-\r
-\r
- # Internal: get a response plus following text from the server.\r
-\r
- def _getlongresp(self):\r
- resp = self._getresp()\r
- list = []; octets = 0\r
- line, o = self._getline()\r
- while line != '.':\r
- if line[:2] == '..':\r
- o = o-1\r
- line = line[1:]\r
- octets = octets + o\r
- list.append(line)\r
- line, o = self._getline()\r
- return resp, list, octets\r
-\r
-\r
- # Internal: send a command and get the response\r
-\r
- def _shortcmd(self, line):\r
- self._putcmd(line)\r
- return self._getresp()\r
-\r
-\r
- # Internal: send a command and get the response plus following text\r
-\r
- def _longcmd(self, line):\r
- self._putcmd(line)\r
- return self._getlongresp()\r
-\r
-\r
- # These can be useful:\r
-\r
- def getwelcome(self):\r
- return self.welcome\r
-\r
-\r
- def set_debuglevel(self, level):\r
- self._debugging = level\r
-\r
-\r
- # Here are all the POP commands:\r
-\r
- def user(self, user):\r
- """Send user name, return response\r
-\r
- (should indicate password required).\r
- """\r
- return self._shortcmd('USER %s' % user)\r
-\r
-\r
- def pass_(self, pswd):\r
- """Send password, return response\r
-\r
- (response includes message count, mailbox size).\r
-\r
- NB: mailbox is locked by server from here to 'quit()'\r
- """\r
- return self._shortcmd('PASS %s' % pswd)\r
-\r
-\r
- def stat(self):\r
- """Get mailbox status.\r
-\r
- Result is tuple of 2 ints (message count, mailbox size)\r
- """\r
- retval = self._shortcmd('STAT')\r
- rets = retval.split()\r
- if self._debugging: print '*stat*', repr(rets)\r
- numMessages = int(rets[1])\r
- sizeMessages = int(rets[2])\r
- return (numMessages, sizeMessages)\r
-\r
-\r
- def list(self, which=None):\r
- """Request listing, return result.\r
-\r
- Result without a message number argument is in form\r
- ['response', ['mesg_num octets', ...], octets].\r
-\r
- Result when a message number argument is given is a\r
- single response: the "scan listing" for that message.\r
- """\r
- if which is not None:\r
- return self._shortcmd('LIST %s' % which)\r
- return self._longcmd('LIST')\r
-\r
-\r
- def retr(self, which):\r
- """Retrieve whole message number 'which'.\r
-\r
- Result is in form ['response', ['line', ...], octets].\r
- """\r
- return self._longcmd('RETR %s' % which)\r
-\r
-\r
- def dele(self, which):\r
- """Delete message number 'which'.\r
-\r
- Result is 'response'.\r
- """\r
- return self._shortcmd('DELE %s' % which)\r
-\r
-\r
- def noop(self):\r
- """Does nothing.\r
-\r
- One supposes the response indicates the server is alive.\r
- """\r
- return self._shortcmd('NOOP')\r
-\r
-\r
- def rset(self):\r
- """Unmark all messages marked for deletion."""\r
- return self._shortcmd('RSET')\r
-\r
-\r
- def quit(self):\r
- """Signoff: commit changes on server, unlock mailbox, close connection."""\r
- try:\r
- resp = self._shortcmd('QUIT')\r
- except error_proto, val:\r
- resp = val\r
- self.file.close()\r
- self.sock.close()\r
- del self.file, self.sock\r
- return resp\r
-\r
- #__del__ = quit\r
-\r
-\r
- # optional commands:\r
-\r
- def rpop(self, user):\r
- """Not sure what this does."""\r
- return self._shortcmd('RPOP %s' % user)\r
-\r
-\r
- timestamp = re.compile(r'\+OK.*(<[^>]+>)')\r
-\r
- def apop(self, user, secret):\r
- """Authorisation\r
-\r
- - only possible if server has supplied a timestamp in initial greeting.\r
-\r
- Args:\r
- user - mailbox user;\r
- secret - secret shared between client and server.\r
-\r
- NB: mailbox is locked by server from here to 'quit()'\r
- """\r
- m = self.timestamp.match(self.welcome)\r
- if not m:\r
- raise error_proto('-ERR APOP not supported by server')\r
- import hashlib\r
- digest = hashlib.md5(m.group(1)+secret).digest()\r
- digest = ''.join(map(lambda x:'%02x'%ord(x), digest))\r
- return self._shortcmd('APOP %s %s' % (user, digest))\r
-\r
-\r
- def top(self, which, howmuch):\r
- """Retrieve message header of message number 'which'\r
- and first 'howmuch' lines of message body.\r
-\r
- Result is in form ['response', ['line', ...], octets].\r
- """\r
- return self._longcmd('TOP %s %s' % (which, howmuch))\r
-\r
-\r
- def uidl(self, which=None):\r
- """Return message digest (unique id) list.\r
-\r
- If 'which', result contains unique id for that message\r
- in the form 'response mesgnum uid', otherwise result is\r
- the list ['response', ['mesgnum uid', ...], octets]\r
- """\r
- if which is not None:\r
- return self._shortcmd('UIDL %s' % which)\r
- return self._longcmd('UIDL')\r
-\r
-try:\r
- import ssl\r
-except ImportError:\r
- pass\r
-else:\r
-\r
- class POP3_SSL(POP3):\r
- """POP3 client class over SSL connection\r
-\r
- Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)\r
-\r
- hostname - the hostname of the pop3 over ssl server\r
- port - port number\r
- keyfile - PEM formatted file that countains your private key\r
- certfile - PEM formatted certificate chain file\r
-\r
- See the methods of the parent class POP3 for more documentation.\r
- """\r
-\r
- def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None):\r
- self.host = host\r
- self.port = port\r
- self.keyfile = keyfile\r
- self.certfile = certfile\r
- self.buffer = ""\r
- msg = "getaddrinfo returns an empty list"\r
- self.sock = None\r
- for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):\r
- af, socktype, proto, canonname, sa = res\r
- try:\r
- self.sock = socket.socket(af, socktype, proto)\r
- self.sock.connect(sa)\r
- except socket.error, msg:\r
- if self.sock:\r
- self.sock.close()\r
- self.sock = None\r
- continue\r
- break\r
- if not self.sock:\r
- raise socket.error, msg\r
- self.file = self.sock.makefile('rb')\r
- self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)\r
- self._debugging = 0\r
- self.welcome = self._getresp()\r
-\r
- def _fillBuffer(self):\r
- localbuf = self.sslobj.read()\r
- if len(localbuf) == 0:\r
- raise error_proto('-ERR EOF')\r
- self.buffer += localbuf\r
-\r
- def _getline(self):\r
- line = ""\r
- renewline = re.compile(r'.*?\n')\r
- match = renewline.match(self.buffer)\r
- while not match:\r
- self._fillBuffer()\r
- match = renewline.match(self.buffer)\r
- line = match.group(0)\r
- self.buffer = renewline.sub('' ,self.buffer, 1)\r
- if self._debugging > 1: print '*get*', repr(line)\r
-\r
- octets = len(line)\r
- if line[-2:] == CRLF:\r
- return line[:-2], octets\r
- if line[0] == CR:\r
- return line[1:-1], octets\r
- return line[:-1], octets\r
-\r
- def _putline(self, line):\r
- if self._debugging > 1: print '*put*', repr(line)\r
- line += CRLF\r
- bytes = len(line)\r
- while bytes > 0:\r
- sent = self.sslobj.write(line)\r
- if sent == bytes:\r
- break # avoid copy\r
- line = line[sent:]\r
- bytes = bytes - sent\r
-\r
- def quit(self):\r
- """Signoff: commit changes on server, unlock mailbox, close connection."""\r
- try:\r
- resp = self._shortcmd('QUIT')\r
- except error_proto, val:\r
- resp = val\r
- self.sock.close()\r
- del self.sslobj, self.sock\r
- return resp\r
-\r
- __all__.append("POP3_SSL")\r
-\r
-if __name__ == "__main__":\r
- import sys\r
- a = POP3(sys.argv[1])\r
- print a.getwelcome()\r
- a.user(sys.argv[2])\r
- a.pass_(sys.argv[3])\r
- a.list()\r
- (numMsgs, totalSize) = a.stat()\r
- for i in range(1, numMsgs + 1):\r
- (header, msg, octets) = a.retr(i)\r
- print "Message %d:" % i\r
- for line in msg:\r
- print ' ' + line\r
- print '-----------------------'\r
- a.quit()\r