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

[minion-cvs] Start implementing directory agreement.



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

Modified Files:
	Config.py ServerInfo.py test.py 
Log Message:
Start implementing directory agreement.

DirFormats:
- New file.  Contains code to generate vote directories, validate vote
  directories, and generate and sign consensus directories.

ServerInfo:
- Add code to parse and validate new multiply-signed directory formats.
- Make Hostname, Contact, Maximum-Size, and Allow-From into mandatory fields
- Ignore Key-Digest and IP.
- Make the serverdesc-can't-supersede-itself check more explicit
- Remove unused "Packet-Formats" field
- Stop generating IPv4 routing, ever.

Config:
- Drop max nickname length to 24.  128-character nicknames are insane.
- Add capability for _ConfigFile objects to retain their original
  unparsed contents in a field.  Useful for directory generation.
- Add simple support for 'IGNORE'd fields, to make deprecation and removal
  of features easier to do.

test:
- Make sure DirFormats parses; stop enforcing Key-Digest
- Stop checking for IP in server descriptors.

ServerConfig:
- Mark IP for deprecation.

ServerKeys:
- Stop checking IP in server descriptors.



Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.87
retrieving revision 1.88
diff -u -d -r1.87 -r1.88
--- Config.py	18 May 2004 02:55:14 -0000	1.87
+++ Config.py	24 Aug 2004 22:16:08 -0000	1.88
@@ -418,7 +418,7 @@
 _NICKNAME_INITIAL_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
                            "abcdefghijklmnopqrstuvwxyz")
 
-MAX_NICKNAME = 128
+MAX_NICKNAME = 24
 def _parseNickname(s):
     """Validation function.  Returns true iff s contains a valid
        server nickname -- that is, a string of 1..128 characters,
@@ -678,7 +678,7 @@
     #
     # Fields to be set by a subclass:
     #     _syntax is map from sec->{key:
-    #                               (ALLOW/REQUIRE/ALLOW*/REQUIRE*,
+    #                               (ALLOW/REQUIRE/ALLOW*/REQUIRE*/IGNORE,
     #                                 type,
     #                                 default, ) }
     #     _restrictFormat is 1/0: do we allow full RFC822ness, or do
@@ -735,7 +735,7 @@
     _restrictKeys = 1
     _restrictSections = 1
 
-    def __init__(self, filename=None, string=None, assumeValid=0):
+    def __init__(self, filename=None, string=None, assumeValid=0, keep=0):
         """Create a new _ConfigFile.  If <filename> is set, read from
            a corresponding file.  If <string> is set, parse its contents.
 
@@ -753,13 +753,17 @@
         self.assumeValid = assumeValid
 
         if filename:
-            contents = mixminion.Common.readPossiblyGzippedFile(filename)
+            assert string is None
+            string = mixminion.Common.readPossiblyGzippedFile(filename)
             self.fname = filename
-            self.__load(contents)
         else:
             assert string is not None
             self.fname = None
-            self.__load(string)
+
+        self.__load(string)
+
+        if keep:
+            self._originalContents = string
 
     def __load(self, fileContents):
         """As in .reload(), but takes an open file object _or_ a string."""
@@ -841,17 +845,19 @@
                                           % (k, line))
                     else:
                         section[k] = v
-                else:
-                    assert rule in ('REQUIRE*','ALLOW*')
+                elif rule in ('REQUIRE*','ALLOW*'):
                     try:
                         section[k].append(v)
                     except KeyError:
                         section[k] = [v]
+                else:
+                    assert rule == 'IGNORE'
+                    pass
 
             # Check for missing entries, setting defaults and detecting
             # missing requirements as we go.
             for k, (rule, parseType, default) in secConfig.items():
-                if k == '__SECTION__':
+                if k == '__SECTION__' or rule == 'IGNORE':
                     continue
                 elif not section.has_key(k):
                     if rule in ('REQUIRE', 'REQUIRE*'):

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.85
retrieving revision 1.86
diff -u -d -r1.85 -r1.86
--- ServerInfo.py	27 Jul 2004 03:10:03 -0000	1.85
+++ ServerInfo.py	24 Aug 2004 22:16:08 -0000	1.86
@@ -123,29 +123,28 @@
                      "Published": ("REQUIRE", "time", None),
                      "Valid-After": ("REQUIRE", "date", None),
                      "Valid-Until": ("REQUIRE", "date", None),
