[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/POP3 - IPOP3Message.py:1.1.2.1 IPOP3MessageList.py:1.1.2.1 POP3Message.py:1.1.2.1 POP3MessageList.py:1.1.2.1 IPOP3CommandHandler.py:1.1.2.2 POP3Server.py:1.1.2.2 POP3ServerChannel.py:1.1.2.2 POP3StatusMessages.py:1.1.2.2

Stephan Richter srichter@cbu.edu
Sun, 7 Apr 2002 17:39:18 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Server/POP3
In directory cvs.zope.org:/tmp/cvs-serv25992/POP3

Modified Files:
      Tag: Zope3-Server-Branch
	IPOP3CommandHandler.py POP3Server.py POP3ServerChannel.py 
	POP3StatusMessages.py 
Added Files:
      Tag: Zope3-Server-Branch
	IPOP3Message.py IPOP3MessageList.py POP3Message.py 
	POP3MessageList.py 
Log Message:
I just finished the POP3 server implmentation. Right now it does only work
with Unix-like mailboxes, but is not limited to run on Unix I believe. It
will not make much sense to write the Zope connection until we have the 
SMTP implemented and a MailMessageService has been written. 

Please try the code and let me know what could be improved. BTW, I tested
the server with Eudora and it worked great, which included teh APOP authen-
tication mechanism!


=== Added File Zope3/lib/python/Zope/Server/POP3/IPOP3Message.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""

$Id: IPOP3Message.py,v 1.1.2.1 2002/04/07 21:39:16 srichter Exp $
"""

from Interface import Interface

class IPOP3Message(Interface):
    """THis interface describes the methods that are expected of a
       message by the POP3 server.
    """

    def isDeleted():
        """Return the 'deleted' status of the message.
        """

    def setDeleted(deleted=1):
        """Set the message as delted or not-deleted.
        """
        
    def getEntireMessage():
        """Return the entire message, including headers.
        """

    def getBody():
        """Return only the body of the message.
        """

    def getSize():
        """Return the size of the message in octets.
        """

    def getUID():
        """Get a unique id, following th eguidelines given in the POP3 RFC
           in the UIDL section.
        """
    



=== Added File Zope3/lib/python/Zope/Server/POP3/IPOP3MessageList.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""

$Id: IPOP3MessageList.py,v 1.1.2.1 2002/04/07 21:39:16 srichter Exp $
"""

from Interface import Interface

class IPOP3MessageList(Interface):    
    """
    """

    def open():
        """Open the mailbox and create the message list.
        """

    def close():
        """Close the mailbox.

           Here the messages that are marked as 'deleted' should be
           removed before saving the list.
        """

    def exists(index):
        """Check whether this index exists in this Message List.
        """

    def getMessages():
        """Return a list of non-deleted messages.
        """

    def getMessage(index):
        """Return the message of the given index.
        """

    def getTotalSize():
        """Get the total size of all non-deleted messages.
        """

    def getIndex(message):
        """Get the index of a particular method."""
        

    def __getitem__(index):
        """Get the message wuth the specified 'index'.
        """

        

    


=== Added File Zope3/lib/python/Zope/Server/POP3/POP3Message.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""

$Id: POP3Message.py,v 1.1.2.1 2002/04/07 21:39:16 srichter Exp $
"""

from Zope.Server.POP3.IPOP3Message import IPOP3Message
import md5

class POP3Message:

    __implements__ =  IPOP3Message


    def __init__(self, rfc822_msg):
        """ """
        self.rfc822_msg = rfc822_msg
        self._deleted = 0
    
    ############################################################
    # Implementation methods for interface
    # Zope.Server.POP3.IPOP3Message.

    def isDeleted(self):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        return self._deleted

    def setDeleted(self, deleted=1):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        self._deleted = deleted
        
    def getEntireMessage(self):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        self.rfc822_msg.fp.seek(0)
        return self.rfc822_msg.fp.read()

    def getBody(self):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        return self.rfc822_msg.fp.read()

    def getSize(self):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        return len(self.getEntireMessage())

    def getUID(self):
        'See Zope.Server.POP3.IPOP3Message.IPOP3Message'
        hash = md5.md5(self.getBody())
        return hash.hexdigest()

    #
    ############################################################


=== Added File Zope3/lib/python/Zope/Server/POP3/POP3MessageList.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""

$Id: POP3MessageList.py,v 1.1.2.1 2002/04/07 21:39:16 srichter Exp $
"""
import mailbox
import os

from IPOP3MessageList import IPOP3MessageList
from POP3Message import POP3Message

class POP3MessageList:

    __implements__ =  IPOP3MessageList



    def __init__(self, maildir, maildrop=None):
        """ """
        self.maildir = maildir
        self.maildrop = maildrop

        self._messagelist = []
        self._mb = None

    ############################################################
    # Implementation methods for interface
    # Zope.Server.POP3.IPOP3MessageList.

    def open(self, maildrop=None):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        if maildrop is None:
            maildrop = self.maildrop
        else:
            self.maildrop = maildrop
        md_file = self.maildir.open(maildrop, 'r')
        self._mb = mailbox.UnixMailbox(md_file)
        msg = self._mb.next()
        self._messagelist = []
        while msg is not None:
            self._messagelist.append(POP3Message(msg))
            msg = self._mb.next()


    def close(self):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        mb_str = ''
        for msg in self.getMessages():
            mb_str += msg.getEntireMessage()
        self._mb.fp.close()
        file = self.maildir.open(self.maildrop, 'w')
        file.write(mb_str)
        file.close()
            

    def exists(self, index):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        if index > 0 and index <= len(self._messagelist):
            return 1
        return 0


    def getMessages(self):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        return filter(lambda msg: not msg.isDeleted(), self._messagelist)


    def getMessage(self, index):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        return self._messagelist[index-1]


    def getTotalSize(self):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        sizes = map(lambda msg: msg.getSize(), self.getMessages())
        if sizes:
            return reduce(lambda x, y: x+y, sizes)
        else:
            return 0


    def getIndex(self, message):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        return self._messagelist.index(message) + 1


    def __getitem__(self, index):
        'See Zope.Server.POP3.IPOP3MessageList.IPOP3MessageList'
        return self.getMessage(index)
        
    #
    ############################################################


=== Zope3/lib/python/Zope/Server/POP3/IPOP3CommandHandler.py 1.1.2.1 => 1.1.2.2 ===
 
 class IPOP3CommandHandler(Interface):
-    """
+    """This interface lists the implemented POP3 commands following
+       RFC 1939. Some advanced commands specified in RFC 2449 are also
+       listed.
     """
 
     def cmd_apop(args):
@@ -111,6 +113,46 @@
         """
 
 
+    def cmd_capa(args):
+        """CAPA
+
+           Arguments: none
+           
+           Restrictions: none
+           
+           Discussion:
+               An -ERR response indicates the capability command
+               is not implemented and the client will have to
+               probe for capabilities as before.
+           
+               An +OK response is followed by a list of
+               capabilities, one per line.  Each capability name
+               MAY be followed by a single space and a
+               space-separated list of parameters.  Each
+               capability line is limited to 512 octets
+               (including the CRLF).  The capability list is
+               terminated by a line containing a termination
+               octet (".") and a CRLF pair.
+           
+            Possible Responses:
+                +OK -ERR
+           
+            Examples:
+                C: CAPA
+                S: +OK Capability list follows
+                S: TOP
+                S: USER
+                S: SASL CRAM-MD5 KERBEROS_V4
+                S: RESP-CODES
+                S: LOGIN-DELAY 900
+                S: PIPELINING
+                S: EXPIRE 60
+                S: UIDL
+                S: IMPLEMENTATION Shlemazle-Plotz-v302
+                S: .
+
+        """
+
     def cmd_dele(args):
         """DELE msg
 
@@ -191,6 +233,28 @@
              C: LIST 3
              S: -ERR no such message, only 2 messages in maildrop
         """
+
+             
+    def cmd_noop(args):
+        """NOOP
+
+           Arguments: none
+
+           Restrictions:
+               may only be given in the TRANSACTION state
+
+           Discussion:
+               The POP3 server does nothing, it merely replies with a
+               positive response.
+
+           Possible Responses:
+               +OK
+
+           Examples:
+               C: NOOP
+               S: +OK
+        """
+
 
     def cmd_pass(args):
         """PASS string


=== Zope3/lib/python/Zope/Server/POP3/POP3Server.py 1.1.2.1 => 1.1.2.2 ===
 from Zope.Server.ServerBase import ServerBase
 
+from Zope.Server.VFS.UnixFileSystem import UnixFileSystem
+from Zope.Server.Authentication.DictionaryAuthentication import \
+     DictionaryAuthentication
+
+
 class POP3Server(ServerBase):
     """Generic FTP Server"""
 
@@ -25,11 +30,16 @@
     SERVER_IDENT = 'Zope.Server.POP3Server'
 
 
-    def __init__(self, ip, port, task_dispatcher=None, adj=None, start=1,
-                 hit_log=None, verbose=0, socket_map=None):
-        super(FTPServer, self).__init__(ip, port, task_dispatcher,
-                                        adj, start, hit_log,
-                                        verbose, socket_map)
+    def __init__(self, ip, port, maildir, auth, task_dispatcher=None,
+                 adj=None, start=1, hit_log=None, verbose=0,
+                 socket_map=None):
+
+        self.auth_source = auth
+        self.maildir = UnixFileSystem(maildir)
+
+        super(POP3Server, self).__init__(ip, port, task_dispatcher,
+                                         adj, start, hit_log,
+                                         verbose, socket_map)
 
 
 if __name__ == '__main__':
@@ -39,11 +49,12 @@
 
     td = ThreadedTaskDispatcher()
     td.setThreadCount(4)
-    POP3Server('', 8110, task_dispatcher=td)
+    auth_source = DictionaryAuthentication({'foo': 'bar'})
+    POP3Server('', 110, '/var/mail', auth_source, task_dispatcher=td)
     try:
         while 1:
             asyncore.poll(5)
-            print 'active channels:', POP3ServerChannel.active_channels
+            # print 'active channels:', POP3ServerChannel.active_channels
     except KeyboardInterrupt:
         print 'shutting down...'
         td.shutdown()


=== Zope3/lib/python/Zope/Server/POP3/POP3ServerChannel.py 1.1.2.1 => 1.1.2.2 ===
 import stat
 import socket
+import thread
 import time
+import md5
 
 from Zope.Server.LineReceiver.LineServerChannel import LineServerChannel
 from POP3StatusMessages import status_msgs
-from FileProducer import FileProducer
+from POP3MessageList import POP3MessageList
 
 from IPOP3CommandHandler import IPOP3CommandHandler
-from PassiveAcceptor import PassiveAcceptor
-from RecvChannel import RecvChannel
-from XmitChannel import XmitChannel
 
 
 class POP3ServerChannel(LineServerChannel):
@@ -37,28 +36,46 @@
 
     __implements__ = LineServerChannel.__implements__, IPOP3CommandHandler
 
-
     # These are the commands that are allowed without the channel being in
     # "Transaction State", as the POP3 RFC calls it.
-    special_commands = ('cmd_quit', 'cmd_user', 'cmd_pass')
+    special_commands = ('cmd_quit', 'cmd_apop', 'cmd_capa',
+                        'cmd_user', 'cmd_pass')
 
     # These are the commands that are accessing the filesystem.
     # Since this could be also potentially a longer process, these commands
     # are also the ones that are executed in a different thread.
-    thread_commands = ('cmd_apop', 'cmd_dele', 'cmd_list', 'cmd_pass',
-                       'cmd_retr', 'cmd_rset', 'cmd_stat', 'cmd_top',
-                       'cmd_uidl')
+    # thread_commands = ('cmd_apop', 'cmd_dele', 'cmd_list', 'cmd_pass',
+    #                    'cmd_retr', 'cmd_rset', 'cmd_stat', 'cmd_top',
+    #                    'cmd_uidl')
 
     # Define the status messages
     status_messages = status_msgs
 
+    # Deifne the factory that will create a message list object
+    message_list_factory = POP3MessageList
+
+    # Define the error message that occurs, when the reply code was not found.
+    reply_error = '-ERR Reply Code unknown: %s'
+
+    # A list of this servers capabilities
+    capa_list = ('APOP MAILBOX MD5-DISGEST', 'CAPA', 'DELE MSG-INDEX',
+                 'LIST [MSG-INDEX]', 'NOOP', 'PASS PASSWORD', 'QUIT',
+                 'RETR MSG-INDEX', 'RSET', 'STAT', 'TOP MSG-INDEX NROFLINES',
+                 'UIDL [MESG-INDEX', 'USER MAILBOX')
+
     def __init__(self, server, conn, addr, adj=None, socket_map=None):
         super(POP3ServerChannel, self).__init__(server, conn, addr,
                                                adj, socket_map)
 
         self.username = ''
         self.password = ''
-        self.messagelist = []
+        self.messagelist = None
+
+        self.secret = "<%d.%d.%s@%s.%s>" %( os.getpid(), thread.get_ident(),
+                                            str(time.clock()),
+                                            socket.gethostname(),
+                                            str(id(self)) )
+        self.reply('OK_GREETING', self.secret)
         
 
     ############################################################
@@ -67,21 +84,48 @@
 
     def cmd_apop(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-        pass
+        if self.authenticated:
+            return self.reply('ERR_INV_STATE')
+
+        # We require the username and the MD5 hash
+        if len(args.split()) != 2:
+            return self.reply('ERR_CMD_UNKNOWN')
+
+        # Get the username and check whether the user exists
+        auth = self.server.auth_source
+        self.username, hash = args.split()
+        if not auth.hasUser(self.username):
+            return self.reply('ERR_LOGIN_MISMATCH')
+
+        # See whether we got the right MD5 hash
+        self.password = auth.getPassword(self.username)
+        expected = md5.new(self.secret + self.password).hexdigest()
+        if expected != hash:
+            return self.reply('ERR_LOGIN_MISMATCH')
+        
+        self.openMessageList()
+        self.reply('OK_LOGIN')
+        self.authenticated = 1
+
+
+    def cmd_capa(self, args):
+        'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
+        self.reply('OK_CAPA')
+        self.write('\r\n'.join(self.capa_list))
+        self.write('\r\n.\r\n')
+        self.flush()
 
 
     def cmd_dele(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
         try:
-            msg_index = int(args)-1
+            msg_index = int(args)
         except:
             return self.reply('ERR_MSG_UNKNOWN')
 
-        msg_list = self.messagelist
-
-        if msg_index >= 0 and msg_index < len(msg_list):
+        if self.messagelist.exists(msg_index):
             # mark message as deleted
-            self.msg_list[msg_index].deleted = 1
+            self.messagelist[msg_index].setDeleted()
             return self.reply('OK_DELETE')
 
         return self.reply('ERR_MSG_UNKNOWN')    
@@ -89,43 +133,44 @@
 
     def cmd_list(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-
-        msg_list = self.messagelist
-
         if args:
             # A message id was passed, so let's work with it.
             try:
-                msg_index = int(args)-1
+                msg_index = int(args)
             except:
                 return self.reply('ERR_MSG_UNKNOWN')
 
-            if msg_index >= 0 and msg_index < len(msg_list):
+            if not self.messagelist.exists(msg_index):
                 return self.reply('ERR_MSG_UNKNOWN')
 
-            message = msg_list[msg_index]
-            self.reply('OK_SINGLE_LIST', (msg_index+1, message.size)
+            message = self.messagelist[msg_index]
+            self.reply('OK_SINGLE_LIST', (msg_index, message.getSize()))
 
         else:
-            usedbytes = self.getTotalSize(msg_list)
-            total_msgs = len(map(lambda msg: not msg.deleted, msg_list))
-            self.reply('OK_MSG_LIST', (len(msg_list), usedbytes))
-
-            for msg in msg_list:
-                if msg.deleted: continue 
-                self.write("%d %d\r\n" % (msg_list.index(msg)+1, msg.size))
+            self.reply('OK_MSG_LIST', (len(self.messagelist.getMessages()),
+                                       self.messagelist.getTotalSize()))
+
+            for msg in self.messagelist.getMessages():
+                self.write("%d %d\r\n" % (self.messagelist.getIndex(msg),
+                                          msg.getSize()))
 
             self.write('.\r\n')
             self.flush()
 
 
-    def cmd_pass(self, args):
+    def cmd_noop(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
+        self.reply('OK_GENERAL')
 
+        
+    def cmd_pass(self, args):
+        'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
         self.password = args
 
         if self.authenticated:
             return self.reply('ERR_INV_STATE')
 
+        # No username was specified. Use USER first
         if not self.username:
             return self.reply('ERR_NO_USER')
 
@@ -133,53 +178,50 @@
         self.authenticated, message = auth.authenticate(self.username,
                                                         self.password)
         if self.authenticated:
+            self.openMessageList()
             self.reply('OK_LOGIN')
         else:
             self.reply('ERR_LOGIN_MISMATCH')
-            self.close_when_done()
+            self.close()
 
 
     def cmd_quit(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
         self.reply('OK_QUIT')
-        # XXX Should de;ete messages
-        self.close_when_done()
+        self.closeMessageList()
+        self.close()
 
         
     def cmd_retr(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-        
         try:
-            msg_index = int(args)-1
+            msg_index = int(args)
         except:
             return self.reply('ERR_MSG_UNKNOWN')
 
-        msg_list = self.messagelist
-
-        if msg_index >= 0 and msg_index < len(msg_list):
-            # mark message as deleted
-            self.write(msg.body)
-            return self.reply('OK_RETR')
-
-        return self.reply('ERR_MSG_UNKNOWN')    
+        # Output the message quickly, so we get done.
+        if self.messagelist.exists(msg_index):
+            msg = self.messagelist.getMessage(msg_index)
+            self.reply('OK_RETR', msg.getSize())
+            self.write(msg.getEntireMessage())
+            self.write('\r\n.\r\n')
+        else:
+            return self.reply('ERR_MSG_UNKNOWN')    
 
 
     def cmd_rset(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-
-        for msg in self.messagelist):
+        for msg in self.messagelist:
             # mark all messages as not-deleted 
-            msg.deleted = 0
+            msg.setDeleted(0)
 
         self.reply('OK_RSET')
 
 
     def cmd_stat(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-        usedbytes = self.getTotalSize(msg_list)
-        total_msgs = len(map(lambda msg: not msg.deleted, msg_list))
-
-        self.reply('OK_STAT', (total_msgs, usedbytes))
+        self.reply('OK_STAT', (self.messagelist.getTotalSize(),
+                               len(self.messagelist.getMessages())) )
 
 
     def cmd_top(self, args):
@@ -187,35 +229,22 @@
 
         try:
             msg_index, nroflines = args.split()
-            msg_index = int(args)-1
+            msg_index = int(msg_index)
             nroflines = int(nroflines)
         except:
             return self.reply('ERR_MSG_UNKNOWN')
 
-        msg_list = self.messagelist
-
-        if msg_index >= 0 and msg_index < len(msg_list):
-            # Split message body into lines/
-            lines = self.messagelist[msg_index].body.split("\r\n")
-
-            found, isHeader, line = -1, 1, 0
-
-            # XXX: This is too complicated. It can be done easier.
-
-            while line < len(nroflines):
-                if isHeader:
-                    # skip header lines. Header lines have a :
-                    if lines[line].find(':') == 0:
-                        isHeader = 0
-                else:
-                    found += 1
-                    if found == nroflines:
-                        break
+        if self.messagelist.exists(msg_index):
+            # Split message body into lines
+            lines = self.messagelist[msg_index].getBody().split('\n')
+
+            # Determine the true number of lines that will be returned
+            if nroflines <= len(lines):
+                lines = lines[:nroflines]
 
-            self.reply('OK_TOP', line)
-            lines = lines[:line]
+            self.reply('OK_TOP', len(lines))
             self.write("\r\n".join(lines))
-            self.write('.\r\n')
+            self.write('\r\n.\r\n')
             self.flush()
         else:                    
             return self.reply('ERR_MSG_UNKNOWN')    
@@ -223,7 +252,28 @@
 
     def cmd_uidl(self, args):
         'See Zope.Server.POP3.IPOP3CommandHandler.IPOP3CommandHandler'
-        pass
+        if args:
+            # A message id was passed, so let's work with it.
+            try:
+                msg_index = int(args)
+            except:
+                return self.reply('ERR_MSG_UNKNOWN')
+
+            if not self.messagelist.exists(msg_index):
+                return self.reply('ERR_MSG_UNKNOWN')
+
+            message = self.messagelist[msg_index]
+            self.reply('OK_SINGLE_UIDL', (msg_index, message.getUID()))
+
+        else:
+            self.reply('OK_MSG_UIDL')
+
+            for msg in self.messagelist.getMessages():
+                self.write("%i %s\r\n" % (self.messagelist.getIndex(msg),
+                                          msg.getUID()))
+
+            self.write('.\r\n')
+            self.flush()
 
 
     def cmd_user(self, args):
@@ -232,10 +282,21 @@
         if self.authenticated:
             return self.reply('ERR_INV_STATE')
 
-        if self.auth.hasUser(args):
+        if self.server.auth_source.hasUser(args):
+            self.username = args
             self.reply('OK_USER', args)
         else:
             self.reply('ERR_NOT_USER')
 
     #
     ############################################################
+
+
+    def openMessageList(self):
+        self.messagelist = self.message_list_factory(self.server.maildir)
+        self.messagelist.open(self.username)
+        
+
+    def closeMessageList(self):
+        if self.messagelist is not None:
+            self.messagelist.close()


=== Zope3/lib/python/Zope/Server/POP3/POP3StatusMessages.py 1.1.2.1 => 1.1.2.2 ===
 
 status_msgs = {
-    'OK_GENERAL'        : '+OK',
-    'OK_LOGIN'          : '+OK login successful',
-    'OK_USER'           : '+OK %s is a valid name box',
-    'OK_QUIT'           : '+OK',
-    'OK_SINGLE_LIST'    : '+OK %i %i',
-    'OK_MSG_LIST'       : '+OK %i message (%i octets)',
-    'OK_DELETE'         : '+OK The message was successfully deleted'
-    'OK_RETR'           : '+OK message follows',
-    'OK_RSET'           : '+OK Resetting all messages done',
-    'OKTOP'             : '+OK top %i lines of message follows',
-    'OK_APOP'           : '+OK maildrop locked and ready',
-    'OK_UIDL'           : '+OK unique-id listing follows',
+    'OK_GENERAL'         : '+OK',
+    'OK_GREETING'        : '+OK Zope 3 POP3 server ready %s',
+    'OK_LOGIN'           : '+OK login successful',
+    'OK_USER'            : '+OK %s is a valid mailbox',
+    'OK_QUIT'            : '+OK Connection is closing.',
+    'OK_SINGLE_LIST'     : '+OK %i %i',
+    'OK_MSG_LIST'        : '+OK %i message (%i octets)',
+    'OK_DELETE'          : '+OK The message was successfully deleted',
+    'OK_RETR'            : '+OK message follows (%i octets)',
+    'OK_RSET'            : '+OK Resetting all messages done',
+    'OK_STAT'            : '+OK %i %i',
+    'OK_TOP'             : '+OK top %i lines of message follows',
+    'OK_APOP'            : '+OK maildrop locked and ready',
+    'OK_SINGLE_UIDL'     : '+OK %i %s',
+    'OK_MSG_UIDL'        : '+OK Unique message id list follows',
+    'OK_CAPA'            : '+OK Capability list follows',
 
-    'ERR_CMD_UNKNOWN'   : '-ERR unknown command',
-    'ERR_MSG_UNKNOWN'   : '-ERR unknown or invalid message id',
-    'ERR_INV_STATE'     : '-ERR Invalid State',
-    'ERR_NO_USER'       : '-ERR No user was yet specified'
-    'ERR_NOT_USER'      : '-ERR never heard of mailbox name',
-    'ERR_LOGIN_MISMATH' : '-ERR username and password did not match',
+    'ERR_CMD_UNKNOWN'    : '-ERR unknown command',
+    'ERR_MSG_UNKNOWN'    : '-ERR unknown or invalid message id',
+    'ERR_INV_STATE'      : '-ERR Invalid State',
+    'ERR_NO_USER'        : '-ERR No user was yet specified',
+    'ERR_NOT_USER'       : '-ERR never heard of mailbox name',
+    'ERR_LOGIN_MISMATCH' : '-ERR username and password did not match',
+
+    'CMD_UNKNOWN'        : '-ERR %s command unknown',
+    'LOGIN_REQUIRED'     : '-ERR Not yet logged in.',
     }