[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