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

[minion-cvs] Tweaks to make server need less babysitting; patch for ...



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv11825/lib/mixminion

Modified Files:
	BuildMessage.py ClientMain.py Config.py Crypto.py HashLog.py 
	MMTPClient.py MMTPServer.py Modules.py Packet.py 
	PacketHandler.py Queue.py ServerInfo.py ServerMain.py test.py 
Log Message:
Tweaks to make server need less babysitting; patch for attack; work on client.

PacketHandler.py, BuildMessage.py, Crypto.py, test.py:
	Patch for George's attack of august 15

ClientMain.py:
	Untested beginnings of directory store

Config.py:
	Check config files for bogus characters

Crypyo.py:
	Make RSA public keys pickleable

HashLog.py:
	Remove debugging output

MMTPClient.py, BuildMessage.py, MMTPServer.py, Packet.py, Modules.py,
   ServerInfo.py:
	Change version numbers from "1.0" to "0.1" so we can obsolete this
	version of the code on a real 1.0 release.

Queues.py:
	Add note about the Cottrell algorithm not being the one I implemented
	now.

ServerMain.py:
	Clean up hash logs on exit.




Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- BuildMessage.py	31 Aug 2002 04:12:36 -0000	1.12
+++ BuildMessage.py	10 Sep 2002 14:45:29 -0000	1.13
@@ -276,6 +276,11 @@
     key = Crypto.lioness_keys_from_payload(payload)
     header2 = Crypto.lioness_encrypt(header2, key)
 
