[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] ClientMain:
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv28684/src/minion/lib/mixminion
Modified Files:
ClientMain.py Packet.py test.py
Log Message:
ClientMain:
- Client support for headers
- Support for removing old msgs from client queue
test:
- Tests for headers in DeliveryPacket
- Tests for headers in SMTP delivery
ServerMain:
- Add a 'quiet' startup option.
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.94
retrieving revision 1.95
diff -u -d -r1.94 -r1.95
--- ClientMain.py 26 Jun 2003 17:52:08 -0000 1.94
+++ ClientMain.py 7 Jul 2003 18:55:15 -0000 1.95
@@ -39,9 +39,9 @@
from mixminion.Crypto import sha1, ctr_crypt, trng
from mixminion.Config import ClientConfig, ConfigError
from mixminion.ServerInfo import ServerInfo, ServerDirectory
-from mixminion.Packet import ParseError, parseMBOXInfo, parseReplyBlocks, \
- parseSMTPInfo, parseTextEncodedMessages, parseTextReplyBlocks, \
- ReplyBlock, MBOX_TYPE, SMTP_TYPE, DROP_TYPE
+from mixminion.Packet import encodeMessageHeaders, ParseError, parseMBOXInfo, \
+ parseReplyBlocks, parseSMTPInfo, parseTextEncodedMessages, \
+ parseTextReplyBlocks, ReplyBlock, MBOX_TYPE, SMTP_TYPE, DROP_TYPE
# FFFF This should be made configurable and adjustable.
MIXMINION_DIRECTORY_URL = "http://mixminion.net/directory/Directory.gz"
@@ -1328,6 +1328,21 @@
print "%2d messages for server at %s:%s (oldest is %s days old)"%(
count, s.ip, s.port, days)
+ def cleanQueue(self, maxAge, now=None):
+ """Remove all messages older than maxAge seconds from this
+ queue."""
+ if now is None:
+ now = time.time()
+ cutoff = now - maxAge
+ remove = []
+ for h in handles:
+ when = self.getPacket(h)[2]
+ if when < cutoff:
+ remove.append(when)
+ LOG.info("Removing %s old messages from queue", len(remove))
+ for h in remove:
+ self.removePacket(h)
+
class MixminionClient:
"""Access point for client functionality. Currently, this is limited
to generating and sending forward messages"""
@@ -1593,6 +1608,15 @@
else:
LOG.info("No messages delivered")
+ def cleanQueue(self, maxAge, now=None):
+ """Remove all messages older than maxAge seconds from the
+ client queue."""
+ try:
+ clientLock()
+ self.queue.cleanQueue(maxAge, now)
+ finally:
+ clientUnlock()
+
def queueMessages(self, msgList, routing):
"""Insert all the messages in msgList into the queue, to be sent
to the server identified by the IPV4Info object 'routing'.
@@ -2059,6 +2083,8 @@
-R <file>, --reply-block=<file>
%(Send)s the message to a reply block in <file>,
or '-' for a reply block read from stdin.
+ --subject=<str>, --from=<str>, --in-reply-to=<str>, --references=<str>
+ Specify an email header for the exiting message.
%(extra)s
EXAMPLES:
@@ -2121,15 +2147,25 @@
options, args = getopt.getopt(args, "hvf:D:t:H:P:R:i:",
["help", "verbose", "config=", "download-directory=",
"to=", "hops=", "path=", "reply-block=",
- "input=", "queue", "no-queue" ])
+ "input=", "queue", "no-queue"
+ "subject=", "from=", "in-reply-to=", "references=", ])
if not options:
sendUsageAndExit(cmd)
inFile = None
+ headers = {}
for opt,val in options:
if opt in ('-i', '--input'):
inFile = val
+ elif opt == '--subject':
+ headers["SUBJECT"] = val
+ elif opt == '--from':
+ headers["FROM"] = val
+ elif opt == '--in-reply-to':
+ headers["IN-REPLY-TO"] = val
+ elif opt == '--references':
+ headers["REFERENCES"] = val
if args:
sendUsageAndExit(cmd,"Unexpected arguments")
@@ -2146,6 +2182,10 @@
e.dump()
sendUsageAndExit(cmd)
+ # Encode the headers early so that we die before reading the message if
+ # they won't work.
+ headerStr = encodeMessageHeaders(headers)
+
if inFile in (None, '-') and '-' in parser.replyBlockFiles:
raise UIError(
"Can't read both message and reply block from stdin")
@@ -2195,6 +2235,8 @@
print "Interrupted. Message not sent."
sys.exit(1)
+ payload = "%s%s" % headerStr, payload
+
if parser.usingSURBList:
assert isinstance(path2, ListType)
client.sendReplyMessage(payload, path1, path2,
@@ -2638,6 +2680,43 @@
client.flushQueue(count)
+_CLEAN_QUEUE_USAGE = """\
+Usage: %(cmd)s <-D n|--days=n> [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)
+ -D <n>, --days=<n> Remove all messages older than <n> days old.
+
+EXAMPLES:
+ Remove all pending messages older than one week.
+ %(cmd)s -D 30
+""".strip()
+
+def cleanQueue(cmd, args):
+ options, args = getopt.getopt(args, "hvf:D:",
+ ["help", "verbose", "config=", "days=",])
+ days = 0
+ for o,v in options:
+ if o in ('-D','--days'):
+ try:
+ days = int(v)
+ except ValueError:
+ print "ERROR: %s expects an integer" % o
+ sys.exit(1)
+ try:
+ if count is None:
+ raise UsageError()
+ parser = CLIArgumentParser(options, wantConfig=1, wantLog=1,
+ wantClient=1)
+ except UsageError, e:
+ e.dump()
+ print _CLEAN_QUEUE_USAGE % { 'cmd' : cmd }
+ sys.exit(1)
+
+ parser.init()
+ client = parser.client
+ client.cleanQueue(days*24*60*60)
_LIST_QUEUE_USAGE = """\
Usage: %(cmd)s [options]
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.48
retrieving revision 1.49
diff -u -d -r1.48 -r1.49
--- Packet.py 30 Jun 2003 17:33:33 -0000 1.48
+++ Packet.py 7 Jul 2003 18:55:15 -0000 1.49
@@ -687,7 +687,7 @@
return encodeMessageHeaders(message, headers)
def encodeMessageHeaders(headers):
- """DOCDOC msg, dict
+ """DOCDOC dict
Requires that headers are in acceptable format.
"""
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.128
retrieving revision 1.129
diff -u -d -r1.128 -r1.129
--- test.py 1 Jul 2003 21:18:32 -0000 1.128
+++ test.py 7 Jul 2003 18:55:15 -0000 1.129
@@ -1345,6 +1345,8 @@
pmh = parseMessageAndHeaders
eq = self.assertEquals
+ eq(emh({}), "\n")
+
encoded = emh({"ABC": "x y zzy", "X": "<42>"}) + "Hello whirled"
eq(encoded, "ABC:x y zzy\nX:<42>\n\nHello whirled")
m,h = pmh(encoded)
@@ -2351,6 +2353,7 @@
pkt.getAsciiContents())
# with an overcompressed content
pcomp = " "*4096
+ # (forward, overcompressed)
m = bfm(pcomp, SMTP_TYPE, "nobody@invalid",
[self.server1], [self.server3])
pkt = self.do_test_chain(m,
@@ -2364,6 +2367,7 @@
self.assert_(pkt.getAsciiContents(),
encodeBase64(compressData(pcomp)))
+ # (enc-forward, overcompressed)
m = befm(p, SMTP_TYPE, "nobody@invalid", [self.server1],
[self.server3], getRSAKey(0,1024))
pkt = self.do_test_chain(m,
@@ -2378,6 +2382,39 @@
self.assertEquals(encodeBase64(pkt.getContents()),
pkt.getAsciiContents())
+ # Header features
+ #
+ # (ASCII msg with headers)
+ h = {"FROM":'fred@foo', "SUBJECT":'Stuff'}
+ p = encodeMessageHeaders(h) + "This is the message.\n"
+ m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
+ pkt = self.do_test_chain(m,
+ [self.sp1, self.sp3],
+ [FWD_TYPE, SMTP_TYPE],
+ [self.server3.getRoutingInfo().pack(),
+ "nobody@invalid"],
+ "")
+ self.assert_(pkt.isPrintingAscii())
+ self.assert_(not pkt.isEncrypted())
+ self.assertEquals(pkt.getContents(), "This is the message.\n")
+ self.assertEquals(pkt.getHeaders(), h)
+
+ # (binary msg with headers.)
+ body = "\x01\x02\x03\x04"*10
+ p = encodeMessageHeaders(h) + body
+ m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
+ pkt = self.do_test_chain(m,
+ [self.sp1, self.sp3],
+ [FWD_TYPE, SMTP_TYPE],
+ [self.server3.getRoutingInfo().pack(),
+ "nobody@invalid"],
+ "")
+ self.assert_(not pkt.isPrintingAscii())
+ self.assert_(not pkt.isEncrypted())
+ self.assertEquals(pkt.getContents(), body)
+ self.assertEquals(pkt.getHeaders(), h)
+
+
def test_rejected(self):
bfm = BuildMessage.buildForwardMessage
brm = BuildMessage.buildReplyMessage
@@ -4676,6 +4713,7 @@
{"Enabled":1, "Server": "nonesuch",
"Retry": [0,0,0,0],
"SubjectLine":'foobar',
+ "FromTag" : '[NotReally]',
'MixCommand' : ('ls', ['-z'])}},
manager)
queue = manager.queues['SMTP_MIX2']
@@ -4708,6 +4746,34 @@
sendfn, sendargs = calls[-1][1][2], calls[-1][1][3:]
self.assertEquals("ls", sendfn)
self.assertEquals(sendargs, ('-S',))
+
+ clearReplacedFunctionCallLog()
+
+ # Send another message, with headers.
+ queue.queueDeliveryMessage(
+ FDP('plain', SMTP_TYPE, "foo@bar",
+ "This is the message",
+ headers={"FROM":"Mal", "SUBJECT":"Fnord"}))
+ queue.sendReadyMessages()
+ calls = getReplacedFunctionCallLog()
+ mixfn, mixargs = calls[0][1][2], calls[0][1][3:]
+ fn = mixargs[-1]
+ fn = os.path.join(os.path.split(fn)[0],
+ "rmv_"+os.path.split(fn)[1][4:])
+ m = readFile(fn)
+ self.assertEquals(m, '''\
+To: foo@bar
+From: "[NotReally] Mal" <nobody>
+Subject: Fnord
+X-Anonymous: yes
+
+-----BEGIN TYPE III ANONYMOUS MESSAGE-----
+Message-type: plaintext
+
+This is the message
+-----END TYPE III ANONYMOUS MESSAGE-----
+''')
+
finally:
undoReplacedAttributes()
clearReplacedFunctionCallLog()
@@ -4739,6 +4805,7 @@
# Stub out sendSMTPMessage.
replaceFunction(mixminion.server.Modules, 'sendSMTPMessage',
lambda *args: mixminion.server.Modules.DELIVER_OK)
+
try:
haiku = ("Hidden, we are free\n"+
"Free to speak, to free ourselves\n"+
@@ -4779,6 +4846,46 @@
self.assert_(EXPECTED_SMTP_PACKET == args[3])
clearReplacedFunctionCallLog()
+ # Now, with headers.
+ queueMessage(FDP('plain', SMTP_TYPE, "users@everywhere", haiku,
+ headers={"FROM":"Captain Nick",
+ "SUBJECT":"Gold Doubloons",
+ "IN-REPLY-TO":"aaaaa@b.com",
+ "REFERENCES":"cccccc@d.com"}))
+ queue.sendReadyMessages()
+ # Was sendSMTPMessage invoked correctly?
+ calls = getReplacedFunctionCallLog()
+ self.assertEquals(1, len(calls))
+ fn, args, _ = calls[0]
+ self.assertEquals("sendSMTPMessage", fn)
+ #server, toList, fromAddr, message
+ self.assertEquals(('nowhere',
+ ['users@everywhere'],
+ 'yo.ho.ho@bottle.of.rum'),
+ args[:3])
+ EXPECTED_SMTP_PACKET = '''\
+To: users@everywhere
+From: "[Anon] Captain Nick" <yo.ho.ho@bottle.of.rum>
+Subject: Gold Doubloons
+In-Reply-To: aaaaa@b.com
+References: cccccc@d.com
+X-Anonymous: yes
+
+Avast ye mateys! Prepare to be anonymized!
+
+-----BEGIN TYPE III ANONYMOUS MESSAGE-----
+Message-type: plaintext
+
+Hidden, we are free
+Free to speak, to free ourselves
+Free to hide no more.
+-----END TYPE III ANONYMOUS MESSAGE-----\n'''
+ d = findFirstDiff(EXPECTED_SMTP_PACKET, args[3])
+ if d != -1:
+ print d, "near", repr(args[3][d-10:d+10])
+ self.assert_(EXPECTED_SMTP_PACKET == args[3])
+ clearReplacedFunctionCallLog()
+
# Now, try a bunch of messages that won't be delivered: one with
# an invalid address, and one with a blocked address.
try:
@@ -5936,7 +6043,7 @@
tc = loader.loadTestsFromTestCase
if 0:
- suite.addTest(tc(MiscTests))
+ suite.addTest(tc(ModuleTests))
return suite
suite.addTest(tc(MiscTests))