[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[PATCH] adding --passphrase-fd to 'mixminion generate-surb'



Howdy all. I'm working on an anti-spam SMTP replacement called PETmail [1]
(which will be presented at CodeCon next month). I'm in the process of adding
mixminion support to it, and have a few small patches to make it easier for
my agent program to drive mixminion as a child process. The patches are
attached below.

The first is just to emit the version number and testing-warning on stderr
instead of stdout, so that they are not mixed up with the SURB or decoded
message being emitted on stdout. It looks like there is a --quiet argument
intended to silence these messages, but passing that argument causes a
failure later, as it doesn't appear in any of the getopt lists.

The second adds a --passphrase-fd option to the 'generate-surb' command, and
works just like gpg's option of the same name. This makes it possible to run
mixminion as a child process to generate a SURB, passing the keyring
passphrase in on a spare filehandle, perhaps fd 3. (PETmail will publish
these SURBs through a "Transport Server", described vaguely in the paper
referenced below, to accomplish the nymserver-like property of allowing
repeated access to the same anonymous recipient).

It also fixes what I think is a minor bug in
MixminionClient.generateReplyBlock, in which a failed password request (such
as one which hit the retry limit) leads to a cryptic exception inside a
crypto function instead of a more useful "bad password" UIError.

In addition, I am looking at adding something like a --surb-fd to 'mixmaster
send', which would make it possible to send messages through a SURB without
having to create a temporary file first. At the moment you can pass the
message body or the SURB in on stdin, but not both. I haven't investigated
how to actually implement this, though.. I suspect it will be trickier than
--passphrase-fd was.

cheers,
 -Brian

[1]: http://www.lothar.com/tech/spam/index.html
Index: lib/mixminion/Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.66
diff -u -r1.66 Main.py
--- lib/mixminion/Main.py	14 Dec 2003 01:43:38 -0000	1.66
+++ lib/mixminion/Main.py	15 Jan 2004 10:02:48 -0000
@@ -265,8 +265,8 @@
     if args[1] not in ('unittests', 'benchmarks', 'version') and \
        '--quiet' not in args and '-Q' not in args:
         import mixminion
-        print "Mixminion version %s" % mixminion.__version__
-        print "This software is for testing purposes only."\
+        print >>sys.stderr, "Mixminion version %s" % mixminion.__version__
+        print >>sys.stderr, "This software is for testing purposes only."\
               "  Anonymity is not guaranteed."
 
     # Read the 'common' module to get the UIError class.  To simplify
Index: lib/mixminion/ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.149
diff -u -r1.149 ClientMain.py
--- lib/mixminion/ClientMain.py	8 Jan 2004 22:35:24 -0000	1.149
+++ lib/mixminion/ClientMain.py	15 Jan 2004 10:07:53 -0000
@@ -194,7 +194,7 @@
     # keys: A ClientKeyring object.
     # queue: A ClientQueue object.
     # surbLogFilename: The filename used by the SURB log.
-    def __init__(self, conf):
+    def __init__(self, conf, password_fileno=None):
         """Create a new MixminionClient with a given configuration"""
         self.config = conf
 
@@ -202,7 +202,7 @@
         userdir = self.config['User']['UserDir']
         createPrivateDir(userdir)
         keyDir = os.path.join(userdir, "keys")
-        self.pwdManager = mixminion.ClientUtils.CLIPasswordManager()
+        self.pwdManager = mixminion.ClientUtils.CLIPasswordManager(password_fileno)
         self.keys = ClientKeyring(keyDir, self.pwdManager)
         self.surbLogFilename = os.path.join(userdir, "surbs", "log")
 
@@ -308,6 +308,8 @@
         """
         #XXXX write unit tests
         key = self.keys.getSURBKey(name=name, create=1)
+        if not key:
+            raise UIError("unable to get SURB key")
         exitType, exitInfo, _ = address.getRouting()
 
         block = mixminion.BuildMessage.buildReplyBlock(
@@ -781,6 +783,7 @@
         self.configFile = None
         self.verbose = 0
         self.download = None
+        self.password_fileno = None
 
         self.path = None
         self.nHops = None
@@ -848,6 +851,11 @@
                     self.lifetime = int(v)
                 except ValueError:
                     raise UsageError("%s expects an integer"%o)
+            elif o in ('--passphrase-fd',):
+                try:
+                    self.password_fileno = int(v)
+                except ValueError:
+                    raise UsageError("%s expects an integer"%o)
             elif o in ('--queue',):
                 self.forceQueue = 1
             elif o in ('--no-queue',):
@@ -890,7 +898,7 @@
         if self.wantClient:
             assert self.wantConfig
             LOG.debug("Configuring client")
-            self.client = MixminionClient(self.config)
+            self.client = MixminionClient(self.config, self.password_fileno)
 
         if self.wantClientDirectory:
             assert self.wantConfig
@@ -1564,6 +1572,8 @@
                                of ascii mode.
   -n <N>, --count=<N>        Generate <N> reply blocks. (Defaults to 1.)
   --identity=<name>          Specify a pseudonymous identity.
+  --passphrase-fd=<N>        Read passphrase from file descriptor N instead
+                               of asking on the console.
 
 EXAMPLES:
   Generate a reply block to deliver messages to the address given in
@@ -1591,7 +1601,7 @@
 def generateSURB(cmd, args):
     options, args = getopt.getopt(args, "hvf:D:t:H:P:o:bn:",
           ['help', 'verbose', 'config=', 'download-directory=',
-           'to=', 'hops=', 'path=', 'lifetime=',
+           'to=', 'hops=', 'path=', 'lifetime=', 'passphrase-fd=',
            'output=', 'binary', 'count=', 'identity='])
 
     outputFile = '-'
Index: lib/mixminion/ClientUtils.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientUtils.py,v
retrieving revision 1.16
diff -u -r1.16 ClientUtils.py
--- lib/mixminion/ClientUtils.py	3 Jan 2004 07:35:23 -0000	1.16
+++ lib/mixminion/ClientUtils.py	15 Jan 2004 10:07:53 -0000
@@ -37,6 +37,8 @@
     """A PasswordManager keeps track of a set of named passwords, so that
        a user never has to enter any password more than once.  This is an
        abstract class."""
+    do_retry = 1
+
     ## Fields
     # passwords: map from password name to string value of the password.
     def __init__(self):
@@ -83,6 +85,8 @@
             if confirmFn(pwd):
                 self.passwords[name] = pwd
                 return pwd
+            if not self.do_retry:
+                break
             maxTries -= 1
             pmt = "Incorrect password. "+prompt
 
@@ -95,13 +99,29 @@
 class CLIPasswordManager(PasswordManager):
     """Impementation of PasswordManager that asks for passwords from the
        command line."""
-    def __init__(self):
+    def __init__(self, password_fileno=None):
         PasswordManager.__init__(self)
+        self.password_fileno = password_fileno
+        if self.password_fileno != None:
+            self.do_retry = 0
     def _getPassword(self, name, prompt):
+        if self.password_fileno != None:
+            return getPassword_fd(self.password_fileno)
         return getPassword_term(prompt)
     def _getNewPassword(self, name, prompt):
+        if self.password_fileno != None:
+            return getPassword_fd(self.password_fileno)
         return getNewPassword_term(prompt)
 
+def getPassword_fd(fileno):
+    pw = ""
+    while 1:
+        chunk = os.read(fileno, 1024) # read from --password-fd filehandle
+        if not chunk:
+            break
+        pw += chunk
+    return pw
+
 def getPassword_term(prompt):
     """Read a password from the console, then return it.  Use the string
        'message' as a prompt."""