+    # Encrypt payload with a hash of header2.  Now tagging either will make
+    # both unrecoverable.
+    key = Crypto.lioness_keys_from_header(header2)
+    payload = Crypto.lioness_encrypt(payload, key)
+
     # Copy secrets1 so we don't reverse the original.
     secrets1 = secrets1[:]
 

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- ClientMain.py	31 Aug 2002 04:12:36 -0000	1.1
+++ ClientMain.py	10 Sep 2002 14:45:30 -0000	1.2
@@ -10,6 +10,21 @@
    XXXX at least one configuration works.
    """
 
+
+# The client needs to store:
+#      - config
+#      - keys for pending SURBs
+#      - server directory
+#          (Have dir of files from which to reconstruct a shelf of cached
+#           info.)
+#          (Don't *name* files in dir; or at least, don't make their names
+#           magic.  Files can be: ServerInfos, ServerDirectories, or 'fake'
+#           directories.  Each server can have any number of virtual or 
+#           official tags.  Users should use the CLI to add/remove entries from
+#           dir.)
+#      - Per-systemm directory location is a neat idea, but individual users
+#        must check signature.  That's a way better idea for later.
+
 import os
 import getopt
 import sys
@@ -17,11 +32,101 @@
 import bisect
 
 import mixminion.Crypto
-from mixminion.Common import getLog, floorDiv
+from mixminion.Common import getLog, floorDiv, createPrivateDir
 import mixminion.Config
 import mixminion.BuildMessage
 import mixminion.MMTPClient
 import mixminion.Modules
+
+class DirectoryCache:
+    """Holds a set of directories and serverinfo objects persistently.
+
+       FFFF This should actually cache the nickname and liveness information 
+       FFFF rather than parsing and reparsing it each time.  Of course, we'll
+       FFFF want to re-do it entirely once we have directory support, so it
+       FFFF doesn't matter so much right now.
+       """
+    def __init__(self, dirname):
+	createPrivateDir(dirname)
+	self.dirname = dirname
+	self.servers = None
+
+    def load(self, forceReload=0):
+	if not (self.servers is None or forceReload):
+	    return
+	now = time.time()
+	self.servers = {}
+	self.allServers = []
+	self.highest_num = -1
+	for fn in os.listdir(self.dirname):
+	    if not fn.startswith("si"):
+		continue
+	    n = int(fn[2:])
+	    if n > self.highest_num:
+		self.highest_num = n
+	    info = ServerInfo(fname=os.path.join(self.dirname, fn),
+			      assumeValid=1)
+	    nickname = info['Server']['Nickname']
+	    if info['Server']['Valid-Until'] < now:
+		getLog().info("Removing expired descriptor for %s",
+			      nickname)
+		os.unlink(os.path.join(dirname, fn))
+		continue
+	    self.allServers.append(info)
+	    if self.servers.has_key(nickname):
+		self.servers[nickname].append(info)
+	    else:
+		self.servers[nickname] = info
+
+    def getCurrentServer(nickname, when=None):
+	if when is None:
+	    when = time.time()
+	for info in self.servers[nickname]:
+	    #XXXX fail on DNE
+	    server = info['Server']
+	    if server['Valid-After'] <= now <= server['Valid-Until']:
+		return info
+	#XXXX fail on DNE
+	return None
+
+    def importServerInfo(self, fname, force=1):
+	self.load()
+	f = open(fname)
+	contents = f.read()
+	f.close()
+	info = ServerInfo(string=contents, assumeValid=0)
+	now = time.time()
+	if info['Server']['Valid-Until'] < now:
+	    getLog().error("Not importing descriptor %s: already expired", 
+			   fname)
+	    return 0
+	nickname = info['Server']['Nickname']
+	identity_pk = info['Server']['Identity'].get_public_key()
+	if self.servers.has_key(nickname):
+	    other = self.servers[nickname][0]
+	    if other['Server']['Identity'].get_public_key() != identity_pk:
+		getLog().error("Possible spoofing: that's not the public key I remember for %s", nickname)
+		if not force:
+		    getLog().error("I'm not going to import it.")
+		    return 0
+		else:
+		    getLog().error("... importing anyway.")
+	
+	    self.servers[nickname].append(info)
+	else:
+	    self.servers[nickname] = info
+
+	self.allServers.append(info)
+
+	self.highest_num += 1
+	fname_new = "si%d" % self.highest_num
+	f = os.fdopen(os.open(os.path.join(self.dirname, fname_name),
+			      os.O_CREAT|os.O_EXCL, 0600),
+		      'w')
+	f.write(contents)
+	f.close()
+
+
 
 def sendTestMessage(servers1, servers2):
     assert len(servers1)

Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- Config.py	31 Aug 2002 04:12:36 -0000	1.14
+++ Config.py	10 Sep 2002 14:45:30 -0000	1.15
@@ -59,6 +59,11 @@
 import mixminion.Packet
 import mixminion.Crypto
 
+# String with all characters 0..255; used for str.translate
+_ALLCHARS = "".join(map(chr, range(256)))
+# String with all printing ascii characters.
+_GOODCHARS = "".join(map(chr, range(0x07,0x0e)+range(0x20,0x80)))
+
 class ConfigError(MixError):
     """Thrown when an error is found in a configuration file."""
     pass
@@ -213,11 +218,10 @@
 
         raise ConfigError("No match found for command %r" %cmd)
 
-_allChars = "".join(map(chr, range(256)))
 def _parseBase64(s,_hexmode=0):
     """Validation function.  Converts a base-64 encoded config value into
        its original. Raises ConfigError on failure."""
-    s = s.translate(_allChars, " \t\v\n")
+    s = s.translate(_ALLCHARS, " \t\v\n")
     try:
 	if _hexmode:
 	    return binascii.a2b_hex(s)
@@ -327,6 +331,10 @@
     curSection = None
     lineno = 0
     lastKey = None
+
+    badchars = contents.translate(_ALLCHARS, _GOODCHARS)
+    if badchars:
+	raise ConfigError("Invalid characters in file: %r", badchars)
 
     fileLines = contents.split("\n")
     if fileLines[-1] == '':

Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.19
retrieving revision 1.20
diff -u -d -r1.19 -r1.20
--- Crypto.py	29 Aug 2002 03:30:21 -0000	1.19
+++ Crypto.py	10 Sep 2002 14:45:30 -0000	1.20
@@ -11,6 +11,7 @@
 import os
 import sys
 import stat
+import copy_reg
 from types import StringType
 
 import mixminion._minionlib as _ml
@@ -47,7 +48,7 @@
     except:
         raise MixFatalError("Error initializing entropy source")
     openssl_seed(40)
-
+    
 def sha1(s):
     """Return the SHA1 hash of its argument"""
     return _ml.sha1(s)
@@ -219,6 +220,14 @@
     f.close()
     return rsa
     
+def _pickle_rsa(rsa):
+    return _ml.rsa_make_public_key, rsa.get_public_key()
+
+# Register this function to make RSA keys pickleable.  Note that we only
+# pickle the public part of an RSA key; for long-term storage of private
+# keys, you should use PEM so we can support encryption.
+copy_reg.pickle(_ml.RSA, _pickle_rsa, _ml.rsa_make_public_key)
+
 #----------------------------------------------------------------------
 # OAEP Functionality
 #
@@ -328,6 +337,9 @@
 # Used to LIONESS-encrypt the header at the swap point.
 HIDE_HEADER_MODE = "HIDE HEADER"
 
+# Used to LIONESS-encrypt the payload at the swap point.
+HIDE_PAYLOAD_MODE = "HIDE PAYLOAD"
+
 # Used to remember whether we've seen a secret before
 REPLAY_PREVENTION_MODE = "REPLAY PREVENTION"
 
@@ -365,6 +377,12 @@
        at the swap point.''' 
     digest = sha1(payload)
     return Keyset(digest).getLionessKeys(HIDE_HEADER_MODE)
+
+def lioness_keys_from_header(header2):
+    '''Given the off-header, returns the LIONESS keys to encrypt the payload
+       at the swap point.''' 
+    digest = sha1(header2)
+    return Keyset(digest).getLionessKeys(HIDE_PAYLOAD_MODE)
 
 #---------------------------------------------------------------------
 # Random number generators

Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/HashLog.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- HashLog.py	31 Aug 2002 04:12:36 -0000	1.10
+++ HashLog.py	10 Sep 2002 14:45:30 -0000	1.11
@@ -13,6 +13,9 @@
 
 # FFFF Mechanism to force a different default db module.
 
+# FFFF Journaling for dbs that don't recover from catastrophic failure during
+# FFFF writes.
+
 class HashLog:
     """A HashLog is a file containing a list of message digests that we've
        already processed.
