[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.