[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Last round (I hope) of code tweaks before 0.0.1. Only ...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv2678/lib/mixminion
Modified Files:
BuildMessage.py ClientMain.py Common.py Config.py Crypto.py
MMTPClient.py Main.py Packet.py ServerInfo.py __init__.py
benchmark.py test.py testSupport.py
Log Message:
Last round (I hope) of code tweaks before 0.0.1. Only system tests and docs remain!
Refactoring:
- Move _base64 from ServerInfo to formatBase64 in Common
- Finish separating server code.
- Move ServerConfig to new file
- Move Server key mgt to new file
- Don't warn on missing overwrite; we now override.
- Minimize calls to true RNG
Everywhere:
- Remove extraneous whitespace
- Wrap overlong lines
- Remove extraneous import statements
- Give all modules sane __all__ fields
- Make Pychecker pleased.
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- BuildMessage.py 11 Dec 2002 05:53:32 -0000 1.21
+++ BuildMessage.py 12 Dec 2002 19:56:46 -0000 1.22
@@ -3,14 +3,14 @@
"""mixminion.BuildMessage
- Code to construct messages and reply blocks, and to decode received
+ Code to construct messages and reply blocks, and to decode received
message payloads."""
import zlib
import operator
+import mixminion.Crypto as Crypto
from mixminion.Packet import *
from mixminion.Common import MixError, MixFatalError, LOG
-import mixminion.Crypto as Crypto
__all__ = ['buildForwardMessage', 'buildEncryptedMessage', 'buildReplyMessage',
'buildReplyBlock', 'decodePayload' ]
@@ -29,7 +29,8 @@
Neither path1 nor path2 may be empty.
"""
- if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+ if paddingPRNG is None:
+ paddingPRNG = Crypto.getCommonPRNG()
assert path1 and path2
LOG.debug("Encoding forward message for %s-byte payload",len(payload))
@@ -40,7 +41,7 @@
# Compress, pad, and checksum the payload.
payload = _encodePayload(payload, 0, paddingPRNG)
-
+
# Choose a random decoding tag.
tag = _getRandomTag(paddingPRNG)
exitInfo = tag + exitInfo
@@ -60,7 +61,8 @@
paddingPRNG: random number generator used to generate padding.
If None, a new PRNG is initialized.
"""
- if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+ if paddingPRNG is None:
+ paddingPRNG = Crypto.getCommonPRNG()
if secretRNG is None: secretRNG = paddingPRNG
LOG.debug("Encoding encrypted forward message for %s-byte payload",
@@ -86,7 +88,7 @@
lionessPart = payload[rsaDataLen:]
# RSA encryption: To avoid leaking information about our RSA modulus,
- # we keep trying to encrypt until the MSBit of our encrypted value is
+ # we keep trying to encrypt until the MSBit of our encrypted value is
# zero.
while 1:
encrypted = Crypto.pk_encrypt(rsaPart, key)
@@ -111,7 +113,8 @@
"""Build a message using a reply block. 'path1' is a sequence of
ServerInfo for the nodes on the first leg of the path.
"""
- if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
+ if paddingPRNG is None:
+ paddingPRNG = Crypto.getCommonPRNG()
LOG.debug("Encoding reply message for %s-byte payload",
len(payload))
@@ -130,7 +133,7 @@
return _buildMessage(payload, None, None,
path1=path1, path2=replyBlock)
-def _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime=0,
+def _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime=0,
secretPRNG=None, tag=None):
"""Helper function: makes a reply block, given a tag and a PRNG to
generate secrets. Returns a 3-tuple containing (1) a
@@ -149,11 +152,11 @@
is generated.
"""
if secretPRNG is None:
- secretPRNG = Crypto.AESCounterPRNG()
+ secretPRNG = Crypto.getCommonPRNG()
LOG.debug("Building reply block for path %s",
[s.getNickname() for s in path])
- LOG.debug(" Delivering to %04x:%r", exitType, exitInfo)
+ LOG.debug(" Delivering to %04x:%r", exitType, exitInfo)
# The message is encrypted first by the end-to-end key, then by
# each of the path keys in order. We need to reverse these steps, so we
@@ -168,7 +171,7 @@
tag = _getRandomTag(secretPRNG)
header = _buildHeader(path, headerSecrets, exitType, tag+exitInfo,
- paddingPRNG=Crypto.AESCounterPRNG())
+ paddingPRNG=Crypto.getCommonPRNG())
return ReplyBlock(header, expiryTime,
SWAP_FWD_TYPE,
@@ -181,7 +184,7 @@
reply-message recipient to remember a list of secrets.
Instead, all secrets are generated from an AES counter-mode
stream, and the seed for the stream is stored in the 'tag'
- field of the final block's routing info. (See the spec for more
+ field of the final block's routing info. (See the spec for more
info).
path: a list of ServerInfo objects
@@ -190,17 +193,18 @@
NOTE: We used to allow another kind of 'non-state-carrying' reply
block that stored its secrets on disk, and used an arbitrary tag to
- determine
+ determine
"""
- if secretRNG is None: secretRNG = Crypto.AESCounterPRNG()
+ if secretRNG is None:
+ secretRNG = Crypto.getCommonPRNG()
# We need to pick the seed to generate our keys. To make the decoding
# step a little faster, we find a seed such that H(seed|userKey|"Validate")
# ends with 0. This way, we can detect whether we really have a reply
# message with 99.6% probability. (Otherwise, we'd need to repeatedly
- # lioness-decrypt the payload in order to see whether the message was
+ # lioness-decrypt the payload in order to see whether the message was
# a reply.)
-
+
# XXXX D'oh! This enables an offline password guessing attack for
# XXXX anybody who sees multiple tags. We need to make sure that userKey
# XXXX is stored on disk, and isn't a password. This needs more thought.
@@ -211,22 +215,22 @@
prng = Crypto.AESCounterPRNG(Crypto.sha1(seed+userKey+"Generate")[:16])
- return _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng,
+ return _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng,
seed)[0]
#----------------------------------------------------------------------
# MESSAGE DECODING
-def decodePayload(payload, tag, key=None,
+def decodePayload(payload, tag, key=None,
#storedKeys=None, # 'Stateful' reply blocks are disabled.
userKey=None):
"""Given a 28K payload and a 20-byte decoding tag, attempt to decode and
- decompress the original message.
+ decompress the original message.
key: an RSA key to decode encrypted forward messages, or None
userKey: our encryption key for reply blocks, or None.
-
- If we can successfully decrypt the payload, we return it. If we
+
+ If we can successfully decrypt the payload, we return it. If we
might be able to decrypt the payload given more/different keys,
we return None. If the payload is corrupt, we raise MixError.
"""
@@ -299,7 +303,7 @@
k = Crypto.Keyset(rsaPart[:SECRET_LEN]).getLionessKeys(
Crypto.END_TO_END_ENCRYPT_MODE)
rest = rsaPart[SECRET_LEN:] + Crypto.lioness_decrypt(rest, k)
-
+
# ... and then, check the checksum and continue.
return _decodePayloadImpl(rest)
@@ -308,7 +312,7 @@
master secrets. If 'check' is true, then 'secerets' may be overlong.
Return values are the same as decodePayload.
[secrets must be in _reverse_ order]
- """
+ """
# Reverse the 'decrypt' operations of the reply mixes, and the initial
# 'decrypt' of the originating user...
for sec in secrets:
@@ -342,7 +346,7 @@
include the 20-byte decoding tag.)
path1: a sequence of ServerInfo objects, one for each node on
the first leg of the path.
- path2:
+ path2:
EITHER
a sequence of ServerInfo objects, one for each node
on the second leg of the path.
@@ -373,7 +377,7 @@
# Set up the random number generators.
if paddingPRNG is None:
- paddingPRNG = Crypto.AESCounterPRNG()
+ paddingPRNG = Crypto.getCommonPRNG()
if paranoia:
nHops = len(path1)
if path2: nHops += len(path2)
@@ -413,7 +417,7 @@
secrets: A list of 16-byte strings to use as master-secrets for
each of the subheaders.
exitType: The routing type for the last node in the header
- exitInfo: The routing info for the last node in the header.
+ exitInfo: The routing info for the last node in the header.
(Must include 20-byte decoding tag.)
paddingPRNG: A pseudo-random number generator to generate padding
"""
@@ -566,7 +570,7 @@
return chr(b) + rng.getBytes(TAG_LEN-1)
def _decodePayloadImpl(payload):
- """Helper: try to decode an encoded payload: checks only encoding,
+ """Helper: try to decode an encoded payload: checks only encoding,
not encryption."""
# Is the hash ok?
if not _checkPayload(payload):
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- ClientMain.py 11 Dec 2002 06:58:55 -0000 1.14
+++ ClientMain.py 12 Dec 2002 19:56:46 -0000 1.15
@@ -25,26 +25,26 @@
# - Per-system directory location is a neat idea, but individual users
# must check signature. That's a way better idea for later.
-import os
import getopt
+import os
import sys
import time
import types
-from mixminion.Common import LOG, floorDiv, createPrivateDir, MixError, \
- MixFatalError, isSMTPMailbox
-import mixminion.Crypto
import mixminion.BuildMessage
+import mixminion.Crypto
import mixminion.MMTPClient
-from mixminion.ServerInfo import ServerInfo
+from mixminion.Common import LOG, floorDiv, MixError, MixFatalError, \
+ createPrivateDir, isSMTPMailbox
from mixminion.Config import ClientConfig, ConfigError
+from mixminion.ServerInfo import ServerInfo
from mixminion.Packet import ParseError, parseMBOXInfo, parseSMTPInfo, \
MBOX_TYPE, SMTP_TYPE, DROP_TYPE
class TrivialKeystore:
"""This is a temporary keystore implementation until we get a working
directory server implementation.
-
+
The idea couldn't be simpler: we just keep a directory of files, each
containing a single server descriptor. We cache nothing; we validate
everything; we have no automatic path generation. Servers can be
@@ -57,11 +57,11 @@
## Fields:
# directory: path to the directory we scan for server descriptors.
# byNickname: a map from nickname to valid ServerInfo object.
- # byFilename: a map from filename within self.directory to valid
+ # byFilename: a map from filename within self.directory to valid
# ServerInfo object.
def __init__(self, directory, now=None):
"""Create a new TrivialKeystore to access the descriptors stored in
- directory. Selects descriptors that are valid at the time 'now',
+ directory. Selects descriptors that are valid at the time 'now',
or at the current time if 'now' is None."""
self.directory = directory
createPrivateDir(directory)
@@ -135,7 +135,7 @@
(name, e))
except ConfigError, e:
raise MixError("Couldn't parse descriptor %s: %s" %
- (name, e))
+ (name, e))
else:
return None
@@ -157,7 +157,7 @@
def getRandomServers(self, prng, n):
"""Returns a list of n different servers, in random order, according
- to prng. Raises MixError if not enough exist.
+ to prng. Raises MixError if not enough exist.
(This isn't actually used.)"""
vals = self.byNickname.values()
@@ -218,7 +218,7 @@
os.path.join(userdir,"servers"))
# Initialize PRNG
- self.prng = mixminion.Crypto.AESCounterPRNG()
+ self.prng = mixminion.Crypto.getCommonPRNG()
def sendForwardMessage(self, address, payload, path1, path2):
"""Generate and send a forward message.
@@ -233,14 +233,14 @@
self.sendMessages([message], firstHop)
def generateForwardMessage(self, address, payload, path1, path2):
- """Generate a forward message, but do not send it. Returns
+ """Generate a forward message, but do not send it. Returns
a tuple of (the message body, a ServerInfo for the first hop.)
address -- the results of a parseAddress call
payload -- the contents of the message to send
path1,path2 -- lists of servers or server names for the first and
second legs of the path, respectively. These are processed
- as described in TrivialKeystore.getServerInfo"""
+ as described in TrivialKeystore.getServerInfo"""
if not path1:
raise MixError("No servers in first leg of path")
if not path2:
@@ -265,7 +265,7 @@
else:
servers2.append(self.keystore.getServerInfo(lastHop))
msg = mixminion.BuildMessage.buildForwardMessage(
- payload, routingType, routingInfo, servers1, servers2,
+ payload, routingType, routingInfo, servers1, servers2,
self.prng)
return msg, servers1[0]
@@ -291,7 +291,7 @@
OR smtp:<email address>
OR <email address> (smtp is implicit)
OR drop
- OR 0x<routing type>:<routing info>
+ OR 0x<routing type>:<routing info>
"""
# ???? Should this should get refactored into clientmodules, or someplace?
if s.lower() == 'drop':
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.31
retrieving revision 1.32
diff -u -d -r1.31 -r1.32
--- Common.py 11 Dec 2002 05:53:33 -0000 1.31
+++ Common.py 12 Dec 2002 19:56:46 -0000 1.32
@@ -5,20 +5,24 @@
Common functionality and utility code for Mixminion"""
-__all__ = [ 'MixError', 'MixFatalError', 'onReset', 'onTerminate',
- 'installSignalHandlers', 'secureDelete', 'secureRename',
- 'ceilDiv', 'floorDiv', 'LOG', 'stringContains',
- 'formatDate', 'formatTime', 'isSMTPMailbox']
+__all__ = [ 'LOG', 'MixError', 'MixFatalError', 'MixProtocolError', 'ceilDiv',
+ 'checkPrivateDir', 'createPrivateDir', 'floorDiv', 'formatBase64',
+ 'formatDate', 'formatTime', 'installSignalHandlers',
+ 'isSMTPMailbox', 'mkgmtime', 'onReset', 'onTerminate',
+ 'previousMidnight', 'secureDelete', 'stringContains',
+ 'waitForChildren' ]
+import base64
+import calendar
import os
+import re
import signal
-import sys
-import time
import stat
-import re
import statvfs
+import sys
+import time
import traceback
-import calendar
+
from types import StringType
class MixError(Exception):
@@ -90,6 +94,10 @@
"""Remove all whitespace from s."""
return s.translate(_ALLCHARS, space)
+def formatBase64(s):
+ """Convert 's' to a one-line base-64 representation."""
+ return base64.encodestring(s).replace("\n", "")
+
#----------------------------------------------------------------------
def createPrivateDir(d, nocreate=0):
"""Create a directory, and all parent directories, checking permissions
@@ -107,7 +115,7 @@
def checkPrivateDir(d, recurse=1):
"""Return true iff d is a directory owned by this uid, set to mode
0700. All of d's parents must not be writable or owned by anybody but
- this uid and uid 0. If any of these conditions are unmet, raise
+ this uid and uid 0. If any of these conditions are unmet, raise
MixFatalErrror. Otherwise, return None."""
me = os.getuid()
@@ -138,7 +146,7 @@
mode = st[stat.ST_MODE]
owner = st[stat.ST_UID]
if owner not in (0, me):
- raise MixFatalError("Bad owner (uid=%s) on directory %s"
+ raise MixFatalError("Bad owner (uid=%s) on directory %s"
% (owner, d))
if (mode & 02) and not (mode & stat.S_ISVTX):
raise MixFatalError("Bad mode (%o) on directory %s" %(mode, d))
@@ -152,12 +160,12 @@
# Secure filesystem operations.
# A 'shred' command to overwrite and unlink files. It should accept an
-# arbitrary number of arguments. (If "---", we haven't configured the
+# arbitrary number of arguments. (If "---", we haven't configured the
# shred command. If None, we're using our internal implementation.)
_SHRED_CMD = "---"
# Tuple of options to be passed to the 'shred' command
_SHRED_OPTS = None
-
+
def configureShredCommand(conf):
"""Initialize the secure delete command from a given Config object.
If no object is provided, try some sane defaults."""
@@ -173,7 +181,7 @@
if os.path.exists("/usr/bin/shred"):
cmd, opts = "/usr/bin/shred", ["-uz", "-n0"]
else:
- LOG.warn("Files will not be securely deleted.")
+ # Use built-in _overwriteFile
cmd, opts = None, None
_SHRED_CMD, _SHRED_OPTS = cmd, opts
@@ -235,14 +243,14 @@
XXXX operation has the regrettable property that two shred commands
XXXX running in the same directory can sometimes get into a race.
XXXX The source to shred.c seems to imply that this is harmless, but
- XXXX let's try to avoid that, to be on the safe side.
+ XXXX let's try to avoid that, to be on the safe side.
"""
if _SHRED_CMD == "---":
configureShredCommand(None)
if fnames == []:
return
-
+
if isinstance(fnames, StringType):
fnames = [fnames]
@@ -251,7 +259,7 @@
_overwriteFile(f)
os.unlink(f)
return None
-
+
if blocking:
mode = os.P_WAIT
else:
@@ -288,7 +296,7 @@
to implement log rotation."""
if self.file is not None:
self.file.close()
- try:
+ try:
parent = os.path.split(self.fname)[0]
if not os.path.exists(parent):
createPrivateDir(parent)
@@ -304,13 +312,13 @@
if self.file is None:
return
print >> self.file, "%s [%s] %s" % (_logtime(), severity, message)
-
-class _ConsoleLogHandler:
+
+class _ConsoleLogHandler:
"""Helper class for logging: directs all log messages to a stderr-like
file object"""
def __init__(self, file):
"Create a new _ConsoleLogHandler attached to a given file."""
- self.file = file
+ self.file = file
def reset(self): pass
def close(self): pass
def write(self, severity, message):
@@ -337,7 +345,7 @@
and messages through the system. This is a security risk.
INFO: non-critical events.
WARN: recoverable errors
- ERROR: nonrecoverable errors that affect only a single
+ ERROR: nonrecoverable errors that affect only a single
message or a connection.
FATAL: nonrecoverable errors that affect the entire system.
@@ -374,20 +382,20 @@
self.error(str(e))
if logfile and not config['Server'].get('EchoMessages',0):
del self.handlers[0]
-
+
def setMinSeverity(self, minSeverity):
- """Sets the minimum severity of messages to be logged.
+ """Sets the minimum severity of messages to be logged.
minSeverity -- the string representation of a severity level."""
self.severity = _SEVERITIES.get(minSeverity, 1)
def getMinSeverity(self):
- """Return a string representation of this log's minimum severity
+ """Return a string representation of this log's minimum severity
level."""
for k,v in _SEVERITIES.items():
if v == self.severity:
return k
return "INFO"
-
+
def addHandler(self, handler):
"""Add a LogHandler object to the list of objects that receive
messages from this log."""
@@ -408,7 +416,7 @@
"""Close all logs"""
for h in self.handlers:
h.close()
-
+
def log(self, severity, message, *args):
"""Send a message of a given severity to the log. If additional
arguments are provided, write 'message % args'. """
@@ -426,10 +434,10 @@
else:
m = message % args
- # Enable this block to debug message formats.
+ # Enable this block to debug message formats.
if _SEVERITIES.get(severity, 100) < self.severity:
return
-
+
for h in self.handlers:
h.write(severity, m)
@@ -452,11 +460,11 @@
"Write a fatal (unrecoverable system error) message to the log"
self.log("FATAL", message, *args)
def log_exc(self, severity, (exclass, ex, tb), message=None, *args):
- """Write an exception and stack trace to the log. If message and
+ """Write an exception and stack trace to the log. If message and
args are provided, use them as an explanitory message; otherwise,
introduce the message as "Unexpected exception".
- This should usually be called as
+ This should usually be called as
LOG.log_exc('ERROR', sys.exc_info(), message, args...)
"""
if message is not None:
@@ -466,7 +474,7 @@
self.log(severity, "Unexpected exception in %s", filename)
else:
self.log(severity, "Unexpected exception")
-
+
formatted = traceback.format_exception(exclass, ex, tb)
formatted[1:] = [ " %s" % line for line in formatted[1:] ]
indented = "".join(formatted)
@@ -491,7 +499,7 @@
def mkgmtime(yyyy,MM,dd,hh,mm,ss):
"""Analogously to time.mktime, return a number of seconds since the
epoch when GMT is yyyy/MM/dd hh:mm:ss"""
-
+
# we set the DST flag to zero so that subtracting time.timezone always
# gives us gmt.
return calendar.timegm((yyyy,MM,dd,hh,mm,ss,0,0,0))
@@ -525,7 +533,7 @@
# (This is more strict than RFC822, actually. RFC822 allows tricky stuff to
# quote special characters, and I don't trust every MTA or delivery command
# to support addresses like <bob@bob."; rm -rf /; echo".com>)
-
+
# An 'Atom' is a non-escape, non-null, non-space, non-punctuation character.
_ATOM_PAT = r'[^\x00-\x20()\[\]()<>@,;:\\".\x7f-\xff]+'
# The 'Local part' (and, for us, the domain portion too) is a sequence of
@@ -539,7 +547,7 @@
"""Return true iff s is a valid SMTP address"""
m = RFC822_RE.match(s)
return m is not None
-
+
#----------------------------------------------------------------------
# Signal handling
@@ -560,7 +568,7 @@
terminateHooks.append(fn)
def waitForChildren():
- """Wait until all subprocesses have finished. Useful for testing."""
+ """Wait until all subprocesses have finished. Useful for testing."""
while 1:
try:
# FFFF This won't work on Windows. What to do?
@@ -575,7 +583,7 @@
# Because of the peculiarities of Python's signal handling logic, I
# believe we need to re-register ourself.
signal.signal(signal_num, _sigChldHandler)
-
+
while 1:
try:
# This waitpid call won't work on Windows. What to do?
@@ -584,7 +592,7 @@
break
except OSError:
break
-
+
#outcome, core, sig = status & 0xff00, status & 0x0080, status & 0x7f
# FFFF Log if outcome wasn't as expected.
@@ -598,7 +606,7 @@
else:
for hook in resetHooks:
hook()
-
+
def installSignalHandlers(child=1,hup=1,term=1):
'''Register signal handlers for this process. If 'child', registers
a handler for SIGCHLD. If 'hup', registers a handler for SIGHUP.
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.25
retrieving revision 1.26
diff -u -d -r1.25 -r1.26
--- Config.py 11 Dec 2002 06:58:55 -0000 1.25
+++ Config.py 12 Dec 2002 19:56:46 -0000 1.26
@@ -43,21 +43,20 @@
[Section2]
Key4: Value4
- The restricted format is used for server descriptors.
+ The restricted format is used for server descriptors.
"""
-__all__ = [ 'getConfig', 'loadConfig' ]
+__all__ = [ 'ConfigError', 'ClientConfig' ]
+import binascii
import os
import re
-import binascii
import socket # for inet_aton and error
from cStringIO import StringIO
import mixminion.Common
-from mixminion.Common import MixError, LOG, isPrintingAscii, stripSpace
-import mixminion.Packet
import mixminion.Crypto
+from mixminion.Common import MixError, LOG, isPrintingAscii, stripSpace
class ConfigError(MixError):
"""Thrown when an error is found in a configuration file."""
@@ -65,7 +64,7 @@
#----------------------------------------------------------------------
# Validation functions. These are used to convert values as they appear
-# in configuration files and server descriptors into corresponding Python
+# in configuration files and server descriptors into corresponding Python
# objects, and validate their formats
def _parseBoolean(boolean):
@@ -269,7 +268,7 @@
yyyy, MM, dd, hh, mm, ss = map(int, m.groups())
else:
yyyy, MM, dd = map(int, m.groups())
- hh, mm, ss = 0, 0, 0
+ hh, mm, ss = 0, 0, 0
if not ((1 <= dd <= 31) and (1 <= MM <= 12) and
(1970 <= yyyy) and (0 <= hh < 24) and
@@ -348,7 +347,7 @@
# Make sure all characters in the file are ASCII.
if not isPrintingAscii(contents):
- raise ConfigError("Invalid characters in file: %r", badchars)
+ raise ConfigError("Invalid characters in file")
#FFFF We should really use xreadlines or something if we have a file.
fileLines = contents.split("\n")
@@ -415,7 +414,7 @@
# _sections: A map from secname->key->value.
# _sectionEntries: A map from secname->[ (key, value) ] inorder.
# _sectionNames: An inorder list of secnames.
- # _callbacks: A map from section name to a callback function that should
+ # _callbacks: A map from section name to a callback function that should
# be invoked with (section,sectionEntries) after each section is
# read. This shouldn't be used for validation; it's for code that
# needs to change the semantics of the parser.
@@ -583,10 +582,10 @@
elif not self_sections.has_key(secName):
self_sections[secName] = {}
self_sectionEntries[secName] = []
-
+
if not self.assumeValid:
# Call our validation hook.
- self.validate(self_sections, self_sectionEntries,
+ self.validate(self_sections, self_sectionEntries,
sectionEntryLines, fileContents)
self._sections = self_sections
@@ -663,113 +662,6 @@
if p < 4:
LOG.warn("Your default path length is frighteningly low."
" I'll trust that you know what you're doing.")
-
-SERVER_SYNTAX = {
- 'Host' : ClientConfig._syntax['Host'],
- 'Server' : { '__SECTION__' : ('REQUIRE', None, None),
- 'Homedir' : ('ALLOW', None, "/var/spool/minion"),
- 'LogFile' : ('ALLOW', None, None),
- 'LogLevel' : ('ALLOW', _parseSeverity, "WARN"),
- 'EchoMessages' : ('ALLOW', _parseBoolean, "no"),
- 'EncryptIdentityKey' : ('REQUIRE', _parseBoolean, "yes"),
- 'IdentityKeyBits': ('ALLOW', _parseInt, "2048"),
- 'PublicKeyLifetime' : ('ALLOW', _parseInterval,
- "30 days"),
- 'PublicKeySloppiness': ('ALLOW', _parseInterval,
- "5 minutes"),
- 'EncryptPrivateKey' : ('REQUIRE', _parseBoolean, "no"),
- 'Mode' : ('REQUIRE', _parseServerMode, "local"),
- 'Nickname': ('ALLOW', None, None),
- 'Contact-Email': ('ALLOW', None, None),
- 'Comments': ('ALLOW', None, None),
- 'ModulePath': ('ALLOW', None, None),
- 'Module': ('ALLOW*', None, None),
- },
- 'DirectoryServers' : { 'ServerURL' : ('ALLOW*', None, None),
- 'Publish' : ('ALLOW', _parseBoolean, "no"),
- 'MaxSkew' : ('ALLOW', _parseInterval,
- "10 minutes",) },
- # FFFF Generic multi-port listen/publish options.
- 'Incoming/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
- 'IP' : ('ALLOW', _parseIP, "0.0.0.0"),
- 'Port' : ('ALLOW', _parseInt, "48099"),
- 'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
- 'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
- 'Outgoing/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
- 'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
- 'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
- # FFFF Missing: Queue-Size / Queue config options
- # FFFF timeout options
- # FFFF listen timeout??
- # FFFF Retry options
- # FFFF pool options
- }
-
-class ServerConfig(_ConfigFile):
- ##
- # Fields:
- # moduleManager
- #
- _restrictFormat = 0
- def __init__(self, fname=None, string=None, moduleManager=None):
- # We use a copy of SERVER_SYNTAX, because the ModuleManager will
- # mess it up.
- self._syntax = SERVER_SYNTAX.copy()
-
- import mixminion.server.Modules
- if moduleManager is None:
- self.moduleManager = mixminion.server.Modules.ModuleManager()
- else:
- self.moduleManager = moduleManager
- self._addCallback("Server", self.__loadModules)
-
- _ConfigFile.__init__(self, fname, string)
-
- def validate(self, sections, entries, lines, contents):
- log = LOG
- _validateHostSection(sections.get('Host', {}))
- # Server section
- server = sections['Server']
- bits = server['IdentityKeyBits']
- if not (2048 <= bits <= 4096):
- raise ConfigError("IdentityKeyBits must be between 2048 and 4096")
- if server['EncryptIdentityKey']:
- log.warn("Identity key encryption not yet implemented")
- if server['EncryptPrivateKey']:
- log.warn("Encrypted private keys not yet implemented")
- if server['PublicKeyLifetime'][2] < 24*60*60:
- raise ConfigError("PublicKeyLifetime must be at least 1 day.")
- if server['PublicKeySloppiness'][2] > 20*60:
- raise ConfigError("PublicKeySloppiness must be <= 20 minutes.")
- if [e for e in entries['Server'] if e[0]=='Mode']:
- log.warn("Mode specification is not yet supported.")
-
- if not sections['Incoming/MMTP'].get('Enabled'):
- log.warn("Disabling incoming MMTP is not yet supported.")
- if [e for e in entries['Incoming/MMTP'] if e[0] in ('Allow', 'Deny')]:
- log.warn("Allow/deny are not yet supported")
-
- if not sections['Outgoing/MMTP'].get('Enabled'):
- log.warn("Disabling incoming MMTP is not yet supported.")
- if [e for e in entries['Outgoing/MMTP'] if e[0] in ('Allow', 'Deny')]:
- log.warn("Allow/deny are not yet supported")
-
- self.moduleManager.validate(sections, entries, lines, contents)
-
- def __loadModules(self, section, sectionEntries):
- """Callback from the [Server] section of a config file. Parses
- the module options, and adds new sections to the syntax
- accordingly."""
- self.moduleManager.setPath(section.get('ModulePath', None))
- for mod in section.get('Module', []):
- LOG.info("Loading module %s", mod)
- self.moduleManager.loadExtModule(mod)
-
- self._syntax.update(self.moduleManager.getConfigSyntax())
-
- def getModuleManager(self):
- "Return the module manager initialized by this server."
- return self.moduleManager
def _validateHostSection(sec):
"""Helper function: Makes sure that the shared [Host] section is correct;
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -d -r1.26 -r1.27
--- Crypto.py 11 Dec 2002 05:53:33 -0000 1.26
+++ Crypto.py 12 Dec 2002 19:56:46 -0000 1.27
@@ -8,26 +8,24 @@
the functions in mixminion.Crypto, and not call _minionlib's crypto
functionality themselves."""
+import copy_reg
import os
-import sys
import stat
-import copy_reg
+import sys
from types import StringType
import mixminion._minionlib as _ml
from mixminion.Common import MixError, MixFatalError, floorDiv, ceilDiv, LOG
-__all__ = [ 'CryptoError', 'init_crypto', 'sha1', 'ctr_crypt', 'prng',
- 'strxor', 'lioness_encrypt', 'lioness_decrypt',
- 'bear_encrypt', 'bear_decrypt', 'trng',
- 'pk_encrypt', 'pk_decrypt', 'pk_sign', 'pk_check_signature',
- 'pk_generate', 'openssl_seed',
- 'pk_get_modulus', 'pk_from_modulus',
- 'pk_encode_private_key', 'pk_decode_private_key',
- 'Keyset', 'AESCounterPRNG', 'HEADER_SECRET_MODE',
- 'PRNG_MODE', 'RANDOM_JUNK_MODE', 'HEADER_ENCRYPT_MODE',
- 'APPLICATION_KEY_MODE', 'PAYLOAD_ENCRYPT_MODE',
- 'HIDE_HEADER_MODE' ]
+__all__ = [ 'AESCounterPRNG', 'CryptoError', 'Keyset', 'bear_decrypt',
+ 'bear_encrypt', 'ctr_crypt', 'init_crypto', 'lioness_decrypt',
+ 'lioness_encrypt', 'openssl_seed', 'pk_check_signature',
+ 'pk_decode_private_key', 'pk_decrypt', 'pk_encode_private_key',
+ 'pk_encrypt', 'pk_from_modulus', 'pk_generate', 'pk_get_modulus',
+ 'pk_sign', 'prng', 'sha1', 'strxor', 'trng',
+ 'AES_KEY_LEN', 'DIGEST_LEN', 'HEADER_SECRET_MODE', 'PRNG_MODE',
+ 'RANDOM_JUNK_MODE', 'HEADER_ENCRYPT_MODE', 'APPLICATION_KEY_MODE',
+ 'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
# Expose _minionlib.CryptoError as Crypto.CryptoError
CryptoError = _ml.CryptoError
@@ -50,7 +48,7 @@
except:
raise MixFatalError("Error initializing entropy source")
openssl_seed(40)
-
+
def sha1(s):
"""Return the SHA1 hash of a string"""
return _ml.sha1(s)
@@ -96,11 +94,11 @@
# may look slow, but it contributes only .7% to the total time for
# LIONESS.
right = _ml.aes_ctr128_crypt(
- _ml.aes_key(_ml.sha1("".join((key1,left,key1)))[:AES_KEY_LEN]),
- right, 0)
+ _ml.aes_key(_ml.sha1("".join((key1,left,key1)))[:AES_KEY_LEN]),
+ right, 0)
left = _ml.strxor(left, _ml.sha1("".join((key2,right,key2))))
right = _ml.aes_ctr128_crypt(
- _ml.aes_key(_ml.sha1("".join((key3,left,key3)))[:AES_KEY_LEN]),
+ _ml.aes_key(_ml.sha1("".join((key3,left,key3)))[:AES_KEY_LEN]),
right, 0)
left = _ml.strxor(left, _ml.sha1("".join((key4,right,key4))))
@@ -109,7 +107,7 @@
# left = strxor(left, sha1("".join((key2,right,key2))))
# right = ctr_crypt(right, "".join((key3,left,key3))[:AES_KEY_LEN])
# left = strxor(left, sha1("".join((key4,right,key4))))
- # but that would be slower by about 10%. (Since LIONESS is in the
+ # but that would be slower by about 10%. (Since LIONESS is in the
# critical path, we care.)
return left + right
@@ -219,7 +217,7 @@
def pk_check_signature(data, key):
"""If data holds the RSA signature of some OAEP-padded data, check the
- signature using public key 'key', and return the orignal data.
+ signature using public key 'key', and return the orignal data.
Throw CryptoError on failure. """
bytes = key.get_modulus_bytes()
# private key decrypt
@@ -276,7 +274,7 @@
rsa = _ml.rsa_PEM_read_key(f, 0)
f.close()
return rsa
-
+
def _pickle_rsa(rsa):
return _ml.rsa_make_public_key, rsa.get_public_key()
@@ -345,7 +343,7 @@
# This test (though required in the OAEP spec) is extraneous here.
#if len(data) < 2*DIGEST_LEN+1:
# raise CryptoError("Decoding error")
-
+
if data[0]!= '\x00':
raise CryptoError("Decoding error")
maskedSeed, maskedDB = data[1:DIGEST_LEN+1], data[DIGEST_LEN+1:]
@@ -430,7 +428,7 @@
key2 = _ml.strxor(key1, z19+"\x01")
key3 = _ml.strxor(key1, z19+"\x02")
key4 = _ml.strxor(key1, z19+"\x03")
-
+
return (key1, key2, key3, key4)
def getBearKeys(self,mode):
@@ -441,13 +439,13 @@
def lioness_keys_from_payload(payload):
'''Given a payload, returns the LIONESS keys to encrypt the off-header
- at the swap point.'''
+ at the swap point.'''
digest = sha1(payload)
return Keyset(digest).getLionessKeys(HIDE_HEADER_MODE)
def lioness_keys_from_header(header2):
'''Given the off-header, returns the LIONESS keys to encrypt the payload
- at the swap point.'''
+ at the swap point.'''
digest = sha1(header2)
return Keyset(digest).getLionessKeys(HIDE_PAYLOAD_MODE)
@@ -524,9 +522,9 @@
while 1:
# Get a random positive int between 0 and 0x7fffffff.
b = self.getBytes(4)
- o = (((((((_ord(b[0])&0x7f)<<8) +
- _ord(b[1]))<<8) +
- _ord(b[2]))<<8) +
+ o = (((((((_ord(b[0])&0x7f)<<8) +
+ _ord(b[1]))<<8) +
+ _ord(b[2]))<<8) +
_ord(b[3]))
# Retry if we got a value that would fall in an incomplete
# run of 'max' elements.
@@ -537,7 +535,7 @@
"""Return a floating-point number between 0 and 1."""
b = self.getBytes(4)
_ord = ord
- o = ((((((_ord(b[0])&0x7f)<<8) + _ord(b[1]))<<8) +
+ o = ((((((_ord(b[0])&0x7f)<<8) + _ord(b[1]))<<8) +
_ord(b[2]))<<8) + _ord(b[3])
#return o / float(0x7fffffff)
return o / 2147483647.0
@@ -560,7 +558,7 @@
if seed is None:
seed = trng(AES_KEY_LEN)
self.key = aes_key(seed)
-
+
def _prng(self, n):
"""Implementation: uses the AES counter stream to generate entropy."""
c = self.counter
@@ -598,7 +596,7 @@
_TRNG_FILENAME = None
def configure_trng(config):
"""Initialize the true entropy source from a given Config object. If
- none is provided, tries some sane defaults."""
+ none is provided, tries some sane defaults."""
global _TRNG_FILENAME
if config is not None:
requestedFile = config['Host'].get('EntropySource', None)
@@ -614,7 +612,7 @@
# device.
randFile = None
for file in files:
- if file is None:
+ if file is None:
continue
verbose = 1#(file == requestedFile)
@@ -640,7 +638,7 @@
else:
LOG.info("Setting entropy source to %r", randFile)
_TRNG_FILENAME = randFile
-
+
class _TrueRNG(RNG):
'''Random number generator that yields pieces of entropy from
our true rng.'''
Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- MMTPClient.py 9 Dec 2002 04:47:40 -0000 1.12
+++ MMTPClient.py 12 Dec 2002 19:56:46 -0000 1.13
@@ -3,7 +3,7 @@
"""mixminion.MMTPClient
This module contains a single, synchronous implementation of the client
- side of the Mixminion Transfer protocol. You can use this client to
+ side of the Mixminion Transfer protocol. You can use this client to
upload messages to any conforming Mixminion server.
(We don't use this module for tranferring packets between servers;
@@ -15,6 +15,8 @@
FFFF: As yet unsupported are: Session resumption and key renegotiation.
FFFF: Also unsupported: timeouts."""
+__all__ = [ "BlockingClientConnection", "sendMessages" ]
+
import socket
import mixminion._minionlib as _ml
from mixminion.Crypto import sha1
@@ -26,14 +28,14 @@
"""
## Fields:
# targetIP -- the dotted-quad, IPv4 address of our server.
- # targetPort -- the port on the server
+ # targetPort -- the port on the server
# targetKeyID -- sha1 hash of the ASN1 encoding of the public key we
# expect the server to use.
# context: a TLSContext object; used to create connections.
# sock: a TCP socket, open to the server.
# tls: a TLS socket, wrapping sock.
def __init__(self, targetIP, targetPort, targetKeyID):
- """Open a new connection."""
+ """Open a new connection."""
self.targetIP = targetIP
self.targetPort = targetPort
self.targetKeyID = targetKeyID
@@ -46,7 +48,7 @@
# Connect to the server
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setblocking(1)
- LOG.debug("Connecting to %s:%s", self.targetIP, self.targetPort)
+ LOG.debug("Connecting to %s:%s", self.targetIP, self.targetPort)
# Do the TLS handshaking
self.sock.connect((self.targetIP,self.targetPort))
@@ -63,7 +65,7 @@
if self.targetKeyID is not None and (keyID != self.targetKeyID):
raise MixProtocolError("Bad Key ID: Expected %r but got %r" % (
self.targetKeyID, keyID))
-
+
####
# Protocol negotiation
# For now, we only support 1.0, but we call it 0.1 so we can
@@ -75,7 +77,7 @@
if inp != "MMTP 0.1\r\n":
raise MixProtocolError("Protocol negotiation failed")
LOG.debug("MMTP protocol negotated: version 0.1")
-
+
def sendPacket(self, packet):
"""Send a single packet to a server."""
assert len(packet) == 1<<15
@@ -86,7 +88,7 @@
self.tls.write(packet)
self.tls.write(sha1(packet+"SEND"))
LOG.debug("Packet sent; waiting for ACK")
-
+
# And we expect, "RECEIVED\r\n", and sha1(packet|"RECEIVED")
inp = self.tls.read(len("RECEIVED\r\n")+20)
if inp != "RECEIVED\r\n"+sha1(packet+"RECEIVED"):
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- Main.py 11 Dec 2002 06:58:55 -0000 1.12
+++ Main.py 12 Dec 2002 19:56:46 -0000 1.13
@@ -26,9 +26,9 @@
" You seem to be running version %s.\n")%_ver)
sys.exit(1)
+import getopt
import os
import stat
-import getopt
import types
def filesAreSame(f1, f2):
@@ -45,7 +45,7 @@
def correctPath(myself):
"Given a command (sys.argv[0]), fix sys.path so 'import mixminion' works"
- # (If the admin uses distutils to install Mixminion, the code will
+ # (If the admin uses distutils to install Mixminion, the code will
# wind up somewhere appropriate on pythonpath. This isn't good enough,
# however: we want to run even when sysadmins don't understand distutils.)
@@ -82,7 +82,7 @@
continue
if os.path.normpath(pathEntry) == parentdir:
foundEntry = 1; break
-
+
ent = os.path.join(pathEntry, 'mixminion', 'Main.py')
if os.path.exists(ent) and filesAreSame(pathEntry, myself):
foundEntry = 1; break
@@ -108,7 +108,7 @@
# 'Main.py <cmd> arg1 arg2 arg3' will result in a call to <function_name>
# in <module_name>. The function should take two arguments: a string to
# be used as command name in error messages, and a list of [arg1,arg2,arg3].'
-#
+#
# By convention, all commands must print a usage message and exit when
# invoked with a single argument, "--help"
_COMMANDS = {
@@ -134,7 +134,7 @@
# Specifically, args[0] is used to fix sys.path so we can import
# mixminion.*; args[1] is used to select a command name from _COMMANDS,
# and args[2:] are passed to the command we select.
-
+
correctPath(args[0])
# Check whether we have a recognized command.
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- Packet.py 11 Dec 2002 05:53:33 -0000 1.20
+++ Packet.py 12 Dec 2002 19:56:46 -0000 1.21
@@ -208,7 +208,7 @@
assert self.routingtype >= MIN_EXIT_TYPE
assert len(self.routinginfo) >= TAG_LEN
return self.routinginfo[TAG_LEN:]
-
+
def getTag(self):
"""Return the part of the routingInfo that contains the decoding
tag. (Requires that routingType is an exit type.)"""
@@ -291,7 +291,7 @@
ENC_FWD_OVERHEAD = OAEP_OVERHEAD - TAG_LEN + SECRET_LEN
def parsePayload(payload):
- """Convert a decoded mixminion payload into a SingletonPayload or a
+ """Convert a decoded mixminion payload into a SingletonPayload or a
FragmentPayload object. Raise ParseError on failure or data
corruption."""
if len(payload) not in (PAYLOAD_LEN, PAYLOAD_LEN-ENC_FWD_OVERHEAD):
@@ -338,7 +338,7 @@
return 1
def getContents(self):
- """Returns the non-padding portion of this payload's data"""
+ """Returns the non-padding portion of this payload's data"""
return self.data[:self.size]
def pack(self):
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -d -r1.24 -r1.25
--- ServerInfo.py 11 Dec 2002 05:53:33 -0000 1.24
+++ ServerInfo.py 12 Dec 2002 19:56:46 -0000 1.25
@@ -10,19 +10,14 @@
__all__ = [ 'ServerInfo' ]
-import time
-import os
-import base64
-import socket
-
-from mixminion.Common import createPrivateDir, LOG, MixError, formatTime, \
- formatDate
-from mixminion.Packet import IPV4Info
import mixminion.Config
import mixminion.Crypto
-from mixminion.Crypto import DIGEST_LEN
-ConfigError = mixminion.Config.ConfigError
+from mixminion.Common import LOG, MixError, createPrivateDir, formatBase64, \
+ formatDate, formatTime
+from mixminion.Config import ConfigError
+from mixminion.Packet import IPV4Info
+from mixminion.Crypto import DIGEST_LEN
# Longest allowed Nickname
MAX_NICKNAME = 128
@@ -127,8 +122,8 @@
inMMTP['Version'])
if len(inMMTP['Key-Digest']) != DIGEST_LEN:
raise ConfigError("Invalid key digest %s"%
- base64.endodestring(inMMTP['Key-Digest']))
-
+ formatBase64(inMMTP['Key-Digest']))
+
## Outgoing/MMTP section
outMMTP = sections['Outgoing/MMTP']
if outMMTP:
@@ -136,9 +131,9 @@
raise ConfigError("Unrecognized MMTP descriptor version %s"%
inMMTP['Version'])
- # FFFF When a better client module system exists, check the
+ # FFFF When a better client module system exists, check the
# FFFF module descriptors.
-
+
def getNickname(self):
"""Returns this server's nickname"""
return self['Server']['Nickname']
@@ -165,241 +160,6 @@
return IPV4Info(self.getAddr(), self.getPort(), self.getKeyID())
#----------------------------------------------------------------------
-class ServerKeyset:
- """A set of expirable keys for use by a server.
-
- A server has one long-lived identity key, and two short-lived
- temporary keys: one for subheader encryption and one for MMTP. The
- subheader (or 'packet') key has an associated hashlog, and the
- MMTP key has an associated self-signed X509 certificate.
-
- Whether we publish or not, we always generate a server descriptor
- to store the keys' lifetimes.
-
- When we create a new ServerKeyset object, the associated keys are not
- read from disk unil the object's load method is called."""
- ## Fields:
- # hashlogFile: filename of this keyset's hashlog.
- # packetKeyFile, mmtpKeyFile: filename of this keyset's short-term keys
- # certFile: filename of this keyset's X509 certificate
- # descFile: filename of this keyset's server descriptor.
- #
- # packetKey, mmtpKey: This server's actual short-term keys.
- def __init__(self, keyroot, keyname, hashroot):
- """Load a set of keys named "keyname" on a server where all keys
- are stored under the directory "keyroot" and hashlogs are stored
- under "hashroot". """
- keydir = os.path.join(keyroot, "key_"+keyname)
- self.hashlogFile = os.path.join(hashroot, "hash_"+keyname)
- self.packetKeyFile = os.path.join(keydir, "mix.key")
- self.mmtpKeyFile = os.path.join(keydir, "mmtp.key")
- self.certFile = os.path.join(keydir, "mmtp.cert")
- self.descFile = os.path.join(keydir, "ServerDesc")
- if not os.path.exists(keydir):
- createPrivateDir(keydir)
-
- def load(self, password=None):
- """Read the short-term keys from disk. Must be called before
- getPacketKey or getMMTPKey."""
- self.packetKey = mixminion.Crypto.pk_PEM_load(self.packetKeyFile,
- password)
- self.mmtpKey = mixminion.Crypto.pk_PEM_load(self.mmtpKeyFile,
- password)
- def save(self, password=None):
- """Save this set of keys to disk."""
- mixminion.Crypto.pk_PEM_save(self.packetKey, self.packetKeyFile,
- password)
- mixminion.Crypto.pk_PEM_save(self.mmtpKey, self.mmtpKeyFile,
- password)
- def getCertFileName(self): return self.certFile
- def getHashLogFileName(self): return self.hashlogFile
- def getDescriptorFileName(self): return self.descFile
- def getPacketKey(self): return self.packetKey
- def getMMTPKey(self): return self.mmtpKey
- def getMMTPKeyID(self):
- "Return the sha1 hash of the asn1 encoding of the MMTP public key"
- return mixminion.Crypto.sha1(self.mmtpKey.encode_key(1))
-
-def _base64(s):
- "Helper function: returns a one-line base64 encoding of a given string."
- return base64.encodestring(s).replace("\n", "")
-
-def _rule(allow, (ip, mask, portmin, portmax)):
- """Return an external represenntation of an IP allow/deny rule."""
- if mask == '0.0.0.0':
- ip="*"
- mask=""
- elif mask == "255.255.255.255":
- mask = ""
- else:
- mask = "/%s" % mask
-
- if portmin==portmax==48099 and allow:
- ports = ""
- elif portmin == 0 and portmax == 65535 and not allow:
- ports = ""
- elif portmin == portmax:
- ports = " %s" % portmin
- else:
- ports = " %s-%s" % (portmin, portmax)
-
- return "%s%s%s\n" % (ip,mask,ports)
-
-# We have our X509 certificate set to expire a bit after public key does,
-# so that slightly-skewed clients don't incorrectly give up while trying to
-# connect to us.
-CERTIFICATE_EXPIRY_SLOPPINESS = 5*60
-
-def generateServerDescriptorAndKeys(config, identityKey, keydir, keyname,
- hashdir,
- validAt=None):
- """Generate and sign a new server descriptor, and generate all the keys to
- go with it.
-
- config -- Our ServerConfig object.
- identityKey -- This server's private identity key
- keydir -- The root directory for storing key sets.
- keyname -- The name of this new key set within keydir
- hashdir -- The root directory for storing hash logs.
- validAt -- The starting time (in seconds) for this key's lifetime."""
-
- # First, we generate both of our short-term keys...
- packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
- mmtpKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
-
- # ...and save them to disk, setting up our directory structure while
- # we're at it.
- serverKeys = ServerKeyset(keydir, keyname, hashdir)
- serverKeys.packetKey = packetKey
- serverKeys.mmtpKey = mmtpKey
- serverKeys.save()
-
- # FFFF unused
- # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)
-
- # Now, we pull all the information we need from our configuration.
- nickname = config['Server']['Nickname']
- if not nickname:
- nickname = socket.gethostname()
- if not nickname or nickname.lower().startswith("localhost"):
- nickname = config['Incoming/MMTP'].get('IP', "<Unknown host>")
- LOG.warn("No nickname given: defaulting to %r", nickname)
- contact = config['Server']['Contact-Email']
- comments = config['Server']['Comments']
- if not validAt:
- validAt = time.time()
-
- # Calculate descriptor and X509 certificate lifetimes.
- # (Round validAt to previous mignight.)
- validAt = mixminion.Common.previousMidnight(validAt+30)
- validUntil = validAt + config['Server']['PublicKeyLifetime'][2]
- certStarts = validAt - CERTIFICATE_EXPIRY_SLOPPINESS
- certEnds = validUntil + CERTIFICATE_EXPIRY_SLOPPINESS + \
- config['Server']['PublicKeySloppiness'][2]
-
- # Create the X509 certificate.
- mixminion.Crypto.generate_cert(serverKeys.getCertFileName(),
- mmtpKey,
- "MMTP certificate for %s" %nickname,
- certStarts, certEnds)
-
- fields = {
- "IP": config['Incoming/MMTP'].get('IP', "0.0.0.0"),
- "Port": config['Incoming/MMTP'].get('Port', 0),
- "Nickname": nickname,
- "Identity":
- _base64(mixminion.Crypto.pk_encode_public_key(identityKey)),
- "Published": formatTime(time.time()),
- "ValidAfter": formatDate(validAt),
- "ValidUntil": formatDate(validUntil),
- "PacketKey":
- _base64(mixminion.Crypto.pk_encode_public_key(packetKey)),
- "KeyID":
- _base64(serverKeys.getMMTPKeyID()),
- }
-
- # If we don't know our IP address, try to guess
- if fields['IP'] == '0.0.0.0':
- try:
- fields['IP'] = _guessLocalIP()
- LOG.warn("No IP configured; guessing %s",fields['IP'])
- except IPGuessError, e:
- LOG.error("Can't guess IP: %s", str(e))
- raise MixError("Can't guess IP: %s" % str(e))
-
- # Fill in a stock server descriptor. Note the empty Digest: and
- # Signature: lines.
- info = """\
- [Server]
- Descriptor-Version: 0.1
- IP: %(IP)s
- Nickname: %(Nickname)s
- Identity: %(Identity)s
- Digest:
- Signature:
- Published: %(Published)s
- Valid-After: %(ValidAfter)s
- Valid-Until: %(ValidUntil)s
- Packet-Key: %(PacketKey)s
- """ % fields
- if contact:
- info += "Contact: %s\n"%contact
- if comments:
- info += "Comments: %s\n"%comments
-
- # Only advertise incoming MMTP if we support it.
- if config["Incoming/MMTP"].get("Enabled", 0):
- info += """\
- [Incoming/MMTP]
- Version: 0.1
- Port: %(Port)s
- Key-Digest: %(KeyID)s
- Protocols: 0.1
- """ % fields
- for k,v in config.getSectionItems("Incoming/MMTP"):
- if k not in ("Allow", "Deny"):
- continue
- info += "%s: %s" % (k, _rule(k=='Allow',v))
-
- # Only advertise outgoing MMTP if we support it.
- if config["Outgoing/MMTP"].get("Enabled", 0):
- info += """\
- [Outgoing/MMTP]
- Version: 0.1
- Protocols: 0.1
- """
- for k,v in config.getSectionItems("Outgoing/MMTP"):
- if k not in ("Allow", "Deny"):
- continue
- info += "%s: %s" % (k, _rule(k=='Allow',v))
-
- # Ask our modules for their configuration information.
- info += "".join(config.moduleManager.getServerInfoBlocks())
-
- # Remove extra (leading or trailing) whitespace from the lines.
- lines = [ line.strip() for line in info.split("\n") ]
- # Remove empty lines
- lines = filter(None, lines)
- # Force a newline at the end of the file, rejoin, and sign.
- lines.append("")
- info = "\n".join(lines)
- info = signServerInfo(info, identityKey)
-
- # Write the desciptor
- f = open(serverKeys.getDescriptorFileName(), 'w')
- try:
- f.write(info)
- finally:
- f.close()
-
- # This is for debugging: we try to parse and validate the descriptor
- # we just made.
- # FFFF Remove this once we're more confident.
- ServerInfo(string=info)
-
- return info
-
-#----------------------------------------------------------------------
def getServerInfoDigest(info):
"""Calculate the digest of a server descriptor"""
return _getServerInfoDigestImpl(info, None)
@@ -443,61 +203,10 @@
# If we got an RSA key, we need to add the digest and signature.
signature = mixminion.Crypto.pk_sign(digest,rsa)
- digest = _base64(digest)
- signature = base64.encodestring(signature).replace("\n","")
+ digest = formatBase64(digest)
+ signature = formatBase64(signature)
infoLines[digestLine] = 'Digest: '+digest
infoLines[signatureLine] = 'Signature: '+signature
return "\n".join(infoLines)
-class IPGuessError(MixError):
- """Exception: raised when we can't guess a single best IP."""
- pass
-
-# Cached guessed IP address
-_GUESSED_IP = None
-
-def _guessLocalIP():
- "Try to find a reasonable IP for this host."
- global _GUESSED_IP
- if _GUESSED_IP is not None:
- return _GUESSED_IP
-
- # First, let's see what our name resolving subsystem says our
- # name is.
- ip_set = {}
- try:
- ip_set[ socket.gethostbyname(socket.gethostname()) ] = 1
- except socket.error:
- try:
- ip_set[ socket.gethostbyname(socket.getfqdn()) ] = 1
- except socket.error:
- pass
-
- # And in case that doesn't work, let's see what other addresses we might
- # think we have by using 'getsockname'.
- for target_addr in ('18.0.0.1', '10.0.0.1', '192.168.0.1',
- '172.16.0.1')+tuple(ip_set.keys()):
- # open a datagram socket so that we don't actually send any packets
- # by connecting.
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- s.connect((target_addr, 9)) #discard port
- ip_set[ s.getsockname()[0] ] = 1
- except socket.error:
- pass
-
- for ip in ip_set.keys():
- if ip.startswith("127.") or ip.startswith("0."):
- del ip_set[ip]
-
- # FFFF reject 192.168, 10., 176.16.x
-
- if len(ip_set) == 0:
- raise IPGuessError("No address found")
-
- if len(ip_set) > 1:
- raise IPGuessError("Multiple addresses found: %s" % (
- ", ".join(ip_set.keys())))
-
- return ip_set.keys()[0]
Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- __init__.py 11 Dec 2002 06:58:55 -0000 1.9
+++ __init__.py 12 Dec 2002 19:56:46 -0000 1.10
@@ -3,11 +3,12 @@
"""mixminion
- Client and server code for type III anonymous remailers.
+ Client and shared code for type III anonymous remailers.
"""
__version__ = "0.0.1a0"
-## __all__ = [ ]
+__all__ = [ 'server' ]
+
## import mixminion.BuildMessage
## import mixminion.Crypto
## import mixminion.Common
Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -d -r1.17 -r1.18
--- benchmark.py 11 Dec 2002 06:58:55 -0000 1.17
+++ benchmark.py 12 Dec 2002 19:56:46 -0000 1.18
@@ -10,12 +10,26 @@
>>> mixminion.benchmark.timeAll()
"""
+__pychecker__ = 'no-funcdoc no-reimport'
+__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
+
+import os
+import stat
from time import time
-from mixminion.testSupport import mix_mktemp
-__pychecker__ = 'no-funcdoc no-reimport'
-__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
+import mixminion._minionlib as _ml
+from mixminion.BuildMessage import _buildHeader, buildForwardMessage
+from mixminion.Common import secureDelete, installSignalHandlers, \
+ waitForChildren
+from mixminion.Crypto import *
+from mixminion.Crypto import OAEP_PARAMETER
+from mixminion.Crypto import _add_oaep_padding, _check_oaep_padding
+from mixminion.Packet import SMTP_TYPE
+from mixminion.server.HashLog import HashLog
+from mixminion.server.PacketHandler import PacketHandler
+from mixminion.test import FakeServerInfo
+from mixminion.testSupport import mix_mktemp
# If PRECISION_FACTOR is >1, we time everything for PRECISION_FACTOR times
# more iterations than ususal.
@@ -74,10 +88,6 @@
return "%d GB" % (n >> 30)
#----------------------------------------------------------------------
-import mixminion._minionlib as _ml
-from Crypto import *
-from Crypto import _add_oaep_padding, _check_oaep_padding
-from Crypto import OAEP_PARAMETER
short = "Hello, Dali!"
s20b = "ABCDEFGHIJKLMNOPQRST"
@@ -163,7 +173,7 @@
timeit((lambda c=c,L=L1000: c.shuffle(L)), 30)
print "aesprng.shuffle (10/1000)", \
timeit((lambda c=c,L=L1000: c.shuffle(L,10)), 1000)
-
+
lkey = Keyset("keymaterial foo bar baz").getLionessKeys("T")
print "lioness E (1K)", timeit((
lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
@@ -269,8 +279,6 @@
print "Timing overhead: %s...%s" % (timestr(min(o)),timestr(max(o)))
#----------------------------------------------------------------------
-import os
-import stat
def hashlogTiming():
print "#==================== HASH LOGS ======================="
@@ -286,11 +294,10 @@
pass
def _hashlogTiming(fname, load):
- from mixminion.server.HashLog import HashLog
# Try more realistic access patterns.
prng = AESCounterPRNG("a"*16)
-
+
print "Testing hash log (%s entries)"%load
if load > 20000:
print "This may take a few minutes..."
@@ -335,8 +342,6 @@
print "File size (%s entries)"%load, spacestr(size)
#----------------------------------------------------------------------
-from mixminion.BuildMessage import _buildHeader, buildForwardMessage
-from mixminion.test import FakeServerInfo
def buildMessageTiming():
@@ -345,7 +350,7 @@
payload = ("Junky qoph flags vext crwd zimb."*1024)[:22*1024]
serverinfo = [FakeServerInfo("127.0.0.1", 48099, pk,"x"*20)
] * 16
-
+
def bh(np,it, serverinfo=serverinfo):
ctr = AESCounterPRNG()
@@ -381,9 +386,6 @@
def seenHash(self,h): return 0
def logHash(self,h): pass
-from mixminion.server.PacketHandler import PacketHandler
-from mixminion.Packet import SMTP_TYPE
-
def serverProcessTiming():
print "#================= SERVER PROCESS ====================="
@@ -411,7 +413,7 @@
# its efficiency. If function X is pretty efficient, there's not much
# reason to try to optimise its implementation; instead, we need to attack
# the functions it uses.
-
+
##### LIONESS
shakey = "z"*20
@@ -498,9 +500,6 @@
#----------------------------------------------------------------------
-from mixminion.Common import secureDelete, installSignalHandlers, \
- waitForChildren
-
def fileOpsTiming():
print "#================= File ops ====================="
installSignalHandlers(child=1,hup=0,term=0)
@@ -520,7 +519,7 @@
waitForChildren()
t = time()-t1
- print " (sync)", timestr(t)
+ print " (sync)", timestr(t)
lst = [ os.path.join(dname,str(i)) for i in range(100,200) ]
t1 = time()
@@ -531,9 +530,9 @@
waitForChildren()
t = time()-t1
- print " (sync)", timestr(t/100)
+ print " (sync)", timestr(t/100)
+
-
#----------------------------------------------------------------------
def testLeaks1():
print "Trying to leak (sha1,aes,xor,seed,oaep)"
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.46
retrieving revision 1.47
diff -u -d -r1.46 -r1.47
--- test.py 11 Dec 2002 06:58:55 -0000 1.46
+++ test.py 12 Dec 2002 19:56:46 -0000 1.47
@@ -13,16 +13,17 @@
__pychecker__ = 'no-funcdoc maxlocals=100'
+import base64
+import cPickle
+import cStringIO
import os
+import re
+import stat
import sys
import threading
import time
-import re
-import base64
-import stat
import types
-import cPickle
-import cStringIO
+from string import atoi
# Not every post-2.0 version of Python has a working 'unittest' module, so
# we include a copy with mixminion, as 'mixminion._unittest'.
@@ -35,10 +36,31 @@
from mixminion.testSupport import mix_mktemp, suspendLog, resumeLog, \
replaceAttribute, undoReplacedAttributes, replaceFunction, \
getReplacedFunctionCallLog, clearReplacedFunctionCallLog
-from mixminion.Common import MixError, MixFatalError, MixProtocolError, \
- LOG, previousMidnight, stringContains
+
+import mixminion.BuildMessage as BuildMessage
+import mixminion.ClientMain
+import mixminion.Config
import mixminion.Crypto as Crypto
+import mixminion.MMTPClient
+import mixminion.Packet
+import mixminion.ServerInfo
+import mixminion._minionlib as _ml
+import mixminion.server.MMTPServer
+import mixminion.server.Modules
+import mixminion.server.ServerConfig
+import mixminion.server.ServerKeys
+import mixminion.server.ServerMain
+from mixminion.Common import *
+from mixminion.Common import Log, _FileLogHandler, _ConsoleLogHandler
+from mixminion.Config import _ConfigFile, ConfigError, _parseInt
+from mixminion.Crypto import *
+from mixminion.Packet import *
+from mixminion.server.HashLog import HashLog
+from mixminion.server.Modules import *
+from mixminion.server.PacketHandler import *
+from mixminion.server.Queue import *
+from mixminion.server.ServerKeys import generateServerDescriptorAndKeys
# Set this flag to 1 in order to have all RSA keys and diffie-hellman params
# generated independently. Otherwise, we cache diffie-hellman parameters
@@ -64,7 +86,7 @@
def findFirstDiff(s1, s2):
"""Helper function. Returns the first index i for which s1[i] != s2[i],
- or -1 s1 == s2."""
+ or -1 s1 == s2."""
if s1 == s2:
return -1
last = min(len(s1), len(s2))
@@ -140,8 +162,6 @@
class MiscTests(unittest.TestCase):
def testDiv(self):
- from mixminion.Common import floorDiv, ceilDiv
-
self.assertEquals(floorDiv(10,1), 10)
self.assertEquals(floorDiv(10,2), 5)
self.assertEquals(floorDiv(10,3), 3)
@@ -163,7 +183,6 @@
self.assertEquals(ceilDiv(-10,-3), 4)
def testTimeFns(self):
- from mixminion.Common import floorDiv, mkgmtime, previousMidnight
# This isn't a very good test.
now = int(time.time())
max_sec_per_day = 24*60*60+ 1
@@ -184,7 +203,6 @@
self.assertEquals(previousMidnight(pm), pm)
def test_isSMTPMailbox(self):
- from mixminion.Common import isSMTPMailbox
# Do we accept good addresses?
for addr in "Foo@bar.com", "a@b", "a@b.c.d.e", "a!b.c@d", "z@z":
self.assert_(isSMTPMailbox(addr))
@@ -197,7 +215,6 @@
self.assert_(not isSMTPMailbox(addr))
#----------------------------------------------------------------------
-import mixminion._minionlib as _ml
class MinionlibCryptoTests(unittest.TestCase):
"""Tests for cryptographic C extensions."""
@@ -405,7 +422,6 @@
self.assertEquals(p.encode_key(0), p2.encode_key(0))
#----------------------------------------------------------------------
-from mixminion.Crypto import *
class CryptoTests(unittest.TestCase):
"""Tests for Python cryptographic library"""
@@ -622,8 +638,6 @@
self.assertEquals(s, range(100))
#----------------------------------------------------------------------
-import mixminion.Packet
-from mixminion.Packet import *
class PacketTests(unittest.TestCase):
def test_subheader(self):
@@ -651,10 +665,10 @@
self.failUnless(not s.isExtended())
self.assertEquals(s.pack(), expected)
- ts_eliot = "Who is the third who walks always beside you? / "+\
- "When I count, there are only you and I together / "+\
- "But when I look ahead up the white road / "+\
- "There is always another one walking beside you"
+ ts_eliot = ("Who is the third who walks always beside you? / "+
+ "When I count, there are only you and I together / "+
+ "But when I look ahead up the white road / "+
+ "There is always another one walking beside you")
s = Subheader(3,9,"abcdeabcdeabcdef",
"ABCDEFGHIJABCDEFGHIJ",
@@ -663,15 +677,18 @@
self.assertEquals(len(ts_eliot), 186)
# test extended subeaders
- expected = "\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ\000\272\001\054Who is the third who walks always beside you"
+ expected = ("\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ"+
+ "\000\272\001\054Who is the third who walks always"+
+ " beside you")
self.assertEquals(len(expected), mixminion.Packet.MAX_SUBHEADER_LEN)
self.assertEquals(s.pack(), expected)
extra = s.getExtraBlocks()
self.assertEquals(len(extra), 2)
- self.assertEquals(extra[0], "? / When I count, there are only you "+\
- "and I together / But when I look ahead up the white "+\
- "road / There is always another one walk")
+ self.assertEquals(extra[0],
+ ("? / When I count, there are only you "+
+ "and I together / But when I look ahead up the white "+
+ "road / There is always another one walk"))
self.assertEquals(extra[1], "ing beside you"+(114*'\000'))
# test parsing extended subheaders
@@ -760,7 +777,7 @@
def test_payloads(self):
# Checks for payload structure functions.
-
+
# First, generate some plausible singleton payloads.
contents = ("payload"*(4*1024))[:28*1024 - 22]
hash = "HASH"*5
@@ -841,8 +858,6 @@
#----------------------------------------------------------------------
-from mixminion.server.HashLog import HashLog
-
class HashLogTests(unittest.TestCase):
def test_hashlog(self):
# Create a new,empty hashlog.
@@ -916,7 +931,7 @@
h[0].close()
h[0] = HashLog(fname, "Xyzzy")
seen("ddddd"*4)
-
+
# Make sure that hashlog still works when we sync.
log("Abcd"*5)
log("Defg"*5)
@@ -934,9 +949,6 @@
h[0].close()
#----------------------------------------------------------------------
-import mixminion.BuildMessage as BuildMessage
-import mixminion.server.Modules
-from mixminion.server.Modules import *
# Dummy PRNG class that just returns 0-valued bytes. We use this to make
# generated padding predictable in our BuildMessage tests below.
@@ -976,7 +988,7 @@
def test_compression(self):
# Make sure that our compression helper functions work properly.
-
+
p = AESCounterPRNG()
longMsg = p.getBytes(100)*2 + str(dir(Crypto))
@@ -1515,15 +1527,15 @@
self.assertEquals(payload,
BuildMessage._decodeEncryptedForwardPayload(efwd_p,efwd_t,rsa1))
- # Stateful reply
- secrets = [ "Is that you, Des","troyer?Rinehart?" ]
- sdict = { 'tag1'*5 : secrets }
- ks = Keyset(secrets[1])
- m = lioness_decrypt(encoded1, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
- ks = Keyset(secrets[0])
- m = lioness_decrypt(m, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
- self.assertEquals(payload, BuildMessage._decodeReplyPayload(m,secrets))
- repl1 = m
+## # Stateful reply
+## secrets = [ "Is that you, Des","troyer?Rinehart?" ]
+## sdict = { 'tag1'*5 : secrets }
+## ks = Keyset(secrets[1])
+## m = lioness_decrypt(encoded1, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+## ks = Keyset(secrets[0])
+## m = lioness_decrypt(m, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+## self.assertEquals(payload, BuildMessage._decodeReplyPayload(m,secrets))
+## repl1 = m
# Stateless reply
tag = "To light my way out\xBE"
@@ -1634,7 +1646,6 @@
class PacketHandlerTests(unittest.TestCase):
def setUp(self):
- from mixminion.server.PacketHandler import PacketHandler
self.pk1 = getRSAKey(0,1024)
self.pk2 = getRSAKey(1,1024)
self.pk3 = getRSAKey(2,1024)
@@ -1738,7 +1749,6 @@
bfm = BuildMessage.buildForwardMessage
brm = BuildMessage.buildReplyMessage
brbi = BuildMessage._buildReplyBlockImpl
- from mixminion.server.PacketHandler import ContentError
# A long intermediate header needs to fail.
server1X = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
@@ -1858,8 +1868,6 @@
#----------------------------------------------------------------------
# QUEUE
-from mixminion.Common import waitForChildren
-from mixminion.server.Queue import *
class TestDeliveryQueue(DeliveryQueue):
def __init__(self,d):
@@ -1938,7 +1946,6 @@
q2h.append(nh)
# Look at the messages in queue2, 15 then 30 at a time.
- from string import atoi
for group in queue2.pickRandom(15), queue2.pickRandom(30):
seen = {}
for h in group:
@@ -2005,7 +2012,7 @@
d_d = mix_mktemp("qd")
queue = TestDeliveryQueue(d_d)
-
+
# First, make sure the queue stores messages correctly.
h1 = queue.queueDeliveryMessage("Address 1", "Message 1")
h2 = queue.queueDeliveryMessage("Address 2", "Message 2")
@@ -2114,7 +2121,6 @@
# LOGGING
class LogTests(unittest.TestCase):
def testLogging(self):
- from mixminion.Common import Log, _FileLogHandler, _ConsoleLogHandler
# Create a new loghandler, and try sending a few messages to it.
log = Log("INFO")
@@ -2165,7 +2171,7 @@
#----------------------------------------------------------------------
# File paranoia
-from mixminion.Common import createPrivateDir, checkPrivateDir
+
class FileParanoiaTests(unittest.TestCase):
def testPrivateDirs(self):
@@ -2275,9 +2281,6 @@
# MMTP
# FFFF Write more tests
-import mixminion.server.MMTPServer
-import mixminion.MMTPClient
-
# Run on a different port so we don't conflict with any actual servers
# running on this machine.
TEST_PORT = 40199
@@ -2452,7 +2455,6 @@
#----------------------------------------------------------------------
# Config files
-from mixminion.Config import _ConfigFile, ConfigError, _parseInt
class TestConfigFile(_ConfigFile):
_syntax = { 'Sec1' : {'__SECTION__': ('REQUIRE', None, None),
@@ -2660,7 +2662,7 @@
tm = C._parseTime("2001/12/25 06:15:10")
self.assertEquals(time.gmtime(tm)[:6], (2001,12,25,6,15,10))
- ##
+ ##
# Now, try the failing cases.
def fails(fn, val, self=self):
self.failUnlessRaises(ConfigError, fn, val)
@@ -2750,8 +2752,7 @@
Nickname: fred-the-bunny
"""
-import mixminion.Config
-import mixminion.ServerInfo
+
class ServerInfoTests(unittest.TestCase):
def testServerInfoGen(self):
# Try generating a serverinfo and see if its values are as expected.
@@ -2759,17 +2760,17 @@
d = mix_mktemp()
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=SERVER_CONFIG)
finally:
resumeLog()
if not os.path.exists(d):
os.mkdir(d, 0700)
- inf = mixminion.ServerInfo.generateServerDescriptorAndKeys(conf,
- identity,
- d,
- "key1",
- d)
+ inf = generateServerDescriptorAndKeys(conf,
+ identity,
+ d,
+ "key1",
+ d)
info = mixminion.ServerInfo.ServerInfo(string=inf)
eq = self.assertEquals
eq(info['Server']['Descriptor-Version'], "0.1")
@@ -2801,7 +2802,7 @@
# Now make sure everything was saved properly
keydir = os.path.join(d, "key_key1")
eq(inf, readFile(os.path.join(keydir, "ServerDesc")))
- mixminion.ServerInfo.ServerKeyset(d, "key1", d) # Can we load?
+ mixminion.server.ServerKeys.ServerKeyset(d, "key1", d) # Can we load?
packetKey = Crypto.pk_PEM_load(
os.path.join(keydir, "mix.key"))
eq(packetKey.get_public_key(),
@@ -2823,18 +2824,18 @@
# Now with a shorter configuration
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=SERVER_CONFIG_SHORT+
+ conf = mixminion.server.ServerConfig.ServerConfig(string=SERVER_CONFIG_SHORT+
"""[Incoming/MMTP]
Enabled: yes
IP: 192.168.0.99
""")
finally:
resumeLog()
- mixminion.ServerInfo.generateServerDescriptorAndKeys(conf,
- identity,
- d,
- "key2",
- d)
+ generateServerDescriptorAndKeys(conf,
+ identity,
+ d,
+ "key2",
+ d)
# Now with a bad signature
sig2 = Crypto.pk_sign(sha1("Hello"), identity)
sig2 = base64.encodestring(sig2).replace("\n", "")
@@ -2926,7 +2927,7 @@
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=cfg_test)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=cfg_test)
finally:
resumeLog()
manager = conf.getModuleManager()
@@ -3007,7 +3008,7 @@
# Check serverinfo generation.
try:
suspendLog()
- info = mixminion.ServerInfo.generateServerDescriptorAndKeys(
+ info = generateServerDescriptorAndKeys(
conf, getRSAKey(0,2048), home_dir, "key11", home_dir)
self.failUnless(stringContains(info,"\n[Example]\nFoo: 99\n"))
finally:
@@ -3023,7 +3024,7 @@
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=cfg_test)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=cfg_test)
finally:
resumeLog()
manager = conf.getModuleManager()
@@ -3058,7 +3059,7 @@
#####
# Test escapeMessage
-
+
# plaintext text message, text mode.
self.assertEquals(em(message, None, 1), ("TXT", message, None))
# plaintext text message, bin mode.
@@ -3090,7 +3091,7 @@
# Sample address file for testing MBOX
MBOX_ADDRESS_SAMPLE = """\
# This is a sample address file
-mix-minion: mixminion@thishost
+mix-minion: mixminion@thishost
mixdaddy: mixminion@thathost
mixdiddy=mixminion@theotherhost
@@ -3103,8 +3104,8 @@
Subject: Anonymous Mixminion message
THIS IS AN ANONYMOUS MESSAGE. The mixminion server 'nickname' at
-<Unknown IP> has been configured to deliver messages to your address.
-If you do not want to receive messages in the future, contact removeaddress@x
+<Unknown IP> has been configured to deliver messages to your address.
+If you do not want to receive messages in the future, contact removeaddress@x
and you will be removed.
This message is not in plaintext. It's either 1) a reply; 2) a forward
@@ -3129,14 +3130,14 @@
module = mixminion.server.Modules.MixmasterSMTPModule()
module.configure({"Delivery/SMTP-Via-Mixmaster" :
{"Enabled":1, "Server": "nonesuch",
- "SubjectLine":'foobar',
+ "SubjectLine":'foobar',
'MixCommand' : ('ls', ['-z'])}},
manager)
queue = manager.queues['SMTP_MIX2']
replaceFunction(os, "spawnl")
try:
# Send a message...
- queue.queueDeliveryMessage((SMTP_TYPE, "foo@bar", None),
+ queue.queueDeliveryMessage((SMTP_TYPE, "foo@bar", None),
"This is the message")
queue.sendReadyMessages()
# And make sure that Mixmaster was invoked correctly.
@@ -3144,9 +3145,9 @@
self.assertEquals('spawnl', calls[0][0])
mixfn, mixargs = calls[0][1][2], calls[0][1][3:]
self.assertEquals("ls", mixfn)
- self.assertEquals(mixargs[:-1],
+ self.assertEquals(mixargs[:-1],
('-z', '-l', 'nonesuch', '-s', 'foobar',
- '-t', 'foo@bar'))
+ '-t', 'foo@bar'))
# ...and, if the temporary file it used hasn't been removed yet,
# that it contains the correct data.
fn = mixargs[-1]
@@ -3170,14 +3171,14 @@
with a stub function so that we don't actually send anything."""
# Configure the module
manager = self.getManager()
- module = mixminion.server.Modules.MBoxModule()
+ module = mixminion.server.Modules.MBoxModule()
addrfile = mix_mktemp()
writeFile(addrfile, MBOX_ADDRESS_SAMPLE)
module.configure({'Server':{'Nickname': "nickname"},
'Incoming/MMTP':{},
"Delivery/MBOX" :
{"Enabled": 1,
- "AddressFile": addrfile,
+ "AddressFile": addrfile,
"ReturnAddress": "returnaddress@x",
"RemoveContact": "removeaddress@x",
"SMTPServer" : "foo.bar.baz"}}, manager)
@@ -3212,9 +3213,9 @@
self.assertEquals(1, len(getReplacedFunctionCallLog()))
fn, args, _ = getReplacedFunctionCallLog()[0]
self.assertEquals('sendSMTPMessage', fn)
- self.assertEquals(('foo.bar.baz',
- ['mixminion@theotherhost'],
- 'returnaddress@x'),
+ self.assertEquals(('foo.bar.baz',
+ ['mixminion@theotherhost'],
+ 'returnaddress@x'),
args[:3])
d = findFirstDiff(MBOX_EXPECTED_MESSAGE, args[3])
if d != -1:
@@ -3223,7 +3224,7 @@
finally:
undoReplacedAttributes()
clearReplacedFunctionCallLog()
-
+
def testDirectoryDump(self):
"""Check out the DirectoryStoreModule that we use for testing on
machines with unreliable/nonexistant SMTP."""
@@ -3238,18 +3239,18 @@
{'Location': dir, 'UseQueue' : 0}}, manager)
# Try sending a couple of messages.
queue = manager.queues['Testing_DirectoryDump']
- queue.queueDeliveryMessage((0xFFFE, "addr1", "t"*20),
+ queue.queueDeliveryMessage((0xFFFE, "addr1", "t"*20),
"This is the message")
self.assert_(os.path.exists(os.path.join(dir, "0")))
- queue.queueDeliveryMessage((0xFFFE, "addr2", "x"*20),
+ queue.queueDeliveryMessage((0xFFFE, "addr2", "x"*20),
"This is message 2")
self.assert_(os.path.exists(os.path.join(dir, "1")))
- self.assertEquals(eme("This is message 2", "x"*20),
+ self.assertEquals(eme("This is message 2", "x"*20),
readFile(os.path.join(dir, "1")))
# test failure.
try:
suspendLog()
- queue.queueDeliveryMessage((0xFFFE, "FAIL!", "y"*20),
+ queue.queueDeliveryMessage((0xFFFE, "FAIL!", "y"*20),
"This is message X which won't be delivered")
self.assert_(not os.path.exists(os.path.join(dir, "2")))
finally:
@@ -3258,7 +3259,7 @@
try:
suspendLog()
- queue.queueDeliveryMessage((0xFFFE, "fail", "z"*20),
+ queue.queueDeliveryMessage((0xFFFE, "fail", "z"*20),
"This is message X which won't be delivered")
self.assert_(not os.path.exists(os.path.join(dir, "2")))
finally:
@@ -3267,7 +3268,7 @@
queue.sendReadyMessages()
- # Check sane behavior on missing files, and on restart.
+ # Check sane behavior on missing files, and on restart.
writeFile(os.path.join(dir, "90"), 'zed')
module = mixminion.testSupport.DirectoryStoreModule()
# This time, use a queue.
@@ -3277,13 +3278,13 @@
self.assertEquals(module.next, 91)
self.assertEquals(len(os.listdir(dir)), 3)
queue = manager.queues['Testing_DirectoryDump']
- queue.queueDeliveryMessage((0xFFFE, "addr91", None),
+ queue.queueDeliveryMessage((0xFFFE, "addr91", None),
"This is message 91")
- queue.queueDeliveryMessage((0xFFFE, "addr92", None),
+ queue.queueDeliveryMessage((0xFFFE, "addr92", None),
"This is message 92")
- queue.queueDeliveryMessage((0xFFFE, "fail", None),
+ queue.queueDeliveryMessage((0xFFFE, "fail", None),
"This is message 93")
- queue.queueDeliveryMessage((0xFFFE, "FAIL!", None),
+ queue.queueDeliveryMessage((0xFFFE, "FAIL!", None),
"This is message 94")
# All 4 messages go into the queue...
self.assertEquals(4, queue.count())
@@ -3303,7 +3304,7 @@
c = SERVER_CONFIG_SHORT+"\nHomedir: %s\n"%d
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=c)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=c)
m = conf.getModuleManager()
m.configure(conf)
return m
@@ -3311,9 +3312,8 @@
resumeLog()
#----------------------------------------------------------------------
-import mixminion.server.ServerMain
-# Sample server configuration to test ServerMain.
+# Sample server configuration to test ServerKeys
SERVERCFG = """
[Server]
Homedir: %(home)s
@@ -3335,12 +3335,12 @@
cfg = SERVERCFG % { 'home' : _FAKE_HOME }
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=cfg)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=cfg)
finally:
resumeLog()
- return mixminion.server.ServerMain.ServerKeyring(conf)
+ return mixminion.server.ServerKeys.ServerKeyring(conf)
-class ServerMainTests(unittest.TestCase):
+class ServerKeysTests(unittest.TestCase):
def testServerKeyring(self):
keyring = _getServerKeyring()
home = _FAKE_HOME
@@ -3396,9 +3396,9 @@
waitForChildren() # make sure keys are really gone before we remove
# In case we started very close to midnight, remove keys as if it
- # were a little in the future; otherwise, we won't remove the
+ # were a little in the future; otherwise, we won't remove the
# just-expired keys.
- keyring.removeDeadKeys(now+360)
+ keyring.removeDeadKeys(now+360)
self.assertEquals(3, len(keyring.keyIntervals))
if USE_SLOW_MODE:
@@ -3444,17 +3444,17 @@
]
def getExampleServerDescriptors():
- """Helper function: generate a list of list of ServerInfo objects based
+ """Helper function: generate a list of list of ServerInfo objects based
on the values of _EXAMPLE_DESCRIPTORS_INP"""
if _EXAMPLE_DESCRIPTORS:
return _EXAMPLE_DESCRIPTORS
- gen = mixminion.ServerInfo.generateServerDescriptorAndKeys
+ gen = generateServerDescriptorAndKeys
tmpkeydir = mix_mktemp()
now = time.time()
sys.stdout.flush()
-
+
# For each server...
for (nickname, lifetime, ip, starting, types) in _EXAMPLE_DESCRIPTORS_INP:
# Generate a config file
@@ -3474,7 +3474,7 @@
raise MixFatalError("Unrecognized type: %04x"%t)
try:
suspendLog()
- conf = mixminion.Config.ServerConfig(string=conf)
+ conf = mixminion.server.ServerConfig.ServerConfig(string=conf)
conf.getModuleManager().configure(conf)
finally:
resumeLog()
@@ -3490,18 +3490,17 @@
sd = os.path.join(tmpkeydir,"key_"+k,"ServerDesc")
_EXAMPLE_DESCRIPTORS[nickname].append(readFile(sd))
-
+
# (print some dots here; this step can take a while)
sys.stdout.write('.')
sys.stdout.flush()
sys.stdout.flush()
return _EXAMPLE_DESCRIPTORS
-import mixminion.ClientMain
-
# variable to hold the latest instance of FakeBCC.
BCC_INSTANCE = None
+
class ClientMainTests(unittest.TestCase):
def testTrivialKeystore(self):
"""Check out ClientMain's keystore implementation"""
@@ -3601,7 +3600,7 @@
def parseFails(s, f=self.failUnlessRaises):
f(ParseError, mixminion.ClientMain.parseAddress, s)
-
+
# Check failing cases
parseFails("sxtp:foo@bar.com") # unknown module
parseFails("mbox") # missing mbox address
@@ -3625,7 +3624,7 @@
usercfgstr = "[User]\nUserDir: %s\n[DirectoryServers]\n"%userdir
usercfg = mixminion.Config.ClientConfig(string=usercfgstr)
client = mixminion.ClientMain.MixminionClient(usercfg)
-
+
# Make sure client sets its directories up correctly.
serverdir = os.path.join(userdir, 'servers')
self.assert_(os.path.exists(serverdir))
@@ -3642,7 +3641,7 @@
## Test generateForwardMessage.
# We replace 'buildForwardMessage' to make this easier to test.
- replaceFunction(mixminion.BuildMessage, "buildForwardMessage",
+ replaceFunction(mixminion.BuildMessage, "buildForwardMessage",
lambda *a:"X")
try:
getCalls = getReplacedFunctionCallLog
@@ -3661,13 +3660,13 @@
for fn, args, kwargs in getCalls():
self.assertEquals(fn, "buildForwardMessage")
- self.assertEquals(args[0:3],
+ self.assertEquals(args[0:3],
(payload, SMTP_TYPE, "joe@cledonism.net"))
self.assert_(len(args[3]) == len(args[4]) == 2)
self.assertEquals(["Lola", "Joe", "Alice", "Joe"],
[x['Server']['Nickname'] for x in args[3]+args[4]])
clearCalls()
-
+
# Now try an mbox message, with an explicit last hop.
payload = "Hey, Lo', where you goin' with that pun in your hand?"
client.generateForwardMessage(
@@ -3678,11 +3677,11 @@
client.generateForwardMessage(
parseAddress("mbox:granola@Lola"),
payload,
- path1=["Lola", "Joe"], path2=["Alice"])
+ path1=["Lola", "Joe"], path2=["Alice"])
for fn, args, kwargs in getCalls():
self.assertEquals(fn, "buildForwardMessage")
- self.assertEquals(args[0:3],
+ self.assertEquals(args[0:3],
(payload, MBOX_TYPE, "granola"))
self.assert_(len(args[3]) == len(args[4]) == 2)
self.assertEquals(["Lola", "Joe", "Alice", "Lola"],
@@ -3694,27 +3693,27 @@
### Now try some failing cases for generateForwardMessage:
# Empty path...
- self.assertRaises(MixError,
- client.generateForwardMessage,
+ self.assertRaises(MixError,
+ client.generateForwardMessage,
parseAddress("0xFFFF:zed"),
"Z", [], ["Alice"])
# Nonexistant servers...
- self.assertRaises(MixError,
+ self.assertRaises(MixError,
client.generateForwardMessage,
parseAddress("0xFFFF:zed"),
"Z", ["Marvin"], ["Fred"])
# Lola doesn't support SMTP...
- self.assertRaises(MixError,
+ self.assertRaises(MixError,
client.generateForwardMessage,
parseAddress("smtp:joe@cledonism.net"),
"Z", ["Joe"], ["Lola"])
# Joe doesn't support MBOX...
- self.assertRaises(MixError,
+ self.assertRaises(MixError,
client.generateForwardMessage,
parseAddress("mbox:wahoo"),
"Z", ["Lola"], ["Joe"])
-
-
+
+
# Temporarily replace BlockingClientConnection so we can try the client
# without hitting the network.
class FakeBCC:
@@ -3787,7 +3786,7 @@
suite.addTest(tc(ModuleTests))
suite.addTest(tc(ClientMainTests))
- suite.addTest(tc(ServerMainTests))
+ suite.addTest(tc(ServerKeysTests))
# These tests are slowest, so we do them last.
suite.addTest(tc(ModuleManagerTests))
Index: testSupport.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/testSupport.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- testSupport.py 11 Dec 2002 06:58:55 -0000 1.10
+++ testSupport.py 12 Dec 2002 19:56:46 -0000 1.11
@@ -6,19 +6,20 @@
Shared support code for unit tests, benchmark tests, and integration tests.
"""
-import os
-import sys
-import stat
import base64
import cStringIO
+import os
+import stat
+import sys
import mixminion.Crypto
import mixminion.Common
from mixminion.Common import waitForChildren, createPrivateDir, LOG
from mixminion.Config import _parseBoolean, ConfigError
-from mixminion.server.Modules import DeliveryModule, ImmediateDeliveryQueue, \
- SimpleModuleDeliveryQueue, DELIVER_OK, DELIVER_FAIL_RETRY, \
- DELIVER_FAIL_NORETRY, _escapeMessageForEmail
+
+from mixminion.server.Modules import DELIVER_FAIL_NORETRY, DELIVER_FAIL_RETRY,\
+ DELIVER_OK, DeliveryModule, ImmediateDeliveryQueue, \
+ SimpleModuleDeliveryQueue, _escapeMessageForEmail
#----------------------------------------------------------------------
# DirectoryStoreModule
@@ -43,24 +44,24 @@
return { 'Testing/DirectoryDump':
{ 'Location' : ('REQUIRE', None, None),
'UseQueue': ('REQUIRE', _parseBoolean, None) } }
-
+
def validateConfig(self, sections, entries, lines, contents):
# loc = sections['Testing/DirectoryDump'].get('Location')
- pass
-
+ pass
+
def configure(self, config, manager):
self.loc = config['Testing/DirectoryDump'].get('Location')
if not self.loc:
return
self.useQueue = config['Testing/DirectoryDump']['UseQueue']
manager.enableModule(self)
-
+
if not os.path.exists(self.loc):
createPrivateDir(self.loc)
max = -1
for f in os.listdir(self.loc):
- if int(f) > max:
+ if int(f) > max:
max = int(f)
self.next = max+1
@@ -69,16 +70,16 @@
def getName(self):
return "Testing_DirectoryDump"
-
+
def getExitTypes(self):
return [ 0xFFFE ]
-
+
def createDeliveryQueue(self, queueDir):
if self.useQueue:
return SimpleModuleDeliveryQueue(self, queueDir)
else:
return ImmediateDeliveryQueue(self)
-
+
def processMessage(self, message, tag, exitType, exitInfo):
assert exitType == 0xFFFE
@@ -111,7 +112,7 @@
# Constant flag: are we paranoid about permissions and uid on our tmpdir?
_MM_TESTING_TEMPDIR_PARANOIA = 1
-# Name of our temporary directory: all temporary files go under this
+# Name of our temporary directory: all temporary files go under this
# directory. If None, it hasn't been created yet. If it exists,
# it must be owned by us, mode 700, and have no parents that an adversary
# (other than root) could write to.
@@ -180,10 +181,10 @@
print "Directory %s has fishy permissions %o" %(parent,m)
sys.exit(1)
if st[stat.ST_UID] not in (0, os.getuid()):
- print "Directory %s has bad owner %s" % (parent,
+ print "Directory %s has bad owner %s" % (parent,
st[stat.ST_UID])
sys.exit(1)
-
+
_MM_TESTING_TEMPDIR = temp
if _MM_TESTING_TEMPDIR_REMOVE_ON_EXIT:
import atexit
@@ -386,6 +387,6 @@
]
TEST_KEYS_2048 = [
- mixminion.Crypto.pk_decode_private_key(base64.decodestring(s))
+ mixminion.Crypto.pk_decode_private_key(base64.decodestring(s))
for s in TEST_KEYS_2048 ]
del s