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

[minion-cvs] Last round (I hope) of code tweaks before 0.0.1. Only ...



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

Modified Files:
	BuildMessage.py ClientMain.py Common.py Config.py Crypto.py 
	MMTPClient.py Main.py Packet.py ServerInfo.py __init__.py 
	benchmark.py test.py testSupport.py 
Log Message:
Last round (I hope) of code tweaks before 0.0.1.  Only system tests and docs remain!

Refactoring:
	- Move _base64 from ServerInfo to formatBase64 in Common
	- Finish separating server code.
		- Move ServerConfig to new file
		- Move Server key mgt to new file
	- Don't warn on missing overwrite; we now override.
	- Minimize calls to true RNG

Everywhere:
	- Remove extraneous whitespace
	- Wrap overlong lines
	- Remove extraneous import statements
	- Give all modules sane __all__ fields
	- Make Pychecker pleased.



Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- BuildMessage.py	11 Dec 2002 05:53:32 -0000	1.21
+++ BuildMessage.py	12 Dec 2002 19:56:46 -0000	1.22
@@ -3,14 +3,14 @@
 
 """mixminion.BuildMessage
 
-   Code to construct messages and reply blocks, and to decode received 
+   Code to construct messages and reply blocks, and to decode received
    message payloads."""
 
 import zlib
 import operator
+import mixminion.Crypto as Crypto
 from mixminion.Packet import *
 from mixminion.Common import MixError, MixFatalError, LOG
-import mixminion.Crypto as Crypto
 
 __all__ = ['buildForwardMessage', 'buildEncryptedMessage', 'buildReplyMessage',
            'buildReplyBlock', 'decodePayload' ]
@@ -29,7 +29,8 @@
 
         Neither path1 nor path2 may be empty.
     """
-    if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+    if paddingPRNG is None: 
+	paddingPRNG = Crypto.getCommonPRNG()
     assert path1 and path2
 
     LOG.debug("Encoding forward message for %s-byte payload",len(payload))
@@ -40,7 +41,7 @@
 
     # Compress, pad, and checksum the payload.
     payload = _encodePayload(payload, 0, paddingPRNG)
-    
+
     # Choose a random decoding tag.
     tag = _getRandomTag(paddingPRNG)
     exitInfo = tag + exitInfo
@@ -60,7 +61,8 @@
 	    paddingPRNG: random number generator used to generate padding.
 	          If None, a new PRNG is initialized.
     """
-    if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+    if paddingPRNG is None: 
+	paddingPRNG = Crypto.getCommonPRNG()
     if secretRNG is None: secretRNG = paddingPRNG
 
     LOG.debug("Encoding encrypted forward message for %s-byte payload",
@@ -86,7 +88,7 @@
     lionessPart = payload[rsaDataLen:]
 
     # RSA encryption: To avoid leaking information about our RSA modulus,
-    # we keep trying to encrypt until the MSBit of our encrypted value is 
+    # we keep trying to encrypt until the MSBit of our encrypted value is
     # zero.
     while 1:
 	encrypted = Crypto.pk_encrypt(rsaPart, key)
@@ -111,7 +113,8 @@
     """Build a message using a reply block.  'path1' is a sequence of
        ServerInfo for the nodes on the first leg of the path.
     """
-    if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+    if paddingPRNG is None:
+	paddingPRNG = Crypto.getCommonPRNG()
 
     LOG.debug("Encoding reply message for %s-byte payload",
 		   len(payload))
@@ -130,7 +133,7 @@
     return _buildMessage(payload, None, None,
                          path1=path1, path2=replyBlock)
 
-def _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime=0, 
+def _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime=0,
 			 secretPRNG=None, tag=None):
     """Helper function: makes a reply block, given a tag and a PRNG to
        generate secrets. Returns a 3-tuple containing (1) a
@@ -149,11 +152,11 @@
                  is generated.
        """
     if secretPRNG is None:
-        secretPRNG = Crypto.AESCounterPRNG()
+        secretPRNG = Crypto.getCommonPRNG()
 
     LOG.debug("Building reply block for path %s",
 		   [s.getNickname() for s in path])
-    LOG.debug("  Delivering to %04x:%r", exitType, exitInfo)    
+    LOG.debug("  Delivering to %04x:%r", exitType, exitInfo)
 
     # The message is encrypted first by the end-to-end key, then by
     # each of the path keys in order. We need to reverse these steps, so we
@@ -168,7 +171,7 @@
 	tag = _getRandomTag(secretPRNG)
 
     header = _buildHeader(path, headerSecrets, exitType, tag+exitInfo,
-                          paddingPRNG=Crypto.AESCounterPRNG())
+                          paddingPRNG=Crypto.getCommonPRNG())
 
     return ReplyBlock(header, expiryTime,
                       SWAP_FWD_TYPE,
@@ -181,7 +184,7 @@
        reply-message recipient to remember a list of secrets.
        Instead, all secrets are generated from an AES counter-mode
        stream, and the seed for the stream is stored in the 'tag'
-       field of the final block's routing info.   (See the spec for more 
+       field of the final block's routing info.   (See the spec for more
        info).
 
                path: a list of ServerInfo objects
@@ -190,17 +193,18 @@
 
        NOTE: We used to allow another kind of 'non-state-carrying' reply
        block that stored its secrets on disk, and used an arbitrary tag to
-       determine 
+       determine
        """
-    if secretRNG is None: secretRNG = Crypto.AESCounterPRNG()
+    if secretRNG is None: 
+	secretRNG = Crypto.getCommonPRNG()
 
     # We need to pick the seed to generate our keys.  To make the decoding
     # step a little faster, we find a seed such that H(seed|userKey|"Validate")
     # ends with 0.  This way, we can detect whether we really have a reply
     # message with 99.6% probability.  (Otherwise, we'd need to repeatedly
-    # lioness-decrypt the payload in order to see whether the message was 
+    # lioness-decrypt the payload in order to see whether the message was
     # a reply.)
-    
+
     # XXXX D'oh!  This enables an offline password guessing attack for
     # XXXX anybody who sees multiple tags.  We need to make sure that userKey
     # XXXX is stored on disk, and isn't a password.  This needs more thought.
@@ -211,22 +215,22 @@
 
     prng = Crypto.AESCounterPRNG(Crypto.sha1(seed+userKey+"Generate")[:16])
 
-    return _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng, 
+    return _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng,
 				seed)[0]
 
 #----------------------------------------------------------------------
 # MESSAGE DECODING
 
-def decodePayload(payload, tag, key=None, 
+def decodePayload(payload, tag, key=None,
 		  #storedKeys=None, # 'Stateful' reply blocks are disabled.
 		  userKey=None):
     """Given a 28K payload and a 20-byte decoding tag, attempt to decode and
-       decompress the original message.  
+       decompress the original message.
 
            key: an RSA key to decode encrypted forward messages, or None
 	   userKey: our encryption key for reply blocks, or None.
-         
-       If we can successfully decrypt the payload, we return it.  If we 
+
+       If we can successfully decrypt the payload, we return it.  If we
        might be able to decrypt the payload given more/different keys,
        we return None.  If the payload is corrupt, we raise MixError.
     """
@@ -299,7 +303,7 @@
     k = Crypto.Keyset(rsaPart[:SECRET_LEN]).getLionessKeys(
 	Crypto.END_TO_END_ENCRYPT_MODE)
     rest = rsaPart[SECRET_LEN:] + Crypto.lioness_decrypt(rest, k)
-    
+
     # ... and then, check the checksum and continue.
     return _decodePayloadImpl(rest)
 
@@ -308,7 +312,7 @@
          master secrets. If 'check' is true, then 'secerets' may be overlong.
          Return values are the same as decodePayload.
       [secrets must be in _reverse_ order]
-    """ 
+    """
     # Reverse the 'decrypt' operations of the reply mixes, and the initial
     # 'decrypt' of the originating user...
     for sec in secrets:
@@ -342,7 +346,7 @@
               include the 20-byte decoding tag.)
        path1: a sequence of ServerInfo objects, one for each node on
           the first leg of the path.
-       path2: 
+       path2:
         EITHER
              a sequence of ServerInfo objects, one for each node
              on the second leg of the path.
@@ -373,7 +377,7 @@
 
     # Set up the random number generators.
     if paddingPRNG is None:
-        paddingPRNG = Crypto.AESCounterPRNG()
+	paddingPRNG = Crypto.getCommonPRNG()
     if paranoia:
         nHops = len(path1)
         if path2: nHops += len(path2)
@@ -413,7 +417,7 @@
            secrets: A list of 16-byte strings to use as master-secrets for
                each of the subheaders.
            exitType: The routing type for the last node in the header
-           exitInfo: The routing info for the last node in the header. 
+           exitInfo: The routing info for the last node in the header.
                (Must include 20-byte decoding tag.)
            paddingPRNG: A pseudo-random number generator to generate padding
     """
@@ -566,7 +570,7 @@
     return chr(b) + rng.getBytes(TAG_LEN-1)
 
 def _decodePayloadImpl(payload):
-    """Helper: try to decode an encoded payload: checks only encoding, 
+    """Helper: try to decode an encoded payload: checks only encoding,
        not encryption."""
     # Is the hash ok?
     if not _checkPayload(payload):

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- ClientMain.py	11 Dec 2002 06:58:55 -0000	1.14
+++ ClientMain.py	12 Dec 2002 19:56:46 -0000	1.15
@@ -25,26 +25,26 @@
 #      - Per-system directory location is a neat idea, but individual users
 #        must check signature.  That's a way better idea for later.
 
-import os
 import getopt
+import os
 import sys
 import time
 import types
 
-from mixminion.Common import LOG, floorDiv, createPrivateDir, MixError, \
-     MixFatalError, isSMTPMailbox
-import mixminion.Crypto
 import mixminion.BuildMessage
+import mixminion.Crypto
 import mixminion.MMTPClient
-from mixminion.ServerInfo import ServerInfo
+from mixminion.Common import LOG, floorDiv, MixError, MixFatalError, \
+     createPrivateDir, isSMTPMailbox
 from mixminion.Config import ClientConfig, ConfigError
+from mixminion.ServerInfo import ServerInfo
 from mixminion.Packet import ParseError, parseMBOXInfo, parseSMTPInfo, \
      MBOX_TYPE, SMTP_TYPE, DROP_TYPE
 
 class TrivialKeystore:
     """This is a temporary keystore implementation until we get a working
        directory server implementation.
-       
+
        The idea couldn't be simpler: we just keep a directory of files, each
        containing a single server descriptor.  We cache nothing; we validate
        everything; we have no automatic path generation.  Servers can be
@@ -57,11 +57,11 @@
     ## Fields:
     # directory: path to the directory we scan for server descriptors.
     # byNickname: a map from nickname to valid ServerInfo object.
-    # byFilename: a map from filename within self.directory to valid 
+    # byFilename: a map from filename within self.directory to valid
     #     ServerInfo object.
     def __init__(self, directory, now=None):
 	"""Create a new TrivialKeystore to access the descriptors stored in
-	   directory.  Selects descriptors that are valid at the time 'now', 
+	   directory.  Selects descriptors that are valid at the time 'now',
 	   or at the current time if 'now' is None."""
 	self.directory = directory
 	createPrivateDir(directory)
@@ -135,7 +135,7 @@
 			       (name, e))
 	    except ConfigError, e:
 		raise MixError("Couldn't parse descriptor %s: %s" %
-			       (name, e))	    
+			       (name, e))
 	else:
 	    return None
 
@@ -157,7 +157,7 @@
 
     def getRandomServers(self, prng, n):
 	"""Returns a list of n different servers, in random order, according
-	   to prng.  Raises MixError if not enough exist.  
+	   to prng.  Raises MixError if not enough exist.
 
 	   (This isn't actually used.)"""
 	vals = self.byNickname.values()
@@ -218,7 +218,7 @@
 	    os.path.join(userdir,"servers"))
 
 	# Initialize PRNG
