[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Directory work: towards automated keygen and automated ...
Update of /home/minion/cvsroot/src/minion/lib/mixminion/directory
In directory moria.mit.edu:/tmp/cvs-serv26382/lib/mixminion/directory
Modified Files:
DirMain.py ServerList.py
Log Message:
Directory work: towards automated keygen and automated directory generation.
ServerList:
- Remember server identities longterm
- New code to receive and process an incoming server.
- Add an 'incoming' directory and functions to manipulate/check/add incoming
servers.
- Include a list of recommended servers with each directory.
ServerKeys, ServerMain:
- Generate keys automatically, in advance.
- Refactor
- Remember which keysets are published (currently, none).
DirMain:
- Always clean directory before generating a new one.
ServerInfo:
- Add 'Recommended-Servers' to directory
- Bump directory version to 0.2.
test.py:
- Make pychecker happy
- Adapt to new behaviors
Index: DirMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/directory/DirMain.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- DirMain.py 7 Jan 2003 04:49:11 -0000 1.8
+++ DirMain.py 23 May 2003 07:54:11 -0000 1.9
@@ -63,6 +63,7 @@
def cmd_generate(cmd, base, rest):
if len(rest) != 0: usageAndExit(cmd)
lst = ServerList(base)
+ lst.clean()
key = getIdentity(base)
# XXXX Until we have support for automatic directory generation, we
# XXXX set the validity time to be pretty long: 2 months.
Index: ServerList.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/directory/ServerList.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -d -r1.15 -r1.16
--- ServerList.py 20 Feb 2003 06:38:29 -0000 1.15
+++ ServerList.py 23 May 2003 07:54:11 -0000 1.16
@@ -17,14 +17,17 @@
import os
import time
-
+import threading
import mixminion
-from mixminion.Crypto import pk_encode_public_key, pk_same_public_key
+from mixminion.Crypto import pk_decode_public_key, pk_encode_public_key, \
+ pk_same_public_key
from mixminion.Common import IntervalSet, LOG, MixError, UIError, \
createPrivateDir, \
- formatBase64, formatDate, formatFnameTime, formatTime, openUnique, \
- previousMidnight, readPossiblyGzippedFile, stringContains
+ formatBase64, formatDate, formatFnameTime, formatTime, Lockfile, \
+ openUnique, \
+ previousMidnight, readPickled, readPossiblyGzippedFile, stringContains, \
+ writePickled
from mixminion.Config import ConfigError
from mixminion.ServerInfo import ServerDirectory, ServerInfo, \
_getDirectoryDigestImpl
@@ -59,10 +62,16 @@
# rejectDir: Directory where we store invalid descriptors.
# archiveDir: Directory where we store old descriptors
# servers: Map from filename within <serverDir> to ServerInfo objects.
+ # serverIDs: Map from lowercased server nickname to serverID.
# serversByNickname: A map from lowercased server nickname to
# lists of filenames within <serverDir>
##Layout:
# basedir
+ # server-ids/
+ # nickname-dateinserted
+ # (Pickled: ("V0", (nickname, encoded public key)))
+ # incoming/
+ # nickname-dateinserted.N ...
# servers/
# nickname-dateinserted.N ...
# archive/
@@ -73,166 +82,231 @@
# dirArchive/
# dir-dategenerated.N ...
# identity
+ # .lock
def __init__(self, baseDir):
"""Initialize a ServerList to store servers under baseDir/servers,
creating directories as needed.
"""
self.baseDir = baseDir
+ self.serverIDDir = os.path.join(self.baseDir, "server-ids")
self.serverDir = os.path.join(self.baseDir, "servers")
+ self.incomingDir = os.path.join(self.baseDir, "incoming")
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.lockfile = Lockfile(os.path.join(self.baseDir, ".lock"))
+ self.rlock = threading.RLock()
self.servers = {}
self.serversByNickname = {}
+ createPrivateDir(self.serverIDDir)
createPrivateDir(self.serverDir)
createPrivateDir(self.rejectDir)
createPrivateDir(self.archiveDir)
createPrivateDir(self.dirArchiveDir)
self.rescan()
- def importServerInfo(self, server, knownOnly=0):
+
+ def isServerKnown(self, server):
+ """Return true iff the current server descriptor is known. Raises
+ MixError if we have a server descriptor with this name, but
+ a different key."""
+ try:
+ self._lock()
+ nickname = server.getNickname()
+ lcnickname = nickname.lower()
+ try:
+ oldIdentity = self.serverIDs[lcnickname]
+ except KeyError:
+ return 0
+
+ newIdentity = server.getIdentity()
+ if not pk_same_public_key(newIdentity, oldIdentity):
+ raise UIError("Already know a server named %r with a different identity key." % nickname)
+
+ return 1
+ finally:
+ self._unlock()
+
+ def learnServerID(self, server):
+ """DOCDOC"""
+ try:
+ self._lock()
+ nickname = server.getNickname()
+ LOG.info("Learning identity for new server %s", nickname)
+ ident = server.getIdentity()
+ writePickled(os.path.join(self.serverIDDir,
+ nickname+"-"+formatFnameTime()),
+ ("V0", (nickname, pk_encode_public_key(ident))))
+
+ self.serverIDs[nickname.lower()] = ident
+ finally:
+ self._unlock()
+
+ def importServerInfo(self, contents, knownOnly=0, server=None):
"""Insert a ServerInfo into the list. If the server is expired, or
superseded, or inconsistent, raise a MixError.
- server -- a string containing the descriptor, or the name of a
+ contents -- a string containing the descriptor, or the name of a
file containing the descriptor (possibly gzip'd)
knownOnly -- if true, raise MixError is we don't already have
a descriptor with this nickname.
+
+ DOCDOC
"""
# Raises ConfigError, MixError,
- if stringContains(server, "[Server]"):
- contents = server
- else:
- contents = readPossiblyGzippedFile(server)
- server = ServerInfo(string=contents, assumeValid=0)
+ if not server:
+ contents, server = _readServer(contents)
+ try:
+ self._lock()
- nickname = server.getNickname()
- lcnickname = nickname.lower()
- if knownOnly and not self.serversByNickname.has_key(lcnickname):
- raise UIError("Unknown server %s: use import-new."%nickname)
+ nickname = server.getNickname()
+ lcnickname = nickname.lower()
- # Is the server already invalid?
- if server.isExpiredAt(time.time()):
- raise UIError("Descriptor has already expired")
+ known = self.isServerKnown(server)
+ if knownOnly and not known:
+ raise UIError("Unknown server %s: use import-new."%nickname)
- # Is there already a server with the same nickname?
- if self.serversByNickname.has_key(lcnickname):
- # Make sure the identity key is the same.
- oldServer = self.servers[self.serversByNickname[lcnickname][0]]
- oldIdentity = oldServer.getIdentity()
- newIdentity = server.getIdentity()
- if not pk_same_public_key(newIdentity, oldIdentity):
- raise UIError("Identity key has changed for %r" % nickname)
- # Okay -- make sure we don't have this same descriptor.
- for fn in self.serversByNickname[lcnickname]:
- oldServer = self.servers[fn]
- if oldServer['Server']['Digest'] == server['Server']['Digest']:
- raise UIError("Server descriptor already inserted.")
- # Okay -- make sure that this server isn't superseded.
- if server.isSupersededBy(
- [ self.servers[fn] for fn in self.serversByNickname[lcnickname]]):
- raise UIError("Server descriptor is superseded")
+ # Is the server already invalid?
+ if server.isExpiredAt(time.time()):
+ raise UIError("Descriptor has already expired")
- newFile = nickname+"-"+formatFnameTime()
- f, newFile = openUnique(os.path.join(self.serverDir, newFile))
- newFile = os.path.split(newFile)[1]
- f.write(contents)
- f.close()
+ # Is there already a server with the same nickname?
+ if self.serversByNickname.has_key(lcnickname):
+ # Okay -- make sure we don't have this same descriptor.
+ for fn in self.serversByNickname[lcnickname]:
+ oldServer = self.servers[fn]
+ if oldServer['Server']['Digest'] == server['Server']['Digest']:
+ raise UIError("Server descriptor already inserted.")
+ # Okay -- make sure that this server isn't superseded.
+ if server.isSupersededBy(
+ [ self.servers[fn] for fn in self.serversByNickname[lcnickname]]):
+ raise UIError("Server descriptor is superseded")
- # Now update the internal structure
- self.servers[newFile] = server
- self.serversByNickname.setdefault(lcnickname, []).append(newFile)
+ if not known:
+ # Is the identity new to us?
+ self.learnServerID(server)
+
+ newFile = _writeServer(self.serverDir, contents, nickname)
+
+ # Now update the internal structure
+ self.servers[newFile] = server
+ self.serversByNickname.setdefault(lcnickname, []).append(newFile)
+ finally:
+ self._unlock()
def expungeServersByNickname(self, nickname):
"""Forcibly remove all servers named <nickname>"""
- LOG.info("Removing all servers named %s", nickname)
- lcnickname = nickname.lower()
- if not self.serversByNickname.has_key(lcnickname):
- LOG.info(" (No such servers exist)")
- return
- servers = self.serversByNickname[lcnickname]
- for fn in servers:
- LOG.info(" Removing %s", fn)
- os.rename(os.path.join(self.serverDir, fn),
- os.path.join(self.archiveDir, fn))
- del self.servers[fn]
- del self.serversByNickname[lcnickname]
- LOG.info(" (%s servers removed)", len(servers))
-
+ try:
+ self._lock()
+ LOG.info("Removing all servers named %s", nickname)
+ lcnickname = nickname.lower()
+ if not self.serversByNickname.has_key(lcnickname):
+ LOG.info(" (No such servers exist)")
+ return
+ servers = self.serversByNickname[lcnickname]
+ for fn in servers:
+ LOG.info(" Removing %s", fn)
+ os.rename(os.path.join(self.serverDir, fn),
+ os.path.join(self.archiveDir, fn))
+ del self.servers[fn]
+ del self.serversByNickname[lcnickname]
+ LOG.info(" (%s servers removed)", len(servers))
+ finally:
+ self._unlock()
+
def generateDirectory(self,
startAt, endAt, extraTime,
- identityKey, publicationTime=None):
+ identityKey,
+ publicationTime=None,
+ goodServers=None):
"""Generate and sign a new directory, to be effective from <startAt>
through <endAt>. It includes all servers that are valid at
any time between <startAt> and <endAt>+>extraTime>. The directory
is signed with <identityKey> """
- if publicationTime is None:
- publicationTime = time.time()
- if previousMidnight(startAt) >= previousMidnight(endAt):
- raise MixError("Validity range does not contain a full day.")
- included = []
- for fn, s in self.servers.items():
- if not s.isValidAtPartOf(startAt, endAt+extraTime):
- continue
- nickname = s.getNickname()
- validAfter = s['Server']['Valid-After']
- included.append((nickname, validAfter, fn))
+ try:
+ self._lock()
+ self.clean()
+ if publicationTime is None:
+ publicationTime = time.time()
+ if previousMidnight(startAt) >= previousMidnight(endAt):
+ raise MixError("Validity range does not contain a full day.")
+ included = []
+ for fn, s in self.servers.items():
+ if not s.isValidAtPartOf(startAt, endAt+extraTime):
+ continue
+ nickname = s.getNickname()
+ validAfter = s['Server']['Valid-After']
+ 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()
+ 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:
- [Recommended-Software]
- MixminionClient: %s
- MixminionServer: %s
- """ % (formatTime(publicationTime),
- formatDate(startAt),
- formatDate(endAt),
- formatBase64(pk_encode_public_key(identityKey)),
- ACCEPTABLE_CLIENT_VERSIONS,
- ACCEPTABLE_SERVER_VERSIONS)
+ if goodServers is None:
+ goodServers = [n for n,_,_ in included]
+ else:
+ goodServers = [n for n,_,_ in included if n in goodServers]
+ g = {}
+ for n in goodServers: g[n]=1
+ goodServers = g.keys()
+ goodServers.sort()
+ goodServers = ", ".join(goodServers)
- directory = header+"".join(contents)
- directory = _getDirectoryDigestImpl(directory, identityKey)
+ #FFFF Support for multiple signatures
+ header = """\
+ [Directory]
+ Version: 0.2
+ Published: %s
+ Valid-After: %s
+ Valid-Until: %s
+ Recommended-Servers: %s
+ [Signature]
+ DirectoryIdentity: %s
+ DirectoryDigest:
+ DirectorySignature:
+ [Recommended-Software]
+ MixminionClient: %s
+ MixminionServer: %s
+ """ % (formatTime(publicationTime),
+ formatDate(startAt),
+ formatDate(endAt),
+ goodServers,
+ formatBase64(pk_encode_public_key(identityKey)),
+ ACCEPTABLE_CLIENT_VERSIONS,
+ ACCEPTABLE_SERVER_VERSIONS)
- # Make sure that the directory checks out
- # FFFF remove this once we are _very_ confident.
- if 1:
- parsed = ServerDirectory(string=directory)
- includedDigests = {}
- for _, _, fn in included:
- includedDigests[self.servers[fn]['Server']['Digest']] = 1
- foundDigests = {}
- for s in parsed.getServers():
- foundDigests[s['Server']['Digest']] = 1
- assert foundDigests == includedDigests
+ directory = header+"".join(contents)
+ directory = _getDirectoryDigestImpl(directory, identityKey)
- f = open(os.path.join(self.baseDir, "directory"), 'w')
- f.write(directory)
- f.close()
+ # Make sure that the directory checks out
+ # FFFF remove this once we are _very_ confident.
+ if 1:
+ parsed = ServerDirectory(string=directory)
+ includedDigests = {}
+ for _, _, fn in included:
+ includedDigests[self.servers[fn]['Server']['Digest']] = 1
+ foundDigests = {}
+ for s in parsed.getServers():
+ foundDigests[s['Server']['Digest']] = 1
+ assert foundDigests == includedDigests
- f, _ = openUnique(os.path.join(self.dirArchiveDir,
- "dir-"+formatFnameTime()))
- f.write(directory)
- f.close()
+ f = open(os.path.join(self.baseDir, "directory"), 'w')
+ f.write(directory)
+ f.close()
+ f, _ = openUnique(os.path.join(self.dirArchiveDir,
+ "dir-"+formatFnameTime()))
+ f.write(directory)
+ f.close()
+ finally:
+ self._unlock()
+
def getDirectoryFilename(self):
"""Return the filename of the most recently generated directory"""
return os.path.join(self.baseDir, "directory")
@@ -245,74 +319,273 @@
if now is None:
now = time.time()
- removed = {} # Map from filename->whyRemoved
- # Find all superseded servers
- for servers in self.serversByNickname.values():
- servers = [ (self.servers[fn]['Server']['Published'],
- fn, self.servers[fn]) for fn in servers ]
- servers.sort()
- fns = [ fn for _, fn, _ in servers]
- servers = [ s for _, _, s in servers ]
- for idx in range(len(servers)):
- if servers[idx].isSupersededBy(servers[idx+1:]):
- removed[fns[idx]] = "superceded"
-
- # Find all expired servers.
- for fn, s in self.servers.items():
- if removed.has_key(fn):
- continue
- if s.isExpiredAt(now-6000):
- # The descriptor is expired.
- removed[fn] = "expired"
-
- # This is a kinda nasty hack: we never remove the last descriptor for
- # a given nickname. If we did, we would lose track of the server's
- # identity key.
- for name, fns in self.serversByNickname.items():
- nRemoved = len([fn for fn in fns if removed.has_key(fn)])
- if nRemoved < len(fns):
- continue
- # We're about to remove all the descriptors--that's bad!
- # We find the most recent one, and remove it from but
- servers = [ (self.servers[fn]['Server']['Published'],
- fn, self.servers[fn]) for fn in fns ]
- servers.sort()
- fn = servers[-1][1]
- LOG.info("Retaining %s descriptor %s -- it's the last one for %s",
- removed[fn], fn, name)
- del removed[fn]
+ try:
+ self._lock()
+ removed = {} # Map from filename->whyRemoved
+ # Find all superseded servers
+ for servers in self.serversByNickname.values():
+ servers = [ (self.servers[fn]['Server']['Published'],
+ fn, self.servers[fn]) for fn in servers ]
+ servers.sort()
+ fns = [ fn for _, fn, _ in servers]
+ servers = [ s for _, _, s in servers ]
+ for idx in range(len(servers)):
+ if servers[idx].isSupersededBy(servers[idx+1:]):
+ removed[fns[idx]] = "superceded"
- # Now, do the actual removing.
- for fn, why in removed.items():
- LOG.info("Removing %s descriptor %s", why, fn)
- os.rename(os.path.join(self.serverDir, fn),
- os.path.join(self.archiveDir, fn))
+ # Find all expired servers.
+ for fn, s in self.servers.items():
+ if removed.has_key(fn):
+ continue
+ if s.isExpiredAt(now-6000):
+ # The descriptor is expired.
+ removed[fn] = "expired"
- del self.servers[fn]
+ # Now, do the actual removing.
+ for fn, why in removed.items():
+ LOG.info("Removing %s descriptor %s", why, fn)
+ os.rename(os.path.join(self.serverDir, fn),
+ os.path.join(self.archiveDir, fn))
- self.__buildNicknameMap()
+ del self.servers[fn]
+ self.__buildNicknameMap()
+ finally:
+ self._unlock()
+
def rescan(self):
"""Reconstruct this ServerList object's internal state."""
- self.servers = {}
- # First, build 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))
+ try:
+ self._lock()
+ # First, build self.servers
+ 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))
- # Then, rebuild self.serversByNickname
- self.__buildNicknameMap()
+ # Next, rebuild self.serverIDs:
+ self.serverIDs = {}
+ for filename in os.listdir(self.serverIDDir):
+ path = os.path.join(self.serverIDDir, filename)
+ t = readPickled(path)
+ if t[0] != 'V0':
+ LOG.warn("Skipping confusing stored key in file %s", filename)
+ continue
+ nickname, key = t[1]
+ self.serverIDs[nickname.lower()] = pk_decode_public_key(key)
+ # (check for consistency)
+ for s in self.servers.values():
+ lcn = s.getNickname().lower()
+ try:
+ ident = self.serverIDs[lcn]
+ except KeyError:
+ raise UIError("No stored key for server %s", s.getNickname())
+
+ if not pk_same_public_key(ident, s.getIdentity()):
+ raise UIError("Inconsistent stored key for server %s",
+ s.getNickname())
+
+ # Then, rebuild self.serversByNickname
+ self.__buildNicknameMap()
+ finally:
+ self._unlock()
+
def __buildNicknameMap(self):
"""Helper method. Regenerate self.serversByNickname from
- self.servers"""
+ self.servers
+
+ Caller must hold lock."""
self.serversByNickname = {}
for fn, server in self.servers.items():
nickname = server.getNickname()
self.serversByNickname.setdefault(nickname.lower(), []).append(fn)
+
+ def _lock(self):
+ self.rlock.acquire()
+ self.lockfile.acquire()
+
+ def _unlock(self):
+ self.lockfile.release()
+ self.rlock.release()
+
+class ServerQueue:
+ """DOCDOC"""
+ def __init__(self, incomingDir):
+ """DOCDOC"""
+ self.incomingDir = incomingDir
+ createPrivateDir(self.incomingDir)
+
+ def queueIncomingServer(self, contents, server):
+ """DOCDOC"""
+ nickname = server.getNickname()
+ _writeServer(nickname, contents, self.incomingDir)
+
+ def serversPending(self):
+ """DOCDOC"""
+ return len(os.listdir(self.incomingDir)) > 0
+
+ def readPendingServers(self):
+ res = []
+ for fname in os.listdir(self.incomingDir):
+ path = os.path.join(self.incomingDir,fname)
+ try:
+ text, server = _readServer(path)
+ except (ConfigError, MixError),e:
+ os.unlink(path)
+ LOG.warn("Removed a bad server descriptor %s from incoming dir: %s",
+ fname, e)
+
+ res.append((fname, server, text, getIDFingerprint(server)))
+ return res
+
+ def delPendingServers(self, fnames):
+ for fname in fnames:
+ try:
+ os.path.unlink(os.path.join(self.incomingDir, fname))
+ except OSError:
+ LOG.warn("delPendingServers: no such server %s"%fname)
+
+class ServerQueuedException(Exception):
+ """DOCDOC"""
+ pass
+
+def receiveServer(serverList, incomingQueue, text, source):
+ """DOCDOC
+
+ Returns true on OK; raises UIError on failure; raises ServerQueued on
+ wait-for-admin."""
+ try:
+ text, server = _readServer(text)
+ except MixError, e:
+ LOG.warn("Rejected invalid server from %s: %s", source,e)
+ raise UIError("Server descriptor was not valid: %s"%e)
+
+ try:
+ known = serverList.isServerKnown(server)
+ except MixError, e:
+ LOG.warn("Rejected server with mismatched identity from %s", source)
+ raise
+
+ if not known:
+ LOG.info("Received previously unknown server from %s", source)
+ incomingQueue.queueIncomingServer(text,server)
+ raise ServerQueuedException()
+
+ serverList.importServerInfo(text, knownOnly=1, server=server)
+
+def acceptServer(serverList, incomingQueue, nickname):
+ if ':' in nickname:
+ nickname, fingerprint = nickname.split(":")
+ else:
+ fingerprint = None
+
+ lcnickname = nickname.lower()
+ incoming = incomingQueue.readPendingServers()
+ # Do we have any pending servers of the desired name?
+ incoming = [ (fname,server,text,fp)
+ for fname,server,text,fp in incoming
+ if server.getNickname().lower() == lcnickname ]
+ if not incoming:
+ raise UIError("No incoming servers named %s"%nickname)
+
+ if not fingerprint:
+ fps = [fp for f,s,t,fp in incoming]
+ for f in fps:
+ if f != fps[0]:
+ raise UIError("Multiple KeyIDs for servers names %s"%nickname)
+ reject = []
+ else:
+ reject = [ (f,s,t,fp) for f,s,t,fp in incoming
+ if fp != fingerprint ]
+ incoming = [ (f,s,t,fp) for f,s,t,fp in incoming
+ if fp == fingerprint ]
+ if not incoming:
+ raise UIError("No servers named %s with matching KeyID"%nickname)
+ if reject:
+ LOG.warn("Rejecting %s server(s) named %s with unmatching KeyIDs",
+ len(reject), nickname)
+
+ try:
+ serverList._lock()
+ serverList.learnServerID(incoming[0][1])
+ accepted = []
+ for fname,server,text,fp in incoming:
+ try:
+ serverList.importServerInfo(text,server=server)
+ accepted.append(fname)
+ except MixError, e:
+ LOG.warn("ServerList refused to include server %s: %s",
+ fname, e)
+ reject.append((fname,server,text,fp))
+
+ if len(accepted):
+ LOG.warn("Key ID learned; no servers actually imported")
+ else:
+ LOG.info("%s server descriptor(s) imported.", len(accepted))
+
+ LOG.info("Moving %s descriptors to rejected status"%len(reject))
+ for fname,_,text,_ in reject:
+ _writeServer(serverList.rejectDir, text, "BAD-"+nickname)
+
+ incomingQueue.delPendingServers([fname for fname,_,text,_ in reject])
+ incomingQueue.delPendingServers(accepted)
+ finally:
+ serverList._unlock()
+
+def listPendingServers(f, incomingQueue):
+ """DOCDOC"""
+ incoming = incomingQueue.readPendingServers()
+ # lcnickname->fp->servers
+ servers = {}
+ # lcnickname->nicknames
+ nicknames = {}
+
+ for f,s,t,fp in incoming:
+ nickname = s.getNickname()
+ lcnickname = nickname.lower()
+ nicknames.setdefault(lcnickname, []).append(nickname)
+ servers.setdefault(lcnickname, {}).setdefault(fp, []).append(s)
+
+ sorted = nicknames.keys()
+ sorted.sort()
+ maxlen = max([len(n) for n in sorted])
+ format = " %"+str(maxlen)+"s:%s [%s descriptors]"
+ for lcnickname in sorted:
+ nickname = nicknames[lcnickname][0]
+ ss = servers[lcnickname]
+ if len(ss) > 1:
+ print >>f, ("***** MULTIPLE KEYIDS FOR %s:"%nickname)
+ for fp, s in ss:
+ print >>f, (format%(nickname,fp,len(s)))
+
+def _writeServer(directory, contents, nickname):
+ newFile = nickname+"-"+formatFnameTime()
+ f, newFile = openUnique(os.path.join(directory, "newFile"))
+ newFile = os.path.split(newFile)[1]
+ f.write(contents)
+ f.close()
+ return newFile
+
+def _readServer(contents):
+ if stringContains(contents, "[Server]"):
+ pass
+ else:
+ contents = readPossiblyGzippedFile(contents)
+
+ # May raise ConfigError, MixError
+ return contents, ServerInfo(string=contents, assumeValid=0)
+
+def getIDFingerprint(server):
+ """DOCDOC"""
+ ident = server.getIdentity()
+ return formatBase64(
+ mixminion.Crypto.sha1(
+ mixminion.Crypto.pk_encode_public_key(ident)))
+