@@ -40,7 +43,6 @@
            'keyid'."""
         parent = os.path.split(filename)[0]
 	createPrivateDir(parent)
-	print filename
         self.log = anydbm.open(filename, 'c')
         if isinstance(self.log, dumbdbm._Database):
             getLog().warn("Warning: logging packet digests to a flat file.")

Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- MMTPClient.py	25 Aug 2002 05:58:02 -0000	1.8
+++ MMTPClient.py	10 Sep 2002 14:45:30 -0000	1.9
@@ -48,10 +48,12 @@
         
         ####
         # Protocol negotiation
-        # For now, we only support 1.0
-        self.tls.write("MMTP 1.0\r\n")
-        inp = self.tls.read(len("PROTOCOL 1.0\r\n"))
-        if inp != "MMTP 1.0\r\n":
+        # For now, we only support 1.0, but we call it 0.1 so we can
+	# change our mind between now and a release candidate, and so we
+	# can obsolete betas come release time.
+        self.tls.write("MMTP 0.1\r\n")
+        inp = self.tls.read(len("MMTP 0.1\r\n"))
+        if inp != "MMTP 0.1\r\n":
             raise MixProtocolError("Protocol negotiation failed")
         
     def sendPacket(self, packet):

Index: MMTPServer.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPServer.py,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- MMTPServer.py	31 Aug 2002 04:12:36 -0000	1.16
+++ MMTPServer.py	10 Sep 2002 14:45:30 -0000	1.17
@@ -397,7 +397,7 @@
 	pass
     
 #----------------------------------------------------------------------
-PROTOCOL_STRING      = "MMTP 1.0\r\n"
+PROTOCOL_STRING      = "MMTP 0.1\r\n"
 PROTOCOL_RE = re.compile("MMTP ([^\s\r\n]+)\r\n")
 SEND_CONTROL         = "SEND\r\n"
 JUNK_CONTROL         = "JUNK\r\n"
@@ -434,7 +434,7 @@
             warn("Bad protocol list.  Closing connection.")
             self.shutdown(err=1)
         protocols = m.group(1).split(",")
-        if "1.0" not in protocols:
+        if "0.1" not in protocols:
             warn("Unsupported protocol list.  Closing connection.")
             self.shutdown(err=1); return
         else:

Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Modules.py,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -d -r1.11 -r1.12
--- Modules.py	31 Aug 2002 04:12:36 -0000	1.11
+++ Modules.py	10 Sep 2002 14:45:30 -0000	1.12
@@ -366,7 +366,7 @@
     def getServerInfoBlock(self):
         return """\
                   [Delivery/MBOX]