-                     #XXXX008 change this to 'require': servers have all
-                     #XXXX008 had it since 007.
-                     "Contact": ("ALLOW", None, None),
+                     "Contact": ("REQUIRE", None, None),
                      "Comments": ("ALLOW", None, None),
                      "Packet-Key": ("REQUIRE", "publicKey", None),
                      "Contact-Fingerprint": ("ALLOW", None, None),
-                     "Packet-Formats": ("ALLOW", None, None),#XXXX008 remove
                      # XXXX008 change these next few to "REQUIRE"; servers
                      # XXXX008 have had them all since 0.0.5
-                     "Packet-Versions": ("ALLOW", "list", '0.3'),
+                     "Packet-Versions": ("REQUIRE", "list", '0.3'),
                      "Software": ("ALLOW", None, None),
                      "Secure-Configuration": ("ALLOW", "boolean", None),
                      "Why-Insecure": ("ALLOW", None, None),
                      },
         "Incoming/MMTP" : {
                      "Version": ("REQUIRE", None, None),
-                     "IP": ("ALLOW", "IP", None),#XXXX008 remove
-                     "Hostname": ("ALLOW", "host", None),#XXXX008 require;since 0.0.6
+                     #XXXX0010 remove; ungenerated since 009.
+                     "IP": ("IGNORE", "IP", None),
+                     "Hostname": ("REQUIRE", "host", None),
                      "Port": ("REQUIRE", "int", None),
-                     "Key-Digest": ("ALLOW", "base64", None),#XXXX008 rmv; not used since 0.0.5
                      "Protocols": ("REQUIRE", "list", None),
                      "Allow": ("ALLOW*", "addressSet_allow", None),
                      "Deny": ("ALLOW*", "addressSet_deny", None),
+                     #XXXX0010 remove; ungenerated since 009.
+                     "Key-Digest":("IGNORE", None, None),
                      },
         "Outgoing/MMTP" : {
                      "Version": ("REQUIRE", None, None),
@@ -155,16 +154,13 @@
                      },
         "Delivery/MBOX" : {
                      "Version": ("REQUIRE", None, None),
-                     # XXXX008 change to 'REQUIRE'; since 0.0.6
-                     "Maximum-Size": ("ALLOW", "int", "32"),
-                     # XXXX008 change to 'REQUIRE'; since 0.0.6
-                     "Allow-From": ("ALLOW", "boolean", "yes"),
+                     "Maximum-Size": ("REQUIRE", "int", "32"),
+                     "Allow-From": ("REQUIRE", "boolean", "yes"),
                      },
         "Delivery/SMTP" : {
                      "Version": ("REQUIRE", None, None),
-                     # XXXX008 change to 'REQUIRE'; since 0.0.6
-                     "Maximum-Size": ("ALLOW", "int", "32"),
-                     "Allow-From": ("ALLOW", "boolean", "yes"),
+                     "Maximum-Size": ("REQUIRE", "int", "32"),
+                     "Allow-From": ("REQUIRE", "boolean", "yes"),
                      },
         "Delivery/Fragmented" : {
                      "Version": ("REQUIRE", None, None),
@@ -189,7 +185,7 @@
          }
 
     def __init__(self, fname=None, string=None, assumeValid=0,
-                 validatedDigests=None):
+                 validatedDigests=None, _keepContents=0): #DOCDOC
         """Read a server descriptor from a file named <fname>, or from
              <string>.
 
@@ -201,7 +197,8 @@
         """
         self._isValidated = 0
         self._validatedDigests = validatedDigests
-        mixminion.Config._ConfigFile.__init__(self, fname, string, assumeValid)
+        mixminion.Config._ConfigFile.__init__(self, fname, string, assumeValid,
+                                              keep=_keepContents)
         del self._validatedDigests
 
     def prevalidate(self, contents):
@@ -288,11 +285,6 @@
             if inMMTP['Version'] != '0.1':
                 raise ConfigError("Unrecognized MMTP descriptor version %s"%
                                   inMMTP['Version'])
-            if len(inMMTP['Key-Digest']) != DIGEST_LEN:
-                raise ConfigError("Invalid key digest %s"%
-                                  formatBase64(inMMTP['Key-Digest']))
-            if not inMMTP['IP'] and not inMMTP['Hostname']:
-                raise ConfigError("Incoming/MMTP section has neither IP nor hostname")
 
         ## Outgoing/MMTP section
         outMMTP = self['Outgoing/MMTP']
