diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 2f1619d5c9..11aa025bc9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -254,6 +254,8 @@ public class TunnelController implements Logging { File altFile = getAlternatePrivateKeyFile(); if (altFile == null) return false; + if (altFile.equals(keyFile)) + return false; if (altFile.exists()) return true; PrivateKeyFile pkf = new PrivateKeyFile(keyFile); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java index 26f4265e02..2a684c7e40 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java @@ -264,14 +264,23 @@ public class GeneralHelper { return (tun != null && tun.getSpoofedHost() != null) ? tun.getSpoofedHost() : ""; } + /** + * @return path, non-null, non-empty + */ public String getPrivateKeyFile(int tunnel) { return getPrivateKeyFile(_group, tunnel); } + /** + * @return path, non-null, non-empty + */ public String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) { TunnelController tun = getController(tcg, tunnel); - if (tun != null && tun.getPrivKeyFile() != null) - return tun.getPrivKeyFile(); + if (tun != null) { + String rv = tun.getPrivKeyFile(); + if (rv != null) + return rv; + } if (tunnel < 0) tunnel = tcg == null ? 999 : tcg.getControllers().size(); String rv = "i2ptunnel" + tunnel + "-privKeys.dat"; @@ -284,6 +293,28 @@ public class GeneralHelper { return rv; } + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(int tunnel) { + return getAltPrivateKeyFile(_group, tunnel); + } + + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) { + TunnelController tun = getController(tcg, tunnel); + if (tun != null) { + File f = tun.getAlternatePrivateKeyFile(); + if (f != null) + return f.getAbsolutePath(); + } + return ""; + } + public String getClientInterface(int tunnel) { TunnelController tun = getController(tunnel); if (tun != null) { @@ -357,6 +388,29 @@ public class GeneralHelper { return null; } + /** + * Works even if tunnel is not running. + * @return Destination or null + * @since 0.9.30 + */ + public Destination getAltDestination(int tunnel) { + TunnelController tun = getController(tunnel); + if (tun != null) { + // do this the hard way + File keyFile = tun.getAlternatePrivateKeyFile(); + if (keyFile != null) { + PrivateKeyFile pkf = new PrivateKeyFile(keyFile); + try { + Destination rv = pkf.getDestination(); + if (rv != null) + return rv; + } catch (I2PException e) { + } catch (IOException e) {} + } + } + return null; + } + public boolean shouldStartAutomatically(int tunnel) { TunnelController tun = getController(tunnel); return tun != null ? tun.getStartOnLoad() : false; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java index 6dd91cce36..d381939a11 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java @@ -161,6 +161,16 @@ public class TunnelConfig { public String getPrivKeyFile() { return _privKeyFile; } + + /** + * What filename is this server tunnel's alternate private keys stored in + * @since 0.9.30 + */ + public void setAltPrivKeyFile(String file) { + if (file != null) + _otherOptions.put(I2PTunnelServer.PROP_ALT_PKF, file.trim()); + } + /** * If called with any value, we want this tunnel to start whenever it is * loaded (aka right now and whenever the router is started up) @@ -725,7 +735,8 @@ public class TunnelConfig { PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY, PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY, PROP_MAX_STREAMS, I2PClient.PROP_SIGTYPE, - "inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey" + "inbound.randomKey", "outbound.randomKey", "i2cp.leaseSetSigningPrivateKey", "i2cp.leaseSetPrivateKey", + I2PTunnelServer.PROP_ALT_PKF }; private static final String _httpServerOpts[] = { I2PTunnelHTTPServer.OPT_POST_WINDOW, diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index f252faade8..99317e54b0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -70,6 +70,14 @@ public class EditBean extends IndexBean { public String getPrivateKeyFile(int tunnel) { return _helper.getPrivateKeyFile(tunnel); } + + /** + * @return path or "" + * @since 0.9.30 + */ + public String getAltPrivateKeyFile(int tunnel) { + return _helper.getAltPrivateKeyFile(tunnel); + } /**** public String getNameSignature(int tunnel) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index fe1c30b17b..be841b817c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -488,6 +488,39 @@ public class IndexBean { return d.toBase32(); return ""; } + + /** + * Works even if tunnel is not running. + * @return Destination or null + * @since 0.9.30 + */ + protected Destination getAltDestination(int tunnel) { + return _helper.getAltDestination(tunnel); + } + + /** + * Works even if tunnel is not running. + * @return Base64 or "" + * @since 0.9.30 + */ + public String getAltDestinationBase64(int tunnel) { + Destination d = getAltDestination(tunnel); + if (d != null) + return d.toBase64(); + return ""; + } + + /** + * Works even if tunnel is not running. + * @return "{52 chars}.b32.i2p" or "" + * @since 0.9.30 + */ + public String getAltDestHashBase32(int tunnel) { + Destination d = getAltDestination(tunnel); + if (d != null) + return d.toBase32(); + return ""; + } /** * For index.jsp @@ -613,10 +646,20 @@ public class IndexBean { public void setSpoofedHost(String host) { _config.setSpoofedHost(host); } + /** What filename is this server tunnel's private keys stored in */ public void setPrivKeyFile(String file) { _config.setPrivKeyFile(file); } + + /** + * What filename is this server tunnel's alternate private keys stored in + * @since 0.9.30 + */ + public void setAltPrivKeyFile(String file) { + _config.setAltPrivKeyFile(file); + } + /** * If called with any value (and the form submitted with action=Remove), * we really do want to stop and remove the tunnel. diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index f2e5204e85..c93ceb6ac1 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -669,8 +669,9 @@ input.default { width: 1px; height: 1px; visibility: hidden; } <% **********************/ %> - <% if (true /* editBean.isAdvanced() */ ) { - int currentSigType = editBean.getSigType(curTunnel, tunnelType); + <% + int currentSigType = editBean.getSigType(curTunnel, tunnelType); + if (true /* editBean.isAdvanced() */ ) { %>
<% } // isAdvanced %> + + <% + /* alternate dest, only if current dest is set and is DSA_SHA1 */ + + if (currentSigType == 0 && !"".equals(b64) && !"streamrserver".equals(tunnelType)) { + %>
+ + +
+ + <% + String ab64 = editBean.getAltDestinationBase64(curTunnel); + if (!"".equals(ab64)) { + %>
+ + +
+
+ + <%=editBean.getAltDestHashBase32(curTunnel)%> +
+
+ <% + ab64 = ab64.replace("=", "%3d"); + String name = editBean.getSpoofedHost(curTunnel); + if (name == null || name.equals("")) + name = editBean.getTunnelName(curTunnel); + // mysite.i2p is set in the installed i2ptunnel.config + if (name != null && !name.equals("") && !name.equals("mysite.i2p") && !name.contains(" ") && name.endsWith(".i2p")) { + %> + <%=intl._t("Add to local addressbook")%> + <% + } else { + %> + <%=intl._t("Set name with .i2p suffix to enable QR code generation")%> + <% + } // name + %>
+ <% + } // ab64 + %>

+ <% } // currentSigType %>

diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java index 04b522e245..b79f894a6d 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java @@ -314,6 +314,9 @@ public class AddressbookBean extends BaseBean } if (action.equals(_t("Delete Entry"))) search = null; + } else if (action.equals(_t("Add Alternate"))) { + // button won't be in UI + message = "Unsupported"; } if( changed ) { try { diff --git a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java index 6351764466..a47b125f6f 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java @@ -231,7 +231,7 @@ public class NamingServiceBean extends AddressbookBean if (_context.getBooleanProperty(PROP_PW_ENABLE) || (serial != null && serial.equals(lastSerial))) { boolean changed = false; - if (action.equals(_t("Add")) || action.equals(_t("Replace"))) { + if (action.equals(_t("Add")) || action.equals(_t("Replace")) || action.equals(_t("Add Alternate"))) { if(hostname != null && destination != null) { try { // throws IAE with translated message @@ -243,20 +243,38 @@ public class NamingServiceBean extends AddressbookBean Destination oldDest = getNamingService().lookup(host, nsOptions, outProperties); if (oldDest != null && destination.equals(oldDest.toBase64())) { message = _t("Host name {0} is already in address book, unchanged.", displayHost); - } else if (oldDest != null && !action.equals(_t("Replace"))) { + } else if (oldDest == null && action.equals(_t("Add Alternate"))) { + message = _t("Host name {0} is not in the address book.", displayHost); + } else if (oldDest != null && action.equals(_t("Add"))) { message = _t("Host name {0} is already in address book with a different destination. Click \"Replace\" to overwrite.", displayHost); } else { try { Destination dest = new Destination(destination); if (oldDest != null) { nsOptions.putAll(outProperties); - nsOptions.setProperty("m", Long.toString(_context.clock().now())); + String now = Long.toString(_context.clock().now()); + if (action.equals(_t("Add Alternate"))) + nsOptions.setProperty("a", now); + else + nsOptions.setProperty("m", now); } nsOptions.setProperty("s", _t("Manually added via SusiDNS")); - boolean success = getNamingService().put(host, dest, nsOptions); + boolean success; + if (action.equals(_t("Add Alternate"))) { + // check all for dups + List all = getNamingService().lookupAll(host); + if (all == null || !all.contains(dest)) { + success = getNamingService().addDestination(host, dest, nsOptions); + } else { + // will get generic message below + success = false; + } + } else { + success = getNamingService().put(host, dest, nsOptions); + } if (success) { changed = true; - if (oldDest == null) + if (oldDest == null || action.equals(_t("Add Alternate"))) message = _t("Destination added for {0}.", displayHost); else message = _t("Destination changed for {0}.", displayHost); @@ -285,8 +303,21 @@ public class NamingServiceBean extends AddressbookBean } else if (action.equals(_t("Delete Selected")) || action.equals(_t("Delete Entry"))) { String name = null; int deleted = 0; + Destination matchDest = null; + if (action.equals(_t("Delete Entry"))) { + // remove specified dest only in case there is more than one + if (destination != null) { + try { + matchDest = new Destination(destination); + } catch (DataFormatException dfe) {} + } + } for (String n : deletionMarks) { - boolean success = getNamingService().remove(n, nsOptions); + boolean success; + if (matchDest != null) + success = getNamingService().remove(n, matchDest, nsOptions); + else + success = getNamingService().remove(n, nsOptions); String uni = AddressBean.toUnicode(n); String displayHost = uni.equals(n) ? n : uni + " (" + n + ')'; if (!success) { diff --git a/apps/susidns/src/jsp/addressbook.jsp b/apps/susidns/src/jsp/addressbook.jsp index a72382444a..ce7f573b13 100644 --- a/apps/susidns/src/jsp/addressbook.jsp +++ b/apps/susidns/src/jsp/addressbook.jsp @@ -120,6 +120,7 @@ ${book.loadBookMessages}