-                  Version: 1.0
+                  Version: 0.1
                """
     
     def getName(self):

Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- Packet.py	21 Aug 2002 19:09:48 -0000	1.8
+++ Packet.py	10 Sep 2002 14:45:30 -0000	1.9
@@ -246,8 +246,9 @@
     if magic != 'SURB':
         raise ParseError("Misformatted reply block")
 
-    if major != 0x01 or minor != 0x00:
-        raise ParseError("Unrecognized version on reply block")
+    if major != 0x00 or minor != 0x01:
+        raise ParseError("Unrecognized version on reply block %s.%s",
+			 major,minor)
 
     ri = s[MIN_RB_LEN:]
     if len(ri) != rlen:
@@ -269,7 +270,7 @@
     def pack(self):
         """Returns the external representation of this reply block"""
         return struct.pack(RB_UNPACK_PATTERN,
-                           "SURB", 0x01, 0x00, self.timestamp,
+                           "SURB", 0x00, 0x01, self.timestamp,
                            self.header, len(self.routingInfo),
                            self.routingType)+self.routingInfo
 

Index: PacketHandler.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/PacketHandler.py,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- PacketHandler.py	31 Aug 2002 04:12:36 -0000	1.7
+++ PacketHandler.py	10 Sep 2002 14:45:30 -0000	1.8
@@ -42,6 +42,16 @@
             self.privatekey = (privatekey, )
             self.hashlog = (hashlog, )
 
+    def syncLogs(self):
+	"""Sync all this PacketHandler's hashlogs."""
+	for h in self.hashlog:
+	    h.sync()
+
+    def close(self):
+	"""Close all this PacketHandler's hashlogs."""
+	for h in self.hashlog:
+	    h.close()
+
     def processMessage(self, msg):
         """Given a 32K mixminion message, processes it completely.
 
@@ -156,11 +166,16 @@
         header2 = Crypto.lioness_decrypt(msg.header2,
                            keys.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE))
 
-        # If we're the swap node, decrypt header2 with a hash of the
-        # payload, and swap the headers.
+        # If we're the swap node, (1) decrypt the payload with a hash of 
+	# header2... (2) decrypt header2 with a hash of the payload...
+	# (3) and swap the headers.
         if rt == Modules.SWAP_FWD_TYPE:
+	    hkey = Crypto.lioness_keys_from_header(header2)
+	    payload = Crypto.lioness_decrypt(payload, hkey)
+
             hkey = Crypto.lioness_keys_from_payload(payload)
             header2 = Crypto.lioness_decrypt(header2, hkey)
+
             header1, header2 = header2, header1
 
         # Build the address object for the next hop

Index: Queue.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Queue.py,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- Queue.py	29 Aug 2002 03:30:21 -0000	1.16
+++ Queue.py	10 Sep 2002 14:45:30 -0000	1.17
@@ -382,6 +382,9 @@
 	self.sendRate = 1.0 - retainRate
 
     def getBatch(self):
+	# XXXX This is not the real cottrell algorithm.  Once somebody
+	# XXXX has explained to me what is going on here, I will implement
+	# XXXX the real one. -NM
 	pool = self.count()
 	if pool <= self.threshold:
 	    return []

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- ServerInfo.py	29 Aug 2002 03:30:21 -0000	1.13
+++ ServerInfo.py	10 Sep 2002 14:45:31 -0000	1.14
@@ -15,7 +15,7 @@
 import base64
 import socket
 
-from mixminion.Common import createPrivateDir
+from mixminion.Common import createPrivateDir, getLog
 from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
 from mixminion.Packet import IPV4Info
 import mixminion.Config
@@ -85,8 +85,9 @@
 	####
 	# Check 'Server' section.
 	server = sections['Server']
-	if server['Descriptor-Version'] != '1.0':
-	    raise ConfigError("Unrecognized descriptor version")
+	if server['Descriptor-Version'] != '0.1':
+	    raise ConfigError("Unrecognized descriptor version %r",
+			      server['Descriptor-Version'])
 	if len(server['Nickname']) > MAX_NICKNAME:
 	    raise ConfigError("Nickname too long")
 	identityKey = server['Identity']
@@ -245,6 +246,7 @@
         nickname = socket.gethostname()
         if not nickname or nickname.lower().startswith("localhost"):
             nickname = config['Incoming/MMTP'].get('IP', "<Unknown host>")
+	getLog().warn("No nickname given: defaulting to %r", nickname)
     contact = config['Server']['Contact-Email']
     comments = config['Server']['Comments']
     if not validAt:
@@ -280,7 +282,7 @@
 	
     info = """\
         [Server]
