[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[minion-cvs] Implement much (but not all) of ClientAPI



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv12221/mixminion

Modified Files:
	ClientAPI.py ClientDirectory.py ClientUtils.py 
Log Message:
Implement much (but not all) of ClientAPI

Index: ClientAPI.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientAPI.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- ClientAPI.py	7 Dec 2004 01:44:30 -0000	1.4
+++ ClientAPI.py	3 May 2005 03:31:40 -0000	1.5
@@ -26,9 +26,18 @@
             "AddrMsgDest", "SURBMsgDest", "SURB", "ReceivedPacket",
             ]
 
+import mixminion.Config
+import mixminion.ClientUtils
+
 # The operations in this file raise 'MixError' on failure.
 from mixminion.Common import MixError
 
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
 class ClientEnv:
     """A ClientEnv is an interface to a Mixminion client environment.  Once
        you allocate a ClientEnv, you should release it with .close() before
@@ -87,7 +96,16 @@
          - Reassembling fragmented messages.
     """
     def __init__(self):
-        pass
+        self._configLocation = None
+        self._config = None
+        self._descriptorSource = None
+        self._surbLog = None
+        self._passwordManager = None
+        self._keyring = None
+        self._queue = None
+        self._fragmentPool = None
+        self._logHandler = None
+        self._statusLogHandler = None
 
     # ------------------------------------------------------------
     # Configuration functions
@@ -99,8 +117,33 @@
 
            You don't need to invoke this funtion; if you don't call it,
            the default configuration file will be lazy-loaded on demand.
+
+           Raises IOError, OSError, mixminion.Config.ConfigError
         """
-        pass
+        if location is None:
+            location = self._configLocation
+        if location is None:
+            location = os.environ.get("MIXMINIONRC")
+        if location is None:
+            for candidate in ["~/.mixminionrc", "~/mixminionrc"]:
+                if os.path.exists(os.path.expanduser(candidate)):
+                    location = candidate
+                    break
+        if location is None:
+            if sys.platform == 'win32':
+                location = "~/mixminionrc"
+            else:
+                location = "~/.mixminionrc"
+        if location is not None:
+            location = os.path.expanduser(location)
+
+        if not os.path.exists(location):
+            LOG.warn("Writing default configuration file to %r",location)
+            #XXXX?
+            mixminion.ClientMain.installDefaultConfig(location)
+
+        self._configLocation = location
+        self._config = mixminion.Config.ClientConfig(fname=location)
 
     # Advanced:
     def setConfig(self, config):
@@ -109,8 +152,14 @@
            mapping key names to values; or a string containing the contents
            of a configuration file; or an object conforming to the
            interface of mixminion.Config.ClientConfig.
+
+           raises ConfigError
         """
-        pass
+        if type(self._config) == types.StringType:
+            self._config = mixminion.Config.ClientConfig(string=config)
+        else:
+            self._config = config
+        self._configLocation = None
 
     #------------------------------------------------------------
     # Functions to change the underlying pluggable helper objects.
@@ -123,38 +172,38 @@
            must conform to the interface of
            mixminion.ServerInfo.DescriptorSource
         """
-        pass
+        self._descriptorSource = descriptorSource
 
     def setSURBLog(self, surbLog):
         """Override the implementation of the database used to log
            which SURBs we've used, and when.  Must conform to
            mixminion.ClientUtils.SURBLog."""
-        pass
+        self._surbLog = surbLog
 
     def setPasswordManager(self, passwordManager):
         """Override the object used to ask for passwords from the user
            and remember passwords that we've received.  Must conform to
            mixminion.ClientUtils.PasswordManager.
         """
-        pass
+        self._passwordManage = passwordManager
 
     def setKeyring(self, keyring):
         """Override the object used to store SURB keys, encrypted with the
            user's password.  Must conform to mixminion.ClientUtils.Keyring.
         """
-        pass
+        self._keyring = keyring
 
     def setQueue(self, queue):
         """Override the object used to store pending packets.  Must conform
            to mixminion.ClientUtils.ClientQueue.
         """
-        pass
+        self._queue = queue
 
     def setFragmentPool(self, pool):
         """Override the object used to reassemble fragmented messages. Must
            conform to mixminion.ClientUtils.ClientFragmentPool.
         """
-        pass
+        self._fragmentPool = pool
 
     def addLogHandler(self, func):
         """Add a callback to receive log messages.  The argument
