merge of '0738aeef8a1d4e9ca82dc5ba0077d83a57c47f81'

and '9625ea3e96d57df74bc62018bf64230a22c49ce0'
This commit is contained in:
z3d
2010-10-13 16:07:33 +00:00
149 changed files with 2721 additions and 1741 deletions

View File

@@ -81,7 +81,7 @@
<target name="war" depends="precompilejsp, bundle">
<war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
basedir="../jsp/" excludes="web.xml, **/*.java, *.jsp">
basedir="../jsp/" excludes="web.xml, web-fragment.xml, web-out.xml, **/*.java, *.jsp">
</war>
</target>

View File

@@ -24,6 +24,9 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
* This does the transparent gzip decompression on the client side.
* Extended in I2PTunnelHTTPServer to do the compression on the server side.
*
* Simple stream for delivering an HTTP response to
* the client, trivially filtered to make sure "Connection: close"
* is always in the response. Perhaps add transparent handling of the
@@ -33,29 +36,27 @@ import net.i2p.util.Log;
*
*/
class HTTPResponseOutputStream extends FilterOutputStream {
private I2PAppContext _context;
private Log _log;
private ByteCache _cache;
private final I2PAppContext _context;
private final Log _log;
protected ByteArray _headerBuffer;
private boolean _headerWritten;
private byte _buf1[];
private final byte _buf1[];
protected boolean _gzip;
private long _dataWritten;
private InternalGZIPInputStream _in;
private static final int CACHE_SIZE = 8*1024;
private static final ByteCache _cache = ByteCache.getInstance(8, CACHE_SIZE);
// OOM DOS prevention
private static final int MAX_HEADER_SIZE = 64*1024;
public HTTPResponseOutputStream(OutputStream raw) {
super(raw);
_context = I2PAppContext.getGlobalContext();
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*60*1000 });
_log = _context.logManager().getLog(getClass());
_cache = ByteCache.getInstance(8, CACHE_SIZE);
_headerBuffer = _cache.acquire();
_headerWritten = false;
_gzip = false;
_dataWritten = 0;
_buf1 = new byte[1];
}
@@ -96,14 +97,20 @@ class HTTPResponseOutputStream extends FilterOutputStream {
}
}
/** grow (and free) the buffer as necessary */
private void ensureCapacity() {
/**
* grow (and free) the buffer as necessary
* @throws IOException if the headers are too big
*/
private void ensureCapacity() throws IOException {
if (_headerBuffer.getValid() >= MAX_HEADER_SIZE)
throw new IOException("Max header size exceeded: " + MAX_HEADER_SIZE);
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
int newSize = (int)(_headerBuffer.getData().length * 1.5);
ByteArray newBuf = new ByteArray(new byte[newSize]);
System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid());
newBuf.setValid(_headerBuffer.getValid());
newBuf.setOffset(0);
// if we changed the ByteArray size, don't put it back in the cache
if (_headerBuffer.getData().length == CACHE_SIZE)
_cache.release(_headerBuffer);
_headerBuffer = newBuf;
@@ -219,7 +226,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
//out.flush();
PipedInputStream pi = new PipedInputStream();
PipedOutputStream po = new PipedOutputStream(pi);
new I2PAppThread(new Pusher(pi, out), "HTTP decompresser").start();
new I2PAppThread(new Pusher(pi, out), "HTTP decompressor").start();
out = po;
}
@@ -231,13 +238,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
_out = out;
}
public void run() {
OutputStream to = null;
_in = null;
long start = System.currentTimeMillis();
long written = 0;
ByteArray ba = null;
try {
_in = new InternalGZIPInputStream(_inRaw);
byte buf[] = new byte[8192];
ba = _cache.acquire();
byte buf[] = ba.getData();
int read = -1;
while ( (read = _in.read(buf)) != -1) {
if (_log.shouldLog(Log.DEBUG))
@@ -251,6 +258,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
} catch (OutOfMemoryError oom) {
_log.error("OOM in HTTP Decompressor", oom);
} finally {
if (_log.shouldLog(Log.WARN) && (_in != null))
_log.warn("After decompression, written=" + written +
@@ -259,23 +268,26 @@ class HTTPResponseOutputStream extends FilterOutputStream {
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
+ ", finished=" + _in.getFinished()
: ""));
if (ba != null)
_cache.release(ba);
if (_out != null) try {
_out.close();
} catch (IOException ioe) {}
}
long end = System.currentTimeMillis();
double compressed = (_in != null ? _in.getTotalRead() : 0);
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
double ratio = 0;
if (expanded > 0)
ratio = compressed/expanded;
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
if (compressed > 0 && expanded > 0) {
// only update the stats if we did something
double ratio = compressed/expanded;
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), 0);
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, 0);
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, 0);
}
}
}
/** just a wrapper to provide stats for debugging */
private static class InternalGZIPInputStream extends GZIPInputStream {
public InternalGZIPInputStream(InputStream in) throws IOException {
super(in);
@@ -294,6 +306,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
return 0;
}
}
/**
* From Inflater javadoc:
* Returns the total number of bytes remaining in the input buffer. This can be used to find out
* what bytes still remain in the input buffer after decompression has finished.
*/
public long getRemaining() {
try {
return super.inf.getRemaining();

View File

@@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -110,7 +109,7 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
}
if (size == 1) // skip the rand in the most common case
return dests.get(0);
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
int index = _context.random().nextInt(size);
return dests.get(index);
}
}

