[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))