[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Several days worth of hacking. Highlights: Key rotatio...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv2846/lib/mixminion
Modified Files:
BuildMessage.py ClientMain.py Common.py Config.py Crypto.py
MMTPClient.py Main.py Packet.py __init__.py benchmark.py
test.py testSupport.py
Log Message:
Several days worth of hacking. Highlights: Key rotation, robust queues.
TODO:
- Update status, add time estimates
- Break down directory work
etc/mixminiond.conf:
- Rename PublicKeySloppiness to PublicKeyOverlap
*:
- Whitespace normalization
ClientMain:
- Improve path syntax to include ?, *n, Allow choice-with-replacement
- Use new readPickled functionality from Common
- Add -n argument for flush command
- Add default-path options to ClientConfig
- Be more specific about causes of failure when flushing; be more specific
about # messages flushed.
- Remove --swap-at option: now path syntax is adequate.
Config, ClientMain, Common:
- Change duration from a 3-tuple to an independent class. Now we
can say duration.getSeconds() rather than duration[2], which makes
some stuff more readable.
Common:
- Debug checkPrivateFile
- Add AtomicFile class to help with standard create/rename pattern.
- Add readPickled/writePickled wrappers
MMTPClient:
- Document PeerCertificateCache
Packet:
- Correct documentation on overflow, underflow.
benchmark:
- Improve format of printed sizes
- Improve pk timing; time with bizarre exponent.
- Add Timing for ServerQueues
test:
- Add tests for encodeBase64
- Correct tests for new DeliveryQueue implementation
- Add tests for checkPrivateFile
- Revise tests for _parseInterval in response to new Duration class.
- Add tests for generating new descriptors with existing keys
- Fix test for directory with bad signature: make it fail for the
right reason
- Deal with new validateConfig in Module
- Add test for scheduler.
- Tests for new path selection code
testSupport:
- Module code uses new interface
EventStats:
- Document, clean
MMTPServer:
- Better warning on TLSClosed while connecting.
- Document new functionality
Modules:
- validateConfig function no longer needs 'sections' and 'entries':
make it follow the same interface as other validation fns
- _deliverMessages: use new DeliveryQueue interface
PacketHandler:
- Always take a list of keys, never a single one.
ServerConfig:
- Refactor validateRetrySchedule
- Use new Duration class
- Rename PublicKeySloppiness to PublicKeyOverlap
ServerKeys: ***
- Implement key rotation:
- Notice when to add and remove keys from PacketHandlers, MMTPServer
- Set keys in packethandlers, mmtpserver
- Note that 512-bit DH moduli are kinda silly
- More code and debugging for descriptor regenration
ServerMain:
- Documentation
- Key rotation
- Respond to refactoring in DeliveryQueue
- Use lambdas to wrap EventStats rotation
- Separate reset method
- Remove obsolete commands
ServerQueue: ***
- Refactor DeliveryQueue so that it has a prayer of working: Keep
message delivery state in a separate file, and update separately.
Remember time of queueing for each method, and last attempted
delivery; n_retries is gone. This allows us to change the retry schedule
without putting messages in an inconsistent state.
An earlier version put the state for _all_ queued objects in a
single file: this turned out to be screamingly inefficient.
crypt.c, tls.c:
- Documentation fixes
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.44
retrieving revision 1.45
diff -u -d -r1.44 -r1.45
--- BuildMessage.py 5 May 2003 00:38:45 -0000 1.44
+++ BuildMessage.py 17 May 2003 00:08:41 -0000 1.45
@@ -59,7 +59,7 @@
",".join([s.getNickname() for s in path1]),
",".join([s.getNickname() for s in path2]))
LOG.debug(" Delivering to %04x:%r", exitType, exitInfo)
-
+
# Choose a random decoding tag.
if not suppressTag:
tag = _getRandomTag(paddingPRNG)
@@ -265,7 +265,7 @@
elif err:
raise UIError("Address and %s leg of path will not fit in one header",
["first", "second"][err-1])
-
+
#----------------------------------------------------------------------
# MESSAGE DECODING
@@ -486,8 +486,8 @@
# Node i+1 sees the junk that node i saw, plus the junk that i appends,
# all encrypted by i.
- prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE)
-
+ prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE)
+
# newJunk is the junk that node i will append. (It's as long as
# the data that i removes.)
newJunk = Crypto.prng(prngKey,size)
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.72
retrieving revision 1.73
diff -u -d -r1.72 -r1.73
--- ClientMain.py 5 May 2003 00:38:45 -0000 1.72
+++ ClientMain.py 17 May 2003 00:08:41 -0000 1.73
@@ -15,7 +15,9 @@
import getopt
import getpass
import os
+import re
import stat
+import string
import sys
import time
import urllib
@@ -24,13 +26,13 @@
import mixminion.BuildMessage
[...1042 lines suppressed...]
+ count=None
+ for o,v in options:
+ if o in ('-n','--count'):
+ try:
+ count = int(v)
+ except ValueError:
+ print "ERROR: %s expects an integer" % o
+ sys.exit(1)
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
wantClient=1)
@@ -2637,7 +2591,7 @@
parser.init()
client = parser.client
- client.flushQueue()
+ client.flushQueue(count)
_LIST_QUEUE_USAGE = """\
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.72
retrieving revision 1.73
diff -u -d -r1.72 -r1.73
--- Common.py 5 May 2003 00:41:57 -0000 1.72
+++ Common.py 17 May 2003 00:08:42 -0000 1.73
@@ -8,16 +8,18 @@
__all__ = [ 'IntervalSet', 'Lockfile', 'LOG', 'LogStream', 'MixError',
'MixFatalError', 'MixProtocolError', 'UIError', 'UsageError',
'ceilDiv', 'checkPrivateDir', 'checkPrivateFile',
- 'createPrivateDir',
- 'encodeBase64', 'floorDiv',
- 'formatBase64', 'formatDate', 'formatFnameTime', 'formatTime',
+ 'createPrivateDir', 'encodeBase64', 'floorDiv', 'formatBase64',
+ 'formatDate', 'formatFnameTime', 'formatTime',
'installSIGCHLDHandler', 'isSMTPMailbox', 'openUnique',
- 'previousMidnight', 'readPossiblyGzippedFile', 'secureDelete',
- 'stringContains', 'succeedingMidnight', 'waitForChildren' ]
+ 'previousMidnight', 'readPickled', 'readPossiblyGzippedFile',
+ 'secureDelete', 'stringContains', 'succeedingMidnight',
+ 'waitForChildren', 'writePickled' ]
import binascii
import bisect
import calendar
+import cPickle
+import errno
import fcntl
import gzip
import os
@@ -138,13 +140,12 @@
def formatBase64(s):
"""Convert 's' to a one-line base-64 representation."""
return encodeBase64(s, 64, 1)
-
+
def encodeBase64(s, lineWidth=64, oneline=0):
"""Convert 's' to a multiline base-64 representation. Improves upon
base64.encodestring by having a variable line width. Implementation
is based upon that function.
"""
- # XXXX004 test me
pieces = []
bytesPerLine = floorDiv(lineWidth, 4) * 3
for i in xrange(0, len(s), bytesPerLine):
@@ -154,18 +155,22 @@
return "".join([ s.strip() for s in pieces ])
else:
return "".join(pieces)
-
+
#----------------------------------------------------------------------
def checkPrivateFile(fn, fix=1):
"""Checks whether f is a file owned by this uid, set to mode 0600 or
0700, and all its parents pass checkPrivateDir. Raises MixFatalError
if the assumtions are not met; else return None. If 'fix' is true,
repair permissions on the file rather than raising MixFatalError."""
- #XXXX004 testme
parent, _ = os.path.split(fn)
- if not checkPrivateDir(parent):
- return None
- st = os.stat(fn)
+ checkPrivateDir(parent)
+ try:
+ st = os.stat(fn)
+ except OSError, e:
+ if e.errno == errno.EEXIST:
+ raise MixFatalError("Nonexistant file %s" % fn)
+ else:
+ raise MixFatalError("Could't stat file %s: %s" % (fn, e))
if not st:
raise MixFatalError("Nonexistant file %s" % fn)
if not os.path.isfile(fn):
@@ -176,7 +181,7 @@
mode = st[stat.ST_MODE] & 0777
if mode not in (0700, 0600):
if not fix:
- raise MixFatalError("Bad mode %o on file %s" % mode)
+ raise MixFatalError("Bad mode %o on file %s" % (mode & 0777, fn))
newmode = {0:0600,0100:0700}[(mode & 0100)]
LOG.warn("Repairing permissions on file %s" % fn)
os.chmod(fn, newmode)
@@ -246,6 +251,56 @@
_WARNED_DIRECTORIES[d] = 1
#----------------------------------------------------------------------
+# File helpers
+class AtomicFile:
+ """Wrapper around open/write/rename to encapsulate writing to a temporary
+ file, then moving to the final filename on close"""
+ def __init__(self, fname, mode='w'):
+ self.fname = fname
+ self.tmpname = fname + ".tmp"
+ fd = os.open(self.tmpname, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0600)
+ self.f = os.fdopen(fd, mode)
+
+ def write(self, s):
+ self.f.write(s)
+
+ def close(self):
+ """Close the underlying file and replace the destination file."""
+ os.rename(self.tmpname, self.fname)
+ self.f.close()
+
+ def discard(self):
+ """Discard changes to the temporary file."""
+ self.f.close()
+ os.unlink(self.tmpname)
+
+def readPickled(fn):
+ """Given the name of a file containing a pickled object, return the pickled
+ object."""
+ f = open(fn, 'rb')
+ try:
+ return cPickle.load(f)
+ finally:
+ f.close()
+
+def writePickled(fn, obj):
+ """Given a filename and an object to be pickled, pickles the object into
+ a temporary file, then replaces the file with the temporary file.
+ """
+ tmpname = fn + ".tmp"
+ f, tmpname = openUnique(tmpname, 'wb')
+ try:
+ try:
+ cPickle.dump(obj, f, 1)
+ finally:
+ f.close()
+ except:
+ if os.path.exists(tmpname): os.unlink(tmpname)
+ raise
+
+ os.rename(tmpname, fn)
+
+#----------------------------------------------------------------------
# Secure filesystem operations.
# A 'shred' command to overwrite and unlink files. It should accept an
@@ -637,10 +692,10 @@
LOG.log(self.severity, "->%s: %s", self.name, line)
del self.buf[:]
s = s[idx+1:]
-
+
if s:
self.buf.append(s)
-
+
def flush(self): pass
def close(self): pass
@@ -690,6 +745,64 @@
if when is None:
when = time.time()
return time.strftime("%Y%m%d%H%M%S", time.localtime(when))
+
+#----------------------------------------------------------------------
+class Duration:
+ """A Duration is a number of time units, such as '1.5 seconds' or
+ '2 weeks'. Durations are stored internally as a number of seconds.
+ """
+ ## Fields:
+ # seconds: the number of seconds in this duration
+ # unitName: the name of the units comprising this duration.
+ # nUnits: the number of units in this duration
+ def __init__(self, seconds, unitName=None, nUnits=None):
+ """Initialize a new Duration with a given number of seconds."""
+ self.seconds = seconds
+ if unitName:
+ self.unitName = unitName
+ self.nUnits = nUnits
+ else:
+ self.unitName = "second"
+ self.nUnits = seconds
+
+ def __str__(self):
+ s = ""
+ if self.nUnits != 1:
+ s = "s"
+ return "%s %s%s" % (self.nUnits, self.unitName, s)
+
+ def __repr__(self):
+ return "Duration(%r, %r, %r)" % (self.seconds, self.unitName,
+ self.nUnits)
+
+ def __float__(self):
+ """Return the number of seconds in this duration"""
+ return self.seconds
+
+ def __int__(self):
+ """Return the number of seconds in this duration"""
+ return int(self.seconds)
+
+ def getSeconds(self):
+ """Return the number of seconds in this duration"""
+ return self.seconds
+
+ def reduce(self):
+ """Change the representation of this object to its clearest form"""
+ s = self.seconds
+ for n,u in [(60*60*24*365,'year'),
+ (60*60*24*30, 'month'),
+ (60*60*24*7, 'week'),
+ (60*60*24, 'day'),
+ (60*60, 'hour'),
+ (60, 'minute')]:
+ if s % n == 0:
+ self.nUnits = floorDiv(s,n)
+ self.unitName = u
+ return
+ self.nUnits = s
+ self.unitName = 'second'
+ return self
#----------------------------------------------------------------------
# InteralSet
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.41
retrieving revision 1.42
diff -u -d -r1.41 -r1.42
--- Config.py 26 Apr 2003 14:39:58 -0000 1.41
+++ Config.py 17 May 2003 00:08:42 -0000 1.42
@@ -118,16 +118,19 @@
_canonical_unit_names = { 'sec' : 'second', 'min': 'minute', 'mon' : 'month' }
def _parseInterval(interval):
"""Validation function. Converts a config value to an interval of time,
- in the format (number of units, name of unit, total number of seconds).
- Raises ConfigError on failure."""
+ returning a Duration object. Raises ConfigError on failure."""
inter = interval.strip().lower()
m = _interval_re.match(inter)
if not m:
raise ConfigError("Unrecognized interval %r" % inter)
- num, unit = float(m.group(1)), m.group(2)
+ num, unit = m.group(1), m.group(2)
+ if '.' in num:
+ num = float(num)
+ else:
+ num = int(num)
nsec = num * _seconds_per_unit[unit]
- return num, _canonical_unit_names.get(unit,unit), nsec
-
+ return mixminion.Common.Duration(nsec,
+ _canonical_unit_names.get(unit,unit), num)
def _parseIntervalList(s):
"""Validation functions. Parse a list of comma-separated intervals
@@ -141,8 +144,8 @@
if item.startswith("every "):
item = item[6:]
interval, duration = item.split(" for ", 1)
- interval = int(_parseInterval(interval)[2])
- duration = int(_parseInterval(duration)[2])
+ interval = int(_parseInterval(interval))
+ duration = int(_parseInterval(duration))
if interval < 1:
raise ConfigError("Repeated interval too small in %s"%s)
@@ -152,7 +155,7 @@
"Bad syntax on interval %s. (Did you mean %s for X days?)",
item, item)
else:
- interval = _parseInterval(item)[2]
+ interval = int(_parseInterval(item))
ilist.append(interval)
return ilist
@@ -512,7 +515,7 @@
steps. (Use this to load a file that's already been checked as
valid.)"""
assert (filename is None) != (string is None)
-
+
if not hasattr(self, '_callbacks'):
self._callbacks = {}
@@ -571,7 +574,7 @@
raise ConfigError("Unrecognized key %s on line %s" %
(k, line))
else:
- LOG.warn("Unregognized key %s on line %s", k, line)
+ LOG.warn("Unrecognized key %s on line %s", k, line)
continue
# Parse and validate the value of this entry.
@@ -700,7 +703,11 @@
'Security' : { 'PathLength' : ('ALLOW', _parseInt, "8"),
'SURBAddress' : ('ALLOW', None, None),
'SURBPathLength' : ('ALLOW', _parseInt, "4"),
- 'SURBLifetime' : ('ALLOW', _parseInterval, "7 days") },
+ 'SURBLifetime' : ('ALLOW', _parseInterval, "7 days"),
+ 'ForwardPath' : ('ALLOW', None, "*"),
+ 'ReplyPath' : ('ALLOW', None, "*"),
+ 'SURBPath' : ('ALLOW', None, "*"),
+ },
'Network' : { 'ConnectionTimeout' : ('ALLOW', _parseInterval, None) }
}
def __init__(self, fname=None, string=None):
@@ -719,9 +726,9 @@
t = self['Network'].get('ConnectionTimeout')
if t:
- if t[2] < 5:
+ if int(t) < 5:
LOG.warn("Very short connection timeout")
- elif t[2] > 60:
+ elif int(t) > 60:
LOG.warn("Very long connection timeout")
def _validateHostSection(sec):
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.42
retrieving revision 1.43
diff -u -d -r1.42 -r1.43
--- Crypto.py 26 Apr 2003 14:39:58 -0000 1.42
+++ Crypto.py 17 May 2003 00:08:42 -0000 1.43
@@ -481,7 +481,7 @@
bytes at a time."""
self.bytes = ""
self.chunksize = chunksize
-
+
def getBytes(self, n):
"""Returns a string of 'n' random bytes."""
@@ -702,7 +702,7 @@
bytes at a time"""
RNG.__init__(self,n)
self.__lock = threading.Lock()
-
+
def _prng(self,n):
"Returns n fresh bytes from our true RNG."
if _TRNG_FILENAME is None:
Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- MMTPClient.py 5 May 2003 00:42:49 -0000 1.30
+++ MMTPClient.py 17 May 2003 00:08:43 -0000 1.31
@@ -94,7 +94,7 @@
assert sig == signal.SIGALRM
if connectTimeout:
signal.signal(signal.SIGALRM, sigalarmHandler)
-
+
# Connect to the server
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setblocking(1)
@@ -114,7 +114,7 @@
finally:
if connectTimeout:
signal.alarm(0)
-
+
LOG.debug("Handshaking with %s:%s",self.targetIP, self.targetPort)
self.tls = self.context.sock(self.sock.fileno())
self.tls.connect()
@@ -164,7 +164,7 @@
self._sendPacket(packet,
control="JUNK\r\n", serverControl="RECEIVED\r\n",
hashExtra="JUNK", serverHashExtra="RECEIVED JUNK")
-
+
def _sendPacket(self, packet,
control="SEND\r\n", serverControl="RECEIVED\r\n",
hashExtra="SEND",serverHashExtra="RECEIVED"):
@@ -199,7 +199,7 @@
LOG.debug("ACK received; packet successfully delivered")
except (socket.error, _ml.TLSError, _ml.TLSClosed), e:
self._raise(e, "sending packet")
-
+
def shutdown(self):
"""Close this connection."""
LOG.debug("Shutting down connection to %s:%s",
@@ -256,28 +256,37 @@
def pingServer(routing, connectTimeout=5):
"""Try to connect to a server and send a junk packet.
-
+
May raise MixProtocolBadAuth, or other MixProtocolError if server
isn't up."""
sendMessages(routing, ["JUNK"], connectTimeout=connectTimeout)
-
+
class PeerCertificateCache:
- "DOCDOC"
+ """A PeerCertificateCache validates certificate chains from MMTP servers,
+ and remembers which chains we've already seen and validated."""
+ ## Fieleds
+ # cache: A map from peer (temporary) KeyID's to a (signing) KeyID.
def __init__(self):
- self.cache = {} # hashed peer pk -> identity keyid that it is valid for
+ self.cache = {}
def check(self, tls, targetKeyID, address):
- "DOCDOC"
- if targetKeyID is None:
- return
-
+ """Check whether the certificate chain on the TLS connection 'tls'
+ is valid, current, and matches the keyID 'targetKeyID'. If so,
+ return. If not, raise MixProtocolBadAuth.
+ """
+ # First, make sure the certificate is neither premature nor expired.
try:
tls.check_cert_alive()
except _ml.TLSError, e:
raise MixProtocolBadAuth("Invalid certificate: %s", str(e))
- peer_pk = tls.get_peer_cert_pk()
- hashed_peer_pk = sha1(peer_pk.encode_key(public=1))
+ # If we don't care whom we're talking to, we don't need to check
+ # them out.
+ if targetKeyID is None:
+ return
+
+ # Get the KeyID for the peer (temporary) key.
+ hashed_peer_pk = sha1(tls.get_peer_cert_pk().encode_key(public=1))
# Before 0.0.4alpha, a server's keyID was a hash of its current
# TLS public key. In 0.0.4alpha, we allowed this for backward
# compatibility. As of 0.0.4alpha2, since we've dropped backward
@@ -287,28 +296,41 @@
raise MixProtocolBadAuth(
"Pre-0.0.4 (non-rotatable) certificate from server at %s",
address)
+
try:
- if self.cache[hashed_peer_pk] == targetKeyID:
+ if targetKeyID == self.cache[hashed_peer_pk]:
+ # We recognize the key, and have already seen it to be
+ # signed by the target identity.
LOG.trace("Got a cached certificate from server at %s",
address)
return # All is well.
else:
+ # We recognize the key, but some other identity signed it.
raise MixProtocolBadAuth(
"Mismatch between expected and actual key id")
except KeyError:
- # We haven't found an identity for this pk yet.
pass
+ # We haven't found an identity for this pk yet. Try to check the
+ # signature on it.
try:
identity = tls.verify_cert_and_get_identity_pk()
except _ml.TLSError, e:
raise MixProtocolBadAuth("Invalid KeyID from server at %s: %s"
%(address, e))
+ # Okay, remember who has signed this certificate.
hashed_identity = sha1(identity.encode_key(public=1))
LOG.trace("Remembering valid certificate for server at %s",
address)
self.cache[hashed_peer_pk] = hashed_identity
+
+ # Note: we don't need to worry about two identities signing the
+ # same certificate. While this *is* possible to do, it's useless:
+ # You could get someone else's certificate and sign it, but you
+ # couldn't start up a TLS connection with that certificate without
+ # stealing their private key too.
+
+ # Was the signer the right person?
if hashed_identity != targetKeyID:
raise MixProtocolBadAuth("Invalid KeyID for server at %s" %address)
-
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -d -r1.40 -r1.41
--- Main.py 26 Apr 2003 14:39:58 -0000 1.40
+++ Main.py 17 May 2003 00:08:43 -0000 1.41
@@ -119,7 +119,7 @@
"client" : ( 'mixminion.ClientMain', 'runClient' ),
# XXXX Obsolete; use "queue"; remove in 0.0.5
"pool" : ( 'mixminion.ClientMain', 'runClient' ),
- "queue" : ( 'mixminion.ClientMain', 'runClient' ),
+ "queue" : ( 'mixminion.ClientMain', 'runClient' ),
"import-server" : ( 'mixminion.ClientMain', 'importServer' ),
"list-servers" : ( 'mixminion.ClientMain', 'listServers' ),
"update-servers" : ( 'mixminion.ClientMain', 'updateServers' ),
@@ -132,7 +132,7 @@
"inspect-queue" : ( 'mixminion.ClientMain', 'listQueue' ),
# XXXX Obsolete; use "inspect-queue"; remove in 0.0.5
"inspect-pool" : ( 'mixminion.ClientMain', 'listQueue' ),
- "ping" : ( 'mixminion.ClientMain', 'runPing' ),
+ "ping" : ( 'mixminion.ClientMain', 'runPing' ),
# XXXX Obsolete; use "server-start"; remove in 0.0.5
"server" : ( 'mixminion.server.ServerMain', 'runServer' ),
"server-start" : ( 'mixminion.server.ServerMain', 'runServer' ),
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -d -r1.40 -r1.41
--- Packet.py 5 May 2003 00:38:45 -0000 1.40
+++ Packet.py 17 May 2003 00:08:43 -0000 1.41
@@ -142,21 +142,23 @@
ri, underflow = ri[:rlen], ri[rlen:]
if rt >= MIN_EXIT_TYPE and rlen < 20:
raise ParseError("Subheader missing tag")
- #XXXX004 test underflow
return Subheader(major,minor,secret,digest,rt,ri,rlen,underflow)
class Subheader:
- """Represents a decoded Mixminion subheader
+ """Represents a decoded Mixminion subheader.
Fields: major, minor, secret, digest, routinglen, routinginfo,
routingtype.
A Subheader can exist in a half-initialized state where routing
- info has been read from the first header, but not from the
- extened headers. If this is so, routinglen will be > len(routinginfo).
- DOCDOC underflow
- """
+ info has been read from the first RSA-encrypted data, but not
+ from the symmetrically encrypted data in the rest of the
+ header. If this is so, routinglen will be > len(routinginfo).
+ If 'underflow' is present, it contains material that does not
+ belong to this subheader that was provided to 'parseSubheader'
+ anyway.
+ """
def __init__(self, major, minor, secret, digest, routingtype,
routinginfo, routinglen=None, underflow=""):
"""Initialize a new subheader"""
@@ -198,26 +200,27 @@
self.routinglen = len(info)
def appendOverflow(self, data):
- """Given a string containing additional
- routing info, add it to the routinginfo of this
- object.
- DOCDOC
- """
- #XXXX004 test
+ """Given a string containing additional routing info, add it
+ to the routinginfo of this object. """
self.routinginfo += data
assert len(self.routinginfo) <= self.routinglen
def getUnderflowLength(self):
+ """Return the number of bytes from the rest of the header that should
+ be included in the RSA-encrypted part of the header.
+ """
return max(0, MAX_ROUTING_INFO_LEN - self.routinglen)
def getOverflowLength(self):
- """DOCDOC"""
- #XXXX004 test
+ """Return the length of the data from routinginfo that will
+ not fit in the RSA-encrypted part of the header.
+ """
return max(0, self.routinglen - MAX_ROUTING_INFO_LEN)
def getOverflow(self):
- """DOCDOC"""
- #XXXX004 test
+ """Return the portion of routinginfo that doesn't fit into the
+ RSA-encrypted part of the header.
+ """
return self.routinginfo[MAX_ROUTING_INFO_LEN:]
def pack(self):
@@ -473,7 +476,7 @@
if not text.endswith("\n"):
text += "\n"
return "%s\nVersion: 0.1\n\n%s%s\n"%(RB_TEXT_START,text,RB_TEXT_END)
-
+
#----------------------------------------------------------------------
# Routing info
@@ -691,7 +694,7 @@
if (c.startswith("Decoding-handle:") or
c.startswith("Message-type:")):
preNL = "\n"
-
+
if self.messageType == 'TXT':
tagLine = ""
elif self.messageType == 'ENC':
Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.31
retrieving revision 1.32
diff -u -d -r1.31 -r1.32
--- __init__.py 26 Apr 2003 14:39:59 -0000 1.31
+++ __init__.py 17 May 2003 00:08:43 -0000 1.32
@@ -90,7 +90,7 @@
raise ValueError, "Can't compare versions"
return cmp(a[4],b[4])
-
+
assert __version__ == version_tuple_to_string(version_info)
assert parse_version_string(__version__) == version_info
assert cmp_versions(version_info, version_info) == 0
Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.33
retrieving revision 1.34
diff -u -d -r1.33 -r1.34
--- benchmark.py 26 Apr 2003 14:39:59 -0000 1.33
+++ benchmark.py 17 May 2003 00:08:43 -0000 1.34
@@ -19,8 +19,9 @@
import threading
from time import time
-
import mixminion._minionlib as _ml
+import mixminion.server.ServerQueue
+
from mixminion.BuildMessage import _buildHeader, buildForwardMessage, \
compressData, uncompressData, _encodePayload, decodePayload
from mixminion.Common import secureDelete, installSIGCHLDHandler, \
@@ -83,9 +84,9 @@
def spacestr(n):
"""Converts number of bytes to readable representation)"""
- if abs(n) < 1e4:
- return "%d bytes" %n
- elif abs(n) < 1e7:
+ if abs(n) < 1024:
+ return "%d B" %n
+ elif abs(n) < 1048576:
return "%d KB" % (n >> 10)
elif abs(n) < 1e10:
return "%d MB" % (n >> 20)
@@ -223,6 +224,8 @@
print "bear D (32K)", timeit((
lambda bkey=bkey: bear_decrypt(s32K, bkey)), 100)
+def rsaTiming():
+ c = AESCounterPRNG()
if hasattr(_ml, 'add_oaep_padding'):
print "OAEP_add (70->128B) (C)",
print timeit((lambda: _ml.add_oaep_padding(s70b,OAEP_PARAMETER,128)),
@@ -265,7 +268,7 @@
print "RSA generate (1024 bit,e=65535)", timeit((lambda: pk_generate(1024,
65535)),10)
- rsa = pk_generate()
+ rsa = pk_generate(1024,65535)
print "Pad+RSA public encrypt",
print timeit((lambda rsa=rsa: pk_encrypt(s70b, rsa)),1000)
enc = pk_encrypt(s70b, rsa)
@@ -274,7 +277,17 @@
print "RSA generate (1024 bit,e=3)", timeit((lambda: pk_generate(1024,
3)),10)
- rsa = pk_generate()
+ rsa = pk_generate(1024,3)
+ print "Pad+RSA public encrypt",
+ print timeit((lambda rsa=rsa: pk_encrypt(s70b, rsa)),1000)
+ enc = pk_encrypt(s70b, rsa)
+ print "Pad+RSA private decrypt", \
+ timeit((lambda enc=enc,rsa=rsa: pk_decrypt(enc, rsa)),100)
+
+ print "RSA generate (1024 bit,e=100073471)", timeit(
+ lambda: pk_generate(1024, 100073471), 10)
+
+ rsa = pk_generate(1024, 100073471)
print "Pad+RSA public encrypt",
print timeit((lambda rsa=rsa: pk_encrypt(s70b, rsa)),1000)
enc = pk_encrypt(s70b, rsa)
@@ -381,7 +394,7 @@
keyring = ServerKeyring(config)
keyring.getIdentityKey()
print "Create and sign server descriptor", timeit(keyring.createKeys, 10)
- liveKey = keyring.getServerKeyset()
+ liveKey = keyring.getServerKeysets()[0]
descFile = liveKey.getDescriptorFileName()
desc = open(descFile).read()
## for _ in xrange(2000):
@@ -463,6 +476,70 @@
bm(16,16,10)
#----------------------------------------------------------------------
+def serverQueueTiming():
+ print "#================= SERVER QUEUES ====================="
+ Queue = mixminion.server.ServerQueue.Queue
+ DeliveryQueue = mixminion.server.ServerQueue.DeliveryQueue
+ d1 = mix_mktemp()
+ q1 = Queue(d1, create=1)
+
+ d2 = mix_mktemp()
+ os.mkdir(d2,0700)
+ getCommonPRNG().getBytes(1)
+
+ #for ln,it in (32*1024,100),(128,400),(1024,400), (32*1024,100):
+ for ln,it in ():
+ msg = "z"*ln
+ def y(msg=msg,idx=[0],d2=d2):
+ fn = os.path.join(d2,"k_"+str(idx[0]))
+ idx[0] += 1
+ f = open(fn, 'wb')
+ f.write(msg)
+ f.close()
+ def x(msg=msg,d2=d2):
+ f,b=getCommonPRNG().openNewFile(d2,"k_",1)
+ f.write(msg)
+ f.close()
+ print "Base: write %s file: %s" %(
+ spacestr(ln), timestr(timeit_(y, it)))
+ for p in os.listdir(d2):
+ os.unlink(os.path.join(d2,p))
+ print "Base: write %s file with random name: %s" %(
+ spacestr(ln), timestr(timeit_(x, it)))
+ for p in os.listdir(d2):
+ os.unlink(os.path.join(d2,p))
+
+ tm = timeit_(lambda q1=q1,msg=msg:q1.queueMessage(msg), it)
+ print "Queue %s message: %s" %(spacestr(ln), timestr(tm))
+ t2 = time()
+ q1.removeAll()
+ q1.cleanQueue()
+ t2 = time() - t2
+ print "Scrub %s message: %s" %(spacestr(ln), timestr(t2/float(it)))
+
+ msg = [ 123, 414, msg ]
+ tm = timeit_(lambda q1=q1,msg=msg:q1.queueObject(msg), it)
+ print "Pickle %s message: %s" %(spacestr(ln), timestr(tm))
+ q1.removeAll()
+ q1.cleanQueue()
+
+ for ln,it in (128,400),(1024,400), (32*1024,100):
+ q1 = DeliveryQueue(d1, [100,100,100,100])
+ msg = "z"*ln
+ print "Delivery queue: %s message: %s" %(
+ spacestr(ln),
+ timeit(lambda q1=q1,msg=msg: q1.queueDeliveryMessage(msg), it))
+ print " (repOK):", \
+ timeit(lambda q1=q1: q1._repOk(), it*10)
+# q1._bs2()
+# print " (set metadata 2):", \
+# timeit(lambda q1=q1: q1._saveState2(), it)
+
+ for p in os.listdir(d1):
+ os.unlink(os.path.join(d1,p))
+
+
+#----------------------------------------------------------------------
class DummyLog:
def seenHash(self,h): return 0
def logHash(self,h): pass
@@ -472,7 +549,7 @@
pk = pk_generate(2048)
server = FakeServerInfo("127.0.0.1", 1, pk, "X"*20)
- sp = PacketHandler(pk, DummyLog())
+ sp = PacketHandler([pk], [DummyLog()])
m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
[server, server], [server, server])
@@ -494,7 +571,7 @@
t = prng.getBytes(20)
print "Decode short payload", timeit(
lambda p=p,t=t: decodePayload(p, t), 1000)
-
+
k20 = prng.getBytes(20*1024)
p = _encodePayload(k20, 0, prng)
t = prng.getBytes(20)
@@ -510,7 +587,7 @@
except CompressedDataTooLong:
pass
print "Decode overcompressed payload", timeit(decode, 1000)
-
+
#----------------------------------------------------------------------
def timeEfficiency():
print "#================= ACTUAL v. IDEAL ====================="
@@ -580,7 +657,7 @@
prng_128b = timeit_((lambda k=aeskey: prng(k,128)),10000)
server = FakeServerInfo("127.0.0.1", 1, pk, "X"*20)
- sp = PacketHandler(pk, DummyLog())
+ sp = PacketHandler([pk], [DummyLog()])
m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
[server, server], [server, server])
@@ -620,7 +697,7 @@
lockfile.release()
t = time()-t1
print "Lockfile: lock+unlock", timestr(t/2000.)
-
+
for i in xrange(200):
f = open(os.path.join(dname, str(i)), 'wb')
f.write(s32K)
@@ -745,14 +822,14 @@
context = _ml.TLSContext_new(fn, p, dh)
s1 = context.sock(0, 0)
s2 = context.sock(0, 1)
-
+
def testLeaks5():
from mixminion.test import _getMMTPServer
server, listener, messagesIn, keyid = _getMMTPServer(1)
#t = threading.Thread(None, testLeaks5_send,
# args=(keyid,))
#t.start()
-
+
while 1:
server.process(0.5)
#if messagesIn:
@@ -789,7 +866,7 @@
_ml.generate_dh_parameters(dh, 1, 512)
print "OK"
context = _ml.TLSContext_new(fn, p)#XXXX, dh)
-
+
listenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listenSock.bind(("127.0.0.1", 48999))
@@ -827,10 +904,12 @@
#----------------------------------------------------------------------
def timeAll(name, args):
cryptoTiming()
+ rsaTiming()
buildMessageTiming()
directoryTiming()
fileOpsTiming()
encodingTiming()
+ serverQueueTiming()
serverProcessTiming()
hashlogTiming()
timeEfficiency()
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.103
retrieving revision 1.104
diff -u -d -r1.103 -r1.104
--- test.py 5 May 2003 02:52:00 -0000 1.103
+++ test.py 17 May 2003 00:08:43 -0000 1.104
@@ -491,11 +491,6 @@
LF2.release()
LF1.acquire(blocking=1)
- # XXXX004 reenable this once we figure out how to do so
- # happily on *BSD. (The issue is that a blocking
- # flock seems to block _all_ the threads in this
- # process, not just this one.)
-
# Now try a blocking lock. We need to block in another process
# because of some platforms' implementations of threading.
releasedFile = mix_mktemp()
@@ -515,6 +510,18 @@
os.waitpid(pid, 0)
self.assertEquals("GOOD", readFile(releasedFile))
+ def test_encodeBase64(self):
+ longish = "xyzzyasjklsadjsakldjsakldjsakldjskljaskldjsadkljsa"*10
+ d32 = encodeBase64(longish, lineWidth=32)
+ d64 = encodeBase64(longish, lineWidth=64)
+ self.assertEquals(longish, base64.decodestring(d32))
+ self.assertEquals(longish, base64.decodestring(d64))
+ for enc, max in ((d32, 33), (d64,65)):
+ lines = enc.split("\n")
+ for line in lines[-1]:
+ self.assertEquals(len(line), max)
+ self.assert_(len(lines[-1]) <= max)
+
def _intervalEq(self, a, *others):
eq = self.assertEquals
for b in others:
@@ -1356,7 +1363,7 @@
def __init__(self, addr, port, key, keyid):
assert key.get_modulus_bytes() == 256
self.addr = addr
- self.port = port
+ self.port = port
self.key = key
self.keyid = keyid
@@ -1741,12 +1748,12 @@
# Drop message gets no tag, random payload
m = bfm(payload, DROP_TYPE, "", [self.server1], [self.server3])
-
+
def decoderDrop(p,t,self=self):
self.assertEquals(None, t)
self.failIf(BuildMessage._checkPayload(p))
return ""
-
+
self.do_message_test(m,
( (self.pk1,), None,
(SWAP_FWD_TYPE,),
@@ -1756,7 +1763,7 @@
("",) ),
"",
decoder=decoderDrop)
-
+
# Encrypted forward message
rsa1, rsa2 = self.pk1, self.pk512
@@ -1891,7 +1898,7 @@
"Version: 0.1\n"+
"xyz\n"+
"== END TYPE III REPLY BLOCK ==\n")
-
+
# Test decoding
seed = loc[:20]
prng = AESCounterPRNG(sha1(seed+"Tyrone SlothropGenerate")[:16])
@@ -2097,9 +2104,9 @@
self.server1 = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
self.server2 = FakeServerInfo("127.0.0.2", 3, self.pk2, "Z"*20)
self.server3 = FakeServerInfo("127.0.0.3", 5, self.pk3, "Q"*20)
- self.sp1 = PacketHandler(self.pk1, h)
- self.sp2 = PacketHandler(self.pk2, h)
- self.sp3 = PacketHandler(self.pk3, h)
+ self.sp1 = PacketHandler([self.pk1], [h])
+ self.sp2 = PacketHandler([self.pk2], [h])
+ self.sp3 = PacketHandler([self.pk3], [h])
self.sp2_3 = PacketHandler((self.pk2,self.pk3), (h,h))
def tearDown(self):
@@ -2151,14 +2158,14 @@
# A one-hop/one-hop message.
m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server1], [self.server3])
-
+
self.do_test_chain(m,
[self.sp1,self.sp3],
[FWD_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
p)
-
+
# Try servers with multiple keys
m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server2], [self.server3])
self.do_test_chain(m, [self.sp2_3, self.sp2_3], [FWD_TYPE, SMTP_TYPE],
@@ -2192,7 +2199,7 @@
p = "That gum you like, it's coming back in style."
m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server1], [self.server3])
-
+
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
[FWD_TYPE, SMTP_TYPE],
@@ -2255,7 +2262,7 @@
self.assertEquals(len(pkt.getContents()), 28*1024)
self.assertEquals(encodeBase64(pkt.getContents()),
pkt.getAsciiContents())
-
+
def test_rejected(self):
bfm = BuildMessage.buildForwardMessage
brm = BuildMessage.buildReplyMessage
@@ -2367,8 +2374,8 @@
class TestDeliveryQueue(DeliveryQueue):
- def __init__(self,d):
- DeliveryQueue.__init__(self,d)
+ def __init__(self,d,now=None):
+ DeliveryQueue.__init__(self,d,now=now)
self._msgs = None
def sendReadyMessages(self, *x, **y):
self._msgs = None
@@ -2511,28 +2518,29 @@
def testDeliveryQueues(self):
d_d = mix_mktemp("qd")
- queue = TestDeliveryQueue(d_d)
+ now = 10000 # time.time()
+ queue = TestDeliveryQueue(d_d, now)
queue.setRetrySchedule([10, 10, 10, 10]) # Retry up to 40 sec.
- now = time.time()
# First, make sure the queue stores messages correctly.
- h1 = queue.queueDeliveryMessage("Message 1")
- h2 = queue.queueDeliveryMessage("Message 2")
- self.assertEquals((0, "Message 1", 0), queue.get(h1))
+ h1 = queue.queueDeliveryMessage("Message 1", now)
+ h2 = queue.queueDeliveryMessage("Message 2", now)
+ self.assertEquals(("Message 1", now, None, now), queue._inspect(h1))
+ self.assertEquals(("Message 2", now, None, now), queue._inspect(h2))
# Call sendReadyMessages to begin 'sending' msg1 and msg2.
queue.sendReadyMessages(now)
msgs = queue._msgs
self.assertEquals(2, len(msgs))
# _deliverMessages should have gotten them both.
- self.failUnless((h1, "Message 1", 0) in msgs)
- self.failUnless((h2, "Message 2", 0) in msgs)
+ self.failUnless((h1, "Message 1") in msgs)
+ self.failUnless((h2, "Message 2") in msgs)
# Add msg3, and acknowledge that msg1 succeeded. msg2 is now in limbo
- h3 = queue.queueDeliveryMessage("Message 3")
+ h3 = queue.queueDeliveryMessage("Message 3", now)
queue.deliverySucceeded(h1)
# Only msg3 should get sent out, since msg2 is still in progress.
queue.sendReadyMessages(now+1)
msgs = queue._msgs
- self.assertEquals([(h3, "Message 3", 0)], msgs)
+ self.assertEquals([(h3, "Message 3")], msgs)
# Now, make sure that msg1 is gone from the pool.
allHandles = queue.getAllMessages()
@@ -2544,11 +2552,23 @@
# Now, fail msg2 retriably, and fail msg3 hard. Only one message
# should be left. (It will have a different handle from the old
# msg2.)
- queue.deliveryFailed(h2, retriable=1)
- queue.deliveryFailed(h3, retriable=0)
+ queue.deliveryFailed(h2, retriable=1, now=now+4)
+ queue.deliveryFailed(h3, retriable=0, now=now+4)
allHandles = queue.getAllMessages()
h4 = allHandles[0]
+ queue.cleanQueue()
+ files = os.listdir(d_d)
+ files.sort()
+ self.assertEquals(files, ["meta_"+h4, "msg_"+h4])
self.assertEquals([h4], queue.getAllMessages())
+ self.assertEquals(("Message 2", now, now, now+10), queue._inspect(h2))
+
+ # Reload the queue; and try that again.
+ queue = TestDeliveryQueue(d_d, now+4)
+ queue.setRetrySchedule([10, 10, 10, 10]) # Retry up to 40 sec.
+ self.assertEquals([h4], queue.getAllMessages())
+ self.assertEquals(("Message 2", now, now, now+10), queue._inspect(h2))
+
# When we try to send messages again after 5 seconds, nothing happens.
queue.sendReadyMessages(now+5)
msgs = queue._msgs
@@ -2556,30 +2576,47 @@
# When we try to send again after after 11 seconds, message 2 fires.
queue.sendReadyMessages(now+11)
msgs = queue._msgs
- self.assertEquals([(h4, "Message 2", 1)], msgs)
- self.assertNotEquals(h2, h4)
- queue.deliveryFailed(h4, retriable=1)
- # At 30 seconds, message 2 fires.
+ self.assertEquals([(h4, "Message 2")], msgs)
+ self.assertEquals(h2, h4)
+ queue.deliveryFailed(h4, retriable=1, now=now+15)
+ self.assertEquals(("Message 2", now, now+11, now+20),
+ queue._inspect(h2))
+ # At 31 seconds, message 2 fires.
h5 = queue.getAllMessages()[0]
- queue.sendReadyMessages(now+30)
+ queue.sendReadyMessages(now+31)
msgs = queue._msgs
- self.assertEquals([(h5, "Message 2", 2)], msgs)
- self.assertNotEquals(h5, h4)
- queue.deliveryFailed(h5, retriable=1)
+ self.assertEquals([(h5, "Message 2")], msgs)
+ self.assertEquals(h5, h4)
+ queue.deliveryFailed(h5, retriable=1, now=now+33)
+ self.assertEquals(("Message 2", now, now+31, now+40),
+ queue._inspect(h2))
# At 45 sec, it fires one last time. It will have gotten up to #4
# already.
h6 = queue.getAllMessages()[0]
queue.sendReadyMessages(now+45)
msgs = queue._msgs
- self.assertEquals([(h6, "Message 2", 4)], msgs)
- self.assertNotEquals(h6, h5)
- queue.deliveryFailed(h6, retriable=1)
+ self.assertEquals([(h6, "Message 2")], msgs)
+ self.assertEquals(h6, h5)
+ queue.deliveryFailed(h6, retriable=1, now=now+100)
# Now Message 2 is timed out.
self.assertEquals([], queue.getAllMessages())
-
+
queue.removeAll()
queue.cleanQueue()
+ # Make sure old-style messages get nuked.
+ writePickled(os.path.join(d_d, "msg_ABCDEFGH"),
+ (5, None, "xyzzy", 6))
+ try:
+ suspendLog("TRACE")
+ queue = TestDeliveryQueue(d_d, now+4)
+ finally:
+ s = resumeLog()
+ self.assert_(stringContains(s, "No metadata for file handle ABCDEFGH"))
+ self.assert_(stringContains(s, "Removing item ABCDEFGH"))
+ queue.setRetrySchedule([10, 10, 10, 10]) # Retry up to 40 sec.
+ self.assertEquals([], queue.getAllMessages())
+
def testMixPools(self):
d_m = mix_mktemp("qm")
@@ -2711,17 +2748,21 @@
class FileParanoiaTests(unittest.TestCase):
- def testPrivateDirs(self):
- # Pick a private directory under tempdir, but don't create it.
- noia = mix_mktemp("noia")
+ def ensureParanoia(self, whatkind):
tempdir = mixminion.testSupport._MM_TESTING_TEMPDIR
# If our tempdir doesn't exist and isn't private, we can't go on.
try:
checkPrivateDir(tempdir)
except MixFatalError, e:
- self.fail("Can't test directory paranoia, because something's\n"
- +" wrong with %s: %s"%(tempdir, str(e)))
+ self.fail(("Can't test %s paranoia, because something's\n"
+ +" wrong with %s: %s")%(whatkind, tempdir, str(e)))
+
+ def testPrivateDirs(self):
+ self.ensureParanoia("directory")
+
+ # Pick a private directory under tempdir, but don't create it.
+ noia = mix_mktemp("noia")
# Nonexistant directory.
self.failUnlessRaises(MixFatalError, checkPrivateDir, noia)
@@ -2809,6 +2850,61 @@
if old_mask is not None:
os.umask(old_mask)
+ def testPrivateFiles(self):
+ self.ensureParanoia("file")
+ dir = mix_mktemp()
+ os.mkdir(dir, 0700)
+ subdir = os.path.join(dir, "subdir")
+ os.mkdir(subdir, 0777)
+
+ # File doesn't exist.
+ self.failUnlessRaises(MixFatalError,
+ checkPrivateFile, os.path.join(dir, "x"))
+
+ # Parent not private.
+ subdir_x = os.path.join(subdir, "x")
+ writeFile(subdir_x, "zzz")
+ os.chmod(subdir_x, 0600)
+ self.failUnlessRaises(MixFatalError,
+ checkPrivateFile, subdir_x)
+
+ # File not owned by us. (???)
+ if os.getuid() == 0:
+ os.chmod(subdir_x, 0600)
+ os.chown(subdir_x, 1)
+ self.failUnlessRaises(MixFatalError, checkPrivateFile, subdir_x)
+ os.chown(subdir_x, 0)
+ else:
+ if os.path.exists("/etc/shadow"):
+ self.failUnlessRaises(MixFatalError, checkPrivateFile,
+ "/etc/shadow")
+
+ # File not private (fix, noex)
+ os.chmod(subdir, 0700)
+ os.chmod(subdir_x, 0777)
+ try:
+ suspendLog()
+ checkPrivateFile(subdir_x)
+ finally:
+ s = resumeLog()
+ self.assertEquals(0700, os.stat(subdir_x)[stat.ST_MODE] & 0777)
+ self.assert_(stringContains(s,
+ "Repairing permissions on file "+subdir_x))
+
+ os.chmod(subdir_x, 0606)
+ try:
+ suspendLog()
+ checkPrivateFile(subdir_x)
+ finally:
+ s = resumeLog()
+ self.assertEquals(0600, os.stat(subdir_x)[stat.ST_MODE] & 0777)
+ self.assert_(stringContains(s,
+ "Repairing permissions on file "+subdir_x))
+
+ # File not private, nofix.
+ os.chmod(subdir_x, 0701)
+ self.failUnlessRaises(MixFatalError, checkPrivateFile, subdir_x, 0)
+
#----------------------------------------------------------------------
# SIGHANDLERS
# FFFF Write tests here
@@ -2816,7 +2912,6 @@
#----------------------------------------------------------------------
# MMTP
-# FFFF Write more tests
# Run on a different port so we don't conflict with any actual servers
# running on this machine.
@@ -2971,12 +3066,12 @@
t.join()
def testStallingTransmission(self):
- # XXXX004 I know this works, but there doesn't seem to be a good
- # XXXX004 way to test it. It's hard to open a connection that
- # XXXX004 will surely stall. For now, I'm disabling this test.
+ # XXXX I know this works, but there doesn't seem to be a good
+ # XXXX way to test it. It's hard to open a connection that
+ # XXXX will surely stall. For now, I'm disabling this test.
if 1:
return
-
+
def threadfn(pausing):
# helper fn to run in a different thread: bind a socket,
# but don't listen.
@@ -2994,7 +3089,7 @@
pausing = [4]
t = threading.Thread(None, threadfn, args=(pausing,))
t.start()
-
+
now = time.time()
timedout = 0
try:
@@ -3009,7 +3104,7 @@
passed = time.time() - now
pausing[0] = 0
t.join()
-
+
self.assert_(passed < 2)
self.assert_(timedout)
@@ -3053,7 +3148,7 @@
self.failUnless(len(c) == 1)
self.failUnless(startTime <= c[0].lastActivity <= endTime)
self.assertEquals(2, server.nJunkPackets)
-
+
# Again, with bad keyid.
clientcon = mixminion.server.MMTPServer.MMTPClientConnection(
_getTLSContext(0), "127.0.0.1", TEST_PORT, "Z"*20,
@@ -3074,7 +3169,7 @@
t.join()
finally:
resumeLog() #unsuppress warning
-
+
def _testTimeout(self):
server, listener, messagesIn, keyid = _getMMTPServer()
self.listener = listener
@@ -3160,7 +3255,7 @@
except mixminion.Common.MixProtocolReject:
ok[0] = 1
done[0] = 1
-
+
t = threading.Thread(None, _t)
t.start()
while not done[0]:
@@ -3348,14 +3443,20 @@
self.assertEquals(C._parseServerMode(" relay "), "relay")
self.assertEquals(C._parseServerMode("Local"), "local")
# interval
- self.assertEquals(C._parseInterval(" 1 sec "), (1,"second", 1))
- self.assertEquals(C._parseInterval(" 99 sec "), (99,"second", 99))
- self.failUnless(floatEq(C._parseInterval("1.5 minutes")[2],
+ self.assertEquals(str(C._parseInterval(" 1 sec ")),"1 second")
+ self.assertEquals(str(C._parseInterval(" 99 sec ")),"99 seconds")
+ self.failUnless(floatEq(float(C._parseInterval("1.5 minutes")),
90))
- self.assertEquals(C._parseInterval("2 houRS"), (2,"hour",7200))
+ h2 = C._parseInterval("2 houRS")
+ m120 = C._parseInterval("120 minutes")
+ self.assertEquals(str(h2), "2 hours")
+ self.assertEquals(str(m120), "120 minutes")
+ self.assert_(int(h2) == int(m120) == 7200)
+ m120.reduce()
+ self.assertEquals(str(m120), "2 hours")
# IntervalList
self.assertEquals(C._parseIntervalList(" 5 sec, 1 min, 2 hours"),
- [ 5, 60, 7200 ])#XXXX mode
+ [ 5, 60, 7200 ])
self.assertEquals([5,5,5,5,5,5, 8*3600,8*3600,8*3600,8*3600,],
C._parseIntervalList("5 sec for 30 sec, 8 hours for 1.3 days"))
self.assertEquals([60], C._parseIntervalList("1 min for 1 min"))
@@ -3482,12 +3583,11 @@
# IntervalSet validation
def warns(mixInterval, retryList, self=self):
- ents = { "Section":
- [('Retry', mixminion.Config._parseIntervalList(retryList))]}
+ ent = mixminion.Config._parseIntervalList(retryList)
try:
suspendLog()
mixminion.server.ServerConfig._validateRetrySchedule(
- mixInterval, ents, "Section")
+ mixInterval, ent, "Section")
finally:
r = resumeLog()
self.assert_(stringContains(r, "[WARN]"))
@@ -3650,7 +3750,12 @@
# Make sure we accept an extra key.
inf2 = inf+"Unexpected-Key: foo\n"
inf2 = mixminion.ServerInfo.signServerInfo(inf2, identity)
- mixminion.ServerInfo.ServerInfo(string=inf2)
+ try:
+ suspendLog()
+ mixminion.ServerInfo.ServerInfo(string=inf2)
+ finally:
+ s = resumeLog()
+ self.assert_(stringContains(s,"Unrecognized key Unexpected-Key on line"))
# Now make sure everything was saved properly
keydir = os.path.join(d, "key_key1")
@@ -3736,6 +3841,40 @@
except ConfigError, p:
self.assertEquals(str(p), "Unrecognized descriptor version 0.99")
+ # Try regenerating server descriptor with existing keys.
+ key2 = mixminion.server.ServerKeys.ServerKeyset(d, "key2", d)
+ key2.load()
+ try:
+ suspendLog()
+ conf = mixminion.server.ServerConfig.ServerConfig(
+ string=(SERVER_CONFIG_SHORT%mix_mktemp())+
+ """[Incoming/MMTP]
+Enabled: yes
+IP: 192.168.100.3
+[Delivery/SMTP]
+Enabled: yes
+ReturnAddress: X@Y.Z
+""")
+ finally:
+ resumeLog()
+
+ inf3 = generateServerDescriptorAndKeys(conf,
+ identity,
+ d,
+ "key2",
+ d,
+ useServerKeys=1)
+
+ key3 = mixminion.server.ServerKeys.ServerKeyset(d, "key2", d)
+ key3.load()
+ info3 = key3.getServerDescriptor()
+ self.assertEquals(key3.getMMTPKey().get_public_key(),
+ key2.getMMTPKey().get_public_key())
+ self.assertEquals(key3.getPacketKey().get_public_key(),
+ key2.getPacketKey().get_public_key())
+ eq(info3['Incoming/MMTP']['IP'], "192.168.100.3")
+ self.assert_('smtp' in info3.getCaps())
+
def test_directory(self):
eq = self.assertEquals
examples = getExampleServerDescriptors()
@@ -3794,7 +3933,7 @@
self.failUnlessRaises(ConfigError, ServerDirectory, dBad)
# Bad signature.
dBad = re.compile(r"^DirectorySignature: ........", re.M).sub(
- "Directory: ZZZZZZZZ", d)
+ "DirectorySignature: ZZZZZZZZ", d)
self.failUnlessRaises(ConfigError, ServerDirectory, dBad)
# Can we use messed-up spaces and line-endings?
@@ -4014,7 +4153,7 @@
ES.log.lastSave = pm-1800
ES.log._setNextRotation(now=pm-1800)
eq(ES.log.getNextRotation(), pm+3600)
-
+
# 2) Rotation interval is not a multiple of hours: We don't round
# 2A) Accumulated time << interval
ES.log.rotateInterval = 40*60
@@ -4046,10 +4185,12 @@
def getConfigSyntax(self):
return { "Example" : { "Foo" : ("REQUIRE",
mixminion.Config._parseInt, None) } }
- def validateConfig(self, cfg, entries, lines, contents):
+ def validateConfig(self, cfg, lines, contents):
if cfg['Example'] is not None:
if cfg['Example'].get('Foo',1) % 2 == 0:
raise ConfigError("Foo was even")
+ def getRetrySchedule(self):
+ return [.1] *100
def configure(self,cfg, manager):
if cfg['Example']:
self.enabled = 1
@@ -4141,6 +4282,7 @@
# It should have processed all three.
self.assertEquals(3, len(exampleMod.processedMessages))
# If we try to send agin, the second message should get re-sent.
+ time.sleep(.15) # so we retry.
manager.sendReadyMessages()
self.assertEquals(1, queue.count())
self.assertEquals(4, len(exampleMod.processedMessages))
@@ -4555,7 +4697,8 @@
module.configure({'Testing/DirectoryDump' : {}}, manager)
self.assert_(not manager.queues.has_key('Testing_DirectoryDump'))
module.configure({'Testing/DirectoryDump' :
- {'Location': dir, 'UseQueue' : 0}}, manager)
+ {'Location': dir, 'UseQueue' : 0,
+ 'Retry': [60]}}, manager)
# Try sending a couple of messages.
queue = manager.queues['Testing_DirectoryDump']
p1 = FDP('plain',0xFFFE, "addr1","this is the message","t"*20)
@@ -4594,7 +4737,8 @@
module = mixminion.testSupport.DirectoryStoreModule()
# This time, use a queue.
module.configure({'Testing/DirectoryDump' :
- {'Location': dir, 'UseQueue' : 1}}, manager)
+ {'Location': dir, 'UseQueue' : 1,
+ 'Retry': [60]}}, manager)
# Do we skip over the missing messages?
self.assertEquals(module.next, 91)
self.assertEquals(len(os.listdir(dir)), 3)
@@ -4603,7 +4747,7 @@
FDP('plain',0xFFFE, "addr91", "This is message 91"))
queue.queueDeliveryMessage(
FDP('plain',0xFFFE, "addr92", "This is message 92"))
- queue.queueDeliveryMessage(
+ h3 = queue.queueDeliveryMessage(
FDP('plain',0xFFFE, "fail", "This is message 93"))
queue.queueDeliveryMessage(
FDP('plain',0xFFFE, "FAIL!", "This is message 94"))
@@ -4666,6 +4810,7 @@
class ServerKeysTests(unittest.TestCase):
def testServerKeyring(self):
+ #XXXX004 rethink this
keyring = _getServerKeyring()
home = _FAKE_HOME
@@ -4699,17 +4844,17 @@
keyring.createKeys(2)
# Check the first key we created
- va, vu, curKey = keyring._getLiveKey()
+ va, vu, curKey = keyring._getLiveKeys()[0]
self.assertEquals(va, start)
self.assertEquals(vu, finish)
- self.assertEquals(vu, keyring.getNextKeyRotation())
self.assertEquals(curKey, "0001")
- keyset = keyring.getServerKeyset()
+ keyset = keyring.getServerKeysets()[0]
self.assertEquals(keyset.getHashLogFileName(),
os.path.join(home, "work", "hashlogs", "hash_0001"))
+ self.assertEquals(vu, keyring.getNextKeyRotation())
# Check the second key we created.
- va, vu, curKey = keyring._getLiveKey(vu + 3600)
+ va, vu, curKey = keyring._getLiveKeys(vu + 3600)[0]
self.assertEquals(va, finish)
self.assertEquals(vu, mixminion.Common.previousMidnight(
finish+10*24*60*60+60))
@@ -4734,15 +4879,47 @@
self.assertEquals(f, f2)
# Test getTLSContext
- keyring.getTLSContext()
+ keyring._getTLSContext()
# Test getPacketHandler
- _ = keyring.getPacketHandler()
+ #_ = keyring.getPacketHandler()
#----------------------------------------------------------------------
class ServerMainTests(unittest.TestCase):
+ def testScheduler(self):
+ _Scheduler = mixminion.server.ServerMain._Scheduler
+ _RecurringEvent = mixminion.server.ServerMain._RecurringEvent
+ lst=[]
+ def a(lst=lst): lst.append('a')
+ def b(lst=lst): lst.append('b')
+ def c(lst=lst): lst.append('c')
+ def d(lst=lst): lst.append('d')
+ def e(lst=lst): lst.append('e')
+ s = _Scheduler()
+ self.assertEquals(s.firstEventTime(), -1)
+ tm = time.time()
+
+ s.scheduleOnce(tm+1, "A", a)
+ s.scheduleOnce(tm+2, "B", b)
+ self.assertEquals(s.firstEventTime(), tm+1)
+ s.processEvents(tm+1)
+ self.assertEquals(['a'],lst)
+ del lst[:]
+
+ s.scheduleRecurring(tm+1.5, 1, "C", c)
+ s.scheduleOnce(tm+1.9, "D", d)
+ self.assertEquals(s.firstEventTime(), tm+1.5)
+ s.processEvents(tm+1.5)
+ self.assertEquals(["c"], lst)
+ diff = abs(s.firstEventTime()-(time.time()+1))
+ self.assert_(diff < 0.01)
+
+ s.processEvents(tm+5)
+ self.assertEquals(["c", "c", "d", "b"], lst)
+
+
def testMixPool(self):
ServerConfig = mixminion.server.ServerConfig.ServerConfig
MixPool = mixminion.server.ServerMain.MixPool
@@ -5029,12 +5206,6 @@
n += 1
return n
- def allUnique(lst):
- d = {}
- for item in lst:
- d[item] = 1
- return len(d) == len(lst)
-
# Override ks.DEFAULT_REQUIRED_LIFETIME so we don't need to
# explicitly specify a really early endAt all the time.
ks.DEFAULT_REQUIRED_LIFETIME = 1
@@ -5048,43 +5219,35 @@
try:
### Try out getPath.
# 1. Fully-specified paths.
- p = ks.getPath(startServers=("Joe", "Lisa"),
- endServers=("Alice", "Joe"))
- p = ks.getPath(startServers=("Joe", "Lisa", "Alice", "Joe"))
- p = ks.getPath(endServers=("Joe", "Lisa", "Alice", "Joe"))
+ p = ks.getPath(None, ['Joe', 'Lisa', 'Alice', 'Joe'])
# 2. Partly-specified paths...
# 2a. With plenty of servers
- p = ks.getPath(length=2)
+ p = ks.getPath(None, [None, None])
eq(2, len(p))
neq(p[0].getNickname(), p[1].getNickname())
- p = ks.getPath(startServers=("Joe",), length=3)
+ p = ks.getPath(None, ["Joe", None, None])
eq(3, len(p))
self.assertSameSD(p[0], joe[0])
- self.assert_(allUnique([s.getNickname() for s in p]))
neq(p[1].getNickname(), "Joe")
neq(p[2].getNickname(), "Joe")
neq(p[1].getNickname(), p[2].getNickname())
- p = ks.getPath(endServers=("Joe",), length=3)
+ p = ks.getPath(None, [None, None, "Joe"])
eq(3, len(p))
self.assertSameSD(joe[0], p[2])
- self.assert_(allUnique([s.getNickname() for s in p]))
- p = ks.getPath(startServers=("Alice",),endServers=("Joe",),
- length=4)
+ p = ks.getPath(None, ["Alice", None, None, "Joe"])
eq(4, len(p))
self.assertSameSD(alice[0], p[0])
self.assertSameSD(joe[0], p[3])
nicks = [ s.getNickname() for s in p ]
- eq(1, nicks.count("Alice"))
- eq(1, nicks.count("Joe"))
+ self.assert_(nicks.count("Alice")>=1)
+ self.assert_(nicks.count("Joe")>=1)
neq(nicks[1],nicks[2])
- self.assert_(allUnique([s.getNickname() for s in p]))
- p = ks.getPath(startServers=("Joe",),endServers=("Alice","Joe"),
- length=4)
+ p = ks.getPath(None, ["Joe", None, "Alice", "Joe"])
eq(4, len(p))
self.assertSameSD(alice[0], p[2])
self.assertSameSD(joe[0], p[0])
@@ -5098,23 +5261,22 @@
ks2.importFromFile(os.path.join(impdirname, "Lisa1"))
ks2.importFromFile(os.path.join(impdirname, "Bob0"))
- p = ks2.getPath(length=9)
+ p = ks2.getPath(None, [None]*9)
eq(9, len(p))
self.failIf(nRuns([s.getNickname() for s in p]))
- p = ks2.getPath(startServers=("Joe",),endServers=("Joe",),
- length=8)
+ p = ks2.getPath(None, ["Joe"]+[None]*6+["Joe"])
self.failIf(nRuns([s.getNickname() for s in p]))
eq(8, len(p))
self.assertSameSD(joe[0], p[0])
self.assertSameSD(joe[0], p[-1])
- p = ks2.getPath(startServers=("Joe",),length=7)
+ p = ks2.getPath(None, ["Joe"]+[None]*6)
self.failIf(nRuns([s.getNickname() for s in p]))
eq(7, len(p))
self.assertSameSD(joe[0], p[0])
- p = ks2.getPath(endServers=("Joe",),length=7)
+ p = ks2.getPath(None, [None]*6+["Joe"])
self.failIf(nRuns([s.getNickname() for s in p]))
eq(7, len(p))
self.assertSameSD(joe[0], p[-1])
@@ -5122,30 +5284,30 @@
# 2c. With 2 servers
ks2.expungeByNickname("Alice")
ks2.expungeByNickname("Bob")
- p = ks2.getPath(length=4)
+ p = ks2.getPath(None, [None]*4)
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
- p = ks2.getPath(length=4,startServers=("Joe",))
+ p = ks2.getPath(None, ["Joe",None,None,None])
self.failIf(nRuns([s.getNickname() for s in p]) > 2)
- p = ks2.getPath(length=4, endServers=("Joe",))
+ p = ks2.getPath(None, [None, None, None, "Joe"])
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
- p = ks2.getPath(length=6, endServers=("Joe",))
+ p = ks2.getPath(None, [None,None,None,None,None, "Joe"])
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
# 2d. With only 1.
ks2.expungeByNickname("Lisa")
- p = ks2.getPath(length=4)
- eq(len(p), 2)
- p = ks2.getPath(length=4, startServers=("Joe",))
- eq(len(p), 3)
- p = ks2.getPath(length=4, endServers=("Joe",))
- eq(len(p), 2)
+ p = ks2.getPath(None,[None]*4)
+ eq(len(p), 4)
+ p = ks2.getPath(None,["Joe",None,None,None])
+ eq(len(p), 4)
+ p = ks2.getPath(None,[None,None,None,"Joe"])
+ eq(len(p), 4)
# 2e. With 0
self.assertRaises(MixError, ks.getPath,
- length=4, startAt=now+100*oneDay)
+ None, [None]*4, startAt=now+100*oneDay)
finally:
s = resumeLog()
self.assertEquals(4, s.count("Not enough servers for distinct"))
@@ -5153,22 +5315,20 @@
self.assertEquals(3, s.count("Only one relay known"))
# 3. With capabilities.
- p = ks.getPath(length=5, endCap="smtp", midCap="relay")
+ p = ks.getPath("smtp", [None]*5)
eq(5, len(p))
self.assertSameSD(p[-1], joe[0]) # Only Joe has SMTP
- p = ks.getPath(length=4, endCap="mbox", midCap="relay")
+ p = ks.getPath("mbox", [None]*4)
eq(4, len(p))
self.assertSameSD(p[-1], lola[1]) # Only Lola has MBOX
- p = ks.getPath(length=5, endCap="mbox", midCap="relay",
- startServers=("Alice",))
+ p = ks.getPath("mbox", ["Alice", None, None, None, None])
eq(5, len(p))
self.assertSameSD(p[-1], lola[1]) # Only Lola has MBOX
self.assertSameSD(p[0], alice[0])
- p = ks.getPath(length=5, endCap="mbox", midCap="relay",
- endServers=("Alice",))
+ p = ks.getPath("mbox", [None,None,None,None, "Alice"])
eq(5, len(p))
self.assertSameSD(p[-1], alice[0]) # We ignore endCap with endServers
@@ -5212,19 +5372,15 @@
fredfile = os.path.join(impdirname, "Fred1")
p1,p2 = ppath(ks, None, "Alice,%s,Bob,Joe"%fredfile, email)
pathIs((p1,p2), ((alice,fred),(bob,joe)))
- p1,p2 = ppath(ks, None, "Alice,Fred,Bob,Joe", email, nHops=4, nSwap=1)
- pathIs((p1,p2), ((alice,fred),(bob,joe)))
- p1,p2 = ppath(ks, None, "Alice,Fred,Bob,Lola,Joe", email, nHops=5,
- nSwap=1)
- pathIs((p1,p2), ((alice,fred),(bob,lola,joe)))
p1,p2 = ppath(ks, None, "Alice,Fred,Bob,Lola,Joe", email, nHops=5)
pathIs((p1,p2), ((alice,fred,bob),(lola,joe)))
p1,p2 = ppath(ks, None, "Alice,Fred,Bob", mboxWithServer)
pathIs((p1,p2), ((alice,fred),(bob,lola)))
p1,p2 = ppath(ks, None, "Alice,Fred,Bob,Lola", mboxWithoutServer)
pathIs((p1,p2), ((alice,fred),(bob,lola)))
- p1,p2 = ppath(ks, None, "Alice,Fred,Bob", mboxWithServer, nSwap=0)
- pathIs((p1,p2), ((alice,),(fred,bob,lola)))
+ p1,p2 = ppath(ks, None, "Alice,?,?,Bob", mboxWithServer)
+ eq((len(p1),len(p2)), (2,3))
+ pathIs((p1[:1],p2[-2:]), ((alice,),(bob,lola)))
# 1b. Colon, no star
p1,p2 = ppath(ks, None, "Alice:Fred,Joe", email)
@@ -5233,10 +5389,11 @@
pathIs((p1,p2), ((alice,),(bob,fred,joe)))
p1,p2 = ppath(ks, None, "Alice,Bob,Fred:Joe", email)
pathIs((p1,p2), ((alice,bob,fred),(joe,)))
+ p1,p2 = ppath(ks, None, "Alice,Bob,?:Joe", email)
+ eq((len(p1),len(p2)), (3,1))
+ pathIs((p1[:-1],p2), ((alice,bob),(joe,)))
p1,p2 = ppath(ks, None, "Alice,Bob,Fred:Joe", email, nHops=4)
pathIs((p1,p2), ((alice,bob,fred),(joe,)))
- p1,p2 = ppath(ks, None, "Alice,Bob,Fred:Joe", email, nSwap=2)
- pathIs((p1,p2), ((alice,bob,fred),(joe,)))
p1,p2 = ppath(ks, None, "Alice,Bob,Fred:Joe", mboxWithServer)
pathIs((p1,p2), ((alice,bob,fred),(joe,lola)))
p1,p2 = ppath(ks, None, "Alice,Bob,Fred:Lola", mboxWithoutServer)
@@ -5244,71 +5401,67 @@
# 1c. Star, no colon
p1,p2 = ppath(ks, None, 'Alice,*,Joe', email, nHops=5)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p2[-1]), (alice, joe))
eq((len(p1),len(p2)), (3,2))
p1,p2 = ppath(ks, None, 'Alice,Bob,*,Joe', email, nHops=6)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p1[1],p2[-1]), (alice, bob, joe))
eq((len(p1),len(p2)), (3,3))
p1,p2 = ppath(ks, None, 'Alice,Bob,*', email, nHops=6)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p1[1],p2[-1]), (alice, bob, joe))
eq((len(p1),len(p2)), (3,3))
p1,p2 = ppath(ks, None, '*,Bob,Joe', email) #default nHops=6
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p2[-2],p2[-1]), (bob, joe))
eq((len(p1),len(p2)), (3,3))
- p1,p2 = ppath(ks, None, 'Bob,*,Alice', mboxWithServer) #default nHops=6
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
+ p1,p2 = ppath(ks, None, 'Bob,*,Alice', mboxWithServer, nHops=5)
pathIs((p1[0],p2[-2],p2[-1]), (bob, alice, lola))
eq((len(p1),len(p2)), (3,3))
p1,p2 = ppath(ks, None, 'Bob,*,Alice,Lola', mboxWithoutServer)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p2[-2],p2[-1]), (bob, alice, lola))
eq((len(p1),len(p2)), (3,3))
# 1d. Star and colon
- p1,p2 = ppath(ks, None, 'Bob:*,Alice', mboxWithServer)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
+ p1,p2 = ppath(ks, None, 'Bob:*,Alice', mboxWithServer, nHops=5)
pathIs((p1[0],p2[-2],p2[-1]), (bob, alice, lola))
eq((len(p1),len(p2)), (1,5))
- p1,p2 = ppath(ks, None, 'Bob,*:Alice', mboxWithServer)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
+ p1,p2 = ppath(ks, None, 'Bob,*:Alice', mboxWithServer, nHops=5)
pathIs((p1[0],p2[-2],p2[-1]), (bob, alice, lola))
eq((len(p1),len(p2)), (4,2))
- p1,p2 = ppath(ks, None, 'Bob,*,Joe:Alice', mboxWithServer)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
+ p1,p2 = ppath(ks, None, 'Bob,*,Joe:Alice', mboxWithServer, nHops=5)
pathIs((p1[0],p1[-1],p2[-2],p2[-1]), (bob, joe, alice, lola))
eq((len(p1),len(p2)), (4,2))
p1,p2 = ppath(ks, None, 'Bob,*,Lola:Alice,Joe', email)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p1[-1],p2[-2],p2[-1]), (bob, lola, alice, joe))
eq((len(p1),len(p2)), (4,2))
p1,p2 = ppath(ks, None, '*,Lola:Alice,Joe', email)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[-1],p2[-2],p2[-1]), (lola, alice, joe))
eq((len(p1),len(p2)), (4,2))
p1,p2 = ppath(ks, None, 'Lola:Alice,*', email)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
pathIs((p1[0],p2[0],p2[-1]), (lola, alice, joe))
eq((len(p1),len(p2)), (1,5))
- p1,p2 = ppath(ks, None, 'Bob:Alice,*', mboxWithServer)
- self.assert_(allUnique([s.getNickname() for s in p1+p2]))
+ p1,p2 = ppath(ks, None, 'Bob:Alice,*', mboxWithServer, nHops=5)
pathIs((p1[0],p2[0],p2[-1]), (bob, alice, lola))
eq((len(p1),len(p2)), (1,5))
+ # 1e. Complex.
+ try:
+ suspendLog()
+ p1,p2 = ppath(ks, None, '?,Bob,*:Joe,*2,Joe', email, nHops=9)
+ finally:
+ resumeLog()
+ pathIs((p1[1],p2[0],p2[-1]), (bob, joe, joe))
+ eq((len(p1),len(p2)), (5,4))
+
# 2. Failing cases
raises = self.assertRaises
# Nonexistant server
@@ -5320,8 +5473,6 @@
raises(MixError, ppath, ks, None, "Alice:Bob,Fred", mboxWithoutServer)
# Two stars.
raises(MixError, ppath, ks, None, "Alice,*,Bob,*,Joe", email)
- # Swap point mismatch
- raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nSwap=1)
# NHops mismatch
raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=2)
raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=4)
@@ -5567,8 +5718,7 @@
tc = loader.loadTestsFromTestCase
if 0:
- suite.addTest(tc(MMTPTests))
- #suite.addTest(tc(MiscTests))
+ suite.addTest(tc(ClientMainTests))
return suite
suite.addTest(tc(MiscTests))
Index: testSupport.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/testSupport.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- testSupport.py 9 Feb 2003 22:30:58 -0000 1.14
+++ testSupport.py 17 May 2003 00:08:44 -0000 1.15
@@ -15,7 +15,7 @@
import mixminion.Crypto
import mixminion.Common
from mixminion.Common import waitForChildren, createPrivateDir, LOG
-from mixminion.Config import _parseBoolean, ConfigError
+from mixminion.Config import _parseBoolean, _parseIntervalList, ConfigError
from mixminion.server.Modules import DELIVER_FAIL_NORETRY, DELIVER_FAIL_RETRY,\
DELIVER_OK, DeliveryModule, ImmediateDeliveryQueue, \
@@ -35,6 +35,9 @@
Otherwise, creates a file in the specified directory, containing
the routing info, a newline, and the message contents.
"""
+ def __init__(self):
+ DeliveryModule.__init__(self)
+
## Fields:
# loc -- The directory to store files in. All filenames are numbers;
# we always put new messages in the smallest number greater than
@@ -43,18 +46,22 @@
def getConfigSyntax(self):
return { 'Testing/DirectoryDump':
{ 'Location' : ('REQUIRE', None, None),
- 'UseQueue': ('REQUIRE', _parseBoolean, None) } }
+ 'UseQueue': ('REQUIRE', _parseBoolean, None),
+ 'Retry' : ('ALLOW', _parseIntervalList,
+ "every 1 min for 10 min") } }
- def validateConfig(self, sections, entries, lines, contents):
+ def validateConfig(self, config, lines, contents):
# loc = sections['Testing/DirectoryDump'].get('Location')
pass
+ def getRetrySchedule(self):
+ return self.retry
+
def configure(self, config, manager):
self.loc = config['Testing/DirectoryDump'].get('Location')
if not self.loc:
return
self.useQueue = config['Testing/DirectoryDump']['UseQueue']
- manager.enableModule(self)
if not os.path.exists(self.loc):
createPrivateDir(self.loc)
@@ -65,6 +72,9 @@
max = int(f)
self.next = max+1
+ self.retry = config['Testing/DirectoryDump']['Retry']
+ manager.enableModule(self)
+
def getServerInfoBlock(self):
return ""
@@ -76,7 +86,8 @@
def createDeliveryQueue(self, queueDir):
if self.useQueue:
- return SimpleModuleDeliveryQueue(self, queueDir)
+ return SimpleModuleDeliveryQueue(self, queueDir,
+ retrySchedule=self.retry)
else:
return ImmediateDeliveryQueue(self)