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

[or-cvs] r12862: Document the heck out of bridgedb and clean up the code a li (in bridgedb/trunk: . lib/bridgedb)



Author: nickm
Date: 2007-12-18 18:04:49 -0500 (Tue, 18 Dec 2007)
New Revision: 12862

Modified:
   bridgedb/trunk/
   bridgedb/trunk/lib/bridgedb/Bridges.py
   bridgedb/trunk/lib/bridgedb/Dist.py
   bridgedb/trunk/lib/bridgedb/Main.py
   bridgedb/trunk/lib/bridgedb/Server.py
   bridgedb/trunk/lib/bridgedb/Tests.py
   bridgedb/trunk/lib/bridgedb/Time.py
   bridgedb/trunk/setup.py
Log:
 r17241@catbus:  nickm | 2007-12-18 18:04:43 -0500
 Document the heck out of bridgedb and clean up the code a little.



Property changes on: bridgedb/trunk
___________________________________________________________________
 svk:merge ticket from /bridgedb/trunk [r17241] on 8246c3cf-6607-4228-993b-4d95d33730f1

Modified: bridgedb/trunk/lib/bridgedb/Bridges.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Bridges.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Bridges.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,7 +1,12 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
+"""
+This module has low-level functionality for parsing bridges and arranging
+them in rings.
+"""
+
 import binascii
 import bisect
 import hmac
@@ -47,6 +52,9 @@
         return True
 
 def is_valid_fingerprint(fp):
+    """Return true iff fp in the right format to be a hex fingerprint
+       of a Tor server.
+    """
     if len(fp) != HEX_FP_LEN:
         return False
     try:
@@ -60,10 +68,13 @@
 fromHex = binascii.a2b_hex
 
 def get_hmac(k,v):
+    """Return the hmac of v using the key k."""
     h = hmac.new(k, v, digestmod=DIGESTMOD)
     return h.digest()
 
 def get_hmac_fn(k, hex=True):
+    """Return a function that computes the hmac of its input using the key k.
+       If 'hex' is true, the output of the function will be hex-encoded."""
     h = hmac.new(k, digestmod=DIGESTMOD)
     def hmac_fn(v):
         h_tmp = h.copy()
@@ -75,11 +86,23 @@
     return hmac_fn
 
 def chopString(s, size):
+    """Generator. Given a string and a length, divide the string into pieces
+       of no more than that length.
+    """
     for pos in xrange(0, len(s), size):
         yield s[pos:pos+size]
 
 class Bridge:
+    """Holds information for a single bridge"""
+    ## Fields:
+    ##   nickname -- The bridge's nickname.  Not currently used.
+    ##   ip -- The bridge's IP address, as a dotted quad.
+    ##   orport -- The bridge's OR port.
+    ##   fingerprint -- The bridge's identity digest, in lowercase hex, with
+    ##       no spaces.
     def __init__(self, nickname, ip, orport, fingerprint=None, id_digest=None):
+        """Create a new Bridge.  One of fingerprint and id_digest must be
+           set."""
         self.nickname = nickname
         self.ip = ip
         self.orport = orport
@@ -97,13 +120,16 @@
             raise TypeError("Bridge with no ID")
 
     def getID(self):
+        """Return the bridge's identity digest."""
         return fromHex(self.fingerprint)
 
     def __repr__(self):
+        """Return a piece of python that evaluates to this bridge."""
         return "Bridge(%r,%r,%d,%r)"%(
             self.nickname, self.ip, self.orport, self.fingerprint)
 
     def getConfigLine(self):
+        """Return a line describing this bridge for inclusion in a torrc."""
         return "bridge %s:%d %s" % (self.ip, self.orport, self.fingerprint)
 
     def assertOK(self):
@@ -112,6 +138,9 @@
         assert 1 <= self.orport <= 65535
 
 def parseDescFile(f, bridge_purpose='bridge'):
+    """Generator. Parses a cached-descriptors file 'f', and yields a Bridge
+       object for every entry whose purpose matches bridge_purpose.
+    """
     nickname = ip = orport = fingerprint = purpose = None
 
     for line in f:
@@ -140,6 +169,7 @@
             nickname = ip = orport = fingerprint = purpose = None
 
 class BridgeHolder:
+    """Abstract base class for all classes that hold bridges."""
     def insert(self, bridge):
         raise NotImplemented()
 
@@ -147,7 +177,15 @@
         return True
 
 class BridgeRing(BridgeHolder):
+    """Arranges bridges in a ring based on an hmac function."""
+    ## Fields:
+    ##   bridges: a map from hmac value to Bridge.
+    ##   bridgesByID: a map from bridge ID Digest to Bridge.
+    ##   isSorted: true iff sortedKeys is currently sorted.
+    ##   sortedKeys: a list of all the hmacs, in order.
+    ##   name: a string to represent this ring in the logs.
     def __init__(self, key):
+        """Create a new BridgeRing, using key as its hmac key."""
         self.bridges = {}
         self.bridgesByID = {}
         self.hmac = get_hmac_fn(key, hex=False)
@@ -159,6 +197,8 @@
         return len(self.bridges)
 
     def insert(self, bridge):
+        """Add a bridge to the ring.  If the bridge is already there,
+           replace the old one."""
         ident = bridge.getID()
         pos = self.hmac(ident)
         if not self.bridges.has_key(pos):
@@ -168,17 +208,20 @@
         self.bridgesByID[id] = bridge
         logging.debug("Adding %s to %s", bridge.getConfigLine(), self.name)
 
-    def sort(self):
+    def _sort(self):
+        """Helper: put the keys in sorted order."""
         if not self.isSorted:
             self.sortedKeys.sort()
             self.isSorted = True
 
     def _getBridgeKeysAt(self, pos, N=1):
+        """Helper: return the N keys appearing in the ring after position
+           pos"""
         assert len(pos) == DIGEST_LEN
         if N >= len(self.sortedKeys):
             return self.sortedKeys
         if not self.isSorted:
-            self.sort()
+            self._sort()
         idx = bisect.bisect_left(self.sortedKeys, pos)
         r = self.sortedKeys[idx:idx+N]
         if len(r) < N:
@@ -188,17 +231,25 @@
         return r
 
     def getBridges(self, pos, N=1):
+        """Return the N bridges appearing in the ring after position pos"""
         keys = self._getBridgeKeysAt(pos, N)
         keys.sort()
         return [ self.bridges[k] for k in keys ]
 
     def getBridgeByID(self, fp):
+        """Return the bridge whose identity digest is fp, or None if no such
+           bridge exists."""
         return self.bridgesByID.get(fp)
 
 
 class LogDB:
+    """Wraps a database object and records all modifications to a
+       human-readable logfile."""
     def __init__(self, kwd, db, logfile):
-        self._kwd = kwd
+        if kwd:
+            self._kwd = "%s: "%kwd
+        else:
+            self._kwd = ""
         self._db = db
         self._logfile = logfile
     def __delitem__(self, k):
@@ -211,7 +262,7 @@
         try:
             return self._db[k]
         except KeyError:
-            self._logfile.write("%s: [%r] = [%r]\n"%(self._kwd, k, v))
+            self._logfile.write("%s[%r] = [%r]\n"%(self._kwd, k, v))
             self._db[k] = v
             return v
     def __len__(self):
@@ -227,6 +278,9 @@
 
 
 class PrefixStore:
+    """Wraps a database object and prefixes the keys in all requests with
+       'prefix'.  This is used to multiplex several key->value mappings
+       onto a single database."""
     def __init__(self, store, prefix):
         self._d = store
         self._p = prefix
@@ -247,6 +301,9 @@
         return [ k[n:] for k in self._d.keys() if k.startswith(self._p) ]
 
 class FixedBridgeSplitter(BridgeHolder):
+    """A bridgeholder that splits bridges up based on an hmac and assigns
+       them to several sub-bridgeholders with equal probability.
+    """
     def __init__(self, key, rings):
         self.hmac = get_hmac_fn(key, hex=True)
         self.rings = rings[:]
@@ -268,6 +325,9 @@
 
 
 class UnallocatedHolder(BridgeHolder):
