Files
i2p.i2p/apps/i2pcontrol/java/net/i2p/i2pcontrol/I2PControlController.java
zzz 24fd48815a I2PControl:
Disable webapp by default
Add link in Services section of sidebar
Add definition in PortMapper
Add stub controller for socket implementation, WIP
Javadocs
2019-01-28 13:31:54 +00:00

407 lines
14 KiB
Java

package net.i2p.i2pcontrol;
/*
* Copyright 2010 hottuna (dev@robertfoss.se)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import net.i2p.I2PAppContext;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.router.RouterContext;
import net.i2p.router.app.RouterApp;
import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.Log;
import net.i2p.util.PortMapper;
import net.i2p.i2pcontrol.security.KeyStoreProvider;
import net.i2p.i2pcontrol.security.SecurityManager;
import net.i2p.i2pcontrol.servlets.JSONRPC2Servlet;
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
/**
* This handles the starting and stopping of Jetty
* from a single static class so it can be called via clients.config.
*
* This makes installation of a new eepsite a turnkey operation.
*
* Usage: I2PControlController -d $PLUGIN [start|stop]
*
* This class is NOT used for the webapp or the bare ServerSocket implementation.
*
* @author hottuna
*/
public class I2PControlController implements RouterApp {
// non-null
private final I2PAppContext _appContext;
// warning, null in app context
private final RouterContext _context;
private final ClientAppManager _mgr;
private final Log _log;
private final String _pluginDir;
private final ConfigurationManager _conf;
private final KeyStoreProvider _ksp;
private final SecurityManager _secMan;
private final Server _server;
private ClientAppState _state = UNINITIALIZED;
// only for main()
private static I2PControlController _instance;
static final String PROP_ALLOWED_HOSTS = "i2pcontrol.allowedhosts";
private static final String SVC_HTTPS_I2PCONTROL = "https_i2pcontrol";
private static final int DEFAULT_PORT = 7650;
/**
* RouterApp (new way)
*/
public I2PControlController(RouterContext ctx, ClientAppManager mgr, String args[]) {
_appContext = _context = ctx;
_mgr = mgr;
_log = _appContext.logManager().getLog(I2PControlController.class);
File pluginDir = new File(_context.getAppDir(), "plugins/I2PControl");
_pluginDir = pluginDir.getAbsolutePath();
_conf = new ConfigurationManager(_appContext, pluginDir, true);
_ksp = new KeyStoreProvider(_pluginDir);
_secMan = new SecurityManager(_appContext, _ksp, _conf);
_server = buildServer();
_state = INITIALIZED;
}
/**
* From main() (old way)
*/
public I2PControlController(File pluginDir) {
_appContext = I2PAppContext.getGlobalContext();
if (_appContext instanceof RouterContext)
_context = (RouterContext) _appContext;
else
_context = null;
_mgr = null;
_log = _appContext.logManager().getLog(I2PControlController.class);
_pluginDir = pluginDir.getAbsolutePath();
_conf = new ConfigurationManager(_appContext, pluginDir, true);
_ksp = new KeyStoreProvider(_pluginDir);
_secMan = new SecurityManager(_appContext, _ksp, _conf);
_server = buildServer();
_state = INITIALIZED;
}
/////// ClientApp methods
public synchronized void startup() {
changeState(STARTING);
try {
start(null);
changeState(RUNNING);
} catch (Exception e) {
changeState(START_FAILED, "Failed to start", e);
_log.error("Unable to start jetty server", e);
stop();
}
}
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
return;
changeState(STOPPING);
stop();
changeState(STOPPED);
}
public synchronized ClientAppState getState() {
return _state;
}
public String getName() {
return "I2PControl";
}
public String getDisplayName() {
return "I2PControl";
}
/////// end ClientApp methods
private void changeState(ClientAppState state) {
changeState(state, null, null);
}
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, msg, e);
if (_context == null) {
if (msg != null)
System.out.println(state + ": " + msg);
if (e != null)
e.printStackTrace();
}
}
/**
* Deprecated, use constructor
*/
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGINDIR [start|stop]");
if ("start".equals(args[2])) {
File pluginDir = new File(args[1]);
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
synchronized(I2PControlController.class) {
if (_instance != null)
throw new IllegalStateException();
I2PControlController i2pcc = new I2PControlController(pluginDir);
try {
i2pcc.startup();
_instance = i2pcc;
} catch (Exception e) {
e.printStackTrace();
}
}
} else if ("stop".equals(args[2])) {
synchronized(I2PControlController.class) {
if (_instance != null) {
_instance.shutdown(null);
_instance = null;
}
}
} else {
throw new IllegalArgumentException("Usage: PluginController -d $PLUGINDIR [start|stop]");
}
}
private synchronized void start(String args[]) throws Exception {
_appContext.logManager().getLog(JSONRPC2Servlet.class).setMinimumPriority(Log.DEBUG);
_server.start();
_context.portMapper().register(SVC_HTTPS_I2PCONTROL,
_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"),
_conf.getConf("i2pcontrol.listen.port", DEFAULT_PORT));
}
/**
* Builds a new server. Used for changing ports during operation and such.
* @return Server - A new server built from current configuration.
*/
private Connector buildDefaultListener(Server server) {
Connector ssl = buildSslListener(server, _conf.getConf("i2pcontrol.listen.address", "127.0.0.1"),
_conf.getConf("i2pcontrol.listen.port", DEFAULT_PORT));
return ssl;
}
/**
* Builds a new server. Used for changing ports during operation and such.
*
* Does NOT start the server. Must call start() on the returned server.
*
* @return Server - A new server built from current configuration.
*/
public Server buildServer() {
Server server = new Server();
Connector ssl = buildDefaultListener(server);
server.addConnector(ssl);
ServletHandler sh = new ServletHandler();
sh.addServletWithMapping(new ServletHolder(new JSONRPC2Servlet(_context, _secMan)), "/");
HostCheckHandler hch = new HostCheckHandler(_appContext);
Set<String> listenHosts = new HashSet<String>(8);
// fix up the allowed hosts set (see HostCheckHandler)
// empty set says all are valid
String address = _conf.getConf("i2pcontrol.listen.address", "127.0.0.1");
if (!(address.equals("0.0.0.0") ||
address.equals("::") ||
address.equals("0:0:0:0:0:0:0:0"))) {
listenHosts.add("localhost");
listenHosts.add("127.0.0.1");
listenHosts.add("::1");
listenHosts.add("0:0:0:0:0:0:0:1");
String allowed = _conf.getConf(PROP_ALLOWED_HOSTS, "");
if (!allowed.equals("")) {
StringTokenizer tok = new StringTokenizer(allowed, " ,");
while (tok.hasMoreTokens()) {
listenHosts.add(tok.nextToken());
}
}
}
hch.setListenHosts(listenHosts);
hch.setHandler(sh);
server.getServer().setHandler(hch);
_conf.writeConfFile();
return server;
}
/**
* Creates a SSLListener with all the default options. The listener will use all the default options.
* @param address - The address the listener will listen to.
* @param port - The port the listener will listen to.
* @return - Newly created listener
*/
private Connector buildSslListener(Server server, String address, int port) {
int listeners = 0;
if (server != null) {
listeners = server.getConnectors().length;
}
// the keystore path and password
SslContextFactory sslFactory = new SslContextFactory(_ksp.getKeyStoreLocation());
sslFactory.setKeyStorePassword(KeyStoreProvider.DEFAULT_KEYSTORE_PASSWORD);
// the X.509 cert password (if not present, verifyKeyStore() returned false)
sslFactory.setKeyManagerPassword(KeyStoreProvider.DEFAULT_CERTIFICATE_PASSWORD);
sslFactory.addExcludeProtocols(I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.toArray(
new String[I2PSSLSocketFactory.EXCLUDE_PROTOCOLS.size()]));
sslFactory.addExcludeCipherSuites(I2PSSLSocketFactory.EXCLUDE_CIPHERS.toArray(
new String[I2PSSLSocketFactory.EXCLUDE_CIPHERS.size()]));
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(port);
httpConfig.addCustomizer(new SecureRequestCustomizer());
// number of acceptors, (default) number of selectors
ServerConnector ssl = new ServerConnector(server, 1, 0,
new SslConnectionFactory(sslFactory, "http/1.1"),
new HttpConnectionFactory(httpConfig));
ssl.setHost(address);
ssl.setPort(port);
ssl.setIdleTimeout(90*1000); // default 10 sec
// all with same name will use the same thread pool
ssl.setName("I2PControl");
ssl.setName("SSL Listener-" + ++listeners);
return ssl;
}
/**
* Add a listener to the server
* If a listener listening to the same port as the provided listener
* uses already exists within the server, replace the one already used by
* the server with the provided listener.
* @param listener
* @throws Exception
*/
/****
public synchronized void replaceListener(Connector listener) throws Exception {
if (_server != null) {
stopServer();
}
_server = buildServer(listener);
}
****/
/**
* Get all listeners of the server.
* @return
*/
/****
public synchronized Connector[] getListeners() {
if (_server != null) {
return _server.getConnectors();
}
return new Connector[0];
}
****/
/**
* Removes all listeners
*/
/****
public synchronized void clearListeners() {
if (_server != null) {
for (Connector listen : getListeners()) {
_server.removeConnector(listen);
}
}
}
****/
/**
* Stop it
*/
private synchronized void stopServer()
{
try {
if (_server != null) {
_appContext.portMapper().unregister(SVC_HTTPS_I2PCONTROL);
_server.stop();
for (Connector listener : _server.getConnectors()) {
listener.stop();
}
_server.destroy();
}
} catch (Exception e) {
_log.error("Stopping server", e);
}
}
private synchronized void stop() {
_conf.writeConfFile();
_secMan.stopTimedEvents();
stopServer();
/****
// Get and stop all running threads
ThreadGroup threadgroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[threadgroup.activeCount() + 3];
threadgroup.enumerate(threads, true);
for (Thread thread : threads) {
if (thread != null) {//&& thread.isAlive()){
thread.interrupt();
}
}
for (Thread thread : threads) {
if (thread != null) {
System.out.println("Active thread: " + thread.getName());
}
}
threadgroup.interrupt();
//Thread.currentThread().getThreadGroup().destroy();
****/
}
public String getPluginDir() {
return _pluginDir;
}
}