[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs] Lots of documentation, lots of usage messages, minor pa...



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv7281/lib/mixminion

Modified Files:
	ClientMain.py Common.py MMTPClient.py Main.py Packet.py 
	__init__.py test.py 
Log Message:
Lots of documentation, lots of usage messages, minor patches.

EVERYWHERE: 
	- documentation.

ClientMain:
	- Improves usage messages
	- Improved errors
	- Move UIError, UsageError to Common.
	- Finish s/keystore/directory/
	- Finish s/firstHop/routing/ as appropriate
	- Make sure that the last node before a reply block can relay.
	- If the user's password is "", don't bother requesting it.
	- Fix getpass behavior when stdout is redirected to a file.
	- Make SURBLog always grab clientLock
	- Debug SURBLog's behavior with 'LAST_CLEANED'
	- Print a helpful message when inspecting an empty pool.
	- Debug SURBLog location.
	- Allow multiple '-R' arguments.
	- Give an error on 'send -D yes -D no'
	- Give an error on 'send -t alice -t bob'
	- Give an error on '--swap-at=1 --swap-at=2'
	- Give an error on '-H 1 -H 2'
	- Give an error on '-P alice,bob -P bob,alice'
	- Give an error on '--lifetime=3 --lifetime=4'
	- Give a better error on 'decode foo'.
	- Display info on whether surbs have been used when 'inspect-surbs'
	  is called.

Main:
	- Centralized handling of UIError and UsageError.
	- Add plausible plural/singulars for generate-surb(s) and 
          inspect-surb(s).

Common:
	- Fix bugs in LockFile
	- Move UIError, UsageError from ClientMain.

MMTPClient:
	- Refactor sendPacket and sendJunkPacket to both use _sendPacket.

Packet:
	- Quick hack to skip over extraneous whitespace in files of 
          binary reply blocks.
	- Temporary patch to allow 'Decoding handle:' as an alterantive
          spelling of 'Decoding-handle:'.  This allows me to test with
          existing servers.

PacketHandler:
	- Additional asserts

test:
	- Tests for Lockfiles
	- Clean up tests that break because of increased DeliveryPacket 
	  strictness.


Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.52
retrieving revision 1.53
diff -u -d -r1.52 -r1.53
--- ClientMain.py	12 Feb 2003 01:22:57 -0000	1.52
+++ ClientMain.py	13 Feb 2003 06:30:22 -0000	1.53
@@ -25,10 +25,10 @@
 import mixminion.Crypto
 import mixminion.MMTPClient
 from mixminion.Common import IntervalSet, LOG, floorDiv, MixError, \
-     MixFatalError, MixProtocolError, ceilDiv, createPrivateDir, \
-     isSMTPMailbox, formatDate, formatFnameTime, formatTime, Lockfile, \
-     openUnique, previousMidnight, readPossiblyGzippedFile, secureDelete, \
-     stringContains, succeedingMidnight
+     MixFatalError, MixProtocolError, UIError, UsageError, ceilDiv, \
+     createPrivateDir, isSMTPMailbox, formatDate, formatFnameTime, formatTime,\
+     Lockfile, openUnique, previousMidnight, readPossiblyGzippedFile, \
+     secureDelete, stringContains, succeedingMidnight
[...1573 lines suppressed...]
 
 def listPool(cmd, args):
     options, args = getopt.getopt(args, "hvf:",
@@ -2056,13 +2406,14 @@
     try:
         parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
                                    wantClient=1)
-    except UIError, e:
+    except UsageError, e:
         e.dump()
-        print _LIST_POOL_USAGE % cmd
+        print _LIST_POOL_USAGE % { 'cmd' : cmd }
         sys.exit(1)
 
     parser.init()
     client = parser.client
