[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Commit changes before moving on to paper.
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv6099/lib/mixminion
Modified Files:
ClientMain.py Common.py Config.py ServerInfo.py ServerMain.py
test.py
Log Message:
Commit changes before moving on to paper.
ClientMain: debug keystore
Common: remove dead code
Config,ServerInfo: Change date format from DD/MM/YYYY (which confuses many
USians and maybe others too) into YYYY/MM/DD (which seems to be unambiguous
for everyone)
ServerInfo: Guess IP address; don't default to 0.0.0.0
ServerMain: Slight doc improvements
test:
- fix suspendLog code
- test new date format
- Make all serverinfo objects explicitly give an IP.
- Test client keyring code
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- ClientMain.py 13 Oct 2002 01:34:44 -0000 1.4
+++ ClientMain.py 30 Oct 2002 02:19:39 -0000 1.5
@@ -22,7 +22,7 @@
# directories. Each server can have any number of virtual or
# official tags. Users should use the CLI to add/remove entries from
# dir.)
-# - Per-systemm directory location is a neat idea, but individual users
+# - Per-system directory location is a neat idea, but individual users
# must check signature. That's a way better idea for later.
import os
@@ -31,12 +31,13 @@
import time
import bisect
+from mixminion.Common import getLog, floorDiv, createPrivateDir, MixError
import mixminion.Crypto
-from mixminion.Common import getLog, floorDiv, createPrivateDir
-import mixminion.Config
import mixminion.BuildMessage
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.
@@ -86,9 +87,9 @@
if self.servers.has_key(nickname):
self.servers[nickname].append(info)
else:
- self.servers[nickname] = info
+ self.servers[nickname] = [ info ]
- def getCurrentServer(nickname, when=None, until=None):
+ 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
@@ -100,7 +101,7 @@
when = time.time()
if until is None:
until = when+1
- if type(nickname) == ServerInfo:
+ if isinstance(nickname, ServerInfo):
serverList = [ nickname ]
else:
try:
@@ -114,7 +115,7 @@
return info
raise MixError("No time-valid information for server %s"%nickname)
- def getAllCurrentServers(when=None, until=None):
+ def getAllCurrentServers(self, when=None, until=None):
"""Return all ServerInfo objects valid during a given interval."""
self.load()
if when is None:
@@ -129,13 +130,17 @@
result.append(info)
return result
- def importServerInfo(self, fname, force=1):
+ 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()
- f = open(fname)
- contents = f.read()
- f.close()
+ 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:
@@ -154,17 +159,22 @@
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.servers[nickname] = [ info ]
self.allServers.append(info)
self.highest_num += 1
fname_new = "si%d" % self.highest_num
- f = os.fdopen(os.open(os.path.join(self.dirname, fname_name),
- os.O_CREAT|os.O_EXCL, 0600),
- 'w')
+ 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()
@@ -210,7 +220,7 @@
if not os.path.exists(conf):
installDefaultConfig(conf)
conf = os.path.expanduser(conf)
- self.config = mixminion.Config.ClientConfig(fname=conf)
+ self.config = ClientConfig(fname=conf)
getLog().configure(self.config)
getLog().debug("Configuring client")
@@ -243,6 +253,9 @@
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 = []
@@ -382,7 +395,7 @@
def readConfigFile(configFile):
try:
- return mixminion.Config.ClientConfig(fname=configFile)
+ return ClientConfig(fname=configFile)
except (IOError, OSError), e:
print >>sys.stderr, "Error reading configuration file %r:"%configFile
print >>sys.stderr, " ", str(e)
@@ -412,7 +425,7 @@
mixminion.Crypto.init_crypto(config)
if len(args) < 2:
print >> sys.stderr, "I need at least 2 servers"
- servers = [ mixminion.ServerInfo.ServerInfo(fn) for fn in args ]
+ servers = [ ServerInfo(fn) for fn in args ]
idx = floorDiv(len(servers),2)
sendTestMessage(servers[:idx], servers[idx:])
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -d -r1.22 -r1.23
--- Common.py 16 Sep 2002 15:30:02 -0000 1.22
+++ Common.py 30 Oct 2002 02:19:39 -0000 1.23
@@ -371,8 +371,6 @@
# we set the DST flag to zero so that subtracting time.timezone always
# gives us gmt.
- #return time.mktime((yyyy,MM,dd,hh,mm,ss,0,0,0))-time.timezone
-
return calendar.timegm((yyyy,MM,dd,hh,mm,ss,0,0,0))
def previousMidnight(when):
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -d -r1.17 -r1.18
--- Config.py 16 Sep 2002 15:30:02 -0000 1.17
+++ Config.py 30 Oct 2002 02:19:39 -0000 1.18
@@ -250,10 +250,10 @@
raise ConfigError("Invalid exponent on public key")
return key
-_date_re = re.compile(r"(\d\d)/(\d\d)/(\d\d\d\d)")
-_time_re = re.compile(r"(\d\d)/(\d\d)/(\d\d\d\d) (\d\d):(\d\d):(\d\d)")
+_date_re = re.compile(r"(\d\d\d\d)/(\d\d)/(\d\d)")
+_time_re = re.compile(r"(\d\d\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)")
def _parseDate(s,_timeMode=0):
- """Validation function. Converts from DD/MM/YYYY format to a (long)
+ """Validation function. Converts from YYYY/MM/DD format to a (long)
time value for midnight on that date."""
s = s.strip()
r = (_date_re, _time_re)[_timeMode]
@@ -261,9 +261,9 @@
if not m:
raise ConfigError("Invalid %s %r" % (("date", "time")[_timeMode],s))
if _timeMode:
- dd, MM, yyyy, hh, mm, ss = map(int, m.groups())
+ yyyy, MM, dd, hh, mm, ss = map(int, m.groups())
else:
- dd, MM, yyyy = map(int, m.groups())
+ yyyy, MM, dd = map(int, m.groups())
hh, mm, ss = 0, 0, 0
if not ((1 <= dd <= 31) and (1 <= MM <= 12) and
@@ -274,7 +274,7 @@
return mixminion.Common.mkgmtime(yyyy, MM, dd, hh, mm, ss)
def _parseTime(s):
- """Validation function. Converts from DD/MM/YYYY HH:MM:SS format
+ """Validation function. Converts from YYYY/MM/DD HH:MM:SS format
to a (float) time value for GMT."""
return _parseDate(s,1)
@@ -647,9 +647,6 @@
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'],
@@ -678,7 +675,7 @@
"10 minutes",) },
# FFFF Generic multi-port listen/publish options.
'Incoming/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
- 'IP' : ('ALLOW', _parseIP, None),
+ 'IP' : ('ALLOW', _parseIP, "0.0.0.0"),
'Port' : ('ALLOW', _parseInt, "48099"),
'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- ServerInfo.py 10 Sep 2002 14:45:31 -0000 1.14
+++ ServerInfo.py 30 Oct 2002 02:19:39 -0000 1.15
@@ -15,7 +15,7 @@
import base64
import socket
-from mixminion.Common import createPrivateDir, getLog
+from mixminion.Common import createPrivateDir, getLog, MixError
from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
from mixminion.Packet import IPV4Info
import mixminion.Config
@@ -156,7 +156,7 @@
self.descFile = os.path.join(keydir, "ServerDesc")
if not os.path.exists(keydir):
createPrivateDir(keydir)
-
+
def load(self, password=None):
"Read this set of keys from disk."
self.packetKey = mixminion.Crypto.pk_PEM_load(self.packetKeyFile,
@@ -186,14 +186,14 @@
"""Helper function: turns a time (in seconds) into the format used by
Server descriptors"""
gmt = time.gmtime(t)
- return "%02d/%02d/%04d %02d:%02d:%02d" % (
- gmt[2],gmt[1],gmt[0], gmt[3],gmt[4],gmt[5])
+ return "%04d/%02d/%02d %02d:%02d:%02d" % (
+ gmt[0],gmt[1],gmt[2], gmt[3],gmt[4],gmt[5])
def _date(t):
"""Helper function: turns a time (in seconds) into a date in the format
used by server descriptors"""
gmt = time.gmtime(t+1) # Add 1 to make sure we round down.
- return "%02d/%02d/%04d" % (gmt[2],gmt[1],gmt[0])
+ return "%04d/%02d/%02d" % (gmt[0],gmt[1],gmt[2])
def _rule(allow, (ip, mask, portmin, portmax)):
if mask == '0.0.0.0':
@@ -279,6 +279,14 @@
"KeyID":
_base64(serverKeys.getMMTPKeyID()),
}
+
+ if fields['IP'] == '0.0.0.0':
+ try:
+ fields['IP'] = _guessLocalIP()
+ getLog().warn("No IP configured; guessing %s",fields['IP'])
+ except IPGuessError, e:
+ getLog().error("Can't guess IP: %s", str(e))
+ raise MixError()
info = """\
[Server]
@@ -353,7 +361,7 @@
def signServerInfo(info, rsa):
"""Sign a server descriptor. <info> should be a well-formed server
descriptor, with Digest: and Signature: lines present but with
- no values."""
+ no values."""
return _getServerInfoDigestImpl(info, rsa)
def _getServerInfoDigestImpl(info, rsa=None):
@@ -395,3 +403,53 @@
infoLines[signatureLine] = 'Signature: '+signature
return "\n".join(infoLines)
+
+
+class IPGuessError(MixError):
+ pass
+
+_GUESSED_IP = None
+
+def _guessLocalIP():
+ "Try to find a reasonable IP for this host."
+ global _GUESSED_IP
+ if _GUESSED_IP is not None:
+ return _GUESSED_IP
+
+ # First, let's see what our name resolving subsystem says our
+ # name is.
+ ip_set = {}
+ try:
+ ip_set[ socket.gethostbyname(socket.gethostname()) ] = 1
+ except socket.error, host_error:
+ try:
+ ip_by_host = socket.gethostbyname(socket.getfqdn())
+ except socket.error, _:
+ pass
+
+ # And in case that doesn't work, let's see what other addresses we might
+ # think we have by using 'getsockname'.
+ for target_addr in ('18.0.0.1', '10.0.0.1', '192.168.0.1',
+ '172.16.0.1')+tuple(ip_set.keys()):
+ # open a datagram socket so that we don't actually send any packets
+ # by connecting.
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect((target_addr, 9)) #discard port
+ ip_set[ s.getsockname()[0] ] = 1
+ except socket.error, _:
+ pass
+
+ if len(ip_set) == 0:
+ raise IPGuessError("No address found")
+
+ for ip in ip_set.keys():
+ if ip.startswith("127.") or ip.startswith("0."):
+ del ip_set[ip]
+
+ if len(ip_set) > 1:
+ raise IPGuessError("Multiple addresses found: %s" % (
+ ", ".join(ip_set)))
+
+ return ip_set.keys()[0]
+
Index: ServerMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerMain.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- ServerMain.py 16 Sep 2002 15:30:02 -0000 1.10
+++ ServerMain.py 30 Oct 2002 02:19:39 -0000 1.11
@@ -3,7 +3,9 @@
"""mixminion.ServerMain
- The main loop and related functionality for a Mixminion server
+ The main loop and related functionality for a Mixminion server.
+ See the "MixminionServer" class for more information about how it
+ all works.
BUG: No support for encrypting private keys."""
@@ -436,19 +438,32 @@
nextRotate = self.keyring.getNextKeyRotation() # FFFF use this.
while 1:
while time.time() < nextMix:
+ # Handle pending network events
self.mmtpServer.process(1)
+ # Process any new messages that have come in, placing them
+ # into the mix pool.
self.incomingQueue.sendReadyMessages()
+ # Before we mix, we need to log the hashes to avoid replays.
+ # FFFF We need to recover on server failure.
self.packetHandler.syncLogs()
+
getLog().trace("Mix interval elapsed")
+ # Choose a set of outgoing messages; put them in outgoingqueue and
+ # modulemanger
self.mixPool.mix()
+ # Send outgoing messages
self.outgoingQueue.sendReadyMessages()
+ # Send exit messages
self.moduleManager.sendReadyMessages()
+ # Choose next mix interval
now = time.time()
nextMix = now + 60
+
if now > nextShred:
- # Configurable shred interval
+ # FFFF Configurable shred interval
+ getLog().trace("Expunging queues")
self.incomingQueue.cleanQueue()
self.mixPool.queue.cleanQueue()
self.outgoingQueue.cleanQueue()
@@ -516,6 +531,7 @@
getLog().fatal_exc(sys.exc_info(),"Exception while running server")
getLog().info("Server shutting down")
server.close()
+ getLog().info("Server is shut down")
sys.exit(0)
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.33
retrieving revision 1.34
diff -u -d -r1.33 -r1.34
--- test.py 21 Oct 2002 02:52:03 -0000 1.33
+++ test.py 30 Oct 2002 02:19:39 -0000 1.34
@@ -25,7 +25,8 @@
import cStringIO
from mixminion.testSupport import mix_mktemp
-from mixminion.Common import MixError, MixFatalError, MixProtocolError, getLog
+from mixminion.Common import MixError, MixFatalError, MixProtocolError, \
+ getLog, previousMidnight
import mixminion.Crypto as Crypto
try:
@@ -58,7 +59,7 @@
if hasattr(log, '_storedHandlers'):
resumeLog()
buf = cStringIO.StringIO()
- h = mixminion.Common._ConsoleLogHandler(cStringIO.StringIO())
+ h = mixminion.Common._ConsoleLogHandler(buf)
log._storedHandlers = log.handlers
log._testBuf = buf
log.handlers = []
@@ -74,7 +75,7 @@
del log._testBuf
log.handlers = log._storedHandlers
del log._storedHandlers
- return str(buf)
+ return buf.getvalue()
# RSA key caching functionality
_generated_rsa_keys = {}
@@ -1417,7 +1418,7 @@
for k in s:
key = Keyset(k).getLionessKeys(PAYLOAD_ENCRYPT_MODE)
m = lioness_decrypt(m,key)
- self.assertEquals(payload,
+ self.assertEquals(payload,
BuildMessage.decodeStatelessReplyPayload(m,tag,passwd))
repl2, repl2tag = m, tag
@@ -1432,13 +1433,13 @@
for d in (sdict, None):
for p in (passwd, None):
for tag in ("zzzz"*5, "pzzz"*5):
- self.assertEquals(payload,
+ self.assertEquals(payload,
decodePayload(encoded1, tag,pk,d,p))
# efwd
for d in (sdict, None):
for p in (passwd, None):
- self.assertEquals(payload,
+ self.assertEquals(payload,
decodePayload(efwd_p, efwd_t, self.pk1, d,p))
self.assertEquals(None,
decodePayload(efwd_p, efwd_t, None, d,p))
@@ -1476,12 +1477,12 @@
# Bad efwd
efwd_pbad = efwd_p[:-1] + chr(ord(efwd_p[-1])^0xaa)
- self.failUnlessRaises(MixError,
- BuildMessage.decodeEncryptedForwardPayload,
+ self.failUnlessRaises(MixError,
+ BuildMessage.decodeEncryptedForwardPayload,
efwd_pbad, efwd_t, self.pk1)
for d in (sdict, None):
for p in (passwd, None):
- self.failUnlessRaises(MixError, decodePayload,
+ self.failUnlessRaises(MixError, decodePayload,
efwd_pbad, efwd_t, self.pk1, d, p)
self.assertEquals(None,
decodePayload(efwd_pbad, efwd_t, self.pk2, d,p))
@@ -1495,9 +1496,9 @@
decodePayload, repl1_bad, "tag1"*5, pk, sd, p)
sd = sdict.copy()
self.failUnlessRaises(MixError,
- BuildMessage.decodeReplyPayload, repl1_bad,
+ BuildMessage.decodeReplyPayload, repl1_bad,
sd["tag1"*5])
- # Bad srepl
+ # Bad srepl
repl2_bad = repl2[:-1] + chr(ord(repl2[-1])^0xaa)
self.assertEquals(None,
decodePayload(repl2_bad, repl2tag, None, None, passwd))
@@ -2475,11 +2476,11 @@
self.assertEquals(C._parseBase64(" YW\nJj"), "abc")
self.assertEquals(C._parseHex(" C0D0"), "\xC0\xD0")
- tm = C._parseDate("30/05/2002")
+ tm = C._parseDate("2002/05/30")
self.assertEquals(time.gmtime(tm)[:6], (2002,5,30,0,0,0))
- tm = C._parseDate("01/01/2000")
+ tm = C._parseDate("2000/01/01")
self.assertEquals(time.gmtime(tm)[:6], (2000,1,1,0,0,0))
- tm = C._parseTime("25/12/2001 06:15:10")
+ tm = C._parseTime("2001/12/25 06:15:10")
self.assertEquals(time.gmtime(tm)[:6], (2001,12,25,6,15,10))
def fails(fn, val, self=self):
@@ -2505,11 +2506,11 @@
fails(C._parseBase64, "Y")
fails(C._parseHex, "Z")
fails(C._parseHex, "A")
- fails(C._parseDate, "1/1/2000")
- fails(C._parseDate, "01/50/2000")
- fails(C._parseDate, "01/50/2000 12:12:12")
- fails(C._parseTime, "01/50/2000 12:12:12")
- fails(C._parseTime, "01/50/2000 12:12:99")
+ fails(C._parseDate, "2000/1/1")
+ fails(C._parseDate, "2000/50/01")
+ fails(C._parseDate, "2000/50/01 12:12:12")
+ fails(C._parseTime, "2000/50/01 12:12:12")
+ fails(C._parseTime, "2000/50/01 12:12:99")
nonexistcmd = '/file/that/does/not/exist'
if not os.path.exists(nonexistcmd):
@@ -2570,8 +2571,8 @@
Nickname: fred-the-bunny
"""
-def _getIdentityKey():
- return getRSAKey(0,2048)
+def _getIdentityKey(n=0):
+ return getRSAKey(n,2048)
import mixminion.Config
import mixminion.ServerInfo
@@ -2645,7 +2646,11 @@
# Now with a shorter configuration
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG_SHORT)
+ conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG_SHORT+
+ """[Incoming/MMTP]
+Enabled: yes
+IP: 192.168.0.99
+""")
finally:
resumeLog()
mixminion.ServerInfo.generateServerDescriptorAndKeys(conf,
@@ -2653,7 +2658,6 @@
d,
"key2",
d)
-
# Now with a bad signature
sig2 = mixminion.Crypto.pk_sign(sha1("Hello"), identity)
sig2 = base64.encodestring(sig2).replace("\n", "")
@@ -2855,18 +2859,18 @@
# plaintext text message, bin mode.
self.assertEquals(dem(payload, tag, 0), ("TXT", message, None))
# plaintext bin message, text mode.
- self.assertEquals(dem(binpayload, tag, 1),
+ self.assertEquals(dem(binpayload, tag, 1),
("BIN", base64.encodestring(binmessage), None))
# plaintext bin message, bin mode.
self.assertEquals(dem(binpayload, tag, 0), ("BIN", binmessage, None))
encoded = "baobob "*1024*4
# "Encoded" message, text mode
- self.assertEquals(dem(encoded, tag, 1),
- ("ENC", base64.encodestring(encoded),
+ self.assertEquals(dem(encoded, tag, 1),
+ ("ENC", base64.encodestring(encoded),
base64.encodestring(tag)[:-1]))
# "Encoded" message, binary mode
- self.assertEquals(dem(encoded, tag, 0),
+ self.assertEquals(dem(encoded, tag, 0),
("ENC", encoded, tag))
####
@@ -2891,10 +2895,13 @@
IdentityKeyBits: 2048
EncryptPrivateKey: no
Nickname: mac-the-knife
+[Incoming/MMTP]
+Enabled: yes
+IP: 10.0.0.1
"""
_FAKE_HOME = None
-def _getKeyring():
+def _getServerKeyring():
global _FAKE_HOME
if _FAKE_HOME is None:
_FAKE_HOME = mix_mktemp()
@@ -2908,7 +2915,7 @@
class ServerMainTests(unittest.TestCase):
def testServerKeyring(self):
- keyring = _getKeyring()
+ keyring = _getServerKeyring()
home = _FAKE_HOME
# Test creating identity key
@@ -2987,12 +2994,172 @@
pass
#----------------------------------------------------------------------
+
+_EXAMPLE_DESCRIPTORS = {} # name->list of str
+EX_SERVER_CONF_TEMPLATE = """
+[Server]
+Mode: relay
+EncryptIdentityKey: No
+PublicKeyLifetime: %(lifetime)s days
+IdentityKeyBits: 2048
+EncryptPrivateKey: no
+Nickname: %(nickname)s
+[Incoming/MMTP]
+Enabled: yes
+IP: %(ip)s
+[Outgoing/MMTP]
+Enabled: yes
+"""
+
+_EXAMPLE_DESCRIPTORS_INP = [
+ # name days ip? validAt
+ [ "Fred", "10 days", "10.0.0.6", (-19,-9,1,11) ],
+ [ "Lola", "5 days", "10.0.0.7", (-2,0,5) ],
+ [ "Joe", "20 days", "10.0.0.8", (-15,5,25) ],
+ [ "Alice", "8 days", "10.0.0.9", (-3,5,13) ],
+ [ "Bob", "11 days", "10.0.0.10", (-10,-1,6) ],
+ [ "Lisa", "3 days", "10.0.0.11", (-10,-1,5) ],
+]
+
+def getExampleServerDescriptors():
+ if _EXAMPLE_DESCRIPTORS:
+ return _EXAMPLE_DESCRIPTORS
+ global _EXAMPLE_DESCRIPTORS_TIME
+ gen = mixminion.ServerInfo.generateServerDescriptorAndKeys
+ tmpkeydir = mix_mktemp()
+ identity = _getIdentityKey()
+ _EXAMPLE_DESCRIPTORS_TIME = now = time.time()
+
+ sys.stdout.flush()
+
+ for (nickname, lifetime, ip, starting) in _EXAMPLE_DESCRIPTORS_INP:
+ conf = EX_SERVER_CONF_TEMPLATE % locals()
+ try:
+ suspendLog()
+ conf = mixminion.Config.ServerConfig(string=conf)
+ finally:
+ resumeLog()
+
+ _EXAMPLE_DESCRIPTORS[nickname] = []
+ for n in xrange(len(starting)):
+ k = "tst%d"%n
+ validAt = previousMidnight(now + 24*60*60*starting[n])
+ gen(config=conf, identityKey=identity, keyname=k,
+ keydir=tmpkeydir, hashdir=tmpkeydir, validAt=validAt)
+
+ sd = os.path.join(tmpkeydir,"key_"+k,"ServerDesc")
+ f = open(sd,'r')
+ _EXAMPLE_DESCRIPTORS[nickname].append(f.read())
+ f.close()
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ sys.stdout.flush()
+ return _EXAMPLE_DESCRIPTORS
+
class ClientMainTests(unittest.TestCase):
def testClientKeystore(self):
+ eq = self.assertEquals
+ raises = self.failUnlessRaises
+
import mixminion.ClientMain
dirname = mix_mktemp()
dc = mixminion.ClientMain.DirectoryCache(dirname)
- dc.load()
+
+ # Test empty directorycache.
+ for _ in xrange(2):
+ now = time.time()
+ eq([], dc.getAllCurrentServers())
+ eq([], dc.getAllCurrentServers(now-1000, now+36000))
+ eq([], dc.getAllCurrentServers(now-1000))
+ eq([], dc.getAllCurrentServers(None, now+36000))
+ raises(MixError, dc.getCurrentServer, "Fred")
+ raises(MixError, dc.getCurrentServer, "Fred", now-1000)
+ raises(MixError, dc.getCurrentServer, "Fred", now-1000,now+36000)
+ raises(MixError, dc.getCurrentServer, "Fred", None,now+36000)
+ dc.load(1)
+
+ edesc = getExampleServerDescriptors()
+
+ ## Test importing.
+ # tell server about descriptors: "Lisa", "Fred". The first of
+ # each is expired.
+ for sd in (edesc['Lisa'][0],edesc['Fred'][0]):
+ try:
+ suspendLog()
+ dc.importServerInfo(fname=None,string=sd)
+ finally:
+ s = resumeLog()
+ self.failUnless(s.find("expired")>=0)
+ for sd in edesc['Lisa'][1:]+edesc['Fred'][1:]:
+ dc.importServerInfo(fname=None,string=sd)
+
+ # tests shouldn't fail at 11:55pm
+ now = previousMidnight(_EXAMPLE_DESCRIPTORS_TIME)+60*60
+
+ for _ in (0,1): # test once before; once after a reload
+ self.assertEquals(5, len(dc.allServers))
+ self.assertEquals(2, len(dc.servers))
+ s = dc.getCurrentServer("Fred",when=now)
+ s2 = dc.getCurrentServer("Fred",when=(now+25*60*60))
+ s3 = dc.getCurrentServer("Fred",when=(now+6*24*60*60))
+ self.assert_(self.isSameServerDesc(edesc['Fred'][1], s))
+ self.assert_(self.isSameServerDesc(s2, s3))
+ self.assert_(not self.isSameServerDesc(s, s3))
+ s4 = dc.getCurrentServer("Lisa")
+ s5 = dc.getCurrentServer("Lisa",when=(now+5*25*60*60))
+ self.assert_(not self.isSameServerDesc(s4, s))
+ self.assert_(not self.isSameServerDesc(s4, s5))
+ raises(MixError, dc.getCurrentServer, "Lisa", when=now+3*25*60*60)
+ s6 = dc.getCurrentServer("Lisa",
+ when=(now-12*60*60),
+ until=(now+12*60*60))
+ self.assert_(self.isSameServerDesc(s6, s4))
+ raises(MixError, dc.getCurrentServer, "Lisa", when=(now-12*60*60),
+ until=(now+3*24*60*60))
+
+ # Test reloading.
+ dc.load(forceReload=1)
+
+ # test duplicates.
+ try:
+ suspendLog()
+ for _ in xrange(10):
+ dc.importServerInfo(fname=None,string=edesc['Lisa'][2])
+ finally:
+ resumeLog()
+ self.assertEquals(5, len(dc.allServers))
+
+ # import rest of servers
+ try:
+ suspendLog()
+ for _, sds in edesc.items():
+ for sd in sds:
+ dc.importServerInfo(fname=None,string=sd)
+ finally:
+ resumeLog()
+
+ s1 = dc.getAllCurrentServers(now)
+ self.assertEquals(len(s1), 8)
+ s2 = dc.getAllCurrentServers(0)
+ self.assertEquals([], s2)
+ s3 = dc.getAllCurrentServers(now, now+50*60*60)
+ self.assertEquals(len(s3), 5)
+ s4 = dc.getAllCurrentServers(now-2*25*60*60, now+2*25*60*60)
+ self.assertEquals(len(s4), 2)
+
+ def isSameServerDesc(self, s1, s2):
+ """s1 and s2 are either ServerInfo objects or strings containing server
+ descriptors. Returns 1 iff their digest fields match"""
+ ds = []
+ for s in s1, s2:
+ if type(s) == type(""):
+ m = re.search(r"^Digest: (\S+)\n", s, re.M)
+ assert m
+ ds.append(base64.decodestring(m.group(1)))
+ else:
+ ds.append(s['Server']['Digest'])
+ return ds[0] == ds[1]
+
#----------------------------------------------------------------------
def testSuite():