[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Batch of changes for 0.0.3rc1
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv18039/lib/mixminion
Modified Files:
ClientMain.py Main.py Packet.py ServerInfo.py __init__.py
test.py
Log Message:
Batch of changes for 0.0.3rc1
README:
- Document changes and new features in 0.0.3
setup.py:
- Bump version number to 0.0.3rc1
ClientMain:
- Change directory URL, so that 0.0.3 clients will look for a
directory at a different place.
- Add support for recommended versions
- Update directory cache format
- Finish documentation
- Notice early if we have no unused SURBs
Main:
- Fix bug in 'mixminion version'.
Packet:
- Increment packet version number
ServerInfo, ServerList:
- Add 'Recommended-Software' section to directories.
test:
- Temporarily disable testStallingTransmission
- Add test for counter mode consistency
ServerMain:
- documentation
aes_ctr.c:
- Break backward compatibility by fixing bug in counter mode.
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -u -d -r1.55 -r1.56
--- ClientMain.py 13 Feb 2003 07:39:25 -0000 1.55
+++ ClientMain.py 13 Feb 2003 10:56:40 -0000 1.56
@@ -37,11 +37,9 @@
MBOX_TYPE, SMTP_TYPE, DROP_TYPE
# FFFF This should be made configurable and adjustable.
-MIXMINION_DIRECTORY_URL = "http://www.mixminion.net/directory/latest.gz"
+MIXMINION_DIRECTORY_URL = "http://www.mixminion.net/directory/directory.gz"
MIXMINION_DIRECTORY_FINGERPRINT = "CD80DD1B8BE7CA2E13C928D57499992D56579CCD"
-#DOCDOC Replace raise MixError with raise UIError as appropriate
-
#----------------------------------------------------------------------
# Global variable; holds an instance of Common.Lockfile used to prevent
# concurrent access to the directory cache, message pool, or SURB log.
@@ -80,12 +78,15 @@
# list of (ServerInfo, source) tuples.
# allServers: Same as byCapability[None]
# __scanning: Flag to prevent recursive invocation of self.rescan().
+ # clientVersions: String of allowable client versions as retrieved
+ # from most recent directory.
## Layout:
- # DIR/cache: A cPickled tuple of ("ClientKeystore-0",
- # lastModified, lastDownload, serverlist, digestMap)
+ # DIR/cache: A cPickled tuple of ("ClientKeystore-0.1",
+ # lastModified, lastDownload, clientVersions, serverlist,
+ # digestMap)
# DIR/dir.gz *or* DIR/dir: A (possibly gzipped) directory file.
# DIR/imported/: A directory of server descriptors.
- MAGIC = "ClientKeystore-0"
+ MAGIC = "ClientKeystore-0.1"
# The amount of time to require a path to be valid, by default.
DEFAULT_REQUIRED_LIFETIME = 3600
@@ -138,8 +139,9 @@
try:
infile = urllib.FancyURLopener().open(url)
except IOError, e:
- #FFFF003 suggest "-D no"
- raise UIError("Couldn't connect to directory server: %s"%e)
+ raise UIError(
+ ("Couldn't connect to directory server: %s.\n"
+ "Try '-D no' to run without downloading a directory.")%e)
# Open a temporary output file.
if url.endswith(".gz"):
fname = os.path.join(self.dir, "dir_new.gz")
@@ -191,6 +193,7 @@
"""Regenerate the cache based on files on the disk."""
self.lastModified = self.lastDownload = -1
self.serverList = []
+ self.clientVersions = None
if force:
self.digestMap = {}
@@ -211,8 +214,11 @@
for s in directory.getServers():
self.serverList.append((s, 'D'))
self.digestMap[s.getDigest()] = 'D'
- break
+ self.clientVersions = (
+ directory['Recommended-Software'].get("MixminionClient"))
+ break
+
# Now check the server in DIR/servers.
serverDir = os.path.join(self.dir, "imported")
createPrivateDir(serverDir)
@@ -243,10 +249,11 @@
try:
f = open(os.path.join(self.dir, "cache"), 'rb')
cached = cPickle.load(f)
- magic, self.lastModified, self.lastDownload, self.serverList, \
- self.digestMap = cached
f.close()
+ magic = cached[0]
if magic == self.MAGIC:
+ _, self.lastModified, self.lastDownload, self.clientVersions, \
+ self.serverList, self.digestMap = cached
self.__rebuildTables()
return
else:
@@ -268,7 +275,8 @@
pass
f = open(fname, 'wb')
cPickle.dump((self.MAGIC,
- self.lastModified, self.lastDownload, self.serverList,
+ self.lastModified, self.lastDownload,
+ self.clientVersions, self.serverList,
self.digestMap),
f, 1)
f.close()
@@ -631,6 +639,40 @@
return startServers + midServers + endServers
+ def checkClientVersion(self):
+ """Check the current client's version against the stated version in
+ the most recently downloaded directory; print a warning if this
+ version isn't listed as recommended.
+ """
+ if not self.clientVersions:
+ return
+ allowed = self.clientVersions.split()
+ current = mixminion.__version__
+ if current in allowed:
+ # This version is recommended.
+ return
+ current_t = mixminion.version_info
+ more_recent_exists = 0
+ most_recent = current_t
+ for a in allowed:
+ try:
+ t = mixminion.parse_version_string(a)
+ except:
+ LOG.warn("Couldn't parse recommended version %s", a)
+ continue
+ try:
+ if mixminion.cmp_versions(current_t, t) < 0:
+ more_recent_exists = 1
+ most_recent = a
+ except ValueError:
+ pass
+ if more_recent_exists:
+ LOG.warn("This software may be obsolete; "
+ "You should consider upgrading.")
+ else:
+ LOG.warn("This software is newer than any version "
+ "on the recommended list.")
+
def resolvePath(directory, address, enterPath, exitPath,
nHops, nSwap, startAt=None, endAt=None, halfPath=0):
"""Compute a two-leg validated path from options as entered on
@@ -984,8 +1026,8 @@
else:
f = sys.stderr
while 1:
- p1 = getpass.getpass(s1)
- p2 = getpass.getpass(s2)
+ p1 = self._getPassword(s1)
+ p2 = self._getPassword(s2)
if p1 == p2:
return p1
f.write("Passwords do not match.\n")
@@ -1013,11 +1055,14 @@
#UserDir: ~/.mixminion
[Security]
-##DOCDOC
-PathLength: 4
+## Default length of forward message paths.
+#PathLength: 4
+## Address to use by default when generating reply blocks
#SURBAddress: <your address here>
-#SURBPathLength: 3 DOCDOC
-#SURBLifetime: 7 days DOCDOC
+## Default length of paths for reply blocks
+#SURBPathLength: 3
+## Deault reply block lifetime
+#SURBLifetime: 7 days
[Network]
ConnectionTimeout: 20 seconds
@@ -1029,7 +1074,8 @@
"""A SURBLog manipulates a database on disk to remember which SURBs we've
used, so we don't reuse them accidentally.
"""
- #XXXX003 testme
+ # XXXX004 write unit tests
+
#FFFF Using this feature should be optional.
##Fields
@@ -1051,10 +1097,39 @@
lastCleaned = int(self.log['LAST_CLEANED'])
except (KeyError, ValueError):
lastCleaned = 0
-
+
+ forceClean = 1
if lastCleaned < time.time()-24*60*60 or forceClean:
self.clean()
+ def findUnusedSURB(self, surbList, verbose=0,now=None):
+ """Given a list of ReplyBlock objects, find the first that is neither
+ expired, about to expire, or used in the past. Return None if
+ no such reply block exists."""
+ if now is None:
+ now = time.time()
+ for surb in surbList:
+ expiry = surb.timestamp
+ timeLeft = expiry - now
+ if self.isSURBUsed(surb):
+ if verbose:
+ LOG.warn("Skipping used reply block")
+ continue
+ elif timeLeft < 60:
+ if verbose:
+ LOG.warn("Skipping expired reply (expired at %s)",
+ formatTime(expiry, 1))
+ continue
+ elif timeLeft < 3*60*60:
+ if verbose:
+ LOG.warn("Skipping soon-to-expire reply block "
+ "(%s hrs, %s min left)",
+ floorDiv(timeLeft, 60), int(timeLeft % 60))
+ continue
+
+ return surb
+ return None
+
def close(self):
"""Release resources associated with the surblog."""
self.log.close()
@@ -1108,7 +1183,7 @@
# packet was inserted into the pool.
# )
- # XXXX003 testme
+ # XXXX004 write unit tests
def __init__(self, directory, prng=None):
"""Create a new ClientPool object, storing packets in 'directory'
@@ -1246,8 +1321,7 @@
pool it and exit.
forceNoPool -- if true, do not pool the message even if delivery
fails."""
-
- #XXXX003 testme
+ #XXXX004 write unit tests
message, firstHop = \
self.generateReplyMessage(payload, servers, surbList)
@@ -1266,7 +1340,7 @@
expiryTime -- if provided, a time at which the replyBlock must
still be valid, and after which it should not be used.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
key = self.keys.getSURBKey(create=1)
exitType, exitInfo, _ = address.getRouting()
@@ -1284,8 +1358,7 @@
messages)
servers1,servers2 -- lists of ServerInfo.
"""
-
- #XXXX003 testme
+ #XXXX004 write unit tests
routingType, routingInfo, _ = address.getRouting()
LOG.info("Generating payload...")
msg = mixminion.BuildMessage.buildForwardMessage(
@@ -1305,34 +1378,22 @@
the path. We use the first one that is neither expired nor
used, and mark it used.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
if now is None:
now = time.time()
clientLock()
surbLog = self.openSURBLog()
try:
- for surb in surbList:
- expiry = surb.timestamp
- timeLeft = expiry - now
- if surbLog.isSURBUsed(surb):
- LOG.warn("Skipping used reply block")
- continue
- elif timeLeft < 60:
- LOG.warn("Skipping expired reply (expired at %s)",
- formatTime(expiry, 1))
- continue
- elif timeLeft < 3*60*30:
- LOG.warn("Reply block will expire in %s hours, %s minutes",
- floorDiv(timeLeft, 60), int(timeLeft % 60))
- continue
-
- LOG.info("Generating payload...")
- msg = mixminion.BuildMessage.buildReplyMessage(
- payload, servers, surb, self.prng)
+ surb = surbLog.findUnusedSURB(surbList, verbose=1, now=now)
+ if surb is None:
+ raise UIError("No usable reply blocks found; all were used or expired.")
- surbLog.markSURBUsed(surb)
- return msg, servers[0]
- raise UIError("No usable SURBs found; all were used or expired.")
+ LOG.info("Generating packet...")
+ msg = mixminion.BuildMessage.buildReplyMessage(
+ payload, servers, surb, self.prng)
+
+ surbLog.markSURBUsed(surb)
+ return msg, servers[0]
finally:
surbLog.close()
clientUnlock()
@@ -1356,7 +1417,7 @@
If warnIfLost is true, log a warning if we fail to deliver
the message, and we don't pool it.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
LOG.info("Connecting...")
timeout = self.config['Network'].get('ConnectionTimeout')
if timeout:
@@ -1395,7 +1456,8 @@
def flushPool(self):
"""Try to send end all messages in the queue to their destinations.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
+
LOG.info("Flushing message pool")
# XXXX This is inefficient in space!
clientLock()
@@ -1433,7 +1495,7 @@
"""Insert all the messages in msgList into the pool, to be sent
to the server identified by the IPV4Info object 'routing'.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
LOG.trace("Pooling messages")
handles = []
try:
@@ -1456,7 +1518,7 @@
Raise ParseError on malformatted messages. Unless 'force' is
true, do not uncompress possible zlib bombs.
"""
- #XXXX003 testme
+ #XXXX004 write unit tests
results = []
idx = 0
while idx < len(s):
@@ -1786,6 +1848,9 @@
finally:
clientUnlock()
+ if self.wantClientDirectory or self.wantDownload:
+ self.directory.checkClientVersion()
+
def parsePath(self):
"""Parse the path specified on the command line and generate a
new list of servers to be retrieved by getForwardPath or
@@ -1982,6 +2047,17 @@
path1, path2 = parser.getForwardPath()
address = parser.address
+ if parser.usingSURBList and inFile in ('-', None):
+ # We check to make sure that we have a valid SURB before reading
+ # from stdin.
+ surblog = client.openSURBLog()
+ try:
+ s = surblog.findUnusedSURB(parser.path2)
+ if s is None:
+ raise UIError("No unused, unexpired reply blocks found.")
+ finally:
+ surblog.close()
+
# XXXX Clean up this ugly control structure.
if address and inFile is None and address.getRouting()[0] == DROP_TYPE:
payload = None
@@ -2179,7 +2255,7 @@
if outputFile == '-':
out = sys.stdout
else:
- # ????003 Should we sometimes open this in text mode?
+ # ???? Should we sometimes open this in text mode?
out = open(outputFile, 'wb')
if inputFile == '-':
@@ -2271,13 +2347,13 @@
wantDownload=1, wantReplyPath=1)
except UsageError, e:
e.dump()
- print _GENERATE_SURB_USAGE % cmd
+ print _GENERATE_SURB_USAGE % { 'cmd' : cmd }
sys.exit(0)
if args:
print >>sys.stderr, "ERROR: Unexpected arguments"
- print _GENERATE_SURB_USAGE % cmd
- sys.exit(1)
+ print _GENERATE_SURB_USAGE % { 'cmd' : cmd }
+ sys.exit(0)
parser.init()
@@ -2376,7 +2452,7 @@
wantClient=1)
except UsageError, e:
e.dump()
- print _FLUSH_POOL_USAGE % cmd
+ print _FLUSH_POOL_USAGE % { 'cmd' : cmd }
sys.exit(1)
parser.init()
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- Main.py 13 Feb 2003 06:30:22 -0000 1.30
+++ Main.py 13 Feb 2003 10:56:40 -0000 1.31
@@ -161,7 +161,7 @@
"For help on sending a message, run 'mixminion send --help'"
)
-def printVersion():
+def printVersion(cmd,args):
import mixminion
print "Mixminion version %s" % mixminion.__version__
print ("Copyright 2002-2003 Nick Mathewson. "+
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.32
retrieving revision 1.33
diff -u -d -r1.32 -r1.33
--- Packet.py 13 Feb 2003 06:30:22 -0000 1.32
+++ Packet.py 13 Feb 2003 10:56:40 -0000 1.33
@@ -39,7 +39,7 @@
import mixminion._zlibutil as zlibutil
# Major and minor number for the understood packet format.
-MAJOR_NO, MINOR_NO = 0,1 #XXXX003 Bump minor_no for 0.0.3
+MAJOR_NO, MINOR_NO = 0,2
# Length of a Mixminion message
MESSAGE_LEN = 1 << 15
@@ -440,8 +440,8 @@
Raise ParseError on failure.
"""
blocks = []
- #DOCDOC
while 1:
+ # Skip over any whitespace before or after the reply blocks.
while s and s[0] in ' \t\r\n':
s = s[1:]
if not s:
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.37
retrieving revision 1.38
diff -u -d -r1.37 -r1.38
--- ServerInfo.py 17 Jan 2003 06:18:06 -0000 1.37
+++ ServerInfo.py 13 Feb 2003 10:56:40 -0000 1.38
@@ -330,6 +330,9 @@
def __getitem__(self, item):
return self.header[item]
+ def get(self, item, default=None):
+ return self.header.get(item, default)
+
class _DirectoryHeader(mixminion.Config._ConfigFile):
"""Internal object: used to parse, validate, and store fields in a
directory's header sections.
@@ -351,7 +354,10 @@
"DirectoryIdentity": ("REQUIRE", C._parsePublicKey, None),
"DirectoryDigest": ("REQUIRE", C._parseBase64, None),
"DirectorySignature": ("REQUIRE", C._parseBase64, None),
- }
+ },
+ 'Recommended-Software': {"__SECTION__": ("ALLOW", None, None),
+ "MixminionClient": ("ALLOW", None, None),
+ "MixminionServer": ("ALLOW", None, None), }
}
def __init__(self, contents, expectedDigest):
"""Parse a directory header out of a provided string; validate it
Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -d -r1.26 -r1.27
--- __init__.py 13 Feb 2003 07:40:26 -0000 1.26
+++ __init__.py 13 Feb 2003 10:56:40 -0000 1.27
@@ -7,7 +7,7 @@
"""
# This version string is generated from setup.py; don't edit it.
-__version__ = "0.0.3alpha"
+__version__ = "0.0.3rc1"
# This 5-tuple encodes the version number for comparison. Don't edit it.
# The first 3 numbers are the version number; the 4th is:
# 0 for alpha
@@ -18,7 +18,7 @@
# The 4th or 5th number may be a string. If so, it is not meant to
# succeed or preceed any other sub-version with the same a.b.c version
# number.
-version_info = (0, 0, 3, 0, -1)
+version_info = (0, 0, 3, 99, 1)
__all__ = [ 'server', 'directory' ]
def version_tuple_to_string(t):
@@ -49,6 +49,8 @@
import re
r = re.compile(r'(\d+)\.(\d+)\.(\d+)(?:([^\d\(]+|\(\d+\))(\d+)?)?')
m = r.match(s)
+ if not m:
+ raise ValueError
major, minor, sub, status, patch = m.groups()
if not status or status in ('.', 'p'):
status = 100
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.86
retrieving revision 1.87
diff -u -d -r1.86 -r1.87
--- test.py 13 Feb 2003 07:40:26 -0000 1.86
+++ test.py 13 Feb 2003 10:56:40 -0000 1.87
@@ -573,6 +573,12 @@
self.failUnless(crypt(key, " "*100, 0)[1:] == crypt(key, " "*99, 1))
self.failUnless(crypt(key, " "*100, 0)[30:] == crypt(key, " "*70, 30))
+ # Make sure ctr_crypt works the same everywhere.
+ expected2 = hexread("351DA02F1CF68C4BED393BC71274D181892FC420CA9E9995"
+ "C6E5E9744920020DB854019CB1CEB6BAD055C64F60E63B91"
+ "5917930EB30972BCB3942E6904252F26")
+ self.failUnless(crypt(key, " "*64, 0xABCD) == expected2)
+
# Counter mode is its own inverse
self.failUnless(crypt(key,crypt(key, " "*100, 0),0) == " "*100)
@@ -2977,6 +2983,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.
+ if 1:
+ return
+
def threadfn(pausing):
# helper fn to run in a different thread: bind a socket,
# but don't listen.