-	Descriptor-Version: 1.0
+	Descriptor-Version: 0.1
         IP: %(IP)s
         Nickname: %(Nickname)s
 	Identity: %(Identity)s
@@ -299,10 +301,10 @@
     if config["Incoming/MMTP"].get("Enabled", 0):
 	info += """\
             [Incoming/MMTP]
-            Version: 1.0
+            Version: 0.1
             Port: %(Port)s
 	    Key-Digest: %(KeyID)s
-	    Protocols: 1.0
+	    Protocols: 0.1
             """ % fields
         for k,v in config.getSectionItems("Incoming/MMTP"):
             if k not in ("Allow", "Deny"):
@@ -312,8 +314,8 @@
     if config["Outgoing/MMTP"].get("Enabled", 0):
 	info += """\
             [Outgoing/MMTP]
-	    Version: 1.0
-            Protocols: 1.0
+	    Version: 0.1
+            Protocols: 0.1
             """
         for k,v in config.getSectionItems("Outgoing/MMTP"):
             if k not in ("Allow", "Deny"):

Index: ServerMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerMain.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- ServerMain.py	31 Aug 2002 04:12:36 -0000	1.8
+++ ServerMain.py	10 Sep 2002 14:45:31 -0000	1.9
@@ -439,6 +439,7 @@
 		self.mmtpServer.process(1)
 		self.incomingQueue.sendReadyMessages()
 	    
+	    self.packetHandler.syncLogs()
 	    getLog().trace("Mix interval elapsed")
 	    self.mixPool.mix()
 	    self.outgoingQueue.sendReadyMessages()
@@ -454,6 +455,10 @@
 		self.moduleManager.cleanQueues()
 		nextShred = now + 6000
 
+    def close(self):
+	"""Release all resources; close all files."""
+	self.packetHandler.close()
+
 #----------------------------------------------------------------------
 
 def usageAndExit(cmd):
@@ -510,7 +515,7 @@
     except:
 	getLog().fatal_exc(sys.exc_info(),"Exception while running server")
     getLog().info("Server shutting down")
-    
+    server.close()
     
     sys.exit(0)
 

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -d -r1.26 -r1.27
--- test.py	31 Aug 2002 04:12:36 -0000	1.26
+++ test.py	10 Sep 2002 14:45:31 -0000	1.27
@@ -383,6 +383,12 @@
         decoded = pk_decode_private_key(encoded)
         eq(msg, pk_decrypt(pk_encrypt(msg, pub512),decoded))
 
+	# Test pickling
+	init_crypto()
+	s = cPickle.dumps(k512)
+	self.assertEquals(cPickle.loads(s).get_public_key(), 
+			  k512.get_public_key())
+
     def test_trng(self):
         # Make sure that the true rng is at least superficially ok.
         self.assertNotEquals(trng(40), trng(40))
@@ -632,7 +638,7 @@
             self.assertEquals(inf.pack(), ri)
 
     def test_replyblock(self):
