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:
zzz
2018-11-25 13:26:43 +00:00
parent d6e350184c
commit d4caafb592
44 changed files with 10640 additions and 1 deletions

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}