propagate from branch 'i2p.i2p' (head 3d405c867f6903bf1d69b04c1daebf3146882525)
to branch 'i2p.i2p.zzz.test4' (head bfd85b10fdd1542526a4b9c53e5d4a733087f317)
This commit is contained in:
@ -3,6 +3,7 @@ package net.i2p.router.web;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -61,14 +62,10 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
|
||||
public static final String DEFAULT_UPDATE_URL;
|
||||
static {
|
||||
String foo;
|
||||
try {
|
||||
Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader());
|
||||
foo = PACK200_URLS;
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
foo = NO_PACK200_URLS;
|
||||
}
|
||||
DEFAULT_UPDATE_URL = foo;
|
||||
if (FileUtil.isPack200Supported())
|
||||
DEFAULT_UPDATE_URL = PACK200_URLS;
|
||||
else
|
||||
DEFAULT_UPDATE_URL = NO_PACK200_URLS;
|
||||
}
|
||||
|
||||
public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
|
||||
|
@ -59,7 +59,13 @@ public class GraphHelper extends FormHandler {
|
||||
try { _width = Math.min(Integer.parseInt(str), MAX_X); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
public void setRefreshDelay(String str) {
|
||||
try { _refreshDelaySeconds = Math.max(Integer.parseInt(str), MIN_REFRESH); } catch (NumberFormatException nfe) {}
|
||||
try {
|
||||
int rds = Integer.parseInt(str);
|
||||
if (rds > 0)
|
||||
_refreshDelaySeconds = Math.max(rds, MIN_REFRESH);
|
||||
else
|
||||
_refreshDelaySeconds = -1;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
public String getImages() {
|
||||
@ -83,7 +89,7 @@ public class GraphHelper extends FormHandler {
|
||||
+ "&periodCount=" + (3 * _periodCount )
|
||||
+ "&width=" + (3 * _width)
|
||||
+ "&height=" + (3 * _height)
|
||||
+ "\" / target=\"_blank\">");
|
||||
+ "\" target=\"_blank\">");
|
||||
String title = _("Combined bandwidth graph");
|
||||
_out.write("<img class=\"statimage\" width=\""
|
||||
+ (_width + 83) + "\" height=\"" + (_height + 92)
|
||||
@ -129,6 +135,8 @@ public class GraphHelper extends FormHandler {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final int[] times = { 60, 2*60, 5*60, 10*60, 30*60, 60*60, -1 };
|
||||
|
||||
public String getForm() {
|
||||
String prev = System.getProperty("net.i2p.router.web.GraphHelper.nonce");
|
||||
if (prev != null) System.setProperty("net.i2p.router.web.GraphHelper.noncePrev", prev);
|
||||
@ -145,8 +153,22 @@ public class GraphHelper extends FormHandler {
|
||||
_out.write(_("Image sizes") + ": " + _("width") + ": <input size=\"4\" type=\"text\" name=\"width\" value=\"" + _width
|
||||
+ "\"> " + _("pixels") + ", " + _("height") + ": <input size=\"4\" type=\"text\" name=\"height\" value=\"" + _height
|
||||
+ "\"> " + _("pixels") + "<br>\n");
|
||||
_out.write(_("Refresh delay") + ": <select name=\"refreshDelay\"><option value=\"60\">1 " + _("minute") + "</option><option value=\"120\">2 " + _("minutes") + "</option><option value=\"300\">5 " + _("minutes") + "</option><option value=\"600\">10 " + _("minutes") + "</option><option value=\"1800\">30 " + _("minutes") + "</option><option value=\"3600\">1 " + _("hour") + "</option><option value=\"-1\">" + _("Never") + "</option></select><br>\n");
|
||||
_out.write("<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>");
|
||||
_out.write(_("Refresh delay") + ": <select name=\"refreshDelay\">");
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
_out.write("<option value=\"");
|
||||
_out.write(Integer.toString(times[i]));
|
||||
_out.write("\"");
|
||||
if (times[i] == _refreshDelaySeconds)
|
||||
_out.write(" selected=\"true\"");
|
||||
_out.write(">");
|
||||
if (times[i] > 0)
|
||||
_out.write(DataHelper.formatDuration2(times[i] * 1000));
|
||||
else
|
||||
_out.write(_("Never"));
|
||||
_out.write("</option>\n");
|
||||
}
|
||||
_out.write("</select><br>\n" +
|
||||
"<hr><div class=\"formaction\"><input type=\"submit\" value=\"" + _("Redraw") + "\"></div></form>");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
@ -53,7 +53,8 @@ public class LocaleWebAppHandler extends WebApplicationHandler
|
||||
// home page
|
||||
pathInContext = "/index.jsp";
|
||||
} else if (pathInContext.indexOf("/", 1) < 0 &&
|
||||
!pathInContext.endsWith(".jsp")) {
|
||||
(!pathInContext.endsWith(".jsp")) &&
|
||||
(!pathInContext.endsWith(".txt"))) {
|
||||
// add .jsp to pages at top level
|
||||
pathInContext += ".jsp";
|
||||
}
|
||||
|
@ -4,37 +4,53 @@ import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.apps.systray.SysTray;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.ShellCommand;
|
||||
|
||||
import org.mortbay.http.DigestAuthenticator;
|
||||
import org.mortbay.http.HashUserRealm;
|
||||
import org.mortbay.http.SecurityConstraint;
|
||||
import org.mortbay.http.SslListener;
|
||||
import org.mortbay.http.handler.SecurityHandler;
|
||||
import org.mortbay.jetty.Server;
|
||||
import org.mortbay.jetty.servlet.WebApplicationContext;
|
||||
import org.mortbay.jetty.servlet.WebApplicationHandler;
|
||||
import org.mortbay.util.InetAddrPort;
|
||||
|
||||
public class RouterConsoleRunner {
|
||||
private Server _server;
|
||||
private String _listenPort = "7657";
|
||||
private String _listenHost = "127.0.0.1";
|
||||
private String _webAppsDir = "./webapps/";
|
||||
private String _listenPort;
|
||||
private String _listenHost;
|
||||
private String _sslListenPort;
|
||||
private String _sslListenHost;
|
||||
private String _webAppsDir;
|
||||
private static final String PROP_WEBAPP_CONFIG_FILENAME = "router.webappsConfigFile";
|
||||
private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config";
|
||||
private static final DigestAuthenticator authenticator = new DigestAuthenticator();
|
||||
public static final String ROUTERCONSOLE = "routerconsole";
|
||||
public static final String PREFIX = "webapps.";
|
||||
public static final String ENABLED = ".startOnLoad";
|
||||
private static final String PROP_KEYSTORE_PASSWORD = "routerconsole.keystorePassword";
|
||||
private static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||
private static final String PROP_KEY_PASSWORD = "routerconsole.keyPassword";
|
||||
private static final String DEFAULT_LISTEN_PORT = "7657";
|
||||
private static final String DEFAULT_LISTEN_HOST = "127.0.0.1";
|
||||
private static final String DEFAULT_WEBAPPS_DIR = "./webapps/";
|
||||
private static final String USAGE = "Bad RouterConsoleRunner arguments, check clientApp.0.args in your clients.config file! " +
|
||||
"Usage: [[port host[,host]] [-s sslPort [host[,host]]] [webAppsDir]]";
|
||||
|
||||
static {
|
||||
System.setProperty("org.mortbay.http.Version.paranoid", "true");
|
||||
@ -42,6 +58,27 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* non-SSL:
|
||||
* RouterConsoleRunner
|
||||
* RouterConsoleRunner 7657
|
||||
* RouterConsoleRunner 7657 127.0.0.1
|
||||
* RouterConsoleRunner 7657 127.0.0.1,::1
|
||||
* RouterConsoleRunner 7657 127.0.0.1,::1 ./webapps/
|
||||
*
|
||||
* SSL:
|
||||
* RouterConsoleRunner -s 7657
|
||||
* RouterConsoleRunner -s 7657 127.0.0.1
|
||||
* RouterConsoleRunner -s 7657 127.0.0.1,::1
|
||||
* RouterConsoleRunner -s 7657 127.0.0.1,::1 ./webapps/
|
||||
*
|
||||
* If using both, non-SSL must be first:
|
||||
* RouterConsoleRunner 7657 127.0.0.1 -s 7667
|
||||
* RouterConsoleRunner 7657 127.0.0.1 -s 7667 127.0.0.1
|
||||
* RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1
|
||||
* RouterConsoleRunner 7657 127.0.0.1,::1 -s 7667 127.0.0.1,::1 ./webapps/
|
||||
* </pre>
|
||||
*
|
||||
* @param args second arg may be a comma-separated list of bind addresses,
|
||||
* for example ::1,127.0.0.1
|
||||
* On XP, the other order (127.0.0.1,::1) fails the IPV6 bind,
|
||||
@ -50,10 +87,40 @@ public class RouterConsoleRunner {
|
||||
* So the wise choice is ::1,127.0.0.1
|
||||
*/
|
||||
public RouterConsoleRunner(String args[]) {
|
||||
if (args.length == 3) {
|
||||
_listenPort = args[0].trim();
|
||||
_listenHost = args[1].trim();
|
||||
_webAppsDir = args[2].trim();
|
||||
if (args.length == 0) {
|
||||
// _listenHost and _webAppsDir are defaulted below
|
||||
_listenPort = DEFAULT_LISTEN_PORT;
|
||||
} else {
|
||||
boolean ssl = false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-s"))
|
||||
ssl = true;
|
||||
else if ((!ssl) && _listenPort == null)
|
||||
_listenPort = args[i];
|
||||
else if ((!ssl) && _listenHost == null)
|
||||
_listenHost = args[i];
|
||||
else if (ssl && _sslListenPort == null)
|
||||
_sslListenPort = args[i];
|
||||
else if (ssl && _sslListenHost == null)
|
||||
_sslListenHost = args[i];
|
||||
else if (_webAppsDir == null)
|
||||
_webAppsDir = args[i];
|
||||
else {
|
||||
System.err.println(USAGE);
|
||||
throw new IllegalArgumentException(USAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_listenHost == null)
|
||||
_listenHost = DEFAULT_LISTEN_HOST;
|
||||
if (_sslListenHost == null)
|
||||
_sslListenHost = _listenHost;
|
||||
if (_webAppsDir == null)
|
||||
_webAppsDir = DEFAULT_WEBAPPS_DIR;
|
||||
// _listenPort and _sslListenPort are not defaulted, if one or the other is null, do not enable
|
||||
if (_listenPort == null && _sslListenPort == null) {
|
||||
System.err.println(USAGE);
|
||||
throw new IllegalArgumentException(USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,22 +163,63 @@ public class RouterConsoleRunner {
|
||||
List<String> notStarted = new ArrayList();
|
||||
WebApplicationHandler baseHandler = null;
|
||||
try {
|
||||
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
|
||||
int boundAddresses = 0;
|
||||
while (tok.hasMoreTokens()) {
|
||||
String host = tok.nextToken().trim();
|
||||
try {
|
||||
if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5
|
||||
_server.addListener('[' + host + "]:" + _listenPort);
|
||||
else
|
||||
_server.addListener(host + ':' + _listenPort);
|
||||
boundAddresses++;
|
||||
} catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below
|
||||
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
|
||||
|
||||
// add standard listeners
|
||||
if (_listenPort != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String host = tok.nextToken().trim();
|
||||
try {
|
||||
if (host.indexOf(":") >= 0) // IPV6 - requires patched Jetty 5
|
||||
_server.addListener('[' + host + "]:" + _listenPort);
|
||||
else
|
||||
_server.addListener(host + ':' + _listenPort);
|
||||
boundAddresses++;
|
||||
} catch (IOException ioe) { // this doesn't seem to work, exceptions don't happen until start() below
|
||||
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + ' ' + ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add SSL listeners
|
||||
int sslPort = 0;
|
||||
if (_sslListenPort != null) {
|
||||
try {
|
||||
sslPort = Integer.parseInt(_sslListenPort);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (sslPort <= 0)
|
||||
System.err.println("Bad routerconsole SSL port " + _sslListenPort);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
File keyStore = new File(ctx.getConfigDir(), "keystore/console.ks");
|
||||
if (verifyKeyStore(keyStore)) {
|
||||
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String host = tok.nextToken().trim();
|
||||
// doing it this way means we don't have to escape an IPv6 host with []
|
||||
InetAddrPort iap = new InetAddrPort(host, sslPort);
|
||||
try {
|
||||
SslListener ssll = new SslListener(iap);
|
||||
// the keystore path and password
|
||||
ssll.setKeystore(keyStore.getAbsolutePath());
|
||||
ssll.setPassword(ctx.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD));
|
||||
// the X.509 cert password (if not present, verifyKeyStore() returned false)
|
||||
ssll.setKeyPassword(ctx.getProperty(PROP_KEY_PASSWORD, "thisWontWork"));
|
||||
_server.addListener(ssll);
|
||||
boundAddresses++;
|
||||
} catch (Exception e) { // probably no exceptions at this point
|
||||
System.err.println("Unable to bind routerconsole to " + host + " port " + _listenPort + " for SSL: " + e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (boundAddresses <= 0) {
|
||||
System.err.println("Unable to bind routerconsole to any address on port " + _listenPort);
|
||||
System.err.println("Unable to bind routerconsole to any address on port " + _listenPort + (sslPort > 0 ? (" or SSL port " + sslPort) : ""));
|
||||
return;
|
||||
}
|
||||
_server.setRootWebApp(ROUTERCONSOLE);
|
||||
@ -201,6 +309,90 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success if it exists and we have a password, or it was created successfully.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static boolean verifyKeyStore(File ks) {
|
||||
if (ks.exists()) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
boolean rv = ctx.getProperty(PROP_KEY_PASSWORD) != null;
|
||||
if (!rv)
|
||||
System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath());
|
||||
return rv;
|
||||
}
|
||||
File dir = ks.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
File sdir = new SecureDirectory(dir.getAbsolutePath());
|
||||
if (!sdir.mkdir())
|
||||
return false;
|
||||
}
|
||||
return createKeyStore(ks);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call out to keytool to create a new keystore with a keypair in it.
|
||||
* Trying to do this programatically is a nightmare, requiring either BouncyCastle
|
||||
* libs or using proprietary Sun libs, and it's a huge mess.
|
||||
*
|
||||
* @return success
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static boolean createKeyStore(File ks) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
// make a random 48 character password (30 * 8 / 5)
|
||||
byte[] rand = new byte[30];
|
||||
ctx.random().nextBytes(rand);
|
||||
String keyPassword = Base32.encode(rand);
|
||||
// and one for the cname
|
||||
ctx.random().nextBytes(rand);
|
||||
String cname = Base32.encode(rand) + ".console.i2p.net";
|
||||
|
||||
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
|
||||
String[] args = new String[] {
|
||||
keytool,
|
||||
"-genkey", // -genkeypair preferred in newer keytools, but this works with more
|
||||
"-storetype", KeyStore.getDefaultType(),
|
||||
"-keystore", ks.getAbsolutePath(),
|
||||
"-storepass", DEFAULT_KEYSTORE_PASSWORD,
|
||||
"-alias", "console",
|
||||
"-dname", "CN=" + cname + ",OU=Console,O=I2P Anonymous Network,L=XX,ST=XX,C=XX",
|
||||
"-validity", "3652", // 10 years
|
||||
"-keyalg", "DSA",
|
||||
"-keysize", "1024",
|
||||
"-keypass", keyPassword};
|
||||
boolean success = (new ShellCommand()).executeSilentAndWaitTimed(args, 30); // 30 secs
|
||||
if (success) {
|
||||
success = ks.exists();
|
||||
if (success) {
|
||||
SecureFileOutputStream.setPerms(ks);
|
||||
try {
|
||||
RouterContext rctx = (RouterContext) ctx;
|
||||
rctx.router().setConfigSetting(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
|
||||
rctx.router().setConfigSetting(PROP_KEY_PASSWORD, keyPassword);
|
||||
rctx.router().saveConfig();
|
||||
} catch (Exception e) {} // class cast exception
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
System.err.println("Created self-signed certificate for " + cname + " in keystore: " + ks.getAbsolutePath() + "\n" +
|
||||
"The certificate name was generated randomly, and is not associated with your " +
|
||||
"IP address, host name, router identity, or destination keys.");
|
||||
} else {
|
||||
System.err.println("Failed to create console SSL keystore using command line:");
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
buf.append('"').append(args[i]).append("\" ");
|
||||
}
|
||||
System.err.println(buf.toString());
|
||||
System.err.println("This is for the Sun/Oracle keytool, others may be incompatible.\n" +
|
||||
"If you create the keystore manually, you must add " + PROP_KEYSTORE_PASSWORD + " and " + PROP_KEY_PASSWORD +
|
||||
" to " + (new File(ctx.getConfigDir(), "router.config")).getAbsolutePath());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static void initialize(WebApplicationContext context) {
|
||||
String password = getPassword();
|
||||
if (password != null) {
|
||||
|
Reference in New Issue
Block a user