-	self.prng = mixminion.Crypto.AESCounterPRNG()
+	self.prng = mixminion.Crypto.getCommonPRNG()
 
     def sendForwardMessage(self, address, payload, path1, path2):
 	"""Generate and send a forward message.
@@ -233,14 +233,14 @@
 	self.sendMessages([message], firstHop)
 
     def generateForwardMessage(self, address, payload, path1, path2):
-	"""Generate a forward message, but do not send it.  Returns 
+	"""Generate a forward message, but do not send it.  Returns
 	   a tuple of (the message body, a ServerInfo for the first hop.)
 
 	    address -- the results of a parseAddress call
 	    payload -- the contents of the message to send
 	    path1,path2 -- lists of servers or server names for the first and
 	       second legs of the path, respectively.  These are processed
-	       as described in TrivialKeystore.getServerInfo"""	
+	       as described in TrivialKeystore.getServerInfo"""
         if not path1:
 	    raise MixError("No servers in first leg of path")
 	if not path2:
@@ -265,7 +265,7 @@
 	else:
 	    servers2.append(self.keystore.getServerInfo(lastHop))
 	msg = mixminion.BuildMessage.buildForwardMessage(
-	    payload, routingType, routingInfo, servers1, servers2, 
+	    payload, routingType, routingInfo, servers1, servers2,
 	    self.prng)
 	return msg, servers1[0]
 
@@ -291,7 +291,7 @@
            OR smtp:<email address>
 	   OR <email address> (smtp is implicit)
 	   OR drop
-	   OR 0x<routing type>:<routing info> 
+	   OR 0x<routing type>:<routing info>
     """
     # ???? Should this should get refactored into clientmodules, or someplace?
     if s.lower() == 'drop':

Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.31
retrieving revision 1.32
diff -u -d -r1.31 -r1.32
--- Common.py	11 Dec 2002 05:53:33 -0000	1.31
+++ Common.py	12 Dec 2002 19:56:46 -0000	1.32
@@ -5,20 +5,24 @@
 
    Common functionality and utility code for Mixminion"""
 
-__all__ = [ 'MixError', 'MixFatalError', 'onReset', 'onTerminate',
-            'installSignalHandlers', 'secureDelete', 'secureRename',
-            'ceilDiv', 'floorDiv', 'LOG', 'stringContains',
-	    'formatDate', 'formatTime', 'isSMTPMailbox']
+__all__ = [ 'LOG', 'MixError', 'MixFatalError', 'MixProtocolError', 'ceilDiv',
+	    'checkPrivateDir', 'createPrivateDir', 'floorDiv', 'formatBase64',
+	    'formatDate', 'formatTime', 'installSignalHandlers',
+	    'isSMTPMailbox', 'mkgmtime', 'onReset', 'onTerminate',
+	    'previousMidnight', 'secureDelete', 'stringContains',
+	    'waitForChildren' ]
 
+import base64
+import calendar
 import os
+import re
 import signal
-import sys
-import time
 import stat
-import re
 import statvfs
+import sys
+import time
 import traceback
-import calendar
+
 from types import StringType
 
 class MixError(Exception):
@@ -90,6 +94,10 @@
     """Remove all whitespace from s."""
     return s.translate(_ALLCHARS, space)
 
+def formatBase64(s):
+    """Convert 's' to a one-line base-64 representation."""
+    return base64.encodestring(s).replace("\n", "")
+
 #----------------------------------------------------------------------
 def createPrivateDir(d, nocreate=0):
     """Create a directory, and all parent directories, checking permissions
@@ -107,7 +115,7 @@
 def checkPrivateDir(d, recurse=1):
     """Return true iff d is a directory owned by this uid, set to mode
        0700. All of d's parents must not be writable or owned by anybody but
-       this uid and uid 0.  If any of these conditions are unmet, raise 
+       this uid and uid 0.  If any of these conditions are unmet, raise
        MixFatalErrror.  Otherwise, return None."""
     me = os.getuid()
 
@@ -138,7 +146,7 @@
 	mode = st[stat.ST_MODE]
 	owner = st[stat.ST_UID]
 	if owner not in (0, me):