View File

@@ -146,7 +146,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
int size = _proxyList.size();
if (size <= 0)
return null;
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
int index = _context.random().nextInt(size);
return _proxyList.get(index);
}
}

View File

@@ -205,7 +205,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log("Proxy list is empty - no outproxy available");
return null;
}
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
int index = _context.random().nextInt(size);
String proxy = (String)proxyList.get(index);
return proxy;
}
@@ -626,6 +626,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
//line = "From: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("authorization: ntlm ")) {
// Block Windows NTLM after 401
line = null;
continue;
} else if (lowercaseLine.startsWith("proxy-authorization: ntlm ")) {
// Block Windows NTLM after 407
line = null;
continue;
}
}
@@ -808,7 +816,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
* @return non-null
*/
private byte[] getErrorPage(String base, byte[] backup) {
return getErrorPage(getTunnel().getContext(), base, backup);
return getErrorPage(_context, base, backup);
}
private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {

View File

@@ -290,6 +290,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
}
}
/** just a wrapper to provide stats for debugging */
private static class InternalGZIPOutputStream extends GZIPOutputStream {
public InternalGZIPOutputStream(OutputStream target) throws IOException {
super(target);

View File

@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -124,7 +123,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
}
if (size == 1) // skip the rand in the most common case
return dests.get(0);
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
int index = _context.random().nextInt(size);
return dests.get(index);
}
@@ -182,6 +181,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
}
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
// probably doesn't do much but can't hurt
output.flush();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("inbound BLOCKED: "+inmsg);
@@ -257,6 +258,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
}
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
// save 250 ms in streaming
output.flush();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");

View File