+    """A pseudo-bridgeholder that ignores its bridges and leaves them
+       unassigned.
+    """
     def insert(self, bridge):
         logging.debug("Leaving %s unallocated", bridge.getConfigLine())
 
@@ -275,6 +335,9 @@
         return False
 
 class BridgeTracker:
+    """A stats tracker that records when we first saw and most recently
+       saw each bridge.
+    """
     def __init__(self, firstSeenStore, lastSeenStore):
         self.firstSeenStore = firstSeenStore
         self.lastSeenStore = lastSeenStore
@@ -289,6 +352,10 @@
         self.firstSeenStore.setdefault(bridgeID, now)
 
 class BridgeSplitter(BridgeHolder):
+    """A BridgeHolder that splits incoming bridges up based on an hmac,
+       and assigns them to sub-bridgeholders with different probabilities.
+       Bridge-to-bridgeholder associations are recorded in a store.
+    """
     def __init__(self, key, store):
         self.hmac = get_hmac_fn(key, hex=True)
         self.store = store
@@ -305,6 +372,13 @@
         return n
 
     def addRing(self, ring, ringname, p=1):
+        """Add a new bridgeholder.
+           ring -- the bridgeholder to add.
+           ringname -- a string representing the bridgeholder.  This is used
+               to record which bridges have been assigned where in the store.
+           p -- the relative proportion of bridges to assign to this
+               bridgeholder.
+        """
         assert isinstance(ring, BridgeHolder)
         self.ringsByName[ringname] = ring
         self.pValues.append(self.totalP)
@@ -312,6 +386,8 @@
         self.totalP += p
 
     def addTracker(self, t):
+        """Adds a statistics tracker that gets told about every bridge we see.
+        """
         self.statsHolders.append(t)
 
     def insert(self, bridge):
@@ -334,11 +410,3 @@
                 self.store[bridgeID] = ringname
             ring.insert(bridge)
 
-if __name__ == '__main__':
-    import sys
-    br = BridgeRing("hello")
-    for fname in sys.argv[1:]:
-        f = open(fname)
-        for bridge in parseDescFile(f):
-            br.insert(bridge)
-

Modified: bridgedb/trunk/lib/bridgedb/Dist.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Dist.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Dist.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,7 +1,11 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
+"""
+This module has functions to decide which bridges to hand out to whom.
+"""
+
 import bridgedb.Bridges
 
 import logging
@@ -17,6 +21,18 @@
     return ".".join( ip.split(".")[:3] )
 
 class IPBasedDistributor(bridgedb.Bridges.BridgeHolder):
+    """Object that hands out bridges based on the IP address of an incoming
+       request and the current time period.
+    """
+    ## Fields:
+    ##    areaMapper -- a function that maps an IP address to a string such
+    ##        that addresses mapping to the same string are in the same "area".
+    ##    rings -- a list of BridgeRing objects.  Every bridge goes into one
+    ##        of these rings, and every area is associated with one.
+    ##    splitter -- a FixedBridgeSplitter to assign bridges into the
+    ##        rings of this distributor.
+    ##    areaOrderHmac -- an hmac function used to order areas within rings.
+    ##    areaClusterHmac -- an hmac function used to assign areas to rings.
     def __init__(self, areaMapper, nClusters, key):
         self.areaMapper = areaMapper
 
@@ -36,9 +52,16 @@
         self.areaClusterHmac = bridgedb.Bridges.get_hmac_fn(key4, hex=True)
 
     def insert(self, bridge):
+        """Assign a bridge to this distributor."""
         self.splitter.insert(bridge)
 
     def getBridgesForIP(self, ip, epoch, N=1):
+        """Return a list of bridges to give to a user.
+           ip -- the user's IP address, as a dotted quad.
+           epoch -- the time period when we got this request.  This can
+               be any string, so long as it changes with every period.
+           N -- the number of bridges to try to give back.
+        """
         if not len(self.splitter):
             return []
 
@@ -61,9 +84,9 @@
 # These characters are the ones that RFC2822 allows.
 #ASPECIAL = '!#$%&*+-/=?^_`{|}~'
 #ASPECIAL += "\\\'"
-
 # These are the ones we're pretty sure we can handle right.
 ASPECIAL = '-_+/=_~'
