[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Bump copyrights, resolve xxxxs, minor cleanups
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv23596/lib/mixminion
Modified Files:
BuildMessage.py ClientMain.py Common.py Config.py
MMTPClient.py Main.py Packet.py __init__.py test.py
testSupport.py
Log Message:
Bump copyrights, resolve xxxxs, minor cleanups
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.38
retrieving revision 1.39
diff -u -d -r1.38 -r1.39
--- BuildMessage.py 5 Feb 2003 06:28:31 -0000 1.38
+++ BuildMessage.py 9 Feb 2003 22:30:58 -0000 1.39
@@ -255,7 +255,6 @@
we return None. If the payload is corrupt, we raise MixError.
"""
# FFFF Take a list of keys?
- # FFFF Allow callbacks?
if len(payload) != PAYLOAD_LEN or len(tag) != TAG_LEN:
raise MixError("Wrong payload or tag length")
@@ -609,7 +608,7 @@
# Uncompress the body.
contents = payload.getContents()
- # FFFF - We should make this rule configurable.
+ # ???? Should we make this rule configurable? I say no.
maxLen = max(20*1024, 20*len(contents))
return uncompressData(contents, maxLength=maxLen)
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.49
retrieving revision 1.50
diff -u -d -r1.49 -r1.50
--- ClientMain.py 7 Feb 2003 17:23:11 -0000 1.49
+++ ClientMain.py 9 Feb 2003 22:30:58 -0000 1.50
@@ -4,22 +4,11 @@
"""mixminion.ClientMain
Code for Mixminion command-line client.
-
- NOTE: THIS IS NOT THE FINAL VERSION OF THE CODE. It needs to
- support replies and end-to-end encryption.
"""
-__all__ = [ 'Address', 'ClientKeyring', 'ClientKeystore', 'MixminionClient',
+__all__ = [ 'Address', 'ClientKeyring', 'ClientDirectory', 'MixminionClient',
'parsePath', ]
-# (NOTE: The stuff in the next comment isn't implemented yet.)
-# The client needs to store:
-# - config
-# - keys for pending SURBs
-# - server directory
-# - Per-system directory location is a neat idea, but individual users
-# must check signature. That's a way better idea for later.
-
import anydbm
import binascii
import cPickle
@@ -52,24 +41,34 @@
MIXMINION_DIRECTORY_URL = "http://www.mixminion.net/directory/latest.gz"
MIXMINION_DIRECTORY_FINGERPRINT = "CD80DD1B8BE7CA2E13C928D57499992D56579CCD"
-
-
+# Global variable; holds an instance of Common.Lockfile used to prevent
+# concurrent access to the directory cache, message pool, or SURB log.
_CLIENT_LOCKFILE = None
def clientLock():
+ """DOCDOC"""
assert _CLIENT_LOCKFILE is not None
_CLIENT_LOCKFILE.acquire(blocking=1)
def clientUnlock():
+ """DOCDOC"""
_CLIENT_LOCKFILE.release()
def configureClientLock(filename):
+ """DOCDOC"""
global _CLIENT_LOCKFILE
_CLIENT_LOCKFILE = Lockfile(filename)
-#XXXX003 rename to server list
-class ClientKeystore:
- """A ClientKeystore manages a list of server descriptors, either
+class UIError(MixError):
+ "DOCDOC"
+ def dump(self):
+ if str(self): print "ERROR:", str(self)
+
+class UsageError(UIError):
+ "DOCDOC"
+
+class ClientDirectory:
+ """A ClientDirectory manages a list of server descriptors, either
imported from the command line or from a directory."""
##Fields:
# dir: directory where we store everything.
@@ -90,13 +89,13 @@
# lastModified, lastDownload, serverlist, digestMap)
# DIR/dir.gz *or* DIR/dir: A (possibly gzipped) directory file.
# DIR/imported/: A directory of server descriptors.
-
MAGIC = "ClientKeystore-0"
- #
+
+ #DOCDOC
DEFAULT_REQUIRED_LIFETIME = 3600
def __init__(self, directory):
- """Create a new ClientKeystore to keep directories and descriptors
+ """Create a new ClientDirectory to keep directories and descriptors
under <directory>."""
self.dir = directory
createPrivateDir(self.dir)
@@ -109,7 +108,6 @@
self.clean()
finally:
clientUnlock()
- #XXXX003 Check version against directory's Recommended-Software field.
# Mixminion 0.0.1 used an obsolete directory-full-of-servers in
# DIR/servers. If there's nothing there, we remove it. Otherwise,
@@ -433,6 +431,7 @@
now = time.time()
cutoff = now - 600
+ #DOCDOC
newServers = []
for info, where in self.serverList:
lcnickname = info.getNickname().lower()
@@ -473,6 +472,7 @@
if endAt is None:
endAt = startAt + self.DEFAULT_REQUIRED_LIFETIME
+ #DOCDOC
if isinstance(name, ServerInfo):
if name.isValidFrom(startAt, endAt):
return name
@@ -636,6 +636,8 @@
All descriptors chosen are valid from startAt to endAt. If the
specified descriptors don't support the required capabilities,
we raise MixError.
+
+ DOCDOC halfPath
"""
# First, find out what the exit node needs to be (or support).
if address is None:
@@ -664,14 +666,17 @@
midCap='relay', endCap=exitCap,
startAt=startAt, endAt=endAt)
+ #DOCDOC
for server in path[:-1]:
if "relay" not in server.getCaps():
raise MixError("Server %s does not support relay"
% server.getNickname())
+ #DOCDOC
if exitCap and exitCap not in path[-1].getCaps():
raise MixError("Server %s does not support %s"
% (path[-1].getNickname(), exitCap))
+ #DOCDOC
if nSwap is None:
nSwap = ceilDiv(len(path),2)-1
@@ -716,7 +721,7 @@
path, nHops must equal the path length; and if nHops is used _with_ a
star on the path, nHops must be >= the path length.
- DOCDOC halfpath, address=None
+ DOCDOC halfpath, address=None, defaultNHops
"""
if not path:
path = '*'
@@ -811,12 +816,15 @@
class ClientKeyring:
"DOCDOC"
+ #DOCDOC
+ # XXXX003 testme
def __init__(self, keyDir):
self.keyDir = keyDir
createPrivateDir(self.keyDir)
self.surbKey = None
def getSURBKey(self, create=0):
+ """DOCDOC"""
if self.surbKey is not None:
return self.surbKey
fn = os.path.join(self.keyDir, "SURBKey")
@@ -850,11 +858,14 @@
raise MixError("Invalid magic on key file")
def _save(self, fn, data, magic, password):
- # File holds magic, enc(sha1(password)[:16],data+sha1(data+magic))
- # XXXX Gosh, that's a lousy key scheme.
+ # File holds magic, salt (8 bytes), enc(key,data+sha1(data+salt+magic))
+ # where key = sha1(salt+password+salt)[:16]
+ salt = mixminion.Crypto.getCommonPRNG().getBytes(8)
+ key = sha1(salt+password+salt)[:16]
f = open(fn, 'wb')
f.write(magic)
- f.write(ctr_crypt(data+sha1(data+magic), sha1(password)[:16]))
+ f.write(salt)
+ f.write(ctr_crypt(data+sha1(data+salt+magic), key))
f.close()
def _load(self, fn, magic, password):
@@ -864,9 +875,16 @@
if not s.startswith(magic):
raise MixError("Invalid key file")
s = s[len(magic):]
- s = ctr_crypt(s, sha1(password)[:16])
+ if len(s) < 8:
+ raise MixError("Key file too short")
+ salt = s[:8]
+ s = s[8:]
+ if len(s) < 20:
+ raise MixError("Key file too short")
+ key = sha1(salt+password+salt)[:16]
+ s = ctr_crypt(s, key)
data, hash = s[:-20], s[-20:]
- if hash != sha1(data+magic):
+ if hash != sha1(data+salt+magic):
raise MixError("Incorrect password")
return data
@@ -907,8 +925,9 @@
#UserDir: ~/.mixminion
[Security]
+##DOCDOC
PathLength: 4
-#SURBAddress: XXXX003
+#SURBAddress: <your address here>
#SURBPathLength: 3 DOCDOC
#SURBLifetime: 7 days DOCDOC
@@ -919,7 +938,10 @@
f.close()
class SURBLog:
- "DOCDOC"
+ """DOCDOC"""
+ #DOCDOC
+ # XXXX003 testme
+
# DB holds HEX(hash) -> str(expiry)
def __init__(self, filename, forceClean=0):
parent, shortfn = os.path.split(filename)
@@ -932,6 +954,7 @@
self.clean()
def close(self):
+ """DOCDOC"""
self.log.close()
def isSURBUsed(self, surb):
@@ -961,6 +984,9 @@
class ClientPool:
"DOCDOC"
+ ## DOCDOC
+ # XXXX003 testme
+
def __init__(self, directory, prng=None):
self.dir = directory
createPrivateDir(directory)
@@ -1028,6 +1054,7 @@
# prng: A pseudo-random number generator for padding and path selection
# keyDir: DOCDOC
# surbKey: DOCDOC
+ # pool: DOCDOC
def __init__(self, conf):
"""Create a new MixminionClient with a given configuration"""
self.config = conf
@@ -1066,6 +1093,7 @@
"""
DOCDOC pool options
"""
+ #XXXX003 testme
message, firstHop = \
self.generateReplyMessage(payload, servers, surbList)
@@ -1079,6 +1107,7 @@
"""
DOCDOC
"""
+ #XXXX003 testme
key = self.keys.getSURBKey(create=1)
exitType, exitInfo, _ = address.getRouting()
@@ -1097,6 +1126,7 @@
path1,path2 -- lists of servers.
"""
+ #XXXX003 testme
routingType, routingInfo, _ = address.getRouting()
LOG.info("Generating payload...")
msg = mixminion.BuildMessage.buildForwardMessage(
@@ -1108,6 +1138,7 @@
"""
DOCDOC
"""
+ #XXXX003 testme
if now is None:
now = time.time()
clientLock()
@@ -1143,6 +1174,7 @@
warnIfLost=1):
"""Given a list of packets and a ServerInfo object, sends the
packets to the server via MMTP"""
+ #XXXX003 testme
LOG.info("Connecting...")
timeout = self.config['Network'].get('ConnectionTimeout')
if timeout:
@@ -1178,6 +1210,10 @@
raise MixError("Error sending packets: %s" % e)
def flushPool(self):
+ """
+ DOCDOC pool options
+ """
+ #XXXX003 testme
LOG.info("Flushing message pool")
# XXXX This is inefficient in space!
clientLock()
@@ -1212,6 +1248,10 @@
LOG.info("Pool flushed")
def poolMessages(self, msgList, server):
+ """
+ DOCDOC
+ """
+ #XXXX003 testme
LOG.trace("Pooling messages")
handles = []
try:
@@ -1225,8 +1265,11 @@
return handles
def decodeMessage(self, s, force=0):
- "DOCDOC"
- #XXXX003 DOCDOC Exceptions
+ """DOCDOC
+
+ Raises ParseError
+ """
+ #XXXX003 testme
results = []
idx = 0
while idx < len(s):
@@ -1335,17 +1378,13 @@
sys.exit(1)
return None #suppress pychecker warning
-class UsageError(MixError):
- "DOCDOC"
- def dump(self):
- if str(self): print "ERROR:", str(self)
-
class CLIArgumentParser:
"DOCDOC"
def __init__(self, opts,
wantConfig=0, wantKeystore=0, wantClient=0, wantLog=0,
wantDownload=0, wantForwardPath=0, wantReplyPath=0,
minHops=0):
+ """DOCDOC"""
self.config = None
self.keystore = None
self.client = None
@@ -1437,6 +1476,7 @@
self.forceNoPool = 1
def init(self):
+ """DOCDOC"""
if self.wantConfig:
self.config = readConfigFile(self.configFile)
if self.wantLog:
@@ -1462,7 +1502,7 @@
if self.wantKeystore:
assert self.wantConfig
LOG.debug("Configuring server list")
- self.keystore = ClientKeystore(userdir)
+ self.keystore = ClientDirectory(userdir)
if self.wantDownload:
assert self.wantKeystore
@@ -1474,6 +1514,7 @@
clientUnlock()
def parsePath(self):
+ """DOCDOC"""
if self.wantReplyPath and self.address is None:
address = self.config['Security'].get('SURBAddress')
if address is None:
@@ -1491,11 +1532,13 @@
f = open(self.replyBlock, 'rb')
s = f.read()
f.close()
- if stringContains(s, "== BEGIN TYPE III REPLY BLOCK =="):
- #????003 catch exceptions
- surbs = parseTextReplyBlocks(s)
- else:
- surbs = parseReplyBlocks(s)
+ try:
+ if stringContains(s, "== BEGIN TYPE III REPLY BLOCK =="):
+ surbs = parseTextReplyBlocks(s)
+ else:
+ surbs = parseReplyBlocks(s)
+ except ParseError, e:
+ raise UIError("Error parsing %s: %s" % (self.replyBlock, e))
else:
assert self.address is not None
useRB = 0
@@ -1539,9 +1582,11 @@
",".join([ s.getNickname() for s in self.path2 ]))
def getForwardPath(self):
+ """DOCDOC"""
return self.path1, self.path2
def getReplyPath(self):
+ """DOCDOC"""
return self.path1
_SEND_USAGE = """\
@@ -1598,6 +1643,7 @@
# NOTE: This isn't the final client interface. Many or all options will
# change between now and 1.0.0
def runClient(cmd, args):
+ #DOCDOC Comment this message
if cmd.endswith(" client"):
print "The 'client' command is deprecated. Use 'send' instead."
poolMode = 0
@@ -1628,10 +1674,11 @@
raise UsageError("Can't use --no-pool option with pool command")
if parser.forcePool and parser.forceNoPool:
raise UsageError("Can't use both --pool and --no-pool")
- except UsageError, e:
+ except UIError, e:
e.dump()
usageAndExit(cmd)
+ # FFFF Make pooling configurable from .mixminionrc
forcePool = poolMode or parser.forcePool
forceNoPool = parser.forceNoPool
@@ -1640,7 +1687,7 @@
try:
parser.parsePath()
- except UsageError, e:
+ except UIError, e:
e.dump()
sys.exit(1)
@@ -1695,7 +1742,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantKeystore=1,
wantLog=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _IMPORT_SERVER_USAGE %cmd
sys.exit(1)
@@ -1735,7 +1782,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantKeystore=1,
wantLog=1, wantDownload=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _LIST_SERVERS_USAGE % cmd
sys.exit(1)
@@ -1761,7 +1808,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantKeystore=1,
wantLog=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _UPDATE_SERVERS_USAGE % cmd
sys.exit(1)
@@ -1776,7 +1823,7 @@
print "Directory updated"
_CLIENT_DECODE_USAGE = """\
-Usage: %s [options] <files>
+Usage: %s [options] -i <file>|--input=<file>
Options:
-h, --help: Print this usage message and exit.
-v, --verbose Display extra debugging messages.
@@ -1785,9 +1832,11 @@
-F, --force: Decode the input files, even if they seem
overcompressed.
-o <file>, --output=<file> Write the results to <file> rather than stdout.
+
""".strip()
def clientDecode(cmd, args):
+ #DOCDOC Comment me
options, args = getopt.getopt(args, "hvf:o:Fi:",
['help', 'verbose', 'config=',
'output=', 'force', 'input='])
@@ -1806,7 +1855,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantClient=1,
wantLog=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _CLIENT_DECODE_USAGE %cmd
sys.exit(1)
@@ -1833,8 +1882,13 @@
f.close()
except OSError, e:
LOG.error("Could not read file %s: %s", inputFile, e)
- # XXXX003 catch exceptions
- res = client.decodeMessage(s, force=force)
+ try:
+ res = client.decodeMessage(s, force=force)
+ except ParseError, e:
+ print "Couldn't parse message: %s"%e
+ out.close()
+ sys.exit(1)
+
for r in res:
out.write(r)
out.close()
@@ -1842,25 +1896,35 @@
_GENERATE_SURB_USAGE = """\
Usage: %s [options]
This space is temporarily left blank.
+ DOCDOC
"""
def generateSURB(cmd, args):
- options, args = getopt.getopt(args, "hvf:D:t:H:P:o:b",
+ #DOCDOC Comment me
+ options, args = getopt.getopt(args, "hvf:D:t:H:P:o:bn:",
['help', 'verbose', 'config=', 'download-directory=',
'to=', 'hops=', 'path=', 'lifetime=',
- 'output=', 'binary'])
+ 'output=', 'binary', 'count='])
outputFile = '-'
binary = 0
+ count = 1
for o,v in options:
if o in ('-o', '--output'):
outputFile = v
elif o in ('-b', '--binary'):
binary = 1
+ elif 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, wantClient=1,
wantLog=1, wantKeystore=1, wantDownload=1,
wantReplyPath=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _GENERATE_SURB_USAGE % cmd
sys.exit(1)
@@ -1875,7 +1939,7 @@
try:
parser.parsePath()
- except UsageError, e:
+ except UIError, e:
e.dump()
sys.exit(1)
@@ -1885,17 +1949,20 @@
if outputFile == '-':
out = sys.stdout
elif binary:
- #XXXX003 handle exception
out = open(outputFile, 'wb')
else:
- #XXXX003 handle exception
out = open(outputFile, 'w')
- surb = client.generateReplyBlock(address, path1, parser.endTime)
- if binary:
- out.write(surb.pack())
- else:
- out.write(surb.packAsText())
+ for i in xrange(count):
+ surb = client.generateReplyBlock(address, path1, parser.endTime)
+ if binary:
+ out.write(surb.pack())
+ else:
+ out.write(surb.packAsText())
+ if i != count-1:
+ parser.parsePath()
+ path1 = parser.getReplyPath()
+
out.close()
_INSPECT_SURBS_USAGE = """\
@@ -1910,7 +1977,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _INSPECT_SURBS_USAGE % cmd
sys.exit(1)
@@ -1921,14 +1988,18 @@
f = open(fn, 'rb')
s = f.read()
f.close()
- if stringContains(s, "== BEGIN TYPE III REPLY BLOCK =="):
- #????003 catch exceptions
- surbs = parseTextReplyBlocks(s)
- else:
- surbs = [ parseReplyBlock(s) ]
print "==== %s"%fn
- for surb in surbs:
- print surb.format()
+ try:
+ if stringContains(s, "== BEGIN TYPE III REPLY BLOCK =="):
+ surbs = parseTextReplyBlocks(s)
+ else:
+ surbs = parseReplyBlocks(s)
+
+ for surb in surbs:
+ print surb.format()
+ except ParseError, e:
+ print "Error while parsing: %s"%e
+
_FLUSH_POOL_USAGE = """\
Usage: %s [options]
@@ -1942,7 +2013,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
wantClient=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _FLUSH_POOL_USAGE % cmd
sys.exit(1)
@@ -1965,7 +2036,7 @@
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
wantClient=1)
- except UsageError, e:
+ except UIError, e:
e.dump()
print _LIST_POOL_USAGE % cmd
sys.exit(1)
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.56
retrieving revision 1.57
diff -u -d -r1.56 -r1.57
--- Common.py 7 Feb 2003 17:23:11 -0000 1.56
+++ Common.py 9 Feb 2003 22:30:58 -0000 1.57
@@ -122,7 +122,7 @@
checkPrivateDir(d)
-_WARNED_DIRECTORIES = {} # ???? Threading danger?
+_WARNED_DIRECTORIES = {}
def checkPrivateDir(d, recurse=1):
"""Return true iff d is a directory owned by this uid, set to mode
@@ -299,7 +299,6 @@
'Helper function. Returns current local time formatted for log.'
t = time.time()
return "%s.%03d"%(time.strftime("%b %d %H:%M:%S", time.localtime(t)),
- # ???? There is probably a faster way to do this.
(t*1000)%1000)
class _FileLogHandler:
@@ -865,6 +864,7 @@
class Lockfile:
"DOCDOC"
def __init__(self, filename):
+ "DOCDOC"
self.filename = filename
self.count = 0
self.fd = None
@@ -889,6 +889,7 @@
raise
def release(self):
+ "DOCDOC"
assert self.fd is not None
self.count -= 1
if self.count > 0:
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.38
retrieving revision 1.39
diff -u -d -r1.38 -r1.39
--- Config.py 5 Feb 2003 05:34:55 -0000 1.38
+++ Config.py 9 Feb 2003 22:30:58 -0000 1.39
@@ -316,7 +316,6 @@
return calendar.timegm((yyyy,MM,dd,hh,mm,ss,0,0,0))
-# ????003 What should this actually be?
_NICKNAME_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
"abcdefghijklmnopqrstuvwxyz"+
"0123456789_.@-")
@@ -324,7 +323,7 @@
def _parseNickname(s):
"""Validation function. Returns true iff s contains a valoid
server nickname-- that is, a string of 1..128 characters,
- containing only the characters [A-Za-z0-9_@] and '-'.
+ containing only the characters [A-Za-z0-9_@], '.' or '-'.
"""
s = s.strip()
bad = s.translate(mixminion.Common._ALLCHARS, _NICKNAME_CHARS)
Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- MMTPClient.py 6 Feb 2003 20:20:03 -0000 1.20
+++ MMTPClient.py 9 Feb 2003 22:30:58 -0000 1.21
@@ -1,4 +1,4 @@
-# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
+# Copyright 2002-2003 Nick Mathewson. See LICENSE for licensing information.
# $Id$
"""mixminion.MMTPClient
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.27
retrieving revision 1.28
diff -u -d -r1.27 -r1.28
--- Main.py 6 Feb 2003 20:20:03 -0000 1.27
+++ Main.py 9 Feb 2003 22:30:58 -0000 1.28
@@ -143,6 +143,7 @@
" decode [Decode or decrypt a received message]\n"+
" generate-surb [Generate a single-use reply block]\n"+
" inspect-surbs [DOCDOC]\n"+
+ " ???? DOCDOC what else ????\n"+
" (For Servers)\n"+
" server [Begin running a Mixminon server]\n"+
" server-keygen [Generate keys for a Mixminion server]\n"+
@@ -160,6 +161,8 @@
print "Mixminion version %s" % mixminion.__version__
print ("Copyright 2002-2003 Nick Mathewson. "+
"See LICENSE for licensing information.")
+ print "NOTE: This software is for testing only. The user set is too small"
+ print " to be anonymous, and the code is too alpha to be reliable."
def printUsage():
import mixminion
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.28
retrieving revision 1.29
diff -u -d -r1.28 -r1.29
--- Packet.py 6 Feb 2003 20:20:03 -0000 1.28
+++ Packet.py 9 Feb 2003 22:30:58 -0000 1.29
@@ -1,4 +1,4 @@
-# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
+# Copyright 2002-2003 Nick Mathewson. See LICENSE for licensing information.
# $Id$
"""mixminion.Packet
@@ -20,8 +20,9 @@
'Subheader', 'TAG_LEN', 'TextEncodedMessage',
'getTotalBlocksForRoutingInfoLen', 'parseHeader', 'parseIPV4Info',
'parseMBOXInfo', 'parseMessage', 'parsePayload', 'parseReplyBlock',
- 'parseSMTPInfo', 'parseSubheader', 'parseTextEncodedMessage',
- 'parseTextReplyBlocks', 'uncompressData' ]
+ 'parseReplyBlocks', 'parseSMTPInfo', 'parseSubheader',
+ 'parseTextEncodedMessage', 'parseTextReplyBlocks', 'uncompressData'
+ ]
import base64
import binascii
@@ -402,34 +403,42 @@
# routingInfo for the last server.
RB_UNPACK_PATTERN = "!4sBBL%dsHH%ss" % (HEADER_LEN, SECRET_LEN)
MIN_RB_LEN = 30+HEADER_LEN
-# XXXX003 handle input with differing number of ='s.
RB_TEXT_START = "======= BEGIN TYPE III REPLY BLOCK ======="
RB_TEXT_END = "======== END TYPE III REPLY BLOCK ========"
-RB_TEXT_RE = re.compile(RB_TEXT_START+
- r'[\r\n]+Version: (\d+.\d+)\s*[\r\n]+(.*)[\r\n]+'+
- RB_TEXT_END, re.M)
+# XXXX Use a better pattern here.
+RB_TEXT_RE = re.compile(r"==+ BEGIN TYPE III REPLY BLOCK ==+"+
+ r'[\r\n]+Version: (\d+\.\d+)\s*[\r\n]+(.*?)'+
+ r"==+ END TYPE III REPLY BLOCK ==+", re.M|re.DOTALL)
def parseTextReplyBlocks(s):
- """DOCDOC"""
+ """Given a string holding one or more text-encoded reply blocks,
+ return a list containing the reply blocks. Raise ParseError on
+ failure."""
idx = 0
blocks = []
while 1:
- idx = s.find(RB_TEXT_START, idx)
- if idx == -1:
+ m = RB_TEXT_RE.search(s[idx:])
+ if m is None:
+ # FFFF Better errors on malformatted reply blocks.
break
- m = RB_TEXT_RE.match(s, idx)
- if not m:
- raise ParseError("Misformatted reply block")
version, text = m.group(1), m.group(2)
+ idx += m.end()
if version != '0.1':
- LOG.warn("Unrecognized reply block version: %s", version)
- val = binascii.a2b_base64(text)
+ LOG.warn("Skipping reply block with unrecognized version: %s",
+ version)
+ continue
+ try:
+ val = binascii.a2b_base64(text)
+ except (TypeError, binascii.Incomplete, binascii.Error), e:
+ raise ParseError("Bad reply block encoding: %s"%e)
blocks.append(parseReplyBlock(val))
- idx = m.end()
return blocks
def parseReplyBlocks(s):
- "DOCDOC"
+ """Given a string containing a list of concatenated encoded reply blocks,
+ return list of reply blocks corresponding to those in the string.
+ Raise ParseError on failure.
+ """
blocks = []
while s:
block, length = parseReplyBlock(s, allowMore=1, returnLen=1)
@@ -438,8 +447,14 @@
return blocks
def parseReplyBlock(s, allowMore=0, returnLen=0):
- """Return a new ReplyBlock object for an encoded reply block."""
- # DOCDOC withIdx
+ """Return a new ReplyBlock object for an encoded reply block.
+ If allowMore is true, accept a string that only begins with a
+ reply block. If returnLen is true, return a 2-tuple of the
+ reply block, and its length when encoded.
+
+ Raise ParseError on failure.
+ """
+
if len(s) < MIN_RB_LEN:
raise ParseError("Reply block too short")
try:
@@ -499,7 +514,8 @@
self.encryptionKey) + self.routingInfo
def packAsText(self):
- text = binascii.b2a_base64(self.pack())
+ """Returns the external text representation of this reply block"""
+ text = base64.encodestring(self.pack())
if not text.endswith("\n"):
text += "\n"
return "%s\nVersion: 0.1\n%s%s\n"%(RB_TEXT_START,text,RB_TEXT_END)
@@ -589,8 +605,10 @@
#----------------------------------------------------------------------
# Ascii-encoded packets
-
-#XXXX003 accept lines with different #'s of equal signs.
+#
+# The format is HeaderLine, TagLine?, Body, FooterLine.
+# TagLine is one of /Message-type: (overcompressed|binary)/
+# or /Decoding-handle: (base64-encoded-stuff)/.
MESSAGE_START_LINE = "======= TYPE III ANONYMOUS MESSAGE BEGINS ======="
MESSAGE_END_LINE = "======== TYPE III ANONYMOUS MESSAGE ENDS ========"
_MESSAGE_START_RE = re.compile(r"==+ TYPE III ANONYMOUS MESSAGE BEGINS ==+")
@@ -600,6 +618,8 @@
_LINE_RE = re.compile(r'[^\r\n]*\r*\n', re.S+re.M)
def _nextLine(s, idx):
+ """Helper method. Return the index of the first character of the first
+ line of s to follow <idx>."""
m = _LINE_RE.match(s[idx:])
if m is None:
return len(s)
@@ -607,7 +627,10 @@
return m.end()+idx
def parseTextEncodedMessage(msg,force=0,idx=0):
- """ DOCDOC
+ """Given a text-encoded Type III packet, return a TextEncodedMessage
+ object or raise ParseError.
+ force -- uncompress the message even if it's overcompressed.
+ idx -- index within <msg> to search.
"""
#idx = msg.find(MESSAGE_START_PAT, idx)
m = _MESSAGE_START_RE.search(msg[idx:])
@@ -624,7 +647,6 @@
if m is None:
msgType = 'TXT'
elif m.group(1):
- # XXXX003 enforce length
ascTag = m.group(1)
msgType = "ENC"
idx = _nextLine(msg, idx)
@@ -632,7 +654,7 @@
if m.group(2) == 'overcompressed':
msgType = 'LONG'
elif m.group(2) == 'binary':
- msgType = 'BIN' #XXXX003 refactor
+ msgType = 'BIN'
else:
raise ParseError("Unknown message type: %r"%m.group(2))
idx = _nextLine(msg, idx)
@@ -643,39 +665,60 @@
if msgType == 'TXT':
return TextEncodedMessage(msg, 'TXT'), endIdx
- msg = binascii.a2b_base64(msg) #XXXX May raise
+ try:
+ msg = binascii.a2b_base64(msg)
+ except (TypeError, binascii.Incomplete, binascii.Error), e:
+ raise ParseError("Error in base64 encoding: %s"%e)
+
if msgType == 'BIN':
return TextEncodedMessage(msg, 'BIN'), endIdx
elif msgType == 'LONG':
if force:
- msg = uncompressData(msg) #XXXX may raise
+ msg = uncompressData(msg) # May raise ParseError
return TextEncodedMessage(msg, 'LONG'), endIdx
elif msgType == 'ENC':
- tag = binascii.a2b_base64(ascTag)
+ try:
+ tag = binascii.a2b_base64(ascTag)
+ except (TypeError, binascii.Incomplete, binascii.Error), e:
+ raise ParseError("Error in base64 encoding: %s"%e)
+ if len(tag) != TAG_LEN:
+ raise ParseError("Impossible tag length: %s"%len(tag))
return TextEncodedMessage(msg, 'ENC', tag), endIdx
else:
raise MixFatalError("unreached")
class TextEncodedMessage:
+ """A TextEncodedMessage object holds a Type-III message as delivered
+ over a text-based medium."""
def __init__(self, contents, messageType, tag=None):
+ """Create a new TextEncodedMessage given a set of contents, a
+ messageType ('TXT', 'ENC', 'LONG', or 'BIN'), and optionally
+ a tag."""
assert messageType in ('TXT', 'ENC', 'LONG', 'BIN')
assert tag is None or (messageType == 'ENC' and len(tag) == 20)
self.contents = contents
self.messageType = messageType
self.tag = tag
def isBinary(self):
+ """Return true iff this is a binary plaintext packet."""
return self.messageType == 'BIN'
def isText(self):
+ """Return true iff this is a text plaintext packet."""
return self.messageType == 'TXT'
def isEncrypted(self):
+ """Return true iff this is an encrypted packet."""
return self.messageType == 'ENC'
def isOvercompressed(self):
+ """Return true iff this is an overcompressed plaintext packet."""
return self.messageType == 'LONG'
def getContents(self):
+ """Return the (unencoded) contents of this packet."""
return self.contents
def getTag(self):
+ """Return the (unencoded) decoding handle for this packet, or None."""
return self.tag
def pack(self):
+ """Return the text representation of this message."""
c = self.contents
preNL = postNL = ""
Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -d -r1.22 -r1.23
--- __init__.py 8 Jan 2003 07:53:24 -0000 1.22
+++ __init__.py 9 Feb 2003 22:30:58 -0000 1.23
@@ -8,15 +8,9 @@
# This version string is generated from setup.py; don't edit it.
__version__ = "0.0.3alpha"
+# DOCDOC
+version_info = (0, 0, 3, 'a', 0)
__all__ = [ 'server', 'directory' ]
-
-## import mixminion.BuildMessage
-## import mixminion.Crypto
-## import mixminion.Common
-## import mixminion.Config
-## import mixminion.MMTPClient
-## import mixminion.Packet
-## import mixminion.ServerInfo
## This next segment keeps pychecker from making spurious complaints.
import sys
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.81
retrieving revision 1.82
diff -u -d -r1.81 -r1.82
--- test.py 7 Feb 2003 17:23:11 -0000 1.81
+++ test.py 9 Feb 2003 22:30:58 -0000 1.82
@@ -1011,6 +1011,15 @@
rb = ReplyBlock(header="Z"*2048,useBy=0,rt=1,ri="F"*10,key=key)
self.assertEquals(r, rb.pack())
+ # Now try two blocks.
+ r += ("SURB\x00\x01"+"\x00\x00\x00\x00"+("Z"*2048)+"\x00\x0A"+
+ "\x00\x01"
+ +key+("G"*10))
+ rb = parseReplyBlocks(r)
+ self.assertEquals(2, len(rb))
+ self.assertEquals(rb[0].timestamp, 0)
+ self.assertEquals(rb[1].routingInfo, "G"*10)
+
def test_payloads(self):
# Checks for payload structure functions.
@@ -1777,7 +1786,16 @@
self.assertEquals(2, len(parsed2))
self.assertEquals(reply.pack(), parsed2[1].pack())
- #XXXX003 test failing cases for parseTextReplyBlocks
+ self.assertEquals([], parseTextReplyBlocks("X"))
+
+ # test failing cases for parseTextReplyBlocks
+ def fails(s, p=parseTextReplyBlocks, self=self):
+ self.assertRaises(ParseError, p, s)
+
+ fails("== BEGIN TYPE III REPLY BLOCK ==\n"+
+ "Version: 0.1\n"+
+ "xyz\n"+
+ "== END TYPE III REPLY BLOCK ==\n")
# Test decoding
seed = loc[:20]
@@ -2588,10 +2606,6 @@
log.error_exc(inf, "And so on")
log.error_exc(inf, "And so %s", "on")
- # print buf.getvalue()
- # FFFF We should examine the value of the above, but inspection
- # FFFF show that we're fine.
-
# Try out file logging
t = mix_mktemp("log")
t1 = t+"1"
@@ -4638,14 +4652,14 @@
BCC_INSTANCE = None
class ClientMainTests(unittest.TestCase):
- def testClientKeystore(self):
- """Check out ClientMain's keystore implementation"""
+ def testClientDirectory(self):
+ """Check out ClientMain's directory implementation"""
eq = self.assertEquals
neq = self.assertNotEquals
ServerInfo = mixminion.ServerInfo.ServerInfo
dirname = mix_mktemp()
- ks = mixminion.ClientMain.ClientKeystore(dirname)
+ ks = mixminion.ClientMain.ClientDirectory(dirname)
## Write the descriptors to disk.
edesc = getExampleServerDescriptors()
@@ -4683,7 +4697,7 @@
self.assertRaises(MixError, ks.getServerInfo, "Joe", startAt=now,
endAt=now+6*oneDay)
if i in (0,1,2):
- ks = mixminion.ClientMain.ClientKeystore(dirname)
+ ks = mixminion.ClientMain.ClientDirectory(dirname)
if i == 1:
ks.rescan()
if i == 2:
@@ -4732,7 +4746,7 @@
edesc["Bob"][4])
if i in (0,1,2):
- ks = mixminion.ClientMain.ClientKeystore(dirname)
+ ks = mixminion.ClientMain.ClientDirectory(dirname)
if i == 1:
ks.rescan()
if i == 2:
@@ -4837,7 +4851,7 @@
neq(p[1].getNickname(), "Alice")
neq(p[1].getNickname(), "Joe")
# 2b. With 3 <= servers < length
- ks2 = mixminion.ClientMain.ClientKeystore(mix_mktemp())
+ ks2 = mixminion.ClientMain.ClientDirectory(mix_mktemp())
ks2.importFromFile(os.path.join(impdirname, "Joe0"))
ks2.importFromFile(os.path.join(impdirname, "Alice0"))
ks2.importFromFile(os.path.join(impdirname, "Lisa1"))
@@ -5084,7 +5098,7 @@
## Now try clean()
ks.clean() # Should do nothing.
- ks = mixminion.ClientMain.ClientKeystore(dirname)
+ ks = mixminion.ClientMain.ClientDirectory(dirname)
ks.clean(now=now+oneDay*500) # Should zap all of imported servers.
raises(MixError, ks.getServerInfo, "Lola")
Index: testSupport.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/testSupport.py,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- testSupport.py 10 Jan 2003 20:12:05 -0000 1.13
+++ testSupport.py 9 Feb 2003 22:30:58 -0000 1.14
@@ -1,4 +1,4 @@
-# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
+# Copyright 2002-2003 Nick Mathewson. See LICENSE for licensing information.
# $Id$
"""mixminion.testSupport