* RouterConsoleRunner:

- Prep for ClientApp interface by storing context in a field,
      shuffle around what's static and what's not
      (ticket #347)
    - Remove ports from port mapper on shutdown, other changes to
      track actual ports better
      (ticket #731)
    - Hook in password manager using MD5, untested.
      (ticket #731)
This commit is contained in:
zzz
2012-10-13 13:06:22 +00:00
parent a475a912e6
commit 0b897fdc98
6 changed files with 101 additions and 93 deletions

View File

@ -260,7 +260,7 @@ public class ConfigClientsHandler extends FormHandler {
}
private void saveWebAppChanges() {
Properties props = RouterConsoleRunner.webAppProperties();
Properties props = RouterConsoleRunner.webAppProperties(_context);
Set keys = props.keySet();
for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
@ -271,7 +271,7 @@ public class ConfigClientsHandler extends FormHandler {
if (! RouterConsoleRunner.ROUTERCONSOLE.equals(app))
props.setProperty(name, "" + (val != null));
}
RouterConsoleRunner.storeWebAppProperties(props);
RouterConsoleRunner.storeWebAppProperties(_context, props);
addFormNotice(_("WebApp configuration saved."));
}

View File

@ -128,7 +128,7 @@ public class ConfigClientsHelper extends HelperBase {
StringBuilder buf = new StringBuilder(1024);
buf.append("<table>\n");
buf.append("<tr><th align=\"right\">" + _("WebApp") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Control") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
Properties props = RouterConsoleRunner.webAppProperties();
Properties props = RouterConsoleRunner.webAppProperties(_context);
Set<String> keys = new TreeSet(props.keySet());
for (Iterator<String> iter = keys.iterator(); iter.hasNext(); ) {
String name = iter.next();

View File

@ -8,6 +8,7 @@ import net.i2p.apps.systray.UrlLauncher;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.startup.ClientAppConfig;
import net.i2p.util.PortMapper;
import net.i2p.util.SystemVersion;
import net.i2p.util.VersionComparator;
@ -257,7 +258,9 @@ public class ConfigServiceHandler extends FormHandler {
}
// releases <= 0.6.5 deleted the entry completely
if (shouldLaunchBrowser && !found) {
ClientAppConfig ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser", "http://127.0.0.1:7657", 5, false);
int port = _context.portMapper().getPort(PortMapper.SVC_CONSOLE, RouterConsoleRunner.DEFAULT_LISTEN_PORT);
ClientAppConfig ca = new ClientAppConfig(UrlLauncher.class.getName(), "consoleBrowser",
"http://127.0.0.1:" + port + '/', 5, false);
clients.add(ca);
}
ClientAppConfig.writeClientAppConfig(_context, clients);

View File

@ -44,6 +44,7 @@ import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerCollection;
import org.mortbay.jetty.handler.RequestLogHandler;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.security.Credential.MD5;
import org.mortbay.jetty.security.DigestAuthenticator;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.Constraint;
@ -61,6 +62,7 @@ import org.mortbay.thread.concurrent.ThreadPool;
* Start the router console.
*/
public class RouterConsoleRunner {
private final RouterContext _context;
private static Server _server;
private String _listenPort;
private String _listenHost;
@ -69,14 +71,19 @@ public class RouterConsoleRunner {
private String _webAppsDir;
private static final String DEFAULT_WEBAPP_CONFIG_FILENAME = "webapps.config";
// Jetty Auth
private static final DigestAuthenticator authenticator = new DigestAuthenticator();
private static final String JETTY_REALM = "i2prouter";
private static final String JETTY_ROLE = "routerAdmin";
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";
public static final int 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! " +
@ -123,7 +130,7 @@ public class RouterConsoleRunner {
public RouterConsoleRunner(String args[]) {
if (args.length == 0) {
// _listenHost and _webAppsDir are defaulted below
_listenPort = DEFAULT_LISTEN_PORT;
_listenPort = Integer.toString(DEFAULT_LISTEN_PORT);
} else {
boolean ssl = false;
for (int i = 0; i < args.length; i++) {
@ -156,11 +163,15 @@ public class RouterConsoleRunner {
System.err.println(USAGE);
throw new IllegalArgumentException(USAGE);
}
List<RouterContext> contexts = RouterContext.listContexts();
if (contexts == null || contexts.isEmpty())
throw new IllegalStateException("no router context");
_context = contexts.get(0);
}
public static void main(String args[]) {
startTrayApp();
RouterConsoleRunner runner = new RouterConsoleRunner(args);
startTrayApp(runner._context);
runner.startConsole();
}
@ -173,14 +184,14 @@ public class RouterConsoleRunner {
return _server;
}
private static void startTrayApp() {
private static void startTrayApp(I2PAppContext ctx) {
try {
//TODO: move away from routerconsole into a separate application.
//ApplicationManager?
VersionComparator v = new VersionComparator();
boolean recentJava = v.compare(System.getProperty("java.runtime.version"), "1.6") >= 0;
// default false for now
boolean desktopguiEnabled = I2PAppContext.getGlobalContext().getBooleanProperty("desktopgui.enabled");
boolean desktopguiEnabled = ctx.getBooleanProperty("desktopgui.enabled");
if (recentJava && desktopguiEnabled) {
//Check if we are in a headless environment, set properties accordingly
System.setProperty("java.awt.headless", Boolean.toString(GraphicsEnvironment.isHeadless()));
@ -215,7 +226,7 @@ public class RouterConsoleRunner {
*</pre>
*/
public void startConsole() {
File workDir = new SecureDirectory(I2PAppContext.getGlobalContext().getTempDir(), "jetty-work");
File workDir = new SecureDirectory(_context.getTempDir(), "jetty-work");
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
if (!workDirRemoved)
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
@ -224,7 +235,7 @@ public class RouterConsoleRunner {
System.err.println("ERROR: Unable to create Jetty temporary work directory");
//try {
// Log.setLog(new I2PLogger(I2PAppContext.getGlobalContext()));
// Log.setLog(new I2PLogger(_context));
//} catch (Throwable t) {
// System.err.println("INFO: I2P Jetty logging class not found, logging to wrapper log");
//}
@ -232,7 +243,7 @@ public class RouterConsoleRunner {
System.setProperty("org.mortbay.log.class", "net.i2p.jetty.I2PLogger");
// so Jetty can find WebAppConfiguration
System.setProperty("jetty.class.path", I2PAppContext.getGlobalContext().getBaseDir() + "/lib/routerconsole.jar");
System.setProperty("jetty.class.path", _context.getBaseDir() + "/lib/routerconsole.jar");
_server = new Server();
_server.setGracefulShutdown(1000);
@ -255,11 +266,11 @@ public class RouterConsoleRunner {
hColl.addHandler(chColl);
hColl.addHandler(new DefaultHandler());
String log = I2PAppContext.getGlobalContext().getProperty("routerconsole.log");
String log = _context.getProperty("routerconsole.log");
if (log != null) {
File logFile = new File(log);
if (!logFile.isAbsolute())
logFile = new File(I2PAppContext.getGlobalContext().getLogDir(), "logs/" + log);
logFile = new File(_context.getLogDir(), "logs/" + log);
try {
RequestLogHandler rhl = new RequestLogHandler();
rhl.setRequestLog(new NCSARequestLog(logFile.getAbsolutePath()));
@ -279,7 +290,7 @@ public class RouterConsoleRunner {
// We assume relative to the base install dir for backward compatibility
File app = new File(_webAppsDir);
if (!app.isAbsolute()) {
app = new File(I2PAppContext.getGlobalContext().getBaseDir(), _webAppsDir);
app = new File(_context.getBaseDir(), _webAppsDir);
try {
_webAppsDir = app.getCanonicalPath();
} catch (IOException ioe) {}
@ -349,7 +360,7 @@ public class RouterConsoleRunner {
}
}
// XXX: what if listenhosts do not include 127.0.0.1? (Should that ever even happen?)
I2PAppContext.getGlobalContext().portMapper().register(PortMapper.SVC_CONSOLE,lport);
_context.portMapper().register(PortMapper.SVC_CONSOLE,lport);
}
// add SSL listeners
@ -362,8 +373,7 @@ public class RouterConsoleRunner {
System.err.println("Bad routerconsole SSL port " + _sslListenPort);
}
if (sslPort > 0) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
File keyStore = new File(ctx.getConfigDir(), "keystore/console.ks");
File keyStore = new File(_context.getConfigDir(), "keystore/console.ks");
if (verifyKeyStore(keyStore)) {
StringTokenizer tok = new StringTokenizer(_sslListenHost, " ,");
while (tok.hasMoreTokens()) {
@ -394,9 +404,9 @@ public class RouterConsoleRunner {
ssll.setPort(sslPort);
// the keystore path and password
ssll.setKeystore(keyStore.getAbsolutePath());
ssll.setPassword(ctx.getProperty(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD));
ssll.setPassword(_context.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"));
ssll.setKeyPassword(_context.getProperty(PROP_KEY_PASSWORD, "thisWontWork"));
ssll.setMaxIdleTime(90*1000); // default 10 sec
ssll.setName("ConsoleSocket"); // all with same name will use the same thread pool
ssll.setUseDirectBuffers(false); // default true seems to be leaky
@ -408,7 +418,7 @@ public class RouterConsoleRunner {
System.err.println("You may ignore this warning if the console is still available at https://localhost:" + sslPort);
}
}
I2PAppContext.getGlobalContext().portMapper().register(PortMapper.SVC_HTTPS_CONSOLE,sslPort);
_context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE,sslPort);
} else {
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
}
@ -419,7 +429,7 @@ public class RouterConsoleRunner {
return;
}
rootWebApp = new LocaleWebAppHandler(I2PAppContext.getGlobalContext(),
rootWebApp = new LocaleWebAppHandler(_context,
"/", _webAppsDir + ROUTERCONSOLE + ".war");
File tmpdir = new SecureDirectory(workDir, ROUTERCONSOLE + "-" +
(_listenPort != null ? _listenPort : _sslListenPort));
@ -429,7 +439,7 @@ public class RouterConsoleRunner {
rootWebApp.setSessionHandler(new SessionHandler());
rootServletHandler = new ServletHandler();
rootWebApp.setServletHandler(rootServletHandler);
initialize(rootWebApp);
initialize(_context, rootWebApp);
chColl.addHandler(rootWebApp);
} catch (Exception ioe) {
@ -466,7 +476,7 @@ public class RouterConsoleRunner {
}
if (error) {
System.err.println("WARNING: Error starting one or more listeners of the Router Console server.\n" +
"If your console is still accessible at http://127.0.0.1:7657/,\n" +
"If your console is still accessible at http://127.0.0.1:" + _listenPort + "/,\n" +
"this may be a problem only with binding to the IPV6 address ::1.\n" +
"If so, you may ignore this error, or remove the\n" +
"\"::1,\" in the \"clientApp.0.args\" line of the clients.config file.");
@ -488,7 +498,7 @@ public class RouterConsoleRunner {
if (! "false".equals(enabled)) {
try {
String path = new File(dir, fileNames[i]).getCanonicalPath();
WebAppStarter.startWebApp(I2PAppContext.getGlobalContext(), chColl, appName, path);
WebAppStarter.startWebApp(_context, chColl, appName, path);
if (enabled == null) {
// do this so configclients.jsp knows about all apps from reading the config
props.setProperty(PREFIX + appName + ENABLED, "true");
@ -509,7 +519,7 @@ public class RouterConsoleRunner {
}
if (rewrite)
storeWebAppProperties(props);
storeWebAppProperties(_context, props);
if (rootServletHandler != null && notStarted.size() > 0) {
// map each not-started webapp to the error page
@ -536,38 +546,32 @@ public class RouterConsoleRunner {
t.setPriority(Thread.NORM_PRIORITY - 1);
t.start();
List<RouterContext> contexts = RouterContext.listContexts();
if (contexts != null) {
RouterContext ctx = contexts.get(0);
NewsFetcher fetcher = NewsFetcher.getInstance(ctx);
NewsFetcher fetcher = NewsFetcher.getInstance(_context);
Thread newsThread = new I2PAppThread(fetcher, "NewsFetcher", true);
newsThread.setPriority(Thread.NORM_PRIORITY - 1);
newsThread.start();
if (PluginStarter.pluginsEnabled(ctx)) {
t = new I2PAppThread(new PluginStarter(ctx), "PluginStarter", true);
if (PluginStarter.pluginsEnabled(_context)) {
t = new I2PAppThread(new PluginStarter(_context), "PluginStarter", true);
t.setPriority(Thread.NORM_PRIORITY - 1);
t.start();
ctx.addShutdownTask(new PluginStopper(ctx));
_context.addShutdownTask(new PluginStopper(_context));
}
ctx.addShutdownTask(new NewsShutdown(fetcher, newsThread));
_context.addShutdownTask(new NewsShutdown(fetcher, newsThread));
// stat summarizer registers its own hook
ctx.addShutdownTask(new ServerShutdown());
ConfigServiceHandler.registerSignalHandler(ctx);
} // else log CRIT ?
_context.addShutdownTask(new ServerShutdown(_context));
ConfigServiceHandler.registerSignalHandler(_context);
}
/**
* @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) {
private boolean verifyKeyStore(File ks) {
if (ks.exists()) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
boolean rv = ctx.getProperty(PROP_KEY_PASSWORD) != null;
boolean rv = _context.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());
System.err.println("Console SSL error, must set " + PROP_KEY_PASSWORD + " in " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
return rv;
}
File dir = ks.getParentFile();
@ -588,14 +592,13 @@ public class RouterConsoleRunner {
* @return success
* @since 0.8.3
*/
private static boolean createKeyStore(File ks) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
private boolean createKeyStore(File ks) {
// make a random 48 character password (30 * 8 / 5)
byte[] rand = new byte[30];
ctx.random().nextBytes(rand);
_context.random().nextBytes(rand);
String keyPassword = Base32.encode(rand);
// and one for the cname
ctx.random().nextBytes(rand);
_context.random().nextBytes(rand);
String cname = Base32.encode(rand) + ".console.i2p.net";
String keytool = (new File(System.getProperty("java.home"), "bin/keytool")).getAbsolutePath();
@ -617,11 +620,10 @@ public class RouterConsoleRunner {
if (success) {
SecureFileOutputStream.setPerms(ks);
try {
RouterContext rctx = (RouterContext) ctx;
Map<String, String> changes = new HashMap();
changes.put(PROP_KEYSTORE_PASSWORD, DEFAULT_KEYSTORE_PASSWORD);
changes.put(PROP_KEY_PASSWORD, keyPassword);
rctx.router().saveConfig(changes, null);
_context.router().saveConfig(changes, null);
} catch (Exception e) {} // class cast exception
}
}
@ -638,27 +640,37 @@ public class RouterConsoleRunner {
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());
" to " + (new File(_context.getConfigDir(), "router.config")).getAbsolutePath());
}
return success;
}
static void initialize(WebAppContext context) {
/**
* Set up basic security constraints for the webapp.
* Add all users and passwords.
*/
static void initialize(RouterContext ctx, WebAppContext context) {
SecurityHandler sec = new SecurityHandler();
List<ConstraintMapping> constraints = new ArrayList(4);
String password = getPassword();
if (password != null) {
HashUserRealm realm = new HashUserRealm("i2prouter");
realm.put("admin", password);
realm.addUserToRole("admin", "routerAdmin");
ConsolePasswordManager mgr = new ConsolePasswordManager(ctx);
mgr.migrateConsole();
Map<String, String> userpw = mgr.getMD5(PasswordManager.PROP_CONSOLE_NEW);
if (!userpw.isEmpty()) {
HashUserRealm realm = new HashUserRealm(JETTY_REALM);
sec.setUserRealm(realm);
sec.setAuthenticator(authenticator);
Constraint constraint = new Constraint("admin", "routerAdmin");
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/");
constraints.add(cm);
for (Map.Entry<String, String> e : userpw.entrySet()) {
String user = e.getKey();
String pw = e.getValue();
realm.put(user, MD5.__TYPE + pw);
realm.addUserToRole(user, JETTY_ROLE);
Constraint constraint = new Constraint(user, JETTY_ROLE);
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/");
constraints.add(cm);
}
}
// This forces a '403 Forbidden' response for TRACE and OPTIONS unless the
@ -693,33 +705,21 @@ public class RouterConsoleRunner {
context.setSecurityHandler(sec);
}
static String getPassword() {
List<RouterContext> contexts = RouterContext.listContexts();
if (contexts != null) {
for (int i = 0; i < contexts.size(); i++) {
RouterContext ctx = contexts.get(i);
String password = ctx.getProperty("consolePassword");
if (password != null) {
password = password.trim();
if (password.length() > 0) {
return password;
}
}
}
// no password in any context
return null;
} else {
// no contexts?!
return null;
}
}
/** @since 0.8.8 */
private static class ServerShutdown implements Runnable {
private final I2PAppContext _ctx;
public ServerShutdown(I2PAppContext ctx) {
_ctx = ctx;
}
public void run() {
try {
_server.stop();
} catch (Exception ie) {}
PortMapper portMapper = _ctx.portMapper();
portMapper.unregister(PortMapper.SVC_CONSOLE);
portMapper.unregister(PortMapper.SVC_HTTPS_CONSOLE);
}
}
@ -739,13 +739,18 @@ public class RouterConsoleRunner {
}
}
public static Properties webAppProperties() {
return webAppProperties(I2PAppContext.getGlobalContext().getConfigDir().getAbsolutePath());
private Properties webAppProperties() {
return webAppProperties(_context.getConfigDir().getAbsolutePath());
}
/** @since 0.9.4 */
public static Properties webAppProperties(I2PAppContext ctx) {
return webAppProperties(ctx.getConfigDir().getAbsolutePath());
}
public static Properties webAppProperties(String dir) {
Properties rv = new Properties();
// String webappConfigFile = ctx.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
// String webappConfigFile = _context.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
String webappConfigFile = DEFAULT_WEBAPP_CONFIG_FILENAME;
File cfgFile = new File(dir, webappConfigFile);
@ -758,10 +763,10 @@ public class RouterConsoleRunner {
return rv;
}
public static void storeWebAppProperties(Properties props) {
// String webappConfigFile = ctx.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
public static void storeWebAppProperties(RouterContext ctx, Properties props) {
// String webappConfigFile = _context.getProperty(PROP_WEBAPP_CONFIG_FILENAME, DEFAULT_WEBAPP_CONFIG_FILENAME);
String webappConfigFile = DEFAULT_WEBAPP_CONFIG_FILENAME;
File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), webappConfigFile);
File cfgFile = new File(ctx.getConfigDir(), webappConfigFile);
try {
DataHelper.storeProps(props, cfgFile);

View File

@ -74,7 +74,7 @@ public class WebAppConfiguration implements Configuration {
cp = props.getProperty(RouterConsoleRunner.PREFIX + appName + CLASSPATH);
dir = pluginDir;
} else {
Properties props = RouterConsoleRunner.webAppProperties();
Properties props = RouterConsoleRunner.webAppProperties(i2pContext);
cp = props.getProperty(RouterConsoleRunner.PREFIX + appName + CLASSPATH);
}
if (cp == null)

View File

@ -6,7 +6,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.FileUtil;
import net.i2p.util.SecureDirectory;
@ -51,7 +51,7 @@ public class WebAppStarter {
* adds and starts
* @throws just about anything, caller would be wise to catch Throwable
*/
static void startWebApp(I2PAppContext ctx, ContextHandlerCollection server,
static void startWebApp(RouterContext ctx, ContextHandlerCollection server,
String appName, String warPath) throws Exception {
File tmpdir = new SecureDirectory(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt());
WebAppContext wac = addWebApp(ctx, server, appName, warPath, tmpdir);
@ -65,7 +65,7 @@ public class WebAppStarter {
* This is used only by RouterConsoleRunner, which adds all the webapps first
* and then starts all at once.
*/
static WebAppContext addWebApp(I2PAppContext ctx, ContextHandlerCollection server,
static WebAppContext addWebApp(RouterContext ctx, ContextHandlerCollection server,
String appName, String warPath, File tmpdir) throws IOException {
// Jetty will happily load one context on top of another without stopping
@ -103,7 +103,7 @@ public class WebAppStarter {
wac.setExtractWAR(false);
// this does the passwords...
RouterConsoleRunner.initialize(wac);
RouterConsoleRunner.initialize(ctx, wac);
// see WebAppConfiguration for info
String[] classNames = wac.getConfigurationClasses();