+
 ACHAR = r'[\w%s]' % "".join("\\%s"%c for c in ASPECIAL)
 DOTATOM = r'%s+(?:\.%s+)*'%(ACHAR,ACHAR)
 DOMAIN = r'\w+(?:\.\w+)*'
@@ -73,14 +96,21 @@
 ADDRSPEC_PAT = re.compile(ADDRSPEC)
 
 class BadEmail(Exception):
+    """Exception raised when we get a bad email address."""
     def __init__(self, msg, email):
         Exception.__init__(self, msg)
         self.email = email
 
 class UnsupportedDomain(BadEmail):
+    """Exception raised when we get an email address from a domain we
+       don't know."""
     pass
 
 def extractAddrSpec(addr):
+    """Given an email From line, try to extract and parse the addrspec
+       portion.  Returns localpart,domain on success; raises BadEmail
+       on failure.
+    """
     orig_addr = addr
     addr = SPACE_PAT.sub(' ', addr)
     addr = addr.strip()
@@ -116,6 +146,10 @@
     return localpart, domain
 
 def normalizeEmail(addr, domainmap):
+    """Given the contents of a from line, and a map of supported email
+       domains (in lowercase), raise BadEmail or return a normalized
+       email address.
+    """
     addr = addr.lower()
     localpart, domain = extractAddrSpec(addr)
     if domainmap is not None:
@@ -128,21 +162,38 @@
     return "%s@%s"%(localpart, domain)
 
 class EmailBasedDistributor(bridgedb.Bridges.BridgeHolder):
+    """Object that hands out bridges based on the email address of an incoming
+       request and the current time period.
+    """
+    ## Fields:
+    ##   emailHmac -- an hmac function used to order email addresses within
+    ##       a ring.
+    ##   ring -- a BridgeRing object to hold all the bridges we hand out.
+    ##   store -- a database object to remember what we've given to whom.
+    ##   domainmap -- a map from lowercase domains that we support mail from
+    ##       to their canonical forms.
     def __init__(self, key, store, domainmap):
-
         key1 = bridgedb.Bridges.get_hmac(key, "Map-Addresses-To-Ring")
         self.emailHmac = bridgedb.Bridges.get_hmac_fn(key1, hex=False)
 
         key2 = bridgedb.Bridges.get_hmac(key, "Order-Bridges-In-Ring")
         self.ring = bridgedb.Bridges.BridgeRing(key2)
         self.ring.name = "email ring"
+        # XXXX clear the store when the period rolls over!
         self.store = store
         self.domainmap = domainmap
 
     def insert(self, bridge):
+        """Assign a bridge to this distributor."""
         self.ring.insert(bridge)
 
     def getBridgesForEmail(self, emailaddress, epoch, N=1):
+        """Return a list of bridges to give to a user.
+           emailaddress -- the user's email address, as given in a from line.
+           epoch -- the time period when we got this request.  This can
+               be any string, so long as it changes with every period.
+           N -- the number of bridges to try to give back.
+        """
         emailaddress = normalizeEmail(emailaddress, self.domainmap)
         if emailaddress is None:
             return [] #XXXX raise an exception.
@@ -163,15 +214,3 @@
         memo = "".join(b.getID() for b in result)
         self.store[emailaddress] = memo
         return result
-
-if __name__ == '__main__':
-    import sys
-    for line in sys.stdin:
-        line = line.strip()
-        if line.startswith("From: "):
-            line = line[6:]
-        try:
-            normal = normalizeEmail(line, None)
-            print normal
-        except BadEmail, e:
-            print line, e

Modified: bridgedb/trunk/lib/bridgedb/Main.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Main.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Main.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,7 +1,11 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
+"""
+This module sets up a bridgedb and starts the servers running.
+"""
+
 import anydbm
 import os
 import signal
@@ -16,9 +20,13 @@
 import bridgedb.Server as Server
 
 class Conf:
+    """A configuration object.  Holds unvalidated attributes.
+    """
     def __init__(self, **attrs):
         self.__dict__.update(attrs)
 