-	    raise MixFatalError("Bad owner (uid=%s) on directory %s" 
+	    raise MixFatalError("Bad owner (uid=%s) on directory %s"
 				% (owner, d))
 	if (mode & 02) and not (mode & stat.S_ISVTX):
 	    raise MixFatalError("Bad mode (%o) on directory %s" %(mode, d))
@@ -152,12 +160,12 @@
 # Secure filesystem operations.
 
 # A 'shred' command to overwrite and unlink files.  It should accept an
-# arbitrary number of arguments.  (If "---", we haven't configured the 
+# arbitrary number of arguments.  (If "---", we haven't configured the
 # shred command.  If None, we're using our internal implementation.)
 _SHRED_CMD = "---"
 # Tuple of options to be passed to the 'shred' command
 _SHRED_OPTS = None
-    
+
 def configureShredCommand(conf):
     """Initialize the secure delete command from a given Config object.
        If no object is provided, try some sane defaults."""
@@ -173,7 +181,7 @@
         if os.path.exists("/usr/bin/shred"):
             cmd, opts = "/usr/bin/shred", ["-uz", "-n0"]
         else:
-            LOG.warn("Files will not be securely deleted.")
+	    # Use built-in _overwriteFile
             cmd, opts = None, None
 
     _SHRED_CMD, _SHRED_OPTS = cmd, opts
@@ -235,14 +243,14 @@
        XXXX operation has the regrettable property that two shred commands
        XXXX running in the same directory can sometimes get into a race.
        XXXX The source to shred.c seems to imply that this is harmless, but
-       XXXX let's try to avoid that, to be on the safe side. 
+       XXXX let's try to avoid that, to be on the safe side.
     """
     if _SHRED_CMD == "---":
         configureShredCommand(None)
 
     if fnames == []:
         return
-    
+
     if isinstance(fnames, StringType):
         fnames = [fnames]
 
@@ -251,7 +259,7 @@
 	    _overwriteFile(f)
             os.unlink(f)
         return None
-	
+
     if blocking:
         mode = os.P_WAIT
     else:
@@ -288,7 +296,7 @@
 	   to implement log rotation."""
         if self.file is not None:
             self.file.close()
-	try: 
+	try:
 	    parent = os.path.split(self.fname)[0]
 	    if not os.path.exists(parent):
 		createPrivateDir(parent)
@@ -304,13 +312,13 @@
 	if self.file is None:
 	    return
         print >> self.file, "%s [%s] %s" % (_logtime(), severity, message)
-        
-class _ConsoleLogHandler: 
+
+class _ConsoleLogHandler:
     """Helper class for logging: directs all log messages to a stderr-like
        file object"""
     def __init__(self, file):
 	"Create a new _ConsoleLogHandler attached to a given file."""
-        self.file = file 
+        self.file = file
     def reset(self): pass
     def close(self): pass
     def write(self, severity, message):
@@ -337,7 +345,7 @@
 	         and messages through the system.  This is a security risk.
               INFO: non-critical events.
 	      WARN: recoverable errors
-	      ERROR: nonrecoverable errors that affect only a single 
+	      ERROR: nonrecoverable errors that affect only a single
                  message or a connection.
 	      FATAL: nonrecoverable errors that affect the entire system.
 
@@ -374,20 +382,20 @@
 		    self.error(str(e))
             if logfile and not config['Server'].get('EchoMessages',0):
 		del self.handlers[0]
-            
+
     def setMinSeverity(self, minSeverity):
-	"""Sets the minimum severity of messages to be logged.  
+	"""Sets the minimum severity of messages to be logged.
 	      minSeverity -- the string representation of a severity level."""
         self.severity = _SEVERITIES.get(minSeverity, 1)
 
     def getMinSeverity(self):
-	"""Return a string representation of this log's minimum severity 
+	"""Return a string representation of this log's minimum severity
 	   level."""
         for k,v in _SEVERITIES.items():
             if v == self.severity:
                 return k
 	return "INFO"
-        
+
     def addHandler(self, handler):
 	"""Add a LogHandler object to the list of objects that receive
 	   messages from this log."""
@@ -408,7 +416,7 @@
 	"""Close all logs"""
         for h in self.handlers:
             h.close()
-        
+
     def log(self, severity, message, *args):
 	"""Send a message of a given severity to the log.  If additional
 	   arguments are provided, write 'message % args'. """
@@ -426,10 +434,10 @@
 	else:
 	    m = message % args
 
-        # Enable this block to debug message formats.    
+        # Enable this block to debug message formats.
         if _SEVERITIES.get(severity, 100) < self.severity:
             return
-            
+
         for h in self.handlers:
             h.write(severity, m)
 
@@ -452,11 +460,11 @@
 	"Write a fatal (unrecoverable system error) message to the log"
         self.log("FATAL", message, *args)
     def log_exc(self, severity, (exclass, ex, tb), message=None, *args):
-	"""Write an exception and stack trace to the log.  If message and 
+	"""Write an exception and stack trace to the log.  If message and
 	   args are provided, use them as an explanitory message; otherwise,
 	   introduce the message as "Unexpected exception".
 
-	   This should usually be called as 
+	   This should usually be called as
 	       LOG.log_exc('ERROR', sys.exc_info(), message, args...)
 	   """
 	if message is not None:
@@ -466,7 +474,7 @@
 	    self.log(severity, "Unexpected exception in %s", filename)
 	else:
 	    self.log(severity, "Unexpected exception")
-	
+
 	formatted = traceback.format_exception(exclass, ex, tb)
 	formatted[1:] = [ "  %s" % line for line in formatted[1:] ]
 	indented = "".join(formatted)
@@ -491,7 +499,7 @@
 def mkgmtime(yyyy,MM,dd,hh,mm,ss):
     """Analogously to time.mktime, return a number of seconds since the
        epoch when GMT is yyyy/MM/dd hh:mm:ss"""
-    
+
     # we set the DST flag to zero so that subtracting time.timezone always
     # gives us gmt.
     return calendar.timegm((yyyy,MM,dd,hh,mm,ss,0,0,0))
@@ -525,7 +533,7 @@
 # (This is more strict than RFC822, actually.  RFC822 allows tricky stuff to
 #  quote special characters, and I don't trust every MTA or delivery command
 #  to support addresses like <bob@bob."; rm -rf /; echo".com>)
- 
+
 # An 'Atom' is a non-escape, non-null, non-space, non-punctuation character.
 _ATOM_PAT = r'[^\x00-\x20()\[\]()<>@,;:\\".\x7f-\xff]+'
 # The 'Local part' (and, for us, the domain portion too) is a sequence of
@@ -539,7 +547,7 @@
     """Return true iff s is a valid SMTP address"""
     m = RFC822_RE.match(s)
     return m is not None
-     
+
 #----------------------------------------------------------------------
 # Signal handling
 
@@ -560,7 +568,7 @@
     terminateHooks.append(fn)
 
 def waitForChildren():
-    """Wait until all subprocesses have finished.  Useful for testing.""" 
+    """Wait until all subprocesses have finished.  Useful for testing."""
     while 1:
         try:
             # FFFF This won't work on Windows.  What to do?
@@ -575,7 +583,7 @@
     # Because of the peculiarities of Python's signal handling logic, I
     # believe we need to re-register ourself.
     signal.signal(signal_num, _sigChldHandler)
-    
+
     while 1:
         try:
             # This waitpid call won't work on Windows.  What to do?
@@ -584,7 +592,7 @@
                 break
         except OSError:
             break
-    
+
     #outcome, core, sig = status & 0xff00, status & 0x0080, status & 0x7f
     # FFFF Log if outcome wasn't as expected.
 
@@ -598,7 +606,7 @@
     else:
         for hook in resetHooks:
             hook()
-            
+
 def installSignalHandlers(child=1,hup=1,term=1):
     '''Register signal handlers for this process.  If 'child', registers
        a handler for SIGCHLD.  If 'hup', registers a handler for SIGHUP.

Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.25
retrieving revision 1.26
diff -u -d -r1.25 -r1.26
--- Config.py	11 Dec 2002 06:58:55 -0000	1.25
+++ Config.py	12 Dec 2002 19:56:46 -0000	1.26
@@ -43,21 +43,20 @@
    [Section2]
    Key4: Value4
 
-   The restricted format is used for server descriptors.   
+   The restricted format is used for server descriptors.
    """
 
-__all__ = [ 'getConfig', 'loadConfig' ]
+__all__ = [ 'ConfigError', 'ClientConfig' ]
 
+import binascii
 import os
 import re
-import binascii
 import socket # for inet_aton and error
 from cStringIO import StringIO
 
 import mixminion.Common
-from mixminion.Common import MixError, LOG, isPrintingAscii, stripSpace
-import mixminion.Packet
 import mixminion.Crypto
+from mixminion.Common import MixError, LOG, isPrintingAscii, stripSpace
 
 class ConfigError(MixError):
     """Thrown when an error is found in a configuration file."""
@@ -65,7 +64,7 @@
 
 #----------------------------------------------------------------------
 # Validation functions.  These are used to convert values as they appear
-# in configuration files and server descriptors into corresponding Python 
+# in configuration files and server descriptors into corresponding Python
 # objects, and validate their formats
 
 def _parseBoolean(boolean):
@@ -269,7 +268,7 @@
 	yyyy, MM, dd, hh, mm, ss = map(int, m.groups())
     else:
 	yyyy, MM, dd = map(int, m.groups())
-	hh, mm, ss = 0, 0, 0	
+	hh, mm, ss = 0, 0, 0
 
     if not ((1 <= dd <= 31) and (1 <= MM <= 12) and
 	    (1970 <= yyyy)  and (0 <= hh < 24) and
@@ -348,7 +347,7 @@
 
     # Make sure all characters in the file are ASCII.
     if not isPrintingAscii(contents):
-	raise ConfigError("Invalid characters in file: %r", badchars)
+	raise ConfigError("Invalid characters in file")
 
     #FFFF We should really use xreadlines or something if we have a file.
     fileLines = contents.split("\n")
@@ -415,7 +414,7 @@
     #  _sections: A map from secname->key->value.
     #  _sectionEntries: A  map from secname->[ (key, value) ] inorder.
     #  _sectionNames: An inorder list of secnames.
-    #  _callbacks: A map from section name to a callback function that should 
+    #  _callbacks: A map from section name to a callback function that should
     #      be invoked with (section,sectionEntries) after each section is
     #      read.  This shouldn't be used for validation; it's for code that
     #      needs to change the semantics of the parser.
@@ -583,10 +582,10 @@
             elif not self_sections.has_key(secName):
                 self_sections[secName] = {}
                 self_sectionEntries[secName] = []
-                
+
         if not self.assumeValid:
             # Call our validation hook.
-            self.validate(self_sections, self_sectionEntries, 
+            self.validate(self_sections, self_sectionEntries,
                           sectionEntryLines, fileContents)
 
         self._sections = self_sections
@@ -663,113 +662,6 @@
 	if p < 4:
 	    LOG.warn("Your default path length is frighteningly low."
 			  "  I'll trust that you know what you're doing.")
-
-SERVER_SYNTAX =  {
-        'Host' : ClientConfig._syntax['Host'],
-        'Server' : { '__SECTION__' : ('REQUIRE', None, None),
-                     'Homedir' : ('ALLOW', None, "/var/spool/minion"),
-                     'LogFile' : ('ALLOW', None, None),
-                     'LogLevel' : ('ALLOW', _parseSeverity, "WARN"),
-                     'EchoMessages' : ('ALLOW', _parseBoolean, "no"),
-                     'EncryptIdentityKey' : ('REQUIRE', _parseBoolean, "yes"),
-		     'IdentityKeyBits': ('ALLOW', _parseInt, "2048"),
-                     'PublicKeyLifetime' : ('ALLOW', _parseInterval,
-                                            "30 days"),
-                     'PublicKeySloppiness': ('ALLOW', _parseInterval,
-                                             "5 minutes"),
-                     'EncryptPrivateKey' : ('REQUIRE', _parseBoolean, "no"),
-                     'Mode' : ('REQUIRE', _parseServerMode, "local"),
-                     'Nickname': ('ALLOW', None, None),
-                     'Contact-Email': ('ALLOW', None, None),
-                     'Comments': ('ALLOW', None, None),
-                     'ModulePath': ('ALLOW', None, None),
-                     'Module': ('ALLOW*', None, None),
-                     },
-        'DirectoryServers' : { 'ServerURL' : ('ALLOW*', None, None),
-                               'Publish' : ('ALLOW', _parseBoolean, "no"),
-                               'MaxSkew' : ('ALLOW', _parseInterval,
-                                            "10 minutes",) },
-	# FFFF Generic multi-port listen/publish options.
-        'Incoming/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
-			    'IP' : ('ALLOW', _parseIP, "0.0.0.0"),
-                            'Port' : ('ALLOW', _parseInt, "48099"),
-                            'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
-                            'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
-        'Outgoing/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
-                            'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
-                            'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
-	# FFFF Missing: Queue-Size / Queue config options
-	# FFFF         timeout options
-	# FFFF         listen timeout??
-	# FFFF         Retry options
-	# FFFF         pool options
-        }
-
-class ServerConfig(_ConfigFile):
-    ##
-    # Fields: 
-    #   moduleManager
-    #
-    _restrictFormat = 0
-    def __init__(self, fname=None, string=None, moduleManager=None):
-	# We use a copy of SERVER_SYNTAX, because the ModuleManager will
-	# mess it up.
-        self._syntax = SERVER_SYNTAX.copy()
-
-        import mixminion.server.Modules
-	if moduleManager is None:
-	    self.moduleManager = mixminion.server.Modules.ModuleManager()
-	else:
-	    self.moduleManager = moduleManager
-        self._addCallback("Server", self.__loadModules)    
-
-        _ConfigFile.__init__(self, fname, string)
-
-    def validate(self, sections, entries, lines, contents):
-	log = LOG
-	_validateHostSection(sections.get('Host', {}))
-	# Server section
-	server = sections['Server']
-	bits = server['IdentityKeyBits']
-	if not (2048 <= bits <= 4096):
-	    raise ConfigError("IdentityKeyBits must be between 2048 and 4096")
-	if server['EncryptIdentityKey']:
-	    log.warn("Identity key encryption not yet implemented")
-	if server['EncryptPrivateKey']:
-	    log.warn("Encrypted private keys not yet implemented")
-	if server['PublicKeyLifetime'][2] < 24*60*60:
-	    raise ConfigError("PublicKeyLifetime must be at least 1 day.")
-	if server['PublicKeySloppiness'][2] > 20*60:
-	    raise ConfigError("PublicKeySloppiness must be <= 20 minutes.")
-	if [e for e in entries['Server'] if e[0]=='Mode']:
-	    log.warn("Mode specification is not yet supported.")
-
-	if not sections['Incoming/MMTP'].get('Enabled'):
-	    log.warn("Disabling incoming MMTP is not yet supported.")
-	if [e for e in entries['Incoming/MMTP'] if e[0] in ('Allow', 'Deny')]:
-	    log.warn("Allow/deny are not yet supported")
-
-	if not sections['Outgoing/MMTP'].get('Enabled'):
-	    log.warn("Disabling incoming MMTP is not yet supported.")
-	if [e for e in entries['Outgoing/MMTP'] if e[0] in ('Allow', 'Deny')]:
-	    log.warn("Allow/deny are not yet supported")
-
-        self.moduleManager.validate(sections, entries, lines, contents)
-
-    def __loadModules(self, section, sectionEntries):
-	"""Callback from the [Server] section of a config file.  Parses
-	   the module options, and adds new sections to the syntax 
-	   accordingly."""
-        self.moduleManager.setPath(section.get('ModulePath', None))
-        for mod in section.get('Module', []):
-	    LOG.info("Loading module %s", mod)
-            self.moduleManager.loadExtModule(mod)
-
-        self._syntax.update(self.moduleManager.getConfigSyntax())
-    
-    def getModuleManager(self):
-	"Return the module manager initialized by this server."
-	return self.moduleManager
 
 def _validateHostSection(sec):
     """Helper function: Makes sure that the shared [Host] section is correct;

Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -d -r1.26 -r1.27
--- Crypto.py	11 Dec 2002 05:53:33 -0000	1.26
+++ Crypto.py	12 Dec 2002 19:56:46 -0000	1.27
@@ -8,26 +8,24 @@
    the functions in mixminion.Crypto, and not call _minionlib's crypto
    functionality themselves."""
 
+import copy_reg
 import os
-import sys
 import stat
-import copy_reg
+import sys
 from types import StringType
 
 import mixminion._minionlib as _ml
 from mixminion.Common import MixError, MixFatalError, floorDiv, ceilDiv, LOG
 
-__all__ = [ 'CryptoError', 'init_crypto', 'sha1', 'ctr_crypt', 'prng',
-            'strxor', 'lioness_encrypt', 'lioness_decrypt',
-            'bear_encrypt', 'bear_decrypt', 'trng',
-            'pk_encrypt', 'pk_decrypt', 'pk_sign', 'pk_check_signature',
-	    'pk_generate', 'openssl_seed',
-            'pk_get_modulus', 'pk_from_modulus',
-            'pk_encode_private_key', 'pk_decode_private_key',
-            'Keyset', 'AESCounterPRNG', 'HEADER_SECRET_MODE',
-            'PRNG_MODE', 'RANDOM_JUNK_MODE', 'HEADER_ENCRYPT_MODE',
-            'APPLICATION_KEY_MODE', 'PAYLOAD_ENCRYPT_MODE',
-            'HIDE_HEADER_MODE' ]
+__all__ = [ 'AESCounterPRNG', 'CryptoError', 'Keyset', 'bear_decrypt',
+            'bear_encrypt', 'ctr_crypt', 'init_crypto', 'lioness_decrypt',
+            'lioness_encrypt', 'openssl_seed', 'pk_check_signature',
+            'pk_decode_private_key', 'pk_decrypt', 'pk_encode_private_key',
+            'pk_encrypt', 'pk_from_modulus', 'pk_generate', 'pk_get_modulus',
+            'pk_sign', 'prng', 'sha1', 'strxor', 'trng',
+	    'AES_KEY_LEN', 'DIGEST_LEN', 'HEADER_SECRET_MODE', 'PRNG_MODE',
+            'RANDOM_JUNK_MODE', 'HEADER_ENCRYPT_MODE', 'APPLICATION_KEY_MODE',
+            'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
 
 # Expose _minionlib.CryptoError as Crypto.CryptoError
 CryptoError = _ml.CryptoError
@@ -50,7 +48,7 @@
     except:
         raise MixFatalError("Error initializing entropy source")
     openssl_seed(40)
-    
+
 def sha1(s):
     """Return the SHA1 hash of a string"""
     return _ml.sha1(s)
@@ -96,11 +94,11 @@
     # may look slow, but it contributes only .7% to the total time for
     # LIONESS.
     right = _ml.aes_ctr128_crypt(
- 	_ml.aes_key(_ml.sha1("".join((key1,left,key1)))[:AES_KEY_LEN]), 
- 	right, 0) 
+ 	_ml.aes_key(_ml.sha1("".join((key1,left,key1)))[:AES_KEY_LEN]),
+ 	right, 0)
     left = _ml.strxor(left,  _ml.sha1("".join((key2,right,key2))))
     right = _ml.aes_ctr128_crypt(
-	_ml.aes_key(_ml.sha1("".join((key3,left,key3)))[:AES_KEY_LEN]), 
+	_ml.aes_key(_ml.sha1("".join((key3,left,key3)))[:AES_KEY_LEN]),
  	right, 0)
     left = _ml.strxor(left,  _ml.sha1("".join((key4,right,key4))))
 
@@ -109,7 +107,7 @@
     #   left = strxor(left, sha1("".join((key2,right,key2))))
     #   right = ctr_crypt(right, "".join((key3,left,key3))[:AES_KEY_LEN])
     #   left = strxor(left, sha1("".join((key4,right,key4))))
-    # but that would be slower by about 10%.  (Since LIONESS is in the 
+    # but that would be slower by about 10%.  (Since LIONESS is in the
     # critical path, we care.)
 
     return left + right
@@ -219,7 +217,7 @@
 
 def pk_check_signature(data, key):
     """If data holds the RSA signature of some OAEP-padded data, check the
-       signature using public key 'key', and return the orignal data.  
+       signature using public key 'key', and return the orignal data.
        Throw CryptoError on failure. """
     bytes = key.get_modulus_bytes()
     # private key decrypt
@@ -276,7 +274,7 @@
         rsa = _ml.rsa_PEM_read_key(f, 0)
     f.close()
     return rsa
-    
+
 def _pickle_rsa(rsa):
     return _ml.rsa_make_public_key, rsa.get_public_key()
 
@@ -345,7 +343,7 @@
     # This test (though required in the OAEP spec) is extraneous here.
     #if len(data) < 2*DIGEST_LEN+1:
     #    raise CryptoError("Decoding error")
-    
+
     if data[0]!= '\x00':
         raise CryptoError("Decoding error")
     maskedSeed, maskedDB = data[1:DIGEST_LEN+1], data[DIGEST_LEN+1:]
@@ -430,7 +428,7 @@
         key2 = _ml.strxor(key1, z19+"\x01")
         key3 = _ml.strxor(key1, z19+"\x02")
         key4 = _ml.strxor(key1, z19+"\x03")
-        
+
         return (key1, key2, key3, key4)
 
     def getBearKeys(self,mode):
@@ -441,13 +439,13 @@
 
 def lioness_keys_from_payload(payload):
     '''Given a payload, returns the LIONESS keys to encrypt the off-header
-       at the swap point.''' 
+       at the swap point.'''
     digest = sha1(payload)
     return Keyset(digest).getLionessKeys(HIDE_HEADER_MODE)
 
 def lioness_keys_from_header(header2):
     '''Given the off-header, returns the LIONESS keys to encrypt the payload
-       at the swap point.''' 
+       at the swap point.'''
     digest = sha1(header2)
     return Keyset(digest).getLionessKeys(HIDE_PAYLOAD_MODE)
 
@@ -524,9 +522,9 @@
 	while 1:
 	    # Get a random positive int between 0 and 0x7fffffff.
 	    b = self.getBytes(4)
-	    o = (((((((_ord(b[0])&0x7f)<<8) + 
- 		       _ord(b[1]))<<8) + 
-	 	       _ord(b[2]))<<8) + 
+	    o = (((((((_ord(b[0])&0x7f)<<8) +
+ 		       _ord(b[1]))<<8) +
+	 	       _ord(b[2]))<<8) +
 	               _ord(b[3]))
 	    # Retry if we got a value that would fall in an incomplete
 	    # run of 'max' elements.
@@ -537,7 +535,7 @@
 	"""Return a floating-point number between 0 and 1."""
 	b = self.getBytes(4)
 	_ord = ord
-	o = ((((((_ord(b[0])&0x7f)<<8) + _ord(b[1]))<<8) + 
+	o = ((((((_ord(b[0])&0x7f)<<8) + _ord(b[1]))<<8) +
 	      _ord(b[2]))<<8) + _ord(b[3])
 	#return o / float(0x7fffffff)
 	return o / 2147483647.0
@@ -560,7 +558,7 @@
         if seed is None:
             seed = trng(AES_KEY_LEN)
         self.key = aes_key(seed)
-        
+
     def _prng(self, n):
         """Implementation: uses the AES counter stream to generate entropy."""
         c = self.counter
@@ -598,7 +596,7 @@
 _TRNG_FILENAME = None
 def configure_trng(config):
     """Initialize the true entropy source from a given Config object.  If
-       none is provided, tries some sane defaults.""" 
+       none is provided, tries some sane defaults."""
     global _TRNG_FILENAME
     if config is not None:
         requestedFile = config['Host'].get('EntropySource', None)
@@ -614,7 +612,7 @@
     # device.
     randFile = None
     for file in files:
-	if file is None: 
+	if file is None:
 	    continue
 
 	verbose = 1#(file == requestedFile)
@@ -640,7 +638,7 @@
     else:
 	LOG.info("Setting entropy source to %r", randFile)
         _TRNG_FILENAME = randFile
-    
+
 class _TrueRNG(RNG):
     '''Random number generator that yields pieces of entropy from
        our true rng.'''

Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- MMTPClient.py	9 Dec 2002 04:47:40 -0000	1.12
+++ MMTPClient.py	12 Dec 2002 19:56:46 -0000	1.13
@@ -3,7 +3,7 @@
 """mixminion.MMTPClient
 
    This module contains a single, synchronous implementation of the client
-   side of the Mixminion Transfer protocol.  You can use this client to 
+   side of the Mixminion Transfer protocol.  You can use this client to
    upload messages to any conforming Mixminion server.
 
    (We don't use this module for tranferring packets between servers;
@@ -15,6 +15,8 @@
    FFFF: As yet unsupported are: Session resumption and key renegotiation.
    FFFF: Also unsupported: timeouts."""
 
+__all__ = [ "BlockingClientConnection", "sendMessages" ]
+
 import socket
 import mixminion._minionlib as _ml
 from mixminion.Crypto import sha1
@@ -26,14 +28,14 @@
     """
     ## Fields:
     # targetIP -- the dotted-quad, IPv4 address of our server.
-    # targetPort -- the port on the server 
+    # targetPort -- the port on the server
     # targetKeyID -- sha1 hash of the ASN1 encoding of the public key we
     #   expect the server to use.
     # context: a TLSContext object; used to create connections.
     # sock: a TCP socket, open to the server.
     # tls: a TLS socket, wrapping sock.
     def __init__(self, targetIP, targetPort, targetKeyID):
-        """Open a new connection.""" 
+        """Open a new connection."""
         self.targetIP = targetIP
         self.targetPort = targetPort
         self.targetKeyID = targetKeyID
@@ -46,7 +48,7 @@
 	# Connect to the server
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.sock.setblocking(1)
-	LOG.debug("Connecting to %s:%s", self.targetIP, self.targetPort) 
+	LOG.debug("Connecting to %s:%s", self.targetIP, self.targetPort)
 
 	# Do the TLS handshaking
         self.sock.connect((self.targetIP,self.targetPort))
@@ -63,7 +65,7 @@
         if self.targetKeyID is not None and (keyID != self.targetKeyID):
             raise MixProtocolError("Bad Key ID: Expected %r but got %r" % (
                 self.targetKeyID, keyID))
-        
+
         ####
         # Protocol negotiation
         # For now, we only support 1.0, but we call it 0.1 so we can
@@ -75,7 +77,7 @@
         if inp != "MMTP 0.1\r\n":
             raise MixProtocolError("Protocol negotiation failed")
 	LOG.debug("MMTP protocol negotated: version 0.1")
-        
+
     def sendPacket(self, packet):
         """Send a single packet to a server."""
         assert len(packet) == 1<<15
@@ -86,7 +88,7 @@
         self.tls.write(packet)
         self.tls.write(sha1(packet+"SEND"))
 	LOG.debug("Packet sent; waiting for ACK")
-        
+
 	# And we expect, "RECEIVED\r\n", and sha1(packet|"RECEIVED")
         inp = self.tls.read(len("RECEIVED\r\n")+20)
         if inp != "RECEIVED\r\n"+sha1(packet+"RECEIVED"):

Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- Main.py	11 Dec 2002 06:58:55 -0000	1.12
+++ Main.py	12 Dec 2002 19:56:46 -0000	1.13
@@ -26,9 +26,9 @@
         "       You seem to be running version %s.\n")%_ver)
     sys.exit(1)
 
+import getopt
 import os
 import stat
-import getopt
 import types
 
 def filesAreSame(f1, f2):
@@ -45,7 +45,7 @@
 
 def correctPath(myself):
     "Given a command (sys.argv[0]), fix sys.path so 'import mixminion' works"
-    # (If the admin uses distutils to install Mixminion, the code will 
+    # (If the admin uses distutils to install Mixminion, the code will
     # wind up somewhere appropriate on pythonpath.  This isn't good enough,
     # however: we want to run even when sysadmins don't understand distutils.)
 
@@ -82,7 +82,7 @@
 	    continue
 	if os.path.normpath(pathEntry) == parentdir:
 	    foundEntry = 1; break
-		
+
 	ent = os.path.join(pathEntry, 'mixminion', 'Main.py')
 	if os.path.exists(ent) and filesAreSame(pathEntry, myself):
 	    foundEntry = 1; break
@@ -108,7 +108,7 @@
 #  'Main.py <cmd> arg1 arg2 arg3' will result in a call to <function_name>
 #   in <module_name>.  The function should take two arguments: a string to
 #   be used as command name in error messages, and a list of [arg1,arg2,arg3].'
-#   
+#
 #   By convention, all commands must print a usage message and exit when
 #   invoked with a single argument, "--help"
 _COMMANDS = {
@@ -134,7 +134,7 @@
     # Specifically, args[0] is used to fix sys.path so we can import
     # mixminion.*; args[1] is used to select a command name from _COMMANDS,
     # and args[2:] are passed to the command we select.
-    
+
     correctPath(args[0])
 
     # Check whether we have a recognized command.

Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- Packet.py	11 Dec 2002 05:53:33 -0000	1.20
+++ Packet.py	12 Dec 2002 19:56:46 -0000	1.21
@@ -208,7 +208,7 @@
 	assert self.routingtype >= MIN_EXIT_TYPE
 	assert len(self.routinginfo) >= TAG_LEN
 	return self.routinginfo[TAG_LEN:]
-    
+
     def getTag(self):
 	"""Return the part of the routingInfo that contains the decoding
 	   tag. (Requires that routingType is an exit type.)"""
@@ -291,7 +291,7 @@
 ENC_FWD_OVERHEAD = OAEP_OVERHEAD - TAG_LEN + SECRET_LEN
 
 def parsePayload(payload):
-    """Convert a decoded mixminion payload into a SingletonPayload or a 
+    """Convert a decoded mixminion payload into a SingletonPayload or a
        FragmentPayload object.  Raise ParseError on failure or data
        corruption."""
     if len(payload) not in (PAYLOAD_LEN, PAYLOAD_LEN-ENC_FWD_OVERHEAD):
@@ -338,7 +338,7 @@
 	return 1
 
     def getContents(self):
-	"""Returns the non-padding portion of this payload's data""" 
+	"""Returns the non-padding portion of this payload's data"""
 	return self.data[:self.size]
 
     def pack(self):

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -d -r1.24 -r1.25
--- ServerInfo.py	11 Dec 2002 05:53:33 -0000	1.24
+++ ServerInfo.py	12 Dec 2002 19:56:46 -0000	1.25
@@ -10,19 +10,14 @@
 
 __all__ = [ 'ServerInfo' ]
 
-import time
-import os
-import base64
-import socket
-
-from mixminion.Common import createPrivateDir, LOG, MixError, formatTime, \
-     formatDate
-from mixminion.Packet import IPV4Info
 import mixminion.Config
 import mixminion.Crypto
-from mixminion.Crypto import DIGEST_LEN
 
-ConfigError = mixminion.Config.ConfigError
+from mixminion.Common import LOG, MixError, createPrivateDir, formatBase64, \
+    formatDate, formatTime
+from mixminion.Config import ConfigError
+from mixminion.Packet import IPV4Info
+from mixminion.Crypto import DIGEST_LEN
 
 # Longest allowed Nickname
 MAX_NICKNAME = 128
@@ -127,8 +122,8 @@
 				  inMMTP['Version'])
 	    if len(inMMTP['Key-Digest']) != DIGEST_LEN:
 		raise ConfigError("Invalid key digest %s"%
-				  base64.endodestring(inMMTP['Key-Digest']))
-	
+				  formatBase64(inMMTP['Key-Digest']))
+
 	## Outgoing/MMTP section
 	outMMTP = sections['Outgoing/MMTP']
 	if outMMTP:
@@ -136,9 +131,9 @@
 		raise ConfigError("Unrecognized MMTP descriptor version %s"%
 				  inMMTP['Version'])
 
-	# FFFF When a better client module system exists, check the 
+	# FFFF When a better client module system exists, check the
 	# FFFF module descriptors.
-	
+
     def getNickname(self):
 	"""Returns this server's nickname"""
 	return self['Server']['Nickname']
@@ -165,241 +160,6 @@
         return IPV4Info(self.getAddr(), self.getPort(), self.getKeyID())
 
 #----------------------------------------------------------------------
-class ServerKeyset:
-    """A set of expirable keys for use by a server.
-
-       A server has one long-lived identity key, and two short-lived
-       temporary keys: one for subheader encryption and one for MMTP.  The
-       subheader (or 'packet') key has an associated hashlog, and the
-       MMTP key has an associated self-signed X509 certificate.
-
-       Whether we publish or not, we always generate a server descriptor
-       to store the keys' lifetimes.
-
-       When we create a new ServerKeyset object, the associated keys are not
-       read from disk unil the object's load method is called."""
-    ## Fields:
-    # hashlogFile: filename of this keyset's hashlog.
-    # packetKeyFile, mmtpKeyFile: filename of this keyset's short-term keys
-    # certFile: filename of this keyset's X509 certificate
-    # descFile: filename of this keyset's server descriptor.
-    #
-    # packetKey, mmtpKey: This server's actual short-term keys.
-    def __init__(self, keyroot, keyname, hashroot):
-	"""Load a set of keys named "keyname" on a server where all keys
-	   are stored under the directory "keyroot" and hashlogs are stored
-	   under "hashroot". """
-	keydir  = os.path.join(keyroot, "key_"+keyname)
-	self.hashlogFile = os.path.join(hashroot, "hash_"+keyname)
-	self.packetKeyFile = os.path.join(keydir, "mix.key")
-	self.mmtpKeyFile = os.path.join(keydir, "mmtp.key")
-	self.certFile = os.path.join(keydir, "mmtp.cert")
-        self.descFile = os.path.join(keydir, "ServerDesc")
-        if not os.path.exists(keydir):
-	    createPrivateDir(keydir)
-
-    def load(self, password=None):
-        """Read the short-term keys from disk.  Must be called before 
-	   getPacketKey or getMMTPKey."""
-        self.packetKey = mixminion.Crypto.pk_PEM_load(self.packetKeyFile,
-                                                      password)
-        self.mmtpKey = mixminion.Crypto.pk_PEM_load(self.mmtpKeyFile,
-                                                    password)
-    def save(self, password=None):
-        """Save this set of keys to disk."""
-        mixminion.Crypto.pk_PEM_save(self.packetKey, self.packetKeyFile,
-                                     password)
-        mixminion.Crypto.pk_PEM_save(self.mmtpKey, self.mmtpKeyFile,
-                                     password)
-    def getCertFileName(self): return self.certFile
-    def getHashLogFileName(self): return self.hashlogFile
-    def getDescriptorFileName(self): return self.descFile
-    def getPacketKey(self): return self.packetKey
-    def getMMTPKey(self): return self.mmtpKey
-    def getMMTPKeyID(self):
-        "Return the sha1 hash of the asn1 encoding of the MMTP public key"
-	return mixminion.Crypto.sha1(self.mmtpKey.encode_key(1))
-
-def _base64(s):
-    "Helper function: returns a one-line base64 encoding of a given string."
-    return base64.encodestring(s).replace("\n", "")
-
-def _rule(allow, (ip, mask, portmin, portmax)):
-    """Return an external represenntation of an IP allow/deny rule."""
-    if mask == '0.0.0.0':
-        ip="*"
-        mask=""
-    elif mask == "255.255.255.255":
-        mask = ""
-    else:
-        mask = "/%s" % mask
-
-    if portmin==portmax==48099 and allow:
-        ports = ""
-    elif portmin == 0 and portmax == 65535 and not allow:
-        ports = ""
-    elif portmin == portmax:
-        ports = " %s" % portmin
-    else:
-        ports = " %s-%s" % (portmin, portmax)
-
-    return "%s%s%s\n" % (ip,mask,ports)
-
-# We have our X509 certificate set to expire a bit after public key does,
-# so that slightly-skewed clients don't incorrectly give up while trying to
-# connect to us.
-CERTIFICATE_EXPIRY_SLOPPINESS = 5*60
-
-def generateServerDescriptorAndKeys(config, identityKey, keydir, keyname,
-                                    hashdir,
-                                    validAt=None):
-    """Generate and sign a new server descriptor, and generate all the keys to
-       go with it.
-
-          config -- Our ServerConfig object.
-          identityKey -- This server's private identity key
-          keydir -- The root directory for storing key sets.
-          keyname -- The name of this new key set within keydir
-	  hashdir -- The root directory for storing hash logs.
-          validAt -- The starting time (in seconds) for this key's lifetime."""
-
-    # First, we generate both of our short-term keys...
-    packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
-    mmtpKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
-
-    # ...and save them to disk, setting up our directory structure while
-    # we're at it.
-    serverKeys = ServerKeyset(keydir, keyname, hashdir)
-    serverKeys.packetKey = packetKey
-    serverKeys.mmtpKey = mmtpKey
-    serverKeys.save()
-
-    # FFFF unused
-    # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)
-
-    # Now, we pull all the information we need from our configuration.
-    nickname = config['Server']['Nickname']
-    if not nickname:
-        nickname = socket.gethostname()
-        if not nickname or nickname.lower().startswith("localhost"):
-            nickname = config['Incoming/MMTP'].get('IP', "<Unknown host>")
-	LOG.warn("No nickname given: defaulting to %r", nickname)
-    contact = config['Server']['Contact-Email']
-    comments = config['Server']['Comments']
-    if not validAt:
-        validAt = time.time()
-    
-    # Calculate descriptor and X509 certificate lifetimes.
-    # (Round validAt to previous mignight.)
-    validAt = mixminion.Common.previousMidnight(validAt+30)
-    validUntil = validAt + config['Server']['PublicKeyLifetime'][2]
-    certStarts = validAt - CERTIFICATE_EXPIRY_SLOPPINESS
-    certEnds = validUntil + CERTIFICATE_EXPIRY_SLOPPINESS + \
-               config['Server']['PublicKeySloppiness'][2]
-
-    # Create the X509 certificate.
-    mixminion.Crypto.generate_cert(serverKeys.getCertFileName(),
-				   mmtpKey,
-				   "MMTP certificate for %s" %nickname,
-                                   certStarts, certEnds)
-
-    fields = {
-	"IP": config['Incoming/MMTP'].get('IP', "0.0.0.0"),
-	"Port": config['Incoming/MMTP'].get('Port', 0),
-	"Nickname": nickname,
-	"Identity":
-	   _base64(mixminion.Crypto.pk_encode_public_key(identityKey)),
-	"Published": formatTime(time.time()),
-	"ValidAfter": formatDate(validAt),
-	"ValidUntil": formatDate(validUntil),
-	"PacketKey":
-  	   _base64(mixminion.Crypto.pk_encode_public_key(packetKey)),
-	"KeyID":
-	   _base64(serverKeys.getMMTPKeyID()),
-	}
-
-    # If we don't know our IP address, try to guess
-    if fields['IP'] == '0.0.0.0':
-	try:
-	    fields['IP'] = _guessLocalIP()
-	    LOG.warn("No IP configured; guessing %s",fields['IP'])
-	except IPGuessError, e:
-	    LOG.error("Can't guess IP: %s", str(e))
-	    raise MixError("Can't guess IP: %s" % str(e))
-	
-    # Fill in a stock server descriptor.  Note the empty Digest: and 
-    # Signature: lines.
-    info = """\
-        [Server]
-	Descriptor-Version: 0.1
-        IP: %(IP)s
-        Nickname: %(Nickname)s
-	Identity: %(Identity)s
-	Digest:
-        Signature:
-        Published: %(Published)s
-        Valid-After: %(ValidAfter)s
-	Valid-Until: %(ValidUntil)s
-	Packet-Key: %(PacketKey)s
-        """ % fields
-    if contact:
-	info += "Contact: %s\n"%contact
-    if comments:
-	info += "Comments: %s\n"%comments
-
-    # Only advertise incoming MMTP if we support it.
-    if config["Incoming/MMTP"].get("Enabled", 0):
-	info += """\
-            [Incoming/MMTP]
-            Version: 0.1
-            Port: %(Port)s
-	    Key-Digest: %(KeyID)s
-	    Protocols: 0.1
-            """ % fields
-        for k,v in config.getSectionItems("Incoming/MMTP"):
-            if k not in ("Allow", "Deny"):
-                continue
-            info += "%s: %s" % (k, _rule(k=='Allow',v))
-
-    # Only advertise outgoing MMTP if we support it.
-    if config["Outgoing/MMTP"].get("Enabled", 0):
-	info += """\
-            [Outgoing/MMTP]
-	    Version: 0.1
-            Protocols: 0.1
-            """
-        for k,v in config.getSectionItems("Outgoing/MMTP"):
-            if k not in ("Allow", "Deny"):
-                continue
-            info += "%s: %s" % (k, _rule(k=='Allow',v))
-
-    # Ask our modules for their configuration information.
-    info += "".join(config.moduleManager.getServerInfoBlocks())
-
-    # Remove extra (leading or trailing) whitespace from the lines.
-    lines = [ line.strip() for line in info.split("\n") ]
-    # Remove empty lines
-    lines = filter(None, lines)
-    # Force a newline at the end of the file, rejoin, and sign.
-    lines.append("")
-    info = "\n".join(lines)
-    info = signServerInfo(info, identityKey)
-
-    # Write the desciptor
-    f = open(serverKeys.getDescriptorFileName(), 'w')
-    try:
-        f.write(info)
-    finally:
-        f.close()
-
-    # This is for debugging: we try to parse and validate the descriptor 
-    #   we just made.
-    # FFFF Remove this once we're more confident.
-    ServerInfo(string=info)
-
-    return info
-
-#----------------------------------------------------------------------
 def getServerInfoDigest(info):
     """Calculate the digest of a server descriptor"""
     return _getServerInfoDigestImpl(info, None)
@@ -443,61 +203,10 @@
     # If we got an RSA key, we need to add the digest and signature.
 
     signature = mixminion.Crypto.pk_sign(digest,rsa)
-    digest = _base64(digest)
-    signature = base64.encodestring(signature).replace("\n","")
+    digest = formatBase64(digest)
+    signature = formatBase64(signature)
     infoLines[digestLine] = 'Digest: '+digest
     infoLines[signatureLine] = 'Signature: '+signature
 
     return "\n".join(infoLines)
 
-class IPGuessError(MixError):
-    """Exception: raised when we can't guess a single best IP."""
-    pass
-
-# Cached guessed IP address
-_GUESSED_IP = None
-
-def _guessLocalIP():
-    "Try to find a reasonable IP for this host."
-    global _GUESSED_IP
-    if _GUESSED_IP is not None:
-	return _GUESSED_IP
-
-    # First, let's see what our name resolving subsystem says our
-    # name is.
-    ip_set = {}
-    try:
-	ip_set[ socket.gethostbyname(socket.gethostname()) ] = 1
-    except socket.error:
-	try:
-	    ip_set[ socket.gethostbyname(socket.getfqdn()) ] = 1
-	except socket.error:
-	    pass
-
-    # And in case that doesn't work, let's see what other addresses we might
-    # think we have by using 'getsockname'.
-    for target_addr in ('18.0.0.1', '10.0.0.1', '192.168.0.1',
-			'172.16.0.1')+tuple(ip_set.keys()):
-	# open a datagram socket so that we don't actually send any packets
-	# by connecting.
-	try:
-	    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-	    s.connect((target_addr, 9)) #discard port
-	    ip_set[ s.getsockname()[0] ] = 1
-	except socket.error:
-	    pass
-
-    for ip in ip_set.keys():
-	if ip.startswith("127.") or ip.startswith("0."):
-	    del ip_set[ip]
-
-    # FFFF reject 192.168, 10., 176.16.x
-
-    if len(ip_set) == 0:
-	raise IPGuessError("No address found")
-
-    if len(ip_set) > 1:
-	raise IPGuessError("Multiple addresses found: %s" % (
-	            ", ".join(ip_set.keys())))
-
-    return ip_set.keys()[0]

Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- __init__.py	11 Dec 2002 06:58:55 -0000	1.9
+++ __init__.py	12 Dec 2002 19:56:46 -0000	1.10
@@ -3,11 +3,12 @@
 
 """mixminion
 
-   Client and server code for type III anonymous remailers.
+   Client and shared code for type III anonymous remailers.
    """
 
 __version__ = "0.0.1a0"
-## __all__ = [ ]
+__all__ = [ 'server' ]
+
 ## import mixminion.BuildMessage
 ## import mixminion.Crypto
 ## import mixminion.Common

Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -d -r1.17 -r1.18
--- benchmark.py	11 Dec 2002 06:58:55 -0000	1.17
+++ benchmark.py	12 Dec 2002 19:56:46 -0000	1.18
@@ -10,12 +10,26 @@
    >>> mixminion.benchmark.timeAll()
 
    """
+__pychecker__ = 'no-funcdoc no-reimport'
+__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
 
+
+import os
+import stat
 from time import time
-from mixminion.testSupport import mix_mktemp
 
-__pychecker__ = 'no-funcdoc no-reimport'
-__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
+import mixminion._minionlib as _ml
+from mixminion.BuildMessage import _buildHeader, buildForwardMessage
+from mixminion.Common import secureDelete, installSignalHandlers, \
+     waitForChildren
+from mixminion.Crypto import *
+from mixminion.Crypto import OAEP_PARAMETER
+from mixminion.Crypto import _add_oaep_padding, _check_oaep_padding
+from mixminion.Packet import SMTP_TYPE
+from mixminion.server.HashLog import HashLog
+from mixminion.server.PacketHandler import PacketHandler
+from mixminion.test import FakeServerInfo
+from mixminion.testSupport import mix_mktemp
 
 # If PRECISION_FACTOR is >1, we time everything for PRECISION_FACTOR times
 # more iterations than ususal.
@@ -74,10 +88,6 @@
         return "%d GB" % (n >> 30)
 
 #----------------------------------------------------------------------
-import mixminion._minionlib as _ml
-from Crypto import *
-from Crypto import _add_oaep_padding, _check_oaep_padding
-from Crypto import OAEP_PARAMETER
 
 short = "Hello, Dali!"
 s20b = "ABCDEFGHIJKLMNOPQRST"
@@ -163,7 +173,7 @@
 	  timeit((lambda c=c,L=L1000: c.shuffle(L)), 30)
     print "aesprng.shuffle (10/1000)", \
 	  timeit((lambda c=c,L=L1000: c.shuffle(L,10)), 1000)
-    
+
     lkey = Keyset("keymaterial foo bar baz").getLionessKeys("T")
     print "lioness E (1K)", timeit((
         lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
@@ -269,8 +279,6 @@
     print "Timing overhead: %s...%s" % (timestr(min(o)),timestr(max(o)))
 
 #----------------------------------------------------------------------
-import os
-import stat
 
 def hashlogTiming():
     print "#==================== HASH LOGS ======================="
@@ -286,11 +294,10 @@
                     pass
 
 def _hashlogTiming(fname, load):
-    from mixminion.server.HashLog import HashLog
 
     # Try more realistic access patterns.
     prng = AESCounterPRNG("a"*16)
-    
+
     print "Testing hash log (%s entries)"%load
     if load > 20000:
 	print "This may take a few minutes..."
@@ -335,8 +342,6 @@
     print "File size (%s entries)"%load, spacestr(size)
 
 #----------------------------------------------------------------------
-from mixminion.BuildMessage import _buildHeader, buildForwardMessage
-from mixminion.test import FakeServerInfo
 
 def buildMessageTiming():
 
@@ -345,7 +350,7 @@
     payload = ("Junky qoph flags vext crwd zimb."*1024)[:22*1024]
     serverinfo = [FakeServerInfo("127.0.0.1", 48099, pk,"x"*20)
                   ] * 16
-    
+
     def bh(np,it, serverinfo=serverinfo):
         ctr = AESCounterPRNG()
 
@@ -381,9 +386,6 @@
     def seenHash(self,h): return 0
     def logHash(self,h): pass
 
-from mixminion.server.PacketHandler import PacketHandler
-from mixminion.Packet import SMTP_TYPE
-
 def serverProcessTiming():
     print "#================= SERVER PROCESS ====================="
 
@@ -411,7 +413,7 @@
     # its efficiency.  If function X is pretty efficient, there's not much
     # reason to try to optimise its implementation; instead, we need to attack
     # the functions it uses.
-    
+
     ##### LIONESS
 
     shakey = "z"*20
@@ -498,9 +500,6 @@
 
 #----------------------------------------------------------------------
 
-from mixminion.Common import secureDelete, installSignalHandlers, \
-     waitForChildren
-
 def fileOpsTiming():
     print "#================= File ops ====================="
     installSignalHandlers(child=1,hup=0,term=0)
@@ -520,7 +519,7 @@
 
     waitForChildren()
     t = time()-t1
-    print "               (sync)", timestr(t) 
+    print "               (sync)", timestr(t)
 
     lst = [ os.path.join(dname,str(i)) for i in range(100,200) ]
     t1 = time()
@@ -531,9 +530,9 @@
 
     waitForChildren()
     t = time()-t1
-    print "          (sync)", timestr(t/100) 
+    print "          (sync)", timestr(t/100)
+
 
-        
 #----------------------------------------------------------------------
 def testLeaks1():
     print "Trying to leak (sha1,aes,xor,seed,oaep)"

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.46
retrieving revision 1.47
diff -u -d -r1.46 -r1.47
--- test.py	11 Dec 2002 06:58:55 -0000	1.46
+++ test.py	12 Dec 2002 19:56:46 -0000	1.47
@@ -13,16 +13,17 @@
 
 __pychecker__ = 'no-funcdoc maxlocals=100'
 
+import base64
+import cPickle
+import cStringIO
 import os
+import re
+import stat
 import sys
 import threading
 import time
-import re
-import base64
-import stat
 import types
-import cPickle
-import cStringIO
+from string import atoi
 
 # Not every post-2.0 version of Python has a working 'unittest' module, so
 # we include a copy with mixminion, as 'mixminion._unittest'.
@@ -35,10 +36,31 @@
 from mixminion.testSupport import mix_mktemp, suspendLog, resumeLog, \
      replaceAttribute, undoReplacedAttributes, replaceFunction, \
      getReplacedFunctionCallLog, clearReplacedFunctionCallLog
-from mixminion.Common import MixError, MixFatalError, MixProtocolError, \
-     LOG, previousMidnight, stringContains
 
+
+import mixminion.BuildMessage as BuildMessage
+import mixminion.ClientMain
+import mixminion.Config
 import mixminion.Crypto as Crypto
+import mixminion.MMTPClient
+import mixminion.Packet
+import mixminion.ServerInfo
+import mixminion._minionlib as _ml
+import mixminion.server.MMTPServer
+import mixminion.server.Modules
+import mixminion.server.ServerConfig
+import mixminion.server.ServerKeys
+import mixminion.server.ServerMain
+from mixminion.Common import *
+from mixminion.Common import Log, _FileLogHandler, _ConsoleLogHandler
+from mixminion.Config import _ConfigFile, ConfigError, _parseInt
+from mixminion.Crypto import *
+from mixminion.Packet import *
+from mixminion.server.HashLog import HashLog
+from mixminion.server.Modules import *
+from mixminion.server.PacketHandler import *
+from mixminion.server.Queue import *
+from mixminion.server.ServerKeys import generateServerDescriptorAndKeys
 
 # Set this flag to 1 in order to have all RSA keys and diffie-hellman params
 # generated independently.  Otherwise, we cache diffie-hellman parameters
@@ -64,7 +86,7 @@
 
 def findFirstDiff(s1, s2):
     """Helper function.  Returns the first index i for which s1[i] != s2[i],
-       or -1 s1 == s2."""       
+       or -1 s1 == s2."""
     if s1 == s2:
 	return -1
     last = min(len(s1), len(s2))
@@ -140,8 +162,6 @@
 
 class MiscTests(unittest.TestCase):
     def testDiv(self):
-	from mixminion.Common import floorDiv, ceilDiv
-
 	self.assertEquals(floorDiv(10,1), 10)
 	self.assertEquals(floorDiv(10,2), 5)
 	self.assertEquals(floorDiv(10,3), 3)
@@ -163,7 +183,6 @@
 	self.assertEquals(ceilDiv(-10,-3), 4)
 
     def testTimeFns(self):
-	from mixminion.Common import floorDiv, mkgmtime, previousMidnight
 	# This isn't a very good test.
 	now = int(time.time())
 	max_sec_per_day = 24*60*60+ 1
@@ -184,7 +203,6 @@
 	    self.assertEquals(previousMidnight(pm), pm)
 
     def test_isSMTPMailbox(self):
-	from mixminion.Common import isSMTPMailbox
 	# Do we accept good addresses?
 	for addr in "Foo@bar.com", "a@b", "a@b.c.d.e", "a!b.c@d", "z@z":
 	    self.assert_(isSMTPMailbox(addr))
@@ -197,7 +215,6 @@
 	    self.assert_(not isSMTPMailbox(addr))
 
 #----------------------------------------------------------------------
-import mixminion._minionlib as _ml
 
 class MinionlibCryptoTests(unittest.TestCase):
     """Tests for cryptographic C extensions."""
@@ -405,7 +422,6 @@
         self.assertEquals(p.encode_key(0), p2.encode_key(0))
 
 #----------------------------------------------------------------------
-from mixminion.Crypto import *
 
 class CryptoTests(unittest.TestCase):
     """Tests for Python cryptographic library"""
@@ -622,8 +638,6 @@
 	    self.assertEquals(s, range(100))
 
 #----------------------------------------------------------------------
-import mixminion.Packet
-from mixminion.Packet import *
 
 class PacketTests(unittest.TestCase):
     def test_subheader(self):
@@ -651,10 +665,10 @@
         self.failUnless(not s.isExtended())
         self.assertEquals(s.pack(), expected)
 
-        ts_eliot = "Who is the third who walks always beside you? / "+\
-                   "When I count, there are only you and I together / "+\
-                   "But when I look ahead up the white road / "+\
-                   "There is always another one walking beside you"
+        ts_eliot = ("Who is the third who walks always beside you? / "+
+		    "When I count, there are only you and I together / "+
+		    "But when I look ahead up the white road / "+
+		    "There is always another one walking beside you")
 
         s = Subheader(3,9,"abcdeabcdeabcdef",
                       "ABCDEFGHIJABCDEFGHIJ",
@@ -663,15 +677,18 @@
         self.assertEquals(len(ts_eliot), 186)
 
         # test extended subeaders
-        expected = "\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ\000\272\001\054Who is the third who walks always beside you"
+        expected = ("\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ"+
+		    "\000\272\001\054Who is the third who walks always"+
+		    " beside you")
         self.assertEquals(len(expected), mixminion.Packet.MAX_SUBHEADER_LEN)
         self.assertEquals(s.pack(), expected)
 
         extra = s.getExtraBlocks()
         self.assertEquals(len(extra), 2)
-        self.assertEquals(extra[0], "? / When I count, there are only you "+\
-                          "and I together / But when I look ahead up the white "+\
-                          "road / There is always another one walk")
+        self.assertEquals(extra[0],
+		 ("? / When I count, there are only you "+
+		  "and I together / But when I look ahead up the white "+
+		  "road / There is always another one walk"))
         self.assertEquals(extra[1], "ing beside you"+(114*'\000'))
 
         # test parsing extended subheaders
@@ -760,7 +777,7 @@
 
     def test_payloads(self):
 	# Checks for payload structure functions.
-	
+
 	# First, generate some plausible singleton payloads.
 	contents = ("payload"*(4*1024))[:28*1024 - 22]
 	hash = "HASH"*5
@@ -841,8 +858,6 @@
 
 
 #----------------------------------------------------------------------
-from mixminion.server.HashLog import HashLog
-
 class HashLogTests(unittest.TestCase):
     def test_hashlog(self):
 	# Create a new,empty hashlog.
@@ -916,7 +931,7 @@
         h[0].close()
         h[0] = HashLog(fname, "Xyzzy")
         seen("ddddd"*4)
-	
+
 	# Make sure that hashlog still works when we sync.
 	log("Abcd"*5)
 	log("Defg"*5)
@@ -934,9 +949,6 @@
         h[0].close()
 
 #----------------------------------------------------------------------
-import mixminion.BuildMessage as BuildMessage
-import mixminion.server.Modules
-from mixminion.server.Modules import *
 
 # Dummy PRNG class that just returns 0-valued bytes.  We use this to make
 # generated padding predictable in our BuildMessage tests below.
@@ -976,7 +988,7 @@
 
     def test_compression(self):
 	# Make sure that our compression helper functions work properly.
-	
+
 	p = AESCounterPRNG()
 	longMsg = p.getBytes(100)*2 + str(dir(Crypto))
 
@@ -1515,15 +1527,15 @@
 	self.assertEquals(payload,
 	     BuildMessage._decodeEncryptedForwardPayload(efwd_p,efwd_t,rsa1))
 
-	# Stateful reply
-	secrets = [ "Is that you, Des","troyer?Rinehart?" ]
-	sdict = { 'tag1'*5 : secrets }
-	ks = Keyset(secrets[1])
-	m = lioness_decrypt(encoded1, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
-	ks = Keyset(secrets[0])
-	m = lioness_decrypt(m, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
-	self.assertEquals(payload, BuildMessage._decodeReplyPayload(m,secrets))
-	repl1 = m
+## 	# Stateful reply
+## 	secrets = [ "Is that you, Des","troyer?Rinehart?" ]
+## 	sdict = { 'tag1'*5 : secrets }
+## 	ks = Keyset(secrets[1])
+## 	m = lioness_decrypt(encoded1, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+## 	ks = Keyset(secrets[0])
+## 	m = lioness_decrypt(m, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+## 	self.assertEquals(payload, BuildMessage._decodeReplyPayload(m,secrets))
+## 	repl1 = m
 
 	# Stateless reply
 	tag = "To light my way out\xBE"
@@ -1634,7 +1646,6 @@
 
 class PacketHandlerTests(unittest.TestCase):
     def setUp(self):
-        from mixminion.server.PacketHandler import PacketHandler
         self.pk1 = getRSAKey(0,1024)
         self.pk2 = getRSAKey(1,1024)
         self.pk3 = getRSAKey(2,1024)
@@ -1738,7 +1749,6 @@
         bfm = BuildMessage.buildForwardMessage
         brm = BuildMessage.buildReplyMessage
         brbi = BuildMessage._buildReplyBlockImpl
-        from mixminion.server.PacketHandler import ContentError
 
         # A long intermediate header needs to fail.
         server1X = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
@@ -1858,8 +1868,6 @@
 #----------------------------------------------------------------------
 # QUEUE
 
-from mixminion.Common import waitForChildren
-from mixminion.server.Queue import *
 
 class TestDeliveryQueue(DeliveryQueue):
     def __init__(self,d):
@@ -1938,7 +1946,6 @@
             q2h.append(nh)
 
         # Look at the messages in queue2, 15 then 30 at a time.
-        from string import atoi
         for group in queue2.pickRandom(15), queue2.pickRandom(30):
             seen = {}
             for h in group:
@@ -2005,7 +2012,7 @@
 	d_d = mix_mktemp("qd")
 
 	queue = TestDeliveryQueue(d_d)
-	
+
 	# First, make sure the queue stores messages correctly.
 	h1 = queue.queueDeliveryMessage("Address 1", "Message 1")
 	h2 = queue.queueDeliveryMessage("Address 2", "Message 2")
@@ -2114,7 +2121,6 @@
 # LOGGING
 class LogTests(unittest.TestCase):
     def testLogging(self):
-        from mixminion.Common import Log, _FileLogHandler, _ConsoleLogHandler
 
 	# Create a new loghandler, and try sending a few messages to it.
         log = Log("INFO")
@@ -2165,7 +2171,7 @@
 
 #----------------------------------------------------------------------
 # File paranoia
-from mixminion.Common import createPrivateDir, checkPrivateDir
+
 
 class FileParanoiaTests(unittest.TestCase):
     def testPrivateDirs(self):
@@ -2275,9 +2281,6 @@
 # MMTP
 # FFFF Write more tests
 
-import mixminion.server.MMTPServer
-import mixminion.MMTPClient
-
 # Run on a different port so we don't conflict with any actual servers
 # running on this machine.
 TEST_PORT = 40199
@@ -2452,7 +2455,6 @@
 #----------------------------------------------------------------------
 # Config files
 
-from mixminion.Config import _ConfigFile, ConfigError, _parseInt
 
 class TestConfigFile(_ConfigFile):
     _syntax = { 'Sec1' : {'__SECTION__': ('REQUIRE', None, None),
@@ -2660,7 +2662,7 @@
 	tm = C._parseTime("2001/12/25 06:15:10")
 	self.assertEquals(time.gmtime(tm)[:6], (2001,12,25,6,15,10))
 
-	## 
+	##
 	# Now, try the failing cases.
         def fails(fn, val, self=self):
             self.failUnlessRaises(ConfigError, fn, val)
@@ -2750,8 +2752,7 @@
 Nickname: fred-the-bunny
 """
 
-import mixminion.Config
-import mixminion.ServerInfo
+
 class ServerInfoTests(unittest.TestCase):
     def testServerInfoGen(self):
 	# Try generating a serverinfo and see if its values are as expected.
@@ -2759,17 +2760,17 @@
         d = mix_mktemp()
 	try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG)
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=SERVER_CONFIG)
 	finally:
 	    resumeLog()
         if not os.path.exists(d):
             os.mkdir(d, 0700)
 
-        inf = mixminion.ServerInfo.generateServerDescriptorAndKeys(conf,
-								   identity,
-                                                                   d,
-                                                                   "key1",
-                                                                   d)
+        inf = generateServerDescriptorAndKeys(conf,
+					      identity,
+					      d,
+					      "key1",
+					      d)
         info = mixminion.ServerInfo.ServerInfo(string=inf)
         eq = self.assertEquals
         eq(info['Server']['Descriptor-Version'], "0.1")
@@ -2801,7 +2802,7 @@
         # Now make sure everything was saved properly
         keydir = os.path.join(d, "key_key1")
         eq(inf, readFile(os.path.join(keydir, "ServerDesc")))
-	mixminion.ServerInfo.ServerKeyset(d, "key1", d) # Can we load?
+	mixminion.server.ServerKeys.ServerKeyset(d, "key1", d) # Can we load?
         packetKey = Crypto.pk_PEM_load(
             os.path.join(keydir, "mix.key"))
         eq(packetKey.get_public_key(),
@@ -2823,18 +2824,18 @@
         # Now with a shorter configuration
 	try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG_SHORT+
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=SERVER_CONFIG_SHORT+
 					   """[Incoming/MMTP]
 Enabled: yes
 IP: 192.168.0.99
 """)
 	finally:
 	    resumeLog()
-	mixminion.ServerInfo.generateServerDescriptorAndKeys(conf,
-							     identity,
-							     d,
-							     "key2",
-							     d)
+	generateServerDescriptorAndKeys(conf,
+	                                identity,
+					d,
+					"key2",
+					d)
         # Now with a bad signature
         sig2 = Crypto.pk_sign(sha1("Hello"), identity)
         sig2 = base64.encodestring(sig2).replace("\n", "")
@@ -2926,7 +2927,7 @@
 
         try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=cfg_test)
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=cfg_test)
 	finally:
 	    resumeLog()
 	manager = conf.getModuleManager()
@@ -3007,7 +3008,7 @@
 	# Check serverinfo generation.
 	try:
 	    suspendLog()
-	    info = mixminion.ServerInfo.generateServerDescriptorAndKeys(
+	    info = generateServerDescriptorAndKeys(
 		conf, getRSAKey(0,2048), home_dir, "key11", home_dir)
 	    self.failUnless(stringContains(info,"\n[Example]\nFoo: 99\n"))
 	finally:
@@ -3023,7 +3024,7 @@
 
         try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=cfg_test)
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=cfg_test)
 	finally:
 	    resumeLog()
 	manager = conf.getModuleManager()
@@ -3058,7 +3059,7 @@
 
 	#####
 	# Test escapeMessage
-	
+
 	# plaintext text message, text mode.
 	self.assertEquals(em(message, None, 1), ("TXT", message, None))
 	# plaintext text message, bin mode.
@@ -3090,7 +3091,7 @@
 # Sample address file for testing MBOX
 MBOX_ADDRESS_SAMPLE = """\
 # This is a sample address file
-mix-minion: mixminion@thishost 
+mix-minion: mixminion@thishost
 
 mixdaddy: mixminion@thathost
 mixdiddy=mixminion@theotherhost
@@ -3103,8 +3104,8 @@
 Subject: Anonymous Mixminion message
 
 THIS IS AN ANONYMOUS MESSAGE.  The mixminion server 'nickname' at
-<Unknown IP> has been configured to deliver messages to your address.  
-If you do not want to receive messages in the future, contact removeaddress@x 
+<Unknown IP> has been configured to deliver messages to your address.
+If you do not want to receive messages in the future, contact removeaddress@x
 and you will be removed.
 
 This message is not in plaintext.  It's either 1) a reply; 2) a forward
@@ -3129,14 +3130,14 @@
 	module = mixminion.server.Modules.MixmasterSMTPModule()
 	module.configure({"Delivery/SMTP-Via-Mixmaster" :
 			  {"Enabled":1, "Server": "nonesuch",
-			   "SubjectLine":'foobar', 
+			   "SubjectLine":'foobar',
 			   'MixCommand' : ('ls', ['-z'])}},
 			 manager)
 	queue = manager.queues['SMTP_MIX2']
 	replaceFunction(os, "spawnl")
 	try:
 	    # Send a message...
-	    queue.queueDeliveryMessage((SMTP_TYPE, "foo@bar", None), 
+	    queue.queueDeliveryMessage((SMTP_TYPE, "foo@bar", None),
 				       "This is the message")
 	    queue.sendReadyMessages()
 	    # And make sure that Mixmaster was invoked correctly.
@@ -3144,9 +3145,9 @@
 	    self.assertEquals('spawnl', calls[0][0])
 	    mixfn, mixargs = calls[0][1][2], calls[0][1][3:]
 	    self.assertEquals("ls", mixfn)
-	    self.assertEquals(mixargs[:-1], 
+	    self.assertEquals(mixargs[:-1],
 			      ('-z', '-l', 'nonesuch', '-s', 'foobar',
-			       '-t', 'foo@bar')) 
+			       '-t', 'foo@bar'))
 	    # ...and, if the temporary file it used hasn't been removed yet,
 	    # that it contains the correct data.
 	    fn = mixargs[-1]
@@ -3170,14 +3171,14 @@
 	   with a stub function so that we don't actually send anything."""
         # Configure the module
 	manager = self.getManager()
-	module = mixminion.server.Modules.MBoxModule()	
+	module = mixminion.server.Modules.MBoxModule()
 	addrfile = mix_mktemp()
 	writeFile(addrfile, MBOX_ADDRESS_SAMPLE)
 	module.configure({'Server':{'Nickname': "nickname"},
                           'Incoming/MMTP':{},
 			  "Delivery/MBOX" :
 			  {"Enabled": 1,
-			   "AddressFile": addrfile, 
+			   "AddressFile": addrfile,
 			   "ReturnAddress": "returnaddress@x",
 			   "RemoveContact": "removeaddress@x",
 			   "SMTPServer" : "foo.bar.baz"}}, manager)
@@ -3212,9 +3213,9 @@
 	    self.assertEquals(1, len(getReplacedFunctionCallLog()))
 	    fn, args, _ = getReplacedFunctionCallLog()[0]
 	    self.assertEquals('sendSMTPMessage', fn)
-	    self.assertEquals(('foo.bar.baz', 
-			       ['mixminion@theotherhost'], 
-			       'returnaddress@x'), 
+	    self.assertEquals(('foo.bar.baz',
+			       ['mixminion@theotherhost'],
+			       'returnaddress@x'),
 			      args[:3])
 	    d = findFirstDiff(MBOX_EXPECTED_MESSAGE, args[3])
 	    if d != -1:
@@ -3223,7 +3224,7 @@
 	finally:
 	    undoReplacedAttributes()
 	    clearReplacedFunctionCallLog()
-	    	
+
     def testDirectoryDump(self):
 	"""Check out the DirectoryStoreModule that we use for testing on
 	   machines with unreliable/nonexistant SMTP."""
@@ -3238,18 +3239,18 @@
 			  {'Location': dir, 'UseQueue' : 0}}, manager)
 	# Try sending a couple of messages.
 	queue = manager.queues['Testing_DirectoryDump']
-	queue.queueDeliveryMessage((0xFFFE, "addr1", "t"*20), 
+	queue.queueDeliveryMessage((0xFFFE, "addr1", "t"*20),
 				   "This is the message")
 	self.assert_(os.path.exists(os.path.join(dir, "0")))
-	queue.queueDeliveryMessage((0xFFFE, "addr2", "x"*20), 
+	queue.queueDeliveryMessage((0xFFFE, "addr2", "x"*20),
 				   "This is message 2")
 	self.assert_(os.path.exists(os.path.join(dir, "1")))
-	self.assertEquals(eme("This is message 2", "x"*20), 
+	self.assertEquals(eme("This is message 2", "x"*20),
 			  readFile(os.path.join(dir, "1")))
 	# test failure.
 	try:
 	    suspendLog()
-	    queue.queueDeliveryMessage((0xFFFE, "FAIL!", "y"*20), 
+	    queue.queueDeliveryMessage((0xFFFE, "FAIL!", "y"*20),
 			       "This is message X which won't be delivered")
 	    self.assert_(not os.path.exists(os.path.join(dir, "2")))
 	finally:
@@ -3258,7 +3259,7 @@
 
 	try:
 	    suspendLog()
-	    queue.queueDeliveryMessage((0xFFFE, "fail", "z"*20), 
+	    queue.queueDeliveryMessage((0xFFFE, "fail", "z"*20),
 			       "This is message X which won't be delivered")
 	    self.assert_(not os.path.exists(os.path.join(dir, "2")))
 	finally:
@@ -3267,7 +3268,7 @@
 
 	queue.sendReadyMessages()
 
-	# Check sane behavior on missing files, and on restart.  
+	# Check sane behavior on missing files, and on restart.
 	writeFile(os.path.join(dir, "90"), 'zed')
 	module = mixminion.testSupport.DirectoryStoreModule()
 	# This time, use a queue.
@@ -3277,13 +3278,13 @@
 	self.assertEquals(module.next, 91)
 	self.assertEquals(len(os.listdir(dir)), 3)
 	queue = manager.queues['Testing_DirectoryDump']
-	queue.queueDeliveryMessage((0xFFFE, "addr91", None), 
+	queue.queueDeliveryMessage((0xFFFE, "addr91", None),
 				   "This is message 91")
-	queue.queueDeliveryMessage((0xFFFE, "addr92", None), 
+	queue.queueDeliveryMessage((0xFFFE, "addr92", None),
 				   "This is message 92")
-	queue.queueDeliveryMessage((0xFFFE, "fail", None), 
+	queue.queueDeliveryMessage((0xFFFE, "fail", None),
 				   "This is message 93")
-	queue.queueDeliveryMessage((0xFFFE, "FAIL!", None), 
+	queue.queueDeliveryMessage((0xFFFE, "FAIL!", None),
 				   "This is message 94")
 	# All 4 messages go into the queue...
 	self.assertEquals(4, queue.count())
@@ -3303,7 +3304,7 @@
 	c = SERVER_CONFIG_SHORT+"\nHomedir: %s\n"%d
 	try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=c)
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=c)
 	    m = conf.getModuleManager()
 	    m.configure(conf)
 	    return m
@@ -3311,9 +3312,8 @@
 	    resumeLog()
 
 #----------------------------------------------------------------------
-import mixminion.server.ServerMain
 
-# Sample server configuration to test ServerMain.
+# Sample server configuration to test ServerKeys
 SERVERCFG = """
 [Server]
 Homedir: %(home)s
@@ -3335,12 +3335,12 @@
     cfg = SERVERCFG % { 'home' : _FAKE_HOME }
     try:
 	suspendLog()
-	conf = mixminion.Config.ServerConfig(string=cfg)
+	conf = mixminion.server.ServerConfig.ServerConfig(string=cfg)
     finally:
 	resumeLog()
-    return mixminion.server.ServerMain.ServerKeyring(conf)
+    return mixminion.server.ServerKeys.ServerKeyring(conf)
 
-class ServerMainTests(unittest.TestCase):
+class ServerKeysTests(unittest.TestCase):
     def testServerKeyring(self):
 	keyring = _getServerKeyring()
 	home = _FAKE_HOME
@@ -3396,9 +3396,9 @@
         waitForChildren() # make sure keys are really gone before we remove
 
         # In case we started very close to midnight, remove keys as if it
-	# were a little in the future; otherwise, we won't remove the 
+	# were a little in the future; otherwise, we won't remove the
 	# just-expired keys.
-	keyring.removeDeadKeys(now+360) 
+	keyring.removeDeadKeys(now+360)
 	self.assertEquals(3, len(keyring.keyIntervals))
 
 	if USE_SLOW_MODE:
@@ -3444,17 +3444,17 @@
 ]
 
 def getExampleServerDescriptors():
-    """Helper function: generate a list of list of ServerInfo objects based 
+    """Helper function: generate a list of list of ServerInfo objects based
        on the values of _EXAMPLE_DESCRIPTORS_INP"""
     if _EXAMPLE_DESCRIPTORS:
  	return _EXAMPLE_DESCRIPTORS
 
-    gen = mixminion.ServerInfo.generateServerDescriptorAndKeys
+    gen = generateServerDescriptorAndKeys
     tmpkeydir = mix_mktemp()
     now = time.time()
 
     sys.stdout.flush()
-    
+
     # For each server...
     for (nickname, lifetime, ip, starting, types) in _EXAMPLE_DESCRIPTORS_INP:
 	# Generate a config file
@@ -3474,7 +3474,7 @@
 		raise MixFatalError("Unrecognized type: %04x"%t)
 	try:
 	    suspendLog()
-	    conf = mixminion.Config.ServerConfig(string=conf)
+	    conf = mixminion.server.ServerConfig.ServerConfig(string=conf)
 	    conf.getModuleManager().configure(conf)
 	finally:
 	    resumeLog()
@@ -3490,18 +3490,17 @@
 
 	    sd = os.path.join(tmpkeydir,"key_"+k,"ServerDesc")
 	    _EXAMPLE_DESCRIPTORS[nickname].append(readFile(sd))
-	    
+
 	    # (print some dots here; this step can take a while)
 	    sys.stdout.write('.')
 	    sys.stdout.flush()
     sys.stdout.flush()
     return _EXAMPLE_DESCRIPTORS
 
-import mixminion.ClientMain
-
 # variable to hold the latest instance of FakeBCC.
 BCC_INSTANCE = None
 
+
 class ClientMainTests(unittest.TestCase):
     def testTrivialKeystore(self):
 	"""Check out ClientMain's keystore implementation"""
@@ -3601,7 +3600,7 @@
 
 	def parseFails(s, f=self.failUnlessRaises):
 	    f(ParseError, mixminion.ClientMain.parseAddress, s)
-	
+
 	# Check failing cases
 	parseFails("sxtp:foo@bar.com") # unknown module
 	parseFails("mbox") # missing mbox address
@@ -3625,7 +3624,7 @@
 	usercfgstr = "[User]\nUserDir: %s\n[DirectoryServers]\n"%userdir
 	usercfg = mixminion.Config.ClientConfig(string=usercfgstr)
 	client = mixminion.ClientMain.MixminionClient(usercfg)
-	
+
 	# Make sure client sets its directories up correctly.
 	serverdir = os.path.join(userdir, 'servers')
 	self.assert_(os.path.exists(serverdir))
@@ -3642,7 +3641,7 @@
 
 	##  Test generateForwardMessage.
 	# We replace 'buildForwardMessage' to make this easier to test.
-	replaceFunction(mixminion.BuildMessage, "buildForwardMessage", 
+	replaceFunction(mixminion.BuildMessage, "buildForwardMessage",
 			lambda *a:"X")
 	try:
 	    getCalls = getReplacedFunctionCallLog
@@ -3661,13 +3660,13 @@
 
 	    for fn, args, kwargs in getCalls():
 		self.assertEquals(fn, "buildForwardMessage")
-		self.assertEquals(args[0:3], 
+		self.assertEquals(args[0:3],
 				  (payload, SMTP_TYPE, "joe@cledonism.net"))
 		self.assert_(len(args[3]) == len(args[4]) == 2)
 		self.assertEquals(["Lola", "Joe", "Alice", "Joe"],
 		     [x['Server']['Nickname'] for x in args[3]+args[4]])
 	    clearCalls()
-	    
+
 	    # Now try an mbox message, with an explicit last hop.
 	    payload = "Hey, Lo', where you goin' with that pun in your hand?"
 	    client.generateForwardMessage(
@@ -3678,11 +3677,11 @@
 	    client.generateForwardMessage(
 		parseAddress("mbox:granola@Lola"),
 		payload,
-		path1=["Lola", "Joe"], path2=["Alice"])	    
+		path1=["Lola", "Joe"], path2=["Alice"])
 
 	    for fn, args, kwargs in getCalls():
 		self.assertEquals(fn, "buildForwardMessage")
-		self.assertEquals(args[0:3], 
+		self.assertEquals(args[0:3],
 				  (payload, MBOX_TYPE, "granola"))
 		self.assert_(len(args[3]) == len(args[4]) == 2)
 		self.assertEquals(["Lola", "Joe", "Alice", "Lola"],
@@ -3694,27 +3693,27 @@
 
 	### Now try some failing cases for generateForwardMessage:
 	# Empty path...
-	self.assertRaises(MixError, 
-			  client.generateForwardMessage, 
+	self.assertRaises(MixError,
+			  client.generateForwardMessage,
 			  parseAddress("0xFFFF:zed"),
 			  "Z", [], ["Alice"])
 	# Nonexistant servers...
-	self.assertRaises(MixError, 
+	self.assertRaises(MixError,
 			  client.generateForwardMessage,
 			  parseAddress("0xFFFF:zed"),
 			  "Z", ["Marvin"], ["Fred"])
 	# Lola doesn't support SMTP...
-	self.assertRaises(MixError, 
+	self.assertRaises(MixError,
 			  client.generateForwardMessage,
 			  parseAddress("smtp:joe@cledonism.net"),
 			  "Z", ["Joe"], ["Lola"])
 	# Joe doesn't support MBOX...
-	self.assertRaises(MixError, 
+	self.assertRaises(MixError,
 			  client.generateForwardMessage,
 			  parseAddress("mbox:wahoo"),
 			  "Z", ["Lola"], ["Joe"])
-	
-	
+
+
 	# Temporarily replace BlockingClientConnection so we can try the client
 	# without hitting the network.
 	class FakeBCC:
@@ -3787,7 +3786,7 @@
     suite.addTest(tc(ModuleTests))
 
     suite.addTest(tc(ClientMainTests))
-    suite.addTest(tc(ServerMainTests))
+    suite.addTest(tc(ServerKeysTests))
 
     # These tests are slowest, so we do them last.
     suite.addTest(tc(ModuleManagerTests))

Index: testSupport.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/testSupport.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- testSupport.py	11 Dec 2002 06:58:55 -0000	1.10
+++ testSupport.py	12 Dec 2002 19:56:46 -0000	1.11
@@ -6,19 +6,20 @@
    Shared support code for unit tests, benchmark tests, and integration tests.
    """
 
-import os
-import sys
-import stat
 import base64
 import cStringIO
+import os
+import stat
+import sys
 
 import mixminion.Crypto
 import mixminion.Common
 from mixminion.Common import waitForChildren, createPrivateDir, LOG
 from mixminion.Config import _parseBoolean, ConfigError
-from mixminion.server.Modules import DeliveryModule, ImmediateDeliveryQueue, \
-     SimpleModuleDeliveryQueue, DELIVER_OK, DELIVER_FAIL_RETRY, \
-     DELIVER_FAIL_NORETRY, _escapeMessageForEmail
+
+from mixminion.server.Modules import DELIVER_FAIL_NORETRY, DELIVER_FAIL_RETRY,\
+     DELIVER_OK, DeliveryModule, ImmediateDeliveryQueue, \
+     SimpleModuleDeliveryQueue, _escapeMessageForEmail
 
 #----------------------------------------------------------------------
 # DirectoryStoreModule
@@ -43,24 +44,24 @@
 	return { 'Testing/DirectoryDump':
 		 { 'Location' : ('REQUIRE', None, None),
 		   'UseQueue': ('REQUIRE', _parseBoolean, None) } }
-    
+
     def validateConfig(self, sections, entries, lines, contents):
 	# loc = sections['Testing/DirectoryDump'].get('Location')
-	pass 
-    
+	pass
+
     def configure(self, config, manager):
 	self.loc = config['Testing/DirectoryDump'].get('Location')
 	if not self.loc:
 	    return
 	self.useQueue = config['Testing/DirectoryDump']['UseQueue']
 	manager.enableModule(self)
-	
+
 	if not os.path.exists(self.loc):
 	    createPrivateDir(self.loc)
 
 	max = -1
 	for f in os.listdir(self.loc):
-	    if int(f) > max: 
+	    if int(f) > max:
 		max = int(f)
 	self.next = max+1
 
@@ -69,16 +70,16 @@
 
     def getName(self):
 	return "Testing_DirectoryDump"
-    
+
     def getExitTypes(self):
 	return [ 0xFFFE ]
-    
+
     def createDeliveryQueue(self, queueDir):
 	if self.useQueue:
 	    return SimpleModuleDeliveryQueue(self, queueDir)
 	else:
 	    return ImmediateDeliveryQueue(self)
-	
+
     def processMessage(self, message, tag, exitType, exitInfo):
 	assert exitType == 0xFFFE
 
@@ -111,7 +112,7 @@
 
 # Constant flag: are we paranoid about permissions and uid on our tmpdir?
 _MM_TESTING_TEMPDIR_PARANOIA = 1
-# Name of our temporary directory: all temporary files go under this 
+# Name of our temporary directory: all temporary files go under this
 # directory.  If None, it hasn't been created yet.  If it exists,
 # it must be owned by us, mode 700, and have no parents that an adversary
 # (other than root) could write to.
@@ -180,10 +181,10 @@
 		    print "Directory %s has fishy permissions %o" %(parent,m)
 		    sys.exit(1)
 		if st[stat.ST_UID] not in (0, os.getuid()):
-		    print "Directory %s has bad owner %s" % (parent, 
+		    print "Directory %s has bad owner %s" % (parent,
 							     st[stat.ST_UID])
 		    sys.exit(1)
-		    
+
 	_MM_TESTING_TEMPDIR = temp
 	if _MM_TESTING_TEMPDIR_REMOVE_ON_EXIT:
 	    import atexit
@@ -386,6 +387,6 @@
 ]
 
 TEST_KEYS_2048 = [
-    mixminion.Crypto.pk_decode_private_key(base64.decodestring(s)) 
+    mixminion.Crypto.pk_decode_private_key(base64.decodestring(s))
     for s in TEST_KEYS_2048 ]
 del s