forked from I2P_Developers/i2p.i2p
i2ptunnel: New form to enter private key file for alternate destination
- Use alt destination for registration if set - Another dup check for alt destination SusiDNS: New button for adding alternate destination - Fix nonces on details page with multiple destinations - Fix single dest deletion on details page with multiple destinations - Set book in all forms to ensure correct book Blockfile: Fix specified-destination deletion from the correct book
This commit is contained in:
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -669,8 +669,9 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
</div>
|
||||
<% **********************/ %>
|
||||
|
||||
<% if (true /* editBean.isAdvanced() */ ) {
|
||||
int currentSigType = editBean.getSigType(curTunnel, tunnelType);
|
||||
<%
|
||||
int currentSigType = editBean.getSigType(curTunnel, tunnelType);
|
||||
if (true /* editBean.isAdvanced() */ ) {
|
||||
%>
|
||||
<div id="tunnelOptionsField" class="rowItem">
|
||||
<label>
|
||||
@ -713,6 +714,50 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
<hr />
|
||||
</div>
|
||||
<% } // isAdvanced %>
|
||||
|
||||
<%
|
||||
/* alternate dest, only if current dest is set and is DSA_SHA1 */
|
||||
|
||||
if (currentSigType == 0 && !"".equals(b64) && !"streamrserver".equals(tunnelType)) {
|
||||
%><div id="privKeyField" class="rowItem">
|
||||
<label for="privKeyFile"><%=intl._t("Alternate private key file")%> (Ed25519-SHA-512):</label>
|
||||
<input type="text" size="30" id="privKeyFile" name="altPrivKeyFile" title="Path to Private Key File" value="<%=editBean.getAltPrivateKeyFile(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<%
|
||||
String ab64 = editBean.getAltDestinationBase64(curTunnel);
|
||||
if (!"".equals(ab64)) {
|
||||
%><div id="destinationField" class="rowItem">
|
||||
<label for="localDestination"><%=intl._t("Alternate local destination")%>:</label>
|
||||
<textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Read Only: Alternate Local Destination" wrap="off" spellcheck="false"><%=ab64%></textarea>
|
||||
</div>
|
||||
<div id="destinationField" class="rowItem">
|
||||
<label> </label>
|
||||
<span class="comment"><%=editBean.getAltDestHashBase32(curTunnel)%></span>
|
||||
</div>
|
||||
<div id="destinationField" class="rowItem">
|
||||
<%
|
||||
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")) {
|
||||
%><label>
|
||||
<a class="control" title="<%=intl._t("Generate QR Code")%>" href="/imagegen/qr?s=320&t=<%=name%>&c=http%3a%2f%2f<%=name%>%2f%3fi2paddresshelper%3d<%=ab64%>" target="_top"><%=intl._t("Generate QR Code")%></a>
|
||||
</label>
|
||||
<a class="control" href="/susidns/addressbook.jsp?book=private&hostname=<%=name%>&destination=<%=ab64%>#add"><%=intl._t("Add to local addressbook")%></a>
|
||||
<%
|
||||
} else {
|
||||
%><label> </label>
|
||||
<span class="comment"><%=intl._t("Set name with .i2p suffix to enable QR code generation")%></span>
|
||||
<%
|
||||
} // name
|
||||
%></div>
|
||||
<%
|
||||
} // ab64
|
||||
%><div class="subdivider"><hr /></div>
|
||||
<% } // currentSigType %>
|
||||
|
||||
<div id="customOptionsField" class="rowItem">
|
||||
<label for="customOptions" accesskey="u">
|
||||
|
@ -259,10 +259,38 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
<span class="comment"><%=intl._t("This will add an alternate destination for {0}", name)%></span>
|
||||
<%
|
||||
} else {
|
||||
// If set, use the configured alternate destination as the new alias destination,
|
||||
// and the configured primary destination as the inner signer.
|
||||
// This is backwards from all the other ones, so we have to make a second HostTxtEntry just for this.
|
||||
SigningPrivateKey spk3 = null;
|
||||
String altdest = null;
|
||||
String altdestfile = editBean.getAltPrivateKeyFile(curTunnel);
|
||||
if (altdestfile.length() > 0) {
|
||||
try {
|
||||
PrivateKeyFile pkf3 = new PrivateKeyFile(altdestfile);
|
||||
altdest = pkf3.getDestination().toBase64();
|
||||
if (!b64.equals(altdest)) {
|
||||
// disallow dup
|
||||
spk3 = pkf3.getSigningPrivKey();
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (spk3 != null) {
|
||||
OrderedProperties props2 = new OrderedProperties();
|
||||
HostTxtEntry he2 = new HostTxtEntry(name, altdest, props2);
|
||||
props2.setProperty(HostTxtEntry.PROP_ACTION, HostTxtEntry.ACTION_ADDDEST);
|
||||
props2.setProperty(HostTxtEntry.PROP_OLDDEST, b64);
|
||||
he2.signInner(spk);
|
||||
he2.sign(spk3);
|
||||
%><textarea rows="1" style="height: 3em;" cols="60" readonly="readonly" id="localDestination" title="Copy and paste this to the registration site" wrap="off" spellcheck="false"><% he2.write(out); %></textarea>
|
||||
<span class="comment"><%=intl._t("This will add an alternate destination for {0}", name)%></span>
|
||||
<%
|
||||
} else {
|
||||
%><span class="comment"><%=intl._t("This tunnel must be configured with the new destination.")%></span>
|
||||
<span class="comment"><%=intl._t("Enter old destination below.")%></span>
|
||||
<%
|
||||
}
|
||||
} // spk3
|
||||
} // spk2
|
||||
%></div>
|
||||
<div class="separator">
|
||||
<hr />
|
||||
|
@ -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 {
|
||||
|
@ -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<Destination> 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) {
|
||||
|
@ -120,6 +120,7 @@ ${book.loadBookMessages}
|
||||
|
||||
<div id="search">
|
||||
<form method="POST" action="addressbook">
|
||||
<input type="hidden" name="book" value="${book.book}">
|
||||
<input type="hidden" name="begin" value="0">
|
||||
<input type="hidden" name="end" value="49">
|
||||
<table><tr>
|
||||
@ -136,6 +137,7 @@ ${book.loadBookMessages}
|
||||
%>
|
||||
<c:if test="${book.notEmpty}">
|
||||
<form method="POST" action="addressbook">
|
||||
<input type="hidden" name="book" value="${book.book}">
|
||||
<input type="hidden" name="serial" value="<%=susiNonce%>">
|
||||
<input type="hidden" name="begin" value="0">
|
||||
<input type="hidden" name="end" value="49">
|
||||
@ -163,7 +165,7 @@ ${book.loadBookMessages}
|
||||
</td><td class="names">
|
||||
<span class="addrhlpr"><a href="http://${addr.b32}/" target="_top" title="<%=intl._t("Base 32 address")%>">b32</a></span>
|
||||
</td><td class="names">
|
||||
<span class="addrhlpr"><a href="details?h=${addr.name}" title="<%=intl._t("More information on this entry")%>"><%=intl._t("details")%></a></span>
|
||||
<span class="addrhlpr"><a href="details?h=${addr.name}&book=${book.book}" title="<%=intl._t("More information on this entry")%>"><%=intl._t("details")%></a></span>
|
||||
</td>
|
||||
<td class="destinations"><textarea rows="1" style="height:3em;" wrap="off" cols="40" readonly="readonly" name="dest_${addr.name}" >${addr.destination}</textarea></td>
|
||||
</tr>
|
||||
@ -208,6 +210,7 @@ ${book.loadBookMessages}
|
||||
</c:if>
|
||||
|
||||
<form method="POST" action="addressbook">
|
||||
<input type="hidden" name="book" value="${book.book}">
|
||||
<input type="hidden" name="serial" value="<%=susiNonce%>">
|
||||
<input type="hidden" name="begin" value="0">
|
||||
<input type="hidden" name="end" value="49">
|
||||
@ -221,6 +224,9 @@ ${book.loadBookMessages}
|
||||
<p class="buttons">
|
||||
<input class="cancel" type="reset" value="<%=intl._t("Cancel")%>" >
|
||||
<input class="accept" type="submit" name="action" value="<%=intl._t("Replace")%>" >
|
||||
<% if (!book.getBook().equals("published")) { %>
|
||||
<input class="add" type="submit" name="action" value="<%=intl._t("Add Alternate")%>" >
|
||||
<% } %>
|
||||
<input class="add" type="submit" name="action" value="<%=intl._t("Add")%>" >
|
||||
</p>
|
||||
</div></form>
|
||||
|
@ -81,6 +81,8 @@
|
||||
if (addrs == null) {
|
||||
%><p>Not found: <%=detail%></p><%
|
||||
} else {
|
||||
// use one nonce for all
|
||||
String nonce = book.getSerial();
|
||||
for (i2p.susi.dns.AddressBean addr : addrs) {
|
||||
String b32 = addr.getB32();
|
||||
%>
|
||||
@ -139,10 +141,12 @@
|
||||
<div id="buttons">
|
||||
<form method="POST" action="addressbook">
|
||||
<p class="buttons">
|
||||
<input type="hidden" name="serial" value="${book.serial}">
|
||||
<input type="hidden" name="book" value="${book.book}">
|
||||
<input type="hidden" name="serial" value="<%=nonce%>">
|
||||
<input type="hidden" name="begin" value="0">
|
||||
<input type="hidden" name="end" value="49">
|
||||
<input type="hidden" name="checked" value="<%=detail%>">
|
||||
<input type="hidden" name="destination" value="<%=addr.getDestination()%>">
|
||||
<input class="delete" type="submit" name="action" value="<%=intl._t("Delete Entry")%>" >
|
||||
</p>
|
||||
</form>
|
||||
|
@ -1578,6 +1578,11 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
}
|
||||
storedOptions.remove(i);
|
||||
removeReverseEntry(hostname, d);
|
||||
if (options != null) {
|
||||
String list = options.getProperty("list");
|
||||
if (list != null)
|
||||
storedOptions.get(0).setProperty("list", list);
|
||||
}
|
||||
return put(hostname, newDests, storedOptions, false);
|
||||
}
|
||||
}
|
||||
|
12
history.txt
12
history.txt
@ -1,3 +1,15 @@
|
||||
2017-03-14 zzz
|
||||
* Blockfile: Fix specified-destination deletion from the correct book
|
||||
* i2ptunnel:
|
||||
- New form to enter private key file for alternate destination
|
||||
- Use alt destination for registration if set
|
||||
* NBI: Adjust info logging at startup
|
||||
* SusiDNS:
|
||||
- New button for adding alternate destination
|
||||
- Fix nonces on details page with multiple destinations
|
||||
- Fix single dest deletion on details page with multiple destinations
|
||||
- Set book in all forms to ensure correct book
|
||||
|
||||
2017-03-13 zzz
|
||||
* i2ptunnel:
|
||||
- Add subsession support to servers
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 5;
|
||||
public final static long BUILD = 6;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
Reference in New Issue
Block a user