[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[minion-cvs] Lots of documentation work and code cleanup.



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

Modified Files:
	HashLog.py MMTPServer.py PacketHandler.py ServerKeys.py 
	ServerMain.py ServerQueue.py 
Log Message:
Lots of documentation work and code cleanup.

ClientMain:
- Refactor path selection so it's easier understand.

Main: 
- Add a new banner.

ServerInbox:
- Actually use ServerQueuedException as documented.

ServerMain:
- Generate keys when keygen is needed, not when key rotation is needed!
- Flush hashlogs on SIGHUP.
- Simplify USAGE code.
- Remove last vestiges of 'stop-server' and 'reload-server' homonyms.

ServerQueue:
- Put off memory-saving work until 0.0.5; it's too invasive for now.

*.py:
- Comment code, resolve XXXXs and DOCDOCs.



Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/HashLog.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- HashLog.py	5 Jun 2003 18:41:40 -0000	1.14
+++ HashLog.py	6 Jun 2003 06:04:58 -0000	1.15
@@ -26,12 +26,17 @@
 # We flush the log every MAX_JOURNAL hashes.
 MAX_JOURNAL = 128
 
+# Lock to pretect _OPEN_HASHLOGS
 _HASHLOG_DICT_LOCK = threading.Lock()
-
-#DOCDOC
+# Map from (filename,keyid) to the open HashLog object with that fname and
+# ID.  Needed to implement getHashLog.
 _OPEN_HASHLOGS = {}
 
 def getHashLog(filename, keyid):
+    """Given a filename and keyid, return a HashLog object with that fname
+       and ID, opening a new one if necessary.  This function is needed to
+       implement key rotation: we want to assemble a list of current
+       hashlogs, but we can't open the same HashLog database twice at once."""
     try:
         _HASHLOG_DICT_LOCK.acquire()
         try:

Index: MMTPServer.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/MMTPServer.py,v
retrieving revision 1.36
retrieving revision 1.37
diff -u -d -r1.36 -r1.37
--- MMTPServer.py	5 Jun 2003 18:41:40 -0000	1.36
+++ MMTPServer.py	6 Jun 2003 06:04:58 -0000	1.37
@@ -235,8 +235,9 @@
     #    __con: an underlying TLS object
     #    __state: a callback to use whenever we get a read or a write. May
     #           throw _ml.TLSWantRead or _ml.TLSWantWrite.  See __acceptFn,
-    #           __connectFn, __shutdownFn, __readFn, __writeFn.
-    #    __server: an AsyncServer.
+    #           __connectFn, __shutdownFn, __readFn, __writeFn.  If __state
+    #           is None, the connection should close immediately.
+    #    __server: an AsyncServer.  If None, the connection is closed.
     #    __inbuf: A list of strings that we've read since the last expectRead.
     #    __inbuflen: The total length of all the strings in __inbuf
     #    __expectReadLen: None, or the number of bytes to read before
@@ -246,9 +247,9 @@
     #           writing.
     #    __servermode: If true, we're the server side of the connection.
     #           Else, we're the client side.
-    # DOCDOC __connecting
-    # DOCDOC __failed
-    # DOCDOC possibile None on state, server.
+    #    __connection: Are we currently trying to start a connection? (boolean)
+    #    __failed: Have we given up on this connection?
+
     def __init__(self, sock, tls, serverMode, address=None):
         """Create a new SimpleTLSConnection.
 
@@ -510,7 +511,8 @@
         return "".join(self.__inbuf)
 
     def pullInput(self):
-        """DOCDOC"""
+        """Returns the current contents of the input buffer, and clears the
+           input buffer."""
         inp = "".join(self.__inbuf)
         del self.__inbuf[:]
         self.__inbuflen = 0
@@ -524,9 +526,15 @@
         pass
 
     def remove(self):
-        """DOCDOC"""
+        """Called when this connection is shut down successfully or closed
+           with an error.  Removes all state associated with this connection.
+           """
         self.__server.unregister(self)
         self.__server = None
+
+        # Under heavy loads, having circular references through __state and
+        # __finished can keep the connection object alive for many garbage
+        # collections.  Let's nuke those so it is deleted right away.
         self.__state = None
         self.finished = None
 
@@ -695,6 +703,8 @@
     # finishedCallback, certCache:
     #   As described in the docstring for __init__ below.  We remove entries
     #   from the front of messageList/handleList as we begin sending them.
+    # _curMessage, _curHandle: Correspond to the message and handle
+    #     that we are currently trying to deliver.    
     # junk: A list of 32KB padding chunks that we're going to send.  We
     #   pregenerate these to avoid timing attacks.  They correspond to
     #   the 'JUNK' entries in messageList.
@@ -705,10 +715,7 @@
     #     if negotiation hasn't completed.
     # PROTOCOL_VERSIONS: (static) a list of protocol versions we allow,
     #     in the order we offer them.
-    # _curMessage, _curHandle: Correspond to the message and handle
-    #     that we are currently trying to deliver.
-    # DOCDOC other callbacks
-    # DOCDOC active
+    # active: Are we currently able to send messages to a server?  Boolean.
 
     PROTOCOL_VERSIONS = [ '0.3' ]
     def __init__(self, context, ip, port, keyID, messageList, handleList,
@@ -772,7 +779,9 @@
         debug("Opening client connection to %s", self.address)
 
     def isActive(self):
-        """DOCDOC"""
+        """Return true iff messages added to this connection via addMessages
+           will be delivered.  isActive() will return false if, for example,
+           the connection is currently shutting down."""
         return self.active
 
     def addMessages(self, messages, handles):
@@ -926,13 +935,10 @@
         self._curMessage = self._curHandle = None
 
     def shutdown(self, err=0, retriable=0):
-        #DOCDOC
-
         self.active = 0
         SimpleTLSConnection.shutdown(self, err=err, retriable=retriable)
 
     def remove(self):
-        """DOCDOC"""
         self.active = 0
         if self.finishedCallback is not None:
             self.finishedCallback()
@@ -941,8 +947,6 @@
         self.sentCallback = None
         
         SimpleTLSConnection.remove(self)
-            
-
 
 
 LISTEN_BACKLOG = 128

Index: PacketHandler.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/PacketHandler.py,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- PacketHandler.py	17 May 2003 00:08:45 -0000	1.16
+++ PacketHandler.py	6 Jun 2003 06:04:58 -0000	1.17
@@ -32,7 +32,7 @@
     # privatekeys: a list of 2-tuples of
     #      (1) a RSA private key that we accept
     #      (2) a HashLog objects corresponding to the given key
-    def __init__(self, privatekey=(), hashlog=()):
+    def __init__(self, privatekeys=(), hashlogs=()):
         """Constructs a new packet handler, given a sequence of
            private key object for header encryption, and a sequence of
            corresponding hashlog object to prevent replays.
@@ -45,9 +45,10 @@
         self.privatekeys = []
         self.lock = threading.Lock()
 
-        assert type(privatekey) in (types.ListType, types.TupleType)
+        assert type(privatekeys) in (types.ListType, types.TupleType)
+        assert type(hashlogs) in (types.ListType, types.TupleType)
 
-        self.setKeys(privatekey, hashlog)
+        self.setKeys(privatekeys, hashlogs)
 
     def setKeys(self, keys, hashlogs):
         """Change the keys and hashlogs used by this PacketHandler.

Index: ServerKeys.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerKeys.py,v
retrieving revision 1.39
retrieving revision 1.40
diff -u -d -r1.39 -r1.40
--- ServerKeys.py	5 Jun 2003 18:41:40 -0000	1.39
+++ ServerKeys.py	6 Jun 2003 06:04:58 -0000	1.40
@@ -47,7 +47,7 @@
 #FFFF Make this configurable?  (Set to 2 weeks).
 PREPUBLICATION_INTERVAL = 14*24*60*60
 
-# DOCDOC
+# URL to which we should post published servers.
 #
 #FFFF Make this configurable
 DIRECTORY_UPLOAD_URL = "http://mixminion.net/minion-cgi/publish";
@@ -55,20 +55,21 @@
 #----------------------------------------------------------------------
 class ServerKeyring:
     """A ServerKeyring remembers current and future keys, descriptors, and
-       hash logs for a mixminion server.
-
-       DOCDOC
+       hash logs for a mixminion server.  It keeps track of key rotation
+       schedules, and generates new keys as needed.
        """
     ## Fields:
     # homeDir: server home directory
     # keyDir: server key directory
     # keyOverlap: How long after a new key begins do we accept the old one?
     # keySets: sorted list of (start, end, keyset)
-    # nextRotation: time_t when this key expires, DOCDOCDOC not so.
+    # nextUpdate: time_t when a new key should be added, or a current key
+    #      should be removed, or "None" for uncalculated.
     # keyRange: tuple of (firstKey, lastKey) to represent which key names
     #      have keys on disk.
-    #
-    #DOCDOC currentKeys
+    # currentKeys: None, if we haven't checked for currently live keys, or
+    #      a list of currently live ServerKeyset objects.
+    # _lock: A lock to prevent concurrent key generation or rotation.
 
     def __init__(self, config):
         "Create a ServerKeyring from a config object"
@@ -90,7 +91,8 @@
         """Internal method: read information about all this server's
            currently-prepared keys from disk.
 
-           DOCDOC raises configerror...
+           May raise ConfigError if any of the server descriptors on disk
+           are invalid.
            """
         self.keySets = []
         firstKey = sys.maxint
@@ -149,7 +151,9 @@
                               formatDate(end), formatDate(start))
 
     def checkDescriptorConsistency(self, regen=1):
-        """DOCDOC"""
+        """Check whether the server descriptors in this keyring are
+           consistent with the server's configuration.  If 'regen' are true,
+           inconsistent descriptors are regenerated."""
         identity = None
         state = []
         for _,_,ks in self.keySets:
@@ -196,7 +200,9 @@
         return key
 
     def publishKeys(self, allKeys=0):
-        """DOCDOC"""
+        """Publish server descriptors to the directory server.  Ordinarily,
+           only unpublished descriptors are sent.  If allKeys is true,
+           all descriptors are sent."""
         keySets = [ ks for _, _, ks in self.keySets ]
         if allKeys:
             LOG.info("Republishing all known keys to directory server")
@@ -241,7 +247,8 @@
             secureDelete([dhfile], blocking=1)
 
     def createKeysAsNeeded(self,now=None):
-        """DOCDOC"""
+        """Generate new keys and descriptors as needed, so that the next
+           PUBLICATION_LATENCY+PREPUBLICATION_INTERVAL seconds are covered."""
         if now is None:
             now = time.time()
 
@@ -292,7 +299,8 @@
 
             keyname = "%04d" % keynum
 
-            nextStart = startAt + self.config['Server']['PublicKeyLifetime'].getSeconds()
+            lifetime = self.config['Server']['PublicKeyLifetime'].getSeconds()
+            nextStart = startAt + lifetime
 
             LOG.info("Generating key %s to run from %s through %s (GMT)",
                      keyname, formatDate(startAt),
@@ -308,16 +316,16 @@
         self.checkKeys()
 
     def regenerateDescriptors(self):
-        """DOCDOC"""
+        """Regenerate all server descriptors for all keysets in this
+           keyring, but keep all old keys intact."""
         LOG.info("Regenerating server descriptors; keeping old keys.")
         identityKey = self.getIdentityKey()
         for _,_,ks in self.keySets:
             ks.regenerateServerDescriptor(self.config, identityKey)
 
     def getNextKeygen(self):
-        """DOCDOC
-
-           -1 => Right now!
+        """Return the time (in seconds) when we should next generate keys.
+           If -1 is returned, keygen should occur immediately.
         """
         if not self.keySets:
             return -1
@@ -373,9 +381,8 @@
                  if va < now and vu > cutoff ]
 
     def getServerKeysets(self, now=None):
