[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs] Replace serverinfo cache with far simpler code. Remove ...



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv24879/lib/mixminion

Modified Files:
	ClientMain.py 
Log Message:
Replace serverinfo cache with far simpler code. Remove unimplemented functionality

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- ClientMain.py	21 Nov 2002 16:55:48 -0000	1.6
+++ ClientMain.py	22 Nov 2002 21:05:22 -0000	1.7
@@ -10,7 +10,6 @@
    XXXX at least one configuration works.
    """
 
-
 # The client needs to store:
 #      - config
 #      - keys for pending SURBs
@@ -33,156 +32,98 @@
 from mixminion.Common import getLog, floorDiv, createPrivateDir, MixError, \
      MixFatalError
 import mixminion.Crypto
-import mixminion.BuildMessage
+from mixminion.BuildMessage import buildForwardMessage
 import mixminion.MMTPClient
 import mixminion.Modules
 from mixminion.ServerInfo import ServerInfo
 from mixminion.Config import ClientConfig
 
-class DirectoryCache:
-    """Holds a set of directories and serverinfo objects persistently.
+class TrivialDirectoryCache:
+    def __init__(self, directory):
+	self.directory = directory
+	createPrivateDir(directory)
+	self.byNickname = {}
+	self.byFilename = {}
 
-       XXXX THIS CLASS IS NOT USED YET.  It's a bad design choice, since
-             the first version of the directory code will mostly obsolete it.
+	for f in os.listdir(self.directory):
+	    p = os.path.join(self.directory, f)
+	    try:
+		info = ServerInfo(fname=p, assumeValid=0)
+	    except ConfigError, e:
+		getLog().warn("Invalid server descriptor %s", p)
+		continue
 
-       FFFF This should actually cache the nickname and liveness information 
-       FFFF rather than parsing and reparsing it each time.  Of course, we'll
-       FFFF want to re-do it entirely once we have directory support, so it
-       FFFF doesn't matter so much right now.
-       """
-    ## Fields
-    # dirname: the name of the storage directory.  All files in the directory
-    #     should be of the form 'si_XXXX...X', and contain validated server
-    #     descriptors.
-    # servers: a map from nickname to [list of serverinfo objects], or None
-    #     if no servers have been loaded
-    # allServers: a list of serverinfo objects
-    def __init__(self, dirname):
-	dirname = os.path.join(dirname, 'servers')
-	createPrivateDir(dirname)
-	self.dirname = dirname
-	self.servers = None
+	    serverSection = info['Server']
+	    nickname = serverSection['Nickname']
+	    
+	    if '.' in f:
+		f = f[:f.rindex('.')]
+	    
+	    now = time.time()
 
-    def load(self, forceReload=0):
-	"""Retrieve a list of servers from disk.  If 'forceReload' is false,
-           only load the servers if we have not already done so."""
-	if not (self.servers is None or forceReload):
-	    return
-	now = time.time()
-	self.servers = {}
-	self.allServers = []
-	self.highest_num = -1
-	for fn in os.listdir(self.dirname):
-	    if not fn.startswith("si"):
+	    if now < serverSec['Valid-After']:
+		getLog().warn("Ignoring future decriptor %s", p)
 		continue
-	    n = int(fn[2:])
-	    if n > self.highest_num:
-		self.highest_num = n
-	    info = ServerInfo(fname=os.path.join(self.dirname, fn),
-			      assumeValid=1)
-	    nickname = info['Server']['Nickname']
-	    if info['Server']['Valid-Until'] < now:
-		getLog().info("Removing expired descriptor for %s",
-			      nickname)
-		os.unlink(os.path.join(self.dirname, fn))
+	    if now >= serverSec['Valid-Until']:
+		getLog().warn("Ignoring expired decriptor %s", p)
 		continue
-	    self.allServers.append(info)
-	    if self.servers.has_key(nickname):
-		self.servers[nickname].append(info)
-	    else:
-		self.servers[nickname] = [ info ]
-
-    def getCurrentServer(self,nickname, when=None, until=None):
-        """Return a server descriptor valid during the interval
-           when...until.  If 'nickname' is a string, return only a
-           server with the appropriate nickname.  If 'nickname' is a
-           server descriptor, return that same server descriptor.
-
-           Raise 'MixError' if no appropriate descriptor is found. """
-        self.load()
-	if when is None:
-	    when = time.time()
-	if until is None:
-	    until = when+1
-	if isinstance(nickname, ServerInfo):
-	    serverList = [ nickname ]
-	else:
-	    try:
-		serverList = self.servers[nickname]
-	    except KeyError, _:
-		raise MixError("Nothing known about server %s"%nickname)
-	for info in serverList:
-	    #XXXX fail on DNE
-	    server = info['Server']
-	    if server['Valid-After'] <= when <= until <= server['Valid-Until']:
-		return info
-	raise MixError("No time-valid information for server %s"%nickname)
+	    if now + 3*60*60 >= serverSec['Valid-Until']:
+		getLog().warn("Ignoring soon-to-expire decriptor %s", p)
+		continue
+	    if self.byNickname.has_key(nickname):
+		getLog().warn(
+		    "Ignoring descriptor %s with duplicate nickname %s",
+		    p, nickname)
+		continue
+	    if self.byFilename.has_key(fname):
+		getLog().warn(
+		    "Ignoring descriptor %s with duplicate prefix %s",
+		    p, f)
+		continue
+	    self.byNickname[nickname] = info
+	    self.byFilename[f] = info
 
-    def getAllCurrentServers(self, when=None, until=None):
-	"""Return all ServerInfo objects valid during a given interval."""
-        self.load()
-	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 getServerInfo(self, name):
+	if isinstance(name, ServerInfo):
+	    return name
+	if self.byNickname.has_key(name):
+	    return self.byNickname(name)
+	if self.byFilename.has_key(name):
+	    return self.byFilename(name)
+	return None
 
-    def importServerInfo(self, fname, force=1, string=None):
-	"""Import a server descriptor from an external file into the internal
-	   cache.  Return 1 on import; 0 on failure."""
-	self.load()
-	if string is None:
-	    f = open(fname)
-	    contents = f.read()
-	    f.close()
-	else:
-	    assert fname is None
-	    contents = string
-	info = ServerInfo(string=contents, assumeValid=0)
-	now = time.time()
-	if info['Server']['Valid-Until'] < now:
-	    getLog().error("Not importing descriptor %s: already expired", 
-			   fname)
-	    return 0
-	nickname = info['Server']['Nickname']
-	identity_pk = info['Server']['Identity'].get_public_key()
-	if self.servers.has_key(nickname):
-	    other = self.servers[nickname][0]
-	    if other['Server']['Identity'].get_public_key() != identity_pk:
-		getLog().error("Possible spoofing: that's not the public key I remember for %s", nickname)
-		if not force:
-		    getLog().error("I'm not going to import it.")
-		    return 0
+    def getPath(self, minLength, serverList):
+	path = []
+	for s in serverList:
+	    if isinstance(s, ServerInfo):
+		path.append(s)
+	    elif isinstance(s, types.StringType):
+		server = self.dirCache.getServer(s)
+		if server is not None:
+		    path.append(server)
+		elif os.path.exists(s):
+		    try:
+			server = ServerInfo(fname=s, assumeValid=0)
+			path.append(server)
+		    except OSError, e:
+			getLog().error("Couldn't read descriptor %s: %s", 
+				       s, e)
+			sys.exit(1)
+		    except ConfigError, e:
+			getLog().error("Couldn't parse descriptor %s: %s", 
+				       s, e)
+			sys.exit(1)
 		else:
-		    getLog().error("... importing anyway.")
-	
-	    for other in self.servers[nickname]:
-		if other['Server']['Digest'] == info['Server']['Digest']:
-		    getLog().warn("Duplicate server info; skipping")
-		    return 0
-
-	    self.servers[nickname].append(info)
-	else:
-	    self.servers[nickname] = [ info ]
-
-	self.allServers.append(info)
-
-	self.highest_num += 1
-	fname_new = "si%d" % self.highest_num
-	fd = os.open(os.path.join(self.dirname, fname_new), 
-		     os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0600)
-	f = os.fdopen(fd, 'w')
-	f.write(contents)
-	f.close()
-
-	return 1
+		    getLog().error("Couldn't find descriptor %s")
+		    sys.exit(1)
+	return path
 
+    def getRandomServers(self, prng, n):
+	vals = self.byNickname.values()
+	if len(vals) < n:
+	    raise MixFatalError("Not enough servers (%s requested)", n)
+	return prng.shuffle(vals, n)
+	
 def installDefaultConfig(fname):
     """Create a default, 'fail-safe' configuration in a given file"""
     getLog().warn("No configuration file found. Installing default file in %s",
@@ -234,130 +175,34 @@
 	userdir = self.config['User']['UserDir']
 	createPrivateDir(userdir)
 	createPrivateDir(os.path.join(userdir, 'surbs'))
+	createPrivateDir(os.path.join(userdir, 'servers'))
 
-	# Get directory cache FFFF (not used yet!)
-	self.dirCache = DirectoryCache(os.path.join(userdir, 
-						    'directory', 'servers'))
-	self.dirCache.load()
+	# Get directory cache
+	self.dirCache = TrivialDirectoryCache(
+	    os.path.join(userdir,"servers"))
 
 	# 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)
-
-	# XXXX Pick only servers that relay to all other servers!
-	# XXXX Watch out for many servers with the same IP or nickname or...
-
-	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(self.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, address, payload, 
-			   serverList=None):
-	message, firstHop = self.generateForwardMessage(address,
-							payload,
-							serverList)
-	self.sendMessages([message], firstHop)
+    def sendForwardMessage(self, address, payload, path1, path2):
+	message, firstHop = \
+		 self.generateForwardMessage(address, payload, path1, path2)
 
-    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:]
+    def generateForwardMessage(self, address, payload, path1, path2):
+	servers1 = self.dirCache.getPath(path1)
+	servers2 = self.dirCache.getPath(path2)
+	# XXXXencode payloadXXXX
 	
 	routingType, routingInfo, lastHop = address.getRouting()
 	if lastHop != None:
-	    servers2.append(self.dirCache.getCurrentServer(lastHop))
-	msg = mixminion.BuildMessage.buildForwardMessage(payload,
-							 routingType, 
-							 routingInfo,
-							 servers1, servers2)
+	    servers2.append(self.dirCache.getServerInfo(lastHop))
+	msg = 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 = mixminion.Crypto.getBytes(16)
-	rt, ri, lastHop = address.getRouting("RTRN"+handle)
-	if lastHop is not None:
-	    path.append(lastHop)
-	block, secrets = mixminion.BuildMessage.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(),
@@ -369,10 +214,15 @@
 	finally:
 	    con.shutdown()
 
+def parseAddress():
+
 class Address:
-    def getRouting(self, tag=None):
-	# Return rt, ri, lasthop
-	raise NotImplemented("Address.getRouting()")
+    def __init__(self, exitType, exitAddress, lastHop=None):
+	self.exitType = exitType
+	self.exitAddress = exitAddress
+	self.lastHop = lastHop
+    def getRouting(self):
+	return self.exitType, self.exitAddress, self.lastHop
 
 def sendTestMessage(servers1, servers2):
     assert len(servers1)
@@ -384,9 +234,8 @@
 	   Here.
 	   """
     rt, ri = 0xFFFE, "deliver"
-    m = mixminion.BuildMessage.buildForwardMessage(payload,
-						   rt, ri,
-						   servers1, servers2)
+    m = buildForwardMessage(payload, rt, ri, servers1, servers2)
+			    
     firstHop = servers1[0]
     b = mixminion.MMTPClient.BlockingClientConnection(firstHop.getAddr(),
 						      firstHop.getPort(),
@@ -395,7 +244,6 @@
     b.sendPacket(m)
     b.shutdown()
 
-
 def readConfigFile(configFile):
     try:
 	return ClientConfig(fname=configFile)
@@ -431,5 +279,3 @@
     idx = floorDiv(len(servers),2)
 
     sendTestMessage(servers[:idx], servers[idx:])
-
-