@@ -163,36 +212,81 @@
            since the epoch), and a message (a string) whenever a log event
            occurs.
         """
+        #XXXX
         pass
 
+
     def addStatusLogHandler(self, func):
         """Add a callback to receive --status-fd messages.  The argument
            'func' will be called once for every line that would be sent to
            the status fd, with the line as an argument.
         """
+        #XXXX
         pass
 
     # ------------------------------------------------------------
     # Internal helpers.  You don't need to call these.
     def _getConfig(self):
-        pass
+        if self._config is None:
+            self.loadConfig(self._configLocation)
+
+        return self._config
 
     def _getClientDirectory(self):
-        pass
+        if self,_clientDirectory is None:
+            CD = mixminion.ClientDirectory.ClientDirectory
+            config = self._getConfig()
+            diskLock = self._getDiskLock()
+            if self._descriptorSource is None:
+                self._clientDirectory = CD(config=config, diskLock=diskLock)
+            else:
+                self._clientDirectory = CD(config=config, diskLock=diskLock,
+                                           stor=self._descriptorSource)
+        return self._clientDirectory
 
     def _getSURBLog(self):
-        pass
+        if self._surbLog is None:
+            userdir = self._getConfig()['User']['UserDir']
+            mixminion.Common.createPrivateDir(userdir)
+            mixminion.Common.createPrivateDir(os.path.join(userdir,"surbs"))
+            fname = os.path.join(userdir, "surbs", "log")
+            self._surbLog = mixminion.ClientUtils.SURBLog(fname)
+        return self._surbLog
 
     def _getPasswordManager(self):
-        pass
+        #XXXX More ways to set password manager!!
+        if self._passwordManager is None:
+            self._passwordManager = mixminion.ClientUtils.CLIPasswordManager()
+        return self._passwordManager
 
     def _getKeyring(self):
-        pass
+        if self._keyring is None:
+            userdir = self._getConfig()['User']['UserDir']
+            keydir = os.path.join(userdir, "keys")
+            mixminion.Common.createPrivateDir(userdir)
+            mixminion.Common.createPrivateDir(keydir)
+            fn = os.path.join(keydir, "keyring")
+            self._keyring = mixminion.ClientUtils.Keyring(
+                fn, self._getPasswordManager())
+        return self._keyring
 
     def _getQueue(self):
-        pass
+        if self._queue is None:
+            userdir = self._getConfig()['User']['UserDir']
+            queuedir = os.path.join(userdir, "queue")
+            self._queue = mixminion.ClientUtils.ClientQueue(queuedir)
+        return self._queue
 
     def _getFragmentPool(self):
+        if self._fragmentPool is None:
+            userdir = self._getConfig()['User']['UserDir']
+            fragentsdir = os.path.join(userdir, "fragments")
+            self._fragmentPool = mixminion.ClientUtils.ClientFragmentPool(
+                fragmentsdir)
+        return self._queue
+
+    def _getDiskLock(self):
+        #XXXX
         pass
 
     # ------------------------------------------------------------
@@ -202,39 +296,83 @@
            Does not force a directory download, even if the directory is
            stale.
         """
-        pass
+        return self._getClientDirectory().getAllNicknames()
 
     def getRecommendedMixNames(self):
         """Return a list of the names of all the recommended mixes we know
            about.  Does not force a directory download, even if the directory
            is stale.
         """