-        """Return a ServerKeyset object for the currently live key.
-
-           DOCDOC"""
+        """Return list of ServerKeyset objects for the currently live keys.
+        """
         # FFFF Support passwords on keys
         keysets = [ ]
         for va, vu, ks in self._getLiveKeys(now):
@@ -414,7 +421,12 @@
                                                    self._getDHFile())
 
     def updateKeys(self, packetHandler, mmtpServer, statusFile=None,when=None):
-        """DOCDOC: Return next rotation."""
+        """Update the keys and certificates stored in a PacketHandler and an
+           MMTPServer object, so that they contain the currently correct
+           keys.  Also removes any dead keys.
+
+           This function is idempotent.
+        """
         self.removeDeadKeys()
         self.currentKeys = keys = self.getServerKeysets(when)
         LOG.info("Updating keys: %s currently valid", len(keys))
@@ -440,7 +452,8 @@
         self.getNextKeyRotation(keys)
 
     def getNextKeyRotation(self, curKeys=None):
-        """DOCDOC"""
+        """Calculate the next time at which we should change the set of live
+           keys."""
         if self.nextUpdate is None:
             if curKeys is None:
                 if self.currentKeys is None:
@@ -449,15 +462,18 @@
                     curKeys = self.currentKeys
             events = []
             curNames = {}
