[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Big pile of changes -- 0.0.4rc1 draws ever closer.
Update of /home/minion/cvsroot/src/minion/lib/mixminion/server
In directory moria.mit.edu:/tmp/cvs-serv21169/src/minion/lib/mixminion/server
Modified Files:
EventStats.py HashLog.py MMTPServer.py Modules.py
PacketHandler.py ServerConfig.py ServerKeys.py ServerMain.py
Log Message:
Big pile of changes -- 0.0.4rc1 draws ever closer.
Main:
- Add a big warning banner to say we aren't compatible with anything
right now.
BuildMessage, Packet, PacketHandler:
- Change to new packet format (2048-bit RSA keys, overlapping
encryption)
setup.py:
- Bump version to 0.0.4alpha2
ClientMain:
- Add a handy 'ping' command -- too handy to stay in the codebase, but
useful for testing.
Common:
- Add a checkPrivateFile function to enforce file permissions.
Config, ServerConfig, ServerInfo, Modules:
- Refactor away a lot of useless code. I once thought the
configuration files would be self-reloading, and everybody would use
some kind of publish/subscribe mechanism to update themselves -- but
that's kinda silly in practise.
- Add a prevalidate function so that we can freak out sooner if the
version number doesn't match.
ServerInfo:
- Move IP to Incoming/MMTP section
- Bump Descriptor-Version to 0.2
Crypto:
- Make sure that private keys are stored mode 0600.
MMTPClient:
- Make 'bad authentication' a separate exception
- Check to make sure our certs aren't expired.
EventStats:
- Begin testing, debugging, refactoring.
- Fancier rule for rotation: don't rotate until we've accumulated data
for a sufficiently long time, even if a long time has passed. (That
is, if we've been offline for 23 hours, don't trigger a daily
rotation.)
MMTPServer:
- Refactor client connection cache
- Use PeerCertificateCache to remember which certificates we've
already verified.
Index: EventStats.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/EventStats.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- EventStats.py 26 Mar 2003 16:36:46 -0000 1.1
+++ EventStats.py 26 Apr 2003 14:39:59 -0000 1.2
@@ -12,9 +12,11 @@
from threading import RLock
from time import time
-from mixminion.Common import formatTime, LOG
+from mixminion.Common import formatTime, LOG, previousMidnight, floorDiv, \
+ createPrivateDir
-EVENTS = [ 'ReceivedPacket',
+# _EVENTS: a list of all recognized event types.
+_EVENTS = [ 'ReceivedPacket',
'AttemptedRelay',
'SuccessfulRelay', 'FailedRelay', 'UnretriableRelay',
'AttemptedDelivery',
@@ -22,20 +24,98 @@
]
class NilEventLog:
+ """Null implementation of EventLog interface: ignores all events and
+ logs nothing.
+ """
def __init__(self):
pass
- def save(self):
+ def save(self, now=None):
+ """Flushes this eventlog to disk."""
pass
- def log(self, event, arg=None):
+ def _log(self, event, arg=None):
+ """Notes that an event has occurred.
+ event -- the type of event to note
+ arg -- an optional topic of the event.
+ """
pass
-
+ def receivedPacket(self, arg=None):
+ """Called whenever a packet is received via MMTP."""
+ self._log("ReceivedPacket", arg)
+ def attemptedRelay(self, arg=None):
+ """Called whenever we attempt to relay a packet via MMTP."""
+ self._log("AttemptedRelay", arg)
+ def successfulRelay(self, arg=None):
+ """Called whenever packet delivery via MMTP succeeds"""
+ self._log("SuccessfulRelay", arg)
+ def failedRelay(self, arg=None):
+ """Called whenever packet delivery via MMTP fails retriably"""
+ self._log("FailedRelay", arg)
+ def unretriableRelay(self, arg=None):
+ """Called whenever packet delivery via MMTP fails unretriably"""
+ self._log("UnretriableRelay", arg)
+ def attemptedDelivery(self, arg=None):
+ """Called whenever we attempt to deliver a message via an exit
+ module.
+ """
+ self._log("AttemptedDelivery", arg)
+ def successfulDelivery(self, arg=None):
+ """Called whenever we successfully deliver a message via an exit
+ module.
+ """
+ self._log("SuccessfulDelivery", arg)
+ def failedDelivery(self, arg=None):
+ """Called whenever an attempt to deliver a message via an exit
+ module fails retriably.
+ """
+ self._log("FailedDelivery", arg)
+ def unretriableDelivery(self, arg=None):
+ """Called whenever an attempt to deliver a message via an exit
+ module fails unretriably.
+ """
+ self._log("UnretriableDelivery", arg)
+
class EventLog(NilEventLog):
- # Fields:
- # count (event -> arg -> value)
- # lastRotation
- # filename, historyFile
- # rotateInterval
+ """An EventLog records events, aggregates them according to some time
+ periods, and logs the totals to disk.
+
+ Currently we retain two log files: one holds an interval-by-interval
+ human-readable record of past intervals; the other holds a pickled
+ record of events in the current interval.
+
+ We take some pains to avoid flushing the statistics when too
+ little time has passed. We only rotate an aggregated total to disk
+ when:
+ - An interval has passsed since the last rotation time
+ AND
+ - We have accumulated events for at least 75% of an interval's
+ worth of time.
+
+ The second requirement prevents the following unpleasant failure mode:
+ - We set the interval to '1 day'. At midnight on monday,
+ we rotate. At 00:05, we go down. At 23:55 we come back
+ up. At midnight at tuesday, we noticing that it's been one
+ day since the last rotation, and rotate again -- thus making
+ a permanent record that reflects 10 minutes worth of traffic,
+ potentially exposing more about individual users than we should.
+ """
+ ### Fields:
+ # count: a map from event name -> argument|None -> total events received.
+ # lastRotation: the time at which we last flushed the log to disk and
+ # reset the log.
+ # filename, historyFile: Names of the pickled and long-term event logs.
+ # rotateInterval: Interval after which to flush the current statistics
+ # to disk.
+ # _lock: a threading.RLock object that must be held when modifying this
+ # object.
+ # accumulatedTime: number of seconds since last rotation that we have
+ # been logging events.
+ # lastSave: last time we saved the file.
+ ### Pickled format:
+ # Map from {"count","lastRotation","accumulatedTime"} to the values
+ # for those fields.
def __init__(self, filename, historyFile, interval):
+ """Intializes an EventLog that caches events in 'filename', and
+ periodically writes to 'historyFile' every 'interval' seconds."""
NilEventLog.__init__(self)
if os.path.exists(filename):
# XXXX If this doesn't work, then we should
@@ -44,46 +124,61 @@
f.close()
assert self.count is not None
assert self.lastRotation is not None
+ assert self.accumulatedTime is not None
else:
self.count = {}
- for e in EVENTS:
+ for e in _EVENTS:
self.count[e] = {}
self.lastRotation = time()
+ self.accumulatedTime = 0
self.filename = filename
self.historyFilename = historyFile
+ for fn in filename, historyFile:
+ parent = os.path.split(fn)[0]
+ createPrivateDir(parent)
self.rotateInterval = interval
+ self.lastSave = time()
+ self._setNextRotation()
self._lock = RLock()
self.save()
- def save(self):
+ def save(self, now=None):
+ """Write the statistics in this log to disk, rotating if necessary."""
try:
self._lock.acquire()
- if time() > self.lastRotation + self.rotateInterval:
- self._rotate()
- self._save()
+ self._save(now)
finally:
self._lock.release()
- def _save(self):
- # Must hold lock
+ def _save(self, now=None):
+ """Implements 'save' method. For internal use. Must hold self._lock
+ to invoke."""
LOG.debug("Syncing statistics to disk")
+ if not now: now = time()
+ if now > self.nextRotation:
+ self._rotate()
tmpfile = self.filename + "_tmp"
try:
os.unlink(tmpfile)
except:
pass
+ self.accumulatedTime += int(now-self.lastSave)
+ self.lastSave = now
f = open(tmpfile, 'wb')
- cPickle.dump({ 'count' : self.count, 'filename' : self.filename,
- 'lastRotation' : self.lastRotation },
+ cPickle.dump({ 'count' : self.count,
+ 'lastRotation' : self.lastRotation,
+ 'accumulatedTime' : self.accumulatedTime,
+ },
f, 1)
f.close()
os.rename(tmpfile, self.filename)
- def log(self, event, arg=None):
+ def _log(self, event, arg=None):
try:
self._lock.acquire()
- if time() > self.lastRotation + self.rotateInterval:
+ if time() > self.nextRotation:
self._rotate()
+ self._save()
try:
self.count[event][arg] += 1
except KeyError:
@@ -95,30 +190,33 @@
self._lock.release()
def _rotate(self, now=None):
- # XXXX Change behavior: rotate indexed on midnight.
+ """Flush all events since the last rotation to the history file,
+ and clears the current event log."""
# Must hold lock
LOG.debug("Flushing statistics log")
- if now is None:
- now = time()
+ if now is None: now = time() #????
+
f = open(self.historyFilename, 'a')
self.dump(f)
f.close()
-
+
+ self.accumulatedTime = 0
self.count = {}
- for e in EVENTS:
+ for e in _EVENTS:
self.count[e] = {}
- self.lastRotation = now
- self._save()
+ self.lastRotation = self.nextRotation
+ self._setNextRotation()
def dump(self, f):
+ """Write the current data to a file handle 'f'."""
try:
self._lock.acquire()
startTime = self.lastRotation
endTime = time()
print >>f, "========== From %s to %s:" % (formatTime(startTime,1),
formatTime(endTime,1))
- for event in EVENTS:
+ for event in _EVENTS:
count = self.count[event]
if len(count) == 0:
print >>f, " %s: 0" % event
@@ -131,6 +229,7 @@
args = count.keys()
args.sort()
length = max([ len(str(arg)) for arg in args ])
+ length = max((length, 10))
fmt = " %"+str(length)+"s: %s"
for arg in args:
v = count[arg]
@@ -141,25 +240,50 @@
finally:
self._lock.release()
-def setLog(eventLog):
- global THE_EVENT_LOG
- global log
- global save
- THE_EVENT_LOG = eventLog
- log = THE_EVENT_LOG.log
- save = THE_EVENT_LOG.save
+
+ def _setNextRotation(self, now=None):
+ # ???? Lock to 24-hour cycle
+
+ # This is a little weird. We won't save *until*:
+ # - .75 * rotateInterval seconds are accumulated.
+ # AND - rotateInterval seconds have elapsed since the last
+ # rotation.
+ #
+ # IF the rotation interval is divisible by one hour, we also
+ # round to the hour, up to 5 minutes down and 55 up.
+ if not now: now = time()
+
+ secToGo = max(0, self.rotateInterval * 0.75 - self.accumulatedTime)
+ self.nextRotation = max(self.lastRotation + self.rotateInterval,
+ now + secToGo)
+
+ if not self.lastRotation: self.lastRotation = now
+ if now - self.lastRotation <= self.rotateInterval * 1.2:
+ base = self.lastRotation
+ else:
+ base = now
+
+ self.nextRotation = base + self.rotateInterval
+
+ if (self.rotateInterval % 3600) == 0:
+ mid = previousMidnight(self.nextRotation)
+ rest = self.nextRotation - mid
+ self.nextRotation = mid + 3600 * floorDiv(rest+55*60, 3600)
def configureLog(config):
+ """DOCDOC"""
+ global log
if config['Server']['LogStats']:
LOG.info("Enabling statistics logging")
- statsfile = config['Server']['StatsFile']
+ homedir = config['Server']['Homedir']
+ statsfile = config['Server'].get('StatsFile')
if not statsfile:
- homedir = config['Server']['Homedir']
statsfile = os.path.join(homedir, "stats")
- workfile = statsfile + ".work"
- setLog(EventLog(
- workfile, statsfile, config['Server']['StatsInterval'][2]))
+ workfile = os.path.join(homedir, "work", "stats.tmp")
+ log = EventLog(
+ workfile, statsfile, config['Server']['StatsInterval'][2])
else:
+ log = NilEventLog()
LOG.info("Statistics logging disabled")
-setLog(NilEventLog())
+log = NilEventLog()
Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/HashLog.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- HashLog.py 13 Apr 2003 15:54:42 -0000 1.8
+++ HashLog.py 26 Apr 2003 14:39:59 -0000 1.9
@@ -63,6 +63,7 @@
'keyid'."""
parent = os.path.split(filename)[0]
createPrivateDir(parent)
+ # XXXX004 catch empty hashlog.
self.log = anydbm.open(filename, 'c')
LOG.debug("Opening database %s for packet digests", filename)
if isinstance(self.log, dumbdbm._Database):
Index: MMTPServer.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/MMTPServer.py,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -d -r1.24 -r1.25
--- MMTPServer.py 22 Apr 2003 01:45:22 -0000 1.24
+++ MMTPServer.py 26 Apr 2003 14:39:59 -0000 1.25
@@ -664,7 +664,8 @@
sentCallback -- None, or a function of (msg, handle) to be called
whenever a message is successfully sent.
failCallback -- None, or a function of (msg, handle, retriable)
- to be called when messages can't be sent."""
+ to be called when messages can't be sent.
+ DOCDOC certcache,finishedCallback"""
# Generate junk before connecting to avoid timing attacks
self.junk = [] #XXXX doc this field.
@@ -855,6 +856,8 @@
callbacks for message success and failure."""
##
# clientConByAddr
+ # certificateCache
+ #DOCDOC fields
def __init__(self, config, tls):
AsyncServer.__init__(self)
@@ -879,6 +882,7 @@
self.listener.register(self)
self._timeout = config['Server']['Timeout'][2]
self.clientConByAddr = {}
+ self.certificateCache = PeerCertificateCache()
def setContext(self, context):
"""Change the TLS context used for newly received connections.
@@ -921,26 +925,30 @@
pass
try:
+ addr = (ip, port, keyID)
+ finished = lambda addr=addr, self=self: self.__clientFinished(addr)
con = MMTPClientConnection(self.context,
- ip, port, keyID, messages, handles,
- self.onMessageSent,
- self.onMessageUndeliverable)
- #XXXX004
- con.finishedCallback = (lambda con=con,self=self:
- self.__clientFinished(con))
+ ip, port, keyID, messages, handles,
+ sentCallback=self.onMessageSent,
+ failCallback=self.onMessageUndeliverable,
+ finishedCallback=finished,
+ certCache=self.certificateCache)
con.register(self)
- self.clientConByAddr[con.getAddr()] = con
+ assert addr == con.getAddr()
+ self.clientConByAddr[addr] = con
except socket.error, e:
LOG.error("Unexpected socket error connecting to %s:%s: %s",
ip, port, e)
for m,h in zip(messages, handles):
self.onMessageUndeliverable(m,h,1)
- def __clientFinished(self, con):
+ def __clientFinished(self, addr):
+ """DOCDOC"""
try:
- del self.clientConByAddr[con.getAddr()]
+ del self.clientConByAddr[addr]
except KeyError:
- LOG.warn("Didn't find client connection in address map")
+ LOG.warn("Didn't find client connection to %s in address map",
+ addr)
def onMessageReceived(self, msg):
pass
Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/Modules.py,v
retrieving revision 1.34
retrieving revision 1.35
diff -u -d -r1.34 -r1.35
--- Modules.py 26 Mar 2003 16:36:46 -0000 1.34
+++ Modules.py 26 Apr 2003 14:39:59 -0000 1.35
@@ -135,21 +135,21 @@
"""Instead of queueing our message, pass it directly to the underlying
DeliveryModule."""
try:
- EventStats.log('AttemptedDelivery') #XXXX
+ EventStats.log.attemptedDelivery() #XXXX
res = self.module.processMessage(packet)
if res == DELIVER_OK:
- EventStats.log('SuccessfulDelivery') #XXXXq
+ EventStats.log.successfulDelivery() #XXXX
return
elif res == DELIVER_FAIL_RETRY:
LOG.error("Unable to retry delivery for message")
- EventStats.log('UnretriableDelivery') #XXXX
+ EventStats.log.unretriableDelivery() #XXXX
else:
LOG.error("Unable to deliver message")
- EventStats.log('UnretriableDelivery') #XXXX
+ EventStats.log.unretriableDelivery() #XXXX
except:
LOG.error_exc(sys.exc_info(),
"Exception delivering message")
- EventStats.log('UnretriableDelivery') #XXXX
+ EventStats.log.unretriableDeliery() #XXXX
def sendReadyMessages(self):
# We do nothing here; we already delivered the messages
@@ -172,23 +172,23 @@
def _deliverMessages(self, msgList):
for handle, packet, n_retries in msgList:
try:
- EventStats.log('AttemptedDelivery') #XXXX
+ EventStats.log.attemptedDelivery() #XXXX
result = self.module.processMessage(packet)
if result == DELIVER_OK:
self.deliverySucceeded(handle)
- EventStats.log('SuccessfulDelivery') #XXXX
+ EventStats.log.successfulDelivery() #XXXX
elif result == DELIVER_FAIL_RETRY:
self.deliveryFailed(handle, 1)
- EventStats.log('FailedDelivery') #XXXX
+ EventStats.log.failedDelivery() #XXXX
else:
LOG.error("Unable to deliver message")
self.deliveryFailed(handle, 0)
- EventStats.log('UnretriableDelivery') #XXXX
+ EventStats.log.unretriableDelivery() #XXXX
except:
LOG.error_exc(sys.exc_info(),
"Exception delivering message")
self.deliveryFailed(handle, 0)
- EventStats.log('UnretriableDelivery') #XXXX
+ EventStats.log.unretriableDelivery() #XXXX
class DeliveryThread(threading.Thread):
"""A thread object used by ModuleManager to send messages in the
Index: PacketHandler.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/PacketHandler.py,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- PacketHandler.py 18 Apr 2003 18:32:36 -0000 1.13
+++ PacketHandler.py 26 Apr 2003 14:39:59 -0000 1.14
@@ -110,6 +110,8 @@
# Break into headers and payload
msg = Packet.parseMessage(msg)
header1 = Packet.parseHeader(msg.header1)
+ encSubh = header1[:Packet.ENC_SUBHEADER_LEN]
+ header1 = header1[Packet.ENC_SUBHEADER_LEN:]
# Try to decrypt the first subheader. Try each private key in
# order. Only fail if all private keys fail.
@@ -119,7 +121,7 @@
try:
for pk, hashlog in zip(self.privatekey, self.hashlog):
try:
- subh = Crypto.pk_decrypt(header1[0], pk)
+ subh = Crypto.pk_decrypt(encSubh, pk)
break
except Crypto.CryptoError, err:
e = err
@@ -137,7 +139,7 @@
raise ContentError("Invalid protocol version")
# Check the digest of all of header1 but the first subheader.
- if subh.digest != Crypto.sha1(header1[1:]):
+ if subh.digest != Crypto.sha1(header1):
raise ContentError("Invalid digest")
# Get ready to generate message keys.
@@ -159,23 +161,26 @@
# using this more than once.
header_sec_key = Crypto.aes_key(keys.get(Crypto.HEADER_SECRET_MODE))
- # If the subheader says that we have extra blocks of routing info,
- # decrypt and parse them now.
- if subh.isExtended():
- nExtra = subh.getNExtraBlocks()
- if (rt < Packet.MIN_EXIT_TYPE) or (nExtra > 15):
- # None of the native methods allow multiple blocks; no
- # size can be longer than the number of bytes in the rest
- # of the header.
- raise ContentError("Impossibly long routing info length")
+ # Prepare key to generate padding
+ junk_key = Crypto.aes_key(keys.get(Crypto.RANDOM_JUNK_MODE))
- extra = Crypto.ctr_crypt(header1[1:1+nExtra], header_sec_key)
- subh.appendExtraBlocks(extra)
- remainingHeader = header1[1+nExtra:]
- else:
- nExtra = 0
- remainingHeader = header1[1:]
+ # Pad the rest of header 1
+ header1 += Crypto.prng(junk_key,
+ Packet.OAEP_OVERHEAD + Packet.MIN_SUBHEADER_LEN
+ + subh.routinglen)
+
+ # Decrypt the rest of header 1, encrypting the padding.
+ header1 = Crypto.ctr_crypt(header1, header_sec_key)
+ # If the subheader says that we have extra routing info that didn't
+ # fit in the RSA-encrypted part, get it now.
+ overflowLength = subh.getOverflowLength()
+ if overflowLength:
+ subh.appendOverflow(header1[:overflowLength])
+ header1 = header1[overflowLength:]
+
+ header1 = subh.underflow + header1
+
# Decrypt the payload.
payload = Crypto.lioness_decrypt(msg.payload,
keys.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE))
@@ -191,14 +196,6 @@
# routing type.
if rt not in (Packet.SWAP_FWD_TYPE, Packet.FWD_TYPE):
raise ContentError("Unrecognized Mixminion routing type")
-
- # Pad the rest of header 1
- remainingHeader = remainingHeader +\
- Crypto.prng(keys.get(Crypto.PRNG_MODE),
- Packet.HEADER_LEN-len(remainingHeader))
-
- # Decrypt the rest of header 1, encrypting the padding.
- header1 = Crypto.ctr_crypt(remainingHeader, header_sec_key, nExtra*128)
# Decrypt header 2.
header2 = Crypto.lioness_decrypt(msg.header2,
Index: ServerConfig.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerConfig.py,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- ServerConfig.py 18 Apr 2003 17:41:38 -0000 1.21
+++ ServerConfig.py 26 Apr 2003 14:39:59 -0000 1.22
@@ -33,20 +33,21 @@
mixminion.Config._ConfigFile.__init__(self, fname, string)
- def validate(self, sections, entries, lines, contents):
- def _haveEntry(entries, section, ent):
+ def validate(self, lines, contents):
+ def _haveEntry(self, section, ent):
+ entries = self._sectionEntries
return len([e for e in entries[section] if e[0] == ent]) != 0
# Pre-emptively configure the log before validation, so we don't
# write to the terminal if we've been asked not to.
- if not sections['Server'].get("EchoMessages", 0):
+ if not self['Server'].get("EchoMessages", 0):
LOG.handlers = []
# ???? This can't be the best way to do this.
# Now, validate the host section.
- mixminion.Config._validateHostSection(sections.get('Host', {}))
+ mixminion.Config._validateHostSection(self['Host'])
# Server section
- server = sections['Server']
+ server = self['Server']
bits = server['IdentityKeyBits']
if not (2048 <= bits <= 4096):
raise ConfigError("IdentityKeyBits must be between 2048 and 4096")
@@ -59,19 +60,19 @@
if server['PublicKeySloppiness'][2] > 20*60:
raise ConfigError("PublicKeySloppiness must be <= 20 minutes.")
- if _haveEntry(entries, 'Server', 'NoDaemon'):
+ if _haveEntry(self, 'Server', 'NoDaemon'):
LOG.warn("The NoDaemon option is obsolete. Use Daemon instead.")
- if _haveEntry(entries, 'Server', 'Mode'):
+ if _haveEntry(self, 'Server', 'Mode'):
LOG.warn("Mode specification is not yet supported.")
mixInterval = server['MixInterval'][2]
if mixInterval < 30*60:
LOG.warn("Dangerously low MixInterval")
if server['MixAlgorithm'] == 'TimedMixPool':
- if _haveEntry(entries, 'Server', 'MixPoolRate'):
+ if _haveEntry(self, 'Server', 'MixPoolRate'):
LOG.warn("Option MixPoolRate is not used for Timed mixing.")
- if _haveEntry(entries, 'Server', 'MixPoolMinSize'):
+ if _haveEntry(self, 'Server', 'MixPoolMinSize'):
LOG.warn("Option MixPoolMinSize is not used for Timed mixing.")
else:
rate = server['MixPoolRate']
@@ -81,19 +82,23 @@
if minSize < 0:
raise ConfigError("MixPoolMinSize %s must be nonnegative.")
- if not sections['Incoming/MMTP'].get('Enabled'):
+ if not self['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')]:
+ if [e for e in self._sectionEntries['Incoming/MMTP']
+ if e[0] in ('Allow', 'Deny')]:
LOG.warn("Allow/deny are not yet supported")
- if not sections['Outgoing/MMTP'].get('Enabled'):
+ if not self['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')]:
+ if [e for e in self._sectionEntries['Outgoing/MMTP']
+ if e[0] in ('Allow', 'Deny')]:
LOG.warn("Allow/deny are not yet supported")
- _validateRetrySchedule(mixInterval, entries, "Outgoing/MMTP")
+ _validateRetrySchedule(mixInterval, self._sectionEntries,
+ "Outgoing/MMTP")
- self.moduleManager.validate(sections, entries, lines, contents)
+ self.moduleManager.validate(self._sections, self._sectionEntries,
+ lines, contents)
def __loadModules(self, section, sectionEntries):
"""Callback from the [Server] section of a config file. Parses
@@ -110,22 +115,37 @@
"Return the module manager initialized by this server."
return self.moduleManager
- def isSecure(self):
- "Return true iff this configuration is reasonably secure."
+ def getInsecurities(self):
+ """Return false iff this configuration is reasonably secure.
+ Otherwise, return a list of reasons why it isn't."""
reasons = ["Software is alpha"]
-
+
+ # SERVER
server = self['Server']
if server['LogLevel'] in ('TRACE', 'DEBUG'):
reasons.append("Log is too verbose")
- if server['LogStats'] and server['LogInterval'][2] < 24*60*60:
+ if server['LogStats'] and server['StatsInterval'][2] < 24*60*60:
reasons.append("StatsInterval is too short")
if not server["EncryptIdentityKey"]:
reasons.append("Identity key is not encrypted")
+ # ???? Pkey lifetime, sloppiness?
if server["MixAlgorithm"] not in _SECURE_MIX_RULES:
reasons.append("Mix algorithm is not secure")
- #XXXX004 finish!
-
+ else:
+ if server["MixPoolMinSize"] < 5:
+ reasons.append("MixPoolMinSize is too small")
+ #MixPoolRate?
+ if server["MixInterval"][2] < 30*60:
+ reasons.append("Mix interval under 30 minutes")
+ # ???? DIRSERVERS?
+
+ # ???? Incoming/MMTP
+
+ # ???? Outgoing/MMTP
+
+ # ???? Modules?
+
def _validateRetrySchedule(mixInterval, entries, sectionname,
entryname='Retry'):
#XXXX writeme.
Index: ServerKeys.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerKeys.py,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -d -r1.18 -r1.19
--- ServerKeys.py 18 Apr 2003 18:32:36 -0000 1.18
+++ ServerKeys.py 26 Apr 2003 14:39:59 -0000 1.19
@@ -23,9 +23,11 @@
import mixminion.server.PacketHandler
import mixminion.server.MMTPServer
-from mixminion.ServerInfo import ServerInfo, PACKET_KEY_BYTES, signServerInfo
+from mixminion.ServerInfo import ServerInfo, PACKET_KEY_BYTES, MMTP_KEY_BYTES,\
+ signServerInfo
from mixminion.Common import LOG, MixError, MixFatalError, createPrivateDir, \
- formatBase64, formatDate, formatTime, previousMidnight, secureDelete
+ checkPrivateFile, formatBase64, formatDate, formatTime, previousMidnight,\
+ secureDelete
#----------------------------------------------------------------------
class ServerKeyring:
@@ -153,6 +155,7 @@
fn = os.path.join(self.keyDir, "identity.key")
bits = self.config['Server']['IdentityKeyBits']
if os.path.exists(fn):
+ checkPrivateFile(fn)
key = mixminion.Crypto.pk_PEM_load(fn, password)
keylen = key.get_modulus_bytes()*8
if keylen != bits:
@@ -326,7 +329,7 @@
"""Return out current ip/port/keyid tuple"""
keys = self.getServerKeyset()
desc = keys.getServerDescriptor()
- return (desc['Server']['IP'],
+ return (desc['Incoming/MMTP']['IP'],
desc['Incoming/MMTP']['Port'],
desc['Incoming/MMTP']['Key-Digest'])
@@ -367,6 +370,8 @@
def load(self, password=None):
"""Read the short-term keys from disk. Must be called before
getPacketKey or getMMTPKey."""
+ checkPrivateFile(self.packetKeyFile)
+ checkPrivateFile(self.mmtpKeyFile)
self.packetKey = mixminion.Crypto.pk_PEM_load(self.packetKeyFile,
password)
self.mmtpKey = mixminion.Crypto.pk_PEM_load(self.mmtpKeyFile,
@@ -432,7 +437,6 @@
if config_s['Comments'] != info_s['Comments']:
warn("Mismatched comments field.")
- lifetime = info_s['Valid-Until'] - info_s['Valid-After']
if (previousMidnight(info_s['Valid-Until']) !=
previousMidnight(config_s['PublicKeyLifetime'][2] +
info_s['Valid-After'])):
@@ -448,7 +452,7 @@
warn("Mismatched ports: %s configured; %s published.",
config_im['Port'], info_im['Port'])
- info_ip = info['Server']['IP']
+ info_ip = info['Incoming/MMTP']['IP']
if config_im['IP'] == '0.0.0.0':
guessed = _guessLocalIP()
if guessed != config_im['IP']:
@@ -503,7 +507,7 @@
if useServerKeys is None:
# 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)
+ mmtpKey = mixminion.Crypto.pk_generate(MMTP_KEY_BYTES*8)
# ...and save them to disk, setting up our directory structure while
# we're at it.
@@ -535,6 +539,11 @@
if not validAt:
validAt = now
+ if config.getInsecurities():
+ secure = "no"
+ else:
+ secure = "yes"
+
# Calculate descriptor and X509 certificate lifetimes.
# (Round validAt to previous mignight.)
validAt = mixminion.Common.previousMidnight(validAt+30)
@@ -577,7 +586,8 @@
"MMTPProtocolsOut" : mmtpProtocolsOut,
"PacketFormat" : "%s.%s"%(mixminion.Packet.MAJOR_NO,
mixminion.Packet.MINOR_NO),
- "mm_version" : mixminion.__version__
+ "mm_version" : mixminion.__version__,
+ "Secure" : secure
}
# If we don't know our IP address, try to guess
@@ -593,8 +603,7 @@
# Signature: lines.
info = """\
[Server]
- Descriptor-Version: 0.1
- IP: %(IP)s
+ Descriptor-Version: 0.2
Nickname: %(Nickname)s
Identity: %(Identity)s
Digest:
@@ -605,6 +614,7 @@
Packet-Key: %(PacketKey)s
Packet-Formats: %(PacketFormat)s
Software: Mixminion %(mm_version)s
+ Secure-Configuration: %(Secure)s
""" % fields
if contact:
info += "Contact: %s\n"%contact
@@ -616,6 +626,7 @@
info += """\
[Incoming/MMTP]
Version: 0.1
+ IP: %(IP)s
Port: %(Port)s
Key-Digest: %(KeyID)s
Protocols: %(MMTPProtocolsIn)s
Index: ServerMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerMain.py,v
retrieving revision 1.49
retrieving revision 1.50
diff -u -d -r1.49 -r1.50
--- ServerMain.py 22 Apr 2003 02:07:34 -0000 1.49
+++ ServerMain.py 26 Apr 2003 14:39:59 -0000 1.50
@@ -274,20 +274,20 @@
def onMessageReceived(self, msg):
self.incomingQueue.queueMessage(msg)
- EventStats.log("ReceivedPacket", None) # XXXX Replace with server.
+ EventStats.receivedPacket("ReceivedPacket", None) # XXXX Replace with server.
def onMessageSent(self, msg, handle):
self.outgoingQueue.deliverySucceeded(handle)
- EventStats.log("AttemptedRelay", None) # XXXX replace with addr
- EventStats.log("SuccessfulRelay", None) # XXXX replace with addr
+ EventStats.log.attemptedRelay() # XXXX replace with addr
+ EventStats.log.successfulRelay() # XXXX replace with addr
def onMessageUndeliverable(self, msg, handle, retriable):
self.outgoingQueue.deliveryFailed(handle, retriable)
- EventStats.log("AttemptedRelay", None) # XXXX replace with addr
+ EventStats.log.attemptedRelay() # XXXX replace with addr
if retriable:
- EventStats.log("FailedRelay", None) # XXXX replace with addr
+ EventStats.log.failedRelay() # XXXX replace with addr
else:
- EventStats.log("UnretriableRelay", None) # XXXX replace with addr
+ EventStats.log.unretriableRelay() # XXXX replace with addr
#----------------------------------------------------------------------
class CleaningThread(threading.Thread):
@@ -444,6 +444,8 @@
# The pid file.
self.pidFile = os.path.join(homeDir, "pid")
+ #XXXX004 Catch ConfigError for bad serverinfo.
+ #XXXX004 Check whether config matches serverinfo
self.keyring = mixminion.server.ServerKeys.ServerKeyring(config)
if self.keyring._getLiveKey() is None:
LOG.info("Generating a month's worth of keys.")
@@ -555,7 +557,7 @@
LOG.info("Caught sighup")
LOG.info("Resetting logs")
LOG.reset()
- EventStats.save()
+ EventStats.log.save()
GOT_HUP = 0
# Make sure that our worker threads are still running.
if not (self.cleaningThread.isAlive() and
@@ -641,7 +643,7 @@
self.packetHandler.close()
- EventStats.save()
+ EventStats.log.save()
try:
self.lockFile.release()
@@ -821,7 +823,7 @@
config = configFromServerArgs(cmd, args)
_signalServer(config, 1)
EventStats.configureLog(config)
- EventStats.THE_EVENT_LOG.dump(sys.stdout)
+ EventStats.log.dump(sys.stdout)
#----------------------------------------------------------------------
_SIGNAL_SERVER_USAGE = """\