diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java
deleted file mode 100644
index 3fcf9312d6dc7780954bd0fa46252768d1cea860..0000000000000000000000000000000000000000
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java
+++ /dev/null
@@ -1,421 +0,0 @@
-package net.i2p.i2ptunnel;
-
-import java.util.Iterator;
-import java.util.Properties;
-import java.util.Random;
-import java.util.StringTokenizer;
-
-/**
- * Uuuugly code to generate the edit/add forms for the various 
- * I2PTunnel types (httpclient/client/server)
- *
- */
-class WebEditPageFormGenerator {
-    private static final String SELECT_TYPE_FORM =
-      "<form action=\"edit.jsp\"> Type of tunnel: <select name=\"type\">" +
-      "<option value=\"httpclient\">HTTP proxy</option>" +
-      "<option value=\"client\">Client tunnel</option>" +
-      "<option value=\"server\">Server tunnel</option>" +
-      "<option value=\"httpserver\">HTTP server tunnel</option>" +
-      "</select> <input type=\"submit\" value=\"GO\" />" +
-      "</form>\n";
-    
-    /**
-     * Retrieve the form requested
-     *
-     */
-    public static String getForm(WebEditPageHelper helper) {
-        TunnelController controller = helper.getTunnelController();
-
-        if ( (helper.getType() == null) && (controller == null) )
-            return SELECT_TYPE_FORM;
-        
-        String id = helper.getNum();
-        String type = helper.getType();
-        if (controller != null)
-            type = controller.getType();
-        
-        if ("httpclient".equals(type))
-            return getEditHttpClientForm(controller, id);
-        else if ("client".equals(type))
-            return getEditClientForm(controller, id);
-        else if ("server".equals(type))
-            return getEditServerForm(controller, id);
-        else if ("httpserver".equals(type))
-            return getEditHttpServerForm(controller, id);
-        else
-            return "WTF, unknown type [" + type + "]";
-    }
-    
-    private static String getEditHttpClientForm(TunnelController controller, String id) {
-        StringBuffer buf = new StringBuffer(1024);
-        addGeneral(buf, controller, id);
-        buf.append("<b>Type:</b> <i>HTTP proxy</i><input type=\"hidden\" name=\"type\" value=\"httpclient\" /><br />\n");
-        
-        addListeningOn(buf, controller, 4444);
-        
-        buf.append("<b>Outproxies:</b> <input type=\"text\" name=\"proxyList\" size=\"20\" ");
-        if ( (controller != null) && (controller.getProxyList() != null) )
-            buf.append("value=\"").append(controller.getProxyList()).append("\" ");
-        else
-            buf.append("value=\"squid.i2p\" ");
-        buf.append("/><br />\n");
-        
-        addStreamingOptions(buf, controller);
-        
-        buf.append("<hr />Note: the following options are shared across all client tunnels and");
-        buf.append(" HTTP proxies<br />\n");
-        
-        addOptions(buf, controller);
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
-        buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
-        buf.append("</form>\n");
-        return buf.toString();
-    }
-    
-    private static String getEditClientForm(TunnelController controller, String id) {
-        StringBuffer buf = new StringBuffer(1024);
-        addGeneral(buf, controller, id);
-        buf.append("<b>Type:</b> <i>Client tunnel</i><input type=\"hidden\" name=\"type\" value=\"client\" /><br />\n");
-        
-        addListeningOn(buf, controller, 2025 + new Random().nextInt(1000)); // 2025 since nextInt can be negative
-
-        buf.append("<b>Target:</b> <input type=\"text\" size=\"40\" name=\"targetDestination\" ");
-        if ( (controller != null) && (controller.getTargetDestination() != null) )
-            buf.append("value=\"").append(controller.getTargetDestination()).append("\" ");
-        buf.append(" /> (either the hosts.txt name or the full base64 destination)<br />\n");
-        
-        addStreamingOptions(buf, controller);
-        
-        buf.append("<hr />Note: the following options are shared across all client tunnels and");
-        buf.append(" HTTP proxies<br />\n");
-        
-        addOptions(buf, controller);
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
-        buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
-        buf.append("</form>\n");
-        return buf.toString();
-    }
-    
-    private static String getEditServerForm(TunnelController controller, String id) {
-        StringBuffer buf = new StringBuffer(1024);
-        addGeneral(buf, controller, id);
-        buf.append("<b>Type:</b> <i>Server tunnel</i><input type=\"hidden\" name=\"type\" value=\"server\" /><br />\n");
-        
-        buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
-        if ( (controller != null) && (controller.getTargetHost() != null) )
-            buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
-        else
-            buf.append("value=\"127.0.0.1\" ");
-        buf.append(" /><br />\n");
-        
-        buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
-        if ( (controller != null) && (controller.getTargetPort() != null) )
-            buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
-        else
-            buf.append("value=\"80\" ");
-        buf.append(" /><br />\n");
-        
-        buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
-        if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
-            buf.append(controller.getPrivKeyFile()).append("\" /><br />");
-        } else {
-            buf.append("myServer.privKey\" /><br />");
-            buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
-        }
-        
-        addStreamingOptions(buf, controller);
-        
-        addOptions(buf, controller);
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
-        buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
-        buf.append("</form>\n");
-        return buf.toString();
-    }
-    
-    private static String getEditHttpServerForm(TunnelController controller, String id) {
-        StringBuffer buf = new StringBuffer(1024);
-        addGeneral(buf, controller, id);
-        buf.append("<b>Type:</b> <i>HTTP server tunnel</i><input type=\"hidden\" name=\"type\" value=\"httpserver\" /><br />\n");
-        
-        buf.append("<b>Target host:</b> <input type=\"text\" size=\"40\" name=\"targetHost\" ");
-        if ( (controller != null) && (controller.getTargetHost() != null) )
-            buf.append("value=\"").append(controller.getTargetHost()).append("\" ");
-        else
-            buf.append("value=\"127.0.0.1\" ");
-        buf.append(" /><br />\n");
-        
-        buf.append("<b>Target port:</b> <input type=\"text\" size=\"4\" name=\"targetPort\" ");
-        if ( (controller != null) && (controller.getTargetPort() != null) )
-            buf.append("value=\"").append(controller.getTargetPort()).append("\" ");
-        else
-            buf.append("value=\"80\" ");
-        buf.append(" /><br />\n");
-        
-        buf.append("<b>Website hostname:</b> <input type=\"text\" size=\"16\" name=\"spoofedHost\" ");
-        if ( (controller != null) && (controller.getSpoofedHost() != null) )
-            buf.append("value=\"").append(controller.getSpoofedHost()).append("\" ");
-        else
-            buf.append("value=\"mysite.i2p\" ");
-        buf.append(" /><br />\n");
-        
-        buf.append("<b>Private key file:</b> <input type=\"text\" name=\"privKeyFile\" value=\"");
-        if ( (controller != null) && (controller.getPrivKeyFile() != null) ) {
-            buf.append(controller.getPrivKeyFile()).append("\" /><br />");
-        } else {
-            buf.append("myServer.privKey\" /><br />");
-            buf.append("<input type=\"hidden\" name=\"privKeyGenerate\" value=\"true\" />");
-        }
-        
-        addStreamingOptions(buf, controller);
-        
-        addOptions(buf, controller);
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
-        buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
-        buf.append("</form>\n");
-        return buf.toString();
-    }
-    
-    /**
-     * Start off the form and add some common fields (name, num, description)
-     *
-     * @param buf where to shove the form
-     * @param controller tunnel in question, or null if we're creating a new tunnel
-     * @param id index into the current list of tunnelControllerGroup.getControllers() list
-     *           (or null if we are generating an 'add' form)
-     */
-    private static void addGeneral(StringBuffer buf, TunnelController controller, String id) {
-        buf.append("<form action=\"edit.jsp\">");
-        if (id != null)
-            buf.append("<input type=\"hidden\" name=\"num\" value=\"").append(id).append("\" />");
-        long nonce = new Random().nextLong();
-        System.setProperty(WebEditPageHelper.class.getName() + ".nonce", nonce+"");
-        buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(nonce).append("\" />");
-            
-        buf.append("<b>Name:</b> <input type=\"text\" name=\"name\" size=\"20\" ");
-        if ( (controller != null) && (controller.getName() != null) )
-            buf.append("value=\"").append(controller.getName()).append("\" ");
-        buf.append("/><br />\n");
-        
-        buf.append("<b>Description:</b> <input type=\"text\" name=\"description\" size=\"60\" ");
-        if ( (controller != null) && (controller.getDescription() != null) )
-            buf.append("value=\"").append(controller.getDescription()).append("\" ");
-        buf.append("/><br />\n");
-        
-        buf.append("<b>Start automatically?</b> \n");
-        buf.append("<input type=\"checkbox\" name=\"startOnLoad\" value=\"true\" ");
-        if ( (controller != null) && (controller.getStartOnLoad()) )
-            buf.append(" checked=\"true\" />\n<br />\n");
-        else
-            buf.append(" />\n<br />\n");
-       
-    }
-    
-    /**
-     * Generate the fields asking for what port and interface the tunnel should 
-     * listen on.
-     *
-     * @param buf where to shove the form
-     * @param controller tunnel in question, or null if we're creating a new tunnel
-     * @param defaultPort if we are creating a new tunnel, default the form to the given port
-     */
-    private static void addListeningOn(StringBuffer buf, TunnelController controller, int defaultPort) {
-        buf.append("<b>Listening on port:</b> <input type=\"text\" name=\"port\" size=\"20\" ");
-        if ( (controller != null) && (controller.getListenPort() != null) )
-            buf.append("value=\"").append(controller.getListenPort()).append("\" ");
-        else
-            buf.append("value=\"").append(defaultPort).append("\" ");
-        buf.append("/><br />\n");
-
-        String selectedOn = null;
-        if ( (controller != null) && (controller.getListenOnInterface() != null) )
-            selectedOn = controller.getListenOnInterface();
-        
-        buf.append("<b>Reachable by:</b> ");
-        buf.append("<select name=\"reachableBy\">");
-        buf.append("<option value=\"127.0.0.1\" ");
-        if ( (selectedOn != null) && ("127.0.0.1".equals(selectedOn)) )
-            buf.append("selected=\"true\" ");
-        buf.append(">Locally (127.0.0.1)</option>\n");
-        buf.append("<option value=\"0.0.0.0\" ");
-        if ( (selectedOn != null) && ("0.0.0.0".equals(selectedOn)) )
-            buf.append("selected=\"true\" ");
-        buf.append(">Everyone (0.0.0.0)</option>\n");
-        buf.append("</select> ");
-        buf.append("Other: <input type=\"text\" name=\"reachableByOther\" value=\"");
-        if ( (selectedOn != null) && (!"127.0.0.1".equals(selectedOn)) && (!"0.0.0.0".equals(selectedOn)) )
-            buf.append(selectedOn);
-        buf.append("\"><br />\n");
-    }
-    
-    private static void addStreamingOptions(StringBuffer buf, TunnelController controller) {
-        int connectDelay = 0;
-        int maxWindowSize = -1;
-
-        Properties opts = getOptions(controller);
-        if (opts != null) {
-            String delay = opts.getProperty("i2p.streaming.connectDelay");
-            if (delay != null) {
-                try {
-                    connectDelay = Integer.parseInt(delay);
-                } catch (NumberFormatException nfe) {
-                    connectDelay = 0;
-                }
-            }
-            String max = opts.getProperty("i2p.streaming.maxWindowSize");
-            if (max != null) {
-                try {
-                    maxWindowSize = Integer.parseInt(max);
-                } catch (NumberFormatException nfe) {
-                    maxWindowSize = -1;
-                }
-            }
-        }
-        
-        buf.append("<b>Delay connection briefly? </b> ");
-        buf.append("<input type=\"checkbox\" name=\"connectDelay\" value=\"");
-        buf.append((connectDelay > 0 ? connectDelay : 1000)).append("\" ");
-        if (connectDelay > 0)
-            buf.append("checked=\"true\" ");
-        buf.append("/> (useful for brief request/response connections)<br />\n");
-        
-        buf.append("<b>Communication profile:</b>");
-        buf.append("<select name=\"profile\">");
-        if (maxWindowSize <= 0)
-            buf.append("<option value=\"interactive\">Interactive</option><option value=\"bulk\" selected=\"true\">Bulk</option>");
-        else
-            buf.append("<option value=\"interactive\" selected=\"true\">Interactive</option><option value=\"bulk\">Bulk</option>");
-        buf.append("</select><br />\n");
-    }
-
-    /**
-     * Add fields for customizing the I2PSession options, including helpers for
-     * tunnel depth and count, as well as I2CP host and port.
-     *
-     * @param buf where to shove the form
-     * @param controller tunnel in question, or null if we're creating a new tunnel
-     */
-    private static void addOptions(StringBuffer buf, TunnelController controller) {
-        int tunnelDepth = 2;
-        int numTunnels = 2;
-        Properties opts = getOptions(controller);
-        if (opts != null) {
-            String depth = opts.getProperty("inbound.length");
-            if (depth != null) {
-                try {
-                    tunnelDepth = Integer.parseInt(depth);
-                } catch (NumberFormatException nfe) {
-                    tunnelDepth = 2;
-                }
-            }
-            String num = opts.getProperty("inbound.quantity");
-            if (num != null) {
-                try {
-                    numTunnels = Integer.parseInt(num);
-                } catch (NumberFormatException nfe) {
-                    numTunnels = 2;
-                }
-            }
-        }
-        
-        buf.append("<b>Tunnel depth:</b> ");
-        buf.append("<select name=\"tunnelDepth\">");
-        buf.append("<option value=\"0\" ");
-        if (tunnelDepth == 0) buf.append(" selected=\"true\" ");
-        buf.append(">0 hop tunnel (low anonymity, low latency)</option>");
-        buf.append("<option value=\"1\" ");
-        if (tunnelDepth == 1) buf.append(" selected=\"true\" ");
-        buf.append(">1 hop tunnel (medium anonymity, medium latency)</option>");
-        buf.append("<option value=\"2\" ");
-        if (tunnelDepth == 2) buf.append(" selected=\"true\" ");
-        buf.append(">2 hop tunnel (high anonymity, high latency)</option>");
-        if (tunnelDepth > 2) {
-            buf.append("<option value=\"").append(tunnelDepth).append("\" selected=\"true\" >");
-            buf.append(tunnelDepth);
-            buf.append(" hop tunnel (custom)</option>");
-        }
-        buf.append("</select><br />\n");
-        
-        buf.append("<b>Tunnel count:</b> ");
-        buf.append("<select name=\"tunnelCount\">");
-        buf.append("<option value=\"1\" ");
-        if (numTunnels == 1) buf.append(" selected=\"true\" ");
-        buf.append(">1 inbound tunnel (low bandwidth usage, less reliability)</option>");
-        buf.append("<option value=\"2\" ");
-        if (numTunnels == 2) buf.append(" selected=\"true\" ");
-        buf.append(">2 inbound tunnels (standard bandwidth usage, standard reliability)</option>");
-        buf.append("<option value=\"3\" ");
-        if (numTunnels == 3) buf.append(" selected=\"true\" ");
-        buf.append(">3 inbound tunnels (higher bandwidth usage, higher reliability)</option>");
-        
-        if (numTunnels > 3) {
-            buf.append("<option value=\"").append(numTunnels).append("\" selected=\"true\" >");
-            buf.append(numTunnels);
-            buf.append(" inbound tunnels (custom)</option>");
-        }
-        buf.append("</select><br />\n");
-        
-        buf.append("<b>I2CP host:</b> ");
-        buf.append("<input type=\"text\" name=\"clientHost\" size=\"20\" value=\"");
-        if ( (controller != null) && (controller.getI2CPHost() != null) )
-            buf.append(controller.getI2CPHost());
-        else
-            buf.append("127.0.0.1");
-        buf.append("\" /><br />\n");
-        buf.append("<b>I2CP port:</b> ");
-        buf.append("<input type=\"text\" name=\"clientPort\" size=\"20\" value=\"");
-        if ( (controller != null) && (controller.getI2CPPort() != null) )
-            buf.append(controller.getI2CPPort());
-        else
-            buf.append("7654");
-        buf.append("\" /><br />\n");
-        
-        buf.append("<b>Other custom options:</b> \n");
-        buf.append("<input type=\"text\" name=\"customOptions\" size=\"60\" value=\"");
-        if (opts != null) {
-            int i = 0;
-            for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
-                String key = (String)iter.next();
-                String val = opts.getProperty(key);
-                if ("inbound.length".equals(key)) continue;
-                if ("outbound.length".equals(key)) continue;
-                if ("inbound.quantity".equals(key)) continue;
-                if ("outbound.quantity".equals(key)) continue;
-                if ("inbound.nickname".equals(key)) continue;
-                if ("outbound.nickname".equals(key)) continue;
-                if ("i2p.streaming.connectDelay".equals(key)) continue;
-                if ("i2p.streaming.maxWindowSize".equals(key)) continue;
-                if (i != 0) buf.append(' ');
-                buf.append(key).append('=').append(val);
-                i++;
-            }
-        }
-        buf.append("\" /><br />\n");
-    }
-
-    /**
-     * Retrieve the client options from the tunnel
-     *
-     * @return map of name=val to be used as I2P session options
-     */
-    private static Properties getOptions(TunnelController controller) {
-        if (controller == null) return null;
-        String opts = controller.getClientOptions();
-        StringTokenizer tok = new StringTokenizer(opts);
-        Properties props = new Properties();
-        while (tok.hasMoreTokens()) {
-            String pair = tok.nextToken();
-            int eq = pair.indexOf('=');
-            if ( (eq <= 0) || (eq >= pair.length()) )
-                continue;
-            String key = pair.substring(0, eq);
-            String val = pair.substring(eq+1);
-            props.setProperty(key, val);
-        }
-        return props;
-    }
-}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebStatusPageHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebStatusPageHelper.java
deleted file mode 100644
index 0137f81c65118f5867253c331af901ae04345ee0..0000000000000000000000000000000000000000
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebStatusPageHelper.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package net.i2p.i2ptunnel;
-
-import java.util.List;
-import net.i2p.I2PAppContext;
-import net.i2p.util.Log;
-
-/**
- * Ugly hack to let the web interface access the list of known tunnels and
- * control their operation.  Any data submitted by setting properties are 
- * acted upon by calling getActionResults() (which returns any messages
- * generated).  In addition, the getSummaryList() generates the html for
- * summarizing all of the tunnels known, including both their status and the
- * links to edit, stop, or start them.
- *
- */
-public class WebStatusPageHelper {
-    private I2PAppContext _context;
-    private Log _log;
-    private String _action;
-    private int _controllerNum;
-    private long _nonce;
-    
-    public WebStatusPageHelper() {
-        _context = I2PAppContext.getGlobalContext();
-        _action = null;
-        _controllerNum = -1;
-        _log = _context.logManager().getLog(WebStatusPageHelper.class);
-    }
-    
-    public void setAction(String action) {
-        _action = action;
-    }
-    public void setNum(String num) {
-        if (num != null) {
-            try {
-                _controllerNum = Integer.parseInt(num);
-            } catch (NumberFormatException nfe) {
-                _controllerNum = -1;
-            }
-        }
-    }
-    public void setNonce(long nonce) { _nonce = nonce; }
-    public void setNonce(String nonce) {
-        if (nonce != null) {
-            try { 
-                _nonce = Long.parseLong(nonce); 
-            } catch (NumberFormatException nfe) {}
-        }
-    }
-    
-    public String getActionResults() {
-        try {
-            return processAction();
-        } catch (Throwable t) {
-            _log.log(Log.CRIT, "Internal error processing web status", t);
-            return "Internal error processing request - " + t.getMessage();
-        }
-    }
-    
-    public String getSummaryList() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-            
-        long nonce = _context.random().nextLong();
-        StringBuffer buf = new StringBuffer(4*1024);
-        buf.append("<ul>");
-        List tunnels = group.getControllers();
-        for (int i = 0; i < tunnels.size(); i++) {
-            buf.append("<li>\n");
-            getSummary(buf, i, (TunnelController)tunnels.get(i), nonce);
-            buf.append("</li>\n");
-        }
-        buf.append("</ul>");
-        
-        buf.append("<hr /><form action=\"index.jsp\" method=\"GET\">\n");
-        buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(nonce).append("\" />\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Stop all\" />\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Start all\" />\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Restart all\" />\n");
-        buf.append("<input type=\"submit\" name=\"action\" value=\"Reload config\" />\n");        
-        buf.append("</form>\n");
-        
-        System.setProperty(getClass().getName() + ".nonce", nonce+"");
-        
-        return buf.toString();
-    }
-    
-    private void getSummary(StringBuffer buf, int num, TunnelController controller, long nonce) {
-        buf.append("<b>").append(controller.getName()).append("</b>: ");
-        if (controller.getIsRunning()) {
-            buf.append("<i>running</i> ");
-            buf.append("<a href=\"index.jsp?num=").append(num);
-            buf.append("&nonce=").append(nonce);
-            buf.append("&action=stop\">stop</a> ");
-        } else if (controller.getIsStarting()) {
-            buf.append("<i>startup in progress (please be patient)</i>");
-        } else {
-            buf.append("<i>not running</i> ");
-            buf.append("<a href=\"index.jsp?num=").append(num);
-            buf.append("&nonce=").append(nonce);
-            buf.append("&action=start\">start</a> ");
-        }
-        buf.append("<a href=\"edit.jsp?num=").append(num).append("\">edit</a> ");
-        buf.append("<br />\n");
-        controller.getSummary(buf);
-    }
-    
-    private String processAction() {
-        if ( (_action == null) || (_action.trim().length() <= 0) )
-            return getMessages();
-        String expected = System.getProperty(getClass().getName() + ".nonce");
-        if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
-            return "<b>Invalid nonce, are you being spoofed?</b>";
-        if ("Stop all".equals(_action)) 
-            return stopAll();
-        else if ("Start all".equals(_action))
-            return startAll();
-        else if ("Restart all".equals(_action))
-            return restartAll();
-        else if ("Reload config".equals(_action))
-            return reloadConfig();
-        else if ("stop".equals(_action))
-            return stop();
-        else if ("start".equals(_action))
-            return start();
-        else 
-            return "Action <i>" + _action + "</i> unknown";
-    }
-    private String stopAll() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        List msgs = group.stopAllControllers();
-        return getMessages(msgs);
-    }
-    private String startAll() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        List msgs = group.startAllControllers();
-        return getMessages(msgs);
-    }
-    private String restartAll() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        List msgs = group.restartAllControllers();
-        return getMessages(msgs);
-    }
-    private String reloadConfig() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        group.reloadControllers();
-        return "Config reloaded";
-    }
-    private String start() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        if (_controllerNum < 0) return "Invalid tunnel";
-        
-        List controllers = group.getControllers();
-        if (_controllerNum >= controllers.size()) return "Invalid tunnel";
-        TunnelController controller = (TunnelController)controllers.get(_controllerNum);
-        controller.startTunnelBackground();
-        return getMessages(controller.clearMessages());
-    }
-    
-    private String stop() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "<b>I2PTunnel instances not yet started - please be patient</b>\n";
-        
-        if (_controllerNum < 0) return "Invalid tunnel";
-        
-        List controllers = group.getControllers();
-        if (_controllerNum >= controllers.size()) return "Invalid tunnel";
-        TunnelController controller = (TunnelController)controllers.get(_controllerNum);
-        controller.stopTunnel();
-        return getMessages(controller.clearMessages());
-    }
-    
-    private String getMessages() {
-        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
-        if (group == null)
-            return "";
-        
-        return getMessages(group.clearAllMessages());
-    }
-    
-    private String getMessages(List msgs) {
-        if (msgs == null) return "";
-        int num = msgs.size();
-        switch (num) {
-            case 0: return "";
-            case 1: return (String)msgs.get(0);
-            default:
-                StringBuffer buf = new StringBuffer(512);
-                buf.append("<ul>");
-                for (int i = 0; i < num; i++)
-                    buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
-                buf.append("</ul>\n");
-                return buf.toString();
-        }
-    }
-}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
new file mode 100644
index 0000000000000000000000000000000000000000..e72fe61666753b9b8578b362a873d3ec77dd0b46
--- /dev/null
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
@@ -0,0 +1,225 @@
+package net.i2p.i2ptunnel.web;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2005 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import net.i2p.I2PAppContext;
+import net.i2p.i2ptunnel.TunnelController;
+import net.i2p.i2ptunnel.TunnelControllerGroup;
+import net.i2p.util.Log;
+
+/**
+ * Ugly little accessor for the edit page
+ */
+public class EditBean extends IndexBean {
+    public EditBean() { super(); }
+    
+    public static boolean staticIsClient(int tunnel) {
+        TunnelControllerGroup group = TunnelControllerGroup.getInstance();
+        List controllers = group.getControllers();
+        if (controllers.size() > tunnel) {
+            TunnelController cur = (TunnelController)controllers.get(tunnel);
+            if (cur == null) return false;
+            return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
+        } else {
+            return false;
+        }
+    }
+    
+    public String getInternalType(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getType();
+        else
+            return "";
+    }
+    
+    public String getTargetHost(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getTargetHost();
+        else
+            return "";
+    }
+    public String getTargetPort(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getTargetPort();
+        else
+            return "";
+    }
+    public String getSpoofedHost(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getSpoofedHost();
+        else
+            return "";
+    }
+    public String getPrivateKeyFile(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getPrivKeyFile();
+        else
+            return "";
+    }
+    
+    public boolean startAutomatically(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getStartOnLoad();
+        else
+            return false;
+    }
+    
+    public boolean shouldDelay(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null) {
+            Properties opts = getOptions(tun);
+            if (opts != null) {
+                String delay = opts.getProperty("i2p.streaming.connectDelay");
+                if ( (delay == null) || ("0".equals(delay)) )
+                    return false;
+                else
+                    return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+    
+    public boolean isInteractive(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null) {
+            Properties opts = getOptions(tun);
+            if (opts != null) {
+                String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
+                if ( (wsiz == null) || (!"1".equals(wsiz)) )
+                    return false;
+                else
+                    return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+    
+    public int getTunnelDepth(int tunnel, int defaultLength) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null) {
+            Properties opts = getOptions(tun);
+            if (opts != null) {
+                String len = opts.getProperty("inbound.length");
+                if (len == null) return defaultLength;
+                try {
+                    return Integer.parseInt(len);
+                } catch (NumberFormatException nfe) {
+                    return defaultLength;
+                }
+            } else {
+                return defaultLength;
+            }
+        } else {
+            return defaultLength;
+        }
+    }
+    
+    public int getTunnelCount(int tunnel, int defaultCount) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null) {
+            Properties opts = getOptions(tun);
+            if (opts != null) {
+                String len = opts.getProperty("inbound.quantity");
+                if (len == null) return defaultCount;
+                try {
+                    return Integer.parseInt(len);
+                } catch (NumberFormatException nfe) {
+                    return defaultCount;
+                }
+            } else {
+                return defaultCount;
+            }
+        } else {
+            return defaultCount;
+        }
+    }
+    
+    public String getI2CPHost(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getI2CPHost();
+        else
+            return "localhost";
+    }
+    
+    public String getI2CPPort(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getI2CPPort();
+        else
+            return "7654";
+    }
+
+    public String getCustomOptions(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null) {
+            Properties opts = getOptions(tun);
+            if (opts == null) return "";
+            StringBuffer buf = new StringBuffer(64);
+            int i = 0;
+            for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
+                String key = (String)iter.next();
+                String val = opts.getProperty(key);
+                if ("inbound.length".equals(key)) continue;
+                if ("outbound.length".equals(key)) continue;
+                if ("inbound.quantity".equals(key)) continue;
+                if ("outbound.quantity".equals(key)) continue;
+                if ("inbound.nickname".equals(key)) continue;
+                if ("outbound.nickname".equals(key)) continue;
+                if ("i2p.streaming.connectDelay".equals(key)) continue;
+                if ("i2p.streaming.maxWindowSize".equals(key)) continue;
+                if (i != 0) buf.append(' ');
+                buf.append(key).append('=').append(val);
+                i++;
+            }
+            return buf.toString();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Retrieve the client options from the tunnel
+     *
+     * @return map of name=val to be used as I2P session options
+     */
+    private static Properties getOptions(TunnelController controller) {
+        if (controller == null) return null;
+        String opts = controller.getClientOptions();
+        StringTokenizer tok = new StringTokenizer(opts);
+        Properties props = new Properties();
+        while (tok.hasMoreTokens()) {
+            String pair = tok.nextToken();
+            int eq = pair.indexOf('=');
+            if ( (eq <= 0) || (eq >= pair.length()) )
+                continue;
+            String key = pair.substring(0, eq);
+            String val = pair.substring(eq+1);
+            props.setProperty(key, val);
+        }
+        return props;
+    }
+}
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
similarity index 52%
rename from apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java
rename to apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
index 3f42c682d5b91ed7958c43574c25beea06dbf11e..c2120eb2b24aafe63d9f1ab52ad1aa2f75738139 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
@@ -1,28 +1,38 @@
-package net.i2p.i2ptunnel;
+package net.i2p.i2ptunnel.web;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2005 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
 
 import java.util.List;
 import java.util.Properties;
 import java.util.StringTokenizer;
 
 import net.i2p.I2PAppContext;
+import net.i2p.i2ptunnel.TunnelController;
+import net.i2p.i2ptunnel.TunnelControllerGroup;
 import net.i2p.util.Log;
 
 /**
- * UUUUuuuuuugly glue code to handle bean interaction from the web, process
- * that data, and spit out the results (or the form requested).  The basic 
- * usage is to set any of the fields with data then query the bean via 
- * getActionResults() which triggers the request processing (taking all the 
- * provided data, doing what needs to be done) and returns the results of those
- * activites.  Then a subsequent call to getEditForm() generates the HTML form
- * to either edit the currently selected tunnel (if specified) or add a new one.
- * This functionality is delegated to the WebEditPageFormGenerator.
+ * Simple accessor for exposing tunnel info, but also an ugly form handler
  *
  */
-public class WebEditPageHelper {
-    private Log _log;
+public class IndexBean {
+    protected I2PAppContext _context;
+    protected Log _log;
+    protected TunnelControllerGroup _group;
     private String _action;
+    private int _tunnel;
+    private long _prevNonce;
+    private long _curNonce;
+    private long _nextNonce;
+    private String _passphrase;
+
     private String _type;
-    private String _id;
     private String _name;
     private String _description;
     private String _i2cpHost;
@@ -44,30 +54,306 @@ public class WebEditPageHelper {
     private boolean _startOnLoad;
     private boolean _privKeyGenerate;
     private boolean _removeConfirmed;
-    private long _nonce;
     
-    public WebEditPageHelper() {
+    public static final int RUNNING = 1;
+    public static final int STARTING = 2;
+    public static final int NOT_RUNNING = 3;
+    
+    public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
+    static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
+    static final String CLIENT_NICKNAME = "shared clients";
+    
+    public IndexBean() {
+        _context = I2PAppContext.getGlobalContext();
+        _log = _context.logManager().getLog(IndexBean.class);
+        _group = TunnelControllerGroup.getInstance();
         _action = null;
-        _type = null;
-        _id = null;
-        _removeConfirmed = false;
-        _log = I2PAppContext.getGlobalContext().logManager().getLog(WebEditPageHelper.class);
+        _tunnel = -1;
+        _curNonce = -1;
+        _prevNonce = -1;
+        try { 
+            String nonce = System.getProperty(PROP_NONCE);
+            if (nonce != null)
+                _prevNonce = Long.parseLong(nonce);
+        } catch (NumberFormatException nfe) {}
+        _nextNonce = _context.random().nextLong();
+        System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
     }
     
+    public long getNextNonce() { return _nextNonce; }
     public void setNonce(String nonce) {
-        if (nonce != null) {
-            try { 
-                _nonce = Long.parseLong(nonce); 
-            } catch (NumberFormatException nfe) {}
+        if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
+        try {
+            _curNonce = Long.parseLong(nonce);
+        } catch (NumberFormatException nfe) {
+            _curNonce = -1;
+        }
+    }
+    public void setPassphrase(String phrase) {
+        _passphrase = phrase;
+    }
+    
+    public void setAction(String action) {
+        if ( (action == null) || (action.trim().length() <= 0) ) return;
+        _action = action;
+    }
+    public void setTunnel(String tunnel) {
+        if ( (tunnel == null) || (tunnel.trim().length() <= 0) ) return;
+        try {
+            _tunnel = Integer.parseInt(tunnel);
+        } catch (NumberFormatException nfe) {
+            _tunnel = -1;
         }
     }
     
+    private boolean validPassphrase(String proposed) {
+        if (proposed == null) return false;
+        String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
+        if ( (pass != null) && (pass.trim().length() > 0) ) 
+            return pass.trim().equals(proposed.trim());
+        else
+            return false;
+    }
+    
+    private String processAction() {
+        if ( (_action == null) || (_action.trim().length() <= 0) )
+            return "";
+        if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
+            return "Invalid nonce, are you being spoofed?";
+        if ("Stop all tunnels".equals(_action)) 
+            return stopAll();
+        else if ("Start all tunnels".equals(_action))
+            return startAll();
+        else if ("Restart all".equals(_action))
+            return restartAll();
+        else if ("Reload config".equals(_action))
+            return reloadConfig();
+        else if ("stop".equals(_action))
+            return stop();
+        else if ("start".equals(_action))
+            return start();
+        else if ("Save changes".equals(_action))
+            return saveChanges();
+        else if ("Delete this proxy".equals(_action))
+            return deleteTunnel();
+        else
+            return "Action " + _action + " unknown";
+    }
+    private String stopAll() {
+        if (_group == null) return "";
+        List msgs = _group.stopAllControllers();
+        return getMessages(msgs);
+    }
+    private String startAll() {
+        if (_group == null) return "";
+        List msgs = _group.startAllControllers();
+        return getMessages(msgs);
+    }
+    private String restartAll() {
+        if (_group == null) return "";
+        List msgs = _group.restartAllControllers();
+        return getMessages(msgs);
+    }
+    private String reloadConfig() {
+        if (_group == null) return "";
+        
+        _group.reloadControllers();
+        return "Config reloaded";
+    }
+    private String start() {
+        if (_tunnel < 0) return "Invalid tunnel";
+        
+        List controllers = _group.getControllers();
+        if (_tunnel >= controllers.size()) return "Invalid tunnel";
+        TunnelController controller = (TunnelController)controllers.get(_tunnel);
+        controller.startTunnelBackground();
+        return "";
+    }
+    
+    private String stop() {
+        if (_tunnel < 0) return "Invalid tunnel";
+        
+        List controllers = _group.getControllers();
+        if (_tunnel >= controllers.size()) return "Invalid tunnel";
+        TunnelController controller = (TunnelController)controllers.get(_tunnel);
+        controller.stopTunnel();
+        return "";
+    }
+    
+    private String saveChanges() {
+        TunnelController cur = getController(_tunnel);
+        
+        Properties config = getConfig();
+        if (config == null)
+            return "Invalid params";
+        
+        if (cur == null) {
+            // creating new
+            cur = new TunnelController(config, "", true);
+            _group.addController(cur);
+            if (cur.getStartOnLoad())
+                cur.startTunnelBackground();
+        } else {
+            cur.setConfig(config, "");
+        }
+        
+        if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
+            // all clients use the same I2CP session, and as such, use the same
+            // I2CP options
+            List controllers = _group.getControllers();
+            for (int i = 0; i < controllers.size(); i++) {
+                TunnelController c = (TunnelController)controllers.get(i);
+                if (c == cur) continue;
+                if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
+                    Properties cOpt = c.getConfig("");
+                    if (_tunnelCount != null) {
+                        cOpt.setProperty("option.inbound.quantity", _tunnelCount);
+                        cOpt.setProperty("option.outbound.quantity", _tunnelCount);
+                    }
+                    if (_tunnelDepth != null) {
+                        cOpt.setProperty("option.inbound.length", _tunnelDepth);
+                        cOpt.setProperty("option.outbound.length", _tunnelDepth);
+                    }
+                    cOpt.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
+                    cOpt.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
+                    
+                    c.setConfig(cOpt, "");
+                }
+            }
+        }
+        
+        List msgs = doSave();
+        msgs.add(0, "Changes saved");
+        return getMessages(msgs);
+    }
+    private List doSave() { 
+        _group.saveConfig();
+        return _group.clearAllMessages();
+    } 
+    private String deleteTunnel() {
+        if (!_removeConfirmed)
+            return "Please confirm removal";
+        
+        TunnelController cur = getController(_tunnel);
+        if (cur == null)
+            return "Invalid tunnel number";
+        
+        List msgs = _group.removeController(cur);
+        msgs.addAll(doSave());
+        return getMessages(msgs);
+    }
+    
     /**
-     * Used for form submit - either "Save" or Remove"
+     * Executes any action requested (start/stop/etc) and dump out the 
+     * messages.
+     *
      */
-    public void setAction(String action) { 
-        _action = (action != null ? action.trim() : null);   
+    public String getMessages() {
+        if (_group == null)
+            return "";
+        
+        StringBuffer buf = new StringBuffer(512);
+        if (_action != null) {
+            try {
+                buf.append(processAction()).append("\n");
+            } catch (Exception e) {
+                _log.log(Log.CRIT, "Error processing " + _action, e);
+            }
+        }
+        getMessages(_group.clearAllMessages(), buf);
+        return buf.toString();
+    }
+    
+    ////
+    // The remaining methods are simple bean props for the jsp to query
+    ////
+    
+    public int getTunnelCount() {
+        if (_group == null) return 0;
+        return _group.getControllers().size();
+    }
+    
+    public boolean isClient(int tunnelNum) {
+        TunnelController cur = getController(tunnelNum);
+        if (cur == null) return false;
+        return ( ("client".equals(cur.getType())) || ("httpclient".equals(cur.getType())) );
+    }
+    
+    public String getTunnelName(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getName();
+        else
+            return "";
+    }
+    
+    public String getClientPort(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getListenPort();
+        else
+            return "";
+    }
+    
+    public String getTunnelType(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return getTypeName(tun.getType());
+        else
+            return "";
+    }
+    
+    public String getTypeName(String internalType) {
+        if ("client".equals(internalType)) return "Client proxy";
+        else if ("httpclient".equals(internalType)) return "HTTP proxy";
+        else if ("server".equals(internalType)) return "Server";
+        else if ("httpserver".equals(internalType)) return "HTTP server";
+        else return internalType;
+    }
+    
+    public String getClientInterface(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getListenOnInterface();
+        else
+            return "";
+    }
+    
+    public int getTunnelStatus(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun == null) return NOT_RUNNING;
+        if (tun.getIsRunning()) return RUNNING;
+        else if (tun.getIsStarting()) return STARTING;
+        else return NOT_RUNNING;
+    }
+    
+    public String getTunnelDescription(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getDescription();
+        else
+            return "";
     }
+    
+    public String getClientDestination(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun == null) return "";
+        if ("client".equals(tun.getType())) return tun.getTargetDestination();
+        else return tun.getProxyList();
+    }
+    
+    public String getServerTarget(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        if (tun != null)
+            return tun.getTargetHost() + ':' + tun.getTargetPort();
+        else
+            return "";
+    }
+    
+    ///
+    /// bean props for form submission
+    ///
+    
     /**
      * What type of tunnel (httpclient, client, or server).  This is 
      * required when adding a new tunnel.
@@ -76,17 +362,7 @@ public class WebEditPageHelper {
     public void setType(String type) { 
         _type = (type != null ? type.trim() : null);   
     }
-    /**
-     * Which particular tunnel should be edited (index into the current
-     * TunnelControllerGroup's getControllers() list).  This is required
-     * when editing a tunnel, but not when adding a new one.
-     *
-     */
-    public void setNum(String id) { 
-        _id = (id != null ? id.trim() : null);   
-    }
     String getType() { return _type; }
-    String getNum() { return _id; }
     
     /** Short name of the tunnel */
     public void setName(String name) { 
@@ -158,14 +434,6 @@ public class WebEditPageHelper {
     public void setPrivKeyFile(String file) { 
         _privKeyFile = (file != null ? file.trim() : null);
     }
-    /** 
-     * If called with any value, we want to generate a new destination
-     * for this server tunnel.  This won't cause any existing private keys
-     * to be overwritten, however.
-     */
-    public void setPrivKeyGenerate(String moo) { 
-        _privKeyGenerate = true;
-    }
     /**
      * If called with any value (and the form submitted with action=Remove),
      * we really do want to stop and remove the tunnel.
@@ -186,145 +454,7 @@ public class WebEditPageHelper {
     public void setProfile(String profile) { 
         _profile = profile; 
     }
-    
-    /**
-     * Process the form and display any resulting messages
-     *
-     */
-    public String getActionResults() {
-        try {
-            return processAction();
-        } catch (Throwable t) {
-            _log.log(Log.CRIT, "Internal error processing request", t);
-            return "Internal error - " + t.getMessage();
-        }
-    }
-    
-    /**
-     * Generate an HTML form to edit / create a tunnel according to the 
-     * specified fields
-     */
-    public String getEditForm() {
-        try {
-            return WebEditPageFormGenerator.getForm(this);
-        } catch (Throwable t) {
-            _log.log(Log.CRIT, "Internal error retrieving edit form", t);
-            return "Internal error - " + t.getMessage();
-        }
-    }
 
-    /**
-     * Retrieve the tunnel pointed to by the current id
-     *
-     */
-    TunnelController getTunnelController() {
-        if (_id == null) return null;
-        int id = -1;
-        try {
-            id = Integer.parseInt(_id);
-            List controllers = TunnelControllerGroup.getInstance().getControllers();
-            if ( (id < 0) || (id >= controllers.size()) )
-                return null;
-            else
-                return (TunnelController)controllers.get(id);
-        } catch (NumberFormatException nfe) {
-            if (_log.shouldLog(Log.WARN))
-                _log.warn("Invalid tunnel id [" + _id + "]", nfe);
-            return null;
-        }
-    }
-    
-    private String processAction() {
-        if ( (_action == null) || (_action.trim().length() <= 0) )
-            return "";
-        String expected = System.getProperty(getClass().getName() + ".nonce");
-        if ( (expected == null) || (!expected.equals(Long.toString(_nonce))) )
-            return "<b>Invalid nonce, are you being spoofed?</b>";
-        if ("Save".equals(_action))
-            return save();
-        else if ("Remove".equals(_action))
-            return remove();
-        else
-            return "Action <i>" + _action + "</i> unknown";
-    }
-    
-    private String remove() {
-        if (!_removeConfirmed)
-            return "Please confirm removal";
-        
-        TunnelController cur = getTunnelController();
-        if (cur == null)
-            return "Invalid tunnel number";
-        
-        List msgs = TunnelControllerGroup.getInstance().removeController(cur);
-        msgs.addAll(doSave());
-        return getMessages(msgs);
-    }
-    
-    private String save() {
-        if (_type == null)
-            return "<b>Invalid form submission (no type?)</b>";
-        Properties config = getConfig();
-        if (config == null)
-            return "<b>Invalid params</b>";
-        
-        TunnelController cur = getTunnelController();
-        if (cur == null) {
-            // creating new
-            cur = new TunnelController(config, "", _privKeyGenerate);
-            TunnelControllerGroup.getInstance().addController(cur);
-            if (cur.getStartOnLoad())
-                cur.startTunnelBackground();
-        } else {
-            cur.setConfig(config, "");
-        }
-        
-        if ("httpclient".equals(cur.getType()) || "client".equals(cur.getType())) {
-            // all clients use the same I2CP session, and as such, use the same
-            // I2CP options
-            List controllers = TunnelControllerGroup.getInstance().getControllers();
-            for (int i = 0; i < controllers.size(); i++) {
-                TunnelController c = (TunnelController)controllers.get(i);
-                if (c == cur) continue;
-                if ("httpclient".equals(c.getType()) || "client".equals(c.getType())) {
-                    Properties cOpt = c.getConfig("");
-                    if (_tunnelCount != null) {
-                        cOpt.setProperty("option.inbound.quantity", _tunnelCount);
-                        cOpt.setProperty("option.outbound.quantity", _tunnelCount);
-                    }
-                    if (_tunnelDepth != null) {
-                        cOpt.setProperty("option.inbound.length", _tunnelDepth);
-                        cOpt.setProperty("option.outbound.length", _tunnelDepth);
-                    }
-                    // these are per-proxy settings, not per-session settings, and
-                    // as such don't need to be shared.  the values are propogated
-                    // to the current tunnel's settings via cur.setConfig above
-                    /*
-                    if (_connectDelay)
-                        cOpt.setProperty("option.i2p.streaming.connectDelay", "1000");
-                    else
-                        cOpt.setProperty("option.i2p.streaming.connectDelay", "0");
-                    if ("interactive".equals(_profile))
-                        cOpt.setProperty("option.i2p.streaming.maxWindowSize", "1");
-                    else
-                        cOpt.remove("option.i2p.streaming.maxWindowSize");
-                    */
-                    if (_name != null) {
-                        cOpt.setProperty("option.inbound.nickname", _name);
-                        cOpt.setProperty("option.outbound.nickname", _name);
-                    }
-                    c.setConfig(cOpt, "");
-                }
-            }
-        }
-        
-        return getMessages(doSave());
-    }
-    private List doSave() { 
-        TunnelControllerGroup.getInstance().saveConfig();
-        return TunnelControllerGroup.getInstance().clearAllMessages();
-    }
-    
     /**
      * Based on all provided data, create a set of configuration parameters 
      * suitable for use in a TunnelController.  This will replace (not add to)
@@ -344,6 +474,9 @@ public class WebEditPageHelper {
                 config.setProperty("interface", _reachableBy);
             if (_proxyList != null)
                 config.setProperty("proxyList", _proxyList);
+
+            config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
+            config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
         } else if ("client".equals(_type)) {
             if (_port != null)
                 config.setProperty("listenPort", _port);
@@ -353,6 +486,9 @@ public class WebEditPageHelper {
                 config.setProperty("interface", _reachableBy);
             if (_targetDestination != null)
                 config.setProperty("targetDestination", _targetDestination);
+            
+            config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
+            config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
         } else if ("server".equals(_type)) {
             if (_targetHost != null)
                 config.setProperty("targetHost", _targetHost);
@@ -422,32 +558,43 @@ public class WebEditPageHelper {
         else
             config.setProperty("option.i2p.streaming.connectDelay", "0");
         if (_name != null) {
-            config.setProperty("option.inbound.nickname", _name);
-            config.setProperty("option.outbound.nickname", _name);
-        }
+            if ( (!"client".equals(_type)) && (!"httpclient".equals(_type)) ) {
+                config.setProperty("option.inbound.nickname", _name);
+                config.setProperty("option.outbound.nickname", _name);
+            } else {
+                config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
+                config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
+            }
+        } 
         if ("interactive".equals(_profile))
             config.setProperty("option.i2p.streaming.maxWindowSize", "1");
         else
             config.remove("option.i2p.streaming.maxWindowSize");
     }
 
-    /**
-     * Pretty print the messages provided
-     *
-     */
+    ///
+    ///
+    ///
+    
+    protected TunnelController getController(int tunnel) {
+        if (tunnel < 0) return null;
+        if (_group == null) return null;
+        List controllers = _group.getControllers();
+        if (controllers.size() > tunnel)
+            return (TunnelController)controllers.get(tunnel); 
+        else
+            return null;
+    }
+    
     private String getMessages(List msgs) {
-        if (msgs == null) return "";
-        int num = msgs.size();
-        switch (num) {
-            case 0: return "";
-            case 1: return (String)msgs.get(0);
-            default:
-                StringBuffer buf = new StringBuffer(512);
-                buf.append("<ul>");
-                for (int i = 0; i < num; i++)
-                    buf.append("<li>").append((String)msgs.get(i)).append("</li>\n");
-                buf.append("</ul>\n");
-                return buf.toString();
+        StringBuffer buf = new StringBuffer(128);
+        getMessages(msgs, buf);
+        return buf.toString();
+    }
+    private void getMessages(List msgs, StringBuffer buf) {
+        if (msgs == null) return;
+        for (int i = 0; i < msgs.size(); i++) {
+            buf.append((String)msgs.get(i)).append("\n");
         }
     }
 }
diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp
index 04b3390887c3723f24a21d3a45ac91d39975163b..bbfff2fbca3b0a6a4a41748e565f116ef8525cc5 100644
--- a/apps/i2ptunnel/jsp/edit.jsp
+++ b/apps/i2ptunnel/jsp/edit.jsp
@@ -1,16 +1,26 @@
-<%@page contentType="text/html" %>
+<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean" %>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-
-<html><head>
-<title>I2PTunnel edit</title>
-</head><body>
-
-<a href="index.jsp">Back</a>
-
-<jsp:useBean class="net.i2p.i2ptunnel.WebEditPageHelper" id="helper" scope="request" />
-<jsp:setProperty name="helper" property="*" />
-<b><jsp:getProperty name="helper" property="actionResults" /></b>
-
-<jsp:getProperty name="helper" property="editForm" />
-</body>
-</html>
+<% String tun = request.getParameter("tunnel");
+   if (tun != null) {
+    try {
+      int curTunnel = Integer.parseInt(tun);
+      if (EditBean.staticIsClient(curTunnel)) {
+      %><jsp:include page="editClient.jsp" /><%
+      } else {
+      %><jsp:include page="editServer.jsp" /><%
+      }
+    } catch (NumberFormatException nfe) {
+      %>Invalid tunnel parameter<%
+    }
+  } else {
+    String type = request.getParameter("type");
+    int curTunnel = -1;
+    if ("client".equals(type) || "httpclient".equals(type)) {
+      %><jsp:include page="editClient.jsp" /><%
+    } else if ("server".equals(type) || "httpserver".equals(type)) {
+      %><jsp:include page="editServer.jsp" /><%
+    } else {
+      %>Invalid tunnel type<%
+    }
+  }
+%>
\ No newline at end of file
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..882a6941705cd5988c80c7a64bee5f0b9ce0f502
--- /dev/null
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -0,0 +1,280 @@
+<%@page contentType="text/html" %>
+<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
+<% String tun = request.getParameter("tunnel");
+   int curTunnel = -1;
+   if (tun != null) {
+     try {
+       curTunnel = Integer.parseInt(tun);
+     } catch (NumberFormatException nfe) {
+       curTunnel = -1;
+     }
+   }
+%>
+<html>
+<head>
+<title>I2PTunnel Webmanager</title>
+</head>
+<body>
+<form action="index.jsp">
+<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
+<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
+<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td colspan="2" align="center">
+<% if (curTunnel >= 0) { %>
+<b>Edit proxy settings</b>
+<% } else { %>
+<b>New proxy settings</b>
+<% } %>
+</td>
+</tr>
+<tr>
+<td><b>Name: </b>
+</td>
+<td>
+<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>Type: </b>
+<td><%
+if (curTunnel >= 0) {
+  %><%=editBean.getTunnelType(curTunnel)%>
+<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
+<%
+} else {
+  %><%=editBean.getTypeName(request.getParameter("type"))%>
+<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
+<%
+}
+%></td>
+</tr>
+<tr>
+<td><b>Description: </b>
+</td>
+<td>
+<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>Start automatically?:</b>
+</td>
+<td>
+<% if (editBean.startAutomatically(curTunnel)) { %>
+<input value="1" type="checkbox" name="startOnLoad" checked="true" />
+<% } else { %>
+<input value="1" type="checkbox" name="startOnLoad" />
+<% } %>
+<i>(Check the Box for 'YES')</i>
+</td>
+</tr>
+<tr>
+<td> <b>Listening Port:</b>
+</td>
+<td>
+<input type="text" size="6" maxlength="5" name="port" value="<%=editBean.getClientPort(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b> Accessable by:</b>
+</td>
+<td>
+<select name="reachableBy">
+<% String clientInterface = editBean.getClientInterface(curTunnel); %>
+<% if (("127.0.0.1".equals(clientInterface)) || (clientInterface == null) || (clientInterface.trim().length() <= 0)) { %>
+<option value="127.0.0.1" selected="true">Locally (127.0.0.1)</option>
+<option value="0.0.0.0">Everyone (0.0.0.0)</option>
+<option value="other">LAN Hosts (Please specify your LAN address)</option>
+
+</select>
+&nbsp;&nbsp;
+<b>others:</b>
+<input type="text" name="reachablyByOther" size="20" />
+<% } else if ("0.0.0.0".equals(clientInterface)) { %>
+<option value="127.0.0.1">Locally (127.0.0.1)</option>
+<option value="0.0.0.0" selected="true">Everyone (0.0.0.0)</option>
+<option value="other">LAN Hosts (Please specify your LAN address)</option>
+
+</select>
+&nbsp;&nbsp;
+<b>others:</b>
+<input type="text" name="reachablyByOther" size="20" />
+<% } else { %>
+<option value="127.0.0.1">Locally (127.0.0.1)</option>
+<option value="0.0.0.0">Everyone (0.0.0.0)</option>
+<option value="other" selected="true">LAN Hosts (Please specify your LAN address)</option>
+
+</select>
+&nbsp;&nbsp;
+<b>others:</b>
+<input type="text" name="reachablyByOther" size="20" value="<%=clientInterface%>" />
+<% } %>
+
+</td>
+</tr>
+<tr>
+<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
+<td><b>Outproxies:</b>
+<% } else { %>
+<td><b>Target:</b>
+<% } %>
+</td>
+<td>
+<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) { %>
+<input type="text" name="proxyList" value="<%=editBean.getClientDestination(curTunnel)%>" />
+<% } else { %>
+<input type="text" name="targetDestination" value="<%=editBean.getClientDestination(curTunnel)%>" />
+<% } %>
+<i>(name or destination)</i>
+</td>
+</tr>
+<tr>
+<td>
+<b>Delayed connect?</b>
+</td>
+<td>
+<% if (editBean.shouldDelay(curTunnel)) { %>
+<input type="checkbox" value="1000" name="connectDelay" checked="true" />
+<% } else { %>
+<input type="checkbox" value="1000" name="connectDelay" />
+<% } %>
+<i>(for request/response connections)</i>
+</td>
+</tr>
+<tr>
+<td><b>Profile:</b>
+</td>
+<td>
+<select name="profile">
+<% if (editBean.isInteractive(curTunnel)) { %>
+<option value="interactive" selected="true">interactive connection </option>
+<option value="bulk">bulk connection (downloads/websites/BT) </option>
+<% } else { %>
+<option value="interactive">interactive connection </option>
+<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
+<% } %>
+</select>
+</td>
+</tr>
+<tr>
+<td colspan="2" align="center">
+<b><hr size="1">
+Advanced networking options<br />
+<span style="color:#dd0000;">(Those are shared between ALL your Client proxies!)</span></b>
+</td>
+</tr>
+<tr>
+<td>
+<b>Tunnel depth:</b>
+</td>
+<td><select name="tunnelDepth">
+<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
+   switch (tunnelDepth) { 
+     case 0: %>
+<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     case 1: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     case 2: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     default: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
+<% } %>
+</select>
+</td>
+</tr>
+<tr>
+<td><b>Tunnel count:</b> 
+</td>
+<td>
+<select name="tunnelCount">
+<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
+   switch (tunnelCount) { 
+     case 1: %>
+<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     case 2: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2"  selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     case 3: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     default: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
+<% } %>
+</select>
+</td>
+<tr>
+<td><b>I2CP host:</b> 
+</td>
+<td>
+<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>I2CP port:</b> 
+</td>
+<td>
+<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
+</td>
+</tr>
+<tr>
+<td><b>Custom options:</b>
+</td>
+<td>
+<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td colspan="2">
+<hr size="1">
+</td>
+</tr>
+<tr>
+<td>
+<b>Save:</b>
+</td>
+<td>
+<input type="submit" name="action" value="Save changes" />
+</td>
+</tr>
+<tr>
+<td><b>Delete?</b>
+</td>
+<td>
+<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
+<b><span style="color:#dd0000;">confirm delete:</span></b>
+<input type="checkbox" value="true" name="removeConfirm" />
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+</form>
+</body>
+</html>
diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..1381a8ee6bbdc9651d636f0a6aa8d583cdac0d9c
--- /dev/null
+++ b/apps/i2ptunnel/jsp/editServer.jsp
@@ -0,0 +1,229 @@
+<%@page contentType="text/html" %>
+<jsp:useBean class="net.i2p.i2ptunnel.web.EditBean" id="editBean" scope="request" />
+<% String tun = request.getParameter("tunnel");
+   int curTunnel = -1;
+   if (tun != null) {
+     try {
+       curTunnel = Integer.parseInt(tun);
+     } catch (NumberFormatException nfe) {
+       curTunnel = -1;
+     }
+   }
+%>
+<html>
+<head>
+<title>I2PTunnel Webmanager</title>
+</head>
+<body>
+<form action="index.jsp">
+<input type="hidden" name="tunnel" value="<%=request.getParameter("tunnel")%>" />
+<input type="hidden" name="nonce" value="<%=editBean.getNextNonce()%>" />
+<table width="80%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td colspan="2" align="center">
+<% if (curTunnel >= 0) { %>
+<b>Edit server settings</b>
+<% } else { %>
+<b>New server settings</b>
+<% } %>
+</td>
+</tr>
+<tr>
+<td><b>Name: </b>
+</td>
+<td>
+<input type="text" size="30" maxlength="50" name="name" value="<%=editBean.getTunnelName(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>Type: </b>
+<td><%
+if (curTunnel >= 0) {
+  %><%=editBean.getTunnelType(curTunnel)%>
+<input type="hidden" name="type" value="<%=editBean.getInternalType(curTunnel)%>" />
+<%
+} else {
+  %><%=editBean.getTypeName(request.getParameter("type"))%>
+<input type="hidden" name="type" value="<%=request.getParameter("type")%>" />
+<%
+}
+%></td>
+</tr>
+<tr>
+<td><b>Description: </b>
+</td>
+<td>
+<input type="text" size="60" maxlength="80" name="description" value="<%=editBean.getTunnelDescription(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>Start automatically?:</b>
+</td>
+<td>
+<% if (editBean.startAutomatically(curTunnel)) { %>
+<input value="1" type="checkbox" name="startOnLoad" checked="true" />
+<% } else { %>
+<input value="1" type="checkbox" name="startOnLoad" />
+<% } %>
+<i>(Check the Box for 'YES')</i>
+</td>
+</tr>
+<tr>
+<td> <b>Target:</b>
+</td>
+<td>
+Host: <input type="text" size="20" name="targetHost" value="<%=editBean.getTargetHost(curTunnel)%>" />
+Port: <input type="text" size="4" maxlength="4" name="targetPort" value="<%=editBean.getTargetPort(curTunnel)%>" />
+</td>
+</tr>
+<% String curType = editBean.getInternalType(curTunnel);
+   if ( (curType == null) || (curType.trim().length() <= 0) )
+       curType = request.getParameter("type");
+   if ("httpserver".equals(curType)) { %>
+<tr>
+<td><b>Website name:</b></td>
+<td><input type="text" size="20" name="spoofedHost" value="<%=editBean.getSpoofedHost(curTunnel)%>" />
+</td></tr>
+<% } %>
+<tr>
+<td><b>Private key file:</b>
+</td>
+<td><input type="text" size="30" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" /></td>
+</tr>
+<tr>
+<td><b>Profile:</b>
+</td>
+<td>
+<select name="profile">
+<% if (editBean.isInteractive(curTunnel)) { %>
+<option value="interactive" selected="true">interactive connection </option>
+<option value="bulk">bulk connection (downloads/websites/BT) </option>
+<% } else { %>
+<option value="interactive">interactive connection </option>
+<option value="bulk" selected="true">bulk connection (downloads/websites/BT) </option>
+<% } %>
+</select>
+</td>
+</tr>
+<tr>
+<td colspan="2" align="center">
+<b><hr size="1">
+Advanced networking options<br />
+</b>
+</td>
+</tr>
+<tr>
+<td>
+<b>Tunnel depth:</b>
+</td>
+<td><select name="tunnelDepth">
+<% int tunnelDepth = editBean.getTunnelDepth(curTunnel, 2);
+   switch (tunnelDepth) { 
+     case 0: %>
+<option value="0" selected="true">0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     case 1: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" selected="true">1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     case 2: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" selected="true">2 hop tunnel (high anonymity, high latency)</option>
+<%     break;
+     default: %>
+<option value="0" >0 hop tunnel (low anonymity, low latency)</option>
+<option value="1" >1 hop tunnel (medium anonymity, medium latency)</option>
+<option value="2" >2 hop tunnel (high anonymity, high latency)</option>
+<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> hop tunnel</option>
+<% } %>
+</select>
+</td>
+</tr>
+<tr>
+<td><b>Tunnel count:</b> 
+</td>
+<td>
+<select name="tunnelCount">
+<% int tunnelCount = editBean.getTunnelCount(curTunnel, 2);
+   switch (tunnelCount) { 
+     case 1: %>
+<option value="1" selected="true" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     case 2: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2"  selected="true" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     case 3: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" selected="true" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<%     break;
+     default: %>
+<option value="1" >1 inbound tunnel (low bandwidth usage, less reliability)</option>
+<option value="2" >2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
+<option value="3" >3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
+<option value="<%=tunnelDepth%>" ><%=tunnelDepth%> inbound tunnels</option>
+<% } %>
+</select>
+</td>
+<tr>
+<td><b>I2CP host:</b> 
+</td>
+<td>
+<input type="text" name="clientHost" size="20" value="<%=editBean.getI2CPHost(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td><b>I2CP port:</b> 
+</td>
+<td>
+<input type="text" name="clientPort" size="20" value="<%=editBean.getI2CPPort(curTunnel)%>" /><br />
+</td>
+</tr>
+<tr>
+<td><b>Custom options:</b>
+</td>
+<td>
+<input type="text" name="customOptions" size="60" value="<%=editBean.getCustomOptions(curTunnel)%>" />
+</td>
+</tr>
+<tr>
+<td colspan="2">
+<hr size="1">
+</td>
+</tr>
+<tr>
+<td>
+<b>Save:</b>
+</td>
+<td>
+<input type="submit" name="action" value="Save changes" />
+</td>
+</tr>
+<tr>
+<td><b>Delete?</b>
+</td>
+<td>
+<input type="submit" name="action" value="Delete this proxy" /> &nbsp;&nbsp;
+<b><span style="color:#dd0000;">confirm delete:</span></b>
+<input type="checkbox" value="true" name="removeConfirm" />
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+</form>
+</body>
+</html>
diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp
index 71b4d95240f97b0a22e642f6cdaa3687cdb85180..6aadf69a86b9f95c4215c3233a6afce59f3bb7d4 100644
--- a/apps/i2ptunnel/jsp/index.jsp
+++ b/apps/i2ptunnel/jsp/index.jsp
@@ -1,26 +1,181 @@
-<%@page contentType="text/html" %>
+<%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean" %>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<jsp:useBean class="net.i2p.i2ptunnel.web.IndexBean" id="indexBean" scope="request" />
+<jsp:setProperty name="indexBean" property="*" />
 
-<html><head>
-<title>I2PTunnel status</title>
-</head><body>
+<html>
+<head>
+<title>I2PTunnel Webmanager</title>
+</head>
+<body style="font-family: Verdana, Tahoma, Helvetica, sans-serif;font-size:12px;">
+<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td nowrap="true"><b>New Messages: </b><br />
+<a href="index.jsp">refresh</a>
+</td>
+<td>
+<textarea rows="3" cols="60" readonly="true"><jsp:getProperty name="indexBean" property="messages" /></textarea>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+</table>
+<br />
+<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
 
-<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
-<jsp:setProperty name="helper" property="*" />
-<h2>Messages since last page load:</h2>
-<b><jsp:getProperty name="helper" property="actionResults" /></b>
- 
-<jsp:getProperty name="helper" property="summaryList" />
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td colspan="7" align="center" valign="middle" style="font-size:14px;">
+<b>Your Client Tunnels:</b><br />
+<hr size="1" />
+</td>
+</tr>
+<tr>
+<td width="15%"><b>Name:</b></td>
+<td><b>Port:</b></td>
+<td><b>Type:</b></td>
+<td><b>Interface:</b></td>
+<td><b>Status:</b></td>
+</tr>
+<% for (int curClient = 0; curClient < indexBean.getTunnelCount(); curClient++) {
+     if (!indexBean.isClient(curClient)) continue; %>
+<tr>
+<td valign="top" align="left">
+<b><a href="edit.jsp?tunnel=<%=curClient%>"><%=indexBean.getTunnelName(curClient) %></a></b></td>
+<td valign="top" align="left" nowrap="true"><%=indexBean.getClientPort(curClient) %></td>
+<td valign="top" align="left" nowrap="true"><%=indexBean.getTunnelType(curClient) %></td>
+<td valign="top" align="left" nowrap="true"><%=indexBean.getClientInterface(curClient) %></td>
+<td valign="top" align="left" nowrap="true"><%
+ switch (indexBean.getTunnelStatus(curClient)) {
+     case IndexBean.STARTING:
+%><b><span style="color:#339933">Starting...</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
+         break;
+     case IndexBean.RUNNING:
+%><b><span style="color:#00dd00">Running</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curClient%>">[STOP]</a><%
+         break;
+     case IndexBean.NOT_RUNNING:
+%><b><span style="color:#dd0000">Not Running</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curClient%>">[START]</a><%
+         break;
+    }
+%></td>
+</tr>
+<tr><td align="right" valign="top">Destination:</td>
+    <td colspan="4"><input align="left" size="40" valign="top" style="overflow: hidden" readonly="true" value="<%=indexBean.getClientDestination(curClient) %>" /></td></tr>
+<tr>
+  <td valign="top" align="right">Description:</td>
+  <td valign="top" align="left" colspan="4"><%=indexBean.getTunnelDescription(curClient) %></td>
+</tr>
+<% } %>
+</table>
+</td>
+</tr>
+</table>
+<br />
 
+<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td colspan="5" align="center" valign="middle" style="font-size:14px;">
+<b>Your Server Tunnels:</b><br />
+<hr size="1" />
+</td>
+</tr>
+<tr>
+<td width="15%"><b>Name: </b>
+</td>
+<td>
+<b>Points at:</b>
+</td>
+<td>
+<b>Status:</b>
+</td>
+</tr>
+
+<% for (int curServer = 0; curServer < indexBean.getTunnelCount(); curServer++) {
+     if (indexBean.isClient(curServer)) continue; %>
+
+<tr>
+<td valign="top">
+<b><a href="edit.jsp?tunnel=<%=curServer%>"><%=indexBean.getTunnelName(curServer)%></a></b>
+</td>
+<td valign="top"><%=indexBean.getServerTarget(curServer)%></td>
+<td valign="top" nowrap="true"><%
+ switch (indexBean.getTunnelStatus(curServer)) {
+     case IndexBean.RUNNING:
+%><b><span style="color:#00dd00">Running</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
+         break;
+     case IndexBean.NOT_RUNNING:
+%><b><span style="color:#dd0000">Not Running</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=start&tunnel=<%=curServer%>">[START]</a><%
+         break;
+     case IndexBean.STARTING:
+%>
+<b><span style="color:#339933">Starting...</span></b>
+<a href="index.jsp?nonce=<%=indexBean.getNextNonce()%>&action=stop&tunnel=<%=curServer%>">[STOP]</a><%
+        break;
+    }
+%>
+</td>
+</tr>
+<tr><td valign="top" align="right">Description:</td>
+    <td valign="top" align="left" colspan="2"><%=indexBean.getTunnelDescription(curServer)%></td></tr>
+<% } %>
+
+</table>
+</td>
+</tr>
+</table>
+<br />
+<table width="90%" align="center" border="0" cellpadding="1" cellspacing="1">
+<tr>
+<td style="background-color:#000">
+<div style="background-color:#ffffed">
+<table width="100%" align="center" border="0" cellpadding="4" cellspacing="1">
+<tr>
+<td colspan="2" align="center" valign="middle">
+<b>Operations Menu - Please chose from below!</b><br /><br />
+</td>
+</tr>
+<tr>
+<form action="index.jsp" method="GET">
+<td >
+<input type="hidden" name="nonce" value="<%=indexBean.getNextNonce()%>" />
+<input type="submit" name="action" value="Stop all tunnels" />
+<input type="submit" name="action" value="Start all tunnels" />
+<input type="submit" name="action" value="Restart all" />
+<input type="submit" name="action" value="Reload config" />
+</td>
+</form>
 <form action="edit.jsp">
+<td >
 <b>Add new:</b> 
- <select name="type">
+<select name="type">
   <option value="httpclient">HTTP proxy</option>
   <option value="client">Client tunnel</option>
   <option value="server">Server tunnel</option>
-  <option value="httpserver">HTTP server tunnel</option>
- </select> <input type="submit" value="GO" />
+   <option value="httpserver">HTTP server tunnel</option>
+</select> <input type="submit" value="Create" />
+</td>
 </form>
-
+</tr>
+</table>
+</td>
+</tr>
+</table>
 </body>
 </html>
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index 66841ada901809ba6509bcbf92c0b14af067f2be..4bd776e45f1e80f9d267b38f611f55a0c7430455 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -11,6 +11,7 @@ import net.i2p.data.DataHelper;
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
 import net.i2p.util.EepGet;
+import net.i2p.util.FileUtil;
 import net.i2p.util.Log;
 
 /**
@@ -26,6 +27,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     public static final NewsFetcher getInstance() { return _instance; }
     
     private static final String NEWS_FILE = "docs/news.xml";
+    private static final String TEMP_NEWS_FILE = "docs/news.xml.temp";
     
     public NewsFetcher(I2PAppContext ctx) {
         _context = ctx;
@@ -82,14 +84,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
         boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
         String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
         String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
+        File tempFile = new File(TEMP_NEWS_FILE);
+        if (tempFile.exists())
+            tempFile.delete();
+        
         int proxyPort = -1;
         try {
             proxyPort = Integer.parseInt(port);
             EepGet get = null;
             if (shouldProxy)
-                get = new EepGet(_context, proxyHost, proxyPort, 10, NEWS_FILE, newsURL);
+                get = new EepGet(_context, proxyHost, proxyPort, 10, TEMP_NEWS_FILE, newsURL);
             else
-                get = new EepGet(_context, 10, NEWS_FILE, newsURL);
+                get = new EepGet(_context, 10, TEMP_NEWS_FILE, newsURL);
             get.addStatusListener(this);
             get.fetch();
         } catch (Throwable t) {
@@ -230,11 +236,20 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
     public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) {
         if (_log.shouldLog(Log.INFO))
             _log.info("News fetched from " + url);
+        
+        File temp = new File(TEMP_NEWS_FILE);
+        if (temp.exists()) {
+            boolean copied = FileUtil.copy(TEMP_NEWS_FILE, NEWS_FILE, true);
+            if (copied)
+                temp.delete();
+        }
         checkForUpdates();
     }
     
     public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
         if (_log.shouldLog(Log.ERROR))
             _log.error("Failed to fetch the news from " + url);
+        File temp = new File(TEMP_NEWS_FILE);
+        temp.delete();
     }
 }
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
index b904c3742af20239355714fc797c3d6c1c022798..cbf9fd0bb1f465a37503e3882b50bf4b083064e5 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
@@ -71,7 +71,7 @@ public class Connection {
     private long _lifetimeDupMessageSent;
     private long _lifetimeDupMessageReceived;
     
-    public static final long MAX_RESEND_DELAY = 30*1000;
+    public static final long MAX_RESEND_DELAY = 20*1000;
     public static final long MIN_RESEND_DELAY = 10*1000;
 
     /** wait up to 5 minutes after disconnection so we can ack/close packets */
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
index 1adf4a3ceef9919d9a0fe0260706b833749a9fed..988723730a3fcf4114e391ef1e8592eb3ce9a2cc 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
@@ -82,7 +82,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
         setConnectDelay(getInt(opts, PROP_CONNECT_DELAY, -1));
         setProfile(getInt(opts, PROP_PROFILE, PROFILE_BULK));
         setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, 4*1024));
-        setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
+        setRTT(getInt(opts, PROP_INITIAL_RTT, 10*1000));
         setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
         setResendDelay(getInt(opts, PROP_INITIAL_RESEND_DELAY, 1000));
         setSendAckDelay(getInt(opts, PROP_INITIAL_ACK_DELAY, 1000));
@@ -107,7 +107,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
         if (opts.containsKey(PROP_MAX_MESSAGE_SIZE))
             setMaxMessageSize(getInt(opts, PROP_MAX_MESSAGE_SIZE, Packet.MAX_PAYLOAD_SIZE));
         if (opts.containsKey(PROP_INITIAL_RTT))
-            setRTT(getInt(opts, PROP_INITIAL_RTT, 30*1000));
+            setRTT(getInt(opts, PROP_INITIAL_RTT, 10*1000));
         if (opts.containsKey(PROP_INITIAL_RECEIVE_WINDOW))
             setReceiveWindow(getInt(opts, PROP_INITIAL_RECEIVE_WINDOW, 1));
         if (opts.containsKey(PROP_INITIAL_RESEND_DELAY))
diff --git a/core/java/src/net/i2p/CoreVersion.java b/core/java/src/net/i2p/CoreVersion.java
index 96ffdd9df11034c22ca886c71c0067628b54d86b..6d1f72c7d6abe8e406ef408085d692df3dc8863d 100644
--- a/core/java/src/net/i2p/CoreVersion.java
+++ b/core/java/src/net/i2p/CoreVersion.java
@@ -14,8 +14,8 @@ package net.i2p;
  *
  */
 public class CoreVersion {
-    public final static String ID = "$Revision: 1.31 $ $Date: 2005/03/18 17:34:53 $";
-    public final static String VERSION = "0.5.0.4";
+    public final static String ID = "$Revision: 1.32 $ $Date: 2005/03/24 02:29:28 $";
+    public final static String VERSION = "0.5.0.5";
 
     public static void main(String args[]) {
         System.out.println("I2P Core version: " + VERSION);
diff --git a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java
index 920cce5dd39f3a5c1b84c7550d1fad6379f1ca38..be28c23556778137a28ed4a5bce65044682d5711 100644
--- a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java
+++ b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java
@@ -1,33 +1,101 @@
 package net.i2p.crypto;
 
+import java.util.Arrays;
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
 import net.i2p.data.SessionKey;
 
 /**
- * Calculate the HMAC-SHA256 of a key+message.  Currently FAKE - returns a stupid
- * kludgy hash: H(H(key) XOR H(data)).  Fix me!
+ * Calculate the HMAC-SHA256 of a key+message.
  *
  */
 public class HMACSHA256Generator {
-    public HMACSHA256Generator(I2PAppContext context) { // nop
+    private I2PAppContext _context;
+    public HMACSHA256Generator(I2PAppContext context) {
+        _context = context;
     }
     
     public static HMACSHA256Generator getInstance() {
         return I2PAppContext.getGlobalContext().hmac();
     }
     
+    private static final int PAD_LENGTH = 64;
+    
+    private static final byte[] _IPAD = new byte[PAD_LENGTH];
+    private static final byte[] _OPAD = new byte[PAD_LENGTH];
+    static {
+        for (int i = 0; i < _IPAD.length; i++) {
+            _IPAD[i] = 0x36;
+            _OPAD[i] = 0x5C;
+        }
+    }
+    
+
+    public Buffer createBuffer(int dataLen) { return new Buffer(dataLen); }
+    
+    public class Buffer {
+        private byte padded[];
+        private byte innerBuf[];
+        private SHA256EntryCache.CacheEntry innerEntry;
+        private byte rv[];
+        private byte outerBuf[];
+        private SHA256EntryCache.CacheEntry outerEntry;
+
+        public Buffer(int dataLength) {
+            padded = new byte[PAD_LENGTH];
+            innerBuf = new byte[dataLength + PAD_LENGTH];
+            innerEntry = _context.sha().cache().acquire(innerBuf.length);
+            rv = new byte[Hash.HASH_LENGTH];
+            outerBuf = new byte[Hash.HASH_LENGTH + PAD_LENGTH];
+            outerEntry = _context.sha().cache().acquire(outerBuf.length);
+        }
+        
+        public void releaseCached() {
+            _context.sha().cache().release(innerEntry);
+            _context.sha().cache().release(outerEntry);
+        }
+        
+        public byte[] getHash() { return rv; }
+    }
+    
     /**
-     * This should calculate the HMAC/SHA256, but it DOESNT.  Its just a kludge.
-     * Fix me.
+     * Calculate the HMAC of the data with the given key
      */
     public Hash calculate(SessionKey key, byte data[]) {
         if ((key == null) || (key.getData() == null) || (data == null))
             throw new NullPointerException("Null arguments for HMAC");
-
-        Hash hkey = SHA256Generator.getInstance().calculateHash(key.getData());
-        Hash hdata = SHA256Generator.getInstance().calculateHash(data);
-        return SHA256Generator.getInstance().calculateHash(DataHelper.xor(hkey.getData(), hdata.getData()));
+        
+        Buffer buf = new Buffer(data.length);
+        calculate(key, data, buf);
+        Hash rv = new Hash(buf.rv);
+        buf.releaseCached();
+        return rv;
+    }
+    
+    /**
+     * Calculate the HMAC of the data with the given key
+     */
+    public void calculate(SessionKey key, byte data[], Buffer buf) {
+        // inner hash
+        padKey(key.getData(), _IPAD, buf.padded);
+        System.arraycopy(buf.padded, 0, buf.innerBuf, 0, PAD_LENGTH);
+        System.arraycopy(data, 0, buf.innerBuf, PAD_LENGTH, data.length);
+        
+        Hash h = _context.sha().calculateHash(buf.innerBuf, buf.innerEntry);
+        
+        // outer hash
+        padKey(key.getData(), _OPAD, buf.padded);
+        System.arraycopy(buf.padded, 0, buf.outerBuf, 0, PAD_LENGTH);
+        System.arraycopy(h.getData(), 0, buf.outerBuf, PAD_LENGTH, Hash.HASH_LENGTH);
+        
+        h = _context.sha().calculateHash(buf.outerBuf, buf.outerEntry);
+        System.arraycopy(h.getData(), 0, buf.rv, 0, Hash.HASH_LENGTH);
+    }
+    
+    private static final void padKey(byte key[], byte pad[], byte out[]) {
+        for (int i = 0; i < SessionKey.KEYSIZE_BYTES; i++)
+            out[i] = (byte) (key[i] ^ pad[i]);
+        Arrays.fill(out, SessionKey.KEYSIZE_BYTES, PAD_LENGTH, pad[0]);
     }
 }
\ No newline at end of file
diff --git a/core/java/src/net/i2p/data/Base64.java b/core/java/src/net/i2p/data/Base64.java
index 85bbd2f282988cf915d61b14397f60ed58baa9bd..554d5ab926fd4e0c5edcef8252186bdab7c4f80c 100644
--- a/core/java/src/net/i2p/data/Base64.java
+++ b/core/java/src/net/i2p/data/Base64.java
@@ -329,6 +329,8 @@ public class Base64 {
      * replacing / with ~, and + with -
      */
     private static String safeEncode(byte[] source, int off, int len, boolean useStandardAlphabet) {
+        if (len + off > source.length)
+            throw new ArrayIndexOutOfBoundsException("Trying to encode too much!  source.len=" + source.length + " off=" + off + " len=" + len);
         String encoded = encodeBytes(source, off, len, false);
         if (useStandardAlphabet) {
             // noop
diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java
index 214b7df7ed45b285f6fc05feaff5d3d28e6c4ee9..f2f54aab8511d3ae4ca5f75d0843e0a2d539b185 100644
--- a/core/java/src/net/i2p/data/LeaseSet.java
+++ b/core/java/src/net/i2p/data/LeaseSet.java
@@ -35,6 +35,7 @@ public class LeaseSet extends DataStructureImpl {
     private Signature _signature;
     private volatile Hash _currentRoutingKey;
     private volatile byte[] _routingKeyGenMod;
+    private boolean _receivedAsPublished;
 
     /** um, no lease can last more than a year.  */
     private final static long MAX_FUTURE_EXPIRATION = 365 * 24 * 60 * 60 * 1000L;
@@ -47,6 +48,7 @@ public class LeaseSet extends DataStructureImpl {
         setRoutingKey(null);
         _leases = new ArrayList();
         _routingKeyGenMod = null;
+        _receivedAsPublished = false;
     }
 
     public Destination getDestination() {
@@ -72,6 +74,14 @@ public class LeaseSet extends DataStructureImpl {
     public void setSigningKey(SigningPublicKey key) {
         _signingKey = key;
     }
+    
+    /**
+     * If true, we received this LeaseSet by a remote peer publishing it to
+     * us, rather than by searching for it ourselves or locally creating it.
+     *
+     */
+    public boolean getReceivedAsPublished() { return _receivedAsPublished; }
+    public void setReceivedAsPublished(boolean received) { _receivedAsPublished = received; }
 
     public void addLease(Lease lease) {
         if (lease == null) throw new IllegalArgumentException("erm, null lease");
diff --git a/core/java/test/net/i2p/crypto/HMACSHA256Bench.java b/core/java/test/net/i2p/crypto/HMACSHA256Bench.java
new file mode 100644
index 0000000000000000000000000000000000000000..715c45ec0bd772c73ba1de48369886db469a3092
--- /dev/null
+++ b/core/java/test/net/i2p/crypto/HMACSHA256Bench.java
@@ -0,0 +1,112 @@
+package net.i2p.crypto;
+
+/* 
+ * Copyright (c) 2003, TheCrypto
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright notice, this 
+ *   list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice, 
+ *   this list of conditions and the following disclaimer in the documentation 
+ *   and/or other materials provided with the distribution.
+ * -  Neither the name of the TheCrypto may be used to endorse or promote 
+ *    products derived from this software without specific prior written 
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Hash;
+import net.i2p.data.SessionKey;
+
+public class HMACSHA256Bench {
+	public static void main(String args[]) {
+        I2PAppContext ctx = I2PAppContext.getGlobalContext();
+        SessionKey key = ctx.keyGenerator().generateSessionKey();
+		Hash asdfs = HMACSHA256Generator.getInstance().calculate(key, "qwerty".getBytes());
+			
+		int times = 100000;
+		long shorttime = 0;
+		long medtime = 0;
+		long longtime = 0;
+		long minShort = 0;
+		long maxShort = 0;
+		long minMed = 0;
+		long maxMed = 0;
+		long minLong = 0;
+		long maxLong = 0;
+        
+		long shorttime1 = 0;
+		long medtime1 = 0;
+		long longtime1 = 0;
+		long minShort1 = 0;
+		long maxShort1 = 0;
+		long minMed1 = 0;
+		long maxMed1 = 0;
+		long minLong1 = 0;
+		long maxLong1 = 0;
+        
+		byte[] smess = new String("abc").getBytes();
+		StringBuffer buf = new StringBuffer();
+		for (int x = 0; x < 2*1024; x++) {
+			buf.append("a");
+		}
+		byte[] mmess = buf.toString().getBytes(); // new String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq").getBytes();
+		buf = new StringBuffer();
+		for (int x = 0; x < 10000; x++) {
+			buf.append("a");
+		}
+		byte[] lmess = buf.toString().getBytes();
+        
+        HMACSHA256Generator.Buffer sbuf = ctx.hmac().createBuffer(smess.length);
+        HMACSHA256Generator.Buffer mbuf = ctx.hmac().createBuffer(mmess.length);
+        HMACSHA256Generator.Buffer lbuf = ctx.hmac().createBuffer(lmess.length);
+        
+		// warm up the engines
+        ctx.hmac().calculate(key, smess, sbuf);
+        ctx.hmac().calculate(key, mmess, mbuf);
+        ctx.hmac().calculate(key, lmess, lbuf);
+        
+        long before = System.currentTimeMillis();
+        for (int x = 0; x < times; x++)
+            ctx.hmac().calculate(key, smess, sbuf);
+        long after = System.currentTimeMillis();
+        
+        display(times, before, after, smess.length, "3 byte");
+        
+        before = System.currentTimeMillis();
+        for (int x = 0; x < times; x++)
+            ctx.hmac().calculate(key, mmess, mbuf);
+        after = System.currentTimeMillis();
+
+        display(times, before, after, mmess.length, "2KB");
+        
+        before = System.currentTimeMillis();
+        for (int x = 0; x < times; x++)
+            ctx.hmac().calculate(key, lmess, lbuf);
+        after = System.currentTimeMillis();
+
+        display(times, before, after, lmess.length, "10KB");
+	}
+    
+    private static void display(int times, long before, long after, int len, String name) {
+        double rate = ((double)times)/(((double)after-(double)before)/1000.0d);
+        double kbps = ((double)len/1024.0d)*((double)times)/(((double)after-(double)before)/1000.0d);
+        System.out.println(name + " HMAC-SHA256 pulled " + kbps + "KBps or " + rate + " calcs per second");
+    }
+}
+	
diff --git a/history.txt b/history.txt
index 5af5f9ff1cba572f2d11a3c80bb8d092cf7cfaff..b7f507747b0e312adad7346a3286af39cbcf6bc5 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,25 @@
-$Id: history.txt,v 1.181 2005/03/24 23:07:06 jrandom Exp $
+$Id: history.txt,v 1.182 2005/03/26 02:13:38 jrandom Exp $
+
+* 2005-03-29  0.5.0.5 released
+
+2005-03-29  jrandom
+    * Decreased the initial RTT estimate to 10s to allow more retries.
+    * Increased the default netDb store replication factor from 2 to 6 to take
+      into consideration tunnel failures.
+    * Address some statistical anonymity attacks against the netDb that could 
+      be mounted by an active internal adversary by only answering lookups for 
+      leaseSets we received through an unsolicited store.
+    * Don't throttle lookup responses (we throttle enough elsewhere)
+    * Fix the NewsFetcher so that it doesn't incorrectly resume midway through
+      the file (thanks nickster!)
+    * Updated the I2PTunnel HTML (thanks postman!)
+    * Added support to the I2PTunnel pages for the URL parameter "passphrase",
+      which, if matched against the router.config "i2ptunnel.passphrase" value,
+      skips the nonce check.  If the config prop doesn't exist or is blank, no
+      passphrase is accepted.
+    * Implemented HMAC-SHA256.
+    * Enable the tunnel batching with a 500ms delay by default
+    * Dropped compatability with 0.5.0.3 and earlier releases
 
 2005-03-26  jrandom
     * Added some error handling and fairly safe to cache data to the streaming
diff --git a/installer/install.xml b/installer/install.xml
index b515ebcec0b03a21a92e61af49f3bb9563247140..d8306c253d58d20308c5d44858120df8c52ee178 100644
--- a/installer/install.xml
+++ b/installer/install.xml
@@ -4,7 +4,7 @@
 
     <info>
         <appname>i2p</appname>
-        <appversion>0.5.0.4</appversion>
+        <appversion>0.5.0.5</appversion>
         <authors>
             <author name="I2P" email="support@i2p.net"/>
         </authors>
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 2a93e5091cb5b6fec3bf5ac2be4e42118c98f84c..d8a40488c0bf84644d3b24808d67194630c698c4 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.174 $ $Date: 2005/03/24 23:07:06 $";
-    public final static String VERSION = "0.5.0.4";
-    public final static long BUILD = 2;
+    public final static String ID = "$Revision: 1.175 $ $Date: 2005/03/26 02:13:38 $";
+    public final static String VERSION = "0.5.0.5";
+    public final static long BUILD = 0;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION);
         System.out.println("Router ID: " + RouterVersion.ID);
diff --git a/router/java/src/net/i2p/router/StatisticsManager.java b/router/java/src/net/i2p/router/StatisticsManager.java
index 10152286c9ba68faf5bb8f08b6fb7e39b5d5f2f8..e3983f655f494de880f6039e0fdfb09945d40ece 100644
--- a/router/java/src/net/i2p/router/StatisticsManager.java
+++ b/router/java/src/net/i2p/router/StatisticsManager.java
@@ -113,8 +113,9 @@ public class StatisticsManager implements Service {
             includeRate("tunnel.buildFailure", stats, new long[] { 60*60*1000 });
             includeRate("tunnel.buildSuccess", stats, new long[] { 60*60*1000 });
 
-            includeRate("tunnel.batchDelaySent", stats, new long[] { 10*60*1000, 60*60*1000 });
+            //includeRate("tunnel.batchDelaySent", stats, new long[] { 10*60*1000, 60*60*1000 });
             includeRate("tunnel.batchMultipleCount", stats, new long[] { 10*60*1000, 60*60*1000 });
+            includeRate("tunnel.corruptMessage", stats, new long[] { 60*60*1000l, 3*60*60*1000l });
             
             includeRate("router.throttleTunnelProbTestSlow", stats, new long[] { 60*60*1000 });
             includeRate("router.throttleTunnelProbTooFast", stats, new long[] { 60*60*1000 });
diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
index b10f06cf8935d00cc2ef6c075f521107733876b7..73af5c9e361ca9d2d93e7e2475f260f367d1e1de 100644
--- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
+++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
@@ -418,7 +418,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
         long sendTime = getContext().clock().now() - _start;
         if (_log.shouldLog(Log.WARN))
             _log.warn(getJobId() + ": Failed to send the message " + _clientMessageId + " after " 
-                       + sendTime + "ms", new Exception("Message send failure"));
+                       + sendTime + "ms");
         
         long messageDelay = getContext().throttle().getMessageDelay();
         long tunnelLag = getContext().throttle().getTunnelLag();
diff --git a/router/java/src/net/i2p/router/networkdb/DatabaseLookupMessageHandler.java b/router/java/src/net/i2p/router/networkdb/DatabaseLookupMessageHandler.java
index 29ee4f3fbd1c2da366263cede98d6e0902495b02..db696461a8f7669e0b5b3329b915592c4887a6e4 100644
--- a/router/java/src/net/i2p/router/networkdb/DatabaseLookupMessageHandler.java
+++ b/router/java/src/net/i2p/router/networkdb/DatabaseLookupMessageHandler.java
@@ -34,7 +34,7 @@ public class DatabaseLookupMessageHandler implements HandlerJobBuilder {
     public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
         _context.statManager().addRateData("netDb.lookupsReceived", 1, 0);
 
-        if (_context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
+        if (true || _context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
             return new HandleDatabaseLookupMessageJob(_context, (DatabaseLookupMessage)receivedMessage, from, fromHash);
         } else {
             if (_log.shouldLog(Log.INFO)) 
diff --git a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java
index 9e69e7254e52cadcca019fc6f73adf1382849e83..7de9ec65966e51f5ce5b56b23c5420de62ef7741 100644
--- a/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java
+++ b/router/java/src/net/i2p/router/networkdb/HandleDatabaseLookupMessageJob.java
@@ -40,6 +40,7 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
     private RouterIdentity _from;
     private Hash _fromHash;
     private final static int MAX_ROUTERS_RETURNED = 3;
+    private final static int CLOSENESS_THRESHOLD = 10; // StoreJob.REDUNDANCY * 2
     private final static int REPLY_TIMEOUT = 60*1000;
     private final static int MESSAGE_PRIORITY = 300;
     
@@ -48,6 +49,9 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
         _log = getContext().logManager().getLog(HandleDatabaseLookupMessageJob.class);
         getContext().statManager().createRateStat("netDb.lookupsHandled", "How many netDb lookups have we handled?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
         getContext().statManager().createRateStat("netDb.lookupsMatched", "How many netDb lookups did we have the data for?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        getContext().statManager().createRateStat("netDb.lookupsMatchedReceivedPublished", "How many netDb lookups did we have the data for that were published to us?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        getContext().statManager().createRateStat("netDb.lookupsMatchedLocalClosest", "How many netDb lookups for local data were received where we are the closest peers?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        getContext().statManager().createRateStat("netDb.lookupsMatchedLocalNotClosest", "How many netDb lookups for local data were received where we are NOT the closest peers?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
         _message = receivedMessage;
         _from = from;
         _fromHash = fromHash;
@@ -65,26 +69,26 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
                           + " (tunnel " + _message.getReplyTunnel() + ")");
         }
 
-        if (getContext().netDb().lookupRouterInfoLocally(_message.getFrom()) == null) {
-            // hmm, perhaps don't always send a lookup for this...
-            // but for now, wtf, why not.  we may even want to adjust it so that 
-            // we penalize or benefit peers who send us that which we can or
-            // cannot lookup
-            getContext().netDb().lookupRouterInfo(_message.getFrom(), null, null, REPLY_TIMEOUT);
-        }
-
-        // whatdotheywant?
-        handleRequest(fromKey);
-    }
-    
-    private void handleRequest(Hash fromKey) {
         LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_message.getSearchKey());
         if (ls != null) {
-            // send that lease set to the _message.getFromHash peer
-            if (_log.shouldLog(Log.DEBUG))
-                _log.debug("We do have key " + _message.getSearchKey().toBase64() 
-                           + " locally as a lease set.  sending to " + fromKey.toBase64());
-            sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
+            // only answer a request for a LeaseSet if it has been published
+            // to us, or, if its local, if we would have published to ourselves
+            if (ls.getReceivedAsPublished()) {
+                getContext().statManager().addRateData("netDb.lookupsMatchedReceivedPublished", 1, 0);
+                sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
+            } else {
+                Set routerInfoSet = getContext().netDb().findNearestRouters(_message.getSearchKey(), 
+                                                                            CLOSENESS_THRESHOLD,
+                                                                            _message.getDontIncludePeers());
+                if (getContext().clientManager().isLocal(ls.getDestination()) && 
+                    weAreClosest(routerInfoSet)) {
+                    getContext().statManager().addRateData("netDb.lookupsMatchedLocalClosest", 1, 0);
+                    sendData(_message.getSearchKey(), ls, fromKey, _message.getReplyTunnel());
+                } else {
+                    getContext().statManager().addRateData("netDb.lookupsMatchedLocalNotClosest", 1, 0);
+                    sendClosest(_message.getSearchKey(), routerInfoSet, fromKey, _message.getReplyTunnel());
+                }
+            }
         } else {
             RouterInfo info = getContext().netDb().lookupRouterInfoLocally(_message.getSearchKey());
             if (info != null) {
@@ -106,6 +110,17 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
         }
     }
     
+    private boolean weAreClosest(Set routerInfoSet) {
+        boolean weAreClosest = false;
+        for (Iterator iter = routerInfoSet.iterator(); iter.hasNext(); ) {
+            RouterInfo cur = (RouterInfo)iter.next();
+            if (cur.getIdentity().calculateHash().equals(getContext().routerHash())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
     private void sendData(Hash key, DataStructure data, Hash toPeer, TunnelId replyTunnel) {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sending data matching key key " + key.toBase64() + " to peer " + toPeer.toBase64() 
@@ -129,27 +144,27 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
             _log.debug("Sending closest routers to key " + key.toBase64() + ": # peers = " 
                        + routerInfoSet.size() + " tunnel " + replyTunnel);
         DatabaseSearchReplyMessage msg = new DatabaseSearchReplyMessage(getContext());
-        msg.setFromHash(getContext().router().getRouterInfo().getIdentity().getHash());
+        msg.setFromHash(getContext().routerHash());
         msg.setSearchKey(key);
         for (Iterator iter = routerInfoSet.iterator(); iter.hasNext(); ) {
             RouterInfo peer = (RouterInfo)iter.next();
             msg.addReply(peer.getIdentity().getHash());
+            if (msg.getNumReplies() >= MAX_ROUTERS_RETURNED)
+                break;
         }
         getContext().statManager().addRateData("netDb.lookupsHandled", 1, 0);
         sendMessage(msg, toPeer, replyTunnel); // should this go via garlic messages instead?
     }
     
     private void sendMessage(I2NPMessage message, Hash toPeer, TunnelId replyTunnel) {
-        Job send = null;
         if (replyTunnel != null) {
             sendThroughTunnel(message, toPeer, replyTunnel);
         } else {
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Sending reply directly to " + toPeer);
-            send = new SendMessageDirectJob(getContext(), message, toPeer, REPLY_TIMEOUT, MESSAGE_PRIORITY);
+            Job send = new SendMessageDirectJob(getContext(), message, toPeer, REPLY_TIMEOUT, MESSAGE_PRIORITY);
+            getContext().netDb().lookupRouterInfo(toPeer, send, null, REPLY_TIMEOUT);
         }
-
-        getContext().netDb().lookupRouterInfo(toPeer, send, null, REPLY_TIMEOUT);
     }
     
     private void sendThroughTunnel(I2NPMessage message, Hash toPeer, TunnelId replyTunnel) {
@@ -171,28 +186,6 @@ public class HandleDatabaseLookupMessageJob extends JobImpl {
         }
     }
     
-    private void sendToGateway(I2NPMessage message, Hash toPeer, TunnelId replyTunnel, TunnelInfo info) {
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Want to reply to a db request via a tunnel, but we're a participant in the reply!  so send it to the gateway");
-
-        if ( (toPeer == null) || (replyTunnel == null) ) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("Someone br0ke us.  where is this message supposed to go again?", getAddedBy());
-            return;
-        }
-
-        long expiration = REPLY_TIMEOUT + getContext().clock().now();
-
-        TunnelGatewayMessage msg = new TunnelGatewayMessage(getContext());
-        msg.setMessage(message);
-        msg.setTunnelId(replyTunnel);
-        msg.setMessageExpiration(expiration);
-        getContext().jobQueue().addJob(new SendMessageDirectJob(getContext(), msg, toPeer, null, null, null, null, REPLY_TIMEOUT, MESSAGE_PRIORITY));
-
-        String bodyType = message.getClass().getName();
-        getContext().messageHistory().wrap(bodyType, message.getUniqueId(), TunnelGatewayMessage.class.getName(), msg.getUniqueId());
-    }
-    
     public String getName() { return "Handle Database Lookup Message"; }
 
     public void dropped() {
diff --git a/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java b/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java
index 080df02424f9a72995c61e31ad8037a5565d48dc..2e4073fc4e668a31dda8032c79425a7608ae8273 100644
--- a/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java
+++ b/router/java/src/net/i2p/router/networkdb/HandleDatabaseStoreMessageJob.java
@@ -10,6 +10,7 @@ package net.i2p.router.networkdb;
 
 import java.util.Date;
 import net.i2p.data.Hash;
+import net.i2p.data.LeaseSet;
 import net.i2p.data.RouterIdentity;
 import net.i2p.data.i2np.DatabaseStoreMessage;
 import net.i2p.data.i2np.DeliveryStatusMessage;
@@ -48,8 +49,18 @@ public class HandleDatabaseStoreMessageJob extends JobImpl {
         boolean wasNew = false;
         if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) {
             try {
-                Object match = getContext().netDb().store(_message.getKey(), _message.getLeaseSet());
-                wasNew = (null == match);
+                LeaseSet ls = _message.getLeaseSet();
+                // mark it as something we received, so we'll answer queries 
+                // for it.  this flag does NOT get set on entries that we 
+                // receive in response to our own lookups.
+                ls.setReceivedAsPublished(true);
+                LeaseSet match = getContext().netDb().store(_message.getKey(), _message.getLeaseSet());
+                if (match == null) {
+                    wasNew = true;
+                } else {
+                    wasNew = false;
+                    match.setReceivedAsPublished(true);
+                }
             } catch (IllegalArgumentException iae) {
                 invalidMessage = iae.getMessage();
             }
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java
index 0f71b172aef46c21ddf64d593585b9d993c92b1f..a8359e09faf3a53fb5fa9c7319f2d46ee58b1568 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/StoreJob.java
@@ -36,8 +36,8 @@ class StoreJob extends JobImpl {
     private long _expiration;
     private PeerSelector _peerSelector;
 
-    private final static int PARALLELIZATION = 1; // how many sent at a time
-    private final static int REDUNDANCY = 2; // we want the data sent to 2 peers
+    private final static int PARALLELIZATION = 2; // how many sent at a time
+    private final static int REDUNDANCY = 6; // we want the data sent to 6 peers
     /**
      * additionally send to 1 outlier(s), in case all of the routers chosen in our
      * REDUNDANCY set are attacking us by accepting DbStore messages but dropping
diff --git a/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java b/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
index f324919602f3b6074a49c3ebfc45a2771c46d067..70130f32c904faebe4aae7cd413e63dc487b3448 100644
--- a/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/tcp/TCPTransport.java
@@ -87,8 +87,7 @@ public class TCPTransport extends TransportImpl {
     public static final int DEFAULT_ESTABLISHERS = 3;
     
     /** Ordered list of supported I2NP protocols */
-    public static final int[] SUPPORTED_PROTOCOLS = new int[] { 3
-                                                              , 4}; // forward compat, to drop <= 0.5.0.3
+    public static final int[] SUPPORTED_PROTOCOLS = new int[] { 4 }; // drop <= 0.5.0.3
     /** blah, people shouldnt use defaults... */
     public static final int DEFAULT_LISTEN_PORT = 8887;
     
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
index 4231197c53d70e20a97e315bf9b3e9c3df207f40..b213f5738450d9270374dae3707aaa0e6656d749 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
@@ -34,16 +34,18 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                                          - 1  // 0x00 ending the padding
                                          - 4; // 4 byte checksum
 
+    private static final boolean DISABLE_BATCHING = false;
+    
     /* not final or private so the test code can adjust */
     static long DEFAULT_DELAY = 500;
     /** wait up to 2 seconds before sending a small message */
     protected long getSendDelay() { return DEFAULT_DELAY; }
     
     public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Preprocess queue with " + pending.size() + " to send");
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Preprocess queue with " + pending.size() + " to send");
         
-        if (getSendDelay() <= 0) {
+        if (DISABLE_BATCHING || getSendDelay() <= 0) {
             if (_log.shouldLog(Log.INFO))
                 _log.info("No batching, send all messages immediately");
             while (pending.size() > 0) {
@@ -131,8 +133,8 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
             }
         }
         
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Sent everything on the list (pending=" + pending.size() + ")");
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Sent everything on the list (pending=" + pending.size() + ")");
 
         // sent everything from the pending list, no need to delayed flush
         return false;
@@ -150,9 +152,6 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sending " + startAt + ":" + sendThrough + " out of " + pending.size());
         byte preprocessed[] = _dataCache.acquire().getData();
-        ByteArray ivBuf = _ivCache.acquire();
-        byte iv[] = ivBuf.getData(); // new byte[IV_SIZE];
-        _context.random().nextBytes(iv);
         
         int offset = 0;
         offset = writeFragments(pending, startAt, sendThrough, preprocessed, offset);
@@ -160,6 +159,17 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         // so we need to format, pad, and rearrange according to the spec to
         // generate the final preprocessed data
         
+        if (offset <= 0) {
+            StringBuffer buf = new StringBuffer(128);
+            buf.append("wtf, written offset is ").append(offset);
+            buf.append(" for ").append(startAt).append(" through ").append(sendThrough);
+            for (int i = startAt; i <= sendThrough; i++) {
+                buf.append(" ").append(pending.get(i).toString());
+            }
+            _log.log(Log.CRIT, buf.toString());
+            return;
+        }
+        
         preprocess(preprocessed, offset);
         
         sender.sendPreprocessed(preprocessed, rec);
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
index b8f1795161f455dbac5d6ea30945e6231e3b4a34..7c8552e9d94181746455718ad35117d01ba5c045 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
@@ -17,7 +17,7 @@ public class BatchedRouterPreprocessor extends BatchedPreprocessor {
      */
     public static final String PROP_BATCH_FREQUENCY = "batchFrequency";
     public static final String PROP_ROUTER_BATCH_FREQUENCY = "router.batchFrequency";
-    public static final int DEFAULT_BATCH_FREQUENCY = 0;
+    public static final int DEFAULT_BATCH_FREQUENCY = 500;
     
     public BatchedRouterPreprocessor(RouterContext ctx) {
         this(ctx, null);
diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
index b6c71fe978d6f853620f5261dbe589b5a4cac4ab..ae95d0330d61f0a6e96b316a32f97eab0c084290 100644
--- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
@@ -49,6 +49,8 @@ public class FragmentHandler {
                                               "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
         _context.statManager().createRateStat("tunnel.fragmentedDropped", "How many fragments were in a partially received yet failed message?", 
                                               "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
+        _context.statManager().createRateStat("tunnel.corruptMessage", "How many corrupted messages arrived?", 
+                                              "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000 });
     }
     
     /**
@@ -61,9 +63,11 @@ public class FragmentHandler {
     public void receiveTunnelMessage(byte preprocessed[], int offset, int length) {
         boolean ok = verifyPreprocessed(preprocessed, offset, length);
         if (!ok) {
-            _log.error("Unable to verify preprocessed data (pre.length=" + preprocessed.length 
-                       + " off=" +offset + " len=" + length, new Exception("failed"));
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Unable to verify preprocessed data (pre.length=" 
+                          + preprocessed.length + " off=" +offset + " len=" + length);
             _cache.release(new ByteArray(preprocessed));
+            _context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
             return;
         }
         offset += HopProcessor.IV_LENGTH; // skip the IV
@@ -84,6 +88,7 @@ public class FragmentHandler {
         } catch (RuntimeException e) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Corrupt fragment received: offset = " + offset, e);
+            _context.statManager().addRateData("tunnel.corruptMessage", 1, 1);
             throw e;
         } finally {
             // each of the FragmentedMessages populated make a copy out of the
@@ -110,8 +115,19 @@ public class FragmentHandler {
     private boolean verifyPreprocessed(byte preprocessed[], int offset, int length) {
         // now we need to verify that the message was received correctly
         int paddingEnd = HopProcessor.IV_LENGTH + 4;
-        while (preprocessed[offset+paddingEnd] != (byte)0x00)
+        while (preprocessed[offset+paddingEnd] != (byte)0x00) {
             paddingEnd++;
+            if (offset+paddingEnd >= length) {
+                if (_log.shouldLog(Log.ERROR))
+                    _log.error("Corrupt tunnel message padding");
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("cannot verify, going past the end [off=" 
+                              + offset + " len=" + length + " paddingEnd=" 
+                              + paddingEnd + " data:\n"
+                              + Base64.encode(preprocessed, offset, length));
+                return false;
+            }
+        }
         paddingEnd++; // skip the last
         
         ByteArray ba = _validateCache.acquire(); // larger than necessary, but always sufficient
@@ -129,10 +145,11 @@ public class FragmentHandler {
         boolean eq = DataHelper.eq(v.getData(), 0, preprocessed, offset + HopProcessor.IV_LENGTH, 4);
         if (!eq) {
             if (_log.shouldLog(Log.ERROR))
-                _log.error("Endpoint data doesn't match: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1));
-            if (_log.shouldLog(Log.DEBUG))
-                _log.debug("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n" 
-                           + Base64.encode(preprocessed, offset + paddingEnd, preV.length-HopProcessor.IV_LENGTH));
+                _log.error("Corrupt tunnel message - verification fails");
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n" 
+                           + " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd
+                           + Base64.encode(preprocessed, offset, length));
         }
         
         _context.sha().cache().release(cache);
@@ -380,8 +397,8 @@ public class FragmentHandler {
             if (removed && !_msg.getReleased()) {
                 _failed++;
                 noteFailure(_msg.getMessageId());
-                if (_log.shouldLog(Log.ERROR))
-                    _log.error("Dropped failed fragmented message: " + _msg);
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Dropped failed fragmented message: " + _msg);
                 _context.statManager().addRateData("tunnel.fragmentedDropped", _msg.getFragmentCount(), _msg.getLifetime());
                 _msg.failed();
             } else {
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
index 7ec95790bdc563596951f17df81f677b6f4f9864..bb3d5b6968b8c44a3e8937051b67de0bd9b9caf6 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
@@ -8,6 +8,7 @@ import net.i2p.data.Hash;
 import net.i2p.data.TunnelId;
 import net.i2p.data.i2np.I2NPMessage;
 import net.i2p.data.i2np.TunnelGatewayMessage;
+import net.i2p.router.Router;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleTimer;
 
@@ -87,7 +88,7 @@ public class TunnelGateway {
         _messagesSent++;
         boolean delayedFlush = false;
         
-        Pending cur = new Pending(msg, toRouter, toTunnel);
+        Pending cur = new PendingImpl(msg, toRouter, toTunnel);
         synchronized (_queue) {
             _queue.add(cur);
             delayedFlush = _preprocessor.preprocessQueue(_queue, _sender, _receiver);
@@ -96,9 +97,9 @@ public class TunnelGateway {
             // expire any as necessary, even if its framented
             for (int i = 0; i < _queue.size(); i++) {
                 Pending m = (Pending)_queue.get(i);
-                if (m.getExpiration() < _lastFlush) {
+                if (m.getExpiration() + Router.CLOCK_FUDGE_FACTOR < _lastFlush) {
                     if (_log.shouldLog(Log.ERROR))
-                        _log.error("Expire on the queue: " + m);
+                        _log.error("Expire on the queue (size=" + _queue.size() + "): " + m);
                     _queue.remove(i);
                     i--;
                 }
@@ -140,13 +141,13 @@ public class TunnelGateway {
     }
     
     public static class Pending {
-        private Hash _toRouter;
-        private TunnelId _toTunnel;
+        protected Hash _toRouter;
+        protected TunnelId _toTunnel;
         private long _messageId;
-        private long _expiration;
-        private byte _remaining[];
-        private int _offset;
-        private int _fragmentNumber;
+        protected long _expiration;
+        protected byte _remaining[];
+        protected int _offset;
+        protected int _fragmentNumber;
         
         public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
             _toRouter = toRouter;
@@ -174,6 +175,35 @@ public class TunnelGateway {
         /** ok, fragment sent, increment what the next will be */
         public void incrementFragmentNumber() { _fragmentNumber++; }
     }
+    private class PendingImpl extends Pending {
+        private long _created;
+        
+        public PendingImpl(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
+            super(message, toRouter, toTunnel);
+            _created = _context.clock().now();
+        }        
+        
+        public String toString() {
+            StringBuffer buf = new StringBuffer(64);
+            buf.append("Message on ");
+            buf.append(TunnelGateway.this.toString());
+            if (_toRouter != null) {
+                buf.append(" targetting ");
+                buf.append(_toRouter.toBase64()).append(" ");
+                if (_toTunnel != null)
+                    buf.append(_toTunnel.getTunnelId());
+            }
+            long now = _context.clock().now();
+            buf.append(" actual lifetime ");
+            buf.append(now - _created).append("ms");
+            buf.append(" potential lifetime ");
+            buf.append(_expiration - _created).append("ms");
+            buf.append(" size ").append(_remaining.length);
+            buf.append(" offset ").append(_offset);
+            buf.append(" frag ").append(_fragmentNumber);
+            return buf.toString();
+        }
+    }
     
     private class DelayedFlush implements SimpleTimer.TimedEvent {
         public void timeReached() {
diff --git a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
index 5275dedd3e3e02ac74e06fcf0303c2a2cf2a0708..ab6dd977e55dfc29d8c485bb35b7b561c889d981 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
@@ -45,7 +45,7 @@ public class RequestTunnelJob extends JobImpl {
     private boolean _isFake;
     private boolean _isExploratory;
     
-    static final int HOP_REQUEST_TIMEOUT = 30*1000;
+    static final int HOP_REQUEST_TIMEOUT = 20*1000;
     private static final int LOOKUP_TIMEOUT = 10*1000;
     
     public RequestTunnelJob(RouterContext ctx, TunnelCreatorConfig cfg, Job onCreated, Job onFailed, int hop, boolean isFake, boolean isExploratory) {
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
index afa92ac6e16d31b9413d913db471f5844b5fc2d0..8a3b53d26e673a9ba26f1eea3f9a299700919af0 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -37,11 +37,11 @@ public class TunnelPool {
     private long _lastSelectionPeriod;
     
     /**
-     * Only 3 builds per minute per pool, even if we have failing tunnels,
+     * Only 5 builds per minute per pool, even if we have failing tunnels,
      * etc.  On overflow, the necessary additional tunnels are built by the
      * RefreshJob
      */
-    private static final int MAX_BUILDS_PER_MINUTE = 3;
+    private static final int MAX_BUILDS_PER_MINUTE = 5;
     
     public TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel, TunnelBuilder builder) {
         _context = ctx;