diff --git a/apps/addressbook/README.txt b/apps/addressbook/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfeec5f529995979704b2f683cb01b8b59494a3d
--- /dev/null
+++ b/apps/addressbook/README.txt
@@ -0,0 +1,32 @@
+addressbook v2.0.2 - A simple name resolution mechanism for I2P
+
+addressbook is a simple implementation of subscribable address books for I2P.  
+Addresses are stored in userhosts.txt and a second copy of the address book is 
+placed on your eepsite as hosts.txt. 
+
+subscriptions.txt contains a list of urls to check for new addresses.
+Since the urls are checked in order, and conflicting addresses are not added,
+addressbook.subscriptions can be considered to be ranked in order of trust.
+
+The system created by addressbook is similar to the early days of DNS,
+when everyone ran a local name server.  The major difference is the lack of
+authority.  Name cannot be guaranteed to be globally unique, but in practise 
+they probably will be, for a variety of social reasons.
+
+Requirements
+************
+
+i2p with a running http proxy
+
+Installation and Usage
+**********************
+
+1. Unzip addressbook-%ver.zip into your i2p directory. 
+2. Restart your router.
+
+The addressbook daemon will automatically run while the router is up.
+
+Aside from the daemon itself, the other elements of the addressbook interface
+are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook 
+directory.  Those files are largely self-documenting, so if you want to know what they 
+do, just read them.
\ No newline at end of file
diff --git a/apps/addressbook/build.xml b/apps/addressbook/build.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a8edbbc8f9f171559a51562c3f498a8ee5e00b82
--- /dev/null
+++ b/apps/addressbook/build.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<project name="addressbook" default="war" basedir=".">
+
+	<property name="src" value="java/src/addressbook"/>
+	<property name="build" value="build"/>
+	<property name="dist"  location="dist"/>
+	<property name="jar" value="addressbook.jar"/>
+	<property name="war" value="addressbook.war"/>
+	<property name="servlet" value="../jetty/jettylib/javax.servlet.jar"/>
+
+	<target name="init">
+		<mkdir dir="${build}"/>
+		<mkdir dir="${dist}"/>
+	</target>
+
+	<target name="clean">
+		<delete dir="${build}"/>
+		<delete dir="${dist}"/>
+	</target>
+
+	<target name="distclean" depends="clean" />
+
+	<target name="compile" depends="init">
+		<javac srcdir="${src}" destdir="${build}" classpath="${servlet}"/>
+	</target>
+
+	<target name="jar" depends="compile">
+		<jar basedir="${build}" destfile="${dist}/${jar}">
+			<manifest>
+				<attribute name="Main-Class" value="addressbook.Daemon"/>
+			</manifest>
+		</jar>
+	</target>
+	
+	<target name="war" depends="compile">
+		<mkdir dir="${dist}/tmp"/>
+		<mkdir dir="${dist}/tmp/WEB-INF"/>
+		<mkdir dir="${dist}/tmp/WEB-INF/classes"/>
+		<copy todir="${dist}/tmp/WEB-INF/classes">
+			<fileset dir="${build}"/>
+		</copy>
+		<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}"/>
+		<delete dir="${dist}/tmp"/>
+	</target>
+	
+</project>
diff --git a/apps/addressbook/config.txt b/apps/addressbook/config.txt
new file mode 100644
index 0000000000000000000000000000000000000000..53e03127e9920ddace6d802b39bc1a9a96658641
--- /dev/null
+++ b/apps/addressbook/config.txt
@@ -0,0 +1,43 @@
+# This is the configuration file for addressbook.
+#
+# Options
+# *******
+# All paths are realitive to i2p/addressbook.  Default value for
+# each option is given in parentheses.
+#
+# proxy_host                 The hostname of your I2P http proxy. 
+#                            (localhost)
+#
+# proxy_port                 The port of your I2P http proxy. (4444)
+#
+# master_addressbook         The path to your master address book, used for local
+#                            changes only. (myhosts.txt)
+#
+# router_addressbook         The path to the address book used by the router.  
+#                            Contains the addresses from your master address book
+#                            and your subscribed address books. (../userhosts.txt)
+#
+# published_addressbook      The path to the copy of your address book made 
+#                            available on i2p. (../eepsite/docroot/hosts.txt)
+#
+# log                        The path to your addressbook log. (log.txt)
+#
+# subscriptions              The path to your subscription file. (subscriptions.txt)
+#
+# etags                      The path to the etags header storage file. (etags)
+#
+# last_modified              The path to the last-modified header storage file. 
+#                            (last_modified)
+#
+# update_delay               The time (in hours) between each update. (1)
+
+proxy_host=localhost
+proxy_port=4444
+master_addressbook=myhosts.txt
+router_addressbook=../userhosts.txt
+published_addressbook=../eepsite/docroot/hosts.txt
+log=log.txt
+subscriptions=subscriptions.txt
+etags=etags
+last_modified=last_modified
+update_delay=1
diff --git a/apps/addressbook/java/src/addressbook/AddressBook.java b/apps/addressbook/java/src/addressbook/AddressBook.java
new file mode 100644
index 0000000000000000000000000000000000000000..4261f2d56e1cb5613d821c8003f3c64cbc768df5
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/AddressBook.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * An address book for storing human readable names mapped to base64 i2p
+ * destinations. AddressBooks can be created from local and remote files, merged
+ * together, and written out to local files.
+ * 
+ * @author Ragnarok
+ *  
+ */
+public class AddressBook {
+
+    private String location;
+
+    private Map addresses;
+
+    private boolean modified;
+
+    /**
+     * Construct an AddressBook from the contents of the Map addresses.
+     * 
+     * @param addresses
+     *            A Map containing human readable addresses as keys, mapped to
+     *            base64 i2p destinations.
+     */
+    public AddressBook(Map addresses) {
+        this.addresses = addresses;
+    }
+
+    /**
+     * Construct an AddressBook from the contents of the file at url. If the
+     * remote file cannot be read, construct an empty AddressBook
+     * 
+     * @param url
+     *            A URL pointing at a file with lines in the format "key=value",
+     *            where key is a human readable name, and value is a base64 i2p
+     *            destination.
+     */
+    public AddressBook(URL url) {
+        this.location = url.getHost();
+
+        try {
+            this.addresses = ConfigParser.parse(url);
+        } catch (IOException exp) {
+            this.addresses = new HashMap();
+        }
+    }
+
+    /**
+     * Construct an AddressBook from the Subscription subscription. If the
+     * address book at subscription has not changed since the last time it was
+     * read or cannot be read, return an empty AddressBook.
+     * 
+     * @param subscription
+     *            A Subscription instance pointing at a remote address book.
+     */
+    public AddressBook(Subscription subscription) {
+        this.location = subscription.getLocation();
+
+        try {
+            URL url = new URL(subscription.getLocation());
+            HttpURLConnection connection = (HttpURLConnection) url
+                    .openConnection();
+            if (subscription.getEtag() != null) {
+                connection.addRequestProperty("If-None-Match", subscription
+                        .getEtag());
+            }
+            if (subscription.getLastModified() != null) {
+                connection.addRequestProperty("If-Modified-Since", subscription
+                        .getLastModified());
+            }
+            connection.connect();
+            if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+                connection.disconnect();
+                this.addresses = new HashMap();
+                return;
+            }
+            if (connection.getHeaderField("ETag") != null) {
+                subscription.setEtag(connection.getHeaderField("ETag"));
+            }
+            if (connection.getHeaderField("Last-Modified") != null) {
+                subscription.setLastModified(connection
+                        .getHeaderField("Last-Modified"));
+            }
+        } catch (IOException exp) {
+        }
+
+        try {
+            this.addresses = ConfigParser.parse(new URL(subscription
+                    .getLocation()));
+        } catch (IOException exp) {
+            this.addresses = new HashMap();
+        }
+    }
+
+    /**
+     * Construct an AddressBook from the contents of the file at file. If the
+     * file cannot be read, construct an empty AddressBook
+     * 
+     * @param file
+     *            A File pointing at a file with lines in the format
+     *            "key=value", where key is a human readable name, and value is
+     *            a base64 i2p destination.
+     */
+    public AddressBook(File file) {
+        this.location = file.toString();
+        try {
+            this.addresses = ConfigParser.parse(file);
+        } catch (IOException exp) {
+            this.addresses = new HashMap();
+        }
+    }
+
+    /**
+     * Return a Map containing the addresses in the AddressBook.
+     * 
+     * @return A Map containing the addresses in the AddressBook, where the key
+     *         is a human readable name, and the value is a base64 i2p
+     *         destination.
+     */
+    public Map getAddresses() {
+        return this.addresses;
+    }
+
+    /**
+     * Return the location of the file this AddressBook was constructed from.
+     * 
+     * @return A String representing either an abstract path, or a url,
+     *         depending on how the instance was constructed.
+     */
+    public String getLocation() {
+        return this.location;
+    }
+
+    /**
+     * Return a string representation of the contents of the AddressBook.
+     * 
+     * @return A String representing the contents of the AddressBook.
+     */
+    public String toString() {
+        return this.addresses.toString();
+    }
+
+    /**
+     * Merge this AddressBook with AddressBook other, writing messages about new
+     * addresses or conflicts to log. Addresses in AddressBook other that are
+     * not in this AddressBook are added to this AddressBook. In case of a
+     * conflict, addresses in this AddressBook take precedence
+     * 
+     * @param other
+     *            An AddressBook to merge with.
+     * @param log
+     *            The log to write messages about new addresses or conflicts to.
+     */
+    public void merge(AddressBook other, Log log) {
+        Iterator otherIter = other.addresses.keySet().iterator();
+
+        while (otherIter.hasNext()) {
+            String otherKey = (String) otherIter.next();
+            String otherValue = (String) other.addresses.get(otherKey);
+
+            if (otherValue.length() >= 516) {
+                if (this.addresses.containsKey(otherKey)) {
+                    if (!this.addresses.get(otherKey).equals(otherValue)
+                            && log != null) {
+                        log.append("Conflict for " + otherKey + " from "
+                                + other.location
+                                + ". Destination in remote address book is "
+                                + otherValue);
+                    }
+                } else {
+                    this.addresses.put(otherKey, otherValue);
+                    this.modified = true;
+                    if (log != null) {
+                        log.append("New address " + otherKey
+                                + " added to address book.");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Merge this AddressBook with other, without logging.
+     * 
+     * @param other
+     *            An AddressBook to merge with.
+     */
+    public void merge(AddressBook other) {
+        this.merge(other, null);
+    }
+
+    /**
+     * Write the contents of this AddressBook out to the File file. If the file
+     * cannot be writen to, this method will silently fail.
+     * 
+     * @param file
+     *            The file to write the contents of this AddressBook too.
+     */
+    public void write(File file) {
+        if (this.modified) {
+            try {
+                ConfigParser.write(this.addresses, file);
+            } catch (IOException exp) {
+            }
+        }
+    }
+
+    /**
+     * Write this AddressBook out to the file it was read from. Requires that
+     * AddressBook was constructed from a file on the local filesystem. If the
+     * file cannot be writen to, this method will silently fail.
+     */
+    public void write() {
+        this.write(new File(this.location));
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/ConfigParser.java b/apps/addressbook/java/src/addressbook/ConfigParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ae0aa1326cb286bb638e8d7abd214965a550602
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/ConfigParser.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.io.*;
+import java.net.URL;
+
+/**
+ * Utility class providing methods to parse and write files in config file
+ * format, and subscription file format.
+ * 
+ * @author Ragnarok
+ */
+public class ConfigParser {
+
+    /**
+     * Strip the comments from a String. Lines that begin with '#' and ';' are
+     * considered comments, as well as any part of a line after a '#'.
+     * 
+     * @param inputLine
+     *            A String to strip comments from.
+     * @return A String without comments, but otherwise identical to inputLine.
+     */
+    public static String stripComments(String inputLine) {
+        if (inputLine.startsWith(";")) {
+            return "";
+        }
+        if (inputLine.split("#").length > 0) {
+            return inputLine.split("#")[0];
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Return a Map using the contents of BufferedReader input. input must have
+     * a single key, value pair on each line, in the format: key=value. Lines
+     * starting with '#' or ';' are considered comments, and ignored. Lines that
+     * are obviously not in the format key=value are also ignored.
+     * 
+     * @param input
+     *            A BufferedReader with lines in key=value format to parse into
+     *            a Map.
+     * @return A Map containing the key, value pairs from input.
+     * @throws IOException
+     *             if the BufferedReader cannot be read.
+     *  
+     */
+    public static Map parse(BufferedReader input) throws IOException {
+        Map result = new HashMap();
+        String inputLine;
+        inputLine = input.readLine();
+        while (inputLine != null) {
+            inputLine = ConfigParser.stripComments(inputLine);
+            String[] splitLine = inputLine.split("=");
+            if (splitLine.length == 2) {
+                result.put(splitLine[0].trim(), splitLine[1].trim());
+            }
+            inputLine = input.readLine();
+        }
+        input.close();
+        return result;
+    }
+
+    /**
+     * Return a Map using the contents of the file at url. See
+     * parseBufferedReader for details of the input format.
+     * 
+     * @param url
+     *            A url pointing to a file to parse.
+     * @return A Map containing the key, value pairs from url.
+     * @throws IOException
+     *             if url cannot be read.
+     */
+    public static Map parse(URL url) throws IOException {
+        InputStream urlStream;
+        urlStream = url.openConnection().getInputStream();
+        BufferedReader br = new BufferedReader(new InputStreamReader(urlStream));
+        return ConfigParser.parse(br);
+    }
+
+    /**
+     * Return a Map using the contents of the File file. See parseBufferedReader
+     * for details of the input format.
+     * 
+     * @param file
+     *            A File to parse.
+     * @return A Map containing the key, value pairs from file.
+     * @throws IOException
+     *             if file cannot be read.
+     */
+    public static Map parse(File file) throws IOException {
+        FileInputStream fileStream;
+        fileStream = new FileInputStream(file);
+        BufferedReader br = new BufferedReader(
+                new InputStreamReader(fileStream));
+        return ConfigParser.parse(br);
+    }
+
+    /**
+     * Return a List where each element is a line from the File file.
+     * 
+     * @param file
+     *            A File to parse.
+     * @return A List consisting of one element for each line in file.
+     * @throws IOException
+     *             if file cannot be read.
+     */
+    public static List parseSubscriptions(File file) throws IOException {
+        FileInputStream fileStream = new FileInputStream(file);
+        BufferedReader br = new BufferedReader(
+                new InputStreamReader(fileStream));
+        List result = new LinkedList();
+        String inputLine = br.readLine();
+        while (inputLine != null) {
+            inputLine = ConfigParser.stripComments(inputLine);
+            if (inputLine.trim().length() > 0) {
+                result.add(inputLine.trim());
+            }
+            inputLine = br.readLine();
+        }
+        br.close();
+        return result;
+    }
+
+    /**
+     * Write contents of Map hash to BufferedWriter output. Output is written
+     * with one key, value pair on each line, in the format: key=value.
+     * 
+     * @param hash
+     *            A Map to write to output.
+     * @param output
+     *            A BufferedWriter to write the Map to.
+     * @throws IOException
+     *             if the BufferedWriter cannot be written to.
+     */
+    public static void write(Map hash, BufferedWriter output)
+            throws IOException {
+        Iterator keyIter = hash.keySet().iterator();
+
+        while (keyIter.hasNext()) {
+            String key = (String) keyIter.next();
+            output.write(key + "=" + (String) hash.get(key));
+            output.newLine();
+        }
+        output.close();
+    }
+
+    /**
+     * Write contents of Map hash to the file at location. Output is written
+     * with one key, value pair on each line, in the format: key=value.
+     * 
+     * @param hash
+     *            A Map to write to file.
+     * @param file
+     *            A File to write the Map to.
+     * @throws IOException
+     *             if file cannot be written to.
+     */
+    public static void write(Map hash, File file) throws IOException {
+        ConfigParser.write(hash,
+                new BufferedWriter(new FileWriter(file, false)));
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Daemon.java b/apps/addressbook/java/src/addressbook/Daemon.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae1d8040f1b833609edd737f4b14dd0a2086138c
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Daemon.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Main class of addressbook.  Performs updates, and runs the main loop.
+ * 
+ * @author Ragnarok
+ *
+ */
+public class Daemon {
+
+    /**
+     * Update the router and published address books using remote data from the
+     * subscribed address books listed in subscriptions.
+     * 
+     * @param master
+     *            The master AddressBook. This address book is never
+     *            overwritten, so it is safe for the user to write to.
+     * @param router
+     *            The router AddressBook. This is the address book read by
+     *            client applications.
+     * @param published
+     *            The published AddressBook. This address book is published on
+     *            the user's eepsite so that others may subscribe to it.
+     * @param subscriptions
+     *            A SubscriptionList listing the remote address books to update
+     *            from.
+     * @param log
+     *            The log to write changes and conflicts to.
+     */
+    public static void update(AddressBook master, AddressBook router,
+            File published, SubscriptionList subscriptions, Log log) {
+        String routerLocation = router.getLocation();
+        master.merge(router);
+        Iterator iter = subscriptions.iterator();
+        while (iter.hasNext()) {
+            master.merge((AddressBook) iter.next(), log);
+        }
+        master.write(new File(routerLocation));
+        master.write(published);
+        subscriptions.write();
+    }
+
+    /**
+     * Run an update, using the Map settings to provide the parameters.
+     * 
+     * @param settings
+     *            A Map containg the parameters needed by update.
+     * @param home
+     *            The directory containing addressbook's configuration files.
+     */
+    public static void update(Map settings, String home) {
+        File masterFile = new File(home, (String) settings
+                .get("master_addressbook"));
+        File routerFile = new File(home, (String) settings
+                .get("router_addressbook"));
+        File published = new File(home, (String) settings
+                .get("published_addressbook"));
+        File subscriptionFile = new File(home, (String) settings
+                .get("subscriptions"));
+        File logFile = new File(home, (String) settings.get("log"));
+        File etagsFile = new File(home, (String) settings.get("etags"));
+        File lastModifiedFile = new File(home, (String) settings
+                .get("last_modified"));
+
+        AddressBook master = new AddressBook(masterFile);
+        AddressBook router = new AddressBook(routerFile);
+        SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
+                etagsFile, lastModifiedFile);
+        Log log = new Log(logFile);
+
+        Daemon.update(master, router, published, subscriptions, log);
+    }
+
+    /**
+     * Load the settings, set the proxy, then enter into the main loop. The main
+     * loop performs an immediate update, and then an update every number of
+     * hours, as configured in the settings file.
+     * 
+     * @param args
+     *            Command line arguments. If there are any arguments provided,
+     *            the first is taken as addressbook's home directory, and the
+     *            others are ignored.
+     */
+    public static void main(String[] args) {
+        String settingsLocation = "config.txt";
+        Map settings = new HashMap();
+        String home;
+        if (args.length > 0) {
+            home = args[0];
+        } else {
+            home = ".";
+        }
+        try {
+            settings = ConfigParser.parse(new File(home, settingsLocation));
+        } catch (IOException exp) {
+            System.out.println("Could not load " + settingsLocation);
+        }
+
+        System.setProperty("proxySet", "true");
+        System.setProperty("http.proxyHost", (String) settings
+                .get("proxy_host"));
+        System.setProperty("http.proxyPort", (String) settings
+                .get("proxy_port"));
+        long delay = Long.parseLong((String) settings.get("update_delay"));
+        if (delay < 1) {
+            delay = 1;
+        }
+        while (true) {
+            Daemon.update(settings, home);
+            try {
+                Thread.sleep(delay * 60 * 60 * 1000);
+            } catch (InterruptedException exp) {
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/DaemonThread.java b/apps/addressbook/java/src/addressbook/DaemonThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..efc3ab61d65922a3acaeda6ee649b7897463778e
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/DaemonThread.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+/**
+ * A thread that waits five minutes, then runs the addressbook daemon.  
+ * 
+ * @author Ragnarok
+ *
+ */
+public class DaemonThread extends Thread {
+
+    private String[] args;
+
+    /**
+     * Construct a DaemonThread with the command line arguments args.
+     * @param args
+     * A String array to pass to Daemon.main().
+     */
+    public DaemonThread(String[] args) {
+        this.args = args;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Runnable#run()
+     */
+    public void run() {
+        try {
+            Thread.sleep(5 * 60 * 1000);
+        } catch (InterruptedException exp) {
+        }
+        Daemon.main(this.args);
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Log.java b/apps/addressbook/java/src/addressbook/Log.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f9cfec382f0166f0f46ecd4db870291eab798f6
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Log.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * A simple log with automatic time stamping.
+ * 
+ * @author Ragnarok
+ *  
+ */
+public class Log {
+
+    private File file;
+
+    /**
+     * Construct a Log instance that writes to the File file.
+     * 
+     * @param file
+     *            A File for the log to write to.
+     */
+    public Log(File file) {
+        this.file = file;
+    }
+
+    /**
+     * Write entry to a new line in the log, with appropriate time stamp.
+     * 
+     * @param entry
+     *            A String containing a message to append to the log.
+     */
+    public void append(String entry) {
+        try {
+            BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
+                    true));
+            String timestamp = new Date().toString();
+            bw.write(timestamp + " -- " + entry);
+            bw.newLine();
+            bw.close();
+        } catch (IOException exp) {
+        }
+    }
+
+    /**
+     * Return the File that the Log is writing to.
+     * 
+     * @return The File that the log is writing to.
+     */
+    public File getFile() {
+        return this.file;
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Servlet.java b/apps/addressbook/java/src/addressbook/Servlet.java
new file mode 100644
index 0000000000000000000000000000000000000000..4826c9ed990782f6fa8c1992b79d0a17b2a72d7b
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Servlet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+/**
+ * A wrapper for addressbook to allow it to be started as a web application.
+ * 
+ * @author Ragnarok
+ *
+ */
+public class Servlet extends GenericServlet {
+
+    /* (non-Javadoc)
+     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public void service(ServletRequest request, ServletResponse response) {
+    }
+
+    /* (non-Javadoc)
+     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+     */
+    public void init(ServletConfig config) {
+        try {
+            super.init(config);
+        } catch (ServletException exp) {
+        }
+        String[] args = new String[1];
+        args[0] = config.getInitParameter("home");
+        DaemonThread thread = new DaemonThread(args);
+        thread.start();
+    }
+
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Subscription.java b/apps/addressbook/java/src/addressbook/Subscription.java
new file mode 100644
index 0000000000000000000000000000000000000000..1847535a965ce4aaa2235d5073873666f435584f
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Subscription.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+/**
+ * A subscription to a remote address book.
+ * 
+ * @author Ragnarok
+ *  
+ */
+public class Subscription {
+
+    private String location;
+
+    private String etag;
+
+    private String lastModified;
+
+    /**
+     * Construct a Subscription pointing to the address book at location, that
+     * was last read at the time represented by etag and lastModified.
+     * 
+     * @param location
+     *            A String representing a url to a remote address book.
+     * @param etag
+     *            The etag header that we recieved the last time we read this
+     *            subscription.
+     * @param lastModified
+     *            the last-modified header we recieved the last time we read
+     *            this subscription.
+     */
+    public Subscription(String location, String etag, String lastModified) {
+        this.location = location;
+        this.etag = etag;
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * Return the location this Subscription points at.
+     * 
+     * @return A String representing a url to a remote address book.
+     */
+    public String getLocation() {
+        return this.location;
+    }
+
+    /**
+     * Return the etag header that we recieved the last time we read this
+     * subscription.
+     * 
+     * @return A String containing the etag header.
+     */
+    public String getEtag() {
+        return this.etag;
+    }
+
+    /**
+     * Set the etag header.
+     * 
+     * @param etag
+     *            A String containing the etag header.
+     */
+    public void setEtag(String etag) {
+        this.etag = etag;
+    }
+
+    /**
+     * Return the last-modified header that we recieved the last time we read
+     * this subscription.
+     * 
+     * @return A String containing the last-modified header.
+     */
+    public String getLastModified() {
+        return this.lastModified;
+    }
+
+    /**
+     * Set the last-modified header.
+     * 
+     * @param lastModified
+     *            A String containing the last-modified header.
+     */
+    public void setLastModified(String lastModified) {
+        this.lastModified = lastModified;
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/SubscriptionIterator.java b/apps/addressbook/java/src/addressbook/SubscriptionIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..fecbb5c324feae5330e43e9f4c225cb3c1d08165
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/SubscriptionIterator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator over the subscriptions in a SubscriptionList.  Note that this iterator
+ * returns AddressBook objects, and not Subscription objects.
+ * 
+ * @author Ragnarok
+ */
+public class SubscriptionIterator implements Iterator {
+
+    private Iterator subIterator;
+
+    /**
+     * Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
+     * 
+     * @param subscriptions
+     *            List of Subscription objects that represent address books.
+     */
+    public SubscriptionIterator(List subscriptions) {
+        this.subIterator = subscriptions.iterator();
+    }
+
+    
+    /* (non-Javadoc)
+     * @see java.util.Iterator#hasNext()
+     */
+    public boolean hasNext() {
+        return subIterator.hasNext();
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Iterator#next()
+     */
+    public Object next() {
+        Subscription sub = (Subscription) subIterator.next();
+        return new AddressBook(sub);
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Iterator#remove()
+     */
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/SubscriptionList.java b/apps/addressbook/java/src/addressbook/SubscriptionList.java
new file mode 100644
index 0000000000000000000000000000000000000000..95f51483284e8180782217654ab8603dc0e53d9f
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/SubscriptionList.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A list of Subscriptions loaded from a file.
+ * 
+ * @author Ragnarok
+ *  
+ */
+public class SubscriptionList {
+
+    private List subscriptions;
+
+    private File etagsFile;
+
+    private File lastModifiedFile;
+
+    /**
+     * Construct a SubscriptionList using the urls from locationsFile and, if
+     * available, the etags and last-modified headers loaded from etagsFile and
+     * lastModifiedFile.
+     * 
+     * @param locationsFile
+     *            A file containing one url on each line.
+     * @param etagsFile
+     *            A file containg the etag headers used for conditional GET. The
+     *            file is in the format "url=etag".
+     * @param lastModifiedFile
+     *            A file containg the last-modified headers used for conditional
+     *            GET. The file is in the format "url=leastmodified".
+     */
+    public SubscriptionList(File locationsFile, File etagsFile,
+            File lastModifiedFile) {
+        this.subscriptions = new LinkedList();
+        this.etagsFile = etagsFile;
+        this.lastModifiedFile = lastModifiedFile;
+        List locations;
+        Map etags;
+        Map lastModified;
+        String location;
+        try {
+            locations = ConfigParser.parseSubscriptions(locationsFile);
+        } catch (IOException exp) {
+            locations = new LinkedList();
+        }
+        try {
+            etags = ConfigParser.parse(etagsFile);
+        } catch (IOException exp) {
+            etags = new HashMap();
+        }
+        try {
+            lastModified = ConfigParser.parse(lastModifiedFile);
+        } catch (IOException exp) {
+            lastModified = new HashMap();
+        }
+        Iterator iter = locations.iterator();
+        while (iter.hasNext()) {
+            location = (String) iter.next();
+            subscriptions.add(new Subscription(location, (String) etags
+                    .get(location), (String) lastModified.get(location)));
+        }
+
+        iter = this.iterator();
+    }
+
+    /**
+     * Return an iterator over the AddressBooks represented by the Subscriptions
+     * in this SubscriptionList.
+     * 
+     * @return A SubscriptionIterator.
+     */
+    public SubscriptionIterator iterator() {
+        return new SubscriptionIterator(this.subscriptions);
+    }
+
+    /**
+     * Write the etag and last-modified headers for each Subscription to files.
+     */
+    public void write() {
+        Iterator iter = this.subscriptions.iterator();
+        Subscription sub;
+        Map etags = new HashMap();
+        Map lastModified = new HashMap();
+        while (iter.hasNext()) {
+            sub = (Subscription) iter.next();
+            if (sub.getEtag() != null) {
+                etags.put(sub.getLocation(), sub.getEtag());
+            }
+            if (sub.getLastModified() != null) {
+                lastModified.put(sub.getLocation(), sub.getLastModified());
+            }
+        }
+        try {
+            ConfigParser.write(etags, this.etagsFile);
+            ConfigParser.write(lastModified, this.lastModifiedFile);
+        } catch (IOException exp) {
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/addressbook/myhosts.txt b/apps/addressbook/myhosts.txt
new file mode 100644
index 0000000000000000000000000000000000000000..680d4204bbc0cc3b1ad9be78f1144f04d18a48c0
--- /dev/null
+++ b/apps/addressbook/myhosts.txt
@@ -0,0 +1,10 @@
+# addressbook master address book.  Addresses placed in this file take precidence
+# over those in the router address book and in remote address books.  If changes
+# are made to this file, they will be reflected in the router address book and 
+# published address book after the next update.
+#
+# Do not make changes directly to the router address book, as they could be lost
+# during an update.
+#
+# This file takes addresses in the hosts.txt format, i.e.
+# example.i2p=somereallylongbase64thingAAAA
diff --git a/apps/addressbook/subscriptions.txt b/apps/addressbook/subscriptions.txt
new file mode 100644
index 0000000000000000000000000000000000000000..62741055bda5031ab5ad79ccf391184799e4043e
--- /dev/null
+++ b/apps/addressbook/subscriptions.txt
@@ -0,0 +1,7 @@
+# Subscription list for addressbook
+#
+# Each entry is an absolute url to a file in hosts.txt format.
+# Since the list is checked in order, url's should be listed in order of trust.
+#
+http://dev.i2p/i2p/hosts.txt
+http://duck.i2p/hosts.txt
diff --git a/apps/addressbook/web.xml b/apps/addressbook/web.xml
new file mode 100644
index 0000000000000000000000000000000000000000..22d0d7d23a13894066c17cd075e429cb498fda11
--- /dev/null
+++ b/apps/addressbook/web.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE web-app
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
+    "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
+
+<web-app>
+	<servlet>
+		<servlet-name>addressbook</servlet-name>
+		<servlet-class>addressbook.Servlet</servlet-class>
+		<init-param>
+		    <param-name>home</param-name>
+		    <param-value>./addressbook</param-value>
+		</init-param>
+		<load-on-startup>1</load-on-startup>
+	</servlet>
+</web-app>
\ No newline at end of file