-        pass
+        return self._getClientDirectory().getRecommendedNicknames()
 
     def getMixByName(self, name):
         """Return a Mix object for a given (case-insensitive) Mix nickname.
            Raise KeyError is no such Mix is known.  Does not force a directory
            download, even if the directory is stale.
         """
-        pass
+        servers = self._getClientDirectory().getServersByNickname(name)
+        if not servers:
+            raise KeyError(name)
+        return Mix(name, servers)
 
     def updateDirectory(self, force=0):
         """If the directory is stale, or if 'force' is true, download
            a fresh directory.
         """
-        pass
+        self._getClientDirectory().update(force=force)
+
+    def _getPathDuration(self, messageDest):
+        if messageDest.isSURB():
+            #XXXX add way to set this.
+            if self.lifetime is not None:
+                duration = self.lifetime * 24*60*60
+            else:
+                duration = int(self._getConfig()['Security']['SURBLifetime'])
+        else:
+            duration = 24*60*60
+        return duration
 
     def checkPathSpec(self, pathSpec, messageDest):
         """Given a PathSpec object and a MsgDest object, raise MixError if
            no corresponding valid paths could be generated.
+           DOCDOC forSurb
         """
-        pass
+        if pathSpec._forReply and not messageDest.isSURB():
+            raise MixError(
+                "Can't use a non-SURB destination for a reply message")
+        else if messageDest.isSURB() and not pathSpec._forReply():
+            raise MixError(
+                "Can't use a SURB as a destination for a non-reply message")
+
+        duration = self._getPathDuration(messageDest)
+        startAt = time.time()
+        endAt = time.time() + duration
+        d = self._getClientDirectory()
+        parsed = mixminion.ClientDirectory.parsePath(
+            config=self._getConfig(),
+            path=pathSpec._string,
+            isReply=pathSpec._forReply,
+            isSURB=pathSpec._forSURB)
+        # XXXX Some way to set isSSFragmented
+        d.validatePath(parsed, messageDest._getExitAdress(), startAt, endAt)
+        pathSpec._parsed = parsed
 
     def generatePaths(self, pathSpec, messageDest, n=1):
         """Given a PathSpec object and a MsgDest object, return a list of
            'n' Path objects conforming to chosen path spec and dest.
         """
-        pass
+        if pathSpec._parsed is not None:
+            self.checkPaths(pathSpec, messageDest)
+
+        duration = self._getPathDuration(messageDest)
+        startAt = time.time()
+        endAt = time.time() + duration
+        d = self._getClientDirectory()
+        #XXXX isSSFragmented
+        paths = d.generatePaths(n, pathSpec._parsed,
+                                messageDest._getExitAddress(),
+                                startAt, endAt)
+        return [ Path(p1, p2, messageDest) for p1, p2 in paths ]
 
     # ------------------------------------------------------------
     # Generating packets and SURBs
@@ -243,14 +381,24 @@
            EmailHeaders object, return the number of fragments that
            encodeAndSplit will return for the given message.
         """
-        pass
+        #set prefix XXXX
+        #set overhead XXXX
+        prefix = ""
+        overhead = 0
+        return mixminion.BuildMessage.getNPacketsToEncode(
+            messge, overhead, prefix)
 
     def encodeAndSplit(self, message, messageDest, headers=None):
         """Given a message (type string), a MsgDest object, and an optional
            EmailHeaders object, return a list of PacketBody objects for this
            message.
         """
-        pass
+        # Set prefix, set overhead. XXXX
+        prefix = ""
+        overhead = 0
+        payloads = mixminion.BuildMessage.encodeMessage(
+            message, overhead, prefix)
+        return [ PacketBody(p) for p in payloads ]
 
     def generateSURBs(self, n, pathSpec=None,  messageDest=None, identity=None,
                       surbKey=None):
@@ -266,6 +414,7 @@
            (High-level interface.  You can generate SURBs more directly
            using ClientEnv.generatePaths and generateSURB.)
         """