-            #DOCDOC
+            # For every current keyset, we'll remove it at keyOverlap
+            # seconds after its stated expiry time.
             for k in curKeys:
                 va, vu = k.getLiveness()
                 events.append((vu+self.keyOverlap, "RM"))
                 curNames[k.keyname] = 1
+            # For every other keyset, we'll add it when it becomes valid.
             for va, vu, k in self.keySets:
                 if curNames.has_key(k.keyname): continue
                 events.append((va, "ADD"))
 
+            # Which even happens first?
             events.sort()
             if not events:
                 LOG.info("No future key rotation events.")
@@ -504,13 +520,18 @@
        When we create a new ServerKeyset object, the associated keys are not
        read from disk unil the object's load method is called."""
     ## Fields:
+    # keydir: Directory to store this keyset's data.
     # 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.
+    # publishedFile: filename to store this server's publication time.
     #
     # packetKey, mmtpKey: This server's actual short-term keys.
-    # DOCDOC serverinfo, validAfter, validUntil,published(File)?, keydir
+    #
+    # serverinfo: None, or a parsed server descriptor.
+    # validAfter, validUntil: This keyset's published lifespan, or None.
+    # published: has this boolean: has this server been published?
     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
@@ -534,7 +555,7 @@
             createPrivateDir(keydir)
 
     def delete(self):
