[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):