-        r = ("SURB\x01\x00"+"\x00\x00\x00\x00"+("Z"*2048)+"\x00\x0A"+"\x00\x01"
+        r = ("SURB\x00\x01"+"\x00\x00\x00\x00"+("Z"*2048)+"\x00\x0A"+"\x00\x01"
              +("F"*10))
         rb = parseReplyBlock(r)
         self.assertEquals(rb.timestamp, 0)
@@ -913,6 +919,9 @@
                 payload = lioness_decrypt(payload, pkey)
 
             if path is secrets1:
+		swapkey = mixminion.Crypto.lioness_keys_from_header(head2)
+		payload = lioness_decrypt(payload, swapkey)
+
                 swapkey = mixminion.Crypto.lioness_keys_from_payload(payload)
                 head2 = lioness_decrypt(head2, swapkey)
 
@@ -933,6 +942,10 @@
             pkey = ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE)
             head2 = lioness_decrypt(head2, hkey)
             payload = lioness_decrypt(payload, pkey)
+
+        swapkey = mixminion.Crypto.lioness_keys_from_header(head2)
+        payload = lioness_decrypt(payload, swapkey)
+
         swapkey = mixminion.Crypto.lioness_keys_from_payload(payload)
         head2 = lioness_decrypt(head2, swapkey)
 
@@ -961,6 +974,7 @@
             ks = Keyset(s)
             p = lioness_decrypt(p,ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
             h2 = lioness_decrypt(h2,ks.getLionessKeys(HEADER_ENCRYPT_MODE))
+	p = lioness_decrypt(p,mixminion.Crypto.lioness_keys_from_header(h2))
         h2 = lioness_decrypt(h2,mixminion.Crypto.lioness_keys_from_payload(p))
 
         sec = self.do_header_test(h2, *header_info_2)
@@ -2120,6 +2134,7 @@
 PublicKeyLifetime: 10 days
 EncryptPrivateKey: no
 Mode: relay
+Nickname: fred-the-bunny
 """
 
 _IDENTITY_KEY = None
@@ -2150,7 +2165,7 @@
                                                                    d)
         info = mixminion.ServerInfo.ServerInfo(string=inf)
         eq = self.assertEquals
-        eq(info['Server']['Descriptor-Version'], "1.0")
+        eq(info['Server']['Descriptor-Version'], "0.1")
         eq(info['Server']['IP'], "192.168.0.1")
         eq(info['Server']['Nickname'], "The Server")
         self.failUnless(0 <= time.time()-info['Server']['Published'] <= 120)
@@ -2162,11 +2177,11 @@
         eq(info['Server']['Comments'],
            "This is a test of the emergency broadcast system")
         
-        eq(info['Incoming/MMTP']['Version'], "1.0")
+        eq(info['Incoming/MMTP']['Version'], "0.1")
         eq(info['Incoming/MMTP']['Port'], 48099)
-        eq(info['Incoming/MMTP']['Protocols'], "1.0")
-        eq(info['Outgoing/MMTP']['Version'], "1.0")
-        eq(info['Outgoing/MMTP']['Protocols'], "1.0")
+        eq(info['Incoming/MMTP']['Protocols'], "0.1")
+        eq(info['Outgoing/MMTP']['Version'], "0.1")
+        eq(info['Outgoing/MMTP']['Protocols'], "0.1")
         eq(info['Incoming/MMTP']['Allow'], [("192.168.0.16", "255.255.255.255",
                                             1,1024),
                                            ("0.0.0.0", "0.0.0.0",
@@ -2174,7 +2189,7 @@
         eq(info['Incoming/MMTP']['Deny'], [("192.168.0.16", "255.255.255.255",
                                             0,65535),
                                            ])
-        eq(info['Delivery/MBOX']['Version'], "1.0")
+        eq(info['Delivery/MBOX']['Version'], "0.1")
 
         # Now make sure everything was saved properly
         keydir = os.path.join(d, "key_key1")
@@ -2402,6 +2417,7 @@
 PublicKeyLifetime: 10 days
 IdentityKeyBits: 2048
 EncryptPrivateKey: no
+Nickname: mac-the-knife
 """
 
 _FAKE_HOME = None