@@ -315,14 +307,8 @@
            descriptor."""
         return self['Server']['Digest']
 
-    def getIP(self):
-        """Returns this server's IP address.  (Returns None for servers
-           running version 0.0.7 or later.)"""
-        return self['Incoming/MMTP'].get('IP')
-
     def getHostname(self):
-        """Return this server's Hostname. (Returns None for servers running
-           version 0.0.5 or earlier.)"""
+        """Return this server's Hostname."""
         return self['Incoming/MMTP'].get("Hostname")
 
     def getPort(self):
@@ -337,20 +323,12 @@
         """Returns a hash of this server's identity key."""
         return sha1(pk_encode_public_key(self['Server']['Identity']))
 
-    def getIPV4Info(self):
-        """Returns a mixminion.Packet.IPV4Info object for routing messages
-           to this server.  (Returns None for servers running version 0.0.5
-           or earlier.)"""#DOCDOC wrong!
-        ip = self.getIP()
-        if ip is None: return None
-        return mixminion.Packet.IPV4Info(ip, self.getPort(), self.getKeyDigest())
-
     def getMMTPHostInfo(self):
         """Returns a mixminion.Packet.MMTPHostInfo object for routing messages
            to this server.  (Returns None for servers running version 0.0.7
            or later.)""" #DOCDOC wrong!
         host = self.getHostname()
-        if host is None: return None
+        assert host
         return mixminion.Packet.MMTPHostInfo(
             host, self.getPort(), self.getKeyDigest())
 
@@ -358,15 +336,16 @@
         """Return whichever of MMTPHostInfo or IPV4 info is best for
            delivering to this server (assuming that the sending host
            supports both."""
-        if self.getHostname():
-            return self.getMMTPHostInfo()
-        else:
-            return self.getIPV4Info()
+        return self.getMMTPHostInfo()
 
     def getIdentity(self):
         """Return this server's public identity key."""
         return self['Server']['Identity']
 
+    def getIdentityDigest(self):
+        """DOCDOC"""
+        return sha1(pk_encode_public_key(self.getIdentity()))
+
     def getIncomingMMTPProtocols(self):
         """Return a list of the MMTP versions supported by this this server
            for incoming packets."""
@@ -397,8 +376,6 @@
             return 1
         myOutProtocols = self.getOutgoingMMTPProtocols()
         otherInProtocols = otherDesc.getIncomingMMTPProtocols()
-        if not self.getHostname() and not otherDesc.getIP():
-            return 0
         for out in myOutProtocols:
             if out in otherInProtocols:
                 return 1
@@ -424,14 +401,9 @@
            point."""
         assert self.canRelayTo(otherDesc)
         assert 0 <= swap <= 1
-        if self.getHostname() and otherDesc.getHostname():
-            ri = otherDesc.getMMTPHostInfo().pack()
-            rt = [mixminion.Packet.FWD_HOST_TYPE,
-                  mixminion.Packet.SWAP_FWD_HOST_TYPE][swap]
-        else:
-            ri = otherDesc.getIPV4Info().pack()
-            rt = [mixminion.Packet.FWD_IPV4_TYPE,
-                  mixminion.Packet.SWAP_FWD_IPV4_TYPE][swap]
+        ri = otherDesc.getMMTPHostInfo().pack()
+        rt = [mixminion.Packet.FWD_HOST_TYPE,
+              mixminion.Packet.SWAP_FWD_HOST_TYPE][swap]
 
         return rt, ri
 
@@ -517,7 +489,8 @@
         """
         valid = self.getIntervalSet()
         for o in others:
-            if (o.isNewerThan(self) and
+            if (o.getDigest() != self.getDigest() and
+                o.isNewerThan(self) and
                 o.getNickname().lower() == self.getNickname().lower()):
                 valid -= o.getIntervalSet()
         return valid.isEmpty()
@@ -604,6 +577,99 @@
     def get(self, item, default=None):
         return self.header.get(item, default)
 
+class SignedDirectory:
+    ## Fields: DOCDOC
+    # signatues
+    # dirInfo
+    # servers
+    # signers
+    # goodServerNames
+    def __init__(self, string=None, fname=None, validatedDigests=None,
+                 _keepServerContents=0):
+        """DOCDOC
+           raises ConfigError.
+        """
+        if string:
+            contents = string
+        else:
+            contents = readPossiblyGzippedFile(fname)
+
+        contents = _cleanForDigest(contents)
+
+        digest = _getMultisignedDirectoryDigest(contents)
+        sigs, info, servers = splitMultisignedDirectory(contents)
+        del contents
+
+        self.signatures = [ ]
+
+        # Check signature digests.
+        badsigs = 0
+        for idx in range(len(sigs)):
+            sig = _DirectorySignature(sigs[idx])
+            if sig.getDigest() != digest:
+                LOG.warn("Mismatched digest on signature #%s; skipping",
+                         idx)
+                badsigs += 1
+            else:
+                self.signatures.append(sig)
+
+        # Parse the DirectoryInfo
+        self.dirInfo = _DirectoryInfo(info)
+
+        # Parse the Server descriptors.
+        self.servers = [ ]
+        for s in servers:
+            si = ServerInfo(string=s, validatedDigests=validatedDigests,
+                            _keepServerContents=_keepServerContents)
+        self.goodServerNames = [ name.lower()
+             for name in self.dirInfo['Directory-Info']['Recommended-Servers'] ]
+
+        self.signers = None
+
+    def getSigners(self):
+        #DOCDOC -- returns members of self.dirInfo.voters with valid signatures.
+        if self.signers is not None:
+            return self.signers
+
+        sigs = {}
+        self.signers = []
+        for s in self.signatures:
+            sigs[s.getKeyDigest()] = s
+        for digest, url in self.dirInfo.voters:
+            try:
+                s = sigs[digest]
+            except KeyError:
+                #XXXX008 warn
+                continue
+            if s.checkSignature():
+                # XXXX008 LOG.debug("Valid signature from %s")
+                self.signers.append[(digest, url)]
+            else:
+                #LOG.debug("Invalid signature from %s") XXXX008
+                continue
+
+        return self.signers
+
+    def getAllServers(self):
+        return self.servers
+
+    def getRecommendedNicknames(self):
+        return self.goodServerNames
+
+    def getServers(self):
+        #XXXX008 rename to getGoodServers
+        return [ s for s in self.allServers if
+                 s.getNickname().lower() in self.goodServerNicknames ]
+
+    def getSignatures(self):
+        return self.signatures
+
+    def __getitem__(self, item):
+        return self.dirInfo[item]
+
+    def get(self, item, default=None):
+        return self.header.get(item, default)
+
 class _DirectoryHeader(mixminion.Config._ConfigFile):
     """Internal object: used to parse, validate, and store fields in a
        directory's header sections.
@@ -673,6 +739,110 @@
         if self.expectedDigest != signedDigest:
             raise ConfigError("Signed digest was incorrect")
 
+class _DirectoryInfo(mixminion.Config._ConfigFile):
+    """Internal object: used to parse, validate, and store fields in a
+       directory's header sections.
+    """
+    ## Fields:
+    # voters = [ (key digest, URL base) ]
+    # DOCDOC
+    #
+    VERSION = "0.3"
+    _restrictFormat = 1
+    _restrictKeys = _restrictSections = 0
+    _syntax = {
+        "Directory-Info" : {
+           "__SECTION__" : ( "REQUIRE", None, None ),
+           "Version" : ("REQUIRE", None, None ),
+           "Status" : ("REQUIRE", None, None ),
+           "Valid-After" : ("REQUIRE", "date", None),
+           "Valid-Until" : ("REQUIRE", "date", None),
+           "Recommended-Servers" : ("REQUIRE", "list", None),
+           "Voting-Server" : ("REQUIRE*", None, None) },
+        "Recommended-Software" :
+           _DirectoryHeader._syntax["Recommended-Software"] }
+
+    def __init__(self, string):
+        self.sigStatus = None
+        mixminion.Config._ConfigFile.__init__(self, string=contents)
+
+    def prevalidate(self, contents):
+        for name, ents in contents:
+            if name == 'Directory-Info':
+                for k,v,_ in ents:
+                    if k == 'Version' and v.strip() != self.VERSION:
+                        raise ConfigError("Unrecognized descriptor version: %s"
+                                          % v.strip())
+
+    def validate(self):
+        sec = self['Directory-Info']
+        if sec['Status'] not in ("consensus", "vote"):
+            raise ConfigError("Unrecognized 'status' in directory")
+        if sec['Valid-Until'] <= direc['Valid-After']:
+            raise ConfigError("Directory is never valid")
+
+        self.voters = []
+        for s in sec['Voting-Server']:
+            lst = s.split(" ",1)
+            if len(lst) != 2:
+                raise ConfigError("Missing URLBase or fingerprint in Voting-Server")
+            self.voters.append((mixminion.Config._parseBase64(lst[0]),
+                                lst[1].strip()))
+
+class _DirectorySignature(mixminion.Config._ConfigFile):
+    """Internal object: used to parse, validate, and store fields in a
+       directory's signature section.
+    """
+    ## Fields:
+    # sigStatus: None/0/1
+    _restrictFormat = 1
+    _restrictKeys = _restrictSections = 1
+    _syntax = {
+        "Signed-Directory" : {
+           "__SECTION__" : ( "REQUIRE", None, None ),
+           "Directory-Identity" : ( "REQUIRE", "publicKey", None ),
+           "Directory-Digest" : ( "REQUIRE", "base64", None ),
+           "Directory-Signature" : ( "REQUIRE", "base64", None ) } }
+
+    def __init__(self, string):
+        self.sigStatus = None
+        mixminion.Config._ConfigFile.__init__(self, string=contents)
+
+    def validate(self):
+        sec = self['Signed-Directory']
+        idKeyBytes = sec['Directory-Identity'].get_modulus_bytes() 
+        if not (2048 <= idKeyBytes*8 <= 4096):
+            raise ConfigError("Identity key length is out of range (%s bits)"
+                              % idKeyBits*8)
+        if len(sec['Directory-Digest']) != DIGEST_LEN:
+            raise ConfigError("Impossible digest length (%s)"%
+                              len(sec['Directory-Digest']))
+
+    def getDigest(self):
+        return self['Signed-Directory']['Directory-Digest']
+
+    def getKeyDigest(self):
+        return sha1(pk_encode_public_key(
+              self['Signed-Directory']['Directory-Identity']))
+
+    def checkSignature(self):
+        if self.sigStatus is not None:
+            return self.sigStatus
+        sec = self['Signed-Directory']
+        try:
+            r = mixminion.Crypto.pk_check_signature(sec['Directory-Signature'],
+                                                    sec['Directory-Identity'])
+        except mixminion.Crypto.CryptoError:
+            self.sigStatus = 0
+        else:
+            if r == sec['Directory-Digest']:
+                self.sigStatus = 1
+            else:
+                self.sigStatus = 0
+
+        return self.sigStatus
+
+
 #----------------------------------------------------------------------
 def getServerInfoDigest(info):
     """Calculate the digest of a server descriptor"""
@@ -745,3 +915,47 @@
 def _getDirectoryDigestImpl(directory, rsa=None):
     return _getDigestImpl(directory, _dir_special_line_re,
                           "DirectoryDigest", "DirectorySignature", rsa)
+
+def _getMultisignedDirectoryDigest(directory):
+    try:
+        if directory.startswith("[Directory-Info]"):
+            idx = 0
+        else:
+            idx = directory.index("\n[Directory-Info]\n")+1
+    except IndexError:
+        raise ConfigError("No [Directory-Info] found.")
+    digest = sha1(directory[idx:])
+    return digest
+
+def _splitMultisignedDirectory(directory):
+    """DOCDOC -- returns [signature...],info,[server...]"""
+    sigs = []
+
+    # Extract all signatures.
+    while directory.startswith("[Directory-Signature]\n"):
+        eos = directory.find("\n[Directory-Signature]\n", 1)
+        if eos < 0:
+            eos = directory.find("\n[Directory-Info]\n")
+        if eos < 0:
+            raise ConfigError("Missing [Directory-Info] section")
+
+        sigs.append(directory[:eos+1])
+        directory = directory[eos+1:]
+
+    if not directory.startswith("[Directory-Info]\n"):
+        raise ConfigError("Missing [Directory-Info] section")
+
+    # Split the rest by [Server]; the first entry will be a [Directory-Info]
+    servers = []
+    while 1:
+        eos = directory.find("\n[Server]\n")
+        if eos < 0:
+            servers.append(directory)
+            break
+        else:
+            servers.append(directory[:eos+1])
+            directory = directory[eos+1:]
+
+    info = servers[0]
+    del servers[0]
+    return sigs, info, servers

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.202
retrieving revision 1.203
diff -u -d -r1.202 -r1.203
--- test.py	7 Aug 2004 14:08:23 -0000	1.202
+++ test.py	24 Aug 2004 22:16:08 -0000	1.203
@@ -69,6 +69,7 @@
 import mixminion.server.ServerMain
 import mixminion.directory.ServerList
 import mixminion.directory.ServerInbox
+import mixminion.directory.DirFormats
 import mixminion.directory.DirMain
 import mixminion.directory.Directory
 from mixminion.Common import *
@@ -4769,7 +4770,7 @@
         info = mixminion.ServerInfo.ServerInfo(string=inf)
         eq = self.assertEquals
         eq(info['Server']['Descriptor-Version'], "0.2")
-        eq(info['Incoming/MMTP']['IP'], "192.168.0.1")
+        self.assert_(stringContains(inf, "\nIP: 192.168.0.1\n"))
         eq(info['Incoming/MMTP']['Hostname'], "Theserver")
         eq(info['Server']['Nickname'], "The-Server")
         self.failUnless(0 <= time.time()-info['Server']['Published'] <= 120)
@@ -4779,7 +4780,6 @@
            10*24*60*60)
         eq(info['Server']['Contact'], "a@b.c")
         eq(info['Server']['Software'], "Mixminion %s"%mixminion.__version__)
-        eq(info['Server']['Packet-Formats'], None)
         eq(info['Server']['Packet-Versions'], ["0.3"])
         eq(info['Server']['Comments'],
            "This is a test of the emergency broadcast system")
@@ -4914,8 +4914,8 @@
             os.path.join(keydir, "mix.key"))
         eq(packetKey.get_public_key(),
            info['Server']['Packet-Key'].get_public_key())
-        eq(Crypto.sha1(identity.encode_key(1)),
-           info['Incoming/MMTP']['Key-Digest'])
+#        eq(Crypto.sha1(identity.encode_key(1)),
+#           info['Incoming/MMTP']['Key-Digest'])
 
         # Now check the digest and signature
         identityPK = info['Server']['Identity']
@@ -4936,7 +4936,6 @@
         eq(info.isValidated(), loaded.isValidated())
 
         # Other functionality.
-        eq(info.getIPV4Info(), IPV4Info("192.168.0.1", 48099, info.getKeyDigest()))
         eq(info.getMMTPHostInfo(), MMTPHostInfo("Theserver", 48099, info.getKeyDigest()))
         eq(info.getMMTPHostInfo(), info.getRoutingInfo())
         self.assert_(info.canStartAt())
@@ -5053,7 +5052,6 @@
         self.assertEquals(key3.getPacketKey().get_public_key(),
                           key2.getPacketKey().get_public_key())
         eq(info3['Incoming/MMTP']['Hostname'], "Theserver3")
-        eq(info3['Incoming/MMTP']['IP'], "192.168.100.3")
         self.assert_('smtp' in info3.getCaps())
 
         # Check routing info
@@ -5071,7 +5069,6 @@
             undoReplacedAttributes()
         info3 = key3.getServerDescriptor()
         eq(info3['Incoming/MMTP']['Hostname'], "Theserver4")
-        eq(info3['Incoming/MMTP']['IP'], "192.168.100.4")
 
     def test_directory(self):
         eq = self.assertEquals
@@ -6257,16 +6254,20 @@
 
         # Does 'regenerate' work?
         cfg2 = SERVERCFG%{'home':_FAKE_HOME}
-        cfg2 = cfg2.replace("10.0.0.1", "10.0.0.2")
+        cfg2 = cfg2.replace("Theserver5", "Theserver999")
         config2 = mixminion.server.ServerConfig.ServerConfig(string=cfg2)
 
         self.assert_(curKey.isPublished())
         curKey.load()
         key0 = curKey.getPacketKeyID()
-        curKey.regenerateServerDescriptor(config2, keyring.getIdentityKey())
+        try:
+            overrideDNS({"Theserver999" : '1.0.0.1'})
+            curKey.regenerateServerDescriptor(config2, keyring.getIdentityKey())
+        finally:
+            undoReplacedAttributes()
         self.assert_(not curKey.isPublished())
         inf = curKey.getServerDescriptor()
-        self.assertEquals(inf['Incoming/MMTP']['IP'], "10.0.0.2")
+        self.assertEquals(inf['Incoming/MMTP']['Hostname'], "Theserver999")
         curKey.load()
         self.assertEquals(key0, curKey.getPacketKeyID())
 
@@ -7757,7 +7758,8 @@
     tc = loader.loadTestsFromTestCase
 
     if 0:
-        suite.addTest(tc(ClientDirectoryTests))
+        suite.addTest(tc(ServerKeysTests))
+        suite.addTest(tc(ServerInfoTests))
         return suite
     testClasses = [MiscTests,
                    MinionlibCryptoTests,