+# An example configuration.  Used for testing.  See sample
+# bridgedb.conf for documentation.
 CONFIG = Conf(
     RUN_IN_DIR = ".",
 
@@ -58,6 +66,8 @@
   )
 
 def configureLogging(cfg):
+    """Set up Python's logging subsystem based on the configuratino.
+    """
     level = getattr(cfg, 'LOGLEVEL', 'WARNING')
     level = getattr(logging, level)
     extra = {}
@@ -100,17 +110,24 @@
     return k
 
 def load(cfg, splitter):
+    """Read all the bridge files from cfg, and pass them into a splitter
+       object.
+    """
     for fname in cfg.BRIDGE_FILES:
         f = open(fname, 'r')
         for bridge in Bridges.parseDescFile(f, cfg.BRIDGE_PURPOSE):
             splitter.insert(bridge)
         f.close()
 
-_reloadFn = None
+_reloadFn = lambda: True
 def _handleSIGHUP(*args):
+    """Called when we receive a SIGHUP; invokes _reloadFn."""
     reactor.callLater(0, _reloadFn)
 
 def startup(cfg):
+    """Parse bridges, 
+    """
+    # Expand any ~ characters in paths in the configuration.
     cfg.BRIDGE_FILES = [ os.path.expanduser(fn) for fn in cfg.BRIDGE_FILES ]
     for key in ("RUN_IN_DIR", "DB_FILE", "DB_LOG_FILE", "MASTER_KEY_FILE",
                 "HTTPS_CERT_FILE", "HTTPS_KEY_FILE", "PIDFILE", "LOGFILE"):
@@ -118,28 +135,36 @@
         if v:
             setattr(cfg, key, os.path.expanduser(v))
 
+    # Change to the directory where we're supposed to run.
     if cfg.RUN_IN_DIR:
         os.chdir(cfg.RUN_IN_DIR)
 
+    # Write the pidfile.
     if cfg.PIDFILE:
         f = open(cfg.PIDFILE, 'w')
         f.write("%s\n"%os.getpid())
         f.close()
 
+    # Set up logging.
     configureLogging(cfg)
 
+    # Load the master key, or create a new one.
     key = getKey(cfg.MASTER_KEY_FILE)
+
+    # Initialize our DB file.
     dblogfile = None
-    emailDistributor = ipDistributor = None
-
     baseStore = store = anydbm.open(cfg.DB_FILE, "c", 0600)
     if cfg.DB_LOG_FILE:
         dblogfile = open(cfg.DB_LOG_FILE, "a+", 0)
-        store = Bridges.LogDB("db", store, dblogfile)
+        store = Bridges.LogDB(None, store, dblogfile)
 
+    # Create a BridgeSplitter to assign the bridges to the different
+    # distributors.
     splitter = Bridges.BridgeSplitter(Bridges.get_hmac(key, "Splitter-Key"),
                                       Bridges.PrefixStore(store, "sp|"))
 
