[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Add heavily modified patch from Brian Warner for status...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv7396/lib/mixminion
Modified Files:
BuildMessage.py ClientMain.py Common.py Fragments.py Main.py
Packet.py test.py
Log Message:
Add heavily modified patch from Brian Warner for status-fd and packet-counting; refactor argument parsing; add unit tests
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.73
retrieving revision 1.74
diff -u -d -r1.73 -r1.74
--- BuildMessage.py 23 Mar 2004 00:07:02 -0000 1.73
+++ BuildMessage.py 14 May 2004 23:44:08 -0000 1.74
@@ -13,7 +13,8 @@
import mixminion.Crypto as Crypto
import mixminion.Fragments
from mixminion.Packet import *
-from mixminion.Common import MixError, MixFatalError, LOG, UIError
+from mixminion.Common import MixError, MixFatalError, LOG, STATUS, UIError, \
+ formatBase64
import mixminion.Packet
import mixminion._minionlib
@@ -22,7 +23,25 @@
__all__ = ['buildForwardPacket', 'buildEncryptedForwardPacket',
'buildReplyPacket', 'buildReplyBlock', 'checkPathLength',
- 'encodeMessage', 'decodePayload' ]
+ 'encodeMessage', 'decodePayload', 'getNPacketsToEncode' ]
+
+def getNPacketsToEncode(message, overhead, uncompressedFragmentPrefix=""):
+ """Return the number of packets that would be needed to encode 'message'.
+ Arguments are as for encodeMessage.
+ """
+ assert overhead in (0, ENC_FWD_OVERHEAD)
+ compressedLen = len(compressData(message))
+
+ paddingLen = PAYLOAD_LEN - SINGLETON_PAYLOAD_OVERHEAD - overhead - compressedLen
+ if paddingLen >= 0:
+ return 1
+
+ if uncompressedFragmentPrefix:
+ compressedLen += len(uncompressedFragmentPrefix)
+
+ p = mixminion.Fragments.FragmentationParams(compressedLen, overhead)
+
+ return p.n * p.nChunks
def encodeMessage(message, overhead, uncompressedFragmentPrefix="",
paddingPRNG=None):
@@ -31,7 +50,7 @@
of strings, each of which is a message payload suitable for use in
build*Message.
- payload: the initial payload
+ message: the initial message
overhead: number of bytes to omit from each payload,
given the type ofthe message encoding.
(0 or ENC_FWD_OVERHEAD)
@@ -64,7 +83,7 @@
p.computeHash()
return [ p.pack() ]
- # Okay, we need fo fragment the message. First, add the prefix if needed.
+ # Okay, we need to fragment the message. First, add the prefix if needed.
if uncompressedFragmentPrefix:
payload = uncompressedFragmentPrefix+payload
# Now generate a message ID
@@ -296,8 +315,10 @@
prng = Crypto.AESCounterPRNG(Crypto.sha1(seed+userKey+"Generate")[:16])
- return _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng,
- seed)[0]
+ replyBlock, secrets, tag = _buildReplyBlockImpl(path, exitType, exitInfo,
+ expiryTime, prng, seed)
+ STATUS.log("GENERATED_SURB", formatBase64(tag))
+ return replyBlock
def checkPathLength(path1, path2, exitType, exitInfo, explicitSwap=0,
suppressTag=0):
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.178
retrieving revision 1.179
diff -u -d -r1.178 -r1.179
--- ClientMain.py 2 May 2004 18:45:15 -0000 1.178
+++ ClientMain.py 14 May 2004 23:44:09 -0000 1.179
@@ -23,8 +23,9 @@
import mixminion.MMTPClient
from mixminion.Common import LOG, Lockfile, LockfileLocked, MixError, \
- MixFatalError, MixProtocolBadAuth, MixProtocolError, UIError, \
- UsageError, createPrivateDir, englishSequence, floorDiv, isPrintingAscii,\
+ MixFatalError, MixProtocolBadAuth, MixProtocolError, STATUS, UIError, \
+ UsageError, createPrivateDir, englishSequence, floorDiv, formatTime, \
+ isPrintingAscii,\
isSMTPMailbox, readFile, stringContains, succeedingMidnight, writeFile, \
previousMidnight
from mixminion.Packet import encodeMailHeaders, ParseError, parseMBOXInfo, \
@@ -768,6 +769,8 @@
The .init() method initializes a config file, logging, a
MixminionClient object, or the ClientDirectory object as requested.
The parsePath method parses the path as given.
+
+ DOCDOC --status-fd
"""
##Fields:
# want*: as given as arguments to __init__
@@ -871,7 +874,8 @@
(v, o))
self.download = dl
elif o in ('-t', '--to'):
- assert wantForwardPath or wantReplyPath
+ #assert wantForwardPath or wantReplyPath
+ #XXXX008 reenable, sanely.
if self.exitAddress is not None:
raise UIError("Multiple addresses specified.")
try:
@@ -879,7 +883,7 @@
except ParseError, e:
raise UsageError(str(e))
elif o in ('-R', '--reply-block'):
- assert wantForwardPath
+ #assert wantForwardPath #XXXX008 re-enable, sanely
self.replyBlockSources.append(v)
elif o == '--reply-block-fd':
try:
@@ -911,6 +915,11 @@
self.forceQueue = 1
elif o in ('--no-queue',):
self.forceNoQueue = 1
+ elif o in ('--status-fd',):
+ try:
+ STATUS.setFD(int(v))
+ except ValueError:
+ raise UsageError("%s expects an integer"%o)
if self.quiet and self.verbose:
raise UsageError("I can't be quiet and verbose at the same time.")
@@ -1048,9 +1057,42 @@
return self.directory.generatePaths(n,self.pathSpec,self.exitAddress,
self.startAt,self.endAt)
+# DOCDOC
+def getOptions(args, shortOpts="", longOpts=(), dir=0, reply=0, path=0,
+ headers=0, argsOK=0, dest=0, input=0, output=0, passphrase=0):
+ longOpts = list(longOpts)
+ shortOpts += "hvQf:"
+ longOpts += ["help", "verbose", "quiet", "config=", "status-fd="]
+ if dir:
+ shortOpts += "D:"
+ longOpts += ["download-directory="]
+ if reply:
+ shortOpts += "R:"
+ longOpts += ["reply-block=", "reply-block-fd="]
+ if path:
+ shortOpts += "P:H:"
+ longOpts += ["path=","hops="]
+ if headers:
+ longOpts += ["subject=", "from=", "in-reply-to=", "references="]
+ if dest:
+ shortOpts += "t:"
+ longOpts += ["to="]
+ if input:
+ shortOpts += "i:"
+ longOpts += ["input="]
+ if output:
+ shortOpts += "o:"
+ longOpts += ["output="]
+ if passphrase:
+ longOpts += ["passphrase-fd="]
+ o, a = getopt.getopt(args, shortOpts, longOpts)
+ if a and not argsOK:
+ raise UsageError("No arguments expected")
+ return o, a
+
_SEND_USAGE = """\
Usage: %(cmd)s [options] <-t address>|<--to=address>|
- <-R reply-block>|--reply-block=reply-block>
+ <-R reply-block>|<--reply-block=reply-block>
Options:
-h, --help Print this usage message and exit.
-v, --verbose Display extra debugging messages.
@@ -1071,7 +1113,7 @@
packet, then deliver multiple fragmented packets
to the recipient instead of having the server
reassemble the message.
- --reply-block-fd=<N> Read reply blcoks from file descriptor <N>.
+ --reply-block-fd=<N> Read reply blocks from file descriptor <N>.
%(extra)s
EXAMPLES:
@@ -1138,12 +1180,15 @@
###
# Parse and validate our options.
- options, args = getopt.getopt(args, "hvQf:D:t:H:P:R:i:",
- ["help", "verbose", "quiet", "config=", "download-directory=",
- "to=", "hops=", "path=", "reply-block=", "reply-block-fd=",
- "input=", "queue", "no-queue",
- "subject=", "from=", "in-reply-to=", "references=",
- "deliver-fragments", ])
+ options, args = getOptions(args, "",
+ ["queue", "no-queue", "deliver-fragments"],
+ dir=1,reply=1,path=1,headers=1,dest=1,input=1)
+## options, args = getopt.getopt(args, "hvQf:D:t:H:P:R:i:",
+## ["help", "verbose", "quiet", "config=", "download-directory=",
+## "to=", "hops=", "path=", "reply-block=", "reply-block-fd=",
+## "input=", "queue", "no-queue",
+## "subject=", "from=", "in-reply-to=", "references=",
+## "deliver-fragments", "status-fd=" ])
if not options:
sendUsageAndExit(cmd)
@@ -1165,8 +1210,8 @@
elif opt == '--deliver-fragments':
no_ss_fragment = 1
- if args:
- sendUsageAndExit(cmd,"Unexpected arguments")
+## if args:
+## sendUsageAndExit(cmd,"Unexpected arguments")
try:
parser = CLIArgumentParser(options, wantConfig=1,wantClientDirectory=1,
@@ -1258,6 +1303,116 @@
message, parser.startAt, parser.endAt, forceQueue, forceNoQueue,
forceNoServerSideFragments=no_ss_fragment)
+_COUNT_PACKETS_USAGE = """\
+Usage: mixminion count-packets [options] <-t address>|<--to=address>|
+ <-R reply-block>|<--reply-block=reply-block>
+Options:
+ -h, --help Print this usage message and exit.
+ -v, --verbose Display extra debugging messages.
+ -f <file>, --config=<file> Use a configuration file other than ~/.mixminionrc
+ (You can also use MIXMINIONRC=FILE)
+ -i <file>, --input=<file> Read the message from <file>. (Defaults to stdin.)
+ --subject=<str>, --from=<str>, --in-reply-to=<str>, --references=<str>
+ Specify an email header for the exiting message.
+ --deliver-fragments If the message is too long to fit in a single
+ packet, then deliver multiple fragmented packets
+ to the recipient instead of having the server
+ reassemble the message.
+ --reply-block-fd=<N> Read reply blocks from file descriptor <N>.
+"""
+def countPackets(cmd,args):
+ options, args = getOptions(args, dir=1, dest=1, path=1, reply=1,
+ input=1, headers=1)
+
+## options,args = getopt.getopt(args, "hvQf:D:t:R:i:",
+## ["help", "verbose", "quiet", "config=", "download-directory=",
+## "to=", "path=", "reply-block=", "reply-block-fd=",
+## "input=",
+## "subject=", "from=", "in-reply-to=", "references=",
+## "deliver-fragments", "status-fd=" ])
+
+## if args:
+## print >>sys.stderr, "Unexpected arguments"
+## print _COUNTPACKET_USAGE
+## sys.exit(1)
+
+ inFile = '-'
+ h_subject = h_from = h_irt = h_references = None
+ no_ss_fragment = 0
+ for opt,val in options:
+ if opt in ('-i', '--input'):
+ inFile = val
+ elif opt == '--subject':
+ h_subject = val
+ elif opt == '--from':
+ h_from = val
+ elif opt == '--in-reply-to':
+ h_irt = val
+ elif opt == '--references':
+ h_references = val
+ elif opt == '--deliver-fragments':
+ no_ss_fragment = 1
+
+ try:
+ parser = CLIArgumentParser(options, wantConfig=1,wantClientDirectory=1,
+ wantLog=1)
+ except UsageError, e:
+ e.dump()
+ print _COUNT_PACKETS_USAGE
+ sys.exit()
+
+ # Encode the headers early so that we die before reading the message if
+ # they won't work.
+ try:
+ headerStr = encodeMailHeaders(subject=h_subject, fromAddr=h_from,
+ inReplyTo=h_irt, references=h_references)
+ except MixError, e:
+ raise UIError("Invalid headers: %s"%e)
+ if no_ss_fragment:
+ if headerStr != '\n':
+ raise UIError("Can't use --deliver-fragments with message headers")
+ else:
+ # suppress intial newline.
+ headerStr = ""
+
+ if inFile == '-' and '-' in parser.replyBlockSources:
+ raise UIError(
+ "Can't read both message and reply block from stdin")
+
+ parser.init()
+ address = parser.exitAddress
+ address.setHeaders(parseMessageAndHeaders(headerStr+"\n")[1])
+ if address and inFile == '-' and not address.hasPayload():
+ print "1 packet needed"
+ STATUS.log("COUNT_PACKETS", 1)
+ return
+ else:
+ if address and not address.hasPayload():
+ raise UIError("Cannot send a message in a DROP packet")
+ try:
+ if inFile == '-':
+ if os.isatty(sys.stdin.fileno()):
+ print "Enter your message. Type %s when you are done."%(
+ EOF_STR)
+ message = sys.stdin.read()
+ else:
+ message = readFile(inFile)
+ except KeyboardInterrupt:
+ print "Interrupted."
+ return
+
+ message = "%s%s"%(headerStr,message)
+ address.setExitSize(len(message))
+
+ if no_ss_fragment:
+ prefix=""
+ else:
+ prefix=address.getFragmentedMessagePrefix()
+
+ n = mixminion.BuildMessage.getNPacketsToEncode(message, 0, prefix)
+ print "%d packets needed" % n
+ STATUS.log("COUNT_PACKETS", n)
+
_PING_USAGE = """\
Usage: mixminion ping [options] serverName
Options
@@ -1274,11 +1429,13 @@
print _PING_USAGE
sys.exit(0)
- options, args = getopt.getopt(args, "hvQf:D:",
- ["help", "verbose", "quiet", "config=", "download-directory=", ])
+ options, args = getOptions(args, dir=1, argsOK=1)
+## options, args = getopt.getopt(args, "hvQf:D:",
+## ["help", "verbose", "quiet", "config=", "download-directory=",
+## "status-fd=" ])
- if len(args) == 0:
- raise UsageError("(No servers provided)")
+## if len(args) == 0:
+## raise UsageError("(No servers provided)")
print "==========================================================="
print "WARNING: Pinging a server is potentially dangerous, since"
@@ -1341,8 +1498,9 @@
def importServer(cmd, args):
"""[Entry point] Manually add a server to the client directory."""
- options, args = getopt.getopt(args, "hf:vQ",
- ['help', 'config=', 'verbose', 'quiet'])
+ options, args = getOptions(args, dir=1, argsOK=1)
+## options, args = getopt.getopt(args, "hf:vQ",
+## ['help', 'config=', 'verbose', 'quiet', "status-fd="])
try:
parser = CLIArgumentParser(options, wantConfig=1,wantClientDirectory=1,
@@ -1398,12 +1556,18 @@
def listServers(cmd, args):
"""[Entry point] Print info about servers in the directory, or on
the command line."""
- options, args = getopt.getopt(args, "hf:D:vQF:JTrRs:cC",
- ['help', 'config=', "download-directory=",
- 'verbose', 'quiet', 'feature=', 'justify',
- 'with-time', "no-collapse", "recommended",
- "separator=", "cascade","cascade-features",
- 'list-features' ])
+ options, args = getOptions(args, "F:JTrRs:cC",
+ ['feature=', 'justify',
+ 'with-time', "no-collapse", "recommended",
+ "separator=", "cascade","cascade-features",
+ 'list-features'],
+ dir=1, argsOK=1)
+## options, args = getopt.getopt(args, "hf:D:vQF:JTrRs:cC",
+## ['help', 'config=', "download-directory=",
+## 'verbose', 'quiet', 'feature=', 'justify',
+## 'with-time', "no-collapse", "recommended",
+## "separator=", "cascade","cascade-features",
+## 'list-features', "status-fd=" ])
try:
parser = CLIArgumentParser(options, wantConfig=1,
wantClientDirectory=1,
@@ -1519,8 +1683,9 @@
""".strip()
def updateServers(cmd, args):
- options, args = getopt.getopt(args, "hvQf:",
- ['help', 'quiet', 'verbose', 'config='])
+ options, args = getOptions(args)
+## options, args = getopt.getopt(args, "hvQf:",
+## ['help', 'quiet', 'verbose', 'config=', "status-fd="])
try:
parser = CLIArgumentParser(options, wantConfig=1, wantClientDirectory=1,
@@ -1564,9 +1729,11 @@
def clientDecode(cmd, args):
"""[Entry point] Decode a message."""
- options, args = getopt.getopt(args, "hvQf:o:Fi:",
- ['help', 'verbose', 'quiet', 'config=',
- 'output=', 'force', 'input=', 'passphrase-fd=',])
+ options, args = getOptions(args, input=1, output=1, passphrase=1)
+
+## options, args = getopt.getopt(args, "hvQf:o:Fi:",
+## ['help', 'verbose', 'quiet', 'config=',
+## 'output=', 'force', 'input=', 'passphrase-fd=', "status-fd="])
outputFile = '-'
inputFile = '-'
@@ -1666,10 +1833,14 @@
""".strip()
def generateSURB(cmd, args):
- options, args = getopt.getopt(args, "hvQf:D:t:H:P:o:bn:",
- ['help', 'verbose', 'quiet', 'config=', 'download-directory=',
- 'to=', 'hops=', 'path=', 'lifetime=', 'passphrase-fd=',
- 'output=', 'binary', 'count=', 'identity='])
+ options, args = getOptions(args,
+ "bn:", ["binary", "count=", "identity="],
+ dir=1, dest=1, path=1, passphrase=1, output=1)
+
+## options, args = getopt.getopt(args, "hvQf:D:t:H:P:o:bn:",
+## ['help', 'verbose', 'quiet', 'config=', 'download-directory=',
+## 'to=', 'hops=', 'path=', 'lifetime=', 'passphrase-fd=',
+## 'output=', 'binary', 'count=', 'identity=', "status-fd="])
outputFile = '-'
binary = 0
@@ -1741,8 +1912,9 @@
""".strip()
def inspectSURBs(cmd, args):
- options, args = getopt.getopt(args, "hvQf:",
- ["help", "verbose", "quiet", "config=", ])
+ options, args = getOptions(args, argsOK=1)
+## options, args = getopt.getopt(args, "hvQf:",
+## ["help", "verbose", "quiet", "config=", "status-fd="])
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
@@ -1770,6 +1942,9 @@
used = surblog.isSURBUsed(surb) and "yes" or "no"
print surb.format()
print "Used:", used
+ STATUS.log("INSPECT_SURB", surb.getHexDigest(),
+ surb.timestamp,
+ surblog.isSURBUsed(surb) and "1" or "0")
except ParseError, e:
print "Error while parsing: %s"%e
finally:
@@ -1795,8 +1970,9 @@
""".strip()
def flushQueue(cmd, args):
- options, args = getopt.getopt(args, "hvQf:n:",
- ["help", "verbose", "quiet", "config=", "count="])
+ options, args = getOptions(args, "", ["count="], argsOK=1)
+## options, args = getopt.getopt(args, "hvQf:n:",
+## ["help", "verbose", "quiet", "config=", "count=", "status-fd="])
count=None
for o,v in options:
if o in ('-n','--count'):
@@ -1841,8 +2017,9 @@
""".strip()
def cleanQueue(cmd, args):
- options, args = getopt.getopt(args, "hvQf:d:",
- ["help", "verbose", "quiet", "config=", "days=",])
+ options, args = getOptions(args, "d:" ["days"])
+## options, args = getopt.getopt(args, "hvQf:d:",
+## ["help", "verbose", "quiet", "config=", "days=", "status-fd="])
days = 60
for o,v in options:
if o in ('-d','--days'):
@@ -1884,9 +2061,10 @@
""".strip()
def listQueue(cmd, args):
- options, args = getopt.getopt(args, "hvQf:D:",
- ["help", "verbose", "quiet", "config=",
- 'download-directory=',])
+ options, args = getOptions(args, dir=1)
+## options, args = getopt.getopt(args, "hvQf:D:",
+## ["help", "verbose", "quiet", "config=",
+## 'download-directory=', "status-fd="])
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
wantClient=1, wantClientDirectory=1)
@@ -1932,9 +2110,10 @@
""".strip()
def listFragments(cmd, args):
- options, args = getopt.getopt(args, "hvQf:D:",
- ["help", "verbose", "quiet", "config=",
- 'download-directory=',])
+ options, args = getOptions(args, dir=1)
+## options, args = getopt.getopt(args, "hvQf:D:",
+## ["help", "verbose", "quiet", "config=",
+## 'download-directory=', "status-fd="])
try:
parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
wantClient=1, wantClientDirectory=1)
@@ -1969,10 +2148,12 @@
""".strip()
def reassemble(cmd, args):
- options, args = getopt.getopt(args, "hvQf:D:Po:F",
- ["help", "verbose", "quiet", "config=",
- 'download-directory=','--purge',
- '--output', '--force'])
+ options, args = getOptions(args, "PF", ["purge", "force"],
+ output=1, argsOK=1)
+## options, args = getopt.getopt(args, "hvQf:D:Po:F",
+## ["help", "verbose", "quiet", "config=",
+## 'download-directory=','--purge',
+## '--output', '--force',"status-fd="])
reassemble = 1
if cmd.endswith("purge-fragments") or cmd.endswith("purge-fragment"):
reassemble = 0
@@ -1998,7 +2179,7 @@
outfilename = v
elif o in ("-P", "--purge"):
purge = 1
- elif o ("-F", "--force"):
+ elif o in ("-F", "--force"):
force = 1
if not args:
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.138
retrieving revision 1.139
diff -u -d -r1.138 -r1.139
--- Common.py 2 May 2004 18:45:15 -0000 1.138
+++ Common.py 14 May 2004 23:44:09 -0000 1.139
@@ -11,7 +11,7 @@
'armorText', 'ceilDiv', 'checkPrivateDir', 'checkPrivateFile',
'createPrivateDir', 'disp64',
'encodeBase64', 'englishSequence', 'floorDiv', 'formatBase64',
- 'formatDate', 'formatFnameTime', 'formatTime',
+ 'formatDate', 'formatFnameDate', 'formatFnameTime', 'formatTime',
'installSIGCHLDHandler', 'isSMTPMailbox', 'openUnique',
'previousMidnight', 'readFile', 'readPickled',
'readPossiblyGzippedFile', 'secureDelete', 'stringContains',
@@ -1088,6 +1088,103 @@
def close(self): pass
#----------------------------------------------------------------------
+# StatusLog
+
+class StatusLog:
+ """Used to emit machine-parseable status messages to the file
+ descriptor specifed by the --status-fd argument. The set of valid
+ messages and their format is described in doc/statusfd.txt .
+
+ Each message consists of:
+ The string '[MIXMINION:]',
+ A space,
+ A 'message type' consisting only of capital letters, digits,
+ and underscores,
+ Optionally, a space, and a space-separated list of arguments,
+ A newline.
+
+ If an argument contains a space, a newline, or a backslash,
+ these characters are escaped with a backslash.
+
+ To maintain compatibility, arguments must only be added, never
+ removed, moved, or changed. Messages should be emitted by
+ importing the global STATUS instance from this module and calling:
+
+ STATUS.log('SURB_GENERATED', surbid)
+
+ Each message type should be independent (it seems safer to not
+ assume any particular ordering between messages).
+
+ Keep machine-parseability in mind when adding these message types:
+ make sure the name is unique, and separate arguments with colons.
+ """
+ _ESC_PAT = re.compile(r'([ \n\\])')
+ def __init__(self):
+ """DOCDOC"""
+ self.fd = None
+ self.__lock = threading.Lock()
+
+ def setFD(self, fdnum):
+ """DOCDOC"""
+ # this will fail if the invoking user did not actually open the file
+ # descriptor they ask us to use, for example if they did:
+ # mixminion send --status-fd=3
+ # instead of:
+ # mixminion send --status-fd=3 3>somewhere.txt
+ #
+ # remember, this is not meant for use by a shell, it is for
+ # front-end programs that are running mixminion in a child process.
+ # There will be a pipe connected to this fd which the parent process
+ # will be reading from.
+
+ if fdnum is None:
+ self.fd = None
+ else:
+ self.fd = fdnum
+
+ def msg(self, name, args):
+ """DOCDOC"""
+ r = [ "[MIXMINION:]", name ]
+ for arg in args:
+ r.append(self._ESC_PAT.sub(r"\\\1",str(arg)))
+ return "%s\n"%(" ".join(r))
+
+ def log(self, name, *args):
+ """DOCDOC"""
+ if self.fd is None:
+ return
+
+ s = self.msg(name, args)
+ self.__lock.acquire()
+ try:
+ os.write(self.fd, s)
+ finally:
+ self.__lock.release()
+
+# The global Status instance for the mixminion client. Status messages
+# should be emitted with STATUS.log()
+STATUS = StatusLog()
+
+_STATUS_LINE_RE = re.compile(r'^\[MIXMINION:\] ([A-Z_]+)(?: (.*))?', re.S)
+_ARG_RE = re.compile(r'^((?:[^ \n\\]+|\\[ \n\\])+)[ \n]?')
+_UNESC_ARG_RE = re.compile(r'\\([ \n\\])')
+def parseStatusLogLine(s):
+ """DOCDOC"""
+ m = _STATUS_LINE_RE.match(s)
+ if not m:
+ return None,None
+ name = m.group(1)
+ s = m.group(2)
+ res = []
+ while s:
+ m = _ARG_RE.match(s)
+ if not m:
+ return None,None
+ res.append(_UNESC_ARG_RE.sub(r'\1', m.group(1)))
+ s = s[m.end():]
+ return name, res
+
+#----------------------------------------------------------------------
# Time processing
def previousMidnight(when):
@@ -1127,11 +1224,19 @@
gmt = time.gmtime(when+1) # Add 1 to make sure we round down.
return "%04d-%02d-%02d" % (gmt[0],gmt[1],gmt[2])
-def formatFnameTime(when=None):
+def formatFnameDate(when=None):
"""Given a time in seconds since the epoch, returns a date value suitable
for use as part of a filename. Defaults to the current time."""
if when is None:
when = time.time()
+ return time.strftime("%Y%m%d", time.localtime(when))
+
+def formatFnameTime(when=None):
+ """Given a time in seconds since the epoch, returns a date-time
+ value suitable for use as part of a filename. Defaults to the
+ current time."""
+ if when is None:
+ when = time.time()
return time.strftime("%Y%m%d%H%M%S", time.localtime(when))
#----------------------------------------------------------------------
Index: Fragments.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Fragments.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -d -r1.15 -r1.16
--- Fragments.py 23 Mar 2004 00:05:32 -0000 1.15
+++ Fragments.py 14 May 2004 23:44:09 -0000 1.16
@@ -58,7 +58,7 @@
self.nChunks = ceilDiv(minFragments, self.k)
# Number of total fragments per chunk.
self.n = int(math.ceil(EXP_FACTOR * self.k))
- # Data in a single chunk
+ # Data in a single chunk
self.chunkSize = self.fragCapacity * self.k
# Length of data to fill chunks
self.paddedLen = self.nChunks * self.fragCapacity * self.k
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.72
retrieving revision 1.73
diff -u -d -r1.72 -r1.73
--- Main.py 6 Mar 2004 00:04:38 -0000 1.72
+++ Main.py 14 May 2004 23:44:09 -0000 1.73
@@ -127,6 +127,7 @@
"generate-surbs" : ( 'mixminion.ClientMain', 'generateSURB' ),
"inspect-surb" : ( 'mixminion.ClientMain', 'inspectSURBs' ),
"inspect-surbs" : ( 'mixminion.ClientMain', 'inspectSURBs' ),
+ "count-packets": ( 'mixminion.ClientMain', 'countPackets' ),
"flush" : ( 'mixminion.ClientMain', 'flushQueue' ),
"inspect-queue" : ( 'mixminion.ClientMain', 'listQueue' ),
"clean-queue" : ( 'mixminion.ClientMain', 'cleanQueue' ),
@@ -164,6 +165,7 @@
" decode [Decode or decrypt a received message]\n"+
" generate-surb [Generate a single-use reply block]\n"+
" inspect-surbs [Describe a single-use reply block]\n"+
+ " count-packets [DOCDOC]\n"
" ping [Quick and dirty check whether a server is running]\n"
" (For Servers)\n"+
" server-start [Begin running a Mixminion server]\n"+
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.75
retrieving revision 1.76
diff -u -d -r1.75 -r1.76
--- Packet.py 6 Mar 2004 00:04:38 -0000 1.75
+++ Packet.py 14 May 2004 23:44:09 -0000 1.76
@@ -39,8 +39,8 @@
import zlib
from socket import inet_ntoa, inet_aton
from mixminion.Common import MixError, MixFatalError, encodeBase64, \
- floorDiv, formatTime, isSMTPMailbox, LOG, armorText, unarmorText, \
- isPlausibleHostname
+ floorDiv, formatBase64, formatTime, isSMTPMailbox, LOG, armorText, \
+ unarmorText, isPlausibleHostname
from mixminion.Crypto import sha1
if sys.version_info[:3] < (2,2,0):
@@ -524,8 +524,9 @@
self.encryptionKey = key
def format(self):
+ """DOCDOC"""
import mixminion.ServerInfo
- digest = binascii.b2a_hex(sha1(self.pack()))
+ digest = self.getHexDigest()
expiry = formatTime(self.timestamp)
if self.routingType == SWAP_FWD_IPV4_TYPE:
routing = parseIPV4Info(self.routingInfo)
@@ -538,6 +539,10 @@
Expires at: %s GMT
First server is: %s""" % (digest, expiry, server)
+ def getHexDigest(self):
+ """DOCDOC"""
+ return binascii.b2a_hex(sha1(self.pack()))
+
def pack(self):
"""Returns the external representation of this reply block"""
return struct.pack(RB_UNPACK_PATTERN,
@@ -612,6 +617,9 @@
return struct.pack(IPV4_PAT, inet_aton(self.ip),
self.port, self.keyinfo)
+ def __str__(self):
+ return "IP:%s:%s:%s"(self.ip,self.port,formatBase64(self.keyinfo))
+
def __repr__(self):
return "IPV4Info(%r, %r, %r)"%(self.ip, self.port, self.keyinfo)
@@ -664,6 +672,9 @@
assert len(self.keyinfo) == DIGEST_LEN
return struct.pack(MMTP_HOST_PAT,self.port,self.keyinfo)+self.hostname
+ def __str__(self):
+ return "%s:%s:%s"(self.hostname,self.port,formatBase64(self.keyinfo))
+
def __repr__(self):
return "MMTPHostInfo(%r, %r, %r)"%(
self.hostname,self.port,self.keyinfo)
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.196
retrieving revision 1.197
diff -u -d -r1.196 -r1.197
--- test.py 5 May 2004 02:04:48 -0000 1.196
+++ test.py 14 May 2004 23:44:09 -0000 1.197
@@ -343,8 +343,10 @@
now = time.time()
ft = formatFnameTime(now)
+ fd = formatFnameDate(now)
tm = time.localtime(now)
self.assertEquals(ft, "%04d%02d%02d%02d%02d%02d" % tm[:6])
+ self.assertEquals(fd, "%04d%02d%02d" % tm[:3])
def test_isSMTPMailbox(self):
# Do we accept good addresses?
@@ -1884,6 +1886,7 @@
for ov in 0, 42-20+16: # encrypted forward overhead
plds = BuildMessage.encodeMessage(m,ov,"",p)
assert len(plds) == 1
+ assert BuildMessage.getNPacketsToEncode(m,ov,"") == 1
pld = plds[0]
self.assertEquals(28*1024, len(pld)+ov)
comp = compressData(m)
@@ -2226,6 +2229,8 @@
rsa1, rsa2 = self.pk1, self.pk512
payloadE = BuildMessage.encodeMessage(
"<<<<Hello>>>>"*100,ENC_FWD_OVERHEAD)[0]
+ assert BuildMessage.getNPacketsToEncode("<<<<Hello>>>>"*100,
+ ENC_FWD_OVERHEAD) == 1
for rsakey in rsa1,rsa2:
m = befm(payloadE, 500, "Phello",
[self.server1, self.server2],
@@ -2430,6 +2435,7 @@
prefix = "Prefix!"
payloads = BuildMessage.encodeMessage(msg, 0, prefix)
self.assertEquals(len(payloads), 3)
+ self.assertEquals(BuildMessage.getNPacketsToEncode(msg, 0, prefix), 3)
p1 = BuildMessage.decodePayload(payloads[0], "")
p2 = BuildMessage.decodePayload(payloads[1], "")
p3 = BuildMessage.decodePayload(payloads[2], "")
@@ -3218,6 +3224,40 @@
for fn in os.listdir(d_parent):
os.unlink(os.path.join(d_parent,fn))
+ # WritethroughDict
+ loc = os.path.join(d_parent, "db4")
+ WD = mixminion.Filestore.WritethroughDict
+ d = WD(loc, "testing")
+ d["xyzzy"] = 91
+ d["bliznert"] = (4,22,11,8)
+ self.assertEquals(d["xyzzy"], 91)
+ self.assertEquals(d["bliznert"], (4,22,11,8))
+ d.close()
+
+ d = WD(loc, "testing")
+ self.assertEquals(d["xyzzy"], 91)
+ self.assert_(d.has_key("bliznert"))
+ d["bliznert"] = [ 12, "aeaeae" ]
+ d["mulligan"] = ( "stately, plump", )
+ self.assertEquals(d.get("mulligan"), ("stately, plump",))
+ self.assertEquals(d.get("mulliganX"), None)
+ self.assertEquals(d["bliznert"], [12, "aeaeae"])
+ del d['xyzzy']
+ self.assert_(not d.has_key("xyzzy"))
+ d.close()
+
+ d = WD(loc, "testing")
+ try:
+ d["xyzzy"]
+ except KeyError:
+ pass
+ else:
+ self.fail()
+ self.assertEquals(d.get("mulligan"), ("stately, plump",))
+ self.assertEquals(d["bliznert"], [12, "aeaeae"])
+ self.assertUnorderedEq(d.keys(), ["mulligan","bliznert"])
+ d.close()
+
class QueueTests(FStoreTestBase):
def setUp(self):
mixminion.Common.installSIGCHLDHandler()
@@ -3303,6 +3343,10 @@
queue.removeAll(self.unlink)
queue.cleanQueue(self.unlink)
+ def testAddressBasedQueue(self):
+ #XXXX008
+ pass
+
def testMixPools(self):
d_m = mix_mktemp("qm")
@@ -3428,6 +3472,37 @@
self.assertEquals(lines[4], "[WARN] ->STREAM: C")
self.assertEquals(lines[5], "[WARN] ->STREAM: X Y")
+ def testStatusLog(self):
+ SL = mixminion.Common.StatusLog()
+ self.assertEquals("[MIXMINION:] ABC\n",
+ SL.msg("ABC", []))
+ self.assertEquals("[MIXMINION:] ABC 42 HELLO\n",
+ SL.msg("ABC", [42, "HELLO"]))
+ self.assertEquals("[MIXMINION:] ABC A A\\ BC\\ D\n",
+ SL.msg("ABC", ["A", "A BC D"]))
+ self.assertEquals("[MIXMINION:] A_BCD A\\\nB\\\\X\n",
+ SL.msg("A_BCD", ["A\nB\\X"]))
+ tmpfile = mix_mktemp()
+ f = open(tmpfile, 'w')
+ SL.setFD(f.fileno())
+ SL.log("REVOLUTION_9", "number nine", "number nine")
+ SL.log("REVOLUTION_9", "take this brother")
+ SL.setFD(None)
+ f.close()
+ self.assertEquals(readFile(tmpfile),
+ "[MIXMINION:] REVOLUTION_9 number\\ nine number\\ nine\n"
+ "[MIXMINION:] REVOLUTION_9 take\\ this\\ brother\n")
+
+ PSL = mixminion.Common.parseStatusLogLine
+ self.assertEquals(PSL("[MIXMINION:] ABC 42 HELLO\n"),
+ ("ABC",["42", "HELLO"]))
+ self.assertEquals(PSL("[MIXMINION:] ABC 42 HELLO\\ \n"),
+ ("ABC",["42", "HELLO "]))
+ self.assertEquals(PSL("[MIXMINION:] ABC\n"),
+ ("ABC",[]))
+ self.assertEquals(PSL("[MIXMINION:] ABC X\\\nYZ 03.1\\\\\n"),
+ ("ABC",["X\nYZ", "03.1\\"]))
+
#----------------------------------------------------------------------
# File paranoia
@@ -7526,7 +7601,7 @@
tc = loader.loadTestsFromTestCase
if 0:
- suite.addTest(tc(ClientMainTests))
+ suite.addTest(tc(FilestoreTests))
return suite
testClasses = [MiscTests,
MinionlibCryptoTests,