diff --git a/apps/addressbook/web.xml b/apps/addressbook/web.xml index 1aebb0d0964a4e6db360cd5e5b83881a71715404..d86d32289b8c031d5855bcf9c7a3d776dcbd6d15 100644 --- a/apps/addressbook/web.xml +++ b/apps/addressbook/web.xml @@ -28,4 +28,11 @@ <url-pattern>/*</url-pattern> </servlet-mapping> + <!-- this webapp doesn't actually use sessions or cookies --> + <session-config> + <session-timeout>30</session-timeout> + <cookie-config> + <http-only>true</http-only> + </cookie-config> + </session-config> </web-app> diff --git a/apps/i2psnark/web.xml b/apps/i2psnark/web.xml index 68e6abd6412fd4bac016ddb8c83c1a3a75f090e4..d98f637338d7e3456a36dac7ed5e9177d00cb6a3 100644 --- a/apps/i2psnark/web.xml +++ b/apps/i2psnark/web.xml @@ -26,73 +26,14 @@ <url-pattern>/</url-pattern> </servlet-mapping> + <!-- this webapp doesn't actually use sessions or cookies --> <session-config> <session-timeout> 30 </session-timeout> + <cookie-config> + <http-only>true</http-only> + </cookie-config> </session-config> - - <!-- mime types not in mime.properties in the jetty 5.1.15 source --> - - <mime-mapping> - <extension>mkv</extension> - <mime-type>video/x-matroska</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>wmv</extension> - <mime-type>video/x-ms-wmv</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>flv</extension> - <mime-type>video/x-flv</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>mp4</extension> - <mime-type>video/mp4</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>rar</extension> - <mime-type>application/rar</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>7z</extension> - <mime-type>application/x-7z-compressed</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>iso</extension> - <mime-type>application/x-iso9660-image</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>ico</extension> - <mime-type>image/x-icon</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>exe</extension> - <mime-type>application/x-msdos-program</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>flac</extension> - <mime-type>audio/flac</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>m4a</extension> - <mime-type>audio/mpeg</mime-type> - </mime-mapping> - - <mime-mapping> - <extension>wma</extension> - <mime-type>audio/x-ms-wma</mime-type> - </mime-mapping> - </web-app> diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 8f963e6492d173ee801bb94e8224ae917eee09d0..4b06ae0789ca1ae1d15217203453fcbbc961cae9 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -71,11 +71,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { private static final long TOTAL_HEADER_TIMEOUT = 2 * HEADER_TIMEOUT; private static final long START_INTERVAL = (60 * 1000) * 3; private static final int MAX_LINE_LENGTH = 8*1024; + /** ridiculously long, just to prevent OOM DOS @since 0.7.13 */ + private static final int MAX_HEADERS = 60; + /** Includes request, just to prevent OOM DOS @since 0.9.20 */ + private static final int MAX_TOTAL_HEADER_SIZE = 32*1024; + private long _startedOn = 0L; private ConnThrottler _postThrottler; - private final static byte[] ERR_UNAVAILABLE = - ("HTTP/1.1 503 Service Unavailable\r\n"+ + private final static String ERR_UNAVAILABLE = + "HTTP/1.1 503 Service Unavailable\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ "Cache-control: no-cache\r\n"+ "Connection: close\r\n"+ @@ -84,11 +89,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { "<html><head><title>503 Service Unavailable</title></head>\n"+ "<body><h2>503 Service Unavailable</h2>\n" + "<p>This I2P website is unavailable. It may be down or undergoing maintenance.</p>\n" + - "</body></html>") - .getBytes(); + "</body></html>"; - private final static byte[] ERR_DENIED = - ("HTTP/1.1 403 Denied\r\n"+ + private final static String ERR_DENIED = + "HTTP/1.1 403 Denied\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ "Cache-control: no-cache\r\n"+ "Connection: close\r\n"+ @@ -97,11 +101,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { "<html><head><title>403 Denied</title></head>\n"+ "<body><h2>403 Denied</h2>\n" + "<p>Denied due to excessive requests. Please try again later.</p>\n" + - "</body></html>") - .getBytes(); + "</body></html>"; - private final static byte[] ERR_INPROXY = - ("HTTP/1.1 403 Denied\r\n"+ + private final static String ERR_INPROXY = + "HTTP/1.1 403 Denied\r\n"+ "Content-Type: text/html; charset=iso-8859-1\r\n"+ "Cache-control: no-cache\r\n"+ "Connection: close\r\n"+ @@ -110,8 +113,64 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { "<html><head><title>403 Denied</title></head>\n"+ "<body><h2>403 Denied</h2>\n" + "<p>Inproxy access denied. You must run <a href=\"https://geti2p.net/\">I2P</a> to access this site.</p>\n" + - "</body></html>") - .getBytes(); + "</body></html>"; + + private final static String ERR_SSL = + "HTTP/1.1 503 Service Unavailable\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "<html><head><title>503 Service Unavailable</title></head>\n"+ + "<body><h2>503 Service Unavailable</h2>\n" + + "<p>This I2P website is not configured for SSL.</p>\n" + + "</body></html>"; + + private final static String ERR_REQUEST_URI_TOO_LONG = + "HTTP/1.1 414 Request URI too long\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "<html><head><title>414 Request URI Too Long</title></head>\n"+ + "<body><h2>414 Request URI too long</h2>\n" + + "</body></html>"; + + private final static String ERR_HEADERS_TOO_LARGE = + "HTTP/1.1 431 Request header fields too large\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "<html><head><title>431 Request Header Fields Too Large</title></head>\n"+ + "<body><h2>431 Request header fields too large</h2>\n" + + "</body></html>"; + + private final static String ERR_REQUEST_TIMEOUT = + "HTTP/1.1 408 Request timeout\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "<html><head><title>408 Request Timeout</title></head>\n"+ + "<body><h2>408 Request timeout</h2>\n" + + "</body></html>"; + + private final static String ERR_BAD_REQUEST = + "HTTP/1.1 400 Bad Request\r\n"+ + "Content-Type: text/html; charset=iso-8859-1\r\n"+ + "Cache-control: no-cache\r\n"+ + "Connection: close\r\n"+ + "Proxy-Connection: close\r\n"+ + "\r\n"+ + "<html><head><title>400 Bad Request</title></head>\n"+ + "<body><h2>400 Bad request</h2>\n" + + "</body></html>"; + public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super(host, port, privData, l, notifyThis, tunnel); @@ -131,7 +190,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { private void setupI2PTunnelHTTPServer(String spoofHost) { _spoofHost = (spoofHost != null && spoofHost.trim().length() > 0) ? spoofHost.trim() : null; getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 }); - getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 }); } @Override @@ -208,13 +266,88 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { //local is fast, so synchronously. Does not need that many //threads. try { + if (socket.getLocalPort() == 443) { + if (getTunnel().getClientOptions().getProperty("targetForPort.443") == null) { + try { + socket.getOutputStream().write(ERR_SSL.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { + socket.close(); + } catch (IOException ioe) {} + } + return; + } + Socket s = getSocket(socket.getPeerDestination().calculateHash(), 443); + Runnable t = new I2PTunnelRunner(s, socket, slock, null, null, + null, (I2PTunnelRunner.FailCallback) null); + _clientExecutor.execute(t); + return; + } + long afterAccept = getTunnel().getContext().clock().now(); + // The headers _should_ be in the first packet, but // may not be, depending on the client-side options StringBuilder command = new StringBuilder(128); - Map<String, List<String>> headers = readHeaders(socket, null, command, - CLIENT_SKIPHEADERS, getTunnel().getContext()); + Map<String, List<String>> headers; + try { + // catch specific exceptions thrown, to return a good + // error to the client + headers = readHeaders(socket, null, command, + CLIENT_SKIPHEADERS, getTunnel().getContext()); + } catch (SocketTimeoutException ste) { + try { + socket.getOutputStream().write(ERR_REQUEST_TIMEOUT.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { socket.close(); } catch (IOException ioe) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Error while receiving the new HTTP request", ste); + return; + } catch (EOFException eofe) { + try { + socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { socket.close(); } catch (IOException ioe) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Error while receiving the new HTTP request", eofe); + return; + } catch (LineTooLongException ltle) { + try { + socket.getOutputStream().write(ERR_HEADERS_TOO_LARGE.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { socket.close(); } catch (IOException ioe) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Error while receiving the new HTTP request", ltle); + return; + } catch (RequestTooLongException rtle) { + try { + socket.getOutputStream().write(ERR_REQUEST_URI_TOO_LONG.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { socket.close(); } catch (IOException ioe) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Error while receiving the new HTTP request", rtle); + return; + } catch (BadRequestException bre) { + try { + socket.getOutputStream().write(ERR_BAD_REQUEST.getBytes("UTF-8")); + } catch (IOException ioe) { + } finally { + try { socket.close(); } catch (IOException ioe) {} + } + if (_log.shouldLog(Log.WARN)) + _log.warn("Error while receiving the new HTTP request", bre); + return; + } long afterHeaders = getTunnel().getContext().clock().now(); Properties opts = getTunnel().getClientOptions(); @@ -239,7 +372,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { try { // Send a 403, so the user doesn't get an HTTP Proxy error message // and blame his router or the network. - socket.getOutputStream().write(ERR_INPROXY); + socket.getOutputStream().write(ERR_INPROXY.getBytes("UTF-8")); } catch (IOException ioe) {} try { socket.close(); @@ -256,7 +389,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { try { // Send a 403, so the user doesn't get an HTTP Proxy error message // and blame his router or the network. - socket.getOutputStream().write(ERR_DENIED); + socket.getOutputStream().write(ERR_DENIED.getBytes("UTF-8")); } catch (IOException ioe) {} try { socket.close(); @@ -341,7 +474,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { try { // Send a 503, so the user doesn't get an HTTP Proxy error message // and blame his router or the network. - socket.getOutputStream().write(ERR_UNAVAILABLE); + socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8")); } catch (IOException ioe) {} try { socket.close(); @@ -362,7 +495,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { try { // Send a 503, so the user doesn't get an HTTP Proxy error message // and blame his router or the network. - socket.getOutputStream().write(ERR_UNAVAILABLE); + socket.getOutputStream().write(ERR_UNAVAILABLE.getBytes("UTF-8")); } catch (IOException ioe) {} try { socket.close(); @@ -453,7 +586,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { try { if (browserout == null) browserout = _browser.getOutputStream(); - browserout.write(ERR_UNAVAILABLE); + browserout.write(ERR_UNAVAILABLE.getBytes("UTF-8")); } catch (IOException ioe) {} } catch (IOException ioe) { if (_log.shouldLog(Log.WARN)) @@ -633,9 +766,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { return buf.toString(); } - /** ridiculously long, just to prevent OOM DOS @since 0.7.13 */ - private static final int MAX_HEADERS = 60; - /** * Add an entry to the multimap. */ @@ -680,10 +810,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { * @param socket if null, use in as InputStream * @param in if null, use socket.getInputStream() as InputStream * @param command out parameter, first line - * @param command out parameter, first line * @throws SocketTimeoutException if timeout is reached before newline * @throws EOFException if EOF is reached before newline - * @throws LineTooLongException if too long + * @throws LineTooLongException if one header too long, or too many headers, or total size too big + * @throws RequestTooLongException if too long + * @throws BadRequestException on bad headers * @throws IOException on other errors in the underlying stream */ private static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, @@ -694,51 +825,49 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { // slowloris / darkloris long expire = ctx.clock().now() + TOTAL_HEADER_TIMEOUT; if (socket != null) { - readLine(socket, command, HEADER_TIMEOUT); + try { + readLine(socket, command, HEADER_TIMEOUT); + } catch (LineTooLongException ltle) { + // convert for first line + throw new RequestTooLongException("Request too long - max " + MAX_LINE_LENGTH); + } } else { boolean ok = DataHelper.readLine(in, command); if (!ok) - throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]"); + throw new EOFException("EOF reached before the end of the headers"); } //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Read the http command [" + command.toString() + "]"); - // FIXME we probably don't need or want this in the outgoing direction - int trimmed = 0; - if (command.length() > 0) { - for (int i = 0; i < command.length(); i++) { - if (command.charAt(i) == 0) { - command = command.deleteCharAt(i); - i--; - trimmed++; - } - } - } - if (trimmed > 0) - ctx.statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed); - + int totalSize = command.length(); int i = 0; while (true) { - if (++i > MAX_HEADERS) - throw new IOException("Too many header lines - max " + MAX_HEADERS); + if (++i > MAX_HEADERS) { + throw new LineTooLongException("Too many header lines - max " + MAX_HEADERS); + } buf.setLength(0); if (socket != null) { readLine(socket, buf, expire - ctx.clock().now()); } else { boolean ok = DataHelper.readLine(in, buf); if (!ok) - throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]"); + throw new BadRequestException("EOF reached before the end of the headers"); } if ( (buf.length() == 0) || ((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) { // end of headers reached return headers; } else { - if (ctx.clock().now() >= expire) - throw new IOException("Headers took too long [" + buf.toString() + "]"); + if (ctx.clock().now() > expire) { + throw new SocketTimeoutException("Headers took too long"); + } int split = buf.indexOf(":"); - if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]"); + if (split <= 0) + throw new BadRequestException("Invalid HTTP header, missing colon"); + totalSize += buf.length(); + if (totalSize > MAX_TOTAL_HEADER_SIZE) + throw new LineTooLongException("Req+headers too big"); String name = buf.substring(0, split).trim(); String value = null; if (buf.length() > split + 1) @@ -831,5 +960,23 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { super(s); } } + + /** + * @since 0.9.20 + */ + private static class RequestTooLongException extends IOException { + public RequestTooLongException(String s) { + super(s); + } + } + + /** + * @since 0.9.20 + */ + private static class BadRequestException extends IOException { + public BadRequestException(String s) { + super(s); + } + } } diff --git a/apps/i2ptunnel/jsp/web.xml b/apps/i2ptunnel/jsp/web.xml index 1fd11c13d8adb73bab7a400ef46d2a6cedc60a3c..0a74b68357862e51250b96e08bcf82b2db9e966a 100644 --- a/apps/i2ptunnel/jsp/web.xml +++ b/apps/i2ptunnel/jsp/web.xml @@ -32,10 +32,14 @@ <url-pattern>/wizard</url-pattern> </servlet-mapping> + <!-- this webapp doesn't actually use sessions or cookies --> <session-config> <session-timeout> 30 </session-timeout> + <cookie-config> + <http-only>true</http-only> + </cookie-config> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> diff --git a/apps/routerconsole/jsp/web.xml b/apps/routerconsole/jsp/web.xml index 67f0b4ca6e599a2aa4d0e5e2adb5f8eb97256f05..ea183c83564b125d4de28deb2d93f9492cb848e3 100644 --- a/apps/routerconsole/jsp/web.xml +++ b/apps/routerconsole/jsp/web.xml @@ -35,6 +35,9 @@ <session-timeout> 30 </session-timeout> + <cookie-config> + <http-only>true</http-only> + </cookie-config> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java index 8c6212503f55472c8da027cfb1f8a3597e608c0e..451bb5f37c96197cb526f4a306e363192828de9c 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/Connection.java @@ -41,7 +41,8 @@ class Connection { private final MessageInputStream _inputStream; private final MessageOutputStream _outputStream; private final SchedulerChooser _chooser; - private volatile long _nextSendTime; + /** Locking: _nextSendLock */ + private long _nextSendTime; private long _ackedPackets; private final long _createdOn; private final AtomicLong _closeSentOn = new AtomicLong(); @@ -70,6 +71,8 @@ class Connection { private final AtomicBoolean _ackSinceCongestion; /** Notify this on connection (or connection failure) */ private final Object _connectLock; + /** Locking for _nextSendTime */ + private final Object _nextSendLock; /** how many messages have been resent and not yet ACKed? */ private final AtomicInteger _activeResends = new AtomicInteger(); private final ConEvent _connectionEvent; @@ -145,6 +148,7 @@ class Connection { _activityTimer = new ActivityTimer(); _ackSinceCongestion = new AtomicBoolean(true); _connectLock = new Object(); + _nextSendLock = new Object(); _connectionEvent = new ConEvent(); _randomWait = _context.random().nextInt(10*1000); // just do this once to reduce usage // all createRateStats in ConnectionManager @@ -907,7 +911,11 @@ class Connection { * instance, or want to delay an ACK. * @return the next time the scheduler will want to send a packet, or -1 if never. */ - public long getNextSendTime() { return _nextSendTime; } + public long getNextSendTime() { + synchronized(_nextSendLock) { + return _nextSendTime; + } + } /** * If the next send time is currently >= 0 (i.e. not "never"), @@ -917,25 +925,20 @@ class Connection { * options.getSendAckDelay() from now (1000 ms) */ public void setNextSendTime(long when) { - if (_nextSendTime >= 0) { - if (when < _nextSendTime) - _nextSendTime = when; - } else { - _nextSendTime = when; - } + synchronized(_nextSendLock) { + if (_nextSendTime >= 0) { + if (when < _nextSendTime) + _nextSendTime = when; + } else { + _nextSendTime = when; + } - if (_nextSendTime >= 0) { - long max = _context.clock().now() + _options.getSendAckDelay(); - if (max < _nextSendTime) - _nextSendTime = max; + if (_nextSendTime >= 0) { + long max = _context.clock().now() + _options.getSendAckDelay(); + if (max < _nextSendTime) + _nextSendTime = max; + } } - - //if (_log.shouldLog(Log.DEBUG) && false) { - // if (_nextSendTime <= 0) - // _log.debug("set next send time to an unknown time", new Exception(toString())); - // else - // _log.debug("set next send time to " + (_nextSendTime-_context.clock().now()) + "ms from now", new Exception(toString())); - //} } /** how many packets have we sent and the other side has ACKed? @@ -1260,17 +1263,17 @@ class Connection { */ class ResendPacketEvent extends SimpleTimer2.TimedEvent { private final PacketLocal _packet; - private long _nextSendTime; + private long _nextSend; public ResendPacketEvent(PacketLocal packet, long delay) { super(_timer); _packet = packet; - _nextSendTime = delay + _context.clock().now(); + _nextSend = delay + _context.clock().now(); packet.setResendPacketEvent(ResendPacketEvent.this); schedule(delay); } - public long getNextSendTime() { return _nextSendTime; } + public long getNextSendTime() { return _nextSend; } public void timeReached() { retransmit(); } /** * Retransmit the packet if we need to. @@ -1320,7 +1323,7 @@ class Connection { + _activeResends + " active resend, " + _outboundPackets.size() + " unacked, window size = " + _options.getWindowSize()); forceReschedule(1333); - _nextSendTime = 1333 + _context.clock().now(); + _nextSend = 1333 + _context.clock().now(); return false; } @@ -1407,7 +1410,7 @@ class Connection { if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) ) timeout = MAX_RESEND_DELAY; // set this before enqueue() as it passes it on to the router - _nextSendTime = timeout + _context.clock().now(); + _nextSend = timeout + _context.clock().now(); if (_outboundQueue.enqueue(_packet)) { // first resend for this packet ? diff --git a/apps/susimail/src/WEB-INF/web.xml b/apps/susimail/src/WEB-INF/web.xml index 23adde2d5b4eb77df38bfc366c1a640b85b25e30..22f6e9b6f086b9fa7b89473de7706660de888d16 100644 --- a/apps/susimail/src/WEB-INF/web.xml +++ b/apps/susimail/src/WEB-INF/web.xml @@ -23,6 +23,9 @@ </servlet-mapping> <session-config> <session-timeout>1440</session-timeout> + <cookie-config> + <http-only>true</http-only> + </cookie-config> </session-config> <!-- tomcat (untested) --> <context-param> diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 157a86a7139196220dddd912142f326c28c3a9e2..6a1c4230aa0ae3f56cd551c54b41aa0278504ccf 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -904,10 +904,14 @@ public class EepGet { _keepFetching = false; _notModified = true; return; + case 400: // bad req case 401: // server auth case 403: // bad req case 404: // not found + case 408: // req timeout case 409: // bad addr helper + case 414: // URI too long + case 431: // headers too long case 503: // no outproxy _transferFailed = true; if (_alreadyTransferred > 0 || !_shouldWriteErrorToOutput) { diff --git a/installer/resources/eepsite/jetty-ssl.xml b/installer/resources/eepsite/jetty-ssl.xml index c6d91cc83db728947563a057f3307e6698cd2e81..d27effe37b82227bccca1cade223e0fc5bcbfa8b 100644 --- a/installer/resources/eepsite/jetty-ssl.xml +++ b/installer/resources/eepsite/jetty-ssl.xml @@ -1,35 +1,255 @@ <?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> +<!-- ========================================================================= --> +<!-- If you have a 'split' directory installation, with configuration --> +<!-- files in ~/.i2p (Linux) or %APPDATA%\I2P (Windows), be sure to --> +<!-- edit the file in the configuration directory, NOT the install directory. --> +<!-- When running as a Linux daemon, the configuration directory is --> +<!-- /var/lib/i2p and the install directory is /usr/share/i2p . --> +<!-- --> +<!-- ========================================================================= --> + <!-- =============================================================== --> <!-- Configure SSL for the Jetty Server --> <!-- this configuration file should be used in combination with --> -<!-- other configuration files. e.g. --> -<!-- java -jar start.jar etc/jetty-ssl.xml --> +<!-- other configuration files. --> +<!-- --> +<!-- =============================================================== --> +<!-- Add a HTTPS SSL listener on port 7668 --> +<!-- --> +<!-- NOTE: --> +<!-- --> +<!-- While I2P already encrypts end-to-end, HTTPS support --> +<!-- is valuable for authentication. --> +<!-- --> +<!-- These instructions are to add SSL support to an existing --> +<!-- HTTP Jetty website. --> +<!-- --> +<!-- For HTTPS ONLY, create a standard server tunnel --> +<!-- (NOT HTTP server), and skip step 8. --> +<!-- --> +<!-- For non-Jetty servers (e.g. Apache), follow your server --> +<!-- instructions to generate and configure the certificates, --> +<!-- and skip steps 1-7. --> <!-- --> -<!-- alternately, add to the start.ini for easier usage --> <!-- =============================================================== --> +<!-- --> +<!-- To add SSL support for your existing website: --> +<!-- --> +<!-- Step 1: --> +<!-- Get the b32 for your wehsite, it's the link at the --> +<!-- "preview" button in the Hidden Services Manager in --> +<!-- the console. If you aren't running i2p, you can --> +<!-- get it from your private key file --> +<!-- (probably ~/.i2p/eepsite/eepPriv.dat) --> +<!-- with the command: --> +<!-- java -cp ~/i2p/lib/i2p.jar net.i2p.data.PrivateKeyFile ~/.i2p/eepsite/eepPriv.dat --> +<!-- Save the b32 to put in the certificate's CN in Step 2. --> +<!-- --> +<!-- --> +<!-- Step 2: --> +<!-- Generate selfsigned certificates. --> +<!-- We recommend two: one for the hostname, and one for the b32. --> +<!-- Note that server-side SNI to serve the correct certificate --> +<!-- requires Java 8. Otherwise it will pick one. --> +<!-- (at random? first one?) --> +<!-- Change the CN and key password in the example, of course. --> +<!-- It's OK to keep the keystore password as "changeit" if you like. --> +<!-- Use the same passwords for both certificates. --> +<!-- See https://wiki.eclipse.org/Jetty/Howto/Configure_SSL --> +<!-- for alternate methods. --> +<!-- + keytool -genkey -keystore ~/.i2p/eepsite/etc/keystore.ks -storepass changeit -alias b32 -dname CN=biglongkey.b32.i2p,OU=Eepsite,O=XX,L=XX,ST=XX,C=XX -validity 3652 -keyalg RSA -keysize 2048 -keypass myKeyPassword + keytool -genkey -keystore ~/.i2p/eepsite/etc/keystore.ks -storepass changeit -alias hostname -dname CN=example.i2p,OU=Eepsite,O=XX,L=XX,ST=XX,C=XX -validity 3652 -keyalg RSA -keysize 2048 -keypass myKeyPassword + chmod 600 ~/.i2p/eepsite/etc/keystore.ks + --> +<!-- --> +<!-- But does SNI work? see: --> +<!-- http://blog.ivanristic.com/2014/03/ssl-tls-improvements-in-java-8.html --> +<!-- http://stackoverflow.com/questions/20887504/tls-extension-server-name-indication-sni-value-not-available-on-server-side --> +<!-- --> +<!-- And no, you can't get a real certificate for an i2p --> +<!-- address from a Certificate Authority, but someday --> +<!-- it may be possible. Here's how Tor did it: --> +<!-- https://cabforum.org/2015/02/18/ballot-144-validation-rules-dot-onion-names/ --> +<!-- --> +<!-- --> +<!-- Step 3: --> +<!-- Update this configuration file. --> +<!-- Edit the KeyStorePassword, TrustStorePassword, and --> +<!-- KeyManagerPassword below to match the passwords from Step 2. --> +<!-- --> +<!-- --> +<!-- Step 4: --> +<!-- If running I2P, stop the website Jetty on /configclients --> +<!-- in the console. --> +<!-- --> +<!-- --> +<!-- Step 5: --> +<!-- Configure Jetty to read in this file at startup. --> +<!-- If running I2P, edit the website Jetty on /configclients --> +<!-- to add the argument "/path/to/.i2p/eepsite/jetty-ssl.xml". --> +<!-- --> +<!-- If I2P is not running, edit the file ~/.i2p/clients.config --> +<!-- to add the argument "/path/to/.i2p/eepsite/jetty-ssl.xml" --> +<!-- at the end of the line: --> +<-- clientApp.3.args="eepsite/jetty.xml" --> +<!-- so it now looks like: --> +<-- clientApp.3.args="/path to/.i2p/eepsite/jetty.xml" "/path/to/.i2p/eepsite/jetty-ssl.xml" --> +<!-- --> +<!-- --> +<!-- Step 6: --> +<!-- Start Jetty. --> +<!-- If running I2P, start the website Jetty on /configclients --> +<!-- in the console. --> +<!-- If I2P is not running, start it. --> +<!-- --> +<!-- Now go to the /logs page in the console and check for errors --> +<!-- in both the router and wrapper logs. --> +<!-- --> +<!-- --> +<!-- Step 7: --> +<!-- Test Jetty. --> +<!-- If there were no errors, test your Jetty SSL by --> +<!-- going to https://127.0.0.1:7668/ in your browser. --> +<!-- You will have to confirm the security exception for --> +<!-- the selfsigned certificate. --> +<!-- --> +<!-- --> +<!-- Step 8: --> +<!-- Configure i2ptunnel. --> +<!-- Tell i2ptunnel to route SSL to port 7668 by adding the --> +<!-- following custom option on the i2ptunnel edit page --> +<!-- for your website: --> +<!-- targetForPort.443=127.0.0.1:7668 --> +<!-- Also, verify that "Use SSL" near the top is NOT set. --> +<!-- That would be SSL-over-SSL, which won't work. --> +<!-- --> +<!-- --> +<!-- Step 9: --> +<!-- Start the tunnel if it isn't started. --> +<!-- --> +<!-- --> +<!-- Step 10: --> +<!-- In the i2ptunnel HTTP Client configuration, --> +<!-- enable "Allow SSL to I2P addresses" if it isn't already. --> +<!-- --> +<!-- --> +<!-- Step 11: --> +<!-- Test SSL via i2ptunnel. --> +<!-- Test SSL to your website through I2P by entering --> +<!-- https://yoursite.i2p/ in your browser. --> +<!-- If it doesn't work, check the /logs page in the console. --> +<!-- You may need to adjust your browser proxy settings to --> +<!-- ensure that https i2p URLs are fetched through the I2P proxy. --> +<!-- For example, in privoxy, add --> +<!-- https://*.i2p/* and https://*.i2p:*/* --> +<!-- --> +<!-- --> +<!-- Step 12: --> +<!-- Tell your users. --> +<!-- Put a link to the https version on your --> +<!-- home page. Remind them that in --> +<!-- the i2ptunnel HTTP Client configuration, --> +<!-- enable "Allow SSL to I2P addresses" if it isn't already. --> +<!-- Remind them to confirm the security exception for --> +<!-- the selfsigned certificate (but not one for a hostname --> +<!-- mismatch) (but see SNI issues above). --> +<!-- Users may need to adjust their browser proxy settings to --> +<!-- ensure that https i2p URLs are fetched through the I2P proxy. --> +<!-- For example, in privoxy, add --> +<!-- https://*.i2p/* and https://*.i2p:*/* --> +<!-- --> +<!-- Decide what link to use. The hostname is not secure, --> +<!-- as users may have a different hostname in their browser. --> +<!-- Also, new address helpers won't work with SSL. --> +<!-- The b32 is the recommended hostname. --> +<!-- --> +<!-- --> +<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <Configure id="Server" class="org.eclipse.jetty.server.Server"> <!-- if NIO is not available, use org.eclipse.jetty.server.ssl.SslSocketConnector --> <New id="sslContextFactory" class="org.eclipse.jetty.http.ssl.SslContextFactory"> - <Set name="KeyStore">./eepsite/etc/keystore</Set> - <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set> - <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set> - <Set name="TrustStore">./eepsite/etc/keystore</Set> - <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set> + <Set name="KeyStore">./eepsite/etc/keystore.ks</Set> + <Set name="KeyStorePassword">changeit</Set> + <Set name="KeyManagerPassword">myKeyPassword</Set> + <Set name="TrustStore">./eepsite/etc/keystore.ks</Set> + <Set name="TrustStorePassword">changeit</Set> </New> <Call name="addConnector"> <Arg> <New class="org.eclipse.jetty.server.ssl.SslSelectChannelConnector"> <Arg><Ref id="sslContextFactory" /></Arg> - <Set name="Port">8443</Set> + <Set name="host">127.0.0.1</Set> + <Set name="port">7668</Set> <Set name="maxIdleTime">600000</Set> <Set name="useDirectBuffers">false</Set> - <Set name="Acceptors">2</Set> - <Set name="AcceptQueueSize">100</Set> + <Set name="acceptors">1</Set> + <Set name="statsOn">false</Set> + <Set name="lowResourcesConnections">5000</Set> + <Set name="lowResourcesMaxIdleTime">5000</Set> + <Set name="ExcludeCipherSuites"> + <Array type="java.lang.String"> + <Item>SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA</Item> + <Item>SSL_DH_anon_EXPORT_WITH_RC4_40_MD5</Item> + <Item>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</Item> + <Item>SSL_DH_anon_WITH_DES_CBC_SHA</Item> + <Item>SSL_DH_anon_WITH_RC4_128_MD5</Item> + <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item> + <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item> + <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item> + <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item> + <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item> + <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item> + <Item>SSL_RSA_WITH_DES_CBC_SHA</Item> + <Item>SSL_RSA_WITH_NULL_MD5</Item> + <Item>SSL_RSA_WITH_NULL_SHA</Item> + <Item>TLS_DH_anon_WITH_AES_128_CBC_SHA</Item> + <Item>TLS_DH_anon_WITH_AES_128_CBC_SHA256</Item> + <Item>TLS_DH_anon_WITH_AES_128_GCM_SHA256</Item> + <Item>TLS_DH_anon_WITH_AES_256_CBC_SHA</Item> + <Item>TLS_DH_anon_WITH_AES_256_CBC_SHA256</Item> + <Item>TLS_DH_anon_WITH_AES_256_GCM_SHA384</Item> + <Item>TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA</Item> + <Item>TLS_ECDH_anon_WITH_AES_128_CBC_SHA</Item> + <Item>TLS_ECDH_anon_WITH_AES_256_CBC_SHA</Item> + <Item>TLS_ECDH_anon_WITH_NULL_SHA</Item> + <Item>TLS_ECDH_anon_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDH_ECDSA_WITH_NULL_SHA</Item> + <Item>TLS_ECDHE_ECDSA_WITH_NULL_SHA</Item> + <Item>TLS_ECDHE_RSA_WITH_NULL_SHA</Item> + <Item>TLS_ECDH_RSA_WITH_NULL_SHA</Item> + <Item>TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5</Item> + <Item>TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA</Item> + <Item>TLS_KRB5_EXPORT_WITH_RC4_40_MD5</Item> + <Item>TLS_KRB5_EXPORT_WITH_RC4_40_SHA</Item> + <Item>TLS_KRB5_WITH_3DES_EDE_CBC_MD5</Item> + <Item>TLS_KRB5_WITH_3DES_EDE_CBC_SHA</Item> + <Item>TLS_KRB5_WITH_DES_CBC_MD5</Item> + <Item>TLS_KRB5_WITH_DES_CBC_SHA</Item> + <Item>TLS_KRB5_WITH_RC4_128_MD5</Item> + <Item>TLS_KRB5_WITH_RC4_128_SHA</Item> + <Item>TLS_RSA_WITH_NULL_SHA256</Item> + <Item>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</Item> + <Item>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</Item> + <Item>SSL_RSA_WITH_3DES_EDE_CBC_SHA</Item> + <Item>SSL_RSA_WITH_RC4_128_MD5</Item> + <Item>SSL_RSA_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDH_ECDSA_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDH_RSA_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDHE_ECDSA_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDHE_RSA_WITH_RC4_128_SHA</Item> + <Item>TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA</Item> + <Item>TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA</Item> + <Item>TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA</Item> + <Item>TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</Item> + </Array> + </Set> </New> </Arg> </Call> diff --git a/installer/resources/eepsite/jetty.xml b/installer/resources/eepsite/jetty.xml index b52445953136e2fac897dd22ec1b107547eb4311..432bb347e3b5bb7e7cc10602fda52381bc556987 100644 --- a/installer/resources/eepsite/jetty.xml +++ b/installer/resources/eepsite/jetty.xml @@ -135,7 +135,6 @@ <Set name="maxIdleTime">600000</Set> <Set name="Acceptors">1</Set> <Set name="statsOn">false</Set> - <Set name="confidentialPort">8443</Set> <Set name="lowResourcesConnections">5000</Set> <Set name="lowResourcesMaxIdleTime">5000</Set> <Set name="useDirectBuffers">false</Set> @@ -155,7 +154,6 @@ <Set name="maxIdleTime">600000</Set> <Set name="Acceptors">1</Set> <Set name="statsOn">false</Set> - <Set name="confidentialPort">8443</Set> </New> </Arg> </Call> @@ -168,19 +166,6 @@ <!-- --> <!-- clientApp3.args=etc/jetty.xml etc/jetty-ssl.xml --> <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> - <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> - <!-- Add a HTTPS SSL listener on port 8443 --> - <!-- --> - <!-- In the unlikely event you would want SSL support for your eepsite. --> - <!-- You would need to generate a selfsigned certificate in a keystore --> - <!-- in ~/.i2p/eepsite/keystore.ks, for example with the command line: --> - <!-- - keytool -genkey -storetype JKS -keystore ~/.i2p/eepsite/etc/keystore.ks -storepass changeit -alias console -dname CN=xyz123.eepsite.i2p.net,OU=Eepsite,O=I2P Anonymous Network,L=XX,ST=XX,C=XX -validity 3650 -keyalg DSA -keysize 1024 -keypass myKeyPassword - --> - <!-- Change the CN and key password in the example, of course. --> - <!-- You wouldn't want to open this up to the regular internet, --> - <!-- would you?? Untested and not recommended. --> - <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <!-- =========================================================== --> <!-- Set up global session ID manager --> diff --git a/installer/resources/i2ptunnel.config b/installer/resources/i2ptunnel.config index 0f5092cc0aa261ab3cdfec4147b197e483567bcb..944def78d99d13aceada38f4f0c3fad9810cf2ba 100644 --- a/installer/resources/i2ptunnel.config +++ b/installer/resources/i2ptunnel.config @@ -98,6 +98,8 @@ tunnel.3.option.inbound.length=3 tunnel.3.option.inbound.lengthVariance=0 tunnel.3.option.outbound.length=3 tunnel.3.option.outbound.lengthVariance=0 +# uncomment for HTTPS to port 7668 +#tunnel.3.option.targetForPort.443=127.0.0.1:7668 tunnel.3.startOnLoad=false # postman's SMTP server - see hq.postman.i2p diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index 42e88ca2cd1ef0203f2cfcef536ea25a5b63360c..0b0f84fd27f1633f016b7614a7cc874d56c8125f 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -75,7 +75,7 @@ public class JobQueue { /** default max # job queue runners operating */ private final static int DEFAULT_MAX_RUNNERS = 1; - /** router.config parameter to override the max runners @deprecated unimplemented */ + /** router.config parameter to override the max runners */ private final static String PROP_MAX_RUNNERS = "router.maxJobRunners"; /** how frequently should we check and update the max runners */ @@ -330,7 +330,7 @@ public class JobQueue { public void allowParallelOperation() { _allowParallelOperation = true; - runQueue(RUNNERS); + runQueue(_context.getProperty(PROP_MAX_RUNNERS, RUNNERS)); } /** diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 524836cf3f2b04472b9556350f7a5d31be2fe8a8..3a244d715acf8a1d16677a1b45567657dc26f19b 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -8,15 +8,10 @@ package net.i2p.router; * */ -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -49,7 +44,6 @@ import net.i2p.router.util.EventLog; import net.i2p.stat.RateStat; import net.i2p.stat.StatManager; import net.i2p.util.ByteCache; -import net.i2p.util.FileUtil; import net.i2p.util.FortunaRandomSource; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PThread; @@ -113,6 +107,7 @@ public class Router implements RouterClock.ClockShiftListener { private final static String DNS_CACHE_TIME = "" + (5*60); private static final String EVENTLOG = "eventlog.txt"; private static final String PROP_JBIGI = "jbigi.loadedResource"; + public static final String UPDATE_FILE = "i2pupdate.zip"; private static final String originalTimeZoneID; static { @@ -1497,199 +1492,11 @@ public class Router implements RouterClock.ClockShiftListener { // If it does an update, it never returns. // I guess it's better to have the other-router check above this, we don't want to // overwrite an existing running router's jar files. Other than ours. - r.installUpdates(); + InstallUpdate.installUpdates(r); // ********* Start no threads before here ********* // r.runRouter(); } } - - public static final String UPDATE_FILE = "i2pupdate.zip"; - private static final String DELETE_FILE = "deletelist.txt"; - - /** - * Context must be available. - * Unzip update file found in the router dir OR base dir, to the base dir - * - * If successful, will call exit() and never return. - * - * If we can't write to the base dir, complain. - * Note: _log not available here. - */ - private void installUpdates() { - File updateFile = new File(_context.getRouterDir(), UPDATE_FILE); - boolean exists = updateFile.exists(); - if (!exists) { - updateFile = new File(_context.getBaseDir(), UPDATE_FILE); - exists = updateFile.exists(); - } - if (exists) { - // do a simple permissions test, if it fails leave the file in place and don't restart - File test = new File(_context.getBaseDir(), "history.txt"); - if ((test.exists() && !test.canWrite()) || (!_context.getBaseDir().canWrite())) { - System.out.println("ERROR: No write permissions on " + _context.getBaseDir() + - " to extract software update file"); - // carry on - return; - } - System.out.println("INFO: Update file exists [" + UPDATE_FILE + "] - installing"); - // verify the whole thing first - // we could remember this fails, and not bother restarting, but who cares... - boolean ok = FileUtil.verifyZip(updateFile); - if (ok) { - // This may be useful someday. First added in 0.8.2 - // Moved above the extract so we don't NCDFE - _config.put("router.updateLastInstalled", "" + System.currentTimeMillis()); - // Set the last version to the current version, since 0.8.13 - _config.put("router.previousVersion", RouterVersion.VERSION); - _config.put("router.previousFullVersion", RouterVersion.FULL_VERSION); - saveConfig(); - ok = FileUtil.extractZip(updateFile, _context.getBaseDir()); - } - - // Very important - we have now trashed our jars. - // After this point, do not use any new I2P classes, or they will fail to load - // and we will die with NCDFE. - // Ideally, do not use I2P classes at all, new or not. - try { - if (ok) { - // We do this here so we may delete old jars before we restart - deleteListedFiles(); - System.out.println("INFO: Update installed"); - } else { - System.out.println("ERROR: Update failed!"); - } - if (!ok) { - // we can't leave the file in place or we'll continually restart, so rename it - File bad = new File(_context.getRouterDir(), "BAD-" + UPDATE_FILE); - boolean renamed = updateFile.renameTo(bad); - if (renamed) { - System.out.println("Moved update file to " + bad.getAbsolutePath()); - } else { - System.out.println("Deleting file " + updateFile.getAbsolutePath()); - ok = true; // so it will be deleted - } - } - if (ok) { - boolean deleted = updateFile.delete(); - if (!deleted) { - System.out.println("ERROR: Unable to delete the update file!"); - updateFile.deleteOnExit(); - } - } - // exit whether ok or not - if (_context.hasWrapper()) - System.out.println("INFO: Restarting after update"); - else - System.out.println("WARNING: Exiting after update, restart I2P"); - } catch (Throwable t) { - // hide the NCDFE - // hopefully the update file got deleted or we will loop - } - System.exit(EXIT_HARD_RESTART); - } else { - deleteJbigiFiles(); - // It was here starting in 0.8.12 so it could be used the very first time - // Now moved up so it is usually run only after an update - // But the first time before jetty 6 it will run here... - // Here we can't remove jars - deleteListedFiles(); - } - } - - /** - * Remove extracted libjbigi.so and libjcpuid.so files if we have a newer jbigi.jar, - * so the new ones will be extracted. - * We do this after the restart, not after the extract, because it's safer, and - * because people may upgrade their jbigi.jar file manually. - * - * Copied from NativeBigInteger, which we can't access here or the - * libs will get loaded. - */ - private void deleteJbigiFiles() { - boolean isX86 = SystemVersion.isX86(); - String osName = System.getProperty("os.name").toLowerCase(Locale.US); - boolean isWin = SystemVersion.isWindows(); - boolean isMac = SystemVersion.isMac(); - // only do this on these OSes - boolean goodOS = isWin || isMac || - osName.contains("linux") || osName.contains("freebsd"); - - // only do this on these x86 - File jbigiJar = new File(_context.getBaseDir(), "lib/jbigi.jar"); - if (isX86 && goodOS && jbigiJar.exists()) { - String libPrefix = (isWin ? "" : "lib"); - String libSuffix = (isWin ? ".dll" : isMac ? ".jnilib" : ".so"); - - File jcpuidLib = new File(_context.getBaseDir(), libPrefix + "jcpuid" + libSuffix); - if (jcpuidLib.canWrite() && jbigiJar.lastModified() > jcpuidLib.lastModified()) { - String path = jcpuidLib.getAbsolutePath(); - boolean success = FileUtil.copy(path, path + ".bak", true, true); - if (success) { - boolean success2 = jcpuidLib.delete(); - if (success2) { - System.out.println("New jbigi.jar detected, moved jcpuid library to " + - path + ".bak"); - System.out.println("Check logs for successful installation of new library"); - } - } - } - - File jbigiLib = new File(_context.getBaseDir(), libPrefix + "jbigi" + libSuffix); - if (jbigiLib.canWrite() && jbigiJar.lastModified() > jbigiLib.lastModified()) { - String path = jbigiLib.getAbsolutePath(); - boolean success = FileUtil.copy(path, path + ".bak", true, true); - if (success) { - boolean success2 = jbigiLib.delete(); - if (success2) { - System.out.println("New jbigi.jar detected, moved jbigi library to " + - path + ".bak"); - System.out.println("Check logs for successful installation of new library"); - } - } - } - } - } - - /** - * Delete all files listed in the delete file. - * Format: One file name per line, comment lines start with '#'. - * All file names must be relative to $I2P, absolute file names not allowed. - * We probably can't remove old jars this way. - * Fails silently. - * Use no new I2P classes here so it may be called after zip extraction. - * @since 0.8.12 - */ - private void deleteListedFiles() { - File deleteFile = new File(_context.getBaseDir(), DELETE_FILE); - if (!deleteFile.exists()) - return; - // this is similar to FileUtil.readTextFile() but we can't use any I2P classes here - FileInputStream fis = null; - BufferedReader in = null; - try { - fis = new FileInputStream(deleteFile); - in = new BufferedReader(new InputStreamReader(fis, "UTF-8")); - String line; - while ( (line = in.readLine()) != null) { - String fl = line.trim(); - if (fl.contains("..") || fl.startsWith("#") || fl.length() == 0) - continue; - File df = new File(fl); - if (df.isAbsolute()) - continue; - df = new File(_context.getBaseDir(), fl); - if (df.exists() && !df.isDirectory()) { - if (df.delete()) - System.out.println("INFO: File [" + fl + "] deleted"); - } - } - } catch (IOException ioe) {} - finally { - if (in != null) try { in.close(); } catch(IOException ioe) {} - if (deleteFile.delete()) - System.out.println("INFO: File [" + DELETE_FILE + "] deleted"); - } - } /******* private static void verifyWrapperConfig() { diff --git a/router/java/src/net/i2p/router/tasks/InstallUpdate.java b/router/java/src/net/i2p/router/tasks/InstallUpdate.java new file mode 100644 index 0000000000000000000000000000000000000000..b4bea6770669175e48e2ef59a18a66c858b16cea --- /dev/null +++ b/router/java/src/net/i2p/router/tasks/InstallUpdate.java @@ -0,0 +1,215 @@ +package net.i2p.router.tasks; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.RouterVersion; +import net.i2p.util.FileUtil; +import net.i2p.util.SystemVersion; + +/** + * If the i2pupdate.zip file is present, + * unzip it and JVM exit. + * + * @since 0.9.20 moved from Router.java + */ +public class InstallUpdate { + + private static final String DELETE_FILE = "deletelist.txt"; + + /** + * Context must be available. + * Unzip update file found in the router dir OR base dir, to the base dir + * + * If successful, will call exit() and never return. + * + * If we can't write to the base dir, write message to System.out and return. + * Note: _log not available here. + */ + public static void installUpdates(Router r) { + RouterContext context = r.getContext(); + File updateFile = new File(context.getRouterDir(), Router.UPDATE_FILE); + boolean exists = updateFile.exists(); + if (!exists) { + updateFile = new File(context.getBaseDir(), Router.UPDATE_FILE); + exists = updateFile.exists(); + } + if (exists) { + // do a simple permissions test, if it fails leave the file in place and don't restart + File test = new File(context.getBaseDir(), "history.txt"); + if ((test.exists() && !test.canWrite()) || (!context.getBaseDir().canWrite())) { + System.out.println("ERROR: No write permissions on " + context.getBaseDir() + + " to extract software update file"); + // carry on + return; + } + System.out.println("INFO: Update file exists [" + Router.UPDATE_FILE + "] - installing"); + // verify the whole thing first + // we could remember this fails, and not bother restarting, but who cares... + boolean ok = FileUtil.verifyZip(updateFile); + if (ok) { + // This may be useful someday. First added in 0.8.2 + // Moved above the extract so we don't NCDFE + Map<String, String> config = new HashMap<String, String>(4); + config.put("router.updateLastInstalled", "" + System.currentTimeMillis()); + // Set the last version to the current version, since 0.8.13 + config.put("router.previousVersion", RouterVersion.VERSION); + config.put("router.previousFullVersion", RouterVersion.FULL_VERSION); + r.saveConfig(config, null); + ok = FileUtil.extractZip(updateFile, context.getBaseDir()); + } + + // Very important - we have now trashed our jars. + // After this point, do not use any new I2P classes, or they will fail to load + // and we will die with NCDFE. + // Ideally, do not use I2P classes at all, new or not. + try { + if (ok) { + // We do this here so we may delete old jars before we restart + deleteListedFiles(context); + System.out.println("INFO: Update installed"); + } else { + System.out.println("ERROR: Update failed!"); + } + if (!ok) { + // we can't leave the file in place or we'll continually restart, so rename it + File bad = new File(context.getRouterDir(), "BAD-" + Router.UPDATE_FILE); + boolean renamed = updateFile.renameTo(bad); + if (renamed) { + System.out.println("Moved update file to " + bad.getAbsolutePath()); + } else { + System.out.println("Deleting file " + updateFile.getAbsolutePath()); + ok = true; // so it will be deleted + } + } + if (ok) { + boolean deleted = updateFile.delete(); + if (!deleted) { + System.out.println("ERROR: Unable to delete the update file!"); + updateFile.deleteOnExit(); + } + } + // exit whether ok or not + if (context.hasWrapper()) + System.out.println("INFO: Restarting after update"); + else + System.out.println("WARNING: Exiting after update, restart I2P"); + } catch (Throwable t) { + // hide the NCDFE + // hopefully the update file got deleted or we will loop + } + System.exit(Router.EXIT_HARD_RESTART); + } else { + deleteJbigiFiles(context); + // It was here starting in 0.8.12 so it could be used the very first time + // Now moved up so it is usually run only after an update + // But the first time before jetty 6 it will run here... + // Here we can't remove jars + deleteListedFiles(context); + } + } + + /** + * Remove extracted libjbigi.so and libjcpuid.so files if we have a newer jbigi.jar, + * so the new ones will be extracted. + * We do this after the restart, not after the extract, because it's safer, and + * because people may upgrade their jbigi.jar file manually. + * + * Copied from NativeBigInteger, which we can't access here or the + * libs will get loaded. + */ + private static void deleteJbigiFiles(RouterContext context) { + boolean isX86 = SystemVersion.isX86(); + String osName = System.getProperty("os.name").toLowerCase(Locale.US); + boolean isWin = SystemVersion.isWindows(); + boolean isMac = SystemVersion.isMac(); + // only do this on these OSes + boolean goodOS = isWin || isMac || + osName.contains("linux") || osName.contains("freebsd"); + + // only do this on these x86 + File jbigiJar = new File(context.getBaseDir(), "lib/jbigi.jar"); + if (isX86 && goodOS && jbigiJar.exists()) { + String libPrefix = (isWin ? "" : "lib"); + String libSuffix = (isWin ? ".dll" : isMac ? ".jnilib" : ".so"); + + File jcpuidLib = new File(context.getBaseDir(), libPrefix + "jcpuid" + libSuffix); + if (jcpuidLib.canWrite() && jbigiJar.lastModified() > jcpuidLib.lastModified()) { + String path = jcpuidLib.getAbsolutePath(); + boolean success = FileUtil.copy(path, path + ".bak", true, true); + if (success) { + boolean success2 = jcpuidLib.delete(); + if (success2) { + System.out.println("New jbigi.jar detected, moved jcpuid library to " + + path + ".bak"); + System.out.println("Check logs for successful installation of new library"); + } + } + } + + File jbigiLib = new File(context.getBaseDir(), libPrefix + "jbigi" + libSuffix); + if (jbigiLib.canWrite() && jbigiJar.lastModified() > jbigiLib.lastModified()) { + String path = jbigiLib.getAbsolutePath(); + boolean success = FileUtil.copy(path, path + ".bak", true, true); + if (success) { + boolean success2 = jbigiLib.delete(); + if (success2) { + System.out.println("New jbigi.jar detected, moved jbigi library to " + + path + ".bak"); + System.out.println("Check logs for successful installation of new library"); + } + } + } + } + } + + /** + * Delete all files listed in the delete file. + * Format: One file name per line, comment lines start with '#'. + * All file names must be relative to $I2P, absolute file names not allowed. + * We probably can't remove old jars this way. + * Fails silently. + * Use no new I2P classes here so it may be called after zip extraction. + * @since 0.8.12 + */ + private static void deleteListedFiles(RouterContext context) { + File deleteFile = new File(context.getBaseDir(), DELETE_FILE); + if (!deleteFile.exists()) + return; + // this is similar to FileUtil.readTextFile() but we can't use any I2P classes here + FileInputStream fis = null; + BufferedReader in = null; + try { + fis = new FileInputStream(deleteFile); + in = new BufferedReader(new InputStreamReader(fis, "UTF-8")); + String line; + while ( (line = in.readLine()) != null) { + String fl = line.trim(); + if (fl.contains("..") || fl.startsWith("#") || fl.length() == 0) + continue; + File df = new File(fl); + if (df.isAbsolute()) + continue; + df = new File(context.getBaseDir(), fl); + if (df.exists() && !df.isDirectory()) { + if (df.delete()) + System.out.println("INFO: File [" + fl + "] deleted"); + } + } + } catch (IOException ioe) {} + finally { + if (in != null) try { in.close(); } catch(IOException ioe) {} + if (deleteFile.delete()) + System.out.println("INFO: File [" + DELETE_FILE + "] deleted"); + } + } +} + diff --git a/router/java/src/net/i2p/router/tasks/package.html b/router/java/src/net/i2p/router/tasks/package.html index e032edd6cbcbf50df121d685badfea90f4aff897..bc1a92730840d4a233751722629160ec4f1d7f60 100644 --- a/router/java/src/net/i2p/router/tasks/package.html +++ b/router/java/src/net/i2p/router/tasks/package.html @@ -4,6 +4,7 @@ Miscellaneous classes, mostly things that are executed periodically as Jobs, Threads, and SimpleTimer.TimedEvents. These are used only by Router.java. +Nothing here is to be used externally, not a part of the public API. </p> </body> </html> diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java index 3dc610b7813905e4b30623881cc764396f985822..190c3b6416078782733e2299f7caf659f95a406b 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java @@ -70,8 +70,12 @@ public class FIFOBandwidthRefiller implements Runnable { public static final int MIN_INBOUND_BANDWIDTH_PEAK = 3; /** For now, until there is some tuning and safe throttling, we set the floor at a 3KBps during burst */ public static final int MIN_OUTBOUND_BANDWIDTH_PEAK = 3; - /** Max for reasonable bloom filter false positive rate. See util/DecayingBloomFilter and tunnel/BloomFilterIVValidator */ - public static final int MAX_OUTBOUND_BANDWIDTH = 4096; + /** + * Max for reasonable Bloom filter false positive rate. + * Do not increase without adding a new Bloom filter size! + * See util/DecayingBloomFilter and tunnel/BloomFilterIVValidator. + */ + public static final int MAX_OUTBOUND_BANDWIDTH = 8192; /** * how often we replenish the queues. diff --git a/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java index 578390aa3311d1ce1a7bb6cae70c21bde15cc73f..bf0faad97b4fa7fa80d6fc958eda39b9f052f75f 100644 --- a/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java +++ b/router/java/src/net/i2p/router/tunnel/BloomFilterIVValidator.java @@ -1,9 +1,12 @@ package net.i2p.router.tunnel; +import java.io.File; + import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.router.util.DecayingBloomFilter; import net.i2p.router.util.DecayingHashSet; +import net.i2p.util.Log; import net.i2p.util.SimpleByteCache; import net.i2p.util.SystemVersion; @@ -26,12 +29,19 @@ class BloomFilterIVValidator implements IVValidator { private static final int MIN_SHARE_KBPS_TO_USE_BLOOM = 64; private static final int MIN_SHARE_KBPS_FOR_BIG_BLOOM = 512; private static final int MIN_SHARE_KBPS_FOR_HUGE_BLOOM = 1536; + private static final int MIN_SHARE_KBPS_FOR_HUGE2_BLOOM = 4096; private static final long MIN_MEM_TO_USE_BLOOM = 64*1024*1024l; private static final long MIN_MEM_FOR_BIG_BLOOM = 128*1024*1024l; private static final long MIN_MEM_FOR_HUGE_BLOOM = 256*1024*1024l; + private static final long MIN_MEM_FOR_HUGE2_BLOOM = 384*1024*1024l; /** for testing */ private static final String PROP_FORCE = "router.forceDecayingBloomFilter"; + /** for testing */ + private static final String PROP_DISABLE = "router.disableDecayingBloomFilter"; + /** + * @param Kbps share bandwidth + */ public BloomFilterIVValidator(RouterContext ctx, int KBps) { _context = ctx; // Select the filter based on share bandwidth and memory. @@ -39,21 +49,36 @@ class BloomFilterIVValidator implements IVValidator { // to keep acceptable false positive rates. // See DBF, BloomSHA1, and KeySelector for details. long maxMemory = SystemVersion.getMaxMemory(); - if (_context.getBooleanProperty(PROP_FORCE)) + if (_context.getBooleanProperty(PROP_FORCE)) { _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // 2MB fixed - else if (KBps < MIN_SHARE_KBPS_TO_USE_BLOOM || maxMemory < MIN_MEM_TO_USE_BLOOM) + } else if (_context.getBooleanProperty(PROP_DISABLE)) { + _filter = null; + } else if (KBps < MIN_SHARE_KBPS_TO_USE_BLOOM || maxMemory < MIN_MEM_TO_USE_BLOOM) { + if (KBps >= MIN_SHARE_KBPS_TO_USE_BLOOM) + warn(maxMemory, KBps, MIN_MEM_TO_USE_BLOOM, MIN_SHARE_KBPS_TO_USE_BLOOM); _filter = new DecayingHashSet(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // appx. 4MB max - else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE_BLOOM) + } else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE2_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE2_BLOOM) { + _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 26); // 16MB fixed + } else if (KBps >= MIN_SHARE_KBPS_FOR_HUGE_BLOOM && maxMemory >= MIN_MEM_FOR_HUGE_BLOOM) { + if (KBps >= MIN_SHARE_KBPS_FOR_HUGE2_BLOOM) + warn(maxMemory, KBps, MIN_MEM_FOR_HUGE2_BLOOM, MIN_SHARE_KBPS_FOR_HUGE2_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 25); // 8MB fixed - else if (KBps >= MIN_SHARE_KBPS_FOR_BIG_BLOOM && maxMemory >= MIN_MEM_FOR_BIG_BLOOM) + } else if (KBps >= MIN_SHARE_KBPS_FOR_BIG_BLOOM && maxMemory >= MIN_MEM_FOR_BIG_BLOOM) { + if (KBps >= MIN_SHARE_KBPS_FOR_HUGE_BLOOM) + warn(maxMemory, KBps, MIN_MEM_FOR_HUGE_BLOOM, MIN_SHARE_KBPS_FOR_HUGE_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV", 24); // 4MB fixed - else + } else { + if (KBps >= MIN_SHARE_KBPS_FOR_BIG_BLOOM) + warn(maxMemory, KBps, MIN_MEM_FOR_BIG_BLOOM, MIN_SHARE_KBPS_FOR_BIG_BLOOM); _filter = new DecayingBloomFilter(ctx, HALFLIFE_MS, 16, "TunnelIVV"); // 2MB fixed + } ctx.statManager().createRateStat("tunnel.duplicateIV", "Note that a duplicate IV was received", "Tunnels", new long[] { 60*60*1000l }); } public boolean receiveIV(byte ivData[], int ivOffset, byte payload[], int payloadOffset) { + if (_filter == null) // testing only + return true; byte[] buf = SimpleByteCache.acquire(HopProcessor.IV_LENGTH); DataHelper.xor(ivData, ivOffset, payload, payloadOffset, buf, 0, HopProcessor.IV_LENGTH); boolean dup = _filter.add(buf); @@ -62,5 +87,26 @@ class BloomFilterIVValidator implements IVValidator { return !dup; // return true if it is OK, false if it isn't } - public void destroy() { _filter.stopDecaying(); } + public void destroy() { + if (_filter != null) + _filter.stopDecaying(); + } + + /** @since 0.9.20 */ + private void warn(long maxMemory, int KBps, long recMaxMem, int threshKBps) { + if (SystemVersion.isAndroid()) + return; + String msg = + "Configured for " + DataHelper.formatSize(KBps *1024) + + "Bps share bandwidth but only " + + DataHelper.formatSize(maxMemory) + "B available memory." + + " Recommend increasing wrapper.java.maxmemory in " + + _context.getBaseDir() + File.separatorChar + "wrapper.config" + + // getMaxMemory() returns significantly lower than wrapper config, so add 10% + " to at least " + (recMaxMem * 11 / 10 / (1024*1024)) + " (MB)" + + " if the actual share bandwidth exceeds " + + DataHelper.formatSize(threshKBps * 1024) + "Bps."; + System.out.println("WARN: " + msg); + _context.logManager().getLog(BloomFilterIVValidator.class).logAlways(Log.WARN, msg); + } } diff --git a/router/java/src/net/i2p/router/util/DecayingBloomFilter.java b/router/java/src/net/i2p/router/util/DecayingBloomFilter.java index 9aa9c6715f3813d0ba5190ecdbd76ad545fd38e5..a5910e36efbb023735d05c3ad90aa75488ed9fca 100644 --- a/router/java/src/net/i2p/router/util/DecayingBloomFilter.java +++ b/router/java/src/net/i2p/router/util/DecayingBloomFilter.java @@ -372,6 +372,9 @@ public class DecayingBloomFilter { * * Following stats for m=25, k=10: * 1792 2.4E-6; 4096 0.14%; 5120 0.6%; 6144 1.7%; 8192 6.8%; 10240 15% + * + * Following stats for m=26, k=10: + * 4096 7.3E-6; 5120 4.5E-5; 6144 1.8E-4; 8192 0.14%; 10240 0.6%, 12288 1.7% *</pre> */ /***** @@ -400,16 +403,23 @@ public class DecayingBloomFilter { } private static void testByLong(int kbps, int m, int numRuns) { + System.out.println("Starting 8 byte test"); int messages = 60 * 10 * kbps; - Random r = new Random(); + java.util.Random r = new java.util.Random(); DecayingBloomFilter filter = new DecayingBloomFilter(I2PAppContext.getGlobalContext(), 600*1000, 8, "test", m); int falsePositives = 0; long totalTime = 0; double fpr = 0d; for (int j = 0; j < numRuns; j++) { + // screen out birthday paradoxes (waste of time and space?) + java.util.Set<Long> longs = new java.util.HashSet<Long>(messages); long start = System.currentTimeMillis(); for (int i = 0; i < messages; i++) { - if (filter.add(r.nextLong())) { + long rand; + do { + rand = r.nextLong(); + } while (!longs.add(Long.valueOf(rand))); + if (filter.add(rand)) { falsePositives++; //System.out.println("False positive " + falsePositives + " (testByLong j=" + j + " i=" + i + ")"); } @@ -422,13 +432,14 @@ public class DecayingBloomFilter { System.out.println("False postive rate should be " + fpr); System.out.println("After " + numRuns + " runs pushing " + messages + " entries in " + DataHelper.formatDuration(totalTime/numRuns) + " per run, there were " - + falsePositives + " false positives"); - + + falsePositives + " false positives (" + + (((double) falsePositives) / messages) + ')'); } private static void testByBytes(int kbps, int m, int numRuns) { + System.out.println("Starting 16 byte test"); byte iv[][] = new byte[60*10*kbps][16]; - Random r = new Random(); + java.util.Random r = new java.util.Random(); for (int i = 0; i < iv.length; i++) r.nextBytes(iv[i]); @@ -452,7 +463,8 @@ public class DecayingBloomFilter { System.out.println("False postive rate should be " + fpr); System.out.println("After " + numRuns + " runs pushing " + iv.length + " entries in " + DataHelper.formatDuration(totalTime/numRuns) + " per run, there were " - + falsePositives + " false positives"); + + falsePositives + " false positives (" + + (((double) falsePositives) / iv.length) + ')'); //System.out.println("inserted: " + bloom.size() + " with " + bloom.capacity() // + " (" + bloom.falsePositives()*100.0d + "% false positive)"); }