+    emailDistributor = ipDistributor = None
+    # As appropriate, create an IP-based distributor.
     if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
         ipDistributor = Dist.IPBasedDistributor(
             Dist.uniformMap,
@@ -148,6 +173,7 @@
         splitter.addRing(ipDistributor, "https", cfg.HTTPS_SHARE)
         webSchedule = Time.IntervalSchedule("day", 2)
 
+    # As appropriate, create an email-based distributor.
     if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
         for d in cfg.EMAIL_DOMAINS:
             cfg.EMAIL_DOMAIN_MAP[d] = d
@@ -158,15 +184,18 @@
         splitter.addRing(emailDistributor, "email", cfg.EMAIL_SHARE)
         emailSchedule = Time.IntervalSchedule("day", 1)
 
+    # As appropriate, tell the splitter to leave some bridges unallocated.
     if cfg.RESERVED_SHARE:
         splitter.addRing(Bridges.UnallocatedHolder(),
                          "unallocated",
                          cfg.RESERVED_SHARE)
 
+    # Add a tracker to tell us how often we've seen various bridges.
     stats = Bridges.BridgeTracker(Bridges.PrefixStore(store, "fs|"),
                                   Bridges.PrefixStore(store, "ls|"))
     splitter.addTracker(stats)
 
+    # Parse the bridges and log how many we put where.
     logging.info("Loading bridges")
     load(cfg, splitter)
     logging.info("%d bridges loaded", len(splitter))
@@ -177,19 +206,22 @@
         logging.info("  by location set: %s",
                      " ".join(str(len(r)) for r in ipDistributor.rings))
 
+    # Configure HTTP and/or HTTPS servers.
     if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
         Server.addWebServer(cfg, ipDistributor, webSchedule)
 
+    # Configure Email servers.
     if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
         Server.addSMTPServer(cfg, emailDistributor, emailSchedule)
 
+    # Make the parse-bridges function get re-called on SIGHUP.
     def reload():
         load(cfg, splitter)
-
     global _reloadFn
     _reloadFn = reload
     signal.signal(signal.SIGHUP, _handleSIGHUP)
 
+    # Actually run the servers.
     try:
         logging.info("Starting reactors.")
         Server.runServers()
@@ -201,6 +233,9 @@
             os.unlink(cfg.PIDFILE)
 
 def run():
+    """Parse the command line to determine where the configuration is.
+       Parse the configuration, and start the servers.
+    """
     if len(sys.argv) != 2:
         print "Syntax: %s [config file]" % sys.argv[0]
         sys.exit(1)

Modified: bridgedb/trunk/lib/bridgedb/Server.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Server.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Server.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,7 +1,11 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
+"""
+This module implements the web and email interfaces to the bridge database.
+"""
+
 from cStringIO import StringIO
 import MimeWriter
 import rfc822
@@ -68,9 +72,16 @@
 """
 
 class WebResource(twisted.web.resource.Resource):
+    """This resource is used by Twisted Web to give a web page with some
+       bridges in response to a request."""
     isLeaf = True
 
     def __init__(self, distributor, schedule, N=1):
+        """Create a new WebResource.
+             distributor -- an IPBasedDistributor object
+             schedule -- an IntervalSchedule object
+             N -- the number of bridges to hand out per query.
+        """
         twisted.web.resource.Resource.__init__(self)
         self.distributor = distributor
         self.schedule = schedule
@@ -90,22 +101,34 @@
         return HTML_MESSAGE_TEMPLATE % answer
 
 def addWebServer(cfg, dist, sched):
+    """Set up a web server.
+         cfg -- a configuration object from Main.  We use these options:
+                HTTPS_N_BRIDGES_PER_ANSWER
+                HTTP_UNENCRYPTED_PORT
+                HTTP_UNENCRYPTED_BIND_IP
+                HTTPS_PORT
+                HTTPS_BIND_IP
+         dist -- an IPBasedDistributor object.
+         sched -- an IntervalSchedule object.
+    """
     Site = twisted.web.server.Site
     resource = WebResource(dist, sched, cfg.HTTPS_N_BRIDGES_PER_ANSWER)
     site = Site(resource)
     if cfg.HTTP_UNENCRYPTED_PORT:
-        ip = cfg.HTTPS_BIND_IP or ""
+        ip = cfg.HTTP_UNENCRYPTED_BIND_IP or ""
         reactor.listenTCP(cfg.HTTP_UNENCRYPTED_PORT, site, interface=ip)
     if cfg.HTTPS_PORT:
         from twisted.internet.ssl import DefaultOpenSSLContextFactory
         #from OpenSSL.SSL import SSLv3_METHOD
-        ip = cfg.HTTP_UNENCRYPTED_BIND_IP or ""
+        ip = cfg.HTTPS_BIND_IP or ""
         factory = DefaultOpenSSLContextFactory(cfg.HTTPS_KEY_FILE,
                                                cfg.HTTPS_CERT_FILE)
         reactor.listenSSL(cfg.HTTPS_PORT, site, factory, interface=ip)
     return site
 
 class MailFile:
+    """A file-like object used to hand rfc822.Message a list of lines
+       as though it were reading them from a file."""
     def __init__(self, lines):
         self.lines = lines
         self.idx = 0
@@ -113,11 +136,17 @@
         try :
             line = self.lines[self.idx]
             self.idx += 1
-            return line #Append a \n? XXXX
+            return line
         except IndexError:
             return ""
 
 def getMailResponse(lines, ctx):
+    """Given a list of lines from an incoming email message, and a
+       MailContext object, parse the email and decide what to do in response.
+       If we want to answer, return a 2-tuple containing the address that
+       will receive the response, and a readable filelike object containing
+       the response.  Return None,None if we shouldn't answer.
+    """
     # Extract data from the headers.
     msg = rfc822.Message(MailFile(lines))
     subject = msg.getheader("Subject", None)
@@ -132,6 +161,8 @@
     else:
         logging.info("No From or Sender header on incoming mail.")
         return None,None
+
+    # Was the magic string included?
     for ln in lines:
         if ln.strip().lower() in ("get bridges", "subject: get bridges"):
             break
@@ -140,6 +171,7 @@
                      clientAddr)
         return None,None
 
+    # Figure out which bridges to send
     try:
         interval = ctx.schedule.getInterval(time.time())
         bridges = ctx.distributor.getBridgesForEmail(clientAddr,
@@ -160,7 +192,8 @@
     w.addheader("Message-ID", twisted.mail.smtp.messageid())
     if not subject.startswith("Re:"): subject = "Re: %s"%subject
     w.addheader("Subject", subject)
-    w.addheader("In-Reply-To", msgID)
+    if msgID:
+        w.addheader("In-Reply-To", msgID)
     w.addheader("Date", twisted.mail.smtp.rfc822date())
     body = w.startbody("text/plain")
 
@@ -171,6 +204,9 @@
     return clientAddr, f
 
 def replyToMail(lines, ctx):
+    """Given a list of lines from an incoming email message, and a
+       MailContext object, possibly send a reply.
+    """
     logging.info("Got a completed email; attempting to reply.")
     sendToUser, response = getMailResponse(lines, ctx)
     if response is None:
@@ -187,26 +223,38 @@
     return d
 
 class MailContext:
+    """Helper object that holds information used by email subsystem."""
     def __init__(self, cfg, dist, sched):
+        # Reject any RCPT TO lines that aren't to this user.
         self.username = "bridges"
+        # Reject any mail longer than this.
         self.maximumSize = 32*1024
+        # Use this server for outgoing mail.
         self.smtpServer = "127.0.0.1"
         self.smtpPort = 25
+        # Use this address as the from line for outgoing mail.
         self.fromAddr = "bridges@xxxxxxxxxxxxxx"
+        # An EmailBasedDistributor object
         self.distributor = dist
+        # An IntervalSchedule object
         self.schedule = sched
+        # The number of bridges to send for each email.
         self.N = cfg.EMAIL_N_BRIDGES_PER_ANSWER
 
 class MailMessage:
+    """Plugs into the Twisted Mail and receives an incoming message.
+       Once the message is in, we reply or we don't. """
     implements(twisted.mail.smtp.IMessage)
 
     def __init__(self, ctx):
+        """Create a new MailMessage from a MailContext."""
         self.ctx = ctx
         self.lines = []
         self.nBytes = 0
         self.ignoring = False
 
     def lineReceived(self, line):
+        """Called when we get another line of an incoming message."""
         self.nBytes += len(line)
         if self.nBytes > self.ctx.maximumSize:
             self.ignoring = True
@@ -214,14 +262,17 @@
             self.lines.append(line)
 
     def eomReceived(self):
+        """Called when we receive the end of a message."""
         if not self.ignoring:
             replyToMail(self.lines, self.ctx)
         return twisted.internet.defer.succeed(None)
 
     def connectionLost(self):
+        """Called if we die partway through reading a message."""
         pass
 
 class MailDelivery:
+    """Plugs into Twisted Mail and handles SMTP commands."""
     implements(twisted.mail.smtp.IMessageDelivery)
     def setBridgeDBContext(self, ctx):
         self.ctx = ctx
@@ -236,6 +287,8 @@
         return lambda: MailMessage(self.ctx)
 
 class MailFactory(twisted.mail.smtp.SMTPFactory):
+    """Plugs into Twisted Mail; creates a new MailDelivery whenever we get
+       a connection on the SMTP port."""
     def __init__(self, *a, **kw):
         twisted.mail.smtp.SMTPFactory.__init__(self, *a, **kw)
         self.delivery = MailDelivery()
@@ -250,6 +303,14 @@
         return p
 
 def addSMTPServer(cfg, dist, sched):
+    """Set up a smtp server.
+         cfg -- a configuration object from Main.  We use these options:
+                EMAIL_BIND_IP
+                EMAIL_PORT
+                EMAIL_N_BRIDGES_PER_ANSWER
+         dist -- an EmailBasedDistributor object.
+         sched -- an IntervalSchedule object.
+    """
     ctx = MailContext(cfg, dist, sched)
     factory = MailFactory()
     factory.setBridgeDBContext(ctx)
@@ -258,4 +319,5 @@
     return factory
 
 def runServers():
+    """Start all the servers that we've configured. Exits when they do."""
     reactor.run()

Modified: bridgedb/trunk/lib/bridgedb/Tests.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Tests.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Tests.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,6 +1,6 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
 import doctest
 import unittest

Modified: bridgedb/trunk/lib/bridgedb/Time.py
===================================================================
--- bridgedb/trunk/lib/bridgedb/Time.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/lib/bridgedb/Time.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,17 +1,29 @@
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
-# See LICENSE for licensing informatino
+# See LICENSE for licensing information
 
+"""
+This module implements functions for dividing time into chunks.
+"""
+
 import calendar
 import time
 
 KNOWN_INTERVALS = [ "hour", "day", "week", "month" ]
-N_ELEMENTS = { 'month' : 2,
-               'day' : 3,
-               'hour' : 4 }
 
 class IntervalSchedule:
+    """An IntervalSchedule splits time into somewhat natural periods,
+       based on hours, days, weeks, or months.
+    """
+    ## Fields:
+    ##  itype -- one of "month", "day", "hour".
+    ##  count -- how many of the units in itype belong to each period.
     def __init__(self, intervaltype, count):
+        """Create a new IntervalSchedule.
+            intervaltype -- one of month, week, day, hour.
+            count -- how many of the units in intervaltype belong to each
+                     period.
+        """
         it = intervaltype.lower()
         if it.endswith("s"): it = it[:-1]
         if it not in KNOWN_INTERVALS:
@@ -22,26 +34,31 @@
             count *= 7
         self.itype = it
         self.count = count
-        self.n_elements = N_ELEMENTS[it]
 
     def _intervalStart(self, when):
+        """Return the time (as an int) of the start of the interval containing
+           'when'."""
         if self.itype == 'month':
+            # For months, we always start at the beginning of the month.
             tm = time.gmtime(when)
             n = tm.tm_year * 12 + tm.tm_mon - 1
             n -= (n % self.count)
             month = n%12 + 1
             return calendar.timegm((n//12, month, 1, 0, 0, 0))
         elif self.itype == 'day':
+            # For days, we start at the beginning of a day.
             when -= when % (86400 * self.count)
             return when
         elif self.itype == 'hour':
+            # For hours, we start at the beginning of an hour.
             when -= when % (3600 * self.count)
             return when
         else:
             assert False
 
     def getInterval(self, when):
-        """
+        """Return a string representing the interval that contains
+           the time 'when'.
 
         >>> t = calendar.timegm((2007, 12, 12, 0, 0, 0))
         >>> I = IntervalSchedule('month', 1)
@@ -67,6 +84,7 @@
             assert False
 
     def nextIntervalStarts(self, when):
+        """Return the start time of the interval starting _after_ when."""
         if self.itype == 'month':
             tm = time.gmtime(when)
             n = tm.tm_year * 12 + tm.tm_mon - 1

Modified: bridgedb/trunk/setup.py
===================================================================
--- bridgedb/trunk/setup.py	2007-12-18 23:04:47 UTC (rev 12861)
+++ bridgedb/trunk/setup.py	2007-12-18 23:04:49 UTC (rev 12862)
@@ -1,3 +1,4 @@
+#!/usr/bin/python
 # BridgeDB by Nick Mathewson.
 # Copyright (c) 2007, The Tor Project, Inc.
 # See LICENSE for licensing information