diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/access/AccessFilter.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/access/AccessFilter.java
index bc3ec2913a15e381c4103714c7e7d202fc90a044..94c9933e5805845a2bac479ca7bb5ec926477323 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/access/AccessFilter.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/access/AccessFilter.java
@@ -2,15 +2,28 @@ package net.i2p.i2ptunnel.access;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Iterator;
 
+import java.io.File;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
 import java.io.IOException;
 
 import net.i2p.I2PAppContext;
+import net.i2p.util.SimpleTimer2;
+import net.i2p.util.Log;
 import net.i2p.data.Destination;
 import net.i2p.client.streaming.IncomingConnectionFilter;
 
 class AccessFilter implements IncomingConnectionFilter {
 
+    private static final long PURGE_INTERVAL = 1000;
+    private static final long SYNC_INTERVAL = 10 * 1000;
+
     private final FilterDefinition definition;
     private final I2PAppContext context;
 
@@ -23,9 +36,14 @@ class AccessFilter implements IncomingConnectionFilter {
      */
     private final Map<String, DestTracker> unknownDests = new HashMap<String, DestTracker>();
 
-    AccessFilter(I2PAppContext context, FilterDefinition definition) {
+    AccessFilter(I2PAppContext context, FilterDefinition definition) throws IOException {
         this.context = context;
         this.definition = definition;
+
+        reload();
+
+        new Purger();
+        new Syncer();
     }
 
     @Override
@@ -56,4 +74,87 @@ class AccessFilter implements IncomingConnectionFilter {
         }
         
     }
+
+    private void record() throws IOException {
+        for (Recorder recorder : definition.getRecorders()) {
+            Threshold threshold = recorder.getThreshold();
+            File file = recorder.getFile();
+            Set<String> breached = new HashSet<String>();
+            synchronized(unknownDests) {
+                for (DestTracker tracker : unknownDests.values()) {
+                    if (!tracker.getCounter().isBreached(threshold))
+                        continue;
+                    breached.add(tracker.getB32());
+                }
+            }
+            if (breached.isEmpty())
+                continue;
+
+            // if the file already exists, add previously breached b32s
+            if (file.exists() && file.isFile()) {
+                BufferedReader reader = new BufferedReader(new FileReader(file));
+                try {
+                    String b32;
+                    while((b32 = reader.readLine()) != null)
+                        breached.add(b32);
+                } finally {
+                    if (reader != null) try { reader.close(); } catch (IOException ignored) {}
+                }
+            }
+
+            BufferedWriter writer = new BufferedWriter(new FileWriter(file));
+            try {
+                for (String b32 : breached) {
+                    writer.write(b32);
+                    writer.newLine();
+                }
+            } finally {
+                if (writer != null) try { writer.close(); } catch (IOException ignored) {}
+            }
+        }
+    }
+
+    private void purge() {
+        long olderThan = context.clock().now() - definition.getPurgeMinutes() * 60000;
+        
+        synchronized(knownDests) {
+            for (DestTracker tracker : knownDests.values())
+                tracker.purge(olderThan);
+        }
+
+        synchronized(unknownDests) {
+            for (Iterator<Map.Entry<String,DestTracker>> iter = unknownDests.entrySet().iterator();
+                    iter.hasNext();) {
+                Map.Entry<String,DestTracker> entry = iter.next();
+                if (entry.getValue().purge(olderThan))
+                    iter.remove();
+            }
+        }
+    }
+
+    private class Purger extends SimpleTimer2.TimedEvent {
+        Purger() {
+            super(context.simpleTimer2(), PURGE_INTERVAL);
+        }
+        public void timeReached() {
+            purge();
+            schedule(PURGE_INTERVAL);
+        }
+    }
+
+    private class Syncer extends SimpleTimer2.TimedEvent {
+        Syncer() {
+            super(context.simpleTimer2(), SYNC_INTERVAL);
+        }
+        public void timeReached() {
+            try {
+                record();
+                reload();
+                schedule(SYNC_INTERVAL);
+            } catch (IOException bad) {
+                Log log = context.logManager().getLog(AccessFilter.class);
+                log.log(Log.CRIT, "syncing access list failed", bad);
+            }
+        }
+    }
 }
diff --git a/build.properties b/build.properties
index f7820e33a4692ba53a64b3bfdb10e9515dec7afa..706fd975e2ffb2ea5d12f8dfcb86ae9834f865ac 100644
--- a/build.properties
+++ b/build.properties
@@ -31,13 +31,13 @@ sloccount.report.file=sloccount.sc
 # Building EXEs in x64 Linux requires that 32bit libraries are installed. In Debian,
 # for example, installing the libc6-i386 package will satisfy this requirement.
 # Uncomment the next line to prevent building EXEs (changing it to false will have no impact)
-#noExe=true
+noExe=true
 
 # IzPack 5.1.x install dir
 #izpack5.home=/PATH/TO/IzPack
 
 # Change this to false if you don't have gettext or you want to prevent it from running during the build
-require.gettext=true
+require.gettext=false
 
 # Compile for this version of Java
 javac.version=1.7