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