propagate from branch 'i2p.i2p.zzz.test2' (head c182b371bc28158dd47262b89e5bd0cdda29e07b)

to branch 'i2p.i2p' (head 4fc776f7b76b028bb890affccfdcfbefbb932c58)
This commit is contained in:
zzz
2014-08-10 13:56:15 +00:00
30 changed files with 594 additions and 157 deletions

View File

@ -119,15 +119,16 @@ public class BOB implements Runnable, ClientApp {
public final static String PROP_BOB_HOST = "BOB.host";
public final static String PROP_CFG_VER = "BOB.CFG.VER";
/** unused when started via the ClientApp interface */
private static BOB _bob;
private NamedDB database;
private Properties props = new Properties();
private AtomicBoolean spin = new AtomicBoolean(true);
private final NamedDB database;
private final Properties props = new Properties();
private final AtomicBoolean spin = new AtomicBoolean(true);
private static final String P_RUNNING = "RUNNING";
private static final String P_STARTING = "STARTING";
private static final String P_STOPPING = "STOPPING";
private AtomicBoolean lock = new AtomicBoolean(false);
private final AtomicBoolean lock = new AtomicBoolean(false);
// no longer used.
// private static int maxConnections = 0;
@ -143,7 +144,8 @@ public class BOB implements Runnable, ClientApp {
* Stop BOB gracefully
* @deprecated unused
*/
public static void stop() {
public synchronized static void stop() {
if (_bob != null)
_bob.shutdown(null);
}
@ -189,7 +191,7 @@ public class BOB implements Runnable, ClientApp {
*
* @param args
*/
public static void main(String[] args) {
public synchronized static void main(String[] args) {
try {
_bob = new BOB(I2PAppContext.getGlobalContext(), null, args);
_bob.startup();

View File

@ -43,7 +43,9 @@ class PluginUpdateHandler implements Checker, Updater {
Properties props = PluginStarter.pluginProperties(_context, appName);
String oldVersion = props.getProperty("version");
String xpi2pURL = props.getProperty("updateURL");
String xpi2pURL = props.getProperty("updateURL.su3");
if (xpi2pURL == null)
xpi2pURL = props.getProperty("updateURL");
List<URI> updateSources = null;
if (xpi2pURL != null) {
try {

View File

@ -1,6 +1,7 @@
package net.i2p.router.update;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.IllegalArgumentException;
import java.net.URI;
@ -9,6 +10,7 @@ import java.util.Map;
import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.crypto.SU3File;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@ -121,7 +123,6 @@ class PluginUpdateRunner extends UpdateRunner {
@Override
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
boolean update = false;
updateStatus("<b>" + _("Plugin downloaded") + "</b>");
File f = new File(_updateFile);
File appDir = new SecureDirectory(_context.getConfigDir(), PLUGIN_DIR);
@ -130,7 +131,43 @@ class PluginUpdateRunner extends UpdateRunner {
statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
return;
}
boolean isSU3;
try {
isSU3 = isSU3File(f);
} catch (IOException ioe) {
f.delete();
statusDone("<b>" + ioe + "</b>");
return;
}
if (isSU3)
processSU3(f, appDir, url);
else
processSUD(f, appDir, url);
}
/**
* @since 0.9.15
* @return if SU3
*/
private static boolean isSU3File(File f) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
for (int i = 0; i < SU3File.MAGIC.length(); i++) {
if (fis.read() != SU3File.MAGIC.charAt(i))
return false;
}
return true;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
/**
* @since 0.9.15
* @return success
*/
private void processSUD(File f, File appDir, String url) {
TrustedUpdate up = new TrustedUpdate(_context);
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
// extract to a zip file whether the sig is good or not, so we can get the properties file
@ -141,27 +178,9 @@ class PluginUpdateRunner extends UpdateRunner {
to.delete();
return;
}
File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip");
if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) {
f.delete();
to.delete();
FileUtil.rmdir(tempDir, false);
statusDone("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
Properties props = getPluginConfig(f, to, url);
if (props == null)
return;
}
File installProps = new File(tempDir, "plugin.config");
Properties props = new OrderedProperties();
try {
DataHelper.loadProps(props, installProps);
} catch (IOException ioe) {
f.delete();
to.delete();
FileUtil.rmdir(tempDir, false);
statusDone("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
return;
}
// we don't need this anymore, we will unzip again
FileUtil.rmdir(tempDir, false);
// ok, now we check sigs and deal with a bad sig
String pubkey = props.getProperty("key");
@ -251,7 +270,90 @@ class PluginUpdateRunner extends UpdateRunner {
String sudVersion = TrustedUpdate.getVersionString(f);
f.delete();
processFinal(to, appDir, url, props, sudVersion, pubkey, signer);
}
/**
* @since 0.9.15
*/
private void processSU3(File f, File appDir, String url) {
SU3File su3 = new SU3File(_context, f);
File to = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + ZIP);
String sudVersion;
String signingKeyName;
try {
su3.verifyAndMigrate(to);
if (su3.getFileType() != SU3File.TYPE_ZIP)
throw new IOException("bad file type");
if (su3.getContentType() != SU3File.CONTENT_PLUGIN)
throw new IOException("bad content type");
sudVersion = su3.getVersionString();
signingKeyName = su3.getSignerString();
} catch (IOException ioe) {
statusDone("<b>" + ioe + ' ' + _("from {0}", url) + " </b>");
f.delete();
to.delete();
return;
}
Properties props = getPluginConfig(f, to, url);
if (props == null)
return;
String signer = props.getProperty("signer");
if (signer == null || signer.length() <= 0) {
f.delete();
to.delete();
statusDone("<b>" + _("Plugin from {0} contains an invalid key", url) + "</b>");
return;
}
if (!signer.equals(signingKeyName)) {
f.delete();
to.delete();
if (signingKeyName == null)
_log.error("Failed to verify plugin signature, corrupt plugin or bad signature, signed by: " + signer);
else
// shouldn't happen
_log.error("Plugin signer \"" + signer + "\" does not match new signer in plugin.config file \"" + signingKeyName + "\"");
statusDone("<b>" + _("Plugin signature verification of {0} failed", url) + "</b>");
return;
}
processFinal(to, appDir, url, props, sudVersion, null, signer);
}
/**
* @since 0.9.15
* @return null on error
*/
private Properties getPluginConfig(File f, File to, String url) {
File tempDir = new File(_context.getTempDir(), "tmp" + _context.random().nextInt() + "-unzip");
if (!FileUtil.extractZip(to, tempDir, Log.ERROR)) {
f.delete();
to.delete();
FileUtil.rmdir(tempDir, false);
statusDone("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
return null;
}
File installProps = new File(tempDir, "plugin.config");
Properties props = new OrderedProperties();
try {
DataHelper.loadProps(props, installProps);
} catch (IOException ioe) {
f.delete();
to.delete();
statusDone("<b>" + _("Plugin from {0} does not contain the required configuration file", url) + "</b>");
return null;
} finally {
// we don't need this anymore, we will unzip again
FileUtil.rmdir(tempDir, false);
}
return props;
}
/**
* @param pubkey null OK for su3
* @since 0.9.15
*/
private void processFinal(File to, File appDir, String url, Properties props, String sudVersion, String pubkey, String signer) {
boolean update = false;
String appName = props.getProperty("name");
String version = props.getProperty("version");
if (appName == null || version == null || appName.length() <= 0 || version.length() <= 0 ||
@ -302,14 +404,13 @@ class PluginUpdateRunner extends UpdateRunner {
DataHelper.loadProps(oldProps, oldPropFile);
} catch (IOException ioe) {
to.delete();
FileUtil.rmdir(tempDir, false);
statusDone("<b>" + _("Installed plugin does not contain the required configuration file", url) + "</b>");
return;
}
String oldPubkey = oldProps.getProperty("key");
String oldKeyName = oldProps.getProperty("signer");
String oldAppName = oldProps.getProperty("name");
if ((!pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) {
if ((pubkey != null && !pubkey.equals(oldPubkey)) || (!signer.equals(oldKeyName)) || (!appName.equals(oldAppName))) {
to.delete();
statusDone("<b>" + _("Signature of downloaded plugin does not match installed plugin") + "</b>");
return;

View File

@ -390,7 +390,9 @@ public class ConfigClientsHandler extends FormHandler {
private void updatePlugin(String app) {
Properties props = PluginStarter.pluginProperties(_context, app);
String url = props.getProperty("updateURL");
String url = props.getProperty("updateURL.su3");
if (url == null)
url = props.getProperty("updateURL");
if (url == null) {
addFormError(_("No update URL specified for {0}",app));
return;

View File

@ -248,7 +248,9 @@ public class ConfigClientsHelper extends HelperBase {
desc.append("<tr><td>")
.append("<a href=\"").append(s).append("\">").append(_("Website")).append("</a><td>&nbsp;");
}
String updateURL = stripHTML(appProps, "updateURL");
String updateURL = stripHTML(appProps, "updateURL.su3");
if (updateURL == null)
updateURL = stripHTML(appProps, "updateURL");
if (updateURL != null) {
desc.append("<tr><td>")
.append("<a href=\"").append(updateURL).append("\">").append(_("Update link")).append("</a><td>&nbsp;");

View File

@ -298,9 +298,9 @@ public class ConfigNetHelper extends HelperBase {
StringBuilder buf = new StringBuilder(256);
buf.append("<select style=\"text-align: right !important;\" name=\"sharePercentage\">\n");
boolean found = false;
for (int i = 30; i <= 110; i += 10) {
for (int i = 100; i >= -10; i -= 10) {
int val = i;
if (i == 110) {
if (i == -10) {
if (found)
break;
else

View File

@ -161,6 +161,7 @@ public class SAMBridge implements Runnable, ClientApp {
*
* @param name name of the destination
* @return null if the name does not exist, or if it is improperly formatted
* @deprecated unused
*/
public Destination getDestination(String name) {
synchronized (nameToPrivKeys) {

View File

@ -12,6 +12,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.datagram.I2PDatagramDissector;
import net.i2p.client.datagram.I2PDatagramMaker;
@ -88,7 +89,9 @@ class SAMDatagramSession extends SAMMessageSession {
synchronized (dgramMaker) {
dgram = dgramMaker.makeI2PDatagram(data);
}
return sendBytesThroughMessageSession(dest, dgram);
// TODO pass ports through
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM,
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
}
protected void messageReceived(byte[] msg) {

View File

@ -13,6 +13,7 @@ import java.nio.channels.SocketChannel;
import java.nio.ByteBuffer;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
@ -54,7 +55,7 @@ abstract class SAMHandler implements Runnable {
*/
protected SAMHandler(SocketChannel s,
int verMajor, int verMinor, Properties i2cpProps) throws IOException {
_log = new Log(getClass());
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
socket = s;
this.verMajor = verMajor;

View File

@ -9,12 +9,13 @@ package net.i2p.sam;
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import java.nio.ByteBuffer;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.VersionComparator;
@ -26,6 +27,8 @@ class SAMHandlerFactory {
private static final String VERSION = "3.1";
private static final int HELLO_TIMEOUT = 60*1000;
/**
* Return the right SAM handler depending on the protocol version
* required by the client.
@ -36,17 +39,21 @@ class SAMHandlerFactory {
* @return A SAM protocol handler, or null if the client closed before the handshake
*/
public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps) throws SAMException {
String line;
StringTokenizer tok;
Log log = new Log(SAMHandlerFactory.class);
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class);
try {
line = DataHelper.readLine(s.socket().getInputStream());
Socket sock = s.socket();
sock.setSoTimeout(HELLO_TIMEOUT);
String line = DataHelper.readLine(sock.getInputStream());
sock.setSoTimeout(0);
if (line == null) {
log.debug("Connection closed by client");
return null;
}
tok = new StringTokenizer(line.trim(), " ");
} catch (SocketTimeoutException e) {
throw new SAMException("Timeout waiting for HELLO VERSION", e);
} catch (IOException e) {
throw new SAMException("Error reading from socket", e);
} catch (Exception e) {

View File

@ -13,6 +13,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
@ -33,10 +34,8 @@ import net.i2p.util.Log;
abstract class SAMMessageSession {
protected final Log _log;
private I2PSession session = null;
private SAMMessageSessionHandler handler = null;
private I2PSession session;
private SAMMessageSessionHandler handler;
/**
* Initialize a new SAM message-based session.
@ -48,7 +47,7 @@ abstract class SAMMessageSession {
* @throws I2PSessionException
*/
protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException {
_log = new Log(getClass());
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(dest));
initSAMMessageSession(bais, props);
}
@ -103,11 +102,15 @@ abstract class SAMMessageSession {
*
* @param dest Destination
* @param data Bytes to be sent
* @param proto I2CP protocol
* @param fromPort I2CP from port
* @param toPort I2CP to port
*
* @return True if the data was sent, false otherwise
* @throws DataFormatException
* @throws DataFormatException on unknown / bad dest
*/
protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException {
protected boolean sendBytesThroughMessageSession(String dest, byte[] data,
int proto, int fromPort, int toPort) throws DataFormatException {
Destination d = SAMUtils.getDest(dest);
if (_log.shouldLog(Log.DEBUG)) {
@ -115,7 +118,7 @@ abstract class SAMMessageSession {
}
try {
return session.sendMessage(d, data);
return session.sendMessage(d, data, proto, fromPort, toPort);
} catch (I2PSessionException e) {
_log.error("I2PSessionException while sending data", e);
return false;

View File

@ -12,6 +12,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataFormatException;
import net.i2p.util.Log;
@ -73,7 +74,9 @@ class SAMRawSession extends SAMMessageSession {
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
if (data.length > RAW_SIZE_MAX)
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
return sendBytesThroughMessageSession(dest, data);
// TODO pass ports through
return sendBytesThroughMessageSession(dest, data, I2PSession.PROTO_DATAGRAM_RAW,
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
}
protected void messageReceived(byte[] msg) {

View File

@ -25,6 +25,7 @@ import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.streaming.I2PServerSocket;
@ -107,7 +108,7 @@ class SAMStreamSession {
public SAMStreamSession(InputStream destStream, String dir,
Properties props, SAMStreamReceiver recv) throws IOException, DataFormatException, SAMException {
this.recv = recv;
_log = new Log(getClass());
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
if (_log.shouldLog(Log.DEBUG))
_log.debug("SAM STREAM session instantiated");

View File

@ -123,26 +123,12 @@ class SAMUtils {
* Resolved the specified hostname.
*
* @param name Hostname to be resolved
* @param pubKey A stream to write the Destination public key (may be null)
*
* @return the Destination for the specified hostname, or null if not found
*/
public static Destination lookupHost(String name, OutputStream pubKey) {
private static Destination lookupHost(String name) {
NamingService ns = I2PAppContext.getGlobalContext().namingService();
Destination dest = ns.lookup(name);
if ((pubKey != null) && (dest != null)) {
try {
dest.writeBytes(pubKey);
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (DataFormatException e) {
e.printStackTrace();
return null;
}
}
return dest;
}
@ -151,20 +137,26 @@ class SAMUtils {
*
* @param s Hostname or key to be resolved
*
* @return the Destination for the specified hostname, or null if not found
* @return the Destination for the specified hostname, non-null
* @throws DataFormatException on bad Base 64 or name not found
*/
public static Destination getDest(String s) throws DataFormatException
{
Destination d = new Destination() ;
try {
d.fromBase64(s);
} catch (DataFormatException e) {
d = lookupHost(s, null);
if ( d==null ) {
throw e ;
// NamingService caches b64 so just use it for everything
// TODO: Add a static local cache here so SAM doesn't flush the
// NamingService cache
Destination d = lookupHost(s);
if (d == null) {
String msg;
if (s.length() >= 516)
msg = "Bad Base64 dest: ";
else if (s.length() == 60 && s.endsWith(".b32.i2p"))
msg = "Lease set not found: ";
else
msg = "Host name not found: ";
throw new DataFormatException(msg + s);
}
}
return d ;
return d;
}
/**

View File

@ -246,7 +246,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
destKeystream = bridge.getKeystream(dest);
if (destKeystream == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
_log.debug("Custom destination specified [" + dest + "] but it isn't known, creating a new one");
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(baos, null);
destKeystream = Base64.encode(baos.toByteArray());
@ -284,7 +284,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
&& !dir.equals("BOTH")) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
_log.debug("Unknown DIRECTION parameter value: [" + dir + "]");
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
}
props.remove("DIRECTION");

View File

@ -25,6 +25,7 @@ import java.util.Properties;
import java.util.HashMap;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
@ -159,7 +160,13 @@ class SAMv3Handler extends SAMv1Handler
ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]);
outBuf.put(inBuf);
outBuf.flip();
new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
// A new thread for every message is wildly inefficient...
//new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
// inline
// Even though we could be sending messages through multiple sessions,
// that isn't a common use case, and blocking should be rare.
// Inside router context, I2CP drops on overflow.
(new MessageDispatcher(outBuf.array())).run();
}
}
}
@ -194,9 +201,15 @@ class SAMv3Handler extends SAMv1Handler
SessionRecord rec = sSessionsHash.get(nick);
if (rec!=null) {
rec.getHandler().session.sendBytes(dest,data);
} else {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
if (log.shouldLog(Log.WARN))
log.warn("Dropping datagram, no session for " + nick);
}
} catch (Exception e) {
// FIXME log? throw?
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMv3Handler.class);
if (log.shouldLog(Log.WARN))
log.warn("Error handling datagram", e);
}
}
}

View File

@ -22,7 +22,6 @@ import net.i2p.crypto.HMACGenerator;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.Base64;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.internal.InternalClientManager;
@ -76,7 +75,7 @@ public class I2PAppContext {
protected final I2PProperties _overrideProps;
private StatManager _statManager;
private SessionKeyManager _sessionKeyManager;
protected SessionKeyManager _sessionKeyManager;
private NamingService _namingService;
private ElGamalEngine _elGamalEngine;
private ElGamalAESEngine _elGamalAESEngine;
@ -96,7 +95,7 @@ public class I2PAppContext {
private SimpleTimer2 _simpleTimer2;
private final PortMapper _portMapper;
private volatile boolean _statManagerInitialized;
private volatile boolean _sessionKeyManagerInitialized;
protected volatile boolean _sessionKeyManagerInitialized;
private volatile boolean _namingServiceInitialized;
private volatile boolean _elGamalEngineInitialized;
private volatile boolean _elGamalAESEngineInitialized;
@ -599,6 +598,9 @@ public class I2PAppContext {
* For client crypto within the router,
* use RouterContext.clientManager.getClientSessionKeyManager(dest)
*
* As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext.
* The dummy SKM does NOT handle session tags.
* Overridden in RouterContext to return the full TransientSessionKeyManager.
*/
public SessionKeyManager sessionKeyManager() {
if (!_sessionKeyManagerInitialized)
@ -606,11 +608,11 @@ public class I2PAppContext {
return _sessionKeyManager;
}
private void initializeSessionKeyManager() {
protected void initializeSessionKeyManager() {
synchronized (_lock3) {
if (_sessionKeyManager == null)
//_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManager = new TransientSessionKeyManager(this);
_sessionKeyManager = new SessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}

View File

@ -15,6 +15,8 @@ import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
@ -49,29 +51,36 @@ public class SU3File {
private int _versionLength;
private String _signer;
private int _signerLength;
private int _fileType = -1;
private ContentType _contentType;
private long _contentLength;
private PublicKey _signerPubkey;
private boolean _headerVerified;
private SigType _sigType;
private boolean _verifySignature = true;
private File _certFile;
private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3");
public static final String MAGIC = "I2Psu3";
private static final byte[] MAGIC_BYTES = DataHelper.getASCII(MAGIC);
private static final int FILE_VERSION = 0;
private static final int MIN_VERSION_BYTES = 16;
private static final int VERSION_OFFSET = 40; // Signature.SIGNATURE_BYTES; avoid early ctx init
private static final int TYPE_ZIP = 0;
public static final int TYPE_ZIP = 0;
public static final int TYPE_XML = 1;
public static final int CONTENT_UNKNOWN = 0;
public static final int CONTENT_ROUTER = 1;
public static final int CONTENT_PLUGIN = 2;
public static final int CONTENT_RESEED = 3;
public static final int CONTENT_NEWS = 4;
private enum ContentType {
UNKNOWN(CONTENT_UNKNOWN, "unknown"),
ROUTER(CONTENT_ROUTER, "router"),
PLUGIN(CONTENT_PLUGIN, "plugin"),
RESEED(CONTENT_RESEED, "reseed")
RESEED(CONTENT_RESEED, "reseed"),
NEWS(CONTENT_NEWS, "news")
;
private final int code;
@ -125,17 +134,44 @@ public class SU3File {
_file = file;
}
/**
* Should the signature be verified? Default true
* @since 0.9.15
*/
public void setVerifySignature(boolean shouldVerify) {
_verifySignature = shouldVerify;
}
/**
* Use this X.509 cert file for verification instead of $I2P/certificates/content_type/foo_at_mail.i2p
* @since 0.9.15
*/
private void setPublicKeyCertificate(File certFile) {
_certFile = certFile;
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*/
public String getVersionString() throws IOException {
verifyHeader();
return _version;
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*/
public String getSignerString() throws IOException {
verifyHeader();
return _signer;
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*
* @return null if unknown
* @since 0.9.9
*/
@ -145,6 +181,9 @@ public class SU3File {
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*
* @return -1 if unknown
* @since 0.9.9
*/
@ -154,6 +193,21 @@ public class SU3File {
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*
* @return -1 if unknown
* @since 0.9.15
*/
public int getFileType() throws IOException {
verifyHeader();
return _fileType;
}
/**
* This does not check the signature, but it will fail if the signer is unknown,
* unless setVerifySignature(false) has been called.
*
* Throws IOE if verify vails.
*/
public void verifyHeader() throws IOException {
@ -176,9 +230,9 @@ public class SU3File {
* Throws if verify vails.
*/
private void verifyHeader(InputStream in) throws IOException, DataFormatException {
byte[] magic = new byte[MAGIC.length];
byte[] magic = new byte[MAGIC_BYTES.length];
DataHelper.read(in, magic);
if (!DataHelper.eq(magic, MAGIC))
if (!DataHelper.eq(magic, MAGIC_BYTES))
throw new IOException("Not an su3 file");
skip(in, 1);
int foo = in.read();
@ -204,9 +258,9 @@ public class SU3File {
if (_contentLength <= 0)
throw new IOException("bad content length");
skip(in, 1);
foo = in.read();
if (foo != TYPE_ZIP)
throw new IOException("bad type");
_fileType = in.read();
if (_fileType != TYPE_ZIP && _fileType != TYPE_XML)
throw new IOException("bad file type");
skip(in, 1);
int cType = in.read();
_contentType = BY_CODE.get(Integer.valueOf(cType));
@ -231,6 +285,10 @@ public class SU3File {
throw new EOFException();
_signer = DataHelper.getUTF8(data);
if (_verifySignature) {
if (_certFile != null) {
_signerPubkey = loadKey(_certFile);
} else {
KeyRing ring = new DirKeyRing(new File(_context.getBaseDir(), "certificates"));
try {
_signerPubkey = ring.getKey(_signer, _contentType.getName(), _sigType);
@ -239,9 +297,10 @@ public class SU3File {
ioe.initCause(gse);
throw ioe;
}
if (_signerPubkey == null)
throw new IOException("unknown signer: " + _signer);
throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName());
}
}
_headerVerified = true;
}
@ -287,9 +346,9 @@ public class SU3File {
// read 10 bytes to get the sig type
in.mark(10);
// following is a dup of that in verifyHeader()
byte[] magic = new byte[MAGIC.length];
byte[] magic = new byte[MAGIC_BYTES.length];
DataHelper.read(in, magic);
if (!DataHelper.eq(magic, MAGIC))
if (!DataHelper.eq(magic, MAGIC_BYTES))
throw new IOException("Not an su3 file");
skip(in, 1);
int foo = in.read();
@ -310,8 +369,10 @@ public class SU3File {
verifyHeader(in);
else
skip(in, getContentOffset());
if (_verifySignature) {
if (_signerPubkey == null)
throw new IOException("unknown signer: " + _signer);
throw new IOException("unknown signer: " + _signer + " for content type: " + _contentType.getName());
}
if (migrateTo != null) // else verify only
out = new FileOutputStream(migrateTo);
byte[] buf = new byte[16*1024];
@ -324,6 +385,7 @@ public class SU3File {
out.write(buf, 0, read);
tot += read;
}
if (_verifySignature) {
byte[] sha = md.digest();
din.on(false);
Signature signature = new Signature(_sigType);
@ -333,6 +395,9 @@ public class SU3File {
//System.out.println("hash\n" + HexDump.dump(sha));
//System.out.println("sig\n" + HexDump.dump(signature.getData()));
rv = _context.dsa().verifySignature(signature, hash, _signerPubkey);
} else {
rv = true;
}
} catch (DataFormatException dfe) {
IOException ioe = new IOException("foo");
ioe.initCause(dfe);
@ -352,11 +417,12 @@ public class SU3File {
* Throws on all errors.
*
* @param content the input file, probably in zip format
* @param contentType 0-255, 0 for zip
* @param fileType 0-255, 0 for zip
* @param contentType 0-255
* @param version 1-255 bytes when converted to UTF-8
* @param signer ID of the public key, 1-255 bytes when converted to UTF-8
*/
public void write(File content, int contentType, String version,
public void write(File content, int fileType, int contentType, String version,
String signer, PrivateKey privkey, SigType sigType) throws IOException {
InputStream in = null;
DigestOutputStream out = null;
@ -365,7 +431,7 @@ public class SU3File {
in = new BufferedInputStream(new FileInputStream(content));
MessageDigest md = sigType.getDigestInstance();
out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md);
out.write(MAGIC);
out.write(MAGIC_BYTES);
out.write((byte) 0);
out.write((byte) FILE_VERSION);
DataHelper.writeLong(out, 2, sigType.getCode());
@ -386,7 +452,9 @@ public class SU3File {
throw new IllegalArgumentException("No content");
DataHelper.writeLong(out, 8, contentLength);
out.write((byte) 0);
out.write((byte) TYPE_ZIP);
if (fileType < 0 || fileType > 255)
throw new IllegalArgumentException("bad content type");
out.write((byte) fileType);
out.write((byte) 0);
if (contentType < 0 || contentType > 255)
throw new IllegalArgumentException("bad content type");
@ -443,8 +511,11 @@ public class SU3File {
// defaults
String stype = null;
String ctype = null;
String ftype = null;
String kfile = null;
boolean error = false;
Getopt g = new Getopt("SU3File", args, "t:c:");
boolean shouldVerify = true;
Getopt g = new Getopt("SU3File", args, "t:c:f:k:x");
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
@ -456,6 +527,18 @@ public class SU3File {
ctype = g.getOptarg();
break;
case 'f':
ftype = g.getOptarg();
break;
case 'k':
kfile = g.getOptarg();
break;
case 'x':
shouldVerify = false;
break;
case '?':
case ':':
default:
@ -476,18 +559,18 @@ public class SU3File {
Properties props = new Properties();
props.setProperty("prng.bufferSize", "16384");
new I2PAppContext(props);
ok = signCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), "");
ok = signCLI(stype, ctype, ftype, a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), "");
} else if ("bulksign".equals(cmd)) {
Properties props = new Properties();
props.setProperty("prng.bufferSize", "16384");
new I2PAppContext(props);
ok = bulkSignCLI(stype, ctype, a.get(0), a.get(1), a.get(2), a.get(3));
} else if ("verifysig".equals(cmd)) {
ok = verifySigCLI(a.get(0));
ok = verifySigCLI(a.get(0), kfile);
} else if ("keygen".equals(cmd)) {
ok = genKeysCLI(stype, a.get(0), a.get(1), a.get(2));
} else if ("extract".equals(cmd)) {
ok = extractCLI(a.get(0), a.get(1));
ok = extractCLI(a.get(0), a.get(1), shouldVerify);
} else {
showUsageCLI();
}
@ -502,11 +585,11 @@ public class SU3File {
private static final void showUsageCLI() {
System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile keystore.ks you@mail.i2p");
System.err.println(" SU3File sign [-c type|code] [-t type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p");
System.err.println(" SU3File bulksign [-c type|code] [-t type|code] directory keystore.ks version you@mail.i2p");
System.err.println(" SU3File sign [-t type|code] [-c type|code] [-f type|code] inputFile.zip signedFile.su3 keystore.ks version you@mail.i2p");
System.err.println(" SU3File bulksign [-t type|code] [-c type|code] directory keystore.ks version you@mail.i2p");
System.err.println(" SU3File showversion signedFile.su3");
System.err.println(" SU3File verifysig signedFile.su3");
System.err.println(" SU3File extract signedFile.su3 outFile.zip");
System.err.println(" SU3File verifysig [-k file.crt] signedFile.su3 ## -k use this pubkey cert for verification");
System.err.println(" SU3File extract [-x] [-k file.crt] signedFile.su3 outFile ## -x don't check sig");
System.err.println(dumpTypes());
}
@ -529,6 +612,9 @@ public class SU3File {
buf.append(" DEFAULT");
buf.append('\n');
}
buf.append("Available file types (-f):\n");
buf.append(" ZIP\t(code: 0) DEFAULT\n");
buf.append(" XML\t(code: 1)\n");
return buf.toString();
}
@ -554,6 +640,7 @@ public class SU3File {
private static final boolean showVersionCLI(String signedFile) {
try {
SU3File file = new SU3File(signedFile);
file.setVerifySignature(false);
String versionString = file.getVersionString();
if (versionString.equals(""))
System.out.println("No version string found in file '" + signedFile + "'");
@ -574,6 +661,7 @@ public class SU3File {
}
/**
* Zip only
* @return success
* @since 0.9.9
*/
@ -593,7 +681,12 @@ public class SU3File {
try {
while (keypw.length() < 6) {
System.out.print("Enter password for key \"" + signerName + "\": ");
keypw = DataHelper.readLine(System.in).trim();
keypw = DataHelper.readLine(System.in);
if (keypw == null) {
System.out.println("\nEOF reading password");
return false;
}
keypw = keypw.trim();
if (keypw.length() > 0 && keypw.length() < 6)
System.out.println("Key password must be at least 6 characters");
}
@ -608,7 +701,7 @@ public class SU3File {
if (!inputFile.endsWith(".zip"))
continue;
String signedFile = inputFile.substring(0, inputFile.length() - 4) + ".su3";
boolean rv = signCLI(stype, ctype, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
boolean rv = signCLI(stype, ctype, null, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
if (!rv)
return false;
success++;
@ -622,7 +715,7 @@ public class SU3File {
* @return success
* @since 0.9.9
*/
private static final boolean signCLI(String stype, String ctype, String inputFile, String signedFile,
private static final boolean signCLI(String stype, String ctype, String ftype, String inputFile, String signedFile,
String privateKeyFile, String version, String signerName, String keypw) {
SigType type = stype == null ? SigType.getByCode(Integer.valueOf(DEFAULT_SIG_CODE)) : SigType.parseSigType(stype);
if (type == null) {
@ -634,19 +727,42 @@ public class SU3File {
System.out.println("Content type " + ctype + " is not supported");
return false;
}
return signCLI(type, ct, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
int ft = TYPE_ZIP;
if (ftype != null) {
if (ftype.equalsIgnoreCase("ZIP")) {
ft = TYPE_ZIP;
} else if (ftype.equalsIgnoreCase("XML")) {
ft = TYPE_XML;
} else {
try {
ft = Integer.parseInt(ftype);
} catch (NumberFormatException nfe) {
ft = -1;
}
if (ft != TYPE_ZIP && ft != TYPE_XML) {
System.out.println("File type " + ftype + " is not supported");
return false;
}
}
}
return signCLI(type, ct, ft, inputFile, signedFile, privateKeyFile, version, signerName, keypw);
}
/**
* @return success
* @since 0.9.9
*/
private static final boolean signCLI(SigType type, ContentType ctype, String inputFile, String signedFile,
private static final boolean signCLI(SigType type, ContentType ctype, int ftype, String inputFile, String signedFile,
String privateKeyFile, String version, String signerName, String keypw) {
try {
while (keypw.length() < 6) {
System.out.print("Enter password for key \"" + signerName + "\": ");
keypw = DataHelper.readLine(System.in).trim();
keypw = DataHelper.readLine(System.in);
if (keypw == null) {
System.out.println("\nEOF reading password");
return false;
}
keypw = keypw.trim();
if (keypw.length() > 0 && keypw.length() < 6)
System.out.println("Key password must be at least 6 characters");
}
@ -657,7 +773,7 @@ public class SU3File {
return false;
}
SU3File file = new SU3File(signedFile);
file.write(new File(inputFile), ctype.getCode(), version, signerName, pk, type);
file.write(new File(inputFile), ftype, ctype.getCode(), version, signerName, pk, type);
System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'");
return true;
} catch (GeneralSecurityException gse) {
@ -672,10 +788,12 @@ public class SU3File {
}
/** @return valid */
private static final boolean verifySigCLI(String signedFile) {
private static final boolean verifySigCLI(String signedFile, String pkFile) {
InputStream in = null;
try {
SU3File file = new SU3File(signedFile);
if (pkFile != null)
file.setPublicKeyCertificate(new File(pkFile));
boolean isValidSignature = file.verify();
if (isValidSignature)
System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')');
@ -693,10 +811,11 @@ public class SU3File {
* @return success
* @since 0.9.9
*/
private static final boolean extractCLI(String signedFile, String outFile) {
private static final boolean extractCLI(String signedFile, String outFile, boolean verifySig) {
InputStream in = null;
try {
SU3File file = new SU3File(signedFile);
file.setVerifySignature(verifySig);
File out = new File(outFile);
boolean ok = file.verifyAndMigrate(out);
if (ok)
@ -740,11 +859,21 @@ public class SU3File {
try {
while (alias.length() == 0) {
System.out.print("Enter key name (example@mail.i2p): ");
alias = DataHelper.readLine(System.in).trim();
alias = DataHelper.readLine(System.in);
if (alias == null) {
System.out.println("\nEOF reading key name");
return false;
}
alias = alias.trim();
}
while (keypw.length() < 6) {
System.out.print("Enter new key password: ");
keypw = DataHelper.readLine(System.in).trim();
keypw = DataHelper.readLine(System.in);
if (keypw == null) {
System.out.println("\nEOF reading password");
return false;
}
keypw = keypw.trim();
if (keypw.length() > 0 && keypw.length() < 6)
System.out.println("Key password must be at least 6 characters");
}
@ -772,4 +901,26 @@ public class SU3File {
}
return true;
}
/**
* For the -k CLI option
* @return non-null, throws IOE on all errors
* @since 0.9.15
*/
private static PublicKey loadKey(File kd) throws IOException {
InputStream fis = null;
try {
fis = new FileInputStream(kd);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
cert.checkValidity();
return cert.getPublicKey();
} catch (GeneralSecurityException gse) {
IOException ioe = new IOException("cert error");
ioe.initCause(gse);
throw ioe;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
}
}

View File

@ -178,6 +178,29 @@ public enum SigType {
return true;
}
/**
* @return true if supported in this JVM
* @since 0.9.15
*/
public static boolean isAvailable(int code) {
SigType type = getByCode(code);
if (type == null)
return false;
return type.isAvailable();
}
/**
* @param stype number or name
* @return true if supported in this JVM
* @since 0.9.15
*/
public static boolean isAvailable(String stype) {
SigType type = parseSigType(stype);
if (type == null)
return false;
return type.isAvailable();
}
private static final Map<Integer, SigType> BY_CODE = new HashMap<Integer, SigType>();
static {

View File

@ -56,6 +56,7 @@ public class Base32 {
};
private final static byte BAD_ENCODING = -9; // Indicates error in encoding
/** Defeats instantiation. */
private Base32() { // nop
}
@ -136,6 +137,9 @@ public class Base32 {
}
/**
* Returns lower case.
* Does not add trailing '='.
*
* @param source if null will return ""
*/
public static String encode(String source) {
@ -143,6 +147,9 @@ public class Base32 {
}
/**
* Returns lower case.
* Does not add trailing '='.
*
* @param source The data to convert non-null
*/
public static String encode(byte[] source) {
@ -182,6 +189,8 @@ public class Base32 {
/**
* Decodes data from Base32 notation and
* returns it as a string.
* Case-insensitive.
* Does not allow trailing '='.
*
* @param s the string to decode, if null returns null
* @return The data as a string or null on failure
@ -194,6 +203,9 @@ public class Base32 {
}
/**
* Case-insensitive.
* Does not allow trailing '='.
*
* @param s non-null
* @return decoded data, null on error
*/

View File

@ -130,17 +130,19 @@ public class KeysAndCert extends DataStructureImpl {
&& DataHelper.eq(_certificate, ident._certificate);
}
/** the public key has enough randomness in it to use it by itself for speed */
/** the signing key has enough randomness in it to use it by itself for speed */
@Override
public int hashCode() {
if (_publicKey == null)
// don't use public key, some app devs thinking of using
// an all-zeros or leading-zeros public key for destinations
if (_signingKey == null)
return 0;
return _publicKey.hashCode();
return _signingKey.hashCode();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(64);
StringBuilder buf = new StringBuilder(256);
buf.append('[').append(getClass().getSimpleName()).append(": ");
buf.append("\n\tHash: ").append(getHash().toBase64());
buf.append("\n\tCertificate: ").append(_certificate);

View File

@ -13,6 +13,7 @@ import net.i2p.data.Hash;
import net.i2p.data.RouterInfo;
import net.i2p.internal.InternalClientManager;
import net.i2p.router.client.ClientManagerFacadeImpl;
import net.i2p.router.crypto.TransientSessionKeyManager;
import net.i2p.router.dummy.*;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.peermanager.PeerManagerFacadeImpl;
@ -67,7 +68,7 @@ public class RouterContext extends I2PAppContext {
private final Set<Runnable> _finalShutdownTasks;
// split up big lock on this to avoid deadlocks
private volatile boolean _initialized;
private final Object _lock1 = new Object(), _lock2 = new Object();
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object();
private static final List<RouterContext> _contexts = new CopyOnWriteArrayList<RouterContext>();
@ -565,4 +566,20 @@ public class RouterContext extends I2PAppContext {
public RouterAppManager routerAppManager() {
return _appManager;
}
/**
* As of 0.9.15, this returns a dummy SessionKeyManager in I2PAppContext.
* Overridden in RouterContext to return the full TransientSessionKeyManager.
*
* @since 0.9.15
*/
@Override
protected void initializeSessionKeyManager() {
synchronized (_lock3) {
if (_sessionKeyManager == null)
//_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManager = new TransientSessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}
}

View File

@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.client.I2PClient;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
@ -43,6 +42,7 @@ import net.i2p.data.i2cp.SessionId;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.crypto.TransientSessionKeyManager;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;

View File

@ -11,6 +11,7 @@ package net.i2p.router.client;
import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.crypto.SigType;
import net.i2p.data.Hash;
import net.i2p.data.Payload;
import net.i2p.data.i2cp.BandwidthLimitsMessage;
@ -195,10 +196,16 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
if (_log.shouldLog(Log.DEBUG))
_log.debug("Signature verified correctly on create session message");
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Signature verification *FAILED* on a create session message. Hijack attempt?");
// For now, we do NOT send a SessionStatusMessage - see javadoc above
int itype = in.getDestination().getCertificate().getCertificateType();
SigType stype = SigType.getByCode(itype);
if (stype == null || !stype.isAvailable()) {
_log.error("Client requested unsupported signature type " + itype);
_runner.disconnectClient("Unsupported signature type " + itype);
} else {
_log.error("Signature verification failed on a create session message");
_runner.disconnectClient("Invalid signature on CreateSessionMessage");
}
return;
}

View File

@ -1,4 +1,4 @@
package net.i2p.crypto;
package net.i2p.router.crypto;
/*
* free (adj.): unencumbered; not under the control of others
@ -25,6 +25,8 @@ import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TagSetHandle;
import net.i2p.data.DataHelper;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
Classes formerly in net.i2p.crypto but moved here as they are only used by the router.
</p>
</body>
</html>

View File

@ -967,6 +967,32 @@ class EstablishmentManager {
notifyActivity();
}
/**
* Called from UDPReceiver.
* Accelerate response to RelayResponse if we haven't sent it yet.
*
* @since 0.9.15
*/
void receiveHolePunch(InetAddress from, int fromPort) {
RemoteHostId id = new RemoteHostId(from.getAddress(), fromPort);
OutboundEstablishState state = _outboundStates.get(id);
if (state != null) {
boolean sendNow = state.receiveHolePunch();
if (sendNow) {
if (_log.shouldLog(Log.WARN))
_log.warn("Hole punch from " + state + ", sending SessionRequest now");
notifyActivity();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Hole punch from " + state + ", already sent SessionRequest");
}
} else {
// HolePunch received before RelayResponse, and we didn't know the IP/port, or it changed
if (_log.shouldLog(Log.WARN))
_log.warn("No state found for hole punch from " + from + " port " + fromPort);
}
}
/**
* Are IP and port valid? This is only for checking the relay response.
* Reject all IPv6, for now, even if we are configured for it.

View File

@ -99,6 +99,8 @@ class OutboundEstablishState {
/** max delay including backoff */
private static final long MAX_DELAY = 15*1000;
private static final long WAIT_FOR_HOLE_PUNCH_DELAY = 500;
/**
* @param claimedAddress an IP/port based RemoteHostId, or null if unknown
* @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
@ -556,7 +558,7 @@ class OutboundEstablishState {
public synchronized void introduced(byte bobIP[], int bobPort) {
if (_currentState != OutboundState.OB_STATE_PENDING_INTRO)
return; // we've already successfully been introduced, so don't overwrite old settings
_nextSend = _context.clock().now() + 500; // wait briefly for the hole punching
_nextSend = _context.clock().now() + WAIT_FOR_HOLE_PUNCH_DELAY; // wait briefly for the hole punching
_currentState = OutboundState.OB_STATE_INTRODUCED;
if (_claimedAddress != null && bobPort == _bobPort && DataHelper.eq(bobIP, _bobIP)) {
// he's who he said he was
@ -571,6 +573,24 @@ class OutboundEstablishState {
_log.info("Introduced to " + _remoteHostId + ", now lets get on with establishing");
}
/**
* Accelerate response to RelayResponse if we haven't sent it yet.
*
* @return true if we should send the SessionRequest now
* @since 0.9.15
*/
synchronized boolean receiveHolePunch() {
if (_currentState != OutboundState.OB_STATE_INTRODUCED)
return false;
if (_requestSentCount > 0)
return false;
long now = _context.clock().now();
if (_log.shouldLog(Log.WARN))
_log.warn(toString() + " accelerating SessionRequest by " + (_nextSend - now) + " ms");
_nextSend = now;
return true;
}
/** how long have we been trying to establish this session? */
public long getLifetime() { return _context.clock().now() - _establishBegin; }
public long getEstablishBeginTime() { return _establishBegin; }

View File

@ -1,6 +1,7 @@
package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
@ -220,11 +221,12 @@ class UDPReceiver {
// _socketChanged = false;
//}
UDPPacket packet = UDPPacket.acquire(_context, true);
DatagramPacket dpacket = packet.getPacket();
// Android ICS bug
// http://code.google.com/p/android/issues/detail?id=24748
if (_isAndroid)
packet.getPacket().setLength(UDPPacket.MAX_PACKET_SIZE);
dpacket.setLength(UDPPacket.MAX_PACKET_SIZE);
// block before we read...
//if (_log.shouldLog(Log.DEBUG))
@ -236,9 +238,9 @@ class UDPReceiver {
//if (_log.shouldLog(Log.INFO))
// _log.info("Before blocking socket.receive on " + System.identityHashCode(packet));
//synchronized (Runner.this) {
_socket.receive(packet.getPacket());
_socket.receive(dpacket);
//}
int size = packet.getPacket().getLength();
int size = dpacket.getLength();
if (_log.shouldLog(Log.INFO))
_log.info("After blocking socket.receive: packet is " + size + " bytes on " + System.identityHashCode(packet));
packet.resetBegin();
@ -266,7 +268,8 @@ class UDPReceiver {
_context.statManager().addRateData("udp.receiveHolePunch", 1);
// nat hole punch packets are 0 bytes
if (_log.shouldLog(Log.INFO))
_log.info("Received a 0 byte udp packet from " + packet.getPacket().getAddress() + ":" + packet.getPacket().getPort());
_log.info("Received a 0 byte udp packet from " + dpacket.getAddress() + ":" + dpacket.getPort());
_transport.getEstablisher().receiveHolePunch(dpacket.getAddress(), dpacket.getPort());
packet.release();
}
} catch (IOException ioe) {

View File

@ -21,18 +21,21 @@
package org.cybergarage.xml.parser;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.cybergarage.xml.Node;
import org.cybergarage.xml.Parser;
import org.cybergarage.xml.ParserException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
@ -116,8 +119,25 @@ public class JaxpParser extends Parser
org.cybergarage.xml.Node root = null;
try {
// https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
factory.setExpandEntityReferences(false);
try {
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
} catch (ParserConfigurationException pce) {}
try {
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
} catch (ParserConfigurationException pce) {}
try {
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (ParserConfigurationException pce) {}
try {
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (ParserConfigurationException pce) {}
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new BlankingResolver());
InputSource inSrc = new InputSource(new NullFilterInputStream(inStream));
Document doc = builder.parse(inSrc);
@ -163,4 +183,16 @@ public class JaxpParser extends Parser
return rv;
}
}
/**
* I2P -
* http://stackoverflow.com/questions/5883542/disable-xml-validation-based-on-external-dtd-xsd
*/
private static class BlankingResolver implements EntityResolver {
private static final byte[] DUMMY = new byte[0];
public InputSource resolveEntity(String arg0, String arg1) {
return new InputSource(new ByteArrayInputStream(DUMMY));
}
}
}