diff --git a/LICENSE.txt b/LICENSE.txt index a4bd67dcf1d27197eb682e436bb61be263b3a836..e3b631a3bae576f9d09254ddcc45e485b719babf 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -68,6 +68,10 @@ Public domain except as listed below: Copyright (C) 2001, 2007 Free Software Foundation, Inc. See licenses/LICENSE-LGPLv2.1.txt + SSLEepGet: + Contains some code Copyright 2006 Sun Microsystems, Inc. + See licenses/LICENSE-InstallCert.txt + Router: Public domain except as listed below: diff --git a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java index ac471df80c321164d930f6f934af56e6defedb6c..7efc27ea5cae8eff4353dc94c3469054a70c663d 100644 --- a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java +++ b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java @@ -49,6 +49,8 @@ import net.i2p.util.SecureFileOutputStream; */ class ConfigParser { + private static final boolean isWindows = System.getProperty("os.name").startsWith("Win"); + /** * Strip the comments from a String. Lines that begin with '#' and ';' are * considered comments, as well as any part of a line after a '#'. @@ -276,7 +278,8 @@ class ConfigParser { * Write contents of Map map to the File file. Output is written * with one key, value pair on each line, in the format: key=value. * Write to a temp file in the same directory and then rename, to not corrupt - * simultaneous accesses by the router. + * simultaneous accesses by the router. Except on Windows where renameTo() + * will fail if the target exists. * * @param map * A Map to write to file. @@ -286,14 +289,19 @@ class ConfigParser { * if file cannot be written to. */ public static void write(Map map, File file) throws IOException { - File tmp = SecureFile.createTempFile("hoststxt-", ".tmp", file.getAbsoluteFile().getParentFile()); - ConfigParser + boolean success = false; + if (!isWindows) { + File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile()); + ConfigParser .write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8"))); - boolean success = tmp.renameTo(file); + success = tmp.renameTo(file); + if (!success) { + tmp.delete(); + //System.out.println("Warning: addressbook rename fail from " + tmp + " to " + file); + } + } if (!success) { // hmm, that didn't work, try it the old way - System.out.println("Warning: addressbook rename fail from " + tmp + " to " + file); - tmp.delete(); ConfigParser .write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/DataLoader.java b/apps/i2psnark/java/src/org/klomp/snark/DataLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..0bd974f53817d70ba46e9d9c249adaa5030ea94b --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/DataLoader.java @@ -0,0 +1,14 @@ +package org.klomp.snark; + +/** + * Callback used to fetch data + * @since 0.8.2 + */ +interface DataLoader +{ + /** + * This is the callback that PeerConnectionOut calls to get the data from disk + * @return bytes or null for errors + */ + public byte[] loadData(int piece, int begin, int length); +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/Message.java b/apps/i2psnark/java/src/org/klomp/snark/Message.java index a91e5ca234a86dd6f912e280a948cb5a7f4f7694..cdde79a18191f7c2c65dc064d2ec300c311e0c6e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Message.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Message.java @@ -39,23 +39,28 @@ class Message final static byte REQUEST = 6; final static byte PIECE = 7; final static byte CANCEL = 8; + final static byte EXTENSION = 20; // Not all fields are used for every message. // KEEP_ALIVE doesn't have a real wire representation byte type; // Used for HAVE, REQUEST, PIECE and CANCEL messages. + // low byte used for EXTENSION message int piece; // Used for REQUEST, PIECE and CANCEL messages. int begin; int length; - // Used for PIECE and BITFIELD messages + // Used for PIECE and BITFIELD and EXTENSION messages byte[] data; int off; int len; + // Used to do deferred fetch of data + DataLoader dataLoader; + SimpleTimer.TimedEvent expireEvent; /** Utility method for sending a message through a DataStream. */ @@ -68,6 +73,13 @@ class Message return; } + // Get deferred data + if (data == null && dataLoader != null) { + data = dataLoader.loadData(piece, begin, length); + if (data == null) + return; // hmm will get retried, but shouldn't happen + } + // Calculate the total length in bytes // Type is one byte. @@ -85,8 +97,12 @@ class Message if (type == REQUEST || type == CANCEL) datalen += 4; + // length is 1 byte + if (type == EXTENSION) + datalen += 1; + // add length of data for piece or bitfield array. - if (type == BITFIELD || type == PIECE) + if (type == BITFIELD || type == PIECE || type == EXTENSION) datalen += len; // Send length @@ -105,8 +121,11 @@ class Message if (type == REQUEST || type == CANCEL) dos.writeInt(length); + if (type == EXTENSION) + dos.writeByte((byte) piece & 0xff); + // Send actual data - if (type == BITFIELD || type == PIECE) + if (type == BITFIELD || type == PIECE || type == EXTENSION) dos.write(data, off, len); } @@ -135,6 +154,8 @@ class Message return "PIECE(" + piece + "," + begin + "," + length + ")"; case CANCEL: return "CANCEL(" + piece + "," + begin + "," + length + ")"; + case EXTENSION: + return "EXTENSION(" + piece + ',' + data.length + ')'; default: return "<UNKNOWN>"; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index 55b6dcb2ca7a7c7682c56fd0f9da98b650d9e38e..e747332d8391a553935e519ab0cc4622202c73f4 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -59,6 +59,11 @@ public class Peer implements Comparable private long uploaded_old[] = {-1,-1,-1}; private long downloaded_old[] = {-1,-1,-1}; + // bytes per bt spec: 0011223344556677 + static final long OPTION_EXTENSION = 0x0000000000100000l; + static final long OPTION_FAST = 0x0000000000000004l; + private long options; + /** * Creates a disconnected peer given a PeerID, your own id and the * relevant MetaInfo. @@ -285,9 +290,8 @@ public class Peer implements Comparable // Handshake write - header dout.write(19); dout.write("BitTorrent protocol".getBytes("UTF-8")); - // Handshake write - zeros - byte[] zeros = new byte[8]; - dout.write(zeros); + // Handshake write - options + dout.writeLong(OPTION_EXTENSION); // Handshake write - metainfo hash byte[] shared_hash = metainfo.getInfoHash(); dout.write(shared_hash); @@ -312,8 +316,8 @@ public class Peer implements Comparable + "'Bittorrent protocol', got '" + bittorrentProtocol + "'"); - // Handshake read - zeros - din.readFully(zeros); + // Handshake read - options + options = din.readLong(); // Handshake read - metainfo hash bs = new byte[20]; @@ -325,6 +329,15 @@ public class Peer implements Comparable din.readFully(bs); if (_log.shouldLog(Log.DEBUG)) _log.debug("Read the remote side's hash and peerID fully from " + toString()); + +// if ((options & OPTION_EXTENSION) != 0) { + if (options != 0) { + // send them something + if (_log.shouldLog(Log.DEBUG)) + //_log.debug("Peer supports extension message, what should we say? " + toString()); + _log.debug("Peer supports options 0x" + Long.toString(options, 16) + ", what should we say? " + toString()); + } + return bs; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java index 9acfb60e3c9edfd7d28e204e403a839ca4979135..d0abe06efdac40bfff9b047f5cdc03426d249339 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java @@ -171,6 +171,13 @@ class PeerConnectionIn implements Runnable if (_log.shouldLog(Log.DEBUG)) _log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName()); break; + case 20: // Extension message + int id = din.readUnsignedByte(); + byte[] payload = new byte[i-2]; + din.readFully(payload); + ps.extensionMessage(id, payload); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Received extension message from " + peer + " on " + peer.metainfo.getName()); default: byte[] bs = new byte[i-1]; din.readFully(bs); diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java index 81525a1ea82d2d7405f7ca60b824889a0fa0e6d2..8ab0d555944965bddc9b544108ada0446c54d63f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java @@ -430,6 +430,33 @@ class PeerConnectionOut implements Runnable return total; } + /** @since 0.8.2 */ + void sendPiece(int piece, int begin, int length, DataLoader loader) + { + boolean sendNow = false; + // are there any cases where we should? + + if (sendNow) { + // queue the real thing + byte[] bytes = loader.loadData(piece, begin, length); + if (bytes != null) + sendPiece(piece, begin, length, bytes); + return; + } + + // queue a fake message... set everything up, + // except save the PeerState instead of the bytes. + Message m = new Message(); + m.type = Message.PIECE; + m.piece = piece; + m.begin = begin; + m.length = length; + m.dataLoader = loader; + m.off = 0; + m.len = length; + addMessage(m); + } + void sendPiece(int piece, int begin, int length, byte[] bytes) { Message m = new Message(); @@ -488,4 +515,16 @@ class PeerConnectionOut implements Runnable } } } + + /** @since 0.8.2 */ + void sendExtension(int id, byte[] bytes) { + Message m = new Message(); + m.type = Message.EXTENSION; + m.piece = id; + m.data = bytes; + m.begin = 0; + m.length = bytes.length; + addMessage(m); + + } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java index 4d34736a89463f1dca89b5bf27a779a989275c06..8ba5c89696f2581022274f98dd8076225a9f9213 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerState.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerState.java @@ -20,14 +20,20 @@ package org.klomp.snark; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import net.i2p.I2PAppContext; import net.i2p.util.Log; -class PeerState +import org.klomp.snark.bencode.BDecoder; +import org.klomp.snark.bencode.BEValue; + +class PeerState implements DataLoader { private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class); final Peer peer; @@ -201,13 +207,28 @@ class PeerState return; } + if (_log.shouldLog(Log.INFO)) + _log.info("Queueing (" + piece + ", " + begin + ", " + + length + ")" + " to " + peer); + + // don't load the data into mem now, let PeerConnectionOut do it + out.sendPiece(piece, begin, length, this); + } + + /** + * This is the callback that PeerConnectionOut calls + * + * @return bytes or null for errors + * @since 0.8.2 + */ + public byte[] loadData(int piece, int begin, int length) { byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length); if (pieceBytes == null) { // XXX - Protocol error-> diconnect? if (_log.shouldLog(Log.WARN)) _log.warn("Got request for unknown piece: " + piece); - return; + return null; } // More sanity checks @@ -219,13 +240,13 @@ class PeerState + ", " + begin + ", " + length + "' message from " + peer); - return; + return null; } if (_log.shouldLog(Log.INFO)) _log.info("Sending (" + piece + ", " + begin + ", " + length + ")" + " to " + peer); - out.sendPiece(piece, begin, length, pieceBytes); + return pieceBytes; } /** @@ -413,6 +434,24 @@ class PeerState out.cancelRequest(piece, begin, length); } + /** @since 0.8.2 */ + void extensionMessage(int id, byte[] bs) + { + if (id == 0) { + InputStream is = new ByteArrayInputStream(bs); + try { + BDecoder dec = new BDecoder(is); + BEValue bev = dec.bdecodeMap(); + Map map = bev.getMap(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got extension handshake message " + bev.toString()); + } catch (Exception e) {} + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Got extended message type: " + id + " length: " + bs.length); + } + } + void unknownMessage(int type, byte[] bs) { if (_log.shouldLog(Log.WARN)) diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 4ebdbac26d82730cdcd948c79b39ceb5addc87f3..b9f06c8778dbcd7158b2169d155cd4d54138f172 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -242,6 +242,13 @@ public class I2PSnarkServlet extends Default { List snarks = getSortedSnarks(req); String uri = req.getRequestURI(); + boolean isForm = _manager.util().connected() || !snarks.isEmpty(); + if (isForm) { + out.write("<form action=\""); + out.write(uri); + out.write("\" method=\"POST\">\n"); + out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n"); + } out.write(TABLE_HEADER); out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/status.png\""); out.write(" title=\""); @@ -301,25 +308,17 @@ public class I2PSnarkServlet extends Default { out.write("</th>\n"); out.write("<th align=\"center\">"); if (_manager.util().connected()) { - out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce + - "\" title=\""); - out.write(_("Stop all torrents and the I2P tunnel")); - out.write("\">"); - out.write("<img src=\"/themes/snark/ubergine/images/stop_all.png\" title=\""); + out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\""); out.write(_("Stop all torrents and the I2P tunnel")); - out.write("\" alt=\""); + out.write("\" src=\"/themes/snark/ubergine/images/stop_all.png\" alt=\""); out.write(_("Stop All")); out.write("\">"); - out.write("</a>"); } else if (!snarks.isEmpty()) { - out.write("<a href=\"" + uri + "?action=StartAll&nonce=" + _nonce + - "\" title=\""); + out.write("<input type=\"image\" name=\"action\" value=\"StartAll\" title=\""); out.write(_("Start all torrents and the I2P tunnel")); + out.write("\" src=\"/themes/snark/ubergine/images/start_all.png\" alt=\""); + out.write(_("Start All")); out.write("\">"); - out.write("<img src=\"/themes/snark/ubergine/images/start_all.png\" title=\""); - out.write(_("Start all torrents and the I2P tunnel")); - out.write("\" alt=\"Start All\">"); - out.write("</a>"); } else { out.write(" "); } @@ -357,6 +356,8 @@ public class I2PSnarkServlet extends Default { } out.write("</table>"); + if (isForm) + out.write("</form>\n"); } /** @@ -366,7 +367,11 @@ public class I2PSnarkServlet extends Default { String action = req.getParameter("action"); if (action == null) { // noop - } else if ("Add".equals(action)) { + return; + } + if (!"POST".equals(req.getMethod())) + return; + if ("Add".equals(action)) { String newFile = req.getParameter("newFile"); String newURL = req.getParameter("newURL"); // NOTE - newFile currently disabled in HTML form - see below @@ -410,8 +415,8 @@ public class I2PSnarkServlet extends Default { } else { // no file or URL specified } - } else if ("Stop".equals(action)) { - String torrent = req.getParameter("torrent"); + } else if (action.startsWith("Stop_")) { + String torrent = action.substring(5); if (torrent != null) { byte infoHash[] = Base64.decode(torrent); if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1 @@ -425,8 +430,8 @@ public class I2PSnarkServlet extends Default { } } } - } else if ("Start".equals(action)) { - String torrent = req.getParameter("torrent"); + } else if (action.startsWith("Start_")) { + String torrent = action.substring(6); if (torrent != null) { byte infoHash[] = Base64.decode(torrent); if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1 @@ -441,8 +446,8 @@ public class I2PSnarkServlet extends Default { } } } - } else if ("Remove".equals(action)) { - String torrent = req.getParameter("torrent"); + } else if (action.startsWith("Remove_")) { + String torrent = action.substring(7); if (torrent != null) { byte infoHash[] = Base64.decode(torrent); if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1 @@ -461,8 +466,8 @@ public class I2PSnarkServlet extends Default { } } } - } else if ("Delete".equals(action)) { - String torrent = req.getParameter("torrent"); + } else if (action.startsWith("Delete_")) { + String torrent = action.substring(7); if (torrent != null) { byte infoHash[] = Base64.decode(torrent); if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1 @@ -618,10 +623,10 @@ public class I2PSnarkServlet extends Default { if (r.startsWith(skip)) r = r.substring(skip.length()); String llc = l.toLowerCase(); - if (llc.startsWith("the ") || llc.startsWith("the.")) + if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_")) l = l.substring(4); String rlc = r.toLowerCase(); - if (rlc.startsWith("the ") || rlc.startsWith("the.")) + if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_")) r = r.substring(4); return collator.compare(l, r); } @@ -828,62 +833,55 @@ public class I2PSnarkServlet extends Default { out.write("</td>\n\t"); out.write("<td align=\"center\" class=\"snarkTorrentAction " + rowClass + "\">"); String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash()); + String b64 = Base64.encode(snark.meta.getInfoHash()); if (showPeers) parameters = parameters + "&p=1"; if (isRunning) { - out.write("<a href=\"" + uri + "?action=Stop" + parameters - + "\" title=\""); - out.write(_("Stop the torrent")); - out.write("\">"); - out.write("<img src=\"/themes/snark/ubergine/images/stop.png\" title=\""); + out.write("<input type=\"image\" name=\"action\" value=\"Stop_"); + out.write(b64); + out.write("\" title=\""); out.write(_("Stop the torrent")); - out.write("\" alt=\""); + out.write("\" src=\"/themes/snark/ubergine/images/stop.png\" alt=\""); out.write(_("Stop")); out.write("\">"); - out.write("</a>"); } else { if (isValid) { - out.write("<a href=\"" + uri + "?action=Start" + parameters - + "\" title=\""); + out.write("<input type=\"image\" name=\"action\" value=\"Start_"); + out.write(b64); + out.write("\" title=\""); out.write(_("Start the torrent")); - out.write("\">"); - out.write("<img src=\"/themes/snark/ubergine/images/start.png\" title=\""); - out.write(_("Start the torrent")); - out.write("\" alt=\""); + out.write("\" src=\"/themes/snark/ubergine/images/start.png\" alt=\""); out.write(_("Start")); out.write("\">"); - out.write("</a>"); } - out.write("<a href=\"" + uri + "?action=Remove" + parameters - + "\" title=\""); + + out.write("<input type=\"image\" name=\"action\" value=\"Remove_"); + out.write(b64); + out.write("\" title=\""); out.write(_("Remove the torrent from the active list, deleting the .torrent file")); out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. // Single quotes in translate strings with parameters must be doubled. // Then the remaining single quite must be escaped out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename)); - out.write("')) { return false; }\">"); - out.write("<img src=\"/themes/snark/ubergine/images/remove.png\" title=\""); - out.write(_("Remove the torrent from the active list, deleting the .torrent file")); - out.write("\" alt=\""); + out.write("')) { return false; }\""); + out.write(" src=\"/themes/snark/ubergine/images/remove.png\" alt=\""); out.write(_("Remove")); out.write("\">"); - out.write("</a>"); - out.write("<a href=\"" + uri + "?action=Delete" + parameters - + "\" title=\""); + + out.write("<input type=\"image\" name=\"action\" value=\"Delete_"); + out.write(b64); + out.write("\" title=\""); out.write(_("Delete the .torrent file and the associated data file(s)")); out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. // Single quotes in translate strings with parameters must be doubled. // Then the remaining single quite must be escaped out.write(_("Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?", fullFilename)); - out.write("')) { return false; }\">"); - out.write("<img src=\"/themes/snark/ubergine/images/delete.png\" title=\""); - out.write(_("Delete the .torrent file and the associated data file(s)")); - out.write("\" alt=\""); + out.write("')) { return false; }\""); + out.write(" src=\"/themes/snark/ubergine/images/delete.png\" alt=\""); out.write(_("Delete")); out.write("\">"); - out.write("</a>"); } out.write("</td>\n</tr>\n"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigPeerHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigPeerHandler.java index a0a3df8c9d4b15f40020cf86b7d612b3651656ce..5af095d2c49489eec5101cf5e977eac81b5c9aa5 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigPeerHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigPeerHandler.java @@ -36,7 +36,7 @@ public class ConfigPeerHandler extends FormHandler { return; } addFormError(_("Invalid peer")); - } else if (_action.equals(_("Adjust Profile Bonuses"))) { + } else if (_action.equals(_("Adjust peer bonuses"))) { Hash h = getHash(); if (h != null) { PeerProfile prof = _context.profileOrganizer().getProfile(h); @@ -59,6 +59,8 @@ public class ConfigPeerHandler extends FormHandler { addFormError(_("Invalid peer")); } else if (_action.startsWith("Check")) { addFormError(_("Unsupported")); + } else { + addFormError("Unknown action \"" + _action + '"'); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java index f064c5abcd59f32188f4b18fc65bd778a518d4fd..88abf360db641b6baf3c2e4cff634895d3667594 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java @@ -20,26 +20,23 @@ public class FormHandler { protected Log _log; private String _nonce; protected String _action; + protected String _method; protected String _passphrase; - private List<String> _errors; - private List<String> _notices; + private final List<String> _errors; + private final List<String> _notices; private boolean _processed; private boolean _valid; public FormHandler() { _errors = new ArrayList(); _notices = new ArrayList(); - _action = null; - _processed = false; _valid = true; - _nonce = null; - _passphrase = null; } /** * Configure this bean to query a particular router context * - * @param contextId begging few characters of the routerHash, or null to pick + * @param contextId beginning few characters of the routerHash, or null to pick * the first one we come across. */ public void setContextId(String contextId) { @@ -54,6 +51,14 @@ public class FormHandler { public void setNonce(String val) { _nonce = val; } public void setAction(String val) { _action = val; } public void setPassphrase(String val) { _passphrase = val; } + + /** + * Call this to prevent changes using GET + * + * @param the request method + * @since 0.8.2 + */ + public void storeMethod(String val) { _method = val; } /** * Override this to perform the final processing (in turn, adding formNotice @@ -145,6 +150,12 @@ public class FormHandler { _valid = false; return; } + // To prevent actions with GET, jsps must call storeMethod() + if (_method != null && !"POST".equals(_method)) { + addFormError("Invalid form submission, requires POST not " + _method); + _valid = false; + return; + } String sharedNonce = System.getProperty("router.consoleNonce"); if ( (sharedNonce != null) && (sharedNonce.equals(_nonce) ) ) { @@ -211,4 +222,8 @@ public class FormHandler { return Messages.getString(s, o, _context); } + /** two params @since 0.8.2 */ + public String _(String s, Object o, Object o2) { + return Messages.getString(s, o, o2, _context); + } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java index 31b67dc81332ba73f054a2846e1ed00921c9c277..b3ce2fa83237dce542606c94fc40746a6bbb15ac 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java @@ -1,6 +1,7 @@ package net.i2p.router.web; import java.io.IOException; +import java.io.Writer; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -9,7 +10,8 @@ import java.util.TreeSet; import net.i2p.data.DataHelper; import net.i2p.stat.Rate; -public class GraphHelper extends HelperBase { +public class GraphHelper extends FormHandler { + protected Writer _out; private int _periodCount; private boolean _showEvents; private int _width; @@ -29,9 +31,6 @@ public class GraphHelper extends HelperBase { static final int MAX_Y = 1024; private static final int MIN_REFRESH = 15; - public GraphHelper() { - } - /** set the defaults after we have a context */ @Override public void setContextId(String contextId) { @@ -43,6 +42,12 @@ public class GraphHelper extends HelperBase { _showEvents = Boolean.valueOf(_context.getProperty(PROP_EVENTS)).booleanValue(); } + /** + * This was a HelperBase but now it's a FormHandler + * @since 0.8.2 + */ + public void storeWriter(Writer out) { _out = out; } + public void setPeriodCount(String str) { try { _periodCount = Integer.parseInt(str); } catch (NumberFormatException nfe) {} } @@ -125,10 +130,15 @@ public class GraphHelper extends HelperBase { } public String getForm() { - saveSettings(); + String prev = System.getProperty("net.i2p.router.web.GraphHelper.nonce"); + if (prev != null) System.setProperty("net.i2p.router.web.GraphHelper.noncePrev", prev); + String nonce = "" + _context.random().nextLong(); + System.setProperty("net.i2p.router.web.GraphHelper.nonce", nonce); try { _out.write("<br><h3>" + _("Configure Graph Display") + " [<a href=\"configstats\">" + _("Select Stats") + "</a>]</h3>"); - _out.write("<form action=\"graphs\" method=\"POST\">"); + _out.write("<form action=\"graphs\" method=\"POST\">\n" + + "<input type=\"hidden\" name=\"action\" value=\"foo\">\n" + + "<input type=\"hidden\" name=\"nonce\" value=\"" + nonce + "\" >\n"); _out.write(_("Periods") + ": <input size=\"3\" type=\"text\" name=\"periodCount\" value=\"" + _periodCount + "\"><br>\n"); _out.write(_("Plot averages") + ": <input type=\"radio\" class=\"optbox\" name=\"showEvents\" value=\"false\" " + (_showEvents ? "" : "checked=\"true\" ") + "> "); _out.write(_("or")+ " " +_("plot events") + ": <input type=\"radio\" class=\"optbox\" name=\"showEvents\" value=\"true\" "+ (_showEvents ? "checked=\"true\" " : "") + "><br>\n"); @@ -143,6 +153,15 @@ public class GraphHelper extends HelperBase { return ""; } + /** + * This was a HelperBase but now it's a FormHandler + * @since 0.8.2 + */ + @Override + protected void processForm() { + saveSettings(); + } + /** * Silently save settings if changed, no indication of success or failure * @since 0.7.10 @@ -159,6 +178,7 @@ public class GraphHelper extends HelperBase { _context.router().setConfigSetting(PROP_REFRESH, "" + _refreshDelaySeconds); _context.router().setConfigSetting(PROP_EVENTS, "" + _showEvents); _context.router().saveConfig(); + addFormNotice(_("Graph settings saved")); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java index 802e059adf280f3daf6e9a7aa5f07ccc5dbd114e..8ceec1098b1a2acf44debf7b8cdfb94f66c51583 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/HelperBase.java @@ -28,7 +28,13 @@ public abstract class HelperBase { /** might be useful in the jsp's */ //public RouterContext getContext() { return _context; } - public void setWriter(Writer out) { _out = out; } + + /** + * Renamed from setWriter, we realy don't want setFoo(non-String) + * Prevent jsp.error.beans.property.conversion 500 error for ?writer=foo + * @since 0.8.2 + */ + public void storeWriter(Writer out) { _out = out; } /** translate a string */ public String _(String s) { diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp index 2cb8a3aed8cde690f8b6795249ddf7a33462c736..74f1bf4656eacd21986d6067023659e79b6f8578 100644 --- a/apps/routerconsole/jsp/config.jsp +++ b/apps/routerconsole/jsp/config.jsp @@ -16,6 +16,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigNetHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/configadvanced.jsp b/apps/routerconsole/jsp/configadvanced.jsp index 566d0c1bad29ffef47acd2b3c723b110fb3fadb7..0381aef9ab62c0f8ff22a55da27a99999e47411b 100644 --- a/apps/routerconsole/jsp/configadvanced.jsp +++ b/apps/routerconsole/jsp/configadvanced.jsp @@ -18,6 +18,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigAdvancedHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp index 49430e2065b1f8697dc1b83591aa1b617cdfc7bd..4f633c8949a3438e8c6e2f21ba90709d7afcdba9 100644 --- a/apps/routerconsole/jsp/configclients.jsp +++ b/apps/routerconsole/jsp/configclients.jsp @@ -21,6 +21,7 @@ button span.hide{ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigClientsHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:setProperty name="formhandler" property="action" value="<%=request.getParameter("action")%>" /> <jsp:setProperty name="formhandler" property="nonce" value="<%=request.getParameter("nonce")%>" /> diff --git a/apps/routerconsole/jsp/configkeyring.jsp b/apps/routerconsole/jsp/configkeyring.jsp index 84cd543845ba7e2d8458740ec2dd463c6e5c52a3..b260f843a4e60c8f7ee83379989fec1ec43c7d52 100644 --- a/apps/routerconsole/jsp/configkeyring.jsp +++ b/apps/routerconsole/jsp/configkeyring.jsp @@ -13,6 +13,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigKeyringHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/configlogging.jsp b/apps/routerconsole/jsp/configlogging.jsp index a90c362d73112b3a8b5e28eabb86b292c8d1e163..c87fd367db1845f070a5f759ef1ba36b5471746e 100644 --- a/apps/routerconsole/jsp/configlogging.jsp +++ b/apps/routerconsole/jsp/configlogging.jsp @@ -15,6 +15,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigLoggingHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/confignav.jsi b/apps/routerconsole/jsp/confignav.jsi index adbf46fc78482a1ef6ca2c0c0c00a998a79c3cd1..87d0ec5b8c3c87da77cbee480c21908a9c63d18a 100644 --- a/apps/routerconsole/jsp/confignav.jsi +++ b/apps/routerconsole/jsp/confignav.jsi @@ -5,7 +5,7 @@ %> <jsp:useBean class="net.i2p.router.web.ConfigNavHelper" id="navHelper" scope="request" /> <jsp:setProperty name="navHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> -<jsp:setProperty name="navHelper" property="writer" value="<%=out%>" /> +<% navHelper.storeWriter(out); %> <div class="confignav" id="confignav"> <center> <% diff --git a/apps/routerconsole/jsp/configpeer.jsp b/apps/routerconsole/jsp/configpeer.jsp index 0dfb550851b9a52c74fe057561a2534df622bb8a..188ca56d4d9b68eb4f131a54e59cc6c3b263092f 100644 --- a/apps/routerconsole/jsp/configpeer.jsp +++ b/apps/routerconsole/jsp/configpeer.jsp @@ -13,6 +13,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigPeerHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> @@ -27,7 +28,7 @@ peer = net.i2p.data.DataHelper.stripHTML(request.getParameter("peer")); // XSS %> <div class="configure"> - <form action="" method="POST"> + <form action="configpeer" method="POST"> <% String prev = System.getProperty("net.i2p.router.web.ConfigPeerHandler.nonce"); if (prev != null) System.setProperty("net.i2p.router.web.ConfigPeerHandler.noncePrev", prev); System.setProperty("net.i2p.router.web.ConfigPeerHandler.nonce", new java.util.Random().nextLong()+""); %> @@ -64,7 +65,7 @@ <a name="shitlist"> </a><h2><%=intl._("Banned Peers")%></h2> <jsp:useBean class="net.i2p.router.web.ProfilesHelper" id="profilesHelper" scope="request" /> <jsp:setProperty name="profilesHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="profilesHelper" property="writer" value="<%=out%>" /> + <% profilesHelper.storeWriter(out); %> <jsp:getProperty name="profilesHelper" property="shitlistSummary" /> <div class="wideload"><h2><%=intl._("Banned IPs")%></h2> <jsp:getProperty name="peerhelper" property="blocklistSummary" /> diff --git a/apps/routerconsole/jsp/configservice.jsp b/apps/routerconsole/jsp/configservice.jsp index d71c8ee39979541ea45ae7a85c693809f0f22655..f87479a92feca0339b59a678b3c5d8f18f178ab4 100644 --- a/apps/routerconsole/jsp/configservice.jsp +++ b/apps/routerconsole/jsp/configservice.jsp @@ -13,6 +13,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigServiceHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/configstats.jsp b/apps/routerconsole/jsp/configstats.jsp index 1f3dd6354d8e98012db0df5b4c7962df319a6a95..fde10837db0c98212924fa8b488aa4e315367e37 100644 --- a/apps/routerconsole/jsp/configstats.jsp +++ b/apps/routerconsole/jsp/configstats.jsp @@ -58,8 +58,9 @@ function toggleAll(category) <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigStatsHandler" id="formhandler" scope="request" /> - <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> + <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> <jsp:useBean class="net.i2p.router.web.ConfigStatsHelper" id="statshelper" scope="request" /> diff --git a/apps/routerconsole/jsp/configtunnels.jsp b/apps/routerconsole/jsp/configtunnels.jsp index 9a6a11a3781fc370cf1ac16d9d99099102bfdba1..1c1393280a549b76bc9f4686934230256cf358f9 100644 --- a/apps/routerconsole/jsp/configtunnels.jsp +++ b/apps/routerconsole/jsp/configtunnels.jsp @@ -15,6 +15,7 @@ <div class="main" id="main"> <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigTunnelsHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:setProperty name="formhandler" property="shouldsave" value="<%=request.getParameter("shouldsave")%>" /> <jsp:setProperty name="formhandler" property="action" value="<%=request.getParameter("action")%>" /> diff --git a/apps/routerconsole/jsp/configui.jsp b/apps/routerconsole/jsp/configui.jsp index f2a0f5b0fc77452f054acee0e4b5b02a2b874b79..9f3d32e4fada8cc7f0cb104b7895df1fdb02c14d 100644 --- a/apps/routerconsole/jsp/configui.jsp +++ b/apps/routerconsole/jsp/configui.jsp @@ -18,6 +18,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigUIHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/configupdate.jsp b/apps/routerconsole/jsp/configupdate.jsp index 21ae4fedff26d5dc04af2d0a56d3132bcb5551fe..1930614218374158f8d7a011da36a6c55c363a31 100644 --- a/apps/routerconsole/jsp/configupdate.jsp +++ b/apps/routerconsole/jsp/configupdate.jsp @@ -13,6 +13,7 @@ <%@include file="confignav.jsi" %> <jsp:useBean class="net.i2p.router.web.ConfigUpdateHandler" id="formhandler" scope="request" /> + <% formhandler.storeMethod(request.getMethod()); %> <jsp:setProperty name="formhandler" property="*" /> <jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> <jsp:getProperty name="formhandler" property="allMessages" /> diff --git a/apps/routerconsole/jsp/dumpprofile.jsp b/apps/routerconsole/jsp/dumpprofile.jsp index f13a4bcd77e976a212fd273daa4b84d8202950ea..dfdcc0b6e52ffb3687165dce6ff2af104797b8b1 100644 --- a/apps/routerconsole/jsp/dumpprofile.jsp +++ b/apps/routerconsole/jsp/dumpprofile.jsp @@ -1,5 +1,5 @@ <%@page contentType="text/plain" %><jsp:useBean id="helper" class="net.i2p.router.web.StatHelper" /><jsp:setProperty name="helper" property="peer" value="<%=request.getParameter("peer")%>" -/><jsp:setProperty name="helper" property="writer" value="<%=out%>" -/><jsp:getProperty name="helper" property="profile" /> \ No newline at end of file +/><% helper.storeWriter(out); +%><jsp:getProperty name="helper" property="profile" /> diff --git a/apps/routerconsole/jsp/graphs.jsp b/apps/routerconsole/jsp/graphs.jsp index e3d5c01154f33b7c8c98cd14cb2b5778d911c36f..bbda259441a9564c05abfcd7bc3a193e23c389fc 100644 --- a/apps/routerconsole/jsp/graphs.jsp +++ b/apps/routerconsole/jsp/graphs.jsp @@ -13,9 +13,12 @@ <div class="graphspanel"> <div class="widepanel"> <jsp:useBean class="net.i2p.router.web.GraphHelper" id="graphHelper" scope="request" /> + <% graphHelper.storeMethod(request.getMethod()); %> <jsp:setProperty name="graphHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> +<% /* GraphHelper sets the defaults in setContextId, so setting the properties must be after the context */ %> <jsp:setProperty name="graphHelper" property="*" /> - <jsp:setProperty name="graphHelper" property="writer" value="<%=out%>" /> + <% graphHelper.storeWriter(out); %> + <jsp:getProperty name="graphHelper" property="allMessages" /> <jsp:getProperty name="graphHelper" property="images" /> <jsp:getProperty name="graphHelper" property="form" /> </div></div></div></body></html> diff --git a/apps/routerconsole/jsp/i2psnark/index.html b/apps/routerconsole/jsp/i2psnark/index.html index a1500fad320bcf8b33cec983ad1eeb4feb6892d9..681b946562776f185d74ae4946525aacd148ec96 100644 --- a/apps/routerconsole/jsp/i2psnark/index.html +++ b/apps/routerconsole/jsp/i2psnark/index.html @@ -3,6 +3,6 @@ <meta http-equiv="pragma" content="no-cache" /> </head> <body> -The I2PSnark Anonymous BitTorrent Client is not running. Please visit the <a href="/configclients.jsp">config clients page</a> +The I2PSnark Anonymous BitTorrent Client is not running. Please visit the <a href="/configclients#webapp">config clients page</a> to start it. </body></html> diff --git a/apps/routerconsole/jsp/i2ptunnel/index.jsp b/apps/routerconsole/jsp/i2ptunnel/index.jsp index f3cceda0d37cc6baf009e708846baad36df27e9f..db3648aa07e410fe3ed9fbfb80c45e739ee819b3 100644 --- a/apps/routerconsole/jsp/i2ptunnel/index.jsp +++ b/apps/routerconsole/jsp/i2ptunnel/index.jsp @@ -3,5 +3,5 @@ <meta http-equiv="pragma" content="no-cache" /> </head> <body> -The I2P Tunnel Manager is not currently running. Please visit the <a href="/configclients.jsp">Client Configuration</a> page to start it. +The I2P Tunnel Manager is not currently running. Please visit the <a href="/configclients#webapp">Client Configuration</a> page to start it. </body></html> diff --git a/apps/routerconsole/jsp/jobs.jsp b/apps/routerconsole/jsp/jobs.jsp index ef84c8f8c56daa82b66201402f88da2112302ee2..53916dcd44ed0d2ff65f9eb389b6edb66487974e 100644 --- a/apps/routerconsole/jsp/jobs.jsp +++ b/apps/routerconsole/jsp/jobs.jsp @@ -10,6 +10,6 @@ <div class="main" id="main"> <jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" /> <jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" /> + <% jobQueueHelper.storeWriter(out); %> <jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" /> <hr></div></body></html> diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp index caefed97a9899d84e8b506df8ecfe68fc9a60026..75f052e9b22c3136aa20e926e2605c09f3242fa5 100644 --- a/apps/routerconsole/jsp/logs.jsp +++ b/apps/routerconsole/jsp/logs.jsp @@ -10,7 +10,8 @@ <h1><%=intl._("I2P Router Logs")%></h1> <div class="main" id="main"> <div class="joblog"><h3><%=intl._("I2P Version & Running Environment")%></h3><a name="version"> </a> -<p><%=intl._("Please report bugs on <a href=\"http://trac.i2p2.i2p/newticket\">trac.i2p2.i2p</a>.")%> +<p><%=intl._("Please report bugs on <a href=\"http://trac.i2p2.i2p/newticket\">trac.i2p2.i2p</a> or <a href=\"http://trac.i2p2.de/newticket\">trac.i2p2.de</a>.")%> +<%=intl._("You may use the username \"guest\" and password \"guest\" if you do not wish to register.")%> <p><i><%=intl._("Please include this information in bug reports")%>:</i> <p> <b>I2P version:</b> <jsp:getProperty name="helper" property="version" /><br> diff --git a/apps/routerconsole/jsp/netdb.jsp b/apps/routerconsole/jsp/netdb.jsp index 9cbac25e74cc0f379819a3b205ab27701aac293c..2862e9e0e727df2c9502352bf92d2db603edb2ea 100644 --- a/apps/routerconsole/jsp/netdb.jsp +++ b/apps/routerconsole/jsp/netdb.jsp @@ -12,7 +12,7 @@ <div class="wideload"> <jsp:useBean class="net.i2p.router.web.NetDbHelper" id="netdbHelper" scope="request" /> <jsp:setProperty name="netdbHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="netdbHelper" property="writer" value="<%=out%>" /> + <% netdbHelper.storeWriter(out); %> <jsp:setProperty name="netdbHelper" property="full" value="<%=request.getParameter("f")%>" /> <jsp:setProperty name="netdbHelper" property="router" value="<%=request.getParameter("r")%>" /> <jsp:setProperty name="netdbHelper" property="lease" value="<%=request.getParameter("l")%>" /> diff --git a/apps/routerconsole/jsp/oldconsole.jsp b/apps/routerconsole/jsp/oldconsole.jsp index 701d70f7d3f73d7344ef631dd7da5c4df73ae870..3612169e41f1ff9be503ca06e273206d7db3980b 100644 --- a/apps/routerconsole/jsp/oldconsole.jsp +++ b/apps/routerconsole/jsp/oldconsole.jsp @@ -12,7 +12,7 @@ <%@include file="summary.jsi" %> <jsp:useBean class="net.i2p.router.web.OldConsoleHelper" id="conhelper" scope="request" /> <jsp:setProperty name="conhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> -<jsp:setProperty name="conhelper" property="writer" value="<%=out%>" /> +<% conhelper.storeWriter(out); %> <h1>I2P Router » Old Console</h1> <div class="main" id="main"> <jsp:getProperty name="conhelper" property="console" /> diff --git a/apps/routerconsole/jsp/peers.jsp b/apps/routerconsole/jsp/peers.jsp index c4b72b9315b9c09845c99c93a61bdf6379ad81c8..4b48c5957d56b214c2ba61e574e5903f22241736 100644 --- a/apps/routerconsole/jsp/peers.jsp +++ b/apps/routerconsole/jsp/peers.jsp @@ -11,7 +11,7 @@ <div class="main" id="main"><div class="wideload"> <jsp:useBean class="net.i2p.router.web.PeerHelper" id="peerHelper" scope="request" /> <jsp:setProperty name="peerHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="peerHelper" property="writer" value="<%=out%>" /> + <% peerHelper.storeWriter(out); %> <jsp:setProperty name="peerHelper" property="urlBase" value="peers.jsp" /> <jsp:setProperty name="peerHelper" property="sort" value="<%=request.getParameter("sort") != null ? request.getParameter("sort") : ""%>" /> <jsp:getProperty name="peerHelper" property="peerSummary" /> diff --git a/apps/routerconsole/jsp/profiles.jsp b/apps/routerconsole/jsp/profiles.jsp index 69100e349b45336038bab79d5fcfe956101afc44..98ec4882818f2942d11676d4867e2244f1964b94 100644 --- a/apps/routerconsole/jsp/profiles.jsp +++ b/apps/routerconsole/jsp/profiles.jsp @@ -10,7 +10,7 @@ <div class="main" id="main"><div class="wideload"> <jsp:useBean class="net.i2p.router.web.ProfilesHelper" id="profilesHelper" scope="request" /> <jsp:setProperty name="profilesHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="profilesHelper" property="writer" value="<%=out%>" /> + <% profilesHelper.storeWriter(out); %> <jsp:setProperty name="profilesHelper" property="full" value="<%=request.getParameter("f")%>" /> <jsp:getProperty name="profilesHelper" property="profileSummary" /> <a name="shitlist"> </a><h2><%=intl._("Banned Peers")%></h2> diff --git a/apps/routerconsole/jsp/stats.jsp b/apps/routerconsole/jsp/stats.jsp index 61466d98617fb8b4a1b05991938148824b691c77..d2787dea968f2091ff1fdc4f5f57be5c0c20a959 100644 --- a/apps/routerconsole/jsp/stats.jsp +++ b/apps/routerconsole/jsp/stats.jsp @@ -9,7 +9,7 @@ <%@include file="summary.jsi" %> <jsp:useBean class="net.i2p.router.web.OldConsoleHelper" id="oldhelper" scope="request" /> <jsp:setProperty name="oldhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> -<jsp:setProperty name="oldhelper" property="writer" value="<%=out%>" /> +<% oldhelper.storeWriter(out); %> <jsp:setProperty name="oldhelper" property="full" value="<%=request.getParameter("f")%>" /> <h1><%=intl._("I2P Router Statistics")%></h1> <div class="main" id="main"> diff --git a/apps/routerconsole/jsp/summarynoframe.jsi b/apps/routerconsole/jsp/summarynoframe.jsi index 33a029e7657d5bd04a8520b7d027bc8dec89b5e7..355f4bc0b24d08ed9971810dbcd42819fd8a7575 100644 --- a/apps/routerconsole/jsp/summarynoframe.jsi +++ b/apps/routerconsole/jsp/summarynoframe.jsi @@ -11,7 +11,7 @@ <jsp:setProperty name="helper" property="updateNonce" value="<%=request.getParameter("updateNonce")%>" /> <jsp:setProperty name="helper" property="consoleNonce" value="<%=request.getParameter("consoleNonce")%>" /> <jsp:setProperty name="helper" property="requestURI" value="<%=request.getRequestURI()%>" /> -<jsp:setProperty name="helper" property="writer" value="<%=out%>" /> +<% helper.storeWriter(out); %> <% /* * The following is required for the reseed button to work, although we probably diff --git a/apps/routerconsole/jsp/susidns/index.jsp b/apps/routerconsole/jsp/susidns/index.jsp index 5fb267a83cae77f8d4d0677cdda69b35a827df6e..2964615e3153b78ca2dd880c574c22356a81f2b9 100644 --- a/apps/routerconsole/jsp/susidns/index.jsp +++ b/apps/routerconsole/jsp/susidns/index.jsp @@ -3,6 +3,6 @@ <meta http-equiv="pragma" content="no-cache" /> </head> <body> -SusiDNS is not running. Go to <a href="/configclients.jsp">the config clients page</a> +SusiDNS is not running. Go to <a href="/configclients#webapp">the config clients page</a> to start it. </body></html> diff --git a/apps/routerconsole/jsp/susimail/susimail b/apps/routerconsole/jsp/susimail/susimail index 9852921214f68858df775d54a37f8727a5d2eb79..32e9d3787b118f6051fa0ff0adb672c6fb47be0a 100644 --- a/apps/routerconsole/jsp/susimail/susimail +++ b/apps/routerconsole/jsp/susimail/susimail @@ -3,6 +3,6 @@ <meta http-equiv="pragma" content="no-cache" /> </head> <body> -SusiMail is not running. Go to <a href="/configclients.jsp">the config clients page</a> +SusiMail is not running. Go to <a href="/configclients#webapp">the config clients page</a> to start it. </body></html> diff --git a/apps/routerconsole/jsp/tunnels.jsp b/apps/routerconsole/jsp/tunnels.jsp index 63045519c574db9b8bf447c46184d98be5cb64de..e01b331f6bb2a2775b43f4d1b65579791c4665ca 100644 --- a/apps/routerconsole/jsp/tunnels.jsp +++ b/apps/routerconsole/jsp/tunnels.jsp @@ -10,6 +10,6 @@ <div class="main" id="main"> <jsp:useBean class="net.i2p.router.web.TunnelHelper" id="tunnelHelper" scope="request" /> <jsp:setProperty name="tunnelHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> - <jsp:setProperty name="tunnelHelper" property="writer" value="<%=out%>" /> + <% tunnelHelper.storeWriter(out); %> <jsp:getProperty name="tunnelHelper" property="tunnelSummary" /> </div></body></html> diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index e662ecc60a95174749196a406bf4636b2cfe53aa..20b1a0f0169f9aec06187a48f43799018ed749ce 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -98,6 +98,7 @@ public class Connection { _log = _context.logManager().getLog(Connection.class); _receiver = new ConnectionDataReceiver(_context, this); _inputStream = new MessageInputStream(_context); + // FIXME pass through a passive flush delay setting as the 4th arg _outputStream = new MessageOutputStream(_context, _receiver, (opts == null ? Packet.MAX_PAYLOAD_SIZE : opts.getMaxMessageSize())); _outboundPackets = new TreeMap(); _options = (opts != null ? opts : new ConnectionOptions()); diff --git a/build.xml b/build.xml index 0e749ce499ae1aff63cbb998d450c22041eb88bb..44011705d0a202d5adb04d7e3dca3287af708e34 100644 --- a/build.xml +++ b/build.xml @@ -245,7 +245,7 @@ <delete file="debian/substvars"/> </target> <target name="distclean" depends="clean"> - <delete includeemptydirs="true" removeNotFollowedSymlinks="true" failonerror="false" > + <delete includeemptydirs="true" failonerror="false" > <fileset dir="debian/packages" followSymlinks="false" /> </delete> <delete dir="debian/repo" /> @@ -368,7 +368,7 @@ </copy> </target> - <target name="preppkg-base" depends="build, preplicenses, prepconsoleDocs"> + <target name="preppkg-base" depends="build, preplicenses, prepConsoleDocs, prepthemeupdates, prepCertificates"> <!-- if updater200 was run previously, it left *.pack files in pkg-temp --> <delete> <fileset dir="pkg-temp" includes="**/*.jar.pack **/*.war.pack" /> @@ -419,10 +419,6 @@ <copy file="installer/resources/start.ico" todir="pkg-temp/docs/" /> <copy file="installer/resources/console.ico" todir="pkg-temp/docs/" /> <copy file="installer/resources/uninstall.ico" todir="pkg-temp/docs/" /> - <mkdir dir="pkg-temp/docs/themes/" /> - <copy todir="pkg-temp/docs/themes/" > - <fileset dir="installer/resources/themes/" /> - </copy> <!-- Eepsite stuff here --> <mkdir dir="pkg-temp/eepsite" /> <mkdir dir="pkg-temp/eepsite/webapps" /> @@ -455,42 +451,17 @@ <copy file="installer/lib/launch4j/lib/JGoodies.Looks.LICENSE.txt" tofile="pkg-temp/licenses/LICENSE-JGoodies-Looks.txt" /> <copy file="installer/lib/launch4j/lib/XStream.LICENSE.txt" tofile="pkg-temp/licenses/LICENSE-XStream.txt" /> </target> + <target name="prepthemeupdates"> - <!-- Migrated all Snark content to its own dir. Need to ensure snark dir excluded from console theme choices!! --> - <!-- Snark's visible Assets --> - <copy todir="pkg-temp/docs/themes/snark/ubergine/" > - <fileset dir="installer/resources/themes/snark/ubergine/" /> - </copy> - <!-- No need to copy these individually, we're copying the whole dir below.. - <copy file="installer/resources/themes/console/images/favicon.ico" todir="pkg-temp/docs/themes/console/images/" /> - <copy file="installer/resources/themes/console/images/i2plogo.png" todir="pkg-temp/docs/themes/console/images/" /> - --> - <!-- Since the logo moved, we have to update the error pages --> - <copy todir="pkg-temp/docs/" > - <fileset dir="installer/resources/proxy" /> - </copy> - <!-- make a "classic" theme --> - <copy todir="pkg-temp/docs/themes/console/classic/" > - <fileset dir="installer/resources/themes/console/classic/" /> - </copy> - <!-- Add dark theme --> - <copy todir="pkg-temp/docs/themes/console/dark/" > - <fileset dir="installer/resources/themes/console/dark/" /> - </copy> - <!-- Add light theme --> - <copy todir="pkg-temp/docs/themes/console/light/" > - <fileset dir="installer/resources/themes/console/light/" /> + <copy todir="pkg-temp/docs/themes/" > + <fileset dir="installer/resources/themes/" /> </copy> - <!-- Add midnight theme --> - <copy todir="pkg-temp/docs/themes/console/midnight/" > - <fileset dir="installer/resources/themes/console/midnight/" /> - </copy> - <!-- Add shared images.. these are subject to flux and change! --> - <copy todir="pkg-temp/docs/themes/console/images/" > - <fileset dir="installer/resources/themes/console/images/" /> - </copy> - <copy todir="pkg-temp/docs/" > - <fileset dir="installer/resources/readme/" includes="readme*.html" /> + </target> + + <!-- SSL Certs --> + <target name="prepCertificates"> + <copy todir="pkg-temp/certificates/" > + <fileset dir="installer/resources/certificates/" /> </copy> </target> @@ -500,16 +471,23 @@ <tarfileset dir="pkg-temp" includes="**/*" prefix="i2p" /> </tar> </target> + <target name="deletepkg-temp"> <delete dir="pkg-temp" /> </target> - <target name="prepconsoleDocs" depends="prepgeoupdate"> + + <!-- readme and proxy error page files, GeoIP files, and flag icons --> + <target name="prepConsoleDocs" depends="prepConsoleDocUpdates, prepgeoupdate" /> + + <!-- readme and proxy error page files --> + <target name="prepConsoleDocUpdates"> <copy todir="pkg-temp/docs/" > <fileset dir="installer/resources/readme/" includes="readme*.html" /> - <fileset dir="installer/resources/proxy" /> + <fileset dir="installer/resources/proxy/" includes="*.ht" /> </copy> </target> - <target name="consoleDocs" depends="deletepkg-temp, prepconsoleDocs"> + + <target name="consoleDocs" depends="deletepkg-temp, prepConsoleDocs"> <zip destfile="docs.zip" basedir="pkg-temp" whenempty="fail" /> </target> @@ -560,7 +538,8 @@ <copy file="core/java/build/i2ptest.jar" todir="pkg-temp/lib" /> <zip destfile="i2pupdate.zip" basedir="pkg-temp" /> </target> - <target name="prepupdate" depends="build2, prepupdateSmall"> + + <target name="prepupdate" depends="build2, prepupdateSmall, prepConsoleDocUpdates, prepCertificates"> <copy file="build/BOB.jar" todir="pkg-temp/lib/" /> <copy file="build/sam.jar" todir="pkg-temp/lib/" /> <copy file="build/i2psnark.jar" todir="pkg-temp/lib" /> @@ -587,6 +566,7 @@ <copy file="installer/resources/news.xml" todir="pkg-temp/docs/" /> --> </target> + <target name="prepupdateSmall" depends="buildSmall, prepupdateRouter, prepthemeupdates"> <copy file="build/i2ptunnel.jar" todir="pkg-temp/lib/" /> <copy file="build/mstreaming.jar" todir="pkg-temp/lib/" /> @@ -601,10 +581,13 @@ <!-- decapitalized the file in 0.7.8 --> <copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" /> </target> + <target name="prepupdateRouter" depends="buildrouter, deletepkg-temp"> <copy file="build/i2p.jar" todir="pkg-temp/lib/" /> <copy file="build/router.jar" todir="pkg-temp/lib/" /> </target> + + <!-- GeoIP files and flag icons --> <target name="prepgeoupdate"> <copy file="installer/resources/geoip.txt" todir="pkg-temp/geoip/" /> <copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" /> @@ -612,6 +595,7 @@ <fileset dir="installer/resources/icons/flags" /> </copy> </target> + <target name="prepjupdate" depends="prepupdate, buildWEB"> <copy file="build/jasper-compiler.jar" todir="pkg-temp/lib/" /> <copy file="build/jasper-runtime.jar" todir="pkg-temp/lib/" /> diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java index 5b3f25c1925103246a43e741f133965feb55d8bc..f055ce98cbbeb4594cc1a53d5de10bd4bf97a0a5 100644 --- a/core/java/src/net/i2p/util/EepGet.java +++ b/core/java/src/net/i2p/util/EepGet.java @@ -456,7 +456,7 @@ public class EepGet { for (int i = 0; i < _listeners.size(); i++) _listeners.get(i).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe); if (_log.shouldLog(Log.WARN)) - _log.warn("ERR: doFetch failed " + ioe); + _log.warn("ERR: doFetch failed ", ioe); if (ioe instanceof MalformedURLException) _keepFetching = false; } finally { diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java index 8a5bf364af9a3aa5cec0ed02c07fe9e34612b326..e3ba43ae7a3ad12138f924749fbecfef74b188c9 100644 --- a/core/java/src/net/i2p/util/SSLEepGet.java +++ b/core/java/src/net/i2p/util/SSLEepGet.java @@ -1,55 +1,131 @@ package net.i2p.util; +/* + * Contains code from: + * http://blogs.sun.com/andreas/resource/InstallCert.java + * http://blogs.sun.com/andreas/entry/no_more_unable_to_find + * + * =============== + * + * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; +import java.security.KeyStore; +import java.security.GeneralSecurityException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; - -// all part of the CA experiment below -//import java.io.FileInputStream; -//import java.io.InputStream; -//import java.util.Enumeration; -//import java.security.KeyStore; -//import java.security.GeneralSecurityException; -//import java.security.cert.CertificateExpiredException; -//import java.security.cert.CertificateNotYetValidException; -//import java.security.cert.CertificateFactory; -//import java.security.cert.X509Certificate; -//import javax.net.ssl.KeyManagerFactory; -//import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import net.i2p.I2PAppContext; +import net.i2p.data.Base64; import net.i2p.data.DataHelper; /** * HTTPS only, non-proxied only, no retries, no min and max size options, no timeout option * Fails on 301 or 302 (doesn't follow redirect) - * Fails on self-signed certs (must have a valid cert chain) + * Fails on bad certs (must have a valid cert chain) + * Self-signed certs or CAs not in the JVM key store must be loaded to be trusted. + * + * Since 0.8.2, loads additional trusted CA certs from $I2P/certificates/ and ~/.i2p/certificates/ * * @author zzz * @since 0.7.10 */ public class SSLEepGet extends EepGet { - //private static SSLContext _sslContext; + /** if true, save cert chain on cert error */ + private boolean _saveCerts; + /** true if called from main(), used for logging */ + private boolean _commandLine; + /** may be null if init failed */ + private final SSLContext _sslContext; + /** may be null if init failed */ + private SavingTrustManager _stm; + /** + * A new SSLEepGet with a new SSLState + */ public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url) { + this(ctx, outputStream, url, null); + } + + /** + * @param state an SSLState retrieved from a previous SSLEepGet with getSSLState(), or null. + * This makes repeated fetches from the same host MUCH faster, + * and prevents repeated key store loads even for different hosts. + * @since 0.8.2 + */ + public SSLEepGet(I2PAppContext ctx, OutputStream outputStream, String url, SSLState state) { // we're using this constructor: // public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) { super(ctx, false, null, -1, 0, -1, -1, null, outputStream, url, true, null, null); + if (state != null && state.context != null) + _sslContext = state.context; + else + _sslContext = initSSLContext(); + if (_sslContext == null) + _log.error("Failed to initialize custom SSL context, using default context"); } /** - * SSLEepGet url - * no command line options supported + * SSLEepGet https://foo/bar + * or to save cert chain: + * SSLEepGet -s https://foo/bar */ public static void main(String args[]) { String url = null; + boolean saveCerts = false; try { for (int i = 0; i < args.length; i++) { - if (args[i].startsWith("-")) { + if (args[i].equals("-s")) { + saveCerts = true; + } else if (args[i].startsWith("-")) { usage(); return; } else { @@ -77,94 +153,323 @@ public class SSLEepGet extends EepGet { return; } - /****** - * This is all an experiment to add a CA cert loaded from a file so we can use - * selfsigned certs on our servers. - * But it's failing. - * Run as java -Djava.security.debug=certpath -Djavax.net.debug=trustmanager -cp $I2P/lib/i2p.jar net.i2p.util.SSLEepGet "$@" - * to see the problems. It isn't including the added cert in the Trust Anchor list. - ******/ - - /****** - String foo = System.getProperty("javax.net.ssl.keyStore"); - if (foo == null) { - File cacerts = new File(System.getProperty("java.home"), "lib/security/cacerts"); - foo = cacerts.getAbsolutePath(); - } - System.err.println("Location is: " + foo); + SSLEepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url); + if (saveCerts) + get._saveCerts = true; + get._commandLine = true; + get.addStatusListener(get.new CLIStatusListener(1024, 40)); + get.fetch(45*1000, -1, 60*1000); + } + + private static void usage() { + System.err.println("Usage: SSLEepGet https://url"); + System.err.println("To save unknown certs, use: SSLEepGet -s https://url"); + } + + /** + * Loads certs from location of javax.net.ssl.keyStore property, + * else from $JAVA_HOME/lib/security/jssacacerts, + * else from $JAVA_HOME/lib/security/cacerts. + * + * Then adds certs found in the $I2P/certificates/ directory + * and in the ~/.i2p/certificates/ directory. + * + * @return null on failure + * @since 0.8.2 + */ + private SSLContext initSSLContext() { + KeyStore ks; try { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + } catch (GeneralSecurityException gse) { + _log.error("Key Store init error", gse); + return null; + } + boolean success = false; + String override = System.getProperty("javax.net.ssl.keyStore"); + if (override != null) + success = loadCerts(new File(override), ks); + if (!success) + success = loadCerts(new File(System.getProperty("java.home"), "lib/security/jssecacerts"), ks); + if (!success) + success = loadCerts(new File(System.getProperty("java.home"), "lib/security/cacerts"), ks); + + if (!success) { + _log.error("All key store loads failed, will only load local certificates"); + } else if (_log.shouldLog(Log.INFO)) { + int count = 0; try { - InputStream fis = new FileInputStream(foo); - ks.load(fis, "changeit".toCharArray()); - fis.close(); - } catch (GeneralSecurityException gse) { - System.err.println("KS error, no default keys: " + gse); - ks.load(null, "changeit".toCharArray()); - } catch (IOException ioe) { - System.err.println("IO error, no default keys: " + ioe); - ks.load(null, "changeit".toCharArray()); - } + for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) { + String alias = e.nextElement(); + if (ks.isCertificateEntry(alias)) + count++; + } + } catch (Exception foo) {} + _log.info("Loaded " + count + " default trusted certificates"); + } - addCert(ks, "cacert"); + File dir = new File(_context.getBaseDir(), "certificates"); + int adds = addCerts(dir, ks); + int totalAdds = adds; + if (adds > 0 && _log.shouldLog(Log.INFO)) + _log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + if (!_context.getBaseDir().getAbsolutePath().equals(_context.getConfigDir().getAbsolutePath())) { + dir = new File(_context.getConfigDir(), "certificates"); + adds = addCerts(dir, ks); + totalAdds += adds; + if (adds > 0 && _log.shouldLog(Log.INFO)) + _log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + } + dir = new File(System.getProperty("user.dir")); + if (!_context.getBaseDir().getAbsolutePath().equals(dir.getAbsolutePath())) { + dir = new File(_context.getConfigDir(), "certificates"); + adds = addCerts(dir, ks); + totalAdds += adds; + if (adds > 0 && _log.shouldLog(Log.INFO)) + _log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath()); + } + if (_log.shouldLog(Log.INFO)) + _log.info("Loaded total of " + totalAdds + " new trusted certificates"); - for(Enumeration<String> e = ks.aliases(); e.hasMoreElements();) { - String alias = e.nextElement(); - System.err.println("Aliases: " + alias + " isCert? " + ks.isCertificateEntry(alias)); - } + try { + SSLContext sslc = SSLContext.getInstance("TLS"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0]; + _stm = new SavingTrustManager(defaultTrustManager); + sslc.init(null, new TrustManager[] {_stm}, null); + return sslc; + } catch (GeneralSecurityException gse) { + _log.error("Key Store update error", gse); + } + return null; + } - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, "".toCharArray()); - SSLContext sslc = SSLContext.getInstance("SSL"); - sslc.init(kmf.getKeyManagers(), null, null); - _sslContext = sslc; + /** + * Load all X509 Certs from a key store File into a KeyStore + * Note that each call reinitializes the KeyStore + * + * @return success + * @since 0.8.2 + */ + private boolean loadCerts(File file, KeyStore ks) { + if (!file.exists()) + return false; + InputStream fis = null; + try { + fis = new FileInputStream(file); + // "changeit" is the default password + ks.load(fis, "changeit".toCharArray()); } catch (GeneralSecurityException gse) { - System.err.println("KS error: " + gse); - return; + _log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), gse); + try { + // not clear if null is allowed for password + ks.load(null, "changeit".toCharArray()); + } catch (Exception foo) {} + return false; } catch (IOException ioe) { - System.err.println("IO error: " + ioe); - return; + _log.error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe); + try { + ks.load(null, "changeit".toCharArray()); + } catch (Exception foo) {} + return false; + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} } - *******/ - - EepGet get = new SSLEepGet(I2PAppContext.getGlobalContext(), out, url); - get.addStatusListener(get.new CLIStatusListener(1024, 40)); - get.fetch(45*1000, -1, 60*1000); - } - - private static void usage() { - System.err.println("SSLEepGet url"); + return true; } -/****** - private static boolean addCert(KeyStore ks, String file) { + /** + * Load all X509 Certs from a directory and add them to the + * trusted set of certificates in the key store + * + * @return number successfully added + * @since 0.8.2 + */ + private int addCerts(File dir, KeyStore ks) { + if (_log.shouldLog(Log.INFO)) + _log.info("Looking for X509 Certificates in " + dir.getAbsolutePath()); + int added = 0; + if (dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + File f = files[i]; + if (!f.isFile()) + continue; + // use file name as alias + // https://www.sslshopper.com/ssl-converter.html + // No idea if all these formats can actually be read by CertificateFactory + String alias = f.getName().toLowerCase(); + if (alias.endsWith(".crt") || alias.endsWith(".pem") || alias.endsWith(".key") || + alias.endsWith(".der") || alias.endsWith(".key") || alias.endsWith(".p7b") || + alias.endsWith(".p7c") || alias.endsWith(".pfx") || alias.endsWith(".p12")) + alias = alias.substring(0, alias.length() - 4); + boolean success = addCert(f, alias, ks); + if (success) + added++; + } + } + } + return added; + } + /** + * Load an X509 Cert from a file and add it to the + * trusted set of certificates in the key store + * + * @return success + * @since 0.8.2 + */ + private boolean addCert(File file, String alias, KeyStore ks) { + InputStream fis = null; + try { + fis = new FileInputStream(file); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate)cf.generateCertificate(fis); + if (_log.shouldLog(Log.INFO)) { + _log.info("Read X509 Certificate from " + file.getAbsolutePath() + + " Issuer: " + cert.getIssuerX500Principal() + + "; Valid From: " + cert.getNotBefore() + + " To: " + cert.getNotAfter()); + } try { - InputStream fis = new FileInputStream(file); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = (X509Certificate)cf.generateCertificate(fis); - fis.close(); - System.err.println("Adding cert, Issuer: " + cert.getIssuerX500Principal()); - try { - cert.checkValidity(); - } catch (CertificateExpiredException cee) { - System.err.println("Warning - expired cert: " + cee); - } catch (CertificateNotYetValidException cnyve) { - System.err.println("Warning - not yet valid cert: " + cnyve); - } - // use file name as alias - ks.setCertificateEntry(file, cert); - } catch (GeneralSecurityException gse) { - System.err.println("Read cert error: " + gse); + cert.checkValidity(); + } catch (CertificateExpiredException cee) { + _log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee); return false; - } catch (IOException ioe) { - System.err.println("Read cert error: " + ioe); + } catch (CertificateNotYetValidException cnyve) { + _log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve); return false; } + ks.setCertificateEntry(alias, cert); + if (_log.shouldLog(Log.INFO)) + _log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal()); + } catch (GeneralSecurityException gse) { + _log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse); + return false; + } catch (IOException ioe) { + _log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe); + return false; + } finally { + try { if (fis != null) fis.close(); } catch (IOException foo) {} + } return true; } -*******/ + /** + * From http://blogs.sun.com/andreas/resource/InstallCert.java + * This just saves the certificate chain for later inspection. + * @since 0.8.2 + */ + private static class SavingTrustManager implements X509TrustManager { + private final X509TrustManager tm; + private X509Certificate[] chain; + + SavingTrustManager(X509TrustManager tm) { + this.tm = tm; + } + + public X509Certificate[] getAcceptedIssuers() { + throw new UnsupportedOperationException(); + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + throw new UnsupportedOperationException(); + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + this.chain = chain; + tm.checkServerTrusted(chain, authType); + } + } + + /** + * Modified from http://blogs.sun.com/andreas/resource/InstallCert.java + * @since 0.8.2 + */ + private static void saveCerts(String host, SavingTrustManager stm) { + X509Certificate[] chain = stm.chain; + if (chain == null) { + System.out.println("Could not obtain server certificate chain"); + return; + } + for (int k = 0; k < chain.length; k++) { + X509Certificate cert = chain[k]; + String name = host + '-' + (k + 1) + ".crt"; + System.out.println("NOTE: Saving untrusted X509 certificate as " + name); + System.out.println(" Issuer: " + cert.getIssuerX500Principal()); + System.out.println(" Valid From: " + cert.getNotBefore()); + System.out.println(" Valid To: " + cert.getNotAfter()); + try { + cert.checkValidity(); + } catch (Exception e) { + System.out.println(" WARNING: Certificate is not currently valid, it cannot be used"); + } + saveCert(cert, new File(name)); + } + System.out.println("NOTE: To trust them, copy the certificate file(s) to the certificates directory and rerun without the -s option"); + System.out.println("NOTE: EepGet failed, certificate error follows:"); + } + + private static final int LINE_LENGTH = 64; + + /** + * Modified from: + * http://www.exampledepot.com/egs/java.security.cert/ExportCert.html + * + * This method writes a certificate to a file in base64 format. + * @since 0.8.2 + */ + private static void saveCert(Certificate cert, File file) { + OutputStream os = null; + try { + // Get the encoded form which is suitable for exporting + byte[] buf = cert.getEncoded(); + os = new FileOutputStream(file); + PrintWriter wr = new PrintWriter(os); + wr.println("-----BEGIN CERTIFICATE-----"); + String b64 = Base64.encode(buf, true); // true = use standard alphabet + for (int i = 0; i < b64.length(); i += LINE_LENGTH) { + wr.println(b64.substring(i, Math.min(i + LINE_LENGTH, b64.length()))); + } + wr.println("-----END CERTIFICATE-----"); + wr.flush(); + } catch (CertificateEncodingException cee) { + System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + cee); + } catch (IOException ioe) { + System.out.println("Error writing X509 Certificate " + file.getAbsolutePath() + ' ' + ioe); + } finally { + try { if (os != null) os.close(); } catch (IOException foo) {} + } + } + + /** + * An opaque class for the caller to pass to repeated instantiations of SSLEepGet. + * @since 0.8.2 + */ + public static class SSLState { + private SSLContext context; + + private SSLState(SSLContext ctx) { + context = ctx; + } + } + + /** + * Pass this back to the next SSLEepGet constructor for faster fetches. + * This may be called either after the constructor or after the fetch. + * @since 0.8.2 + */ + public SSLState getSSLState() { + return new SSLState(_sslContext); + } + + ///// end of all the SSL stuff + ///// start of overrides + @Override protected void doFetch(SocketTimeout timeout) throws IOException { _headersRead = false; @@ -288,15 +593,16 @@ public class SSLEepGet extends EepGet { //try { URL url = new URL(_actualURL); + String host = null; + int port = 0; if ("https".equals(url.getProtocol())) { - String host = url.getHost(); - int port = url.getPort(); + host = url.getHost(); + port = url.getPort(); if (port == -1) port = 443; - // part of the experiment above - //if (_sslContext != null) - // _proxy = _sslContext.getSocketFactory().createSocket(host, port); - //else + if (_sslContext != null) + _proxy = _sslContext.getSocketFactory().createSocket(host, port); + else _proxy = SSLSocketFactory.getDefault().createSocket(host, port); } else { throw new IOException("Only https supported: " + _actualURL); @@ -309,8 +615,23 @@ public class SSLEepGet extends EepGet { _proxyIn = _proxy.getInputStream(); _proxyOut = _proxy.getOutputStream(); - _proxyOut.write(DataHelper.getUTF8(req)); - _proxyOut.flush(); + // This is where the cert errors happen + try { + _proxyOut.write(DataHelper.getUTF8(req)); + _proxyOut.flush(); + } catch (SSLHandshakeException sslhe) { + // this maybe would be better done in the catch in super.fetch(), but + // then we'd have to copy it all over here. + _log.error("SSL negotiation error with " + host + ':' + port + + " - self-signed certificate or untrusted certificate authority?", sslhe); + if (_saveCerts && _stm != null) + saveCerts(host, _stm); + else if (_commandLine) { + System.out.println("FAILED (probably due to untrusted certificates) - Run with -s option to save certificates"); + } + // this is an IOE + throw sslhe; + } if (_log.shouldLog(Log.DEBUG)) _log.debug("Request flushed"); diff --git a/history.txt b/history.txt index ad7880fb1df9932c0ddccba6f64bc844e0a566ce..398b6ca0c8fa803fc677d92605ae817e97399536 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,26 @@ +2010-11-22 zzz + * Addressbook: Fix rename error on Windows (tkt 323 - thanks RN!) + * build.xml: Cleanup, fix distclean error in older ants. + * Console: + - Convert GraphHelper to a FormHandler + - Require POST for all forms + - Change the way we store the Writer to prevent problems + - Fix bonus setting on configpeer.jsp + - More ".jsp" removal + * i2psnark: + - Defer piece loading until required + - Stub out Extension message support + - Convert GET to POST, require POST + * NTCP: Log tweak + * SSLEepGet, Reseeder: + - Implement additional CA loading + - Provide facility to reuse SSL state for speed + - Provide facility to store previously untrusted certificates + - Add www.cacert.org cert to the installer and updater so + SSL on a.netdb.i2p2.de and c.netdb.i2p2.de will work + - Add SSL reseed hosts, prefer them by default + - Reseed message cleanup + 2010-11-19 zzz * Addressbook - Store last-fetched time so we don't always fetch subscriptions after restart diff --git a/installer/resources/certificates/www.cacert.org.crt b/installer/resources/certificates/www.cacert.org.crt new file mode 100644 index 0000000000000000000000000000000000000000..e7dfc82947e30af0637575869fba5eef4c683b7d --- /dev/null +++ b/installer/resources/certificates/www.cacert.org.crt @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 +IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB +IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA +Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO +BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi +MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ +ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ +8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 +zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y +fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 +w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc +G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k +epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q +laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ +QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU +fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 +YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w +ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY +gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe +MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 +IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy +dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw +czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 +dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl +aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC +AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg +b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB +ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc +nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg +18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c +gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl +Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY +sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T +SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF +CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum +GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk +zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW +omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD +-----END CERTIFICATE----- diff --git a/installer/resources/themes/snark/ubergine/snark.css b/installer/resources/themes/snark/ubergine/snark.css index 535c329549e71d8a1cac703342be2628a3ca7015..52e9f53459644177c8cf7e1617d4b06b3aa12519 100644 --- a/installer/resources/themes/snark/ubergine/snark.css +++ b/installer/resources/themes/snark/ubergine/snark.css @@ -474,6 +474,20 @@ input[type=submit]:active { text-shadow: 0 !important; } +input[type=image] { + padding: 0 !important; + background: #000; + -moz-border-radius: 0px; + -khtml-border-radius: 0px; + border-radius: 0px; + border: medium none; + margin: 0 2px; +} + +input[type=image]:hover { + border: 1px outset #bbb; +} + input[type=text]:active, input[type=text]:hover, input.r:hover { background: #f60; color: #fff; diff --git a/licenses/LICENSE-InstallCert.txt b/licenses/LICENSE-InstallCert.txt new file mode 100644 index 0000000000000000000000000000000000000000..17620f66de99b44599efef3e989f69153813d6f6 --- /dev/null +++ b/licenses/LICENSE-InstallCert.txt @@ -0,0 +1,29 @@ +Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of Sun Microsystems nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 0725033fa600cfb5180ac82283f5194ee35b34b0..f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 3; + public final static long BUILD = 4; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 776d7eee46b4fa0445be32cb80f84b54cdc6748e..9577b2b200e9e00bc8adf72c055a310b6531b671 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -29,6 +29,8 @@ import net.i2p.util.Translate; * specified below unless the I2P configuration property "i2p.reseedURL" is * set. It always writes to ./netDb/, so don't mess with that. * + * This is somewhat complicated by trying to log to three places - the console, + * the router log, and the wrapper log. */ public class Reseeder { private static ReseedRunner _reseedRunner; @@ -40,12 +42,25 @@ public class Reseeder { private static final String DEFAULT_SEED_URL = "http://a.netdb.i2p2.de/,http://b.netdb.i2p2.de/,http://c.netdb.i2p2.de/," + - "http://reseed.i2p-projekt.de/,http://i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/"; + "http://reseed.i2p-projekt.de/,http://www.i2pbote.net/netDb/,http://r31453.ovh.net/static_media/netDb/"; + + /** @since 0.8.2 */ + private static final String DEFAULT_SSL_SEED_URL = + "https://a.netdb.i2p2.de/,https://c.netdb.i2p2.de/," + + "https://www.i2pbote.net/netDb/"; + private static final String PROP_INPROGRESS = "net.i2p.router.web.ReseedHandler.reseedInProgress"; + /** the console shows this message while reseedInProgress == false */ private static final String PROP_ERROR = "net.i2p.router.web.ReseedHandler.errorMessage"; + /** the console shows this message while reseedInProgress == true */ private static final String PROP_STATUS = "net.i2p.router.web.ReseedHandler.statusMessage"; public static final String PROP_PROXY_HOST = "router.reseedProxyHost"; public static final String PROP_PROXY_PORT = "router.reseedProxyPort"; + /** @since 0.8.2 */ + public static final String PROP_PROXY_ENABLE = "router.reseedProxyEnable"; + /** @since 0.8.2 */ + public static final String PROP_SSL_DISABLE = "router.reseedSSLDisable"; + private static final String RESEED_TIPS = _x("Ensure that nothing blocks outbound HTTP, check <a target=\"_top\" href=\"logs.jsp\">logs</a> " + "and if nothing helps, read the <a target=\"_top\" href=\"http://www.i2p2.de/faq.html\">FAQ</a> about reseeding manually."); @@ -63,7 +78,6 @@ public class Reseeder { if (_reseedRunner.isRunning()) { return; } else { - System.setProperty(PROP_INPROGRESS, "true"); // set to daemon so it doesn't hang a shutdown Thread reseed = new I2PAppThread(_reseedRunner, "Reseed", true); reseed.start(); @@ -76,20 +90,46 @@ public class Reseeder { private boolean _isRunning; private String _proxyHost; private int _proxyPort; + private SSLEepGet.SSLState _sslState; public ReseedRunner() { _isRunning = false; + System.clearProperty(PROP_ERROR); System.setProperty(PROP_STATUS, _("Reseeding")); + System.setProperty(PROP_INPROGRESS, "true"); } public boolean isRunning() { return _isRunning; } + + /* + * Do it. + * We update PROP_ERROR here. + */ public void run() { _isRunning = true; - _proxyHost = _context.getProperty(PROP_PROXY_HOST); - _proxyPort = _context.getProperty(PROP_PROXY_PORT, -1); + _sslState = null; // start fresh + if (_context.getBooleanProperty(PROP_PROXY_ENABLE)) { + _proxyHost = _context.getProperty(PROP_PROXY_HOST); + _proxyPort = _context.getProperty(PROP_PROXY_PORT, -1); + } System.out.println("Reseed start"); - reseed(false); - System.out.println("Reseed complete"); + int total = reseed(false); + if (total >= 50) { + System.out.println("Reseed complete, " + total + " received"); + System.clearProperty(PROP_ERROR); + } else if (total > 0) { + System.out.println("Reseed complete, only " + total + " received"); + System.setProperty(PROP_ERROR, ngettext("Reseed fetched only 1 router.", + "Reseed fetched only {0} routers.", total)); + } else { + System.out.println("Reseed failed, check network connection"); + System.out.println( + "Ensure that nothing blocks outbound HTTP, check the logs, " + + "and if nothing helps, read the FAQ about reseeding manually."); + System.setProperty(PROP_ERROR, _("Reseed failed.") + ' ' + _(RESEED_TIPS)); + } System.setProperty(PROP_INPROGRESS, "false"); + System.clearProperty(PROP_STATUS); + _sslState = null; // don't hold ref _isRunning = false; } @@ -112,16 +152,56 @@ public class Reseeder { * the routerInfo-*.dat files from the specified URL (or the default) and * save them into this router's netDb dir. * + * - If list specified in the properties, use it randomly, without regard to http/https + * - If SSL not disabled, use the https randomly then + * the http randomly + * - Otherwise just the http randomly. + * + * @param echoStatus apparently always false + * @return count of routerinfos successfully fetched */ - private void reseed(boolean echoStatus) { - List URLList = new ArrayList(); - String URLs = _context.getProperty("i2p.reseedURL", DEFAULT_SEED_URL); + private int reseed(boolean echoStatus) { + List<String> URLList = new ArrayList(); + String URLs = _context.getProperty("i2p.reseedURL"); + boolean defaulted = URLs == null; + boolean SSLDisable = _context.getBooleanProperty(PROP_SSL_DISABLE); + if (defaulted) { + if (SSLDisable) + URLs = DEFAULT_SEED_URL; + else + URLs = DEFAULT_SSL_SEED_URL; + } StringTokenizer tok = new StringTokenizer(URLs, " ,"); while (tok.hasMoreTokens()) URLList.add(tok.nextToken().trim()); Collections.shuffle(URLList); - for (int i = 0; i < URLList.size() && _isRunning; i++) - reseedOne((String) URLList.get(i), echoStatus); + if (defaulted && !SSLDisable) { + // put the non-SSL at the end of the SSL + List<String> URLList2 = new ArrayList(); + tok = new StringTokenizer(DEFAULT_SSL_SEED_URL, " ,"); + while (tok.hasMoreTokens()) + URLList2.add(tok.nextToken().trim()); + Collections.shuffle(URLList2); + URLList.addAll(URLList2); + } + int total = 0; + for (int i = 0; i < URLList.size() && _isRunning; i++) { + String url = URLList.get(i); + int dl = reseedOne(url, echoStatus); + if (dl > 0) { + total += dl; + // remove alternate version if we haven't tried it yet + String alt; + if (url.startsWith("http://")) + alt = url.replace("http://", "https://"); + else + alt = url.replace("https://", "http://"); + int idx = URLList.indexOf(alt); + if (idx > i) + URLList.remove(i); + } + } + return total; } /** @@ -138,22 +218,23 @@ public class Reseeder { * * Jetty directory listings are not compatible, as they look like * HREF="/full/path/to/routerInfo-... + * + * We update PROP_STATUS here. + * + * @param echoStatus apparently always false + * @return count of routerinfos successfully fetched **/ - private void reseedOne(String seedURL, boolean echoStatus) { - + private int reseedOne(String seedURL, boolean echoStatus) { try { - System.setProperty(PROP_ERROR, ""); System.setProperty(PROP_STATUS, _("Reseeding: fetching seed URL.")); - System.err.println("Reseed from " + seedURL); + System.err.println("Reseeding from " + seedURL); URL dir = new URL(seedURL); byte contentRaw[] = readURL(dir); if (contentRaw == null) { - System.setProperty(PROP_ERROR, - _("Last reseed failed fully (failed reading seed URL).") + ' ' + - _(RESEED_TIPS)); // Logging deprecated here since attemptFailed() provides better info - _log.debug("Failed reading seed URL: " + seedURL); - return; + _log.warn("Failed reading seed URL: " + seedURL); + System.err.println("Reseed got no router infos from " + seedURL); + return 0; } String content = new String(contentRaw); Set<String> urls = new HashSet(1024); @@ -173,11 +254,9 @@ public class Reseeder { cur = end + 1; } if (total <= 0) { - _log.error("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs."); - System.setProperty(PROP_ERROR, - _("Last reseed failed fully (no routerInfo URLs at seed URL).") + ' ' + - _(RESEED_TIPS)); - return; + _log.warn("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs."); + System.err.println("Reseed got no router infos from " + seedURL); + return 0; } List<String> urlList = new ArrayList(urls); @@ -201,32 +280,18 @@ public class Reseeder { errors++; } } - System.err.println("Reseed got " + fetched + " router infos from " + seedURL); - - int failPercent = 100 * errors / total; - - // Less than 10% of failures is considered success, - // because some routerInfos will always fail. - if ((failPercent >= 10) && (failPercent < 90)) { - System.setProperty(PROP_ERROR, - _("Last reseed failed partly ({0}% of {1}).", failPercent, total) + ' ' + - _(RESEED_TIPS)); - } - if (failPercent >= 90) { - System.setProperty(PROP_ERROR, - _("Last reseed failed ({0}% of {1}).", failPercent, total) + ' ' + - _(RESEED_TIPS)); - } + System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors"); + if (fetched > 0) _context.netDb().rescan(); // Don't go on to the next URL if we have enough if (fetched >= 100) _isRunning = false; + return fetched; } catch (Throwable t) { - System.setProperty(PROP_ERROR, - _("Last reseed failed fully (exception caught).") + ' ' + - _(RESEED_TIPS)); - _log.error("Error reseeding", t); + _log.warn("Error reseeding", t); + System.err.println("Reseed got no router infos from " + seedURL); + return 0; } } @@ -248,8 +313,17 @@ public class Reseeder { ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024); EepGet get; - if (url.toString().startsWith("https")) { - get = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString()); + boolean ssl = url.toString().startsWith("https"); + if (ssl) { + SSLEepGet sslget; + if (_sslState == null) { + sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString()); + // save state for next time + _sslState = sslget.getSSLState(); + } else { + sslget = new SSLEepGet(I2PAppContext.getGlobalContext(), baos, url.toString(), _sslState); + } + get = sslget; } else { // Do a (probably) non-proxied eepget into our ByteArrayOutputStream with 0 retries boolean shouldProxy = _proxyHost != null && _proxyHost.length() > 0 && _proxyPort > 0; @@ -257,7 +331,9 @@ public class Reseeder { null, baos, url.toString(), false, null, null); } get.addStatusListener(ReseedRunner.this); - if (get.fetch()) return baos.toByteArray(); else return null; + if (get.fetch()) + return baos.toByteArray(); + return null; } private void writeSeed(String name, byte data[]) throws Exception { @@ -295,6 +371,11 @@ public class Reseeder { return Translate.getString(s, o, o2, _context, BUNDLE_NAME); } + /** translate */ + private String ngettext(String s, String p, int n) { + return Translate.getString(n, s, p, _context, BUNDLE_NAME); + } + /****** public static void main(String args[]) { if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) { diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 58ebe3634739a6b92df4f5ea25bc09b2cf13cb77..4041f3dadbe3830ed096c913e02ca6a9d56b9a0d 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -1274,10 +1274,10 @@ public class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener { return; } } else { - if (_log.shouldLog(Log.ERROR)) - _log.error("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + _expectedCrc + ") size=" + _size + " blocks " + _blocks); + if (_log.shouldLog(Log.WARN)) + _log.warn("CRC incorrect for message " + _messagesRead + " (calc=" + val + " expected=" + _expectedCrc + ") size=" + _size + " blocks " + _blocks); _context.statManager().addRateData("ntcp.corruptI2NPCRC", 1, getUptime()); - // should we try to read in the message and keep going? + // FIXME should we try to read in the message and keep going? close(); // databuf is lost return;