-        """DOCDOC"""
+        """Remove this keyset from disk."""
         files = [self.packetKeyFile,
                  self.mmtpKeyFile,
                  self.certFile,
@@ -577,31 +598,33 @@
         "Return the sha1 hash of the asn1 encoding of the packet public key"
         return mixminion.Crypto.sha1(self.packetKey.encode_key(1))
     def getServerDescriptor(self):
-        """DOCDOC"""
+        """Return a ServerInfo for this keyset, reading it from disk if
+           needed."""
         if self.serverinfo is None:
             self.serverinfo = ServerInfo(fname=self.descFile)
         return self.serverinfo
     def getLiveness(self):
-        """DOCDOC"""
+        """Return a 2-tuple of validAfter/validUntil for this server."""
         if self.validAfter is None or self.validUntil is None:
             info = self.getServerDescriptor()
             self.validAfter = info['Server']['Valid-After']
             self.validUntil = info['Server']['Valid-Until']
         return self.validAfter, self.validUntil
     def isPublished(self):
-        """DOCDOC"""
+        """Return true iff we have published this keyset."""
         return self.published
     def markAsPublished(self):
-        """DOCDOC"""
+        """Mark this keyset as published."""
         contents = "%s\n"%formatTime(time.time(),1)
         writeFile(self.publishedFile, contents, mode=0600)
         self.published = 1
     def markAsUnpublished(self):
-        """DOCDOC"""
+        """Mark this keyset as unpublished."""
         tryUnlink(self.publishedFile)
         self.published = 0
     def regenerateServerDescriptor(self, config, identityKey):
-        """DOCDOC"""
+        """Regenerate the server descriptor for this keyset, keeping the
+           original keys."""
         self.load()
         self.markAsUnpublished()
         validAt,validUntil = self.getLiveness()
@@ -615,14 +638,20 @@
         self.serverinfo = self.validAfter = self.validUntil = None
 
     def checkConsistency(self, config, log=1):
-        """DOCDOC"""
+        """Check whether this server descriptor is consistent with a
+           given configuration file.  Returns are as for
+           'checkDescriptorConsistency'.
+        """
         return checkDescriptorConsistency(self.getServerDescriptor(),
                                           config,
                                           log=log,
                                           isPublished=self.published)
 
     def publish(self, url):
-        """DOCDOC Returns 'accept', 'reject', 'error'. """
+        """Try to publish this descriptor to a given directory URL.  Returns
+           'accept' if the publication was successful, 'reject' if the
+           server refused to accept the descriptor, and 'error' if
+           publication failed for some other reason."""
         fname = self.getDescriptorFileName()
         descriptor = readFile(fname)
         fields = urllib.urlencode({"desc" : descriptor})
@@ -656,7 +685,8 @@
         LOG.info("Directory accepted descriptor: %r", msg)
         self.markAsPublished()
         return 'accept'
-            
+
+# Matches the reply a directory server gives.
 DIRECTORY_RESPONSE_RE = re.compile(r'^Status: (0|1)[ \t]*\nMessage: (.*)$',
                                    re.M)
 
@@ -681,10 +711,12 @@
 def checkDescriptorConsistency(info, config, log=1, isPublished=1):
     """Given a ServerInfo and a ServerConfig, compare them for consistency.
 
-       Return true iff info may have come from 'config'.  If 'log' is
-       true, warn as well.  Does not check keys.
+       Returns 'good' iff info may have come from 'config'.
 
-       DOCDOC returns 'good', 'so-so', 'bad'
+       If the server is inconsistent with the configuration file and should
+       be regenerated, returns 'bad'.  Otherwise, returns 'so-so'.
+
+       If 'log' is true, warn as well.  Does not check keys.
     """
     warn = _WarnWrapper(silence = not log, isPublished=isPublished)
 
@@ -773,7 +805,6 @@
 def generateServerDescriptorAndKeys(config, identityKey, keydir, keyname,
                                     hashdir, validAt=None, now=None,
                                     useServerKeys=0, validUntil=None):
-    #XXXX reorder args
     """Generate and sign a new server descriptor, and generate all the keys to
        go with it.
 
@@ -783,10 +814,11 @@
           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.
-
-          DOCDOC useServerKeys, validUntil
-          """
-
+          useServerKeys -- If true, try to read an existing keyset from
+               (keydir,keyname,hashdir) rather than generating a fresh one.
+          validUntil -- Time at which the generated descriptor should
+               expire.
+    """
     if useServerKeys:
         serverKeys = ServerKeyset(keydir, keyname, hashdir)
         serverKeys.load()
@@ -1036,7 +1068,15 @@
 
 def generateCertChain(filename, mmtpKey, identityKey, nickname,
                       certStarts, certEnds):
-    """Create a two-certificate chain DOCDOC"""
+    """Create a two-certificate chain for use in MMTP.
+
+       filename -- location to store certificate chain.
+       mmtpKey -- a short-term RSA key to use for connection
+           encryption (1024 bits).
+       identityKey -- our long-term signing key (2048-4096 bits).
+       nickname -- nickname to use in our certificates.
+       certStarts, certEnds -- certificate lifetimes.
+    """
     fname = filename+"_tmp"
     mixminion.Crypto.generate_cert(fname,
                                    mmtpKey, identityKey,

Index: ServerMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerMain.py,v
retrieving revision 1.73
retrieving revision 1.74
diff -u -d -r1.73 -r1.74
--- ServerMain.py	5 Jun 2003 18:41:40 -0000	1.73
+++ ServerMain.py	6 Jun 2003 06:04:58 -0000	1.74
@@ -14,14 +14,12 @@
 #    MINION_HOME/work/queues/incoming/ [Queue of received,unprocessed pkts]
 #                            mix/ [Mix pool]
 #                            outgoing/ [Messages for mmtp delivery]
-#                            deliver/mbox/ [DOCDOC]
-#                            deliver/smtp/
-#                            deliver/*/
+#                            deliver/*/ [Messages for delivery via modules]
 #                      tls/dhparam [Diffie-Hellman parameters]
 #                      hashlogs/hash_1*  [HashLogs of packet hashes
 #                               hash_2*    corresponding to key sets]
 #                                ...
-#                      stats.tmp [DOCDOC]
+#                      stats.tmp [Cache of stats from latest period]
 #                log [Messages from the server]
 #                keys/identity.key [Long-lived identity PK]
 #                     key_0001/ServerDesc [Server descriptor]
@@ -31,13 +29,12 @@
 #                              published [present if this desc is published]
 #                     key_0002/...
 #                conf/miniond.conf [configuration file]
-#                current-desc
-#                stats [DOCDOC]
-#                version [DOCDOC]
+#                current-desc [Filename of current server descriptor.]
+#                stats [Log of server statistics]
+#                version [Version of homedir format.]
 
 # FFFF Support to put keys/queues in separate directories.
 
-
 __all__ = [ 'MixminonServer' ]
 
 import errno
@@ -69,11 +66,14 @@
      installSIGCHLDHandler, Lockfile, readFile, secureDelete, tryUnlink, \
      waitForChildren, writeFile
 
-#DOCDOC
+# Version number for server home-directory.
+#
 # For backward-incompatible changes only.
 SERVER_HOMEDIR_VERSION = "1001"
 
 def getHomedirVersion(config):
+    """Return the version of the server's homedir.  If no version is found,
+       the version must be '1000'. """
     homeDir = config['Server']['Homedir']
     versionFile = os.path.join(homeDir, "version")
     if not os.path.exists(homeDir):
@@ -95,6 +95,10 @@
     return dirVersion
 
 def checkHomedirVersion(config):
+    """Check the version of the server's homedir.  If it's too old, tell
+       the user to upgrade and raise UIError.  If it's too new, tell the
+       user we're confused and raise UIError.  Otherwise, return silently.
+    """
     dirVersion = getHomedirVersion(config)
 
     if dirVersion is None:
@@ -530,12 +534,10 @@
 
     def scheduleRecurringComplex(self, first, name, cb):
         """Schedule a callback function 'cb' to be invoked at time 'first,'
-           and thereafter at times returned by 'nextFn'.
-
-           (nextFn is called immediately after the callback is invoked,
-           every time it is invoked, and should return a time at which.)
+           and thereafter at times returned by 'cb'.
 
-           DOCDOC
+           (Every time the callback is invoked, if it returns a non-None value,
+           the event is rescheduled for the time it returns.)
         """
         assert type(name) is StringType
         assert type(first) in (IntType, LongType, FloatType)
@@ -718,10 +720,12 @@
         self.moduleManager.startThreading()
 
     def updateKeys(self, lock=1):
-        """DOCDOC"""
-        # We don't dare to block here -- we could block the main thread for 
-        # as long as it takes to generate several new RSA keys, which would
-        # stomp responsiveness on slow computers.
+        """Change the keys used by the PacketHandler and MMTPServer objects
+           to reflect the currently keys."""
+        # We don't want to block here -- If key generation is in process, we
+        # could block the main thread for as long as it takes to generate
+        # several new RSA keys, which would stomp responsiveness on slow
+        # computers.  Instead, we reschedule for 2 minutes later
         # ???? Could there be a more elegant approach to this?
         if lock and not self.keyring.lock(0):
             LOG.warn("generateKeys in progress:"
@@ -738,8 +742,13 @@
             if lock: self.keyring.unlock()
 
     def generateKeys(self):
-        """DOCDOC"""
-        
+        """Callback used to schedule key-generation"""
+
+        # We generate and publish keys in the processing thread, so we don't
+        # slow down the server.  We also reschedule from the processing thread,
+        # so that we can take the new keyset into account when calculating
+        # when keys are next needed.
+
         def c(self=self):
             try:
                 self.keyring.lock()
@@ -747,7 +756,7 @@
                 self.updateKeys(lock=0)
                 if self.config['DirectoryServers'].get('Publish'):
                     self.keyring.publishKeys()
-                self.scheduleOnce(self.keyring.getNextKeyRotation(),
+                self.scheduleOnce(self.keyring.getNextKeygen(),
                                   "KEY_GEN",
                                   self.generateKeys)
             finally:
@@ -835,20 +844,27 @@
             self.processEvents()
 
     def doReset(self):
+        """Called when server receives SIGHUP.  Flushes logs to disk,
+           regenerates/republishes descriptors as needed.
+        """
         LOG.info("Resetting logs")
         LOG.reset()
         EventStats.log.save()
+        self.packetHandler.syncLogs()
         LOG.info("Checking for key rotation")
         self.keyring.checkKeys()
         self.generateKeys()
 
     def doMix(self):
+        """Called when the server's mix is about to fire.  Picks some
+           messages to send, and sends them to the appropriate queues.
+        """
+        
         now = time.time()
         # Before we mix, we need to log the hashes to avoid replays.
         try:
-            # There's a potential threading problem here... in
-            # between this sync and the 'mix' below, nobody should
-            # insert into the mix pool.
+            # There's a threading issue here... in between this sync and the
+            # 'mix' below, nobody should insert into the mix pool.
             self.mixPool.lock()
             self.packetHandler.syncLogs()
 
@@ -864,6 +880,7 @@
         # Send exit messages
         self.moduleManager.sendReadyMessages()
 
+        #XXXX005 use new schedulerecurringcomplex interface
         # Choose next mix interval
         nextMix = self.mixPool.getNextMixTime(now)
         self.scheduleOnce(nextMix, "MIX", self.doMix)
@@ -940,41 +957,32 @@
     sys.stdout = sys.__stdout__ = LogStream("STDOUT", "WARN")
     sys.stderr = sys.__stderr__ = LogStream("STDERR", "WARN")
 
-_SERVER_USAGE = """\
-Usage: %s [options]
-Options:
-  -h, --help:                Print this usage message and exit.
-  -f <file>, --config=<file> Use a configuration file other than the default.
-""".strip()
-
-def usageAndExit(cmd):
-    print _SERVER_USAGE %cmd
-    sys.exit(0)
-
-def configFromServerArgs(cmd, args, usage=None):
-    """DOCDOC"""
+def configFromServerArgs(cmd, args, usage):
+    """Given cmd and args as passed to one of the entry commands,
+       parses the standard '-h/--help' and '-f/--config' options.
+       If the user wanted a usage message, print the usage message and exit.
+       Otherwise, find and parse the configuration file.
+    """
     options, args = getopt.getopt(args, "hf:", ["help", "config="])
     if args:
-        if usage:
-            print usage
-            sys.exit(0)
-        else:
-            usageAndExit(cmd)
+        print >>sys.stderr, "No arguments expected."
+        print usage
+        sys.exit(0)
     configFile = None
     for o,v in options:
         if o in ('-h', '--help'):
-            if usage:
-                print usage
-                sys.exit(0)
-            else:
-                usageAndExit(cmd)
+            print usage
+            sys.exit(0)
         if o in ('-f', '--config'):
             configFile = v
 
     return readConfigFile(configFile)
 
 def readConfigFile(configFile):
-    """DOCDOC"""
+    """Given a filename from the command line (or None if the user didn't
+       specify a configuration file), find the configuration file, parse it,
+       and validate it.  Return the validated configuration file.
+    """
     if configFile is None:
         configFile = None
         for p in ["~/.mixminiond.conf", "~/etc/mixminiond.conf",
@@ -1000,11 +1008,20 @@
     return None #never reached; here to suppress pychecker warning
 
 #----------------------------------------------------------------------
+_SERVER_START_USAGE = """\
+Usage: mixminion server-start [options]
+Start a Mixminion server.
+Options:
+  -h, --help:                Print this usage message and exit.
+  -f <file>, --config=<file> Use a configuration file other than the default.
+""".strip()
+
 def runServer(cmd, args):
+    """[Entry point]  Start a Mixminion server."""
     if cmd.endswith(" server"):
         print "Obsolete command. Use 'mixminion server-start' instead."
 
-    config = configFromServerArgs(cmd, args)
+    config = configFromServerArgs(cmd, args, _SERVER_START_USAGE)
     try:
         # Configure the log, but delay disabling stderr until the last
         # possible minute; we want to keep echoing to the terminal until
@@ -1085,6 +1102,7 @@
 #----------------------------------------------------------------------
 _UPGRADE_USAGE = """\
 Usage: mixminion server-upgrade [options]
+Upgrade the server's home directory from an earlier version.
 Options:
   -h, --help:                Print this usage message and exit.
   -f <file>, --config=<file> Use a configuration file other than
@@ -1092,8 +1110,9 @@
 """.strip()
 
 def runUpgrade(cmd, args):
-    """Remove all keys server descriptors for old versions of this
-       server.  If any are found, nuke the keysets, """
+    """[Entry point] Check the version on this server's homedir.  If it's
+       old, remove all the old keys and server descriptors, clean out the
+       queues, and mark the directory as up-to-date."""
 
     config = configFromServerArgs(cmd, args, usage=_UPGRADE_USAGE)    
     assert config
@@ -1171,6 +1190,7 @@
 #----------------------------------------------------------------------
 _DELKEYS_USAGE = """\
 Usage: mixminion server-DELKEYS [options]
+Delete all keys for this server (except the identity key).
 Options:
   -h, --help:                Print this usage message and exit.
   -f <file>, --config=<file> Use a configuration file other than
@@ -1178,9 +1198,8 @@
 """.strip()
 
 def runDELKEYS(cmd, args):
-    """Remove all keys server descriptors for old versions of this
-       server.  If any are found, nuke the keysets, """
-
+    """[Entry point.] Remove all keys and server descriptors for this
+       server."""
     config = configFromServerArgs(cmd, args, usage=_DELKEYS_USAGE)
     assert config
 
@@ -1209,13 +1228,15 @@
 #----------------------------------------------------------------------
 _PRINT_STATS_USAGE = """\
 Usage: mixminion server-stats [options]
+Print server statistics for the current statistics interval.
 Options:
   -h, --help:                Print this usage message and exit.
   -f <file>, --config=<file> Use a configuration file other than the default.
 """.strip()
 
 def printServerStats(cmd, args):
-    """DOCDOC"""
+    """[Entry point]  Print server statistics for the current statistics
+       interval."""
     config = configFromServerArgs(cmd, args, _PRINT_STATS_USAGE)
     checkHomedirVersion(config)
     _signalServer(config, 1)
@@ -1224,21 +1245,27 @@
 
 #----------------------------------------------------------------------
 _SIGNAL_SERVER_USAGE = """\
-Usage: %s [options]
+Usage: mixminion %s [options]
+Tell a mixminion server to %s.
 Options:
   -h, --help:                Print this usage message and exit.
   -f <file>, --config=<file> Use a configuration file other than the default.
 """.strip()
 
 def signalServer(cmd, args):
-    config = configFromServerArgs(cmd, args, usage=_SIGNAL_SERVER_USAGE%cmd)
-    LOG.setMinSeverity("ERROR")
-
-    if cmd.endswith("stop-server") or cmd.endswith("server-stop"):
+    """[Entry point] Send a SIGHUP or a SIGTERM to a running mixminion
+       server."""
+    if cmd.endswith("server-stop"):
         reload = 0
+        usage = _SIGNAL_SERVER_USAGE % ("server-stop", "shut down")
     else:
-        assert cmd.endswith("reload-server") or cmd.endswith("server-reload")
+        assert cmd.endswith("server-reload")
         reload = 1
+        usage = _SIGNAL_SERVER_USAGE % ("server-reload",
+                                        "rescan its configuration")
+    
+    config = configFromServerArgs(cmd, args, usage=usage)
+    LOG.setMinSeverity("ERROR")
 
     checkHomedirVersion(config)
 
@@ -1276,6 +1303,7 @@
 #----------------------------------------------------------------------
 _REPUBLISH_USAGE = """\
 Usage: mixminion server-republish [options]
+Force a mixminion server to republish its keys to the directory.
 Options:
   -h, --help:                Print this usage message and exit.
   -f <file>, --config=<file> Use a configuration file other than
@@ -1283,6 +1311,8 @@
 """.strip()
 
 def runRepublish(cmd, args):
+    """[Entry point] Mark all keys as unpublished, and send a SIGHUP to
+       the server."""
     config = configFromServerArgs(cmd, args, usage=_REPUBLISH_USAGE)
 
     checkHomedirVersion(config)    

Index: ServerQueue.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerQueue.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- ServerQueue.py	5 Jun 2003 18:41:41 -0000	1.20
+++ ServerQueue.py	6 Jun 2003 06:04:58 -0000	1.21
@@ -614,10 +614,10 @@
             self._deliverMessages(messages)
         self._repOk()
 
-    # FFFF004 This interface is inefficient in space: we don't need to load 
-    # FFFF004 the messages to tell whether they need to be delivered.  We
-    # FFFF004 should have _deliverMessages() take a list of handles, not of
-    # FFFF004 messages.
+    # FFFF005 This interface is inefficient in space: we don't need to load 
+    # FFFF005 the messages to tell whether they need to be delivered.  We
+    # FFFF005 should have _deliverMessages() take a list of handles, not of
+    # FFFF005 messages.
     def _deliverMessages(self, msgList):
         """Abstract method; Invoked with a list of (handle, message)
            tuples every time we have a batch of messages to send.