@@ -129,7 +129,14 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
// do NOT flush here, it will block and then onTimeout.run() won't happen on fail.
// But if we don't flush, then we have to wait for the connectDelay timer to fire
// in i2p socket? To be researched and/or fixed.
//i2pout.flush();
//
// AS OF 0.8.1, MessageOutputStream.flush() is fixed to only wait for accept,
// not for "completion" (i.e. an ACK from the far end).
// So we now get a fast return from flush(), and can do it here to save 250 ms.
// To make sure we are under the initial window size and don't hang waiting for accept,
// only flush if it fits in one message.
if (initialI2PData.length <= 1730) // ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE
i2pout.flush();
}
}
if (initialSocketData != null) {

View File

@@ -18,6 +18,7 @@ import net.i2p.data.Base32;
import net.i2p.data.Destination;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
/**
* Coordinate the runtime operation and configuration of a tunnel.
@@ -89,7 +90,7 @@ public class TunnelController implements Logging {
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(keyFile);
fos = new SecureFileOutputStream(keyFile);
Destination dest = client.createDestination(fos);
String destStr = dest.toBase64();
log("Private key created and saved in " + keyFile.getAbsolutePath());

View File

@@ -20,6 +20,7 @@ import net.i2p.client.I2PSessionException;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SecureFileOutputStream;
/**
* Coordinate a set of tunnels within the JVM, loading and storing their config
@@ -255,7 +256,7 @@ public class TunnelControllerGroup {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(cfgFile);
fos = new SecureFileOutputStream(cfgFile);
fos.write(buf.toString().getBytes("UTF-8"));
if (_log.shouldLog(Log.INFO))
_log.info("Config written to " + cfgFile.getPath());

View File

@@ -13,6 +13,11 @@ import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.Signature;
import net.i2p.data.SigningPrivateKey;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
@@ -68,6 +73,31 @@ public class EditBean extends IndexBean {
return "i2ptunnel" + tunnel + "-privKeys.dat";
}
public String getNameSignature(int tunnel) {
String spoof = getSpoofedHost(tunnel);
if (spoof.length() <= 0)
return "";
TunnelController tun = getController(tunnel);
if (tun == null)
return "";
String keyFile = tun.getPrivKeyFile();
if (keyFile != null && keyFile.trim().length() > 0) {
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
try {
Destination d = pkf.getDestination();
if (d == null)
return "";
SigningPrivateKey privKey = pkf.getSigningPrivKey();
if (privKey == null)
return "";
//System.err.println("Signing " + spoof + " with " + Base64.encode(privKey.getData()));
Signature sig = _context.dsa().sign(spoof.getBytes("UTF-8"), privKey);
return Base64.encode(sig.getData());
} catch (Exception e) {}
}
return "";
}
public boolean startAutomatically(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)

View File

@@ -9,6 +9,7 @@ package net.i2p.i2ptunnel.web;
*/
import java.util.concurrent.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -41,10 +42,10 @@ public class IndexBean {
protected TunnelControllerGroup _group;
private String _action;
private int _tunnel;
private long _prevNonce;
private long _prevNonce2;
private long _curNonce;
private long _nextNonce;
//private long _prevNonce;
//private long _prevNonce2;
private String _curNonce;
//private long _nextNonce;
private String _type;
private String _name;
@@ -85,10 +86,14 @@ public class IndexBean {
/** deprecated unimplemented, now using routerconsole realm */
//public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
public static final String PROP_TUNNEL_PASSPHRASE = "consolePassword";
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
static final String PROP_NONCE_OLD = PROP_NONCE + '2';
//static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
//static final String PROP_NONCE_OLD = PROP_NONCE + '2';
/** 3 wasn't enough for some browsers. They are reloading the page for some reason - maybe HEAD? @since 0.8.1 */
private static final int MAX_NONCES = 5;
/** store nonces in a static FIFO instead of in System Properties @since 0.8.1 */
private static final List<String> _nonces = new ArrayList(MAX_NONCES + 1);
static final String CLIENT_NICKNAME = "shared clients";
public static final String PROP_THEME_NAME = "routerconsole.theme";
public static final String DEFAULT_THEME = "light";
public static final String PROP_CSS_DISABLED = "routerconsole.css.disabled";
@@ -98,34 +103,39 @@ public class IndexBean {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(IndexBean.class);
_group = TunnelControllerGroup.getInstance();
_action = null;
_tunnel = -1;
_curNonce = -1;
_prevNonce = -1;
_prevNonce2 = -1;
try {
String nonce2 = System.getProperty(PROP_NONCE_OLD);
if (nonce2 != null)
_prevNonce2 = Long.parseLong(nonce2);
String nonce = System.getProperty(PROP_NONCE);
if (nonce != null) {
_prevNonce = Long.parseLong(nonce);
System.setProperty(PROP_NONCE_OLD, nonce);
}
} catch (NumberFormatException nfe) {}
_nextNonce = _context.random().nextLong();
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
_curNonce = "-1";
addNonce();
_booleanOptions = new ConcurrentHashSet(4);
_otherOptions = new ConcurrentHashMap(4);
}
public long getNextNonce() { return _nextNonce; }
public static String getNextNonce() {
synchronized (_nonces) {
return _nonces.get(0);
}
}
public void setNonce(String nonce) {
if ( (nonce == null) || (nonce.trim().length() <= 0) ) return;
try {
_curNonce = Long.parseLong(nonce);
} catch (NumberFormatException nfe) {
_curNonce = -1;
_curNonce = nonce;
}
/** add a random nonce to the head of the queue @since 0.8.1 */
private void addNonce() {
String nextNonce = Long.toString(_context.random().nextLong());
synchronized (_nonces) {
_nonces.add(0, nextNonce);
int sz = _nonces.size();
if (sz > MAX_NONCES)
_nonces.remove(sz - 1);
}
}
/** do we know this nonce? @since 0.8.1 */
private static boolean haveNonce(String nonce) {
synchronized (_nonces) {
return _nonces.contains(nonce);
}
}
@@ -155,7 +165,7 @@ public class IndexBean {
private String processAction() {
if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action)))
return "";
if ( (_prevNonce != _curNonce) && (_prevNonce2 != _curNonce) && (!validPassphrase()) )
if ( (!haveNonce(_curNonce)) && (!validPassphrase()) )
return "Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.";
if ("Stop all".equals(_action))
return stopAll();

View File

@@ -196,7 +196,16 @@
<a href="/susidns/addressbook.jsp?book=private&hostname=<%=editBean.getTunnelName(curTunnel)%>&destination=<%=editBean.getDestinationBase64(curTunnel)%>#add"><%=intl._("Add to local addressbook")%></a>
<% } %>
</div>
<% if (("httpserver".equals(tunnelType)) || ("httpbidirserver".equals(tunnelType))) {
%><div id="sigField" class="rowItem">
<label for="signature">
<%=intl._("Hostname Signature")%>
</label>
<input type="text" size="30" readonly="readonly" title="Use to prove that the website name is for this destination" value="<%=editBean.getNameSignature(curTunnel)%>" wrap="off" class="freetext" />
</div>
<% } %>
<div class="footer">
</div>
</div>