+
     try:
         clientLock()
         client.pool.inspectPool()

Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.59
retrieving revision 1.60
diff -u -d -r1.59 -r1.60
--- Common.py	12 Feb 2003 01:36:22 -0000	1.59
+++ Common.py	13 Feb 2003 06:30:22 -0000	1.60
@@ -5,13 +5,13 @@
 
    Common functionality and utility code for Mixminion"""
 
-__all__ = [ 'IntervalSet', 'LOG', 'LogStream', 'MixError', 'MixFatalError',
-            'MixProtocolError', 'ceilDiv', 'checkPrivateDir',
-            'createPrivateDir', 'floorDiv', 'formatBase64', 'formatDate',
-            'formatFnameTime', 'formatTime', 'installSIGCHLDHandler',
-            'isSMTPMailbox', 'openUnique', 'previousMidnight',
-            'readPossiblyGzippedFile', 'secureDelete', 'stringContains',
-            'succeedingMidnight', 'waitForChildren' ]
+__all__ = [ 'IntervalSet', 'Lockfile', 'LOG', 'LogStream', 'MixError',
+            'MixFatalError', 'MixProtocolError', 'UIError', 'UsageError',
+            'ceilDiv', 'checkPrivateDir', 'createPrivateDir', 'floorDiv',
+            'formatBase64', 'formatDate', 'formatFnameTime', 'formatTime',
+            'installSIGCHLDHandler', 'isSMTPMailbox', 'openUnique',
+            'previousMidnight', 'readPossiblyGzippedFile', 'secureDelete',
+            'stringContains', 'succeedingMidnight', 'waitForChildren' ]
 
 import base64
 import bisect
@@ -47,6 +47,21 @@
     """Exception class for MMTP protocol violations"""
     pass
 
+class UIError(MixError):
+    """Exception raised for an error that should be reported to the user,
+       not dumped as a stack trace."""
+    def dump(self):
+        if str(self): print "ERROR:", str(self)
+    def dumpAndExit(self):
+        self.dump()
+        sys.exit(0)
+
+class UsageError(UIError):
+    """Exception raised for an error that should be reported to the user
+       along with a usage mesage.
+    """
+    pass
+
 #----------------------------------------------------------------------
 # Portability to future Python versions
 
@@ -871,16 +886,24 @@
 
 #----------------------------------------------------------------------
 class Lockfile:
-    "DOCDOC"
-    #XXXX Testme.
+    """Class to implement a recursive advisory lock, using flock on a
+       'well-known' filename."""
+    ## Fields:
+    # filename--the name of the file to lock
+    # count--the recursion depth of the lock; 0 is unlocked.
+    # fd--If fd>1, a file descriptor open to 'filename'.  Otherwise, None.
     def __init__(self, filename):
-        "DOCDOC"
+        """Create a new Lockfile object to acquire and release a lock on
+           'filename'"""
         self.filename = filename
         self.count = 0
         self.fd = None
 
     def acquire(self, contents="", blocking=0):
-        "Raises IOError DOCDOC"
+        """Acquire this lock.  If we're acquiring the lock for the first time,
+           write 'contents' to the lockfile.  If 'blocking' is true, wait until
+           we can acquire the lock.  If 'blocking' is false, raise IOError if
+           we can't acquire the lock."""
         if self.count > 0:
             self.count += 1
             return
@@ -889,17 +912,18 @@
         self.fd = os.open(self.filename, os.O_RDWR|os.O_CREAT, 0600)
         try:
             if blocking:
-                fcntl.flock(self.fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
-            else:
                 fcntl.flock(self.fd, fcntl.LOCK_EX)
+            else:
+                fcntl.flock(self.fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
             self.count += 1
+            os.write(self.fd, contents)
         except:
             os.close(self.fd)
             self.fd = None
             raise
 
     def release(self):
-        "DOCDOC"
+        """Release the lock."""
         assert self.fd is not None
         self.count -= 1
         if self.count > 0:
@@ -911,6 +935,3 @@
             self.fd = None
         except OSError:
             pass
-    
-                
-        

Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -d -r1.22 -r1.23
--- MMTPClient.py	11 Feb 2003 22:18:13 -0000	1.22
+++ MMTPClient.py	13 Feb 2003 06:30:22 -0000	1.23
@@ -17,8 +17,6 @@
 
 __all__ = [ "BlockingClientConnection", "sendMessages" ]
 
-#DOCDOC MixProtocolError pattern
-
 import errno
 import signal
 import socket
@@ -26,7 +24,8 @@
 from mixminion.Crypto import sha1, getCommonPRNG
 from mixminion.Common import MixProtocolError, LOG, MixError
 
-class TimeoutError(MixError):
+class TimeoutError(MixProtocolError):
+    """Exception raised for protocol timeout."""
     pass
 
 class BlockingClientConnection:
@@ -59,13 +58,22 @@
         self.sock = None
 
     def connect(self, connectTimeout=None):
-        "DOCDOC"
+        """Connect to the server, perform the TLS handshake, check the server
+           key, and negotiate a protocol version.  If connectTimeout is set,
+           wait no more than connectTimeout seconds for TCP handshake to
+           complete.
+
+           Raises TimeoutError on timeout, and MixProtocolError on all other
+           errors."""
         try:
             self._connect(connectTimeout)
         except (socket.error, _ml.TLSError), e:
             self._raise(e, "connecting")
 
     def _raise(self, err, action):
+        """Helper method: given an exception (err) and an action string (e.g.,
+           'connecting'), raises an appropriate MixProtocolError.
+        """
         if isinstance(err, socket.error):
             tp = "Socket"
         elif isinstance(err, _ml.TLSError):
@@ -76,8 +84,7 @@
              tp, action, self.targetIP, self.targetPort, err)
 
     def _connect(self, connectTimeout=None):
-        """Negotiate the handshake and protocol."""
-        #DOCDOC connectTimeout
+        """Helper method; implements _connect."""
         # FFFF There should be a way to specify timeout for communication.
         def sigalarmHandler(sig, _):
             assert sig == signal.SIGALRM
@@ -141,16 +148,40 @@
         LOG.debug("MMTP protocol negotated: version %s", self.protocol)
 
     def renegotiate(self):
+        """Re-do the TLS handshake to renegotiate a new connection key."""
         try:
             self.tls.renegotiate()
             self.tls.do_handshake()
         except (socket.error, _ml.TLSError), e:
             self._raise(e, "renegotiating connection")
 
-    def sendPacket(self, packet,
-                   control="SEND\r\n", serverControl="RECEIVED\r\n",
-                   hashExtra="SEND",serverHashExtra="RECEIVED"):
-        """Send a single packet to a server."""
+    def sendPacket(self, packet):
+        """Send a single 32K packet to the server."""
+        self._sendPacket(packet)
+
+    def sendJunkPacket(self, packet):
+        """Send a single 32K junk packet to the server."""
+        if self.protocol == '0.1':
+            LOG.debug("Not sending junk to a v0.1 server")
+            return
+        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"):
+        """Helper method: implements sendPacket and sendJunkPacket.
+              packet -- a 32K string to send
+              control -- a 6-character string ending with CRLF to
+                  indicate the type of message we're sending.
+              serverControl -- a 10-character string ending with CRLF that
+                  we expect to receive if we've sent correctly.
+              hashExtra -- a string to append to the packet when computing
+                  the hash we send.
+              serverHashExtra -- the string we expect the server to append
+                  to the packet when computing the hash it sends in reply.
+           """
         assert len(packet) == 1<<15
         LOG.debug("Sending packet")
         try:
@@ -169,14 +200,6 @@
         except (socket.error, _ml.TLSError), e:
             self._raise(e, "sending packet")
             
-    def sendJunkPacket(self, packet):
-        if self.protocol == '0.1':
-            LOG.debug("Not sending junk to a v0.1 server")
-            return
-        self.sendPacket(packet,
-                        control="JUNK\r\n", serverControl="RECEIVED\r\n",
-                        hashExtra="JUNK", serverHashExtra="RECEIVED JUNK")
-        
     def shutdown(self):
         """Close this connection."""
         LOG.debug("Shutting down connection to %s:%s",
@@ -191,17 +214,19 @@
         LOG.debug("Connection closed")
 
 def sendMessages(routing, packetList, connectTimeout=None, callback=None):
-    """Sends a list of messages to a server.
+    """Sends a list of messages to a server.  Raise MixProtocolError on
+       failure.
 
-       targetIP -- the address to connect to, in dotted-quad format.
-       targetPort -- the port to connect to.
-       targetKeyID -- the keyid to expect, or '\000...\000' to ignore
-           the server's keyid.
+       routing -- an instance of mixminion.Packet.IPV4Info.
+                  If routing.keyinfo == '\000'*20, we ignore the server's
+                  keyid.
        packetList -- a list of 32KB packets and control strings.  Control
            strings must be one of "JUNK" to send a 32KB padding chunk,
            or "RENEGOTIATE" to renegotiate the connection key.
-
-       DOCDOC args are wrong
+       connectTimeout -- None, or a number of seconds to wait for the
+           TCP handshake to finish before raising TimeoutError.
+       callback -- None, or a function to call with a index into packetList
+           after each successful packet delivery.
     """
     # Generate junk before opening connection to avoid timing attacks
     packets = []

Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.29
retrieving revision 1.30
diff -u -d -r1.29 -r1.30
--- Main.py	11 Feb 2003 22:18:13 -0000	1.29
+++ Main.py	13 Feb 2003 06:30:22 -0000	1.30
@@ -123,6 +123,8 @@
     "update-servers" : ( 'mixminion.ClientMain', 'updateServers' ),
     "decode" :         ( 'mixminion.ClientMain', 'clientDecode' ),
     "generate-surb" :  ( 'mixminion.ClientMain', 'generateSURB' ),
+    "generate-surbs" : ( 'mixminion.ClientMain', 'generateSURB' ),
+    "inspect-surb" :   ( 'mixminion.ClientMain', 'inspectSURBs' ),
     "inspect-surbs" :  ( 'mixminion.ClientMain', 'inspectSURBs' ),
     "flush" :          ( 'mixminion.ClientMain', 'flushPool' ),
     "inspect-pool" :   ( 'mixminion.ClientMain', 'listPool' ),
@@ -138,13 +140,15 @@
   "                              (For Everyone)\n"+
   "       version        [Print the version of Mixminion and exit]\n"+
   "       send           [Send an anonymous message]\n"+
+  "       pool           [Schedule an anonymous message to be sent later]\n"+
+  "       flush          [Send all messages waiting in the pool]\n"+
+  "       inspect-pool   [Describe all messages waiting in the pool]\n"+
   "       import-server  [Tell the client about a new server]\n"+
   "       list-servers   [Print a list of currently known servers]\n"+
   "       update-servers [Download a fresh server directory]\n"+
   "       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"+
+  "       inspect-surbs  [Describe a single-use reply block]\n"+
   "                               (For Servers)\n"+
   "       server         [Begin running a Mixminon server]\n"+
   "       server-keygen  [Generate keys for a Mixminion server]\n"+
@@ -185,6 +189,11 @@
         printUsage()
         sys.exit(1)
 
+    # Read the 'common' module to get the UIError class.  To simplify
+    # command implementation code, we catch all UIError exceptions here.
+    commonModule = __import__('mixminion.Common', {}, {}, ['UIError'])
+    uiErrorClass = getattr(commonModule, 'UIError')
+
     # Read the module and function.
     command_module, command_fn = _COMMANDS[args[1]]
     mod = __import__(command_module, {}, {}, [command_fn])
@@ -199,6 +208,8 @@
     except getopt.GetoptError, e:
         sys.stderr.write(str(e)+"\n")
         func(commandStr, ["--help"])
+    except uiErrorClass, e:
+        e.dumpAndExit()
 
 if __name__ == '__main__':
     main(sys.argv)

Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.31
retrieving revision 1.32
diff -u -d -r1.31 -r1.32
--- Packet.py	12 Feb 2003 01:36:22 -0000	1.31
+++ Packet.py	13 Feb 2003 06:30:22 -0000	1.32
@@ -440,7 +440,12 @@
        Raise ParseError on failure.
     """
     blocks = []
-    while s:
+    #DOCDOC
+    while 1:
+        while s and s[0] in ' \t\r\n':
+            s = s[1:]
+        if not s:
+            break
         block, length = parseReplyBlock(s, allowMore=1, returnLen=1)
         blocks.append(block)
         s = s[length:]
@@ -617,7 +622,8 @@
 MESSAGE_END_LINE   = "======== TYPE III ANONYMOUS MESSAGE ENDS ========"
 _MESSAGE_START_RE  = re.compile(r"==+ TYPE III ANONYMOUS MESSAGE BEGINS ==+")
 _MESSAGE_END_RE    = re.compile(r"==+ TYPE III ANONYMOUS MESSAGE ENDS ==+")
-_FIRST_LINE_RE = re.compile(r'''^Decoding-handle:\s(.*)\r*\n|
+#XXXX004 disable "decoding handle" format
+_FIRST_LINE_RE = re.compile(r'''^Decoding[- ]handle:\s(.*)\r*\n|
                                  Message-type:\s(.*)\r*\n''', re.X+re.S)
 _LINE_RE = re.compile(r'[^\r\n]*\r*\n', re.S+re.M)
 
@@ -729,7 +735,9 @@
         if self.messageType != 'TXT':
             c = base64.encodestring(c)
         else:
+            #XXXX004 disable "decoding handle" format
             if (c.startswith("Decoding-handle:") or
+                c.startswith("Decoding handle:") or
                 c.startswith("Message-type:")):
                 preNL = "\n"
                 

Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -d -r1.24 -r1.25
--- __init__.py	12 Feb 2003 01:22:14 -0000	1.24
+++ __init__.py	13 Feb 2003 06:30:23 -0000	1.25
@@ -21,6 +21,7 @@
 version_info = (0, 0, 3, 0, -1)
 __all__ = [ 'server', 'directory' ]
 
+#XXXX003 MOVE THIS TO COMMON!
 def version_tuple_to_string(t):
     assert len(t) == 5
     if t[3] == 0:

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.84
retrieving revision 1.85
diff -u -d -r1.84 -r1.85
--- test.py	12 Feb 2003 01:21:46 -0000	1.84
+++ test.py	13 Feb 2003 06:30:23 -0000	1.85
@@ -433,6 +433,43 @@
         f.close()
         self.assertEquals(fn, dX+".2")
 
+    def test_lockfile(self):
+        fn = mix_mktemp()
+        LF1 = Lockfile(fn)
+        LF2 = Lockfile(fn)
+        LF1.acquire("LF1")
+        self.assertEquals("LF1", readFile(fn))
+        self.assertRaises(IOError, LF2.acquire, blocking=0)
+        LF1.release()
+        LF2.acquire("LF2",1)
+        self.assertEquals("LF2", readFile(fn))
+        self.assertRaises(IOError, LF1.acquire, blocking=0)
+
+        # Now try recursivity.
+        LF2.acquire()
+        self.assertRaises(IOError, LF1.acquire, blocking=0)
+        LF2.release()
+        self.assertRaises(IOError, LF1.acquire, blocking=0)
+        LF2.release()
+        LF1.acquire(blocking=1)
+
+        # Now try a blocking lock.
+        released=[0]
+        def threadBody(LF2=LF2,released=released):
+            LF2.acquire("LF2",blocking=1)
+            if not released[0]:
+                released[0] = 'BAD'
+            else:
+                released[0] = 'GOOD'
+        
+        t = threading.Thread(None, threadBody)
+        t.start()
+        time.sleep(.1)
+        released[0] = 1
+        LF1.release()
+        t.join()
+        self.assertEquals("GOOD", released[0])
+
     def _intervalEq(self, a, *others):
         eq = self.assertEquals
         for b in others:
@@ -3979,7 +4016,7 @@
         binmessage = hexread("00ADD1EDC0FFEED00DAD")*40
         tag = ".!..!....!........!."
 
-        def FDPFast(type,message,tag="xyzzyxyzzzyxyzzyxyzzzy"):
+        def FDPFast(type,message,tag="xyzzyxyzzyxyzzyxyzzy"):
             return FakeDeliveryPacket(type,0xFFFE,"addr",message,tag)
 
         ####
@@ -4048,11 +4085,10 @@
         if tag is None:
             tag = "-="*10
         mixminion.server.PacketHandler.DeliveryPacket.__init__(self,
-                        exitType, exitAddress, None, tag, None)
+                        exitType, exitAddress, "Z"*16, tag, "Q"*(28*1024))
         self.type = type
         self.payload = None
         self.contents = contents
-        
 
 class ModuleTests(unittest.TestCase):
     def testEmailAddressSet(self):