+        #XXXX
         pass
 
     def encryptPackets(self, bodies, messageDest, pathSpec=None,
@@ -279,20 +428,29 @@
            (High-level interface.  You can encrypt packets more directly
            using ClientEnv.generatePaths and encryptPacket.)
         """
+        #XXXX
         pass
 
     # ------------------------------------------------------------
     # Sending and/or queueing packets
-    def sendOrQueuePackets(self, packets, queueOnFail=1):
+    def sendOrQueuePackets(self, packets, queueOnFail=1, lazyQueue=0):
         """Given a sequence of Packet objects, send as many as possible.  If
            'queueOnFail' is true, then add any packets that fail to the queue.
            Returns a list of the packets that were not successfully delivered.
         """
+        #XXXX
         pass
 
     def queuePackets(self, packets):
         """Given a sequence of Packet objects, add them all to the queue."""
-        pass
+        q = self._getQueue()
+        h = []
+        for p in packets:
+            r = p._packetDest._routing
+            assert (isinstance(r, mixminion.Packet.IPV4Info) or
+                    isinstance(r, mixminion.Packet.MMTPHostInfo))
+            h.append(q.queuePacket(p._contents, r))
+        return h
 
     # ------------------------------------------------------------
     # Manipulating the queue
@@ -300,6 +458,7 @@
         """Try to deliver packets from the queue.  If 'serverNames' is provided,
            only send packets whose first hop is in serverNames.  If 'maxCount'
            is provided, send at most 'maxCount' packets."""
+        #XXXX
         pass
 
     def cleanQueue(self, serverNames=None, maxAge=30):
@@ -360,32 +519,63 @@
 
     # ------------------------------------------------------------
     # Housekeeping
-    def clean(self):
-        """Clean up all underlying containers and storage."""
-        pass
+    def clean(self, all=False):
+        """Clean up all underlying containers and storage.
+           DOCDOC all
+        """
+        if all or self._descriptorSource is not None:
+            self._getClientDirectory().clean()
+        if all or self._surbLog is not None:
+            self._getSURBLog().clean()
+        if all or self._keyring is not None:
+            self._getKeyring().save()
+        if all or self._queue is not None:
+            self._getQueue().cleanQueue()
+        if all or self._fragmentPool is not None:
+            #XXXX self._getFragmentPool().expireMessages(????)
+            pass
 
     # ------------------------------------------------------------
+
+    def flush(self):
+        """DOCDOC"""
+        pass
+
     def close(self):
         """Free all resources held by this ClientEnv, and flush all
            changes to disk.
         """
-        pass
+        if self._keyring is not None and self._keyring.isDirty():
+            self._keyring.save()
+        if self._surbLog is not None:
+            self._surbLog.close()
+            self._surbLog = None
+        if self._fragmentPool is not None:
+            self._fragmentPool.close()
+            self._fragmentPool = None
 
 # ============================================================
 class Mix:
     """A Mix object represents a single Type III remailer."""
-    def __init__(self):
-        pass
+    def __init__(self, name, servers):
+        assert len(servers)
+        self._name = name
+        self._servers = servers
     def getName(self):
         """Return this remailer's nickname."""
-        pass
+        return self._name
     def getFeature(self, feature):
-        """Return the value of a given feature for this remailer."""
-        pass
+        """Return a list of the values of a given feature for this remailer."""
+        d = {}
+        for s in self._servers:
+            d[s.getFeature(feature)]=1
+        v = d.keys()
+        v.sort()
+        return v
     def _getServerInfoList(self):
         """Helper: return a list of the server descriptors we have for
            this remailer."""
-        pass
+        return self._servers
 
 # ============================================================
 
@@ -405,7 +595,7 @@
         """Return an ASCII-armored representation of this object surrounded
            with -----BEGIN----- and -----END----- lines.
         """
-        pass
+        raise NotImplemented()
     def writeArmored(self, file):
         """Write the armored version of this object to the file-like object
            'file'.
@@ -422,8 +612,12 @@
             'foo,?,?,bar' (a path starting with foo, followed by 2 hops,
               ending in bar.)
     """
-    def __init__(self, string):
-        pass
+    def __init__(self, string, forSURB=False, forReply=False):
+        assert not (forSURB and forReply)
+        self._string = string
+        self._forSURB = forSURB
+        self._forReply = forReply
+        self._parsed = None
 
 class Path(_Encodeable):
     """A Path is a sequence of names for mixes in the mix-net, and a message
@@ -431,7 +625,9 @@
        clientEnv.generatePaths().
     """
     def __init__(self, path1, path2, msgDest):
-        pass
+        self._path1 = path1
+        self._path2 = path2
+        self._msgDest = msgDest
 
 class PacketBody(_Encodeable):
     """A PacketBody is the encoded contents of a single Type III packet,
@@ -442,7 +638,7 @@
        PacketBody objects.)
     """
     def __init__(self, contents):
-        pass
+        self._contents = contents
 
 class Packet(_Encodeable):
     """A Packet is a single Type III packet, and the address of its first
@@ -451,14 +647,15 @@
        (Use ClientEnv.encryptPackets to encrypt a set of PacketBody objects
        into Packet objects.)"""
     def __init__(self, contents, packetDest):
-        pass
+        self._contents = contents
+        self._packageDest = packetDest
 
 class PacketDest(_Encodeable):
     """A PacketDest is an address where a Mix receives packets; we use it
        to describe a packet's first hop.
     """
-    def __init__(self, routingType, routingInfo):
-        pass
+    def __init__(self, routing)
+        self._routing = routing #IVP4/MMTPHostInfo
 
 class MsgDest(_Encodeable):
     """A MsgDest is the address for a mssage.  It may be either a SURBMsgDest,
@@ -469,7 +666,9 @@
     def __init__(self):
         pass
     def isSURB(self):
-        pass
+        raise NotImplemented()
+    def _getExitAddress(self):
+        raise NotImplemented()
 
 def parseMessageAddress(string):
     """Parse an address given as a string into an AddrMsgDest.  Accepts strings
@@ -481,13 +680,25 @@
            OR 0x<routing type>:<routing info>
            OR 0x<routing type>
     """
-    pass
+    exitAddr = mixminion.ClientDirectory.ParseAddress(string)
+    tp, addr, host = exitAddr.getRouting()
+    return AddrMsgDest(tp, addr, host)
 
 class AddrMsgDest(MsgDest):
     """Indicates the final Type III exit address of a message.  Generate these
        by calling parseMessageAddress."""
-    def __init__(self, routingType, routingInfo, pkey=None):
-        pass
+    def __init__(self, exitAddress, routingInfo, targetHost=None, pkey=None):
+        self._routingType = routingType
+        self._routingInfo = routingInfo
+        self._targetHost = targetHost
+        self._pkey = pkey
+
+    def isSURB(self):
+        return False
+
+    def _getExitAddress(self):
+        return mixminion.ClientDirectory.ExitAddress(
+            self._routingType, self._routingInfo, self._targetHost)
 
 class SURBMsgDest(MsgDest):
     """Indicates that a Type III message will be delivered by one or more
@@ -495,6 +706,12 @@
     def __init__(self):
         pass
 
+    def isSURB(self):
+        return True
+
+    def _getExitAddress(self):
+        return mixminion.ClientDirectory.ExitAddress(isReply=1)
+
 class SURB(_Encodeable):
     """A single-use reply block, and _possibly_ the decoding handle that
        will be used when the corresponding packet is received.
@@ -502,55 +719,70 @@
        (The decodingHandle is only set when the SURB is first generated;
        is is sensitive, and so is *not* encoded along with the SURB.)"""
     def __init__(self, replyBlock, decodingHandle=None):
-        pass
+        self._replyBlock = replyBlock
+        self._decodingHandle = decodingHandle
     def getDecodingHandle(self):
-        pass
+        return self._decodingHandle
 
 class ReceivedPacket(_Encodeable):
     """The (decrypted) contents of a single received Type III packet.
     """
-    def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
-        pass
+    def __init__(self, payload, decodingHandle, isReply=0, replyIdentity=None):
+        self._payload = payload
+        self._handle = handle
+        self._isReply = isReply
+        self._replyIdentity = replyIdentity
 
     def isFragment(self):
-        pass
+        raise NotImplemented
 
     def isReply(self):
-        pass
+        return self._isReply
 
     def getSURBIdentity(self):
-        pass
+        return self._replyIdentity
 
 class ReceivedFragment(ReceivedPacket):
     """The (decrypted) contents of a single packet containing a fragment
        of a larger message.
     """
-    def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
-        pass
+    def __init__(self, payload, decodingHandle, isReply=0, replyIdentity=None):
+        ReceivedPacket.__init__(self, payload, decodingHandle, isReply,
+                                replyIdentiy)
+
+    def isFragment(self):
+        return True
 
     def getMessageID(self):
-        pass
+        return self._payload.msgID
 
 class ReceivedSingleton(ReceivedPacket):
     """The (decrypted) contents of a single packet containing an entire
        message.
     """
-    def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
-        pass
+    def __init__(self, payload, decodingHandle, isReply=0, replyIdentity=None):
+        ReceivedPacket.__init__(self, payload, decodingHandle, isReply,
+                                replyIdentity)
 
-    def getContents(self):
-        pass
+    def isFragment(self):
+        return False
+
+    def getContents(self, uncompress=True):
+        if uncompress:
+            return self._payload.getUncompressedContents()
+        else:
+            return self._payload.getContents()
 
 class EmailHeaders:
     """A set of email headers to be prepended to a message before delivery."""
     def __init__(self):
-        pass
+        self._headers = {}
     def setFrom(self, val):
-        pass
+        self._headers['From'] = val
     def setSubject(self, val):
-        pass
+        self._headers['Subject'] = val
     def setInReplyTo(self, val):
-        pass
+        self._headers['In-Reply-To'] = val
     def setReferences(self, val):
-        pass
+        self._headers['References'] = val
 

Index: ClientDirectory.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientDirectory.py,v
retrieving revision 1.46
retrieving revision 1.47
diff -u -d -r1.46 -r1.47
--- ClientDirectory.py	12 Dec 2004 22:28:39 -0000	1.46
+++ ClientDirectory.py	3 May 2005 03:31:40 -0000	1.47
@@ -889,6 +889,22 @@
         """DOCDOC"""
         return self.allServers
 
+    def getAllNicknames(self):
+        """DOCDOC"""
+        lst = self.byNicknames.keys()
+        lst.sort()
+        return lst
+
+    def getRecommendedNicknames(self):
+        """DOCDOC"""
+        lst = self.goodNicknames.keys()
+        lst.sort()
+        return lst
+
+    def getServersByNickname(self):
+        """DOCDOC"""
+        return self.__find(self.allServers, 0, sys.maxint)
+
     def _installAsKeyIDResolver(self):
         """Use this ClientDirectory to identify servers in calls to
            ServerInfo.displayServer*.

Index: ClientUtils.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientUtils.py,v
retrieving revision 1.28
retrieving revision 1.29
diff -u -d -r1.28 -r1.29
--- ClientUtils.py	25 Mar 2004 08:33:24 -0000	1.28
+++ ClientUtils.py	3 May 2005 03:31:40 -0000	1.29
@@ -495,7 +495,8 @@
 
 class Keyring(_LazyEncryptedStore):
     """Class to wrap a lazy-loaded file holding a bundle of SURB keys for
-       a client.  The format is as described in E2E-spec.txt, appendix A.2."""
+       a client.  The format is as described in E2E-spec.txt, appendix A.2.
+    """
     def __init__(self, fname, pwdManager):
         """Create a new LazyEncryptedStore
               fname -- The name of the file to hold the encrypted object.