formatting
[org.ibex.mail.git] / src / org / ibex / mail / Whitelist.java
index 06724fe..95fb162 100644 (file)
@@ -1,8 +1,10 @@
 package org.ibex.mail;
 
 import org.ibex.io.*;
+import org.ibex.net.*;
 import org.ibex.mail.protocol.*;
 import org.ibex.util.*;
+import org.ibex.net.*;
 import java.sql.*;
 import java.net.*;
 import java.io.*;
@@ -10,78 +12,59 @@ import java.util.*;
 import java.sql.Timestamp;
 import java.sql.Connection;
 
-// now all I need is the click-through page
+public class Whitelist extends SqliteDB {
 
-// FIXME: periodic cleanup
-public class Whitelist {
-
-    private Connection conn;
-
-    // FIXME very ugly
-    static {
-        new Thread() { public void run() { startWebServer(); } }.start();
+    public Whitelist(String filename) throws SQLException {
+        super(filename);
+        SqliteTable whitelist = getTable("whitelist", "(email)");
+        whitelist.createIndex("email");
+        SqliteTable pending   = getTable("pending",   "(spamid,email,message,date)");
+        pending.reap("date");
+        pending.createIndex("spamid");
+        pending.createIndex("email");
     }
-    public static void startWebServer() {
+
+    public boolean handleRequest(org.ibex.net.Connection c) {
         try {
-            ServerSocket ss = new ServerSocket(8025);
-            while(true) {
-                final Socket sock = ss.accept();
-                new Thread() {
-                    public void run() {
-                        try {
-                            BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
-                            String s = br.readLine();
-                            String url = s.substring(s.indexOf(' ')+1);
-                            url = url.substring(0, url.indexOf(' '));
-                            while(s!=null && !s.equals(""))
-                                s = br.readLine();
-                            PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
-                            if (!url.startsWith("/whitelist/")) {
-                                pw.print("HTTP/1.0 404 Not FoundK\r\n");
-                                pw.print("Content-Type: text/plain\r\n");
-                                pw.print("\r\n");
-                                pw.println("you are lost.");
-                            } else {
-                                url = url.substring("/whitelist/".length());
-                                url = URLDecoder.decode(url);
-                                pw.print("HTTP/1.0 200 OK\r\n");
-                                pw.print("Content-Type: text/plain\r\n");
-                                pw.print("\r\n");
-                                try {
-                                    SMTP.whitelist.response(url);
-                                    pw.println("Thanks!  You've been added to my list of non-spammers and your message");
-                                    pw.println("has been moved to my inbox.");
-                                    pw.println("email id " + url);
-                                    pw.println("");
-                                } catch (Exception e) {
-                                    e.printStackTrace(pw);
-                                }
-                            }
-                            pw.flush();
-                            sock.close();
-                        } catch (Exception e) { throw new RuntimeException(e); }
-                    }
-                }.start();
+            Socket sock = c.getSocket();
+            BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
+            String s = br.readLine();
+            String url = s.substring(s.indexOf(' ')+1);
+            url = url.substring(0, url.indexOf(' '));
+            while(s!=null && !s.equals(""))
+                s = br.readLine();
+            PrintWriter pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
+            if (!url.startsWith("/whitelist/")) {
+                pw.print("HTTP/1.0 404 Not Found\r\n");
+                pw.print("Content-Type: text/plain\r\n");
+                pw.print("\r\n");
+                pw.println("you are lost.");
+            } else {
+                url = url.substring("/whitelist/".length());
+                url = URLDecoder.decode(url);
+                if (url.endsWith(".txt")) url = url.substring(0, url.length()-4);
+                pw.print("HTTP/1.0 200 OK\r\n");
+                pw.print("Content-Type: text/plain\r\n");
+                pw.print("\r\n");
+                try {
+                    SMTP.whitelist.response(url);
+                    pw.println("Thanks!  You've been added to my list of non-spammers and your message");
+                    pw.println("has been moved to my inbox.");
+                    pw.println("email id " + url);
+                    pw.println("");
+                } catch (Exception e) {
+                    e.printStackTrace(pw);
+                }
             }
+            pw.flush();
+            sock.close();
         } catch (Exception e) { throw new RuntimeException(e); }
-    }
-
-
-    public Whitelist(String filename) {
-        try {
-            Class.forName("org.sqlite.JDBC");
-            conn = DriverManager.getConnection("jdbc:sqlite:"+filename);
-            conn.prepareStatement("create table if not exists "+
-                                  "'whitelist' (email)").executeUpdate();
-            conn.prepareStatement("create table if not exists "+
-                                  "'pending' (spamid,email,message,date)").executeUpdate();
-        }
-        catch (SQLException e) { throw new RuntimeException(e); }
-        catch (ClassNotFoundException e) { throw new RuntimeException(e); }
+        return true;
     }
 
     public synchronized boolean isWhitelisted(Address a) {
         try {
+            if (a==null) return false;
             PreparedStatement check = conn.prepareStatement("select * from 'whitelist' where email=?");
             check.setString(1, a.toString(false).toLowerCase());
             ResultSet rs = check.executeQuery();
@@ -104,17 +87,46 @@ public class Whitelist {
             ResultSet rs = query.executeQuery();
             if (!rs.next())
                 throw new RuntimeException("could not find messageid \""+messageid+"\"");
-            do {
-                addWhitelist(Address.parse(rs.getString(1)));
-                Message m = Message.newMessage(new Fountain.StringFountain(rs.getString(2)));
+            HashSet<Message> hsm = new HashSet<Message>();
+            synchronized(this) {
+                do {
+                    addWhitelist(Address.parse(rs.getString(1)));
+                    Message m = Message.newMessage(new Fountain.StringFountain(rs.getString(2)));
+                    Address a = m.headers.get("reply-to")==null ? null : Address.parse(m.headers.get("reply-to"));
+                    if (a!=null) addWhitelist(a);
+                    a = m.from;
+                    if (a!=null) addWhitelist(a);
+                    a = m.envelopeFrom;
+                    if (a!=null) addWhitelist(a);
+                    hsm.add(m);
+                    if (m.cc != null) for(Address aa : m.cc) {
+                        if (aa!= null) addWhitelist(aa);
+                    }
+                } while (rs.next());
+            }
+            for(Message m : hsm)
                 Target.root.accept(m);
-            } while (rs.next());
         } catch (SQLException e) { throw new RuntimeException(e); }
     }
 
-    public synchronized void challenge(Message m) {
+    public void challenge(Message m) {
         try {
+            // FIXME: don't challenge emails with binaries in them;
+            // reject them outright and have the sender send an
+            // initial message w/o a binary.
+
+            // FIXME: use Auto here!!!
+            // The challenge should refer to the message-id of the mail being challenged. 
+
+            // FIXME: watch outgoing MessageID's: if something comes
+            // back with an In-Reply-To mentioning a MessageID from
+            // the last few days, auto-whitelist them.
+
+            // FIXME: important that "From" on the challenge matches
+            // RCPT TO on the original message.
+
             Log.warn(Whitelist.class, "challenging message: " + m.summary());
+
             Address to = m.headers.get("reply-to")==null ? null : Address.parse(m.headers.get("reply-to"));
             if (to==null) to = m.from;
             if (to==null) to = m.envelopeFrom;
@@ -129,6 +141,10 @@ public class Whitelist {
                          "with Auto-Submitted=\""+m.headers.get("Auto-Submitted")+"\"");
                 return;
             }
+            if (m.headers.get("List-Id") != null || m.headers.get("List-Post") != null) {
+                Log.warn(this, "refusing to send a challenge to a message with a List-Id or List-Post header");
+                return;
+            }
 
             Address from = Address.parse("adam@megacz.com");
 
@@ -137,7 +153,7 @@ public class Whitelist {
             messageid = messageid.replace('%','_');
             Log.warn(Whitelist.class, "got challenge for: " + messageid);
 
-            String url = "http://www.megacz.com:8025/whitelist/"+URLEncoder.encode(messageid);
+            String url = "http://www.megacz.com:8025/whitelist/"+URLEncoder.encode(messageid)+".txt";
             String message =
                 "Return-Path: <>"                                     + "\r\n" +
                 "Envelope-To: "           + to                        + "\r\n" +
@@ -159,7 +175,8 @@ public class Whitelist {
                 "\n"                                                                              +
                 url+"\n" +
                 "\n"                                                                              +
-                "\n"                                                                              +
+                "\n"                                                                              
+                /*
                 "About this message:\n" +
                 "\n"                                                                              +
                 "NOTE: SPAMCOP DOES NOT CONSIDER THIS TO BE SPAM; see this:\n"+
@@ -183,26 +200,37 @@ public class Whitelist {
                 "      \n"+
                 "      For more information, please see:\n"+
                 "      \n"+
-                "      http://www.templetons.com/brad/spam/crgood.html\n";
-
+                "      http://www.templetons.com/brad/spam/crgood.html\n"
+                */
+                ;
             Message challenge = Message.newMessage(new Fountain.StringFountain(message));
-            if (!SMTP.Outgoing.attempt(challenge))
-                throw new RuntimeException("attempted to send challenge but could not: " + m.summary());
-
-            PreparedStatement add = conn.prepareStatement("insert into pending values(?,?,?,?)");
-            add.setString(1, messageid);
-            add.setString(2, to.toString(false));
-            add.setString(3, streamToString(m.getStream()));
-            add.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
-            add.executeUpdate();
+
+            boolean send = false;
+            synchronized(this) {
+                PreparedStatement query = conn.prepareStatement("select email from pending where email=?");
+                query.setString(1, to.toString(false));
+                ResultSet rs = query.executeQuery();
+                if (rs.next()) {
+                    Log.warn(this, "already challenged " + to.toString(false) + "; not challenging again.");
+                } else {
+                    send = true;
+                }
+            }
+
+            if (send)
+                if (!SMTP.Outgoing.attempt(challenge))
+                    throw new RuntimeException("attempted to send challenge but could not: " + m.summary());
+
+            synchronized(this) {
+                PreparedStatement add = conn.prepareStatement("insert into pending values(?,?,?,?)");
+                add.setString(1, messageid);
+                add.setString(2, to.toString(false));
+                add.setString(3, SqliteDB.streamToString(m.getStream()));
+                add.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
+                add.executeUpdate();
+            }
         } catch (Exception e) { throw new RuntimeException(e); }
     }
 
-    private static String streamToString(Stream stream) throws Exception {
-        StringBuffer b = new StringBuffer();
-        for(String s = stream.readln(); s!=null; s=stream.readln())
-            b.append(s+"\n");
-        return b.toString();
-    }
 }