add org.ibex.mail.VERP
authoradam <adam@megacz.com>
Mon, 22 Jun 2009 00:10:08 +0000 (00:10 +0000)
committeradam <adam@megacz.com>
Mon, 22 Jun 2009 00:10:08 +0000 (00:10 +0000)
darcs-hash:20090622001008-5007d-055e271a158b0865faa77c128107d66f867e880e.gz

src/org/ibex/mail/Script.java
src/org/ibex/mail/VERP.java [new file with mode: 0644]

index 3a829c6..9459c1f 100644 (file)
@@ -139,6 +139,8 @@ public class Script extends JS.Obj implements Target {
             case "mail.clamav.check": return METHOD;
            case "mail.procmail": /* FEATURE */ return null;
            case "mail.vacation": /* FEATURE */ return null;
+           case "mail.verp": return getSub("mail.verp");
+           case "mail.verp.check": return METHOD;
            case "mail.dcc": return getSub("mail.dcc");
             case "mail.dcc.check": return METHOD;
             case "mail.bounce": return METHOD;
@@ -252,6 +254,10 @@ public class Script extends JS.Obj implements Target {
                     new Stream(p.getInputStream()).transcribe(ret);
                     return JSU.S(ret.toString());
                }
+                if (name.equals("mail.verp.check")) {
+                    String ret = VERP.verpVerify(Address.parse(JSU.toString(a)), "SECRET".getBytes(), 0);
+                    return ret==null ? null : JSU.S(ret);
+                }
                 if (name.equals("mail.dcc.check")) {
                    Process p = Runtime.getRuntime().exec(new String[] { "dccproc", "-H" });
                    ((Message)args[0]).getStream().transcribe(new Stream(p.getOutputStream()), true);
diff --git a/src/org/ibex/mail/VERP.java b/src/org/ibex/mail/VERP.java
new file mode 100644 (file)
index 0000000..19b47db
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright 2000-2008 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.mail;
+import org.ibex.crypto.*;
+import org.ibex.js.*;
+import org.ibex.util.*;
+import org.ibex.mail.protocol.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+
+// http://cr.yp.to/proto/verp.txt
+public class VERP {
+
+    private static final Random random = new Random();
+
+    public static Address verpSign(Address a, byte[] secret) {
+        return verpSign(a, secret, System.currentTimeMillis());
+    }
+
+    // format:
+    //   VERP--<when>-<salt>-<sig>-<user>@domain.com
+    //     <when> = 64-bit hexadecimal integer
+    //     <salt> = 16-bit hexadecimal integer
+    //     <user> = original username (ie local-part)
+    //     <sig>  = base64(sha1(VERP--<when>-<salt>-<user><secret>))
+    /** converts an Address into a VERP-signed address with signature time "when" and extra payload "extra" */
+    public static Address verpSign(Address a, byte[] secret, long when) {
+        if (a.user==null || "".equals(a.user)) {
+            Log.warn(VERP.class, "note: not VERP-signing the null address: "+a);
+            return a;
+        }
+
+        int salt = Math.abs(random.nextInt()) & 0xffff;
+
+        StringBuffer ret = new StringBuffer();
+        ret.append("VERP--");
+        ret.append(Long.toString(when, 16));
+        ret.append('-');
+        ret.append(Integer.toString(salt, 16));
+        ret.append('-');
+
+        byte[] b = (ret.toString()+a.user).getBytes();
+        SHA1 sha1 = new SHA1();
+        sha1.update(b, 0, b.length);
+        sha1.update(secret, 0, secret.length);
+        b = new byte[sha1.getDigestSize()];
+        sha1.doFinal(b, 0);
+
+        ret.append(new String(Encode.toBase64(b)));
+        ret.append('-');
+        ret.append(a.user);
+        // FIXME: encode a.host in here
+        return new Address(ret.toString(), SMTP.localHostIsMXFor, a.description);
+    }
+
+    // FIXME: return unverpified address
+    /** returns the field passed as "extra" when signing, or null if signature invalid */
+    public static String verpVerify(Address a, byte[] secret, long noEarlierThan) {
+        String s = a.user;
+        int i = 0;
+        i = s.indexOf("--", i); if (i==-1) return null; i += 2;
+        int first = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int second = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int third = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int fourth = i;
+        String verify = s.substring(0,third)+s.substring(fourth);
+        Log.error("VERP", "verify=\""+verify+"\"");
+
+        byte[] b = verify.getBytes();
+        SHA1 sha1 = new SHA1();
+        sha1.update(b, 0, b.length);
+        sha1.update(secret, 0, secret.length);
+        b = new byte[sha1.getDigestSize()];
+        sha1.doFinal(b, 0);
+        if (!new String(Encode.toBase64(b)).equals(s.substring(third,fourth-1))) {
+            Log.error("VERP", "decrypt failed\n"+new String(Encode.toBase64(b))+"\n"+s.substring(third,fourth-1));
+            return null;
+        }
+        // FIXME: check the host on the address
+        long when = Long.parseLong(s.substring(first, second-1), 16);
+        Log.error("VERP", "when="+when);
+        if (when < noEarlierThan) return null;
+        return s.substring(fourth);
+    }
+
+}