forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.test2' (head 70ae5494bd7255a03f80838a2f3d8e7c0ce86634)
to branch 'i2p.i2p' (head 05a201cc5c1bd841f32e9268b3019b3a3447f4f3)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Serve plugin icons, at /Plugins/pluginicon?plugin=foo
|
||||
*
|
||||
* @author cacapo
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public class CodedIconRendererServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 16851750L;
|
||||
|
||||
private static final String base = I2PAppContext.getGlobalContext().getBaseDir().getAbsolutePath();
|
||||
private static final String file = "docs" + File.separatorChar + "themes" + File.separatorChar + "console" + File.separatorChar + "images" + File.separatorChar + "plugin.png";
|
||||
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest srq, HttpServletResponse srs) throws ServletException, IOException {
|
||||
byte[] data;
|
||||
String name = srq.getParameter("plugin");
|
||||
data = NavHelper.getBinary(name);
|
||||
|
||||
//set as many headers as are common to any outcome
|
||||
|
||||
srs.setContentType("image/png");
|
||||
srs.setDateHeader("Expires", I2PAppContext.getGlobalContext().clock().now() + 86400000l);
|
||||
srs.setHeader("Cache-Control", "public, max-age=86400");
|
||||
OutputStream os = srs.getOutputStream();
|
||||
|
||||
//Binary data is present
|
||||
if(data != null){
|
||||
srs.setHeader("Content-Length", Integer.toString(data.length));
|
||||
int content = Arrays.hashCode(data);
|
||||
int chksum = srq.getIntHeader("If-None-Match");//returns -1 if no such header
|
||||
//Don't render if icon already present
|
||||
if(content != chksum){
|
||||
srs.setIntHeader("ETag", content);
|
||||
try{
|
||||
os.write(data);
|
||||
os.flush();
|
||||
os.close();
|
||||
}catch(IOException e){
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(getClass()).warn("Error writing binary image data for plugin", e);
|
||||
}
|
||||
} else {
|
||||
srs.sendError(304, "Not Modified");
|
||||
}
|
||||
} else {
|
||||
//Binary data is not present but must be substituted by file on disk
|
||||
File pfile = new File(base, file);
|
||||
srs.setHeader("Content-Length", Long.toString(pfile.length()));
|
||||
try{
|
||||
long lastmod = pfile.lastModified();
|
||||
if(lastmod > 0){
|
||||
long iflast = srq.getDateHeader("If-Modified-Since");
|
||||
if(iflast >= ((lastmod/1000) * 1000)){
|
||||
srs.sendError(304, "Not Modified");
|
||||
} else {
|
||||
srs.setDateHeader("Last-Modified", lastmod);
|
||||
FileUtil.readFile(file, base, os);
|
||||
}
|
||||
|
||||
}
|
||||
} catch(IOException e) {
|
||||
if (!srs.isCommitted()) {
|
||||
srs.sendError(403, e.toString());
|
||||
} else {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(getClass()).warn("Error serving plugin.png", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.crypto.CertUtil;
|
||||
import net.i2p.crypto.KeyStoreUtil;
|
||||
import net.i2p.router.crypto.FamilyKeyCrypto;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public class ConfigFamilyHandler extends FormHandler {
|
||||
|
||||
@Override
|
||||
protected void processForm() {
|
||||
|
||||
if (_action.equals(_t("Create Router Family"))) {
|
||||
String family = getJettyString("family");
|
||||
String old = _context.getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME);
|
||||
if (family == null || family.trim().length() <= 0) {
|
||||
addFormError(_t("You must enter a family name"));
|
||||
} else if (old != null) {
|
||||
addFormError("Family already configured: " + family);
|
||||
} else if (family.contains("/") || family.contains("\\")) {
|
||||
addFormError("Bad characters in Family: " + family);
|
||||
} else if (_context.router().saveConfig(FamilyKeyCrypto.PROP_FAMILY_NAME, family.trim())) {
|
||||
addFormNotice(_t("Configuration saved successfully."));
|
||||
addFormError(_t("Restart required to take effect"));
|
||||
} else {
|
||||
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||
}
|
||||
} else if (_action.equals(_t("Join Router Family"))) {
|
||||
InputStream in = _requestWrapper.getInputStream("file");
|
||||
try {
|
||||
// non-null but zero bytes if no file entered, don't know why
|
||||
if (in == null || in.available() <= 0) {
|
||||
addFormError(_t("You must enter a file"));
|
||||
return;
|
||||
}
|
||||
// load data
|
||||
PrivateKey pk = CertUtil.loadPrivateKey(in);
|
||||
List<X509Certificate> certs = CertUtil.loadCerts(in);
|
||||
String family = CertUtil.getSubjectValue(certs.get(0), "CN");
|
||||
if (family == null) {
|
||||
addFormError("Bad certificate - No Subject CN");
|
||||
}
|
||||
if (family.endsWith(FamilyKeyCrypto.CN_SUFFIX) && family.length() > FamilyKeyCrypto.CN_SUFFIX.length())
|
||||
family = family.substring(0, family.length() - FamilyKeyCrypto.CN_SUFFIX.length());
|
||||
// store to keystore
|
||||
File ks = new SecureDirectory(_context.getConfigDir(), "keystore");
|
||||
if (!ks.exists());
|
||||
ks.mkdirs();
|
||||
ks = new File(ks, FamilyKeyCrypto.KEYSTORE_PREFIX + family + FamilyKeyCrypto.KEYSTORE_SUFFIX);
|
||||
String keypw = KeyStoreUtil.randomString();
|
||||
KeyStoreUtil.storePrivateKey(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, family, keypw, pk, certs);
|
||||
// store certificate
|
||||
File cf = new SecureDirectory(_context.getConfigDir(), "certificates");
|
||||
if (!cf.exists());
|
||||
cf.mkdirs();
|
||||
cf = new SecureDirectory(cf, "family");
|
||||
if (!ks.exists());
|
||||
ks.mkdirs();
|
||||
cf = new File(cf, family + FamilyKeyCrypto.CERT_SUFFIX);
|
||||
// ignore failure
|
||||
KeyStoreUtil.exportCert(ks, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD, family, cf);
|
||||
// save config
|
||||
Map<String, String> changes = new HashMap<String, String>();
|
||||
changes.put(FamilyKeyCrypto.PROP_FAMILY_NAME, family);
|
||||
changes.put(FamilyKeyCrypto.PROP_KEY_PASSWORD, keypw);
|
||||
changes.put(FamilyKeyCrypto.PROP_KEYSTORE_PASSWORD, KeyStoreUtil.DEFAULT_KEYSTORE_PASSWORD);
|
||||
if (_context.router().saveConfig(changes, null)) {
|
||||
addFormNotice("Family key configured for router family: " + family);
|
||||
addFormError(_t("Restart required to take effect"));
|
||||
} else {
|
||||
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||
}
|
||||
} catch (GeneralSecurityException gse) {
|
||||
addFormError(_t("Load from file failed") + " - " + gse);
|
||||
} catch (IOException ioe) {
|
||||
addFormError(_t("Load from file failed") + " - " + ioe);
|
||||
} finally {
|
||||
// it's really a ByteArrayInputStream but we'll play along...
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else if (_action.equals(_t("Leave Router Family"))) {
|
||||
List<String> removes = new ArrayList<String>();
|
||||
removes.add(FamilyKeyCrypto.PROP_FAMILY_NAME);
|
||||
removes.add(FamilyKeyCrypto.PROP_KEY_PASSWORD);
|
||||
removes.add(FamilyKeyCrypto.PROP_KEYSTORE_PASSWORD);
|
||||
if (_context.router().saveConfig(null, removes)) {
|
||||
addFormNotice(_t("Configuration saved successfully."));
|
||||
addFormError(_t("Restart required to take effect"));
|
||||
} else {
|
||||
addFormError(_t("Error saving the configuration (applied but not saved) - please see the error logs"));
|
||||
}
|
||||
}
|
||||
//addFormError(_t("Unsupported") + ' ' + _action + '.');
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import net.i2p.router.crypto.FamilyKeyCrypto;
|
||||
|
||||
/**
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public class ConfigFamilyHelper extends HelperBase {
|
||||
|
||||
public String getFamily() {
|
||||
return _context.getProperty(FamilyKeyCrypto.PROP_FAMILY_NAME, "");
|
||||
}
|
||||
|
||||
public String getKeyPW() {
|
||||
return _context.getProperty(FamilyKeyCrypto.PROP_KEY_PASSWORD, "");
|
||||
}
|
||||
}
|
@@ -19,13 +19,13 @@ public class ConfigNavHelper extends HelperBase {
|
||||
private static final String pages[] =
|
||||
{"", "net", "ui", "sidebar", "home", "service", "update", "tunnels",
|
||||
"clients", "peer", "keyring", "logging", "stats",
|
||||
"reseed", "advanced" };
|
||||
"reseed", "advanced", "family" };
|
||||
|
||||
private static final String titles[] =
|
||||
{_x("Bandwidth"), _x("Network"), _x("UI"), _x("Summary Bar"), _x("Home Page"),
|
||||
_x("Service"), _x("Update"), _x("Tunnels"),
|
||||
_x("Clients"), _x("Peers"), _x("Keyring"), _x("Logging"), _x("Stats"),
|
||||
_x("Reseeding"), _x("Advanced") };
|
||||
_x("Reseeding"), _x("Advanced"), _x("Router Family") };
|
||||
|
||||
/** @since 0.9.19 */
|
||||
private static class Tab {
|
||||
|
@@ -67,6 +67,7 @@ public class HomeHelper extends HelperBase {
|
||||
//"Salt" + S + "salt.i2p" + S + "http://salt.i2p/" + S + I + "salt_console.png" + S +
|
||||
"stats.i2p" + S + _x("I2P Network Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "chart_line.png" + S +
|
||||
_x("Technical Docs") + S + _x("Technical documentation") + S + "http://i2p-projekt.i2p/how" + S + I + "education.png" + S +
|
||||
_x("The Tin Hat") + S + _x("Privacy guides and tutorials") + S + "http://secure.thetinhat.i2p/" + S + I + "thetinhat.png" + S +
|
||||
_x("Trac Wiki") + S + S + "http://trac.i2p2.i2p/" + S + I + "billiard_marker.png" + S +
|
||||
//_x("Ugha's Wiki") + S + S + "http://ugha.i2p/" + S + I + "billiard_marker.png" + S +
|
||||
_x("Sponge's main site") + S + _x("Seedless and the Robert BitTorrent applications") + S + "http://sponge.i2p/" + S + I + "user_astronaut.png" + S +
|
||||
|
@@ -13,6 +13,7 @@ public class NavHelper {
|
||||
private static final Map<String, String> _apps = new ConcurrentHashMap<String, String>(4);
|
||||
private static final Map<String, String> _tooltips = new ConcurrentHashMap<String, String>(4);
|
||||
private static final Map<String, String> _icons = new ConcurrentHashMap<String, String>(4);
|
||||
private static final Map<String, byte[]> _binary = new ConcurrentHashMap<String, byte[]>(4);
|
||||
|
||||
/**
|
||||
* To register a new client application so that it shows up on the router
|
||||
@@ -40,6 +41,29 @@ public class NavHelper {
|
||||
_icons.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve binary icon for a plugin
|
||||
* @param name plugin name
|
||||
* @return null if not found
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static byte[] getBinary(String name){
|
||||
if(name != null)
|
||||
return _binary.get(name);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store binary icon for a plugin
|
||||
* @param name plugin name
|
||||
* @since 0.9.25
|
||||
*/
|
||||
public static void setBinary(String name, byte[] arr){
|
||||
_binary.put(name, arr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translated string is loaded by PluginStarter
|
||||
* @param ctx unused
|
||||
|
@@ -22,6 +22,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterVersion;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
@@ -353,6 +354,18 @@ public class PluginStarter implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
//handle console icons for plugins without web-resources through prop icon-code
|
||||
String fullprop = props.getProperty("icon-code");
|
||||
if(fullprop != null && fullprop.length() > 1){
|
||||
byte[] decoded = Base64.decode(fullprop);
|
||||
if(decoded != null) {
|
||||
NavHelper.setBinary(appName, decoded);
|
||||
iconfile = "/Plugins/pluginicon?plugin=" + appName;
|
||||
} else {
|
||||
iconfile = "/themes/console/images/plugin.png";
|
||||
}
|
||||
}
|
||||
|
||||
// load and start things in clients.config
|
||||
File clientConfig = new File(pluginDir, "clients.config");
|
||||
if (clientConfig.exists()) {
|
||||
|
88
apps/routerconsole/jsp/configfamily.jsp
Normal file
88
apps/routerconsole/jsp/configfamily.jsp
Normal file
@@ -0,0 +1,88 @@
|
||||
<%@page contentType="text/html"%>
|
||||
<%@page pageEncoding="UTF-8"%>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<%@include file="css.jsi" %>
|
||||
<%=intl.title("config router family")%>
|
||||
<script src="/js/ajax.js" type="text/javascript"></script>
|
||||
<%@include file="summaryajax.jsi" %>
|
||||
</head><body onload="initAjax()">
|
||||
|
||||
<%@include file="summary.jsi" %>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHelper" id="familyHelper" scope="request" />
|
||||
<jsp:setProperty name="familyHelper" property="contextId" value="<%=(String)session.getAttribute(\"i2p.contextId\")%>" />
|
||||
<h1><%=intl._t("I2P Router Family Configuration")%></h1>
|
||||
<div class="main" id="main">
|
||||
<%@include file="confignav.jsi" %>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.ConfigFamilyHandler" id="formhandler" scope="request" />
|
||||
<%@include file="formhandler.jsi" %>
|
||||
|
||||
<p><%=intl._t("Routers in the same family share a family key.")%>
|
||||
<%=intl._t("To start a new family, enter a family name.")%>
|
||||
<%=intl._t("To join an existing family, import the private key you exported from a router in the family.")%>
|
||||
</p>
|
||||
|
||||
<%
|
||||
String family = familyHelper.getFamily();
|
||||
if (family.length() <= 0) {
|
||||
// no family yet
|
||||
%>
|
||||
<div class="configure"><form action="" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Create Router Family")%></h3>
|
||||
<p><%=intl._t("Family Name")%> :
|
||||
<input name="family" type="text" size="30" value="" />
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="accept" value="<%=intl._t("Create Router Family")%>" />
|
||||
</div></form></div>
|
||||
|
||||
<div class="configure">
|
||||
<form action="" method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Join Router Family")%></h3>
|
||||
<p><%=intl._t("Import the secret family key that you exported from an existing router in the family.")%>
|
||||
<p><%=intl._t("Select secret key file")%> :
|
||||
<input name="file" type="file" value="" />
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="download" value="<%=intl._t("Join Router Family")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
} else {
|
||||
// family is configured
|
||||
String keypw = familyHelper.getKeyPW();
|
||||
if (keypw.length() > 0) {
|
||||
// family is active
|
||||
%>
|
||||
<div class="configure">
|
||||
<form action="/exportfamily" method="GET">
|
||||
<h3><%=intl._t("Export Family Key")%></h3>
|
||||
<p><%=intl._t("Export the secret family key to be imported into other routers you control.")%>
|
||||
</p>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="go" value="<%=intl._t("Export Family Key")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
} else {
|
||||
// family is not active
|
||||
%>
|
||||
<p><b><%=intl._t("Restart required to activate family {0}.", '"' + family + '"')%>
|
||||
<%=intl._t("After restarting, you may export the family key.")%></b></p>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<div class="configure"><form action="" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<h3><%=intl._t("Leave Router Family")%></h3>
|
||||
<p><%=intl._t("No longer be a member of the family {0}.", '"' + family + '"')%>
|
||||
<div class="formaction">
|
||||
<input type="submit" name="action" class="delete" value="<%=intl._t("Leave Router Family")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div></body></html>
|
@@ -78,7 +78,7 @@
|
||||
<b><%=intl._t("Use non-SSL only")%></b></td></tr>
|
||||
<tr><td class="mediumtags" align="right"><b><%=intl._t("Reseed URLs")%>:</b></td>
|
||||
<td><textarea wrap="off" name="reseedURL" cols="60" rows="7" spellcheck="false"><jsp:getProperty name="reseedHelper" property="reseedURL" /></textarea>
|
||||
<div class="formaction"><input type="submit" name="action" value="<%=intl._t("Reset URL list")%>" /></div>
|
||||
<div class="formaction"><input type="submit" name="action" class="reload" value="<%=intl._t("Reset URL list")%>" /></div>
|
||||
</td></tr>
|
||||
|
||||
<tr><td class="mediumtags" align="right"><b><%=intl._t("Enable HTTP Proxy?")%></b></td>
|
||||
|
35
apps/routerconsole/jsp/exportfamily.jsp
Normal file
35
apps/routerconsole/jsp/exportfamily.jsp
Normal file
@@ -0,0 +1,35 @@
|
||||
<%
|
||||
try {
|
||||
net.i2p.I2PAppContext ctx = net.i2p.I2PAppContext.getGlobalContext();
|
||||
String family = ctx.getProperty("netdb.family.name");
|
||||
String keypw = ctx.getProperty("netdb.family.keyPassword");
|
||||
String kspw = ctx.getProperty("netdb.family.keystorePassword", "changeit");
|
||||
if (family == null || keypw == null) {
|
||||
response.sendError(404);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
response.setDateHeader("Expires", 0);
|
||||
response.addHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate");
|
||||
response.addHeader("Pragma", "no-cache");
|
||||
String name = "family-" + family + "-secret.crt";
|
||||
response.setContentType("application/x-x509-ca-cert; name=\"" + name + '"');
|
||||
response.addHeader("Content-Disposition", "attachment; filename=\"" + name + '"');
|
||||
java.io.File ks = new java.io.File(ctx.getConfigDir(), "keystore");
|
||||
ks = new java.io.File(ks, "family-" + family + ".ks");
|
||||
java.io.OutputStream cout = response.getOutputStream();
|
||||
net.i2p.crypto.KeyStoreUtil.exportPrivateKey(ks, kspw, family, keypw, cout);
|
||||
} catch (java.security.GeneralSecurityException gse) {
|
||||
throw new java.io.IOException("key error", gse);
|
||||
}
|
||||
} catch (java.io.IOException ioe) {
|
||||
// prevent 'Committed' IllegalStateException from Jetty
|
||||
if (!response.isCommitted()) {
|
||||
response.sendError(403, ioe.toString());
|
||||
} else {
|
||||
// Jetty doesn't log this
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
// don't worry about a newline after this
|
||||
%>
|
@@ -14,6 +14,18 @@
|
||||
</filter-mapping>
|
||||
|
||||
<!-- precompiled servlets -->
|
||||
|
||||
<servlet>
|
||||
<servlet-name>net.i2p.router.web.CodedIconRendererServlet</servlet-name>
|
||||
<servlet-class>net.i2p.router.web.CodedIconRendererServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.router.web.CodedIconRendererServlet</servlet-name>
|
||||
<url-pattern>/Plugins/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
|
||||
<!-- yeah, i'm lazy, using a jsp instead of a servlet.. -->
|
||||
<servlet-mapping>
|
||||
|
Reference in New Issue
Block a user