[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Start of server impl, module manager, serverinfo genera...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv21049/minion/lib/mixminion
Modified Files:
BuildMessage.py Config.py MMTPClient.py MMTPServer.py
Modules.py Packet.py Queue.py ServerInfo.py test.py
Added Files:
ServerMain.py
Log Message:
Start of server impl, module manager, serverinfo generation, and much more.
ServerMain:
New file to hold main loop and state for server. Will need to be
refactored.
BuildMessage, Packet, test:
Rename Local to MBOX
Config:
Support for loadable modules
MMTPClient, MMTPServer:
Change magic strings to match spec changes
MMTPServer:
Blow up more intelligently on bad protocol list.
Handle new padding format
Add message to sent callback
Add first cut of generic MMTPServer class
Modules:
Add new module manager code, and an example MBOX module.
Queue:
Log queue creation.
Worry more about directory permissions
ServerInfo:
Deal with spec changes; move MBox into a module
crypt.c:
Accept (but ignore) bits argument on DH generation
tls.c:
Add separate 'server mode' for sockets to allow incoming cipher
suites that we'd otherwise reject.
--- NEW FILE: ServerMain.py ---
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id: ServerMain.py,v 1.1 2002/08/06 16:09:21 nickm Exp $
"""mixminion.ServerMain
The main loop and related functionality for a Mixminion server
BUG: No support for public key encryption"""
from mixminion.Common import getLog, MixFatalError, MixError
# Directory layout:
# MINION_HOME/work/queues/incoming/
# mix/
# outgoing/
# deliver/mbox/
# tls/dhparam
# hashlogs/hash_1 ...
# log
# keys/key_1/ServerDesc
# mix.key
# mmtp.key
# mmtp.cert
# key_2/...
# conf/miniond.conf
# ....
def createDir(d):
if not os.path.exists(d):
try:
os.mkdir(d, 0700)
except OSError, e:
getLog().fatal("Unable to create directory %s"%d)
raise MixFatalError()
elif not os.path.isdir(d):
getLog().fatal("%s is not a directory"%d)
raise MixFatalError()
else:
m = os.stat(d)[stat.ST_MODE]
# check permissions
if m & 0077:
getLog().fatal("Directory %s must be mode 0700" %d)
raise MixFatalError()
class ServerState:
# config
# log
# homedir
def __init__(self, config):
self.config = config
#XXXX DOCDOC
# set up directory structure.
c = self.config
self.homedir = c['Server']['Homedir']
createDir(self.homedir)
getLog()._configure() # ????
w = os.path.join(self.homeDir, "work")
q = os.path.join(w, "queues")
self.incomingDir = os.path.join(q, "incoming")
self.mixDir = os.path.join(q, "mix")
self.outgoingDir = os.path.join(q, "outgoing")
self.deliverDir = os.path.join(q, "deliver")
self.deliverMBOXDir = os.path.join(self.deliverDir, "mbox")
tlsDir = os.path.join(w, "tls")
self.hashlogsDir = os.path.join(w, "hashlogs")
self.keysDir = os.path.join(self.homeDir, "keys")
self.confDir = os.path.join(self.homeDir, "conf")
for d in [self.homeDir, w, q, self.incomingDir, self.mixDir,
self.outgoingDir, self.deliverDir, tlsDir,
self.hashlogsDir, self.keysDir, self.confDir]:
createDir(d)
for name in ("incoming", "mix", "outgoing", "deliverMBOX"):
loc = getattr(self, name+"Dir")
queue = mixminion.Queue.Queue(loc, create=1, scrub=1)
setattr(self, name+"Queue", queue)
self.dhFile = os.path.join(tlsDir, "dhparam")
self.checkKeys()
def getDHFile(self):
if not os.path.exists(self.dhFile):
getLog().info("Generating Diffie-Helman parameters for TLS...")
mixminion._minionlib.generate_dh_parameters(self.dhFile, verbose=0)
getLog().info("...done")
return self.dhFile
def checkKeys(self):
self.keyIntervals = [] # list of start, end, keysetname
for dirname in os.listdir(self.keysDir):
if not dirname.startswith('key_'):
continue
keysetname = dirname[4:]
d = os.path.join(self.keysDir, dirname)
si = os.path.join(self.keysDir, "ServerDesc")
if os.path.exists(si):
inf = mixminion.ServerInfo.ServerInfo(fname=si, assumeValid=1)
t1 = inf['Server']['Valid-After']
t2 = inf['Server']['Valid-Until']
self.keyIntervals.append( (t1, t2, keysetname) )
self.keyIntervals.sort()
def removeDeadKeys(self):
now = time.time()
cutoff = now - config['Server']['PublicKeySloppiness']
names = [ os.path.join(self.keyDir,"key_"+name)
for va, vu, name in self.keyIntervals if vu < cutoff ]
# XXXX DELETE KEYS
def _getLiveKey(self):
now = time.time()
idx = bisect.bisect_left(self.keyIntervals, (now, None, None))
return self.keyIntervals[idx]
def getNextKeyRotation(self):
return self._getLiveKey()[1]
def getServerKeys(self):
keyset = self._getLiveKey()[2]
sk = mixminion.ServerInfo.ServerKeys(self.keyDir, keyset,
self.hashlogsDir)
sk.load()
return sk
def getTLSContext(self):
# XXXX NO SUPPORT FOR ROTATION
keys = self.getServerKeys()
return mixminion._minionlib.TLSContext_new(keys.certFile,
keys.mmtpKey,
self.dhFile)
def getPacketHandler(self):
keys = self.getServerKeys()
return mixminion.PacketHandler.PacketHandler(keys.packetKey,
keys.hashlogFile)
def getIncomingQueues(self):
return self.incomingQueue
def getOutgoingQueue(self):
return self.outgoingQueue
def getMixQueue(self):
return self.mixQueue
def getDeliverMBOXQueue(self, which):
return self.deliverMBOXQueue
class _Server(MMTPServer):
def __init__(self, config, serverState):
self.incomingQueue = serverState.getIncomingQueue()
self.outgoingQueue = serverState.getOutgoingQueue()
MMTPServer.__init__(self, config)
def onMessageReceived(self, msg):
self.incomingQueue.queueMessage(msg)
def onMessageSent(self, msg):
self.outgoingQueue.remove
def runServer(config):
s = ServerState(config)
packetHandler = s.getPacketHandler()
context = s.getTLSContext()
shouldProcess = len(os.listdir(s.incomingDir))
shouldSend = len(os.listdir(s.outgoingDir))
shouldMBox = len(os.listdir(s.deliverMBOXDir))
# XXXX Make these configurable; make mixing OO.
mixInterval = 60
mixPoolMinSize = 5
mixPoolMaxRate = 5
nextMixTime = time.time() + mixInterval
server = mixminion.MMTPServer.MMTPServer(config)
while 1:
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- BuildMessage.py 26 Jul 2002 20:52:17 -0000 1.10
+++ BuildMessage.py 6 Aug 2002 16:09:21 -0000 1.11
@@ -70,7 +70,7 @@
path: a list of ServerInfo objects
user: the user's username/email address
userKey: an AES key to encrypt the seed, or None.
- email: If true, delivers via SMTP; else delivers via LOCAL.
+ email: If true, delivers via SMTP; else delivers via MBOX
"""
#XXXX Out of sync with the spec.
if email and userKey:
@@ -86,8 +86,8 @@
exitType = Modules.SMTP_TYPE
exitInfo = SMTPInfo(user, "RTRN"+tag).pack()
else:
- exitType = Modules.LOCAL_TYPE
- exitInfo = LocalInfo(user, "RTRN"+tag).pack()
+ exitType = Modules.MBOX_TYPE
+ exitInfo = MBOXInfo(user, "RTRN"+tag).pack()
prng = Crypto.AESCounterPRNG(seed)
return buildReplyBlock(path, exitType, exitInfo, expiryTime, prng)[0]
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- Config.py 28 Jul 2002 22:42:33 -0000 1.6
+++ Config.py 6 Aug 2002 16:09:21 -0000 1.7
@@ -28,7 +28,6 @@
Key3 = value3
# A comment
Key4=value4
-
[Section2]
Key5 value5
value5 value5 value5
@@ -53,6 +52,7 @@
import re
import binascii
import time
+import copy
from cStringIO import StringIO
import mixminion.Common
@@ -422,6 +422,7 @@
# _sections: A map from secname->key->value.
# _sectionEntries: A map from secname->[ (key, value) ] inorder.
# _sectionNames: An inorder list of secnames.
+ # _callbacks: XXXX DOC
#
# Fields to be set by a subclass:
# _syntax is map from sec->{key:
@@ -451,6 +452,9 @@
steps. (Use this to load a file that's already been checked as
valid.)"""
assert filename is None or string is None
+ if not hasattr(self, '_callbacks'):
+ self._callbacks = {}
+
self.assumeValid = assumeValid
self.fname = filename
if filename:
@@ -568,6 +572,10 @@
assert rule == 'ALLOW*'
section[k] = map(parseFn,default)
+ cb = self._callbacks.get(secName, None)
+ if cb:
+ cb(section, sectionEntries)
+
# Check for missing required sections, setting any missing
# allowed sections to {}.
for secName, secConfig in self._syntax.items():
@@ -595,6 +603,11 @@
self._sectionEntries = self_sectionEntries
self._sectionNames = self_sectionNames
+ def _addCallback(self, section, cb):
+ if not hasattr(self, '_callbacks'):
+ self._callbacks = {}
+ self._callbacks[section] = cb
+
def validate(self, sections, sectionEntries, entryLines,
fileContents):
"""Check additional semantic properties of a set of configuration
@@ -652,9 +665,8 @@
#XXXX Write this
pass
-class ServerConfig(_ConfigFile):
- _restrictFormat = 0
- _syntax = {
+
+SERVER_SYNTAX = {
'Host' : ClientConfig._syntax['Host'],
'Server' : { '__SECTION__' : ('REQUIRE', None, None),
'Homedir' : ('ALLOW', None, "/var/spool/minion"),
@@ -666,11 +678,13 @@
"30 days"),
'PublicKeySloppiness': ('ALLOW', _parseInterval,
"5 minutes"),
- 'EncryptPublicKey' : ('REQUIRE', _parseBoolean, "no"),
+ '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"),
@@ -684,18 +698,33 @@
'Outgoing/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
'Allow' : ('ALLOW*', _parseAddressSet_allow, None),
'Deny' : ('ALLOW*', _parseAddressSet_deny, None) },
- 'Delivery/MBOX' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
- 'AddressFile' : ('ALLOW', None, None),
- 'Command' : ('ALLOW', _parseCommand, "sendmail") },
}
+
+class ServerConfig(_ConfigFile):
+ _restrictFormat = 0
# XXXX Missing: Queue-Size / Queue config options
# XXXX timeout options
def __init__(self, fname=None, string=None):
+ self._syntax = SERVER_SYNTAX.copy()
+
+ import mixminion.Modules
+ self.moduleManager = mixminion.Modules.ModuleManager()
+ self._addCallback("Server", self.loadModules)
+
_ConfigFile.__init__(self, fname, string)
def validate(self, sections, entries, lines, contents):
#XXXX write this.
- pass
+ self.moduleManager.validate(sections, entries, lines, contents)
+
+ def loadModules(self, section, sectionEntries):
+ self.moduleManager.setPath(section.get('ModulePath', None))
+ for mod in section.get('Module', []):
+ self.moduleManager.loadExtModule(mod)
+
+ self._syntax.update(self.moduleManager.getConfigSyntax())
+
+
## if sections['Server']['PublicKeyLifeTime'][2] < 24*60*60:
## raise ConfigError("PublicKeyLifetime must be at least 1 day.")
Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- MMTPClient.py 25 Jul 2002 15:52:57 -0000 1.5
+++ MMTPClient.py 6 Aug 2002 16:09:21 -0000 1.6
@@ -52,9 +52,9 @@
####
# Protocol negotiation
# For now, we only support 1.0
- self.tls.write("PROTOCOL 1.0\r\n")
+ self.tls.write("MMTP 1.0\r\n")
inp = self.tls.read(len("PROTOCOL 1.0\r\n"))
- if inp != "PROTOCOL 1.0\r\n":
+ if inp != "MMTP 1.0\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.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- MMTPServer.py 25 Jul 2002 15:52:57 -0000 1.7
+++ MMTPServer.py 6 Aug 2002 16:09:21 -0000 1.8
@@ -398,11 +398,11 @@
return self.__con.get_peer_cert_pk()
#----------------------------------------------------------------------
-# XXXX Need to support future protos.
-PROTOCOL_STRING = "PROTOCOL 1.0\r\n"
-PROTOCOL_RE = re.compile("PROTOCOL ([^\s\r\n]+)\r\n")
-SEND_CONTROL = "SEND\r\n" #XXXX Not as in spec
-RECEIVED_CONTROL = "RECEIVED\r\n" #XXXX Not as in spec
+PROTOCOL_STRING = "MMTP 1.0\r\n"
+PROTOCOL_RE = re.compile("MMTP ([^\s\r\n]+)\r\n")
+SEND_CONTROL = "SEND\r\n"
+JUNK_CONTROL = "JUNK\r\n"
+RECEIVED_CONTROL = "RECEIVED\r\n"
SEND_CONTROL_LEN = len(SEND_CONTROL)
RECEIVED_CONTROL_LEN = len(RECEIVED_CONTROL)
SEND_RECORD_LEN = len(SEND_CONTROL) + MESSAGE_LEN + DIGEST_LEN
@@ -429,7 +429,11 @@
"""
trace("done w/ client sendproto")
inp = self.getInput()
- m = PROTOCOL_RE.match(inp)
+ m =PROTOCOL_RE.match(inp)
+
+ if not m:
+ warn("Bad protocol list. Closing connection.")
+ self.shutdown(err=1)
protocols = m.group(1).split(",")
if "1.0" not in protocols:
warn("Unsupported protocol list. Closing connection.")
@@ -454,14 +458,24 @@
msg = data[SEND_CONTROL_LEN:-DIGEST_LEN]
digest = data[-DIGEST_LEN:]
- if (not (data.startswith(SEND_CONTROL) and
- sha1(msg+"SEND") == digest)):
+ if data.startswith(JUNK_CONTROL):
+ expectedDigest = sha1(msg+"JUNK")
+ replyDigest = sha1(msg+"RECEIVED JUNK")
+ elif data.startswith(SEND_CONTROL):
+ expectedDigest = sha1(msg+"SEND")
+ replyDigest = sha1(msg+"RECEIVED")
+ else:
+ warn("Unrecognized command. Closing connection.")
+ self.shutdown(err=1)
+ return
+ if expectedDigest != digest:
warn("Invalid checksum. Closing connection.")
self.shutdown(err=1)
+ return
else:
debug("Packet received; Checksum valid.")
self.finished = self.__sentAck
- self.beginWrite(RECEIVED_CONTROL+sha1(msg+"RECEIVED"))
+ self.beginWrite(RECEIVED_CONTROL+replyDigest)
self.messageConsumer(msg)
def __sentAck(self):
@@ -553,7 +567,7 @@
"""Called when we're done reading the ACK. If the ACK is bad,
closes the connection. If the ACK is correct, removes the
just-sent message from the connection's internal queue, and
- calls sentCallback.
+ calls sentCallback with the sent message.
If there are more messages to send, begins sending the next.
Otherwise, begins shutting down.
@@ -566,9 +580,39 @@
return
debug("Received valid ACK for message.")
+ justSent = self.messageList[0]
del self.messageList[0]
if self.sentCallback is not None:
- self.sentCallback()
+ self.sentCallback(justSent)
self.beginNextMessage()
+class MMTPServer(AsyncServer):
+ "XXXX"
+ def __init__(self, config):
+ self.context = config.getTLSContext(server=1)
+ self.listener = ListenConnection("127.0.0.1",
+ config['Outgoing/MMTP']['Port']
+ 10, self._newMMTPConnection)
+ self.config = config
+ self.listener.register(self)
+
+ def _newMMTPConnection(self, sock):
+ "XXXX"
+ # XXXX Check whether incoming IP is valid XXXX
+ tls = self.context.sock(sock, serverMode=1)
+ sock.setblocking(0)
+ con = MMTPServerConnection(sock, tls, self.onMessageReceived)
+ con.register(self)
+
+ def sendMessages(self, ip, port, keyID, messages):
+ con = MMTPClientConnection(ip, port, keyID, messages,
+ self.onMessageSent)
+ con.register(self)
+
+ def onMessageReceived(self, msg):
+ pass
+
+ def onMessageSent(self, msg):
+ pass
+
Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Modules.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- Modules.py 2 Jun 2002 06:11:16 -0000 1.2
+++ Modules.py 6 Aug 2002 16:09:21 -0000 1.3
@@ -5,6 +5,19 @@
Type codes and dispatch functions for routing functionality."""
+#__all__ = [ 'ModuleManager' ]
+
+import os
+
+import mixminion.Config
+import mixminion.Packet
+from mixminion.Config import ConfigError, _parseBoolean, _parseCommand
+from mixminion.Common import getLog
+
+DELIVER_OK = 1
+DELIVER_FAIL_RETRY = 2
+DELIVER_FAIL_NORETRY = 3
+
# Numerically first exit type.
MIN_EXIT_TYPE = 0x0100
@@ -15,4 +28,261 @@
# Exit types
SMTP_TYPE = 0x0100 # Mail the message
-LOCAL_TYPE = 0x0101 # Store the message for local delivery.
+MBOX_TYPE = 0x0101 # Send the message to one of a fixed list of addresses
+
+
+class DeliveryModule:
+ "XXXX DOCME"
+ def __init__(self):
+ pass
+
+ def getConfigSyntax(self):
+ pass
+
+ def validateConfig(self, sections, entries, lines, contents):
+ pass
+
+ def configure(self, config, manager):
+ pass
+
+ def getServerInfoBlock(self):
+ pass
+
+ def getName(self):
+ pass
+
+ def getExitTypes(self):
+ pass
+
+ def processMessage(self, message, exitType, exitInfo):
+ pass
+
+class ModuleManager:
+ def __init__(self):
+ self.syntax = {}
+ self.modules = []
+ self.typeToModule = {}
+
+ self.registerModule(MBoxModule())
+ self.registerModule(DropModule())
+
+ def getConfigSyntax(self):
+ return self.syntax
+
+ def registerModule(self, module):
+ self.modules.append(module)
+ syn = module.getConfigSyntax()
+ for sec, rules in syn.items():
+ if self.syntax.has_key(sec):
+ raise ConfigError("Multiple modules want to define [%s]"% sec)
+ self.syntax.update(syn)
+
+ def setPath(self, path):
+ self.path = path
+
+ def loadExtModule(self, className):
+ # CHECK! XXXX Handle errors
+ ids = className.split(".")
+ pyPkg = ".".join(ids[:-1])
+ pyClassName = ids[-1]
+ try:
+ orig_path = sys.path[:]
+ sys.path.extend(self.path)
+ m = __import__(pyPkg, {}, {}, [])
+ finally:
+ sys.path = orig_path
+ pyClass = getattr(pyPkg, pyClassname)
+ self.registerModule(pyClass())
+
+ def validate(self, sections, entries, lines, contents):
+ for m in self.modules:
+ m.validateConfig(sections, entries, lines, contents)
+
+ def configure(self, config):
+ for m in self.modules:
+ m.configure(config, self)
+
+ def enableModule(self, module):
+ for t in module.getExitTypes():
+ self.typeToModule[t] = module
+
+ def disableModule(self, module):
+ for t in module.getExitTypes():
+ if self.typeToModule.has_key(t):
+ del self.typeToModule[t]
+
+ def processMessage(self, message, exitType, exitInfo):
+ mod = self.typeToModule.get(exitType, None)
+ if mod is not None:
+ return mod.processMessage(message, exitType, exitInfo)
+ else:
+ getLog().error("Unable to deliver message with unknown type %s",
+ exitType)
+ return DELIVER_FAIL_NORETRY
+
+ def getServerInfoBlocks(self):
+ return [ m.getServerInfoBlock() for m in self.modules ]
+
+class DropModule(DeliveryModule):
+ def __init__(self):
+ DeliveryModule.__init__(self)
+
+ def getConfigSyntax(self):
+ return { }
+
+ def validateConfig(self, sections, entries, lines, contents):
+ pass
+
+ def configure(self, config, moduleManager):
+ pass
+
+ def getServerInfoBlock(self):
+ return ""
+
+ def getName(self):
+ return "DROP module"
+
+ def getExitTypes(self):
+ return [ DROP_TYPE ]
+
+ def processMessage(self, message, exitType, exitInfo):
+ getLog().info("Dropping padding message")
+ return DELIVER_OK
+
+#----------------------------------------------------------------------
+class MBoxModule(DeliveryModule):
+ def __init__(self):
+ DeliveryModule.__init__(self)
+ self.command = None
+ self.enabled = 0
+ self.addresses = {}
+
+ def getConfigSyntax(self):
+ return { "Delivery/MBOX" :
+ { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
+ 'AddressFile' : ('ALLOW', None, None),
+ 'ReturnAddress' : ('ALLOW', None, None),
+ 'RemoveContact' : ('ALLOW', None, None),
+ 'Command' : ('ALLOW', _parseCommand, "sendmail") }
+ }
+
+ def validateConfig(self, sections, entries, lines, contents):
+ # XXXX write this. Parse address file.
+ pass
+
+ def configure(self, config, moduleManager):
+ # XXXX Check this. error handling
+ self.enabled = config['Delivery/MBOX'].get("Enabled", 0)
+ self.command = config['Delivery/MBOX']['Command']
+ self.addressFile = config['Delivery/MBOX']['AddressFile']
+ self.returnAddress = config['Delivery/MBOX']['ReturnAddress']
+ self.contact = config['Delivery/MBOX']['RemoveContact']
+ if self.enabled:
+ if not self.addressFile:
+ raise ConfigError("Missing AddressFile field in Delivery/MBOX")
+ if not self.returnAddress:
+ raise ConfigError("Missing ReturnAddress field "+
+ "in Delivery/MBOX")
+ if not self.contact:
+ raise ConfigError("Missing RemoveContact field "+
+ "in Delivery/MBOX")
+
+ self.nickname = config['Server']['Nickname']
+ if not self.nickname:
+ self.nickname = socket.gethostname()
+ self.addr = config['Server'].get('IP', "<Unknown host>")
+
+ if self.command != ('sendmail', []):
+ getLog().warn("Ignoring mail command in version 0.0.1")
+
+ f = open(self.addressfile)
+ addresses = f.read()
+ f.close()
+
+ addresses = mixminion.Config._readConfigFile(addresses)
+ assert len(addresses) > 1
+ assert not addresses.has_key('Addresses')
+
+ self.addresses = {}
+ for k, v, line in addresses[0][1]:
+ if self.addresses.has_key(k):
+ raise ConfigError("Duplicate MBOX user %s"%k)
+ self.addresses[k] = v
+
+ if enabled:
+ moduleManager.enableModule(self)
+ else:
+ moduleManager.disableModule(self)
+
+ def getServerInfoBlock(self):
+ return """\
+ [Delivery/MBOX]
+ Version: 1.0
+ """
+
+ def getName(self):
+ return "MBOX module"
+
+ def getExitTypes(self):
+ return [ MBOX_TYPE ]
+
+ def processMessage(self, message, exitType, exitInfo):
+ assert exitType == MBOX_TYPE
+ getLog().trace("Received MBOX message")
+ info = mixminion.packet.parseMBOXInfo(exitInfo)
+ if not addresses.has_key(info.user):
+ getLog.warn("Unknown MBOX user %r", info.user)
+ return
+ msg = _escapeMessageForEmail(message)
+
+ fields = { 'user': addresses[info.user],
+ 'return': self.returnAddr,
+ 'nickname': self.nickname,
+ 'addr': self.addr,
+ 'contact': self.contact,
+ 'msg': msg }
+ msg = """
+To: %(user)s
+From: %(return)s
+Subject: Anonymous Mixminion message
+
+THIS IS AN ANONYMOUS MESSAGE. The mixminion server '%(nickname)s' at
+%(addr)s has been configured to deliver messages to your address. If you
+do not want to receive messages in the future, contact %(contact)s and you
+will be removed. (XXXX Need real boilerplate)
+
+%(msg)s
+""" % fields
+
+ f = os.popen("sendmail -i -t", 'w')
+ f.write(msg)
+ status = f.close()
+ if status != 0:
+ getLog().error("Unsuccessful sendmail")
+ return DELIVER_FAIL_RETRY
+
+ return DELIVER_OK
+
+#----------------------------------------------------------------------
+
+
+_allChars = "".join(map(chr, range(256)))
+_nonprinting = "".join(map(chr, range(0x00, 0x07)+range(0x0E, 0x20)))
+def _escapeMessageForEmail(msg):
+ printable = msg.translate(_allChars, _nonprinting)
+ if msg[len(printable):] == '\x00'*(len(msg)-len(printable)):
+ msg = msg[len(printable)]
+ return """\
+============ ANONYMOUS MESSAGE BEGINS
+%s
+============ ANONYMOUS MESSAGE ENDS\n""" %msg
+ else:
+ msg = base64.encodestring(msg)
+ return """\
+This message is encoded in Base64 because it contains some nonprintable
+characters. It's possible that this message is a non-text object, that
+it was sent to using a reply block, that it was corrupted on its way to
+you, or that it's just plain junk.
+============ BASE-64 ENCODED ANONYMOUS MESSAGE BEGINS
+%s
+============ BASE-64 ENCODED ANONYMOUS MESSAGE ENDS\n""" % msg
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- Packet.py 1 Jul 2002 18:03:05 -0000 1.5
+++ Packet.py 6 Aug 2002 16:09:21 -0000 1.6
@@ -8,8 +8,8 @@
__all__ = [ 'ParseError', 'Message', 'Header', 'Subheader',
'parseMessage', 'parseHeader', 'parseSubheader',
'getTotalBlocksForRoutingInfoLen', 'ReplyBlock',
- 'IPV4Info', 'SMTPInfo', 'LocalInfo', 'parseIPV4Info',
- 'parseSMTPInfo', 'parseLocalInfo', 'ReplyBlock',
+ 'IPV4Info', 'SMTPInfo', 'MBOXInfo', 'parseIPV4Info',
+ 'parseSMTPInfo', 'parseMBOXInfo', 'ReplyBlock',
'parseReplyBlock', 'ENC_SUBHEADER_LEN',
'HEADER_LEN', 'PAYLOAD_LEN', 'MAJOR_NO', 'MINOR_NO',
'SECRET_LEN']
@@ -348,17 +348,17 @@
else:
return self.email
-def parseLocalInfo(s):
- """Convert the encoding of an LOCAL routinginfo into an LocalInfo
+def parseMBOXInfo(s):
+ """Convert the encoding of an MBOX routinginfo into an MBOXInfo
object."""
lst = s.split("\000",1)
if len(lst) == 1:
- return LocalInfo(s,None)
+ return MBOXInfo(s,None)
else:
- return LocalInfo(lst[0], lst[1])
+ return MBOXInfo(lst[0], lst[1])
-class LocalInfo:
- """Represents the routinginfo for a LOCAL hop.
+class MBOXInfo:
+ """Represents the routinginfo for an MBOX hop.
Fields: user (a user identifier), tag (an arbitrary tag, optional)."""
def __init__(self, user, tag):
Index: Queue.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Queue.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- Queue.py 25 Jul 2002 15:52:57 -0000 1.6
+++ Queue.py 6 Aug 2002 16:09:21 -0000 1.7
@@ -75,6 +75,7 @@
if not os.path.exists(location):
if create:
+ getLog().info("Trying to create queue %s", location)
os.mkdir(location, 0700)
else:
raise MixFatalError("No directory for queue %s" % location)
@@ -82,7 +83,7 @@
# Check permissions
mode = os.stat(location)[stat.ST_MODE]
if mode & 0077:
- # FFFF be more Draconian.
+ # XXXX be more Draconian.
getLog().warn("Worrisome more %o on directory %s", mode, location)
if scrub:
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- ServerInfo.py 28 Jul 2002 22:42:33 -0000 1.7
+++ ServerInfo.py 6 Aug 2002 16:09:21 -0000 1.8
@@ -13,6 +13,7 @@
import time
import os
import binascii
+import socket
from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
from mixminion.Packet import IPV4Info
@@ -62,16 +63,16 @@
"Allow": ("ALLOW*", C._parseAddressSet_allow, None),
"Deny": ("ALLOW*", C._parseAddressSet_deny, None),
},
- "Modules/MMTP" : {
+ "Outgoing/MMTP" : {
"Version": ("REQUIRE", None, None),
"Protocols": ("REQUIRE", None, None),
"Allow": ("ALLOW*", C._parseAddressSet_allow, None),
"Deny": ("ALLOW*", C._parseAddressSet_deny, None),
},
- "Modules/MBOX" : {
+ "Delivery/MBOX" : {
"Version": ("REQUIRE", None, None),
},
- "Modules/SMTP" : {
+ "Delivery/SMTP" : {
"Version": ("REQUIRE", None, None),
}
}
@@ -241,7 +242,9 @@
nickname = config['Server']['Nickname']
if not nickname:
- nickname = config['Incoming/MMTP'].get('IP', "<Unnamed server>")
+ nickname = socket.gethostname()
+ if not nickname or nickname.lower().startswith("localhost"):
+ nickname = config['Incoming/MMTP'].get('IP', "<Unknown host>")
contact = config['Server']['Contact-Email']
comments = config['Server']['Comments']
if not validAt:
@@ -305,7 +308,7 @@
if config["Outgoing/MMTP"].get("Enabled", 0):
info += """\
- [Modules/MMTP]
+ [Outgoing/MMTP]
Version: 1.0
Protocols: 1.0
"""
@@ -314,12 +317,8 @@
continue
info += "%s: %s" % (k, _rule(k=='Allow',v))
- if config["Delivery/MBOX"].get("Enabled", 0):
- info += """\
- [Modules/MBOX]
- Version: 1.0
- """
-
+ info += "".join(config.moduleManager.getServerInfoBlocks())
+
# Remove extra (leading) whitespace.
lines = [ line.strip() for line in info.split("\n") ]
# Remove empty lines
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -d -r1.15 -r1.16
--- test.py 28 Jul 2002 22:42:33 -0000 1.15
+++ test.py 6 Aug 2002 16:09:21 -0000 1.16
@@ -523,9 +523,9 @@
self.failUnlessRaises(ParseError, parseIPV4Info, ri+"x")
- def test_smtpinfolocalinfo(self):
+ def test_smtpinfomboxinfo(self):
for _class, _parse, _key in ((SMTPInfo, parseSMTPInfo, 'email'),
- (LocalInfo, parseLocalInfo, 'user')):
+ (MBOXInfo, parseMBOXInfo, 'user')):
ri = "no-such-user@wangafu.net\x00xyzzy"
inf = _parse(ri)
self.assertEquals(getattr(inf,_key), "no-such-user@wangafu.net")
@@ -776,7 +776,7 @@
longStr2 = longStr * 2
def getLongRoutingInfo(longStr2=longStr2):
- return LocalInfo("fred",longStr2)
+ return MBOXInfo("fred",longStr2)
server4 = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
server4.getRoutingInfo = getLongRoutingInfo
@@ -961,7 +961,7 @@
"fred", "Galaxy Far Away.", 0)
sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
infos+(None,))
s = "fred\x00RTRN"
self.assert_(loc.startswith(s))
@@ -974,7 +974,7 @@
self.server1, self.server3],
"fred", None)
sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
infos+(None,))
self.assert_(loc.startswith(s))
seed = loc[len(s):]
@@ -1099,7 +1099,7 @@
def pack(self): return "x"*200
server1X.getRoutingInfo = lambda _packable=_packable: _packable()
- m = bfm("Z", LOCAL_TYPE, "hello\000bye",
+ m = bfm("Z", MBOX_TYPE, "hello\000bye",
[self.server2, server1X, self.server3],
[server1X, self.server2, self.server3])
self.failUnlessRaises(ContentError, self.sp2.processMessage, m)
@@ -1123,7 +1123,7 @@
prng = AESCounterPRNG(" "*16)
reply1,s = brb([self.server1], SMTP_TYPE, "fred@invalid",0,prng)
prng = AESCounterPRNG(" "*16)
- reply2,s = brb([self.server2], LOCAL_TYPE, "foo",0,prng)
+ reply2,s = brb([self.server2], MBOX_TYPE, "foo",0,prng)
m = brm("Y", [self.server3], reply1)
m2 = brm("Y", [self.server3], reply2)
q, (a,m) = self.sp3.processMessage(m)
@@ -1174,7 +1174,7 @@
# Subhead that claims to be impossibly long: exit case
subh = parseSubheader(subh_real)
- subh.routingtype = LOCAL_TYPE
+ subh.routingtype = MBOX_TYPE
subh.setRoutingInfo("X"*10000)
m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
@@ -1192,7 +1192,7 @@
self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
# Corrupt payload
- m = bfm("Z", LOCAL_TYPE, "Z", [self.server1, self.server2],
+ m = bfm("Z", MBOX_TYPE, "Z", [self.server1, self.server2],
[self.server3])
m_x = m[:-30] + " "*30
assert len(m_x) == len(m)
@@ -1439,7 +1439,7 @@
m.append(pkt)
def conFactory(sock, context=_getTLSContext(1),
receiveMessage=receivedHook):
- tls = context.sock(sock)
+ tls = context.sock(sock, serverMode=1)
sock.setblocking(0)
return mixminion.MMTPServer.MMTPServerConnection(sock,tls,
receiveMessage)
@@ -1467,7 +1467,7 @@
self.server.process(0.1)
count = count + 1
- def ___testBlockingTransmission(self):
+ def testBlockingTransmission(self):
self.doTest(self._testBlockingTransmission)
def testNonblockingTransmission(self):
@@ -1542,8 +1542,6 @@
while not clientcon.isShutdown():
async.process(2)
-
-
severity = getLog().getMinSeverity()
getLog().setMinSeverity("ERROR") #suppress warning
try:
@@ -1807,7 +1805,7 @@
[Server]
EncryptIdentityKey: no
PublicKeyLifetime: 10 days
-EncryptPublicKey: no
+EncryptPrivateKey: no
Mode: relay
Nickname: The Server
Contact-Email: a@b.c
@@ -1826,14 +1824,15 @@
Allow: *
[Delivery/MBOX]
-Enabled: yes
+Enabled: no
+
"""
SERVER_CONFIG_SHORT = """
[Server]
EncryptIdentityKey: no
PublicKeyLifetime: 10 days
-EncryptPublicKey: no
+EncryptPrivateKey: no
Mode: relay
"""
@@ -1870,8 +1869,8 @@
eq(info['Incoming/MMTP']['Version'], "1.0")
eq(info['Incoming/MMTP']['Port'], 48099)
eq(info['Incoming/MMTP']['Protocols'], "1.0")
- eq(info['Modules/MMTP']['Version'], "1.0")
- eq(info['Modules/MMTP']['Protocols'], "1.0")
+ eq(info['Outgoing/MMTP']['Version'], "1.0")
+ eq(info['Outgoing/MMTP']['Protocols'], "1.0")
eq(info['Incoming/MMTP']['Allow'], [("192.168.0.16", "255.255.255.255",
1,1024),
("0.0.0.0", "0.0.0.0",
@@ -1879,7 +1878,7 @@
eq(info['Incoming/MMTP']['Deny'], [("192.168.0.16", "255.255.255.255",
0,65535),
])
- eq(info['Modules/MBOX']['Version'], "1.0")
+ eq(info['Delivery/MBOX']['Version'], "1.0")
# Now make sure everything was saved properly
keydir = os.path.join(d, "key_key1")