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

[minion-cvs] Add first cut of directory backend



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

Added Files:
	ServerList.py __init__.py 
Log Message:
Add first cut of directory backend

--- NEW FILE: ServerList.py ---
# Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
# $Id: ServerList.py,v 1.1 2002/12/31 04:33:25 nickm Exp $

"""mixminion.directory.ServerList

   Implements a store of sererinfos for a diectory.
   
   FFFF Right now, this is about maximally slow.  There are a lot of tricks
   FFFF we could do to speed it up: not revalidating servers in our cache;
   FFFF pickling serverinfo objects for easy access, and so on.  But
   FFFF really, we'd need to get 1000 servers before any of these tricks made
   FFFF more than a 10-second difference in directory generation time, so
   FFFF let's leave it simple for now.
   """

__all__ = [ 'ServerList' ]

import os
import time

from mixminion.Crypto import pk_encode_public_key, pk_same_public_key
from mixminion.Common import IntervalSet, LOG, MixError, createPrivateDir, \
     formatBase64, formatDate, formatFnameTime, formatTime, stringContains
from mixminion.Config import ConfigError
from mixminion.ServerInfo import ServerInfo, _getDirectoryDigestImpl

# Layout:
#  basedir
#     servers/
#       nickname-dateinserted.
#     archive/
#     reject/
#     directory
#     dirArchive/

class ServerList:
    "DOCDOC"
    ##Fields: DOCDOC
    #  baseDir
    #  serverDir
    #  servers (filename->ServerInfo)
    #  serversByNickname (nickname -> [filename, filename,...])
    def __init__(self, baseDir):
        "DOCDOC"
        self.baseDir = baseDir
        self.serverDir = os.path.join(self.baseDir, "servers")
        self.rejectDir = os.path.join(self.baseDir, "reject")
        self.archiveDir = os.path.join(self.baseDir, "archive")
        self.dirArchiveDir = os.path.join(self.baseDir, "dirArchive")
        self.servers = {}
        self.serversByNickname = {}
        createPrivateDir(self.serverDir)
        createPrivateDir(self.rejectDir)
        createPrivateDir(self.archiveDir)
        createPrivateDir(self.dirArchiveDir)
        self.rescan()
        
    def importServerInfo(self, server):
        "DOCDOC"
        # Raises ConfigError, MixError, 
        if stringContains(server, "[Server]"):
            contents = server
        else:
            f = open(server, 'r')
            contents = f.read()
            f.close()

        server = ServerInfo(string=contents, assumeValid=0)

        nickname = server.getNickname()
        validUntil = server['Server']['Valid-Until']
        # Is the server already invalid?
        if validUntil < time.time():
            raise MixError("Descriptor has already expired")

        # Is there already a server with the same nickname?
        if self.serversByNickname.has_key(nickname):
            # Make sure the identity key is the same.
            oldServer = self.servers[self.serversByNickname[nickname][0]]
            oldIdentity = oldServer['Server']['Identity']
            newIdentity = server['Server']['Identity']
            if not pk_same_public_key(newIdentity, oldIdentity):
                raise MixError("Identity key has changed for %r" % nickname)
            # Okay -- make sure we don't have this same descriptor.
            for fn in self.serversByNickname[nickname]:
                oldServer = self.servers[fn]
                if oldServer['Server']['Digest'] == server['Server']['Digest']:
                    raise MixError("Server descriptor already inserted.")

            newFile = nickname+"-"+formatFnameTime()
            if os.path.exists(os.path.join(self.serverDir, newFile)):
                idx = 1
                # XXXX This is race-prone if we try to run many insert
                # XXXX processes at once.
                while os.path.exists(os.path.join(self.serverDir, 
                                                  "%s.%s"%(newFile,idx))):
                    idx += 1
                newFile = "%s.%s" %(newFile,idx)

            f = open(os.path.join(self.serverDir, newFile), 'w')
            f.write(contents)
            f.close()

            # Now update the internal structure
            self.servers[newFile] = server
            self.serversByNickname.setdefault(nickname, []).append(server)

    def generateDirectory(self,
                          #XXXX two of the next 4 args are redundant... which?
                          startAt, endAt, 
                          dirValidAfter, dirValidUntil, 
                          now,
                          identityKey):
        "DOCDOC"
        included = []
        for fn, s in self.servers.items():
            validAfter = s['Server']['Valid-After']
            validUntil = s['Server']['Valid-Until']
            if validUntil < startAt or endAt < validAfter:
                continue
            nickname = s.getNickname()
            included.append((nickname, validAfter, fn))

        included.sort()

        # FFFF We should probably not do all of this in RAM, but what the hey.
        # FFFF It will only matter if we have many, many servers in the system.
        contents = [ ]
        for _, _, fn in included:
            f = open(os.path.join(self.serverDir, fn), 'r')
            contents.append(f.read())
            f.close()
        
        #FFFF Support for multiple signatures
        header = """\
        [Directory]
        Version: 0.1
        Published: %s
        Valid-After: %s
        Valid-Until: %s
        [Signature]
        DirectoryIdentity: %s
        DirectoryDigest:
        DirectorySignature:
        """ % (formatTime(now), formatDate(dirValidAfter), 
               formatDate(dirValidUntil),
               formatBase64(pk_encode_public_key(identityKey)))

        directory = header+"".join(contents)
        directory = _getDirectoryDigestImpl(directory, identityKey)

        for fname in (os.path.join(self.baseDir, "directory"),
                  os.path.join(self.dirArchiveDir, "dir-"+formatFnameTime())):
            f = open(fname, 'w')
            f.write(fname)
            f.close()

    def getDirectoryFilename(self):
        "DOCDOC"
        return os.path.join(self.baseDir, "directory")

    def clean(self, now=None):
        "DOCDOC"
        # A server needs to be cleaned out if it is no longer valid,
        # or if its future validity range is wholly covered by other, more
        # recently published descriptors for the same server.

        # This algorithm is inefficient: O(N_descs * N_descs_per_nickname).
        # We're just going to ignore that.
        if now is None:
            now = time.time()

        removed = {}
        beforeNow = IntervalSet([0, time.time()])
        for name, servers in self.serversByNickname.items():
            valid = {}
            published = {}
            for fn in servers:
                s = self.servers[fn]
                published[fn] = s['Server']['Published']
                validAfter = s['Server']['Valid-After']
                validUntil = s['Server']['Valid-Until']
                valid[fn] = IntervalSet([validAfter, validUntil])
            
            for fn in servers:
                vOrig = valid[fn]
                v = vOrig.copy()
                v -= beforeNow
                p = published[fn]
                s = []
                for fn2 in servers:
                    if published[fn2] <= p:
                        continue
                    if vOrig * valid[fn2]:
                        v -= valid[fn2]
                        s.append(fn2)
                if v.isEmpty():
                    LOG.info("Removing superceded descriptor %s", fn)
                    LOG.info("   (superceded by %s", ",".join(s))
                    removed[fn] = 1

        # This is a kinda nasty hack: we never remove the last server for
        # a given nickname.  If we did, 
        nRemovedByNickname = {}
        for name, fns in self.serversByNickname.items():
            nRemovedByNickname[name] = len(
                [fn for fn in fns if removed.has_key(fn)])
            assert nRemovedByNickname[name] < len(fns)

        for fn, s in self.servers.items():
            if removed.has_key(fn):
                continue
            if s['Server']['Valid-Until'] < now - 6000:
                # Don't remove the last key for a nickname.
                name = s.getNickname()
                if (nRemovedByNickname[name] + 1 == 
                               len(self.serversByNickname[name])):
                    continue
                
                LOG.info("Removing expired descriptor %s", fn)
                removed[fn] = 1
        
        for fn in removed.keys():
            os.rename(os.path.join(self.serverDir, fn),
                      os.path.join(self.archiveDir, fn))
        
            del self.servers[fn]

        self.__buildNicknameMap()        
                    
    def rescan(self):
        "DOCDOC"
        self.servers = {}
        for filename in os.listdir(self.serverDir):
            path = os.path.join(self.serverDir, filename)
            try:
                self.servers[filename] = ServerInfo(fname=path)
            except ConfigError, e:
                LOG.warn("Somehow, a bad server named %s got into our store",
                         filename)
                LOG.warn(" (Error was: %s)", str(e))
                os.rename(path, os.path.join(self.rejectDir, filename))

        self.__buildNicknameMap()

    def __buildNicknameMap(self):
        self.serversByNickname = {}
        for fn, server in self.servers.items():
            nickname = server.getNickname()
            self.serversByNickname.setDefault(nickname, []).append(fn)
        

--- NEW FILE: __init__.py ---
# Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
# $Id: __init__.py,v 1.1 2002/12/31 04:33:25 nickm Exp $

"""mixminion.server

   Directory server code for type III anonymous remailers.
   """

__all__ = [ ]