a pop3 client class based on the j myers pop3 draft jan 96 david ascher davidascherbrown edu heavily stealing from nntplib py updated piers lauder pierscs su oz au jul 97 string method conversion and test jig improvements by esr february 2001 added the pop3ssl class methods loosely based on imapssl hector urtubia urtubiamrbook org aug 2003 example see the test function at the end of this file imports exception raised when an error or invalid response is received standard port pop ssl port line terminators we always output crlf but accept any of crlf lfcr lf maximal line length when calling readline this is to prevent reading arbitrary length lines rfc 1939 limits pop3 line length to 512 characters including crlf we have selected 2048 just to be on the safe side this class supports both the minimal and optional command sets arguments can be strings or integers where appropriate e g retr1 and retr 1 both work equally well minimal command set user name username pass string passstring stat stat list msg listmsg none retr msg retrmsg dele msg delemsg noop noop rset rset quit quit optional commands some servers support these rpop name rpopname apop name digest apopname digest top msg n topmsg n uidl msg uidlmsg none capa capa stls stls utf8 utf8 raises one exception errorproto instantiate with pop3hostname port110 nb the pop protocol locks the mailbox from user ization until quit so be sure to get in suck the messages and quit each time you access the mailbox pop is a linebased protocol which means large mail messages consume lots of python cycles reading them linebyline if it s available on your mail server use imap4 instead it doesn t suffer from the two problems above internal send one command to the server through putline internal return one line from the server stripping crlf this is where all the cpu time of this module is consumed raise errorproto err eof if the connection is closed server can send any combination of cr lf however readline returns lines ending in lf so only possibilities are lf crlf cr lf internal get a response from the server raise errorproto if the response doesn t start with internal get a response plus following text from the server internal send a command and get the response internal send a command and get the response plus following text these can be useful here are all the pop commands send user name return response should indicate password required send password return response response includes message count mailbox size nb mailbox is locked by server from here to quit get mailbox status result is tuple of 2 ints message count mailbox size request listing return result result without a message number argument is in form response mesgnum octets octets result when a message number argument is given is a single response the scan listing for that message retrieve whole message number which result is in form response line octets delete message number which result is response does nothing one supposes the response indicates the server is alive unmark all messages marked for deletion return self shortcmd rset def quitself close the connection without assuming anything about it 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 shutdownsocket shutrdwr except oserror as exc the server might already have closed the connection on windows this may result in wsaeinval error 10022 an invalid operation was attempted if exc errno errno enotconn and getattrexc winerror 0 10022 raise finally sock close del quit optional commands def rpopself user isation only possible if server has supplied a timestamp in initial greeting args user mailbox user password mailbox password nb mailbox is locked by server from here to quit retrieve message header of message number which and first howmuch lines of message body result is in form response line octets 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 try to enter utf8 mode see rfc 6856 returns server response return server capabilities rfc 2449 as a dictionary cpoplib pop3 localhost c capa implementation cyrus pop3 server v2 2 12 top logindelay 0 authrespcode expire never user stls pipelining uidl respcodes really according to rfc 2449 the cyrus folks should avoid having the implementation split into multiple arguments start a tls session on the active connection as specified in rfc 2595 context a ssl sslcontext pop3 client class over ssl connection instantiate with pop3sslhostname port995 contextnone hostname the hostname of the pop3 over ssl server port port number context a ssl sslcontext see the methods of the parent class pop3 for more documentation the method unconditionally raises an exception since the stls command doesn t make any sense on an already established ssltls session david ascher david_ascher brown edu heavily stealing from nntplib py updated piers lauder piers cs su oz au jul 97 string method conversion and test jig improvements by esr february 2001 added the pop3_ssl class methods loosely based on imap_ssl hector urtubia urtubia mrbook org aug 2003 example see the test function at the end of this file imports exception raised when an error or invalid response is received standard port pop ssl port line terminators we always output crlf but accept any of crlf lfcr lf maximal line length when calling readline this is to prevent reading arbitrary length lines rfc 1939 limits pop3 line length to 512 characters including crlf we have selected 2048 just to be on the safe side 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 capa capa stls stls utf8 utf8 raises one exception error_proto instantiate with pop3 hostname port 110 nb the pop protocol locks the mailbox from user ization until quit so be sure to get in suck the messages and quit each time you access the mailbox pop is a line based protocol which means large mail messages consume lots of python cycles reading them line by line if it s available on your mail server use imap4 instead it doesn t suffer from the two problems above internal send one command to the server through _putline 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 server can send any combination of cr lf however readline returns lines ending in lf so only possibilities are lf crlf cr lf internal get a response from the server raise error_proto if the response doesn t start with internal get a response plus following text from the server internal send a command and get the response internal send a command and get the response plus following text these can be useful here are all the pop commands send user name return response should indicate password required send password return response response includes message count mailbox size nb mailbox is locked by server from here to quit get mailbox status result is tuple of 2 ints message count mailbox size request listing return result result without a message number argument is in form response mesg_num octets octets result when a message number argument is given is a single response the scan listing for that message retrieve whole message number which result is in form response line octets delete message number which result is response does nothing one supposes the response indicates the server is alive unmark all messages marked for deletion signoff commit changes on server unlock mailbox close connection close the connection without assuming anything about it the server might already have closed the connection on windows this may result in wsaeinval error 10022 an invalid operation was attempted __del__ quit optional commands not sure what this does isation only possible if server has supplied a timestamp in initial greeting args user mailbox user password mailbox password nb mailbox is locked by server from here to quit retrieve message header of message number which and first howmuch lines of message body result is in form response line octets 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 try to enter utf 8 mode see rfc 6856 returns server response 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 having the implementation split into multiple arguments start a tls session on the active connection as specified in rfc 2595 context a ssl sslcontext pop3 client class over ssl connection instantiate with pop3_ssl hostname port 995 context none hostname the hostname of the pop3 over ssl server port port number context a ssl sslcontext see the methods of the parent class pop3 for more documentation the method unconditionally raises an exception since the stls command doesn t make any sense on an already established ssl tls session
import errno
import re
import socket
import sys

try:
    import ssl
    HAVE_SSL = True
except ImportError:
    HAVE_SSL = False

__all__ = ["POP3","error_proto"]



class error_proto(Exception): pass


POP3_PORT = 110


POP3_SSL_PORT = 995


CR = b'\r'
LF = b'\n'
CRLF = CR+LF





_MAXLINE = 2048


class POP3:

    

    encoding = 'UTF-8'

    def __init__(self, host, port=POP3_PORT,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
        self.host = host
        self.port = port
        self._tls_established = False
        sys.audit("poplib.connect", self, host, port)
        self.sock = self._create_socket(timeout)
        self.file = self.sock.makefile('rb')
        self._debugging = 0
        self.welcome = self._getresp()

    def _create_socket(self, timeout):
        if timeout is not None and not timeout:
            raise ValueError('Non-blocking socket (timeout=0) is not supported')
        return socket.create_connection((self.host, self.port), timeout)

    def _putline(self, line):
        if self._debugging > 1: print('*put*', repr(line))
        sys.audit("poplib.putline", self, line)
        self.sock.sendall(line + CRLF)


    

    def _putcmd(self, line):
        if self._debugging: print('*cmd*', repr(line))
        line = bytes(line, self.encoding)
        self._putline(line)


    
    
    

    def _getline(self):
        line = self.file.readline(_MAXLINE + 1)
        if len(line) > _MAXLINE:
            raise error_proto('line too long')

        if self._debugging > 1: print('*get*', repr(line))
        if not line: raise error_proto('-ERR EOF')
        octets = len(line)
        
        
        
        if line[-2:] == CRLF:
            return line[:-2], octets
        if line[:1] == CR:
            return line[1:-1], octets
        return line[:-1], octets


    
    

    def _getresp(self):
        resp, o = self._getline()
        if self._debugging > 1: print('*resp*', repr(resp))
        if not resp.startswith(b'+'):
            raise error_proto(resp)
        return resp


    

    def _getlongresp(self):
        resp = self._getresp()
        list = []; octets = 0
        line, o = self._getline()
        while line != b'.':
            if line.startswith(b'..'):
                o = o-1
                line = line[1:]
            octets = octets + o
            list.append(line)
            line, o = self._getline()
        return resp, list, octets


    

    def _shortcmd(self, line):
        self._putcmd(line)
        return self._getresp()


    

    def _longcmd(self, line):
        self._putcmd(line)
        return self._getlongresp()


    

    def getwelcome(self):
        return self.welcome


    def set_debuglevel(self, level):
        self._debugging = level


    

    def user(self, user):
        
        return self._shortcmd('USER %s' % user)


    def pass_(self, pswd):
        
        return self._shortcmd('PASS %s' % pswd)


    def stat(self):
        
        retval = self._shortcmd('STAT')
        rets = retval.split()
        if self._debugging: print('*stat*', repr(rets))
        numMessages = int(rets[1])
        sizeMessages = int(rets[2])
        return (numMessages, sizeMessages)


    def list(self, which=None):
        
        if which is not None:
            return self._shortcmd('LIST %s' % which)
        return self._longcmd('LIST')


    def retr(self, which):
        
        return self._longcmd('RETR %s' % which)


    def dele(self, which):
        
        return self._shortcmd('DELE %s' % which)


    def noop(self):
        
        return self._shortcmd('NOOP')


    def rset(self):
        
        return self._shortcmd('RSET')


    def quit(self):
        
        resp = self._shortcmd('QUIT')
        self.close()
        return resp

    def close(self):
        
        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 exc:
                    
                    
                    
                    if (exc.errno != errno.ENOTCONN
                       and getattr(exc, 'winerror', 0) != 10022):
                        raise
                finally:
                    sock.close()

    


    

    def rpop(self, user):
        
        return self._shortcmd('RPOP %s' % user)


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

    def apop(self, user, password):
        
        secret = bytes(password, self.encoding)
        m = self.timestamp.match(self.welcome)
        if not m:
            raise error_proto('-ERR APOP not supported by server')
        import hashlib
        digest = m.group(1)+secret
        digest = hashlib.md5(digest).hexdigest()
        return self._shortcmd('APOP %s %s' % (user, digest))


    def top(self, which, howmuch):
        
        return self._longcmd('TOP %s %s' % (which, howmuch))


    def uidl(self, which=None):
        
        if which is not None:
            return self._shortcmd('UIDL %s' % which)
        return self._longcmd('UIDL')


    def utf8(self):
        
        return self._shortcmd('UTF8')


    def capa(self):
        
        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:
            raise error_proto('-ERR CAPA not supported by server')
        return caps


    def stls(self, context=None):
        
        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:
            context = ssl._create_stdlib_context()
        resp = self._shortcmd('STLS')
        self.sock = context.wrap_socket(self.sock,
                                        server_hostname=self.host)
        self.file = self.sock.makefile('rb')
        self._tls_established = True
        return resp


if HAVE_SSL:

    class POP3_SSL(POP3):
        

        def __init__(self, host, port=POP3_SSL_PORT,
                     *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
            if context is None:
                context = ssl._create_stdlib_context()
            self.context = context
            POP3.__init__(self, host, port, timeout)

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

        def stls(self, context=None):
            
            raise error_proto('-ERR TLS session already established')

    __all__.append("POP3_SSL")

if __name__ == "__main__":
    a = POP3(sys.argv[1])
    print(a.getwelcome())
    a.user(sys.argv[2])
    a.pass_(sys.argv[3])
    a.list()
    (numMsgs, totalSize) = a.stat()
    for i in range(1, numMsgs + 1):
        (header, msg, octets) = a.retr(i)
        print("Message %d:" % i)
        for line in msg:
            print('   ' + line)
        print('-----------------------')
    a.quit()