forked from I2P_Developers/i2p.i2p
Bundle I2PControl 0.12, as a console webapp
Includes mods to use org.json.simple package. See licenses/LICENSE-Apache2.0.txt Includes jBCrypt: Copyright (c) 2006 Damien Miller <djm@mindrot.org> See licenses/LICENSE-jBCrypt.txt Includes jsonrpc2 libs: See licenses/LICENSE-Apache2.0.txt http://software.dzhuvinov.com/json-rpc-2.0-server.html Jars from maven central: jsonrpc2-base-1.38.1-sources.jar 22-Oct-2017 jsonrpc2-server-1.11-sources.jar 16-Mar-2015
This commit is contained in:
127
apps/i2pcontrol/java/net/i2p/i2pcontrol/HostCheckHandler.java
Normal file
127
apps/i2pcontrol/java/net/i2p/i2pcontrol/HostCheckHandler.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package net.i2p.i2pcontrol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
|
||||
/**
|
||||
* Block certain Host headers to prevent DNS rebinding attacks.
|
||||
*
|
||||
* This Handler wraps the ContextHandlerCollection, which handles
|
||||
* all the webapps (not just routerconsole).
|
||||
* Therefore, this protects all the webapps.
|
||||
*
|
||||
* @since 0.12 copied from routerconsole
|
||||
*/
|
||||
public class HostCheckHandler extends HandlerWrapper
|
||||
{
|
||||
private final I2PAppContext _context;
|
||||
private final Set<String> _listenHosts;
|
||||
|
||||
/**
|
||||
* MUST call setListenHosts() afterwards.
|
||||
*/
|
||||
public HostCheckHandler(I2PAppContext ctx) {
|
||||
super();
|
||||
_context = ctx;
|
||||
_listenHosts = new HashSet<String>(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the legal hosts.
|
||||
* Not synched. Call this BEFORE starting.
|
||||
* If empty, all are allowed.
|
||||
*
|
||||
* @param hosts contains hostnames or IPs. But we allow all IPs anyway.
|
||||
*/
|
||||
public void setListenHosts(Set<String> hosts) {
|
||||
_listenHosts.clear();
|
||||
_listenHosts.addAll(hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block by Host header, pass everything else to the delegate.
|
||||
*/
|
||||
public void handle(String pathInContext,
|
||||
Request baseRequest,
|
||||
HttpServletRequest httpRequest,
|
||||
HttpServletResponse httpResponse)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
|
||||
String host = httpRequest.getHeader("Host");
|
||||
if (!allowHost(host)) {
|
||||
Log log = _context.logManager().getLog(HostCheckHandler.class);
|
||||
host = DataHelper.stripHTML(getHost(host));
|
||||
String s = "Console request denied.\n" +
|
||||
" To allow access using the hostname \"" + host + "\", add the line \"" +
|
||||
I2PControlController.PROP_ALLOWED_HOSTS + '=' + host +
|
||||
"\" to I2PControl.conf and restart.";
|
||||
log.logAlways(Log.WARN, s);
|
||||
httpResponse.sendError(403, s);
|
||||
return;
|
||||
}
|
||||
|
||||
super.handle(pathInContext, baseRequest, httpRequest, httpResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we allow a request with this Host header?
|
||||
*
|
||||
* ref: https://en.wikipedia.org/wiki/DNS_rebinding
|
||||
*
|
||||
* @param host the HTTP Host header, null ok
|
||||
* @return true if OK
|
||||
*/
|
||||
private boolean allowHost(String host) {
|
||||
if (host == null)
|
||||
return true;
|
||||
// common cases
|
||||
if (host.equals("127.0.0.1:7650") ||
|
||||
host.equals("localhost:7650"))
|
||||
return true;
|
||||
// all allowed?
|
||||
if (_listenHosts.isEmpty())
|
||||
return true;
|
||||
host = getHost(host);
|
||||
if (_listenHosts.contains(host))
|
||||
return true;
|
||||
// allow all IP addresses
|
||||
if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host))
|
||||
return true;
|
||||
//System.out.println(host + " not found in " + s);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip [] and port from a host header
|
||||
*
|
||||
* @param host the HTTP Host header non-null
|
||||
*/
|
||||
private static String getHost(String host) {
|
||||
if (host.startsWith("[")) {
|
||||
host = host.substring(1);
|
||||
int brack = host.indexOf(']');
|
||||
if (brack >= 0)
|
||||
host = host.substring(0, brack);
|
||||
} else {
|
||||
int colon = host.indexOf(':');
|
||||
if (colon >= 0)
|
||||
host = host.substring(0, colon);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
}
|
@@ -0,0 +1,403 @@
|
||||
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]
|
||||
*
|
||||
* @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";
|
||||
|
||||
/**
|
||||
* 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", 7650));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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", 7650));
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package net.i2p.i2pcontrol;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class I2PControlVersion {
|
||||
/** The current version of I2PControl */
|
||||
public final static String VERSION = "0.12.0";
|
||||
|
||||
/** The current version of the I2PControl API being primarily being implemented */
|
||||
public final static int API_VERSION = 1;
|
||||
|
||||
/** The supported versions of the I2PControl API */
|
||||
public final static Set<Integer> SUPPORTED_API_VERSIONS;
|
||||
|
||||
static {
|
||||
SUPPORTED_API_VERSIONS = new HashSet<Integer>();
|
||||
SUPPORTED_API_VERSIONS.add(1);
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package net.i2p.i2pcontrol.security;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
public class AuthToken {
|
||||
static final int VALIDITY_TIME = 1; // Measured in days
|
||||
private final SecurityManager _secMan;
|
||||
private final String id;
|
||||
private final Date expiry;
|
||||
|
||||
public AuthToken(SecurityManager secMan, String password) {
|
||||
_secMan = secMan;
|
||||
String hash = _secMan.getPasswdHash(password);
|
||||
this.id = _secMan.getHash(hash + Calendar.getInstance().getTimeInMillis());
|
||||
Calendar expiry = Calendar.getInstance();
|
||||
expiry.add(Calendar.DAY_OF_YEAR, VALIDITY_TIME);
|
||||
this.expiry = expiry.getTime();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the AuthToken has expired.
|
||||
* @return True if AuthToken hasn't expired. False in any other case.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return Calendar.getInstance().getTime().before(expiry);
|
||||
}
|
||||
|
||||
public String getExpiryTime() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package net.i2p.i2pcontrol.security;
|
||||
|
||||
public class ExpiredAuthTokenException extends Exception {
|
||||
private static final long serialVersionUID = 2279019346592900289L;
|
||||
|
||||
private String expiryTime;
|
||||
|
||||
public ExpiredAuthTokenException(String str, String expiryTime) {
|
||||
super(str);
|
||||
this.expiryTime = expiryTime;
|
||||
}
|
||||
|
||||
public String getExpirytime() {
|
||||
return expiryTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package net.i2p.i2pcontrol.security;
|
||||
|
||||
public class InvalidAuthTokenException extends Exception {
|
||||
private static final long serialVersionUID = 7605321329341235577L;
|
||||
|
||||
public InvalidAuthTokenException(String str) {
|
||||
super(str);
|
||||
}
|
||||
}
|
@@ -0,0 +1,218 @@
|
||||
package net.i2p.i2pcontrol.security;
|
||||
|
||||
import net.i2p.crypto.KeyStoreUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public class KeyStoreProvider {
|
||||
public static final String DEFAULT_CERTIFICATE_ALGORITHM_STRING = "RSA";
|
||||
public static final int DEFAULT_CERTIFICATE_KEY_LENGTH = 4096;
|
||||
public static final int DEFAULT_CERTIFICATE_VALIDITY = 365 * 10;
|
||||
public final static String DEFAULT_CERTIFICATE_DOMAIN = "localhost";
|
||||
public final static String DEFAULT_CERTIFICATE_ALIAS = "I2PControl CA";
|
||||
public static final String DEFAULT_KEYSTORE_NAME = "i2pcontrol.ks";
|
||||
public static final String DEFAULT_KEYSTORE_PASSWORD = KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD;
|
||||
public static final String DEFAULT_CERTIFICATE_PASSWORD = "nut'nfancy";
|
||||
private final String _pluginDir;
|
||||
private KeyStore _keystore;
|
||||
|
||||
public KeyStoreProvider(String pluginDir) {
|
||||
_pluginDir = pluginDir;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
KeyStoreUtil.createKeys(new File(getKeyStoreLocation()),
|
||||
DEFAULT_KEYSTORE_PASSWORD,
|
||||
DEFAULT_CERTIFICATE_ALIAS,
|
||||
DEFAULT_CERTIFICATE_DOMAIN,
|
||||
"i2pcontrol",
|
||||
DEFAULT_CERTIFICATE_VALIDITY,
|
||||
DEFAULT_CERTIFICATE_ALGORITHM_STRING,
|
||||
DEFAULT_CERTIFICATE_KEY_LENGTH,
|
||||
DEFAULT_CERTIFICATE_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password unused
|
||||
* @return null on failure
|
||||
*/
|
||||
public static X509Certificate readCert(KeyStore ks, String certAlias, String password) {
|
||||
try {
|
||||
X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias);
|
||||
|
||||
if (cert == null) {
|
||||
throw new RuntimeException("Got null cert from keystore!");
|
||||
}
|
||||
|
||||
try {
|
||||
cert.verify(cert.getPublicKey());
|
||||
return cert;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to verify caCert certificate against caCert");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password for the keystore
|
||||
* @return null on failure
|
||||
*/
|
||||
/****
|
||||
public static X509Certificate readCert(File keyStoreFile, String certAlias, String password) {
|
||||
try {
|
||||
KeyStore ks = getDefaultKeyStore();
|
||||
ks.load(new FileInputStream(keyStoreFile), password.toCharArray());
|
||||
X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias);
|
||||
|
||||
if (cert == null) {
|
||||
throw new RuntimeException("Got null cert from keystore!");
|
||||
}
|
||||
|
||||
try {
|
||||
cert.verify(cert.getPublicKey());
|
||||
return cert;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to verify caCert certificate against caCert");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Couldn't read keystore from: " + keyStoreFile.toString());
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (CertificateException e) {
|
||||
e.printStackTrace();
|
||||
} catch (KeyStoreException e) {
|
||||
System.err.println("No certificate with alias: " + certAlias + " found.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* @param password for the key
|
||||
* @return null on failure, or throws RuntimeException...
|
||||
*/
|
||||
/****
|
||||
public static PrivateKey readPrivateKey(KeyStore ks, String alias, String password) {
|
||||
try {
|
||||
// load the key entry from the keystore
|
||||
Key key = ks.getKey(alias, password.toCharArray());
|
||||
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Got null key from keystore!");
|
||||
}
|
||||
|
||||
PrivateKey privKey = (PrivateKey) key;
|
||||
return privKey;
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* @return null on failure
|
||||
*/
|
||||
/****
|
||||
public static PrivateKey readPrivateKey(String alias, File keyStoreFile, String keyStorePassword, String keyPassword) {
|
||||
try {
|
||||
KeyStore ks = getDefaultKeyStore();
|
||||
ks.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
|
||||
return readPrivateKey(ks, alias, keyStorePassword);
|
||||
} catch (CertificateException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Couldn't read keystore from: " + keyStoreFile.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* @return null on failure
|
||||
*/
|
||||
/****
|
||||
public static KeyStore writeCACertToKeyStore(KeyStore keyStore, String keyPassword, String alias, PrivateKey caPrivKey, X509Certificate caCert) {
|
||||
try {
|
||||
X509Certificate[] chain = new X509Certificate[1];
|
||||
chain[0] = caCert;
|
||||
|
||||
keyStore.setKeyEntry(alias, caPrivKey, keyPassword.toCharArray(), chain);
|
||||
File keyStoreFile = new File(I2PControlController.getPluginDir() + File.separator + DEFAULT_KEYSTORE_NAME);
|
||||
keyStore.store(new FileOutputStream(keyStoreFile), DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||
return keyStore;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (CertificateException e) {
|
||||
e.printStackTrace();
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* @return null on failure
|
||||
*/
|
||||
public synchronized KeyStore getDefaultKeyStore() {
|
||||
if (_keystore == null) {
|
||||
File keyStoreFile = new File(getKeyStoreLocation());
|
||||
|
||||
try {
|
||||
_keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
if (keyStoreFile.exists()) {
|
||||
InputStream is = new FileInputStream(keyStoreFile);
|
||||
_keystore.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||
return _keystore;
|
||||
}
|
||||
|
||||
initialize();
|
||||
if (keyStoreFile.exists()) {
|
||||
InputStream is = new FileInputStream(keyStoreFile);
|
||||
_keystore.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
|
||||
return _keystore;
|
||||
} else {
|
||||
throw new IOException("KeyStore file " + keyStoreFile.getAbsolutePath() + " wasn't readable");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore. Not an issue. Let's just create a new keystore instead.
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return _keystore;
|
||||
}
|
||||
}
|
||||
|
||||
public String getKeyStoreLocation() {
|
||||
File keyStoreFile = new File(_pluginDir, DEFAULT_KEYSTORE_NAME);
|
||||
return keyStoreFile.getAbsolutePath();
|
||||
}
|
||||
}
|
@@ -0,0 +1,252 @@
|
||||
package net.i2p.i2pcontrol.security;
|
||||
/*
|
||||
* Copyright 2011 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.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Manage the password storing for I2PControl.
|
||||
*/
|
||||
public class SecurityManager {
|
||||
public final static String DEFAULT_AUTH_PASSWORD = "itoopie";
|
||||
private final HashMap<String, AuthToken> authTokens;
|
||||
private final SimpleTimer2.TimedEvent timer;
|
||||
private final KeyStore _ks;
|
||||
private final Log _log;
|
||||
private final ConfigurationManager _conf;
|
||||
private final I2PAppContext _context;
|
||||
|
||||
/**
|
||||
* @param ksp may be null (if webapp)
|
||||
*/
|
||||
public SecurityManager(I2PAppContext ctx, KeyStoreProvider ksp, ConfigurationManager conf) {
|
||||
_context = ctx;
|
||||
_conf = conf;
|
||||
_log = ctx.logManager().getLog(SecurityManager.class);
|
||||
authTokens = new HashMap<String, AuthToken>();
|
||||
|
||||
timer = new Sweeper();
|
||||
|
||||
_ks = ksp != null ? ksp.getDefaultKeyStore() : null;
|
||||
}
|
||||
|
||||
public void stopTimedEvents() {
|
||||
timer.cancel();
|
||||
synchronized (authTokens) {
|
||||
authTokens.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the X509Certificate of the server as a Base64 encoded string.
|
||||
* @return base64 encode of X509Certificate
|
||||
*/
|
||||
/**** unused and incorrectly uses I2P Base64. Switch to CertUtil.exportCert() if needed.
|
||||
public String getBase64Cert() {
|
||||
X509Certificate caCert = KeyStoreProvider.readCert(_ks,
|
||||
KeyStoreProvider.DEFAULT_CERTIFICATE_ALIAS,
|
||||
KeyStoreProvider.DEFAULT_KEYSTORE_PASSWORD);
|
||||
return getBase64FromCert(caCert);
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Return the X509Certificate as a base64 encoded string.
|
||||
* @param cert
|
||||
* @return base64 encode of X509Certificate
|
||||
*/
|
||||
/**** unused and incorrectly uses I2P Base64. Switch to CertUtil.exportCert() if needed.
|
||||
private static String getBase64FromCert(X509Certificate cert) {
|
||||
try {
|
||||
return Base64.encode(cert.getEncoded());
|
||||
} catch (CertificateEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
****/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Hash pwd with using BCrypt with the default salt.
|
||||
* @param pwd
|
||||
* @return BCrypt hash of salt and input string
|
||||
*/
|
||||
public String getPasswdHash(String pwd) {
|
||||
String salt;
|
||||
synchronized(_conf) {
|
||||
salt = _conf.getConf("auth.salt", "");
|
||||
if (salt.equals("")) {
|
||||
salt = BCrypt.gensalt(10, _context.random());
|
||||
_conf.setConf("auth.salt", salt);
|
||||
_conf.writeConfFile();
|
||||
}
|
||||
}
|
||||
return BCrypt.hashpw(pwd, salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved password hash. Stores if not previously set.
|
||||
* @return BCrypt hash of salt and password
|
||||
* @since 0.12
|
||||
*/
|
||||
private String getSavedPasswdHash() {
|
||||
String pw;
|
||||
synchronized(_conf) {
|
||||
pw = _conf.getConf("auth.password", "");
|
||||
if (pw.equals("")) {
|
||||
pw = getPasswdHash(DEFAULT_AUTH_PASSWORD);
|
||||
_conf.setConf("auth.password", pw);
|
||||
_conf.writeConfFile();
|
||||
}
|
||||
}
|
||||
return pw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash input one time with SHA-256, return Base64 encdoded string.
|
||||
* @param string
|
||||
* @return Base64 encoded string
|
||||
*/
|
||||
public String getHash(String string) {
|
||||
SHA256Generator hashGen = _context.sha();
|
||||
byte[] bytes = string.getBytes();
|
||||
bytes = hashGen.calculateHash(bytes).toByteArray();
|
||||
return Base64.encode(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this password correct?
|
||||
* @return true if password is valid.
|
||||
* @since 0.12
|
||||
*/
|
||||
public boolean isValid(String pwd) {
|
||||
String storedPass = getSavedPasswdHash();
|
||||
byte[] p1 = DataHelper.getASCII(getPasswdHash(pwd));
|
||||
byte[] p2 = DataHelper.getASCII(storedPass);
|
||||
return p1.length == p2.length && DataHelper.eqCT(p1, 0, p2, 0, p1.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this password correct?
|
||||
* @return true if password is valid.
|
||||
* @since 0.12
|
||||
*/
|
||||
public boolean isDefaultPasswordValid() {
|
||||
return isValid(DEFAULT_AUTH_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Authentication Token if the provided password is valid.
|
||||
* The token will be valid for one day.
|
||||
* @return AuthToken if password is valid. If password is invalid null will be returned.
|
||||
*/
|
||||
public AuthToken validatePasswd(String pwd) {
|
||||
if (isValid(pwd)) {
|
||||
AuthToken token = new AuthToken(this, pwd);
|
||||
synchronized (authTokens) {
|
||||
authTokens.put(token.getId(), token);
|
||||
}
|
||||
return token;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new password. Old tokens will NOT remain valid, to encourage the new password being tested.
|
||||
* @param newPasswd
|
||||
* @return Returns true if a new password was set.
|
||||
*/
|
||||
public boolean setPasswd(String newPasswd) {
|
||||
String newHash = getPasswdHash(newPasswd);
|
||||
String oldHash = getSavedPasswdHash();
|
||||
|
||||
if (!newHash.equals(oldHash)) {
|
||||
_conf.setConf("auth.password", newHash);
|
||||
_conf.writeConfFile();
|
||||
synchronized (authTokens) {
|
||||
authTokens.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the AuthToken with the given ID exists and if it does whether is has expired.
|
||||
* @param tokenID - The token to validate
|
||||
* @throws InvalidAuthTokenException
|
||||
* @throws ExpiredAuthTokenException
|
||||
*/
|
||||
public void verifyToken(String tokenID) throws InvalidAuthTokenException, ExpiredAuthTokenException {
|
||||
synchronized (authTokens) {
|
||||
AuthToken token = authTokens.get(tokenID);
|
||||
if (token == null)
|
||||
throw new InvalidAuthTokenException("AuthToken with ID: " + tokenID + " couldn't be found.");
|
||||
if (!token.isValid()) {
|
||||
authTokens.remove(tokenID);
|
||||
throw new ExpiredAuthTokenException("AuthToken with ID: " + tokenID + " expired " + token.getExpiryTime(), token.getExpiryTime());
|
||||
}
|
||||
}
|
||||
// Everything is fine. :)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old authorization tokens to keep the token store slim and fit.
|
||||
* @author hottuna
|
||||
*
|
||||
*/
|
||||
private class Sweeper extends SimpleTimer2.TimedEvent {
|
||||
// Start running periodic task after 1 day, run periodically every 30 minutes.
|
||||
public Sweeper() {
|
||||
super(_context.simpleTimer2(), AuthToken.VALIDITY_TIME * 24*60*60*1000L);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
_log.debug("Starting cleanup job..");
|
||||
synchronized (authTokens) {
|
||||
for (Iterator<AuthToken> iter = authTokens.values().iterator(); iter.hasNext(); ) {
|
||||
AuthToken token = iter.next();
|
||||
if (!token.isValid())
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
_log.debug("Cleanup job done.");
|
||||
schedule(30*60*1000L);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,245 @@
|
||||
package net.i2p.i2pcontrol.servlets;
|
||||
/*
|
||||
* Copyright 2011 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 com.thetransactioncompany.jsonrpc2.*;
|
||||
import com.thetransactioncompany.jsonrpc2.server.Dispatcher;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
import net.i2p.i2pcontrol.I2PControlVersion;
|
||||
import net.i2p.i2pcontrol.security.KeyStoreProvider;
|
||||
import net.i2p.i2pcontrol.security.SecurityManager;
|
||||
import net.i2p.i2pcontrol.servlets.jsonrpc2handlers.*;
|
||||
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
|
||||
/**
|
||||
* Provide an JSON-RPC 2.0 API for remote controlling of I2P
|
||||
*/
|
||||
public class JSONRPC2Servlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = -45075606818515212L;
|
||||
private static final int BUFFER_LENGTH = 2048;
|
||||
private static final String SVC_HTTP_I2PCONTROL = "http_i2pcontrol";
|
||||
private static final String SVC_HTTPS_I2PCONTROL = "https_i2pcontrol";
|
||||
private Dispatcher disp;
|
||||
private Log _log;
|
||||
private final SecurityManager _secMan;
|
||||
private final ConfigurationManager _conf;
|
||||
private final JSONRPC2Helper _helper;
|
||||
private final RouterContext _context;
|
||||
private final boolean _isWebapp;
|
||||
private boolean _isHTTP, _isHTTPS;
|
||||
|
||||
/**
|
||||
* Webapp
|
||||
*/
|
||||
public JSONRPC2Servlet() {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
if (!ctx.isRouterContext())
|
||||
throw new IllegalStateException();
|
||||
_context = (RouterContext) ctx;
|
||||
File appDir = ctx.getAppDir();
|
||||
_conf = new ConfigurationManager(ctx, appDir, false);
|
||||
// we don't really need a keystore
|
||||
//File ksDir = new File(ctx.getConfigDir(), "keystore");
|
||||
//ksDir.mkDir();
|
||||
//KeyStoreProvider ksp = new KeyStoreProvider(ksDir.getAbsolutePath());
|
||||
//_secMan = new SecurityManager(ctx, ksp, _conf);
|
||||
_secMan = new SecurityManager(ctx, null, _conf);
|
||||
_helper = new JSONRPC2Helper(_secMan);
|
||||
_log = ctx.logManager().getLog(JSONRPC2Servlet.class);
|
||||
_conf.writeConfFile();
|
||||
_isWebapp = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin
|
||||
*/
|
||||
public JSONRPC2Servlet(RouterContext ctx, SecurityManager secMan) {
|
||||
_context = ctx;
|
||||
_secMan = secMan;
|
||||
_helper = new JSONRPC2Helper(_secMan);
|
||||
if (ctx != null)
|
||||
_log = ctx.logManager().getLog(JSONRPC2Servlet.class);
|
||||
else
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(JSONRPC2Servlet.class);
|
||||
_conf = null;
|
||||
_isWebapp = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
super.init();
|
||||
disp = new Dispatcher();
|
||||
disp.register(new EchoHandler(_helper));
|
||||
disp.register(new GetRateHandler(_helper));
|
||||
disp.register(new AuthenticateHandler(_helper, _secMan));
|
||||
disp.register(new NetworkSettingHandler(_context, _helper));
|
||||
disp.register(new RouterInfoHandler(_context, _helper));
|
||||
disp.register(new RouterManagerHandler(_context, _helper));
|
||||
disp.register(new I2PControlHandler(_context, _helper, _secMan));
|
||||
disp.register(new AdvancedSettingsHandler(_context, _helper));
|
||||
if (_isWebapp) {
|
||||
PortMapper pm = _context.portMapper();
|
||||
int port = pm.getPort(PortMapper.SVC_CONSOLE);
|
||||
if (port > 0) {
|
||||
String host = pm.getHost(PortMapper.SVC_CONSOLE, "127.0.0.1");
|
||||
pm.register(SVC_HTTP_I2PCONTROL, host, port);
|
||||
_isHTTP = true;
|
||||
}
|
||||
port = pm.getPort(PortMapper.SVC_HTTPS_CONSOLE);
|
||||
if (port > 0) {
|
||||
String host = pm.getHost(PortMapper.SVC_HTTPS_CONSOLE, "127.0.0.1");
|
||||
pm.register(SVC_HTTPS_I2PCONTROL, host, port);
|
||||
_isHTTPS = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (_isWebapp) {
|
||||
PortMapper pm = _context.portMapper();
|
||||
if (_isHTTP)
|
||||
pm.unregister(SVC_HTTP_I2PCONTROL);
|
||||
if (_isHTTPS)
|
||||
pm.unregister(SVC_HTTPS_I2PCONTROL);
|
||||
_secMan.stopTimedEvents();
|
||||
_conf.writeConfFile();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
|
||||
httpServletResponse.setContentType("text/html");
|
||||
PrintWriter out = httpServletResponse.getWriter();
|
||||
out.println("<p>I2PControl RPC Service version " + I2PControlVersion.VERSION + " : Running");
|
||||
if ("/password".equals(httpServletRequest.getServletPath())) {
|
||||
out.println("<form method=\"POST\" action=\"password\">");
|
||||
if (_secMan.isDefaultPasswordValid()) {
|
||||
out.println("<p>The current API password is the default, \"" + _secMan.DEFAULT_AUTH_PASSWORD + "\". You should change it.");
|
||||
} else {
|
||||
out.println("<p>Current API password:<input name=\"password\" type=\"password\">");
|
||||
}
|
||||
out.println("<p>New API password (twice):<input name=\"password2\" type=\"password\">" +
|
||||
"<input name=\"password3\" type=\"password\">" +
|
||||
"<input name=\"save\" type=\"submit\" value=\"Change API Password\">" +
|
||||
"<p>If you forget the API password, stop i2pcontrol, delete the file <tt>" + _conf.getConfFile() +
|
||||
"</tt>, and restart i2pcontrol.");
|
||||
} else {
|
||||
out.println("<p><a href=\"password\">Change API Password</a>");
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
|
||||
/** @since 0.12 */
|
||||
private void doPasswordChange(HttpServletRequest req, HttpServletResponse httpServletResponse) throws ServletException, IOException {
|
||||
httpServletResponse.setContentType("text/html");
|
||||
PrintWriter out = httpServletResponse.getWriter();
|
||||
String pw = req.getParameter("password");
|
||||
if (pw == null)
|
||||
pw = _secMan.DEFAULT_AUTH_PASSWORD;
|
||||
else
|
||||
pw = pw.trim();
|
||||
String pw2 = req.getParameter("password2");
|
||||
String pw3 = req.getParameter("password3");
|
||||
if (pw2 == null || pw3 == null) {
|
||||
out.println("<p>Enter new password twice!");
|
||||
} else {
|
||||
pw2 = pw2.trim();
|
||||
pw3 = pw3.trim();
|
||||
if (!pw2.equals(pw3)) {
|
||||
out.println("<p>New passwords don't match!");
|
||||
} else if (pw2.length() <= 0) {
|
||||
out.println("<p>Enter new password twice!");
|
||||
} else if (_secMan.isValid(pw)) {
|
||||
_secMan.setPasswd(pw2);
|
||||
out.println("<p>API Password changed");
|
||||
} else {
|
||||
out.println("<p>Incorrect old password, not changed");
|
||||
}
|
||||
}
|
||||
out.println("<p><a href=\"password\">Change API Password</a>");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
|
||||
if ("/password".equals(httpServletRequest.getServletPath())) {
|
||||
doPasswordChange(httpServletRequest, httpServletResponse);
|
||||
return;
|
||||
}
|
||||
String req = getRequest(httpServletRequest.getInputStream());
|
||||
httpServletResponse.setContentType("application/json");
|
||||
PrintWriter out = httpServletResponse.getWriter();
|
||||
JSONRPC2Message msg = null;
|
||||
JSONRPC2Response jsonResp = null;
|
||||
try {
|
||||
msg = JSONRPC2Message.parse(req);
|
||||
|
||||
if (msg instanceof JSONRPC2Request) {
|
||||
jsonResp = disp.process((JSONRPC2Request)msg, null);
|
||||
jsonResp.toJSONObject().put("API", I2PControlVersion.API_VERSION);
|
||||
if (_log.shouldDebug()) {
|
||||
_log.debug("Request: " + msg);
|
||||
_log.debug("Response: " + jsonResp);
|
||||
}
|
||||
}
|
||||
else if (msg instanceof JSONRPC2Notification) {
|
||||
disp.process((JSONRPC2Notification)msg, null);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Notification: " + msg);
|
||||
}
|
||||
|
||||
out.println(jsonResp);
|
||||
out.close();
|
||||
} catch (JSONRPC2ParseException e) {
|
||||
_log.error("Unable to parse JSONRPC2Message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getRequest(ServletInputStream sis) throws IOException {
|
||||
Writer writer = new StringWriter();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(sis, "UTF-8"));
|
||||
char[] readBuffer = new char[BUFFER_LENGTH];
|
||||
int n;
|
||||
while ((n = reader.read(readBuffer)) != -1) {
|
||||
writer.write(readBuffer, 0, n);
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,219 @@
|
||||
package net.i2p.i2pcontrol.servlets.configuration;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Manage the configuration of I2PControl.
|
||||
* @author mathias
|
||||
* modified: hottuna
|
||||
*
|
||||
*/
|
||||
public class ConfigurationManager {
|
||||
private final String CONFIG_FILE = "I2PControl.conf";
|
||||
private final String WEBAPP_CONFIG_FILE = "i2pcontrol.config";
|
||||
private final File configLocation;
|
||||
private final Log _log;
|
||||
private boolean _changed;
|
||||
|
||||
//Configurations with a String as value
|
||||
private final Map<String, String> stringConfigurations = new HashMap<String, String>();
|
||||
//Configurations with a Boolean as value
|
||||
private final Map<String, Boolean> booleanConfigurations = new HashMap<String, Boolean>();
|
||||
//Configurations with an Integer as value
|
||||
private final Map<String, Integer> integerConfigurations = new HashMap<String, Integer>();
|
||||
|
||||
|
||||
|
||||
public ConfigurationManager(I2PAppContext ctx, File dir, boolean isPlugin) {
|
||||
_log = ctx.logManager().getLog(ConfigurationManager.class);
|
||||
if (isPlugin) {
|
||||
configLocation = new File(dir, CONFIG_FILE);
|
||||
} else {
|
||||
configLocation = new File(dir, WEBAPP_CONFIG_FILE);
|
||||
}
|
||||
readConfFile();
|
||||
}
|
||||
|
||||
/** @since 0.12 */
|
||||
public File getConfFile() {
|
||||
return configLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects arguments of the form --word, --word=otherword and -blah
|
||||
* to determine user parameters.
|
||||
* @param settingNames Command line arguments to the application
|
||||
*/
|
||||
/****
|
||||
public void loadArguments(String[] settingNames) {
|
||||
for (int i = 0; i < settingNames.length; i++) {
|
||||
String settingName = settingNames[i];
|
||||
if (settingName.startsWith("--")) {
|
||||
parseConfigStr(settingName.substring(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Reads configuration from file, every line is parsed as key=value.
|
||||
*/
|
||||
public synchronized void readConfFile() {
|
||||
try {
|
||||
Properties input = new Properties();
|
||||
// true: map to lower case
|
||||
DataHelper.loadProps(input, configLocation, true);
|
||||
parseConfigStr(input);
|
||||
_changed = false;
|
||||
} catch (FileNotFoundException e) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Unable to find config file, " + configLocation);
|
||||
} catch (IOException e) {
|
||||
_log.error("Unable to read from config file, " + configLocation, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write configuration into default config file.
|
||||
* As of 0.12, doesn't actually write unless something changed.
|
||||
*/
|
||||
public synchronized void writeConfFile() {
|
||||
if (!_changed)
|
||||
return;
|
||||
Properties tree = new OrderedProperties();
|
||||
tree.putAll(stringConfigurations);
|
||||
for (Entry<String, Integer> e : integerConfigurations.entrySet()) {
|
||||
tree.put(e.getKey(), e.getValue().toString());
|
||||
}
|
||||
for (Entry<String, Boolean> e : booleanConfigurations.entrySet()) {
|
||||
tree.put(e.getKey(), e.getValue().toString());
|
||||
}
|
||||
try {
|
||||
DataHelper.storeProps(tree, configLocation);
|
||||
_changed = false;
|
||||
} catch (IOException e1) {
|
||||
_log.error("Couldn't open file, " + configLocation + " for writing config.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the input as 'key=value',
|
||||
* where value will (in order) be parsed as integer/boolean/string.
|
||||
* @param str
|
||||
*/
|
||||
private void parseConfigStr(Properties input) {
|
||||
for (Entry<Object, Object> entry : input.entrySet()) {
|
||||
String key = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
//Try parse as integer.
|
||||
try {
|
||||
int i = Integer.parseInt(value);
|
||||
integerConfigurations.put(key, i);
|
||||
continue;
|
||||
} catch (NumberFormatException e) {}
|
||||
//Check if value is a bool
|
||||
if (value.toLowerCase().equals("true")) {
|
||||
booleanConfigurations.put(key, Boolean.TRUE);
|
||||
continue;
|
||||
} else if (value.toLowerCase().equals("false")) {
|
||||
booleanConfigurations.put(key, Boolean.FALSE);
|
||||
continue;
|
||||
}
|
||||
stringConfigurations.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a specific boolean configuration exists.
|
||||
* @param settingName The key for the configuration.
|
||||
* @param defaultValue If the configuration is not found, we use a default value.
|
||||
* @return The value of a configuration: true if found, defaultValue if not found.
|
||||
*/
|
||||
public synchronized boolean getConf(String settingName, boolean defaultValue) {
|
||||
Boolean value = booleanConfigurations.get(settingName);
|
||||
if (value != null) {
|
||||
return value;
|
||||
} else {
|
||||
booleanConfigurations.put(settingName, defaultValue);
|
||||
_changed = true;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a specific boolean configuration exists.
|
||||
* @param settingName The key for the configuration.
|
||||
* @param defaultValue If the configuration is not found, we use a default value.
|
||||
* @return The value of a configuration: true if found, defaultValue if not found.
|
||||
*/
|
||||
public synchronized int getConf(String settingName, int defaultValue) {
|
||||
Integer value = integerConfigurations.get(settingName);
|
||||
if (value != null) {
|
||||
return value;
|
||||
} else {
|
||||
integerConfigurations.put(settingName, defaultValue);
|
||||
_changed = true;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific String configuration.
|
||||
* @param settingName The key for the configuration.
|
||||
* @param defaultValue If the configuration is not found, we use a default value.
|
||||
* @return The value of the configuration, or the defaultValue.
|
||||
*/
|
||||
public synchronized String getConf(String settingName, String defaultValue) {
|
||||
String value = stringConfigurations.get(settingName);
|
||||
if (value != null) {
|
||||
return value;
|
||||
} else {
|
||||
stringConfigurations.put(settingName, defaultValue);
|
||||
_changed = true;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific int setting
|
||||
* @param settingName
|
||||
* @param nbr
|
||||
*/
|
||||
public synchronized void setConf(String settingName, int nbr) {
|
||||
integerConfigurations.put(settingName, nbr);
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific string setting
|
||||
* @param settingName
|
||||
* @param str
|
||||
*/
|
||||
public synchronized void setConf(String settingName, String str) {
|
||||
stringConfigurations.put(settingName, str);
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific boolean setting
|
||||
* @param settingName
|
||||
* @param bool
|
||||
*/
|
||||
public synchronized void setConf(String settingName, boolean bool) {
|
||||
booleanConfigurations.put(settingName, bool);
|
||||
_changed = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,200 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class AdvancedSettingsHandler implements RequestHandler {
|
||||
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final JSONRPC2Helper _helper;
|
||||
private static final String[] requiredArgs = {};
|
||||
|
||||
public AdvancedSettingsHandler(RouterContext ctx, JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
_context = ctx;
|
||||
if (ctx != null)
|
||||
_log = ctx.logManager().getLog(AdvancedSettingsHandler.class);
|
||||
else
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(AdvancedSettingsHandler.class);
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"AdvancedSettings"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("AdvancedSettings")) {
|
||||
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
|
||||
if (err != null) {
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
}
|
||||
|
||||
if (_context == null) {
|
||||
return new JSONRPC2Response(new JSONRPC2Error(
|
||||
JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"RouterContext was not initialized. Query failed"),
|
||||
req.getID());
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
Map outParams = new HashMap();
|
||||
|
||||
if (inParams.containsKey("setAll")) {
|
||||
Object obj = inParams.get("setAll");
|
||||
if (!(obj instanceof Map)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Value of \"setAll\" is not a Map");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Map objMap = (Map) inParams.get("setAll");
|
||||
if (objMap.size() > 0)
|
||||
{
|
||||
if (!(objMap.keySet().toArray()[0] instanceof String) &&
|
||||
!(objMap.values().toArray()[0] instanceof String)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Map of settings does not contain String keys and values");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
if (!checkTypes(objMap)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"Some of the supplied values are not strings");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
Map<String, String> allSettings = (Map<String, String>) objMap;
|
||||
boolean success = setAdvancedSettings(allSettings, true);
|
||||
if (!success) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"Failed to save new config");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
} else {
|
||||
// Empty list of settings submitted
|
||||
boolean success = setAdvancedSettings(null, true);
|
||||
if (!success) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"Failed to save new config");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("getAll")) {
|
||||
outParams.put("getAll", getAdvancedSettings());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("set")) {
|
||||
Object obj = inParams.get("set");
|
||||
if (!(obj instanceof Map)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Value of \"set\" is not a Map");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
Map objMap = (Map) inParams.get("set");
|
||||
if (objMap.size() > 0)
|
||||
{
|
||||
if (!(objMap.keySet().toArray()[0] instanceof String) &&
|
||||
!(objMap.values().toArray()[0] instanceof String)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Map of settings does not contain String keys and values");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
if (!checkTypes(objMap)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"Some of the supplied values are not strings");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
Map<String, String> allSettings = (Map<String, String>) objMap;
|
||||
boolean success = setAdvancedSettings(allSettings, false);
|
||||
if (!success) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"Failed to save new config");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
|
||||
} else {
|
||||
// Empty list of settings submitted
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Map of settings does not contain any entries");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("get")) {
|
||||
Object obj = inParams.get("get");
|
||||
if (!(obj instanceof String)) {
|
||||
JSONRPC2Error rpcErr = new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"Value of \"get\" is not a string");
|
||||
return new JSONRPC2Response(rpcErr, req.getID());
|
||||
}
|
||||
String getStr = (String) obj;
|
||||
String getVal = getAdvancedSetting(getStr);
|
||||
Map<String, String> outMap = new HashMap<String, String>();
|
||||
outMap.put(getStr, getVal);
|
||||
outParams.put("get", outMap);
|
||||
}
|
||||
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
private String getAdvancedSetting(String key) {
|
||||
return _context.router().getConfigSetting(key);
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> getAdvancedSettings() {
|
||||
return _context.router().getConfigMap();
|
||||
}
|
||||
|
||||
private boolean checkTypes(Map<String, Object> newSettings) {
|
||||
for (String key : newSettings.keySet()) {
|
||||
if (!(newSettings.get(key) instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean setAdvancedSettings(Map<String, String> newSettings, boolean clearConfig) {
|
||||
Set<String> unsetKeys = null;
|
||||
|
||||
if (clearConfig) {
|
||||
unsetKeys = new HashSet<String>(_context.router().getConfigSettings());
|
||||
|
||||
for (String key : newSettings.keySet()) {
|
||||
unsetKeys.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
return _context.router().saveConfig(newSettings, unsetKeys);
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
import net.i2p.i2pcontrol.I2PControlVersion;
|
||||
import net.i2p.i2pcontrol.security.AuthToken;
|
||||
import net.i2p.i2pcontrol.security.SecurityManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class AuthenticateHandler implements RequestHandler {
|
||||
|
||||
private static final String[] requiredArgs = {"Password", "API"};
|
||||
private final JSONRPC2Helper _helper;
|
||||
private final SecurityManager _secMan;
|
||||
|
||||
public AuthenticateHandler(JSONRPC2Helper helper, SecurityManager secMan) {
|
||||
_helper = helper;
|
||||
_secMan = secMan;
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"Authenticate"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("Authenticate")) {
|
||||
JSONRPC2Error err = _helper.validateParams(requiredArgs, req, JSONRPC2Helper.USE_NO_AUTH);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
|
||||
String pwd = (String) inParams.get("Password");
|
||||
|
||||
// Try get an AuthToken
|
||||
|
||||
AuthToken token = _secMan.validatePasswd(pwd);
|
||||
if (token == null) {
|
||||
return new JSONRPC2Response(JSONRPC2ExtendedError.INVALID_PASSWORD, req.getID());
|
||||
}
|
||||
|
||||
Object api = inParams.get("API");
|
||||
err = validateAPIVersion(api);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
|
||||
Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
outParams.put("Token", token.getId());
|
||||
outParams.put("API", I2PControlVersion.API_VERSION);
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the provided I2PControl API version against the ones supported by I2PControl.
|
||||
*/
|
||||
private static JSONRPC2Error validateAPIVersion(Object api) {
|
||||
|
||||
Integer apiVersion;
|
||||
try {
|
||||
apiVersion = ((Long) api).intValue();
|
||||
} catch (ClassCastException e) {
|
||||
e.printStackTrace();
|
||||
return JSONRPC2ExtendedError.UNSPECIFIED_API_VERSION;
|
||||
}
|
||||
|
||||
if (!I2PControlVersion.SUPPORTED_API_VERSIONS.contains(apiVersion)) {
|
||||
String supportedAPIVersions = "";
|
||||
for (Integer i : I2PControlVersion.SUPPORTED_API_VERSIONS) {
|
||||
supportedAPIVersions += ", " + i;
|
||||
}
|
||||
return new JSONRPC2Error(JSONRPC2ExtendedError.UNSUPPORTED_API_VERSION.getCode(),
|
||||
"The provided API version \'" + apiVersion + "\' is not supported. The supported versions are" + supportedAPIVersions + ".");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EchoHandler implements RequestHandler {
|
||||
|
||||
private static final String[] requiredArgs = {"Echo"};
|
||||
private final JSONRPC2Helper _helper;
|
||||
|
||||
public EchoHandler(JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"Echo"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("Echo")) {
|
||||
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
String echo = (String) inParams.get("Echo");
|
||||
Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
outParams.put("Result", echo);
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class GetRateHandler implements RequestHandler {
|
||||
|
||||
private static final String[] requiredArgs = {"Stat", "Period"};
|
||||
private final JSONRPC2Helper _helper;
|
||||
|
||||
public GetRateHandler(JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"GetRate"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("GetRate")) {
|
||||
JSONRPC2Error err = _helper.validateParams(requiredArgs, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
|
||||
String input = (String) inParams.get("Stat");
|
||||
if (input == null) {
|
||||
return new JSONRPC2Response(JSONRPC2Error.INVALID_PARAMS, req.getID());
|
||||
}
|
||||
long period;
|
||||
try {
|
||||
period = (Long) inParams.get("Period");
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(JSONRPC2Error.INVALID_PARAMS, req.getID());
|
||||
}
|
||||
|
||||
RateStat rateStat = I2PAppContext.getGlobalContext().statManager().getRate(input);
|
||||
|
||||
// If RateStat or the requested period doesn't already exist, create them.
|
||||
if (rateStat == null || rateStat.getRate(period) == null) {
|
||||
long[] tempArr = new long[1];
|
||||
tempArr[0] = period;
|
||||
I2PAppContext.getGlobalContext().statManager().createRequiredRateStat(input, "I2PControl", "I2PControl", tempArr);
|
||||
rateStat = I2PAppContext.getGlobalContext().statManager().getRate(input);
|
||||
}
|
||||
if (rateStat.getRate(period) == null)
|
||||
return new JSONRPC2Response(JSONRPC2Error.INTERNAL_ERROR, req.getID());
|
||||
Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
Rate rate = rateStat.getRate(period);
|
||||
rate.coalesce();
|
||||
outParams.put("Result", rate.getAverageValue());
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
@@ -0,0 +1,205 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2pcontrol.I2PControlController;
|
||||
import net.i2p.i2pcontrol.security.SecurityManager;
|
||||
import net.i2p.i2pcontrol.servlets.configuration.ConfigurationManager;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class I2PControlHandler implements RequestHandler {
|
||||
|
||||
private static final int BW_BURST_PCT = 110;
|
||||
private static final int BW_BURST_TIME = 20;
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
//private final ConfigurationManager _conf;
|
||||
private final SecurityManager _secMan;
|
||||
private final JSONRPC2Helper _helper;
|
||||
|
||||
public I2PControlHandler(RouterContext ctx, JSONRPC2Helper helper, SecurityManager secMan) {
|
||||
_helper = helper;
|
||||
_secMan = secMan;
|
||||
_context = ctx;
|
||||
if (ctx != null)
|
||||
_log = ctx.logManager().getLog(I2PControlHandler.class);
|
||||
else
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(I2PControlHandler.class);
|
||||
}
|
||||
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"I2PControl"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("I2PControl")) {
|
||||
return process(req);
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private JSONRPC2Response process(JSONRPC2Request req) {
|
||||
JSONRPC2Error err = _helper.validateParams(null, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
/**** only if we enable host/port changes
|
||||
if (_context == null) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"RouterContext was not initialized. Query failed"),
|
||||
req.getID());
|
||||
}
|
||||
****/
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
|
||||
boolean restartNeeded = false;
|
||||
boolean settingsSaved = false;
|
||||
String inParam;
|
||||
|
||||
/****
|
||||
if (inParams.containsKey("i2pcontrol.port")) {
|
||||
Integer oldPort = _conf.getConf("i2pcontrol.listen.port", 7650);
|
||||
if ((inParam = (String) inParams.get("i2pcontrol.port")) != null) {
|
||||
if (oldPort == null || !inParam.equals(oldPort.toString())) {
|
||||
Integer newPort;
|
||||
try {
|
||||
newPort = Integer.valueOf(inParam);
|
||||
if (newPort < 1 || newPort > 65535) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2pcontrol.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
try {
|
||||
SslSocketConnector ssl = I2PControlController.buildSslListener(_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"), newPort);
|
||||
I2PControlController.clearListeners();
|
||||
I2PControlController.replaceListener(ssl);
|
||||
|
||||
_conf.setConf("i2pcontrol.listen.port", newPort);
|
||||
|
||||
|
||||
ConfigurationManager.writeConfFile();
|
||||
outParams.put("i2pcontrol.port", null);
|
||||
settingsSaved = true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
_conf.setConf("i2pcontrol.listen.port", oldPort);
|
||||
SslSocketConnector ssl = I2PControlController.buildSslListener(_conf.getConf("i2pcontrol.listen.address", "127.0.0.1"), oldPort);
|
||||
I2PControlController.clearListeners();
|
||||
I2PControlController.replaceListener(ssl);
|
||||
} catch (Exception e2) {
|
||||
_log.log(Log.CRIT, "Unable to resume server on previous listening port.");
|
||||
}
|
||||
_log.error("Client tried to set listen port to, " + newPort + " which isn't valid.", e);
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2pcontrol.port\" has been set to a port that is already in use, reverting. " +
|
||||
inParam + " is an already used port.\n"
|
||||
+ "Exception: " + e.toString()),
|
||||
req.getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
outParams.put("RestartNeeded", restartNeeded);
|
||||
}
|
||||
****/
|
||||
|
||||
if (inParams.containsKey("i2pcontrol.password")) {
|
||||
if ((inParam = (String) inParams.get("i2pcontrol.password")) != null) {
|
||||
if (_secMan.setPasswd(inParam)) {
|
||||
outParams.put("i2pcontrol.password", null);
|
||||
settingsSaved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
if (inParams.containsKey("i2pcontrol.address")) {
|
||||
String oldAddress = _conf.getConf("i2pcontrol.listen.address", "127.0.0.1");
|
||||
if ((inParam = (String) inParams.get("i2pcontrol.address")) != null) {
|
||||
if ((oldAddress == null || !inParam.equals(oldAddress.toString()) &&
|
||||
(inParam.equals("0.0.0.0") || inParam.equals("127.0.0.1")))) {
|
||||
InetAddress[] newAddress;
|
||||
|
||||
try {
|
||||
newAddress = InetAddress.getAllByName(inParam);
|
||||
} catch (UnknownHostException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2pcontrol.address\" must be a string representing a hostname or ipaddress. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
try {
|
||||
SslSocketConnector ssl = I2PControlController.buildSslListener(inParam, _conf.getConf("i2pcontrol.listen.port", 7650));
|
||||
I2PControlController.clearListeners();
|
||||
I2PControlController.replaceListener(ssl);
|
||||
_conf.setConf("i2pcontrol.listen.address", inParam);
|
||||
|
||||
ConfigurationManager.writeConfFile();
|
||||
outParams.put("i2pcontrol.address", null);
|
||||
settingsSaved = true;
|
||||
} catch (Exception e) {
|
||||
_conf.setConf("i2pcontrol.listen.address", oldAddress);
|
||||
try {
|
||||
SslSocketConnector ssl = I2PControlController.buildSslListener(inParam, _conf.getConf("i2pcontrol.listen.port", 7650));
|
||||
I2PControlController.clearListeners();
|
||||
I2PControlController.replaceListener(ssl);
|
||||
} catch (Exception e2) {
|
||||
_log.log(Log.CRIT, "Unable to resume server on previous listening ip.");
|
||||
}
|
||||
_log.error("Client tried to set listen address to, " + newAddress.toString() + " which isn't valid.", e);
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2pcontrol.address\" has been set to an invalid address, reverting. "), req.getID());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outParams.put("i2pcontrol.address", oldAddress);
|
||||
}
|
||||
outParams.put("RestartNeeded", restartNeeded);
|
||||
}
|
||||
****/
|
||||
|
||||
outParams.put("SettingsSaved", settingsSaved);
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a JSON-RPC 2.0 error that occured during the processing of a
|
||||
* request.
|
||||
*
|
||||
* <p>The protocol expects error objects to be structured like this:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code code} An integer that indicates the error type.
|
||||
* <li>{@code message} A string providing a short description of the
|
||||
* error. The message should be limited to a concise single sentence.
|
||||
* <li>{@code data} Additional information, which may be omitted. Its
|
||||
* contents is entirely defined by the application.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that the "Error" word in the class name was put there solely to
|
||||
* comply with the parlance of the JSON-RPC spec. This class doesn't inherit
|
||||
* from {@code java.lang.Error}. It's a regular subclass of
|
||||
* {@code java.lang.Exception} and, if thrown, it's to indicate a condition
|
||||
* that a reasonable application might want to catch.
|
||||
*
|
||||
* <p>This class also includes convenient final static instances for all
|
||||
* standard JSON-RPC 2.0 errors:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #PARSE_ERROR} JSON parse error (-32700)
|
||||
* <li>{@link #INVALID_REQUEST} Invalid JSON-RPC 2.0 Request (-32600)
|
||||
* <li>{@link #METHOD_NOT_FOUND} Method not found (-32601)
|
||||
* <li>{@link #INVALID_PARAMS} Invalid parameters (-32602)
|
||||
* <li>{@link #INTERNAL_ERROR} Internal error (-32603)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that the range -32099..-32000 is reserved for additional server
|
||||
* errors.
|
||||
*
|
||||
* <p id="map">The mapping between JSON and Java entities (as defined by the
|
||||
* underlying JSON.simple library):
|
||||
* <pre>
|
||||
* true|false <---> java.lang.Boolean
|
||||
* number <---> java.lang.Number
|
||||
* string <---> java.lang.String
|
||||
* array <---> java.util.List
|
||||
* object <---> java.util.Map
|
||||
* null <---> null
|
||||
* </pre>
|
||||
*
|
||||
* <p>The JSON-RPC 2.0 specification and user group forum can be found
|
||||
* <a href="http://groups.google.com/group/json-rpc">here</a>.
|
||||
*
|
||||
* @author <a href="http://dzhuvinov.com">Vladimir Dzhuvinov</a>
|
||||
* @version 1.16 (2010-10-04)
|
||||
*/
|
||||
public class JSONRPC2ExtendedError extends JSONRPC2Error {
|
||||
|
||||
private static final long serialVersionUID = -6574632977222371077L;
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error INVALID_PASSWORD = new JSONRPC2ExtendedError(-32001, "Invalid password provided.");
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error NO_TOKEN = new JSONRPC2ExtendedError(-32002, "No authentication token presented.");
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error INVALID_TOKEN = new JSONRPC2ExtendedError(-32003, "Authentication token doesn't exist.");
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error TOKEN_EXPIRED = new JSONRPC2ExtendedError(-32004, "Provided authentication token was expired and will be removed.");
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error UNSPECIFIED_API_VERSION = new JSONRPC2ExtendedError(-32005, "The version of the I2PControl API wasn't specified, but is required to be specified.");
|
||||
|
||||
/** Invalid JSON-RPC 2.0, implementation defined error (-32099 .. -32000) */
|
||||
public static final JSONRPC2Error UNSUPPORTED_API_VERSION = new JSONRPC2ExtendedError(-32006, "The version of the I2PControl API specified is not supported by I2PControl.");
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new JSON-RPC 2.0 error with the specified code and
|
||||
* message. The optional data is omitted.
|
||||
*
|
||||
* @param code The error code (standard pre-defined or
|
||||
* application-specific).
|
||||
* @param message The error message.
|
||||
*/
|
||||
public JSONRPC2ExtendedError(int code, String message) {
|
||||
super(code, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new JSON-RPC 2.0 error with the specified code,
|
||||
* message and data.
|
||||
*
|
||||
* @param code The error code (standard pre-defined or
|
||||
* application-specific).
|
||||
* @param message The error message.
|
||||
* @param data Optional error data, must <a href="#map">map</a>
|
||||
* to a valid JSON type.
|
||||
*/
|
||||
public JSONRPC2ExtendedError(int code, String message, Object data) {
|
||||
super(code, message, data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2ParamsType;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import net.i2p.i2pcontrol.security.ExpiredAuthTokenException;
|
||||
import net.i2p.i2pcontrol.security.InvalidAuthTokenException;
|
||||
import net.i2p.i2pcontrol.security.SecurityManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class JSONRPC2Helper {
|
||||
public final static Boolean USE_NO_AUTH = false;
|
||||
public final static Boolean USE_AUTH = true;
|
||||
|
||||
private final SecurityManager _secMan;
|
||||
|
||||
public JSONRPC2Helper(SecurityManager secMan) {
|
||||
_secMan = secMan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check incoming request for required arguments, to make sure they are valid.
|
||||
* @param requiredArgs - Array of names of required arguments. If null don't check for any parameters.
|
||||
* @param req - Incoming JSONRPC2 request
|
||||
* @param useAuth - If true, will validate authentication token.
|
||||
* @return - null if no errors were found. Corresponding JSONRPC2Error if error is found.
|
||||
*/
|
||||
public JSONRPC2Error validateParams(String[] requiredArgs, JSONRPC2Request req, Boolean useAuth) {
|
||||
|
||||
// Error on unnamed parameters
|
||||
if (req.getParamsType() != JSONRPC2ParamsType.OBJECT) {
|
||||
return JSONRPC2Error.INVALID_PARAMS;
|
||||
}
|
||||
Map<String, Object> params = req.getNamedParams();
|
||||
|
||||
// Validate authentication token.
|
||||
if (useAuth) {
|
||||
JSONRPC2Error err = validateToken(params);
|
||||
if (err != null) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// If there exist any required arguments.
|
||||
if (requiredArgs != null && requiredArgs.length > 0) {
|
||||
String missingArgs = "";
|
||||
for (int i = 0; i < requiredArgs.length; i++) {
|
||||
if (!params.containsKey(requiredArgs[i])) {
|
||||
missingArgs = missingArgs.concat(requiredArgs[i] + ",");
|
||||
}
|
||||
}
|
||||
if (missingArgs.length() > 0) {
|
||||
missingArgs = missingArgs.substring(0, missingArgs.length() - 1);
|
||||
return new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(), "Missing parameter(s): " + missingArgs);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check incoming request for required arguments, to make sure they are valid. Will authenticate req.
|
||||
* @param requiredArgs - Array of names of required arguments. If null don't check for any parameters.
|
||||
* @param req - Incoming JSONRPC2 request
|
||||
* @return - null if no errors were found. Corresponding JSONRPC2Error if error is found.
|
||||
*/
|
||||
public JSONRPC2Error validateParams(String[] requiredArgs, JSONRPC2Request req) {
|
||||
return validateParams(requiredArgs, req, JSONRPC2Helper.USE_AUTH);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will check incoming parameters to make sure they contain a valid token.
|
||||
* @param req - Parameters of incoming request
|
||||
* @return null if everything is fine, JSONRPC2Error for any corresponding error.
|
||||
*/
|
||||
private JSONRPC2Error validateToken(Map<String, Object> params) {
|
||||
String tokenID = (String) params.get("Token");
|
||||
if (tokenID == null) {
|
||||
return JSONRPC2ExtendedError.NO_TOKEN;
|
||||
}
|
||||
try {
|
||||
_secMan.verifyToken(tokenID);
|
||||
} catch (InvalidAuthTokenException e) {
|
||||
return JSONRPC2ExtendedError.INVALID_TOKEN;
|
||||
} catch (ExpiredAuthTokenException e) {
|
||||
JSONRPC2Error err = new JSONRPC2ExtendedError(JSONRPC2ExtendedError.TOKEN_EXPIRED.getCode(),
|
||||
"Provided authentication token expired " + e.getExpirytime() + ", will be removed.");
|
||||
return err;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,344 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.FIFOBandwidthRefiller;
|
||||
import net.i2p.router.transport.TransportManager;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class NetworkSettingHandler implements RequestHandler {
|
||||
private static final int BW_BURST_PCT = 110;
|
||||
private static final int BW_BURST_TIME = 20;
|
||||
private final JSONRPC2Helper _helper;
|
||||
private final RouterContext _context;
|
||||
|
||||
public NetworkSettingHandler(RouterContext ctx, JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] {"NetworkSetting"};
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("NetworkSetting")) {
|
||||
return process(req);
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private JSONRPC2Response process(JSONRPC2Request req) {
|
||||
JSONRPC2Error err = _helper.validateParams(null, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
if (_context == null) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"RouterContext was not initialized. Query failed"),
|
||||
req.getID());
|
||||
}
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
|
||||
boolean restartNeeded = false;
|
||||
boolean settingsSaved = false;
|
||||
String inParam;
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ntcp.port")) {
|
||||
String oldNTCPPort = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_PORT);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.port")) != null) {
|
||||
if (oldNTCPPort == null || !oldNTCPPort.equals(inParam.trim())) {
|
||||
Integer newPort;
|
||||
try {
|
||||
newPort = Integer.valueOf(inParam);
|
||||
if (newPort < 1 || newPort > 65535) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.ntcp.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
config.put(NTCPTransport.PROP_I2NP_NTCP_PORT, String.valueOf(newPort));
|
||||
config.put(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT, "false");
|
||||
_context.router().saveConfig(config, null);
|
||||
restartNeeded = true;
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
String sAutoPort = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT, "true");
|
||||
boolean oldAutoPort = "true".equalsIgnoreCase(sAutoPort);
|
||||
if (oldAutoPort) {
|
||||
String oldSSUPort = "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, 8887);
|
||||
outParams.put("i2p.router.net.ntcp.port", oldSSUPort);
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ntcp.port", oldNTCPPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ntcp.hostname")) {
|
||||
String oldNTCPHostname = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.hostname")) != null) {
|
||||
if (oldNTCPHostname == null || !oldNTCPHostname.equals(inParam.trim())) {
|
||||
_context.router().saveConfig(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME, inParam);
|
||||
restartNeeded = true;
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ntcp.hostname", oldNTCPHostname);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ntcp.autoip")) {
|
||||
String oldNTCPAutoIP = _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_IP);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ntcp.autoip")) != null) {
|
||||
inParam = inParam.trim().toLowerCase();
|
||||
if (oldNTCPAutoIP == null || !oldNTCPAutoIP.equals(inParam)) {
|
||||
if ("always".equals(inParam) || "true".equals(inParam) || "false".equals(inParam)) {
|
||||
_context.router().saveConfig(NTCPTransport.PROP_I2NP_NTCP_AUTO_IP, inParam);
|
||||
restartNeeded = true;
|
||||
} else {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.ntcp.autoip\" can only be always, true or false. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ntcp.autoip", oldNTCPAutoIP);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ssu.port")) {
|
||||
String oldSSUPort = "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, 8887);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ssu.port")) != null) {
|
||||
if (oldSSUPort == null || !oldSSUPort.equals(inParam.trim())) {
|
||||
Integer newPort;
|
||||
try {
|
||||
newPort = Integer.valueOf(inParam);
|
||||
if (newPort < 1 || newPort > 65535) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.ssu.port\" must be a string representing a number in the range 1-65535. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
config.put(UDPTransport.PROP_EXTERNAL_PORT, String.valueOf(newPort));
|
||||
config.put(UDPTransport.PROP_INTERNAL_PORT, String.valueOf(newPort));
|
||||
_context.router().saveConfig(config, null);
|
||||
restartNeeded = true;
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ssu.port", oldSSUPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ssu.hostname")) {
|
||||
String oldSSUHostname = _context.getProperty(UDPTransport.PROP_EXTERNAL_HOST);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ssu.hostname")) != null) {
|
||||
if (oldSSUHostname == null || !oldSSUHostname.equals(inParam.trim())) {
|
||||
_context.router().saveConfig(UDPTransport.PROP_EXTERNAL_HOST, inParam);
|
||||
restartNeeded = true;
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ssu.hostname", oldSSUHostname);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.ssu.autoip")) {
|
||||
String oldSSUAutoIP = _context.getProperty(UDPTransport.PROP_SOURCES);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ssu.autoip")) != null) {
|
||||
inParam = inParam.trim().toLowerCase();
|
||||
if (oldSSUAutoIP == null || !oldSSUAutoIP.equals(inParam)) {
|
||||
if (inParam.equals("ssu") || inParam.equals("local,ssu") || inParam.equals("upnp,ssu") || inParam.equals("local,upnp,ssu")) {
|
||||
_context.router().saveConfig(UDPTransport.PROP_SOURCES, inParam);
|
||||
restartNeeded = true;
|
||||
} else {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.ssu.autoip\" can only be ssu/local,upnp,ssu/local/ssu/upnp,ssu. " + inParam + " isn't valid."),
|
||||
req.getID());
|
||||
}
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.ssu.autoip", oldSSUAutoIP);
|
||||
}
|
||||
}
|
||||
|
||||
// Non-setable key.
|
||||
if (inParams.containsKey("i2p.router.net.ssu.detectedip")) {
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.ssu.autoip")) == null) {
|
||||
byte[] ipBytes = _context.router().getRouterInfo().getTargetAddress("SSU").getIP();
|
||||
try {
|
||||
InetAddress i = InetAddress.getByAddress(ipBytes);
|
||||
outParams.put("i2p.router.net.ssu.detectedip", i.getHostAddress());
|
||||
} catch (UnknownHostException e) {
|
||||
outParams.put("i2p.router.net.ssu.detectedip", "Failed to parse ip address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.upnp")) {
|
||||
String oldUPNP = _context.getProperty(TransportManager.PROP_ENABLE_UPNP);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.upnp")) != null) {
|
||||
if (oldUPNP == null || !oldUPNP.equals(inParam.trim())) {
|
||||
_context.router().saveConfig(TransportManager.PROP_ENABLE_UPNP, inParam);
|
||||
restartNeeded = true;
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.upnp", oldUPNP);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.share")) {
|
||||
String oldShare = _context.router().getConfigSetting(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.bw.share")) != null) {
|
||||
if (oldShare == null || !oldShare.equals(inParam.trim())) {
|
||||
Integer percent;
|
||||
try {
|
||||
percent = Integer.parseInt(inParam);
|
||||
if (percent < 0 || percent > 100 || inParam.length() == 0) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.bw.share\" A positive integer must supplied, \"" + inParam + "\" isn't valid"),
|
||||
req.getID());
|
||||
}
|
||||
_context.router().saveConfig(Router.PROP_BANDWIDTH_SHARE_PERCENTAGE, inParam);
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.bw.share", oldShare);
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.in")) {
|
||||
String oldBWIn = _context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.bw.in")) != null) {
|
||||
Integer rate;
|
||||
try {
|
||||
rate = Integer.parseInt(inParam);
|
||||
if (rate < 0 || inParam.length() == 0) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.bw.in\" A positive integer must supplied, " + inParam + " isn't valid"),
|
||||
req.getID());
|
||||
}
|
||||
Integer burstRate = (rate * BW_BURST_PCT) / 100;
|
||||
Integer burstSize = (burstRate * BW_BURST_TIME);
|
||||
if (oldBWIn == null || !oldBWIn.equals(rate.toString())) {
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, rate.toString());
|
||||
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, burstRate.toString());
|
||||
config.put(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, burstSize.toString());
|
||||
_context.router().saveConfig(config, null);
|
||||
_context.bandwidthLimiter().reinitialize();
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.bw.in", oldBWIn);
|
||||
}
|
||||
}
|
||||
if (inParams.containsKey("i2p.router.net.bw.out")) {
|
||||
String oldBWOut = _context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.bw.out")) != null) {
|
||||
Integer rate;
|
||||
try {
|
||||
rate = Integer.parseInt(inParam);
|
||||
if (rate < 0 || inParam.length() == 0)
|
||||
throw new NumberFormatException();
|
||||
} catch (NumberFormatException e) {
|
||||
return new JSONRPC2Response(
|
||||
new JSONRPC2Error(JSONRPC2Error.INVALID_PARAMS.getCode(),
|
||||
"\"i2p.router.net.bw.out\" A positive integer must supplied, " + inParam + " isn't valid"),
|
||||
req.getID());
|
||||
}
|
||||
Integer burstRate = (rate * BW_BURST_PCT) / 100;
|
||||
Integer burstSize = (burstRate * BW_BURST_TIME);
|
||||
if (oldBWOut == null || !oldBWOut.equals(rate.toString())) {
|
||||
Map<String, String> config = new HashMap<String, String>();
|
||||
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, rate.toString());
|
||||
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, burstRate.toString());
|
||||
config.put(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, burstSize.toString());
|
||||
_context.router().saveConfig(config, null);
|
||||
_context.bandwidthLimiter().reinitialize();
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.bw.out", oldBWOut);
|
||||
}
|
||||
}
|
||||
if (inParams.containsKey("i2p.router.net.laptopmode")) {
|
||||
String oldLaptopMode = _context.getProperty(UDPTransport.PROP_LAPTOP_MODE);
|
||||
if ((inParam = (String) inParams.get("i2p.router.net.laptopmode")) != null) {
|
||||
if (oldLaptopMode == null || !oldLaptopMode.equals(inParam.trim())) {
|
||||
_context.router().saveConfig(UDPTransport.PROP_LAPTOP_MODE, String.valueOf(inParam));
|
||||
}
|
||||
settingsSaved = true;
|
||||
} else {
|
||||
outParams.put("i2p.router.net.laptopmode", oldLaptopMode);
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsSaved)
|
||||
_context.router().saveConfig();
|
||||
|
||||
outParams.put("SettingsSaved", settingsSaved);
|
||||
outParams.put("RestartNeeded", restartNeeded);
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
}
|
@@ -0,0 +1,213 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.transport.TransportUtil;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class RouterInfoHandler implements RequestHandler {
|
||||
private final JSONRPC2Helper _helper;
|
||||
private final RouterContext _context;
|
||||
|
||||
public RouterInfoHandler(RouterContext ctx, JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] { "RouterInfo" };
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("RouterInfo")) {
|
||||
return process(req);
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND,
|
||||
req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private JSONRPC2Response process(JSONRPC2Request req) {
|
||||
JSONRPC2Error err = _helper.validateParams(null, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
if (_context == null) {
|
||||
return new JSONRPC2Response(new JSONRPC2Error(
|
||||
JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"RouterContext was not initialized. Query failed"),
|
||||
req.getID());
|
||||
}
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
Map outParams = new HashMap();
|
||||
|
||||
if (inParams.containsKey("i2p.router.version")) {
|
||||
try {
|
||||
Class rvClass = Class.forName("net.i2p.router.RouterVersion");
|
||||
java.lang.reflect.Field field = rvClass.getDeclaredField("FULL_VERSION");
|
||||
String fullVersion = (String) field.get(new RouterVersion());
|
||||
outParams.put("i2p.router.version", fullVersion);
|
||||
} catch (Exception e) {} // Ignore
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.uptime")) {
|
||||
Router router = _context.router();
|
||||
if (router == null) {
|
||||
outParams.put("i2p.router.uptime", 0);
|
||||
} else {
|
||||
outParams.put("i2p.router.uptime", router.getUptime());
|
||||
}
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.status")) {
|
||||
outParams.put("i2p.router.status", _context.throttle().getTunnelStatus());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.status")) {
|
||||
outParams.put("i2p.router.net.status", getNetworkStatus().ordinal());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.inbound.1s")) {
|
||||
outParams.put("i2p.router.net.bw.inbound.1s", _context.bandwidthLimiter().getReceiveBps());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.outbound.1s")) {
|
||||
outParams.put("i2p.router.net.bw.outbound.1s", _context.bandwidthLimiter().getSendBps());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.inbound.15s")) {
|
||||
outParams.put("i2p.router.net.bw.inbound.15s", _context.bandwidthLimiter().getReceiveBps15s());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.bw.outbound.15s")) {
|
||||
outParams.put("i2p.router.net.bw.outbound.15s", _context.bandwidthLimiter().getSendBps15s());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.tunnels.participating")) {
|
||||
outParams.put("i2p.router.net.tunnels.participating", _context.tunnelManager().getParticipatingCount());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.netdb.knownpeers")) {
|
||||
// Why max(-1, 0) is used I don't know, it is the implementation used in the router console.
|
||||
outParams.put("i2p.router.netdb.knownpeers", Math.max(_context.netDb().getKnownRouters() - 1, 0));
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.netdb.activepeers")) {
|
||||
outParams.put("i2p.router.netdb.activepeers", _context.commSystem().countActivePeers());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.netdb.fastpeers")) {
|
||||
outParams.put("i2p.router.netdb.fastpeers", _context.profileOrganizer().countFastPeers());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.netdb.highcapacitypeers")) {
|
||||
outParams.put("i2p.router.netdb.highcapacitypeers", _context.profileOrganizer().countHighCapacityPeers());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.netdb.isreseeding")) {
|
||||
outParams.put("i2p.router.netdb.isreseeding", Boolean.valueOf(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress")).booleanValue());
|
||||
}
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
private static enum NETWORK_STATUS {
|
||||
OK,
|
||||
TESTING,
|
||||
FIREWALLED,
|
||||
HIDDEN,
|
||||
WARN_FIREWALLED_AND_FAST,
|
||||
WARN_FIREWALLED_AND_FLOODFILL,
|
||||
WARN_FIREWALLED_WITH_INBOUND_TCP,
|
||||
WARN_FIREWALLED_WITH_UDP_DISABLED,
|
||||
ERROR_I2CP,
|
||||
ERROR_CLOCK_SKEW,
|
||||
ERROR_PRIVATE_TCP_ADDRESS,
|
||||
ERROR_SYMMETRIC_NAT,
|
||||
ERROR_UDP_PORT_IN_USE,
|
||||
ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL,
|
||||
ERROR_UDP_DISABLED_AND_TCP_UNSET,
|
||||
};
|
||||
|
||||
// Ripped out of SummaryHelper.java
|
||||
private NETWORK_STATUS getNetworkStatus() {
|
||||
if (_context.router().getUptime() > 60 * 1000
|
||||
&& (!_context.router().gracefulShutdownInProgress())
|
||||
&& !_context.clientManager().isAlive())
|
||||
return (NETWORK_STATUS.ERROR_I2CP);
|
||||
long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
|
||||
// Display the actual skew, not the offset
|
||||
if (Math.abs(skew) > 60 * 1000)
|
||||
return NETWORK_STATUS.ERROR_CLOCK_SKEW;
|
||||
if (_context.router().isHidden())
|
||||
return (NETWORK_STATUS.HIDDEN);
|
||||
|
||||
int status = _context.commSystem().getStatus().getCode();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
RouterAddress ra = _context.router().getRouterInfo().getTargetAddress("NTCP");
|
||||
if (ra == null || TransportUtil.isPubliclyRoutable(ra.getIP(), true))
|
||||
return NETWORK_STATUS.OK;
|
||||
return NETWORK_STATUS.ERROR_PRIVATE_TCP_ADDRESS;
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
return NETWORK_STATUS.ERROR_SYMMETRIC_NAT;
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
if (_context.router().getRouterInfo().getTargetAddress("NTCP") != null)
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_WITH_INBOUND_TCP;
|
||||
if (((FloodfillNetworkDatabaseFacade) _context.netDb()).floodfillEnabled())
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_AND_FLOODFILL;
|
||||
if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_AND_FAST;
|
||||
return NETWORK_STATUS.FIREWALLED;
|
||||
case CommSystemFacade.STATUS_HOSED:
|
||||
return NETWORK_STATUS.ERROR_UDP_PORT_IN_USE;
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
default:
|
||||
ra = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
if (ra == null && _context.router().getUptime() > 5 * 60 * 1000) {
|
||||
if (_context.commSystem().countActivePeers() <= 0)
|
||||
return NETWORK_STATUS.ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL;
|
||||
else if (_context.getProperty(NTCPTransport.PROP_I2NP_NTCP_HOSTNAME) == null || _context.getProperty(NTCPTransport.PROP_I2NP_NTCP_PORT) == null)
|
||||
return NETWORK_STATUS.ERROR_UDP_DISABLED_AND_TCP_UNSET;
|
||||
else
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_WITH_UDP_DISABLED;
|
||||
}
|
||||
return NETWORK_STATUS.TESTING;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,231 @@
|
||||
package net.i2p.i2pcontrol.servlets.jsonrpc2handlers;
|
||||
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Request;
|
||||
import com.thetransactioncompany.jsonrpc2.JSONRPC2Response;
|
||||
import com.thetransactioncompany.jsonrpc2.server.MessageContext;
|
||||
import com.thetransactioncompany.jsonrpc2.server.RequestHandler;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.networkdb.reseed.ReseedChecker;
|
||||
import net.i2p.update.UpdateManager;
|
||||
import net.i2p.update.UpdateType;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.tanukisoftware.wrapper.WrapperManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
*
|
||||
*/
|
||||
|
||||
public class RouterManagerHandler implements RequestHandler {
|
||||
private final JSONRPC2Helper _helper;
|
||||
private final RouterContext _context;
|
||||
|
||||
private final static int SHUTDOWN_WAIT = 1500;
|
||||
|
||||
|
||||
public RouterManagerHandler(RouterContext ctx, JSONRPC2Helper helper) {
|
||||
_helper = helper;
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
// Reports the method names of the handled requests
|
||||
public String[] handledRequests() {
|
||||
return new String[] { "RouterManager" };
|
||||
}
|
||||
|
||||
// Processes the requests
|
||||
public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) {
|
||||
if (req.getMethod().equals("RouterManager")) {
|
||||
return process(req);
|
||||
} else {
|
||||
// Method name not supported
|
||||
return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND,
|
||||
req.getID());
|
||||
}
|
||||
}
|
||||
|
||||
private JSONRPC2Response process(JSONRPC2Request req) {
|
||||
JSONRPC2Error err = _helper.validateParams(null, req);
|
||||
if (err != null)
|
||||
return new JSONRPC2Response(err, req.getID());
|
||||
|
||||
if (_context == null) {
|
||||
return new JSONRPC2Response(new JSONRPC2Error(
|
||||
JSONRPC2Error.INTERNAL_ERROR.getCode(),
|
||||
"RouterContext was not initialized. Query failed"),
|
||||
req.getID());
|
||||
}
|
||||
Map<String, Object> inParams = req.getNamedParams();
|
||||
final Map<String, Object> outParams = new HashMap<String, Object>(4);
|
||||
|
||||
if (inParams.containsKey("Shutdown")) {
|
||||
outParams.put("Shutdown", null);
|
||||
(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHUTDOWN_WAIT);
|
||||
} catch (InterruptedException e) {}
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD));
|
||||
_context.router().shutdown(Router.EXIT_HARD);
|
||||
}
|
||||
}).start();
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("Restart")) {
|
||||
outParams.put("Restart", null);
|
||||
(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHUTDOWN_WAIT);
|
||||
} catch (InterruptedException e) {}
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
_context.router().shutdown(Router.EXIT_HARD_RESTART);
|
||||
}
|
||||
}).start();
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("ShutdownGraceful")) {
|
||||
outParams.put("ShutdownGraceful", null);
|
||||
(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHUTDOWN_WAIT);
|
||||
} catch (InterruptedException e) {}
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL));
|
||||
_context.router().shutdownGracefully();
|
||||
}
|
||||
}).start();
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("RestartGraceful")) {
|
||||
outParams.put("RestartGraceful", null);
|
||||
(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHUTDOWN_WAIT);
|
||||
} catch (InterruptedException e) {}
|
||||
_context.addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
}).start();
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("Reseed")) {
|
||||
outParams.put("Reseed", null);
|
||||
(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
ReseedChecker reseeder = new ReseedChecker(_context);
|
||||
reseeder.requestReseed();
|
||||
}
|
||||
}).start();
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("FindUpdates")) {
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClientAppManager clmgr = I2PAppContext.getCurrentContext().clientAppManager();
|
||||
if (clmgr == null) {
|
||||
outParams.put("FindUpdates", "ClientAppManager is null");
|
||||
return;
|
||||
}
|
||||
UpdateManager upmgr = (UpdateManager) clmgr.getRegisteredApp(UpdateManager.APP_NAME);
|
||||
if (upmgr == null) {
|
||||
outParams.put("FindUpdates", "UpdateManager is null");
|
||||
return;
|
||||
}
|
||||
boolean updateIsAvailable = upmgr.checkAvailable(UpdateType.ROUTER_SIGNED) != null;
|
||||
outParams.put("FindUpdates", updateIsAvailable);
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException e) {}
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("Update")) {
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClientAppManager clmgr = I2PAppContext.getCurrentContext().clientAppManager();
|
||||
if (clmgr == null) {
|
||||
outParams.put("Update", "ClientAppManager is null");
|
||||
return;
|
||||
}
|
||||
UpdateManager upmgr = (UpdateManager) clmgr.getRegisteredApp(UpdateManager.APP_NAME);
|
||||
if (upmgr == null) {
|
||||
outParams.put("Update", "UpdateManager is null");
|
||||
return;
|
||||
}
|
||||
boolean updateStarted = upmgr.update(UpdateType.ROUTER_SIGNED);
|
||||
if (!updateStarted) {
|
||||
outParams.put("Update", "Update not started");
|
||||
return;
|
||||
}
|
||||
boolean isUpdating = upmgr.isUpdateInProgress(UpdateType.ROUTER_SIGNED);
|
||||
while (isUpdating) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (Exception e) {}
|
||||
isUpdating = upmgr.isUpdateInProgress(UpdateType.ROUTER_SIGNED);
|
||||
}
|
||||
outParams.put("Update", upmgr.getStatus());
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException e) {}
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
return new JSONRPC2Response(outParams, req.getID());
|
||||
}
|
||||
|
||||
public static class UpdateWrapperManagerTask implements Runnable {
|
||||
private int _exitCode;
|
||||
public UpdateWrapperManagerTask(int exitCode) {
|
||||
_exitCode = exitCode;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
WrapperManager.signalStopped(_exitCode);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user