[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Rudimentary client backend to implement most of George"...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv21079/lib/mixminion
Modified Files:
ClientMain.py Config.py Packet.py test.py
Log Message:
Rudimentary client backend to implement most of George's proposed interfaces.
Reply blocks still aren't as in the spec. Needs a CLI front-end, docs, and
testing.
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- ClientMain.py 10 Sep 2002 14:45:30 -0000 1.2
+++ ClientMain.py 10 Sep 2002 20:06:24 -0000 1.3
@@ -47,6 +47,7 @@
FFFF doesn't matter so much right now.
"""
def __init__(self, dirname):
+ dirname = os.path.join(dirname, 'servers')
createPrivateDir(dirname)
self.dirname = dirname
self.servers = None
@@ -78,16 +79,36 @@
else:
self.servers[nickname] = info
- def getCurrentServer(nickname, when=None):
+ def getCurrentServer(nickname, when=None, until=None):
+ if type(nickname) == ServerInfo:
+ return nickname
if when is None:
when = time.time()
- for info in self.servers[nickname]:
+ if until is None:
+ until = when+1
+ try:
+ serverList = self.servers[nickname]
+ except KeyError, e:
+ raise MixError("Nothing known about server %s"%nickname)
+ for info in serverList:
#XXXX fail on DNE
server = info['Server']
- if server['Valid-After'] <= now <= server['Valid-Until']:
+ if server['Valid-After'] <= when <= until <= server['Valid-Until']:
return info
- #XXXX fail on DNE
- return None
+ raise MixError("No current information for server %s"%nickname)
+
+ def getAllCurrentServers(when=None, until=0):
+ if when is None:
+ when = time.time()
+ if until is None:
+ until = when+1
+ result = []
+ for nickname, infos in self.servers.items():
+ for info in infos:
+ server = info['Server']
+ if server['Valid-After'] <= when <= until <= server['Valid-Until']:
+ result.append(info)
+ return result
def importServerInfo(self, fname, force=1):
self.load()
@@ -126,7 +147,192 @@
f.write(contents)
f.close()
+def installDefaultConfig(fname):
+ getLog().warn("No configuration file found. Installing default file in %s",
+ fname)
+ f = open(os.path.expanduser(fname), 'w')
+ f.write("""\
+# This file contains your options for the mixminion client.
+[Host]
+## Use this option to specify a 'secure remove' command.
+#ShredCommand: rm -f
+## Use this option to specify a nonstandard entropy source.
+#EntropySource: /dev/urandom
+
+[DirectoryServers]
+# Not yet implemented
+
+[User]
+## By default, mixminion puts your files in ~/.mixminion. You can override
+## this directory here.
+#UserDir: ~/.mixminion
+
+[Security]
+PathLength: 4
+
+## Not yet implemented:
+# SURBAddress: mbox:quux
+# SURBPathLength: 8
+
+""")
+ f.close()
+
+class MixminionClient:
+ def __init__(self, conf=None):
+ if conf is None:
+ conf = os.environ.get("MINIONRC", None)
+ if conf is None:
+ conf = "~/.minionrc"
+ if not os.path.exists(conf):
+ installDefaultConfig(conf)
+ conf = os.path.expanduser(conf)
+ self.config = mixminion.Config.ClientConfig(fname=conf)
+
+ getLog().configure(self.config)
+ getLog().debug("Configuring client")
+ mixminion.Common.configureShredCommand(self.config)
+ mixminion.Crypto.init_crypto(self.config)
+
+ # Make directories
+ userdir = self.config['User']['UserDir']
+ createPrivateDir(userdir)
+ createPrivateDir(os.path.join(userdir, 'surbs'))
+
+ # Get directory cache
+ self.dirCache = DirectoryCache(os.path.join(userdir,
+ 'directory', 'servers'))
+ self.dirCache.load()
+
+ # Initialize PRNG
+ self.prng = mixminion.Crypto.AESCounterPRNG()
+
+ def getDirectoryCache(self):
+ return self.dirCache
+
+ def _getRandomPath(self, length=None):
+ # FFFF Base-list functionality
+ if not length:
+ length = self.config['Security'].get('PathLength',8)
+
+ # XXXX We only pick servers that will be good for 24 hours. That's
+ # XXXX bad! It allows a delaying/partitioning attack.
+ servers = self.dirCache.getAllCurrentServers(when=time.time(),
+ until=time.time()+24*60*60)
+
+ if length > len(servers):
+ getLog().warn("I only know about %s servers; That's not enough to use distinct servers on your path.", len(servers))
+ result = []
+ while len(result) < length:
+ result.extend(prng.shuffle(servers))
+ return result[:length]
+ else:
+ return self.prng.shuffle(servers, length)
+
+ def _getPath(self, minLength, startAt, endAt, serverList=None):
+ if serverList is not None:
+ if len(serverList) < minLength:
+ raise MixError("Path must have at least %s hops", minLength)
+
+ serverList = [ self.dirCache.getCurrentServer(s,startAt,endAt)
+ for s in serverList ]
+ else:
+ serverList = self._getRandomPath()
+
+ if len(serverList) < minLength:
+ serverList += self._getRandomPath(minLength-len(serverList))
+
+ return serverList
+
+ def sendForwardMessage(self, routingType, routingInfo, payload,
+ serverList=None):
+ message, firstHop = self.generateForwardMessage(address,
+ payload,
+ serverList)
+ self.sendMessages([message], firstHop)
+
+ def sendReplyMessage(self, payload, replyBlock, serverList=None):
+ message, firstHop = self.generateReplyMessage(payload,
+ replyBlock,
+ serverList)
+ self.sendMessages([message], firstHop)
+
+ def generateForwardMessage(self, address, payload, serverList=None):
+ serverList = self._getPath(2,
+ #XXXX This is bogus; see above
+ time.time(), time.time()+24*60*60,
+ serverList)
+
+ firstPathlen = floorDiv(len(serverList), 2)
+ servers1,servers2 = serverList[:firstPathLen],serverList[firstPathLen:]
+
+ routingType, routingInfo, lastHop = address.getRouting()
+ if lastHop != None:
+ servers2.append(self.dirCache.getCurrentServer(lastHop))
+ msg = mixminion.BuildMessage.buildForwardMessage(payload,
+ routingType,
+ routingInfo,
+ servers1, servers2)
+ return msg, servers1[0]
+
+ def generateReplyBlock(self, address, startAt=None, endAt=None,
+ password=None, serverList=None):
+ if startAt is None:
+ startAt = time.time()
+ if endAt is None:
+ lifetime = self.config['Security'].get('SURBLifetime')
+ if lifetime:
+ endAt = startAt + lifetime[2]
+ else:
+ endAt = startAt + 24*60*60*7
+
+ path = self._getPath(2,
+ #XXXX This is bogus; see above
+ startAt, endAt,
+ serverList)
+
+ if password:
+ # XXXX Out of sync with spec.
+ raise MixFatalError("Not implemented")
+
+ handle = Crypto.getBytes(16)
+ rt, ri, lastHop = address.getRouting("RTRN"+handle)
+ if lastHop is not None:
+ path.append(lastHop)
+ block, secrets = mixminion.BuildMesssage.buildReplyBlock(path, rt, ri,
+ endAt,
+ self.prng)
+
+ # XXXX Store secrets and expiry time
+ return block
+
+ def generateReplyMessage(self, payload, replyBlock, serverList=None):
+ # XXXX Not in sync with spec
+ path = self._getPath(1, time.time(), replyBlock.timestamp,
+ serverList)
+
+ msg = mixminion.BuildMessage.buildReplyMessage(payload,
+ path,
+ replyBlock)
+ return msg, path[0]
+ def decodeReplyMessage(self, tag, payload):
+ pass
+
+ def sendMessages(self, msgList, server):
+ con = mixminion.MMTPClient.BlockingClientConnection(server.getAddr(),
+ server.getPort(),
+ server.getKeyID())
+ try:
+ con.connect()
+ for msg in msgList:
+ con.sendPacket(msg)
+ finally:
+ con.shutdown()
+
+class Address:
+ def getRouting(self, tag=None):
+ # Return rt, ri, lasthop
+ raise NotImplemented("Address.getRouting()")
def sendTestMessage(servers1, servers2):
assert len(servers1)
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -d -r1.15 -r1.16
--- Config.py 10 Sep 2002 14:45:30 -0000 1.15
+++ Config.py 10 Sep 2002 20:06:25 -0000 1.16
@@ -631,13 +631,25 @@
'User' : { 'UserDir' : ('ALLOW', None, "~/.mixminion" ) },
'Security' : { 'PathLength' : ('ALLOW', _parseInt, "8"),
'SURBAddress' : ('ALLOW', None, None),
- 'SURBPathLength' : ('ALLOW', _parseInt, "8") },
+ 'SURBPathLength' : ('ALLOW', _parseInt, "8"),
+ 'SURBLifetime' : ('ALLOW', _parseInterval, "7 days") },
}
def __init__(self, fname=None, string=None):
_ConfigFile.__init__(self, fname, string)
def validate(self, sections, entries, lines, contents):
_validateHostSection(sections.get('Host', {}))
+
+ security = sections.get('Security', {})
+ p = security.get('PathLength', 8)
+ if not 0 < p <= 16:
+ raise ConfigError("Path length must be between 1 and 16")
+ if p < 4:
+ getLog().warn("Your default path length is frighteningly low."
+ " I'll trust that you know what you're doing.")
+
+
+
SERVER_SYNTAX = {
'Host' : ClientConfig._syntax['Host'],
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- Packet.py 10 Sep 2002 14:45:30 -0000 1.9
+++ Packet.py 10 Sep 2002 20:06:26 -0000 1.10
@@ -259,6 +259,7 @@
class ReplyBlock:
"""A mixminion reply block, including the address of the first hop
on the path, and the RoutingType and RoutingInfo for the server."""
+ # XXXXX Not in sync with spec
def __init__(self, header, useBy, rt, ri):
"""Construct a new Reply Block."""
assert len(header) == HEADER_LEN
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.27
retrieving revision 1.28
diff -u -d -r1.27 -r1.28
--- test.py 10 Sep 2002 14:45:31 -0000 1.27
+++ test.py 10 Sep 2002 20:06:28 -0000 1.28
@@ -2521,12 +2521,19 @@
pass
#----------------------------------------------------------------------
+class ClientMainTests(unittest.TestCase):
+ def testClientKeystore(self):
+ pass
+
+#----------------------------------------------------------------------
def testSuite():
suite = unittest.TestSuite()
loader = unittest.TestLoader()
tc = loader.loadTestsFromTestCase
+ suite.addTest(tc(ClientMainTests))
suite.addTest(tc(ServerMainTests))
+
if 0: return suite
suite.addTest(tc(MiscTests))