diff --git a/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java b/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java index bd2f78e53..1d48654ba 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java +++ b/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java @@ -54,8 +54,15 @@ public interface CompleteListener { */ public void gotPiece(Snark snark); - // not really listeners but the easiest way to get back to an optional SnarkManager + /** not really listeners but the easiest way to get back to an optional SnarkManager */ public long getSavedTorrentTime(Snark snark); public BitField getSavedTorrentBitField(Snark snark); + /** + * @since 0.9.15 + */ public boolean getSavedPreserveNamesSetting(Snark snark); + /** + * @since 0.9.15 + */ + public long getSavedUploaded(Snark snark); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 3dcb535cb..382ca4817 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -281,6 +281,14 @@ class PeerCoordinator implements PeerListener return uploaded; } + /** + * Sets the initial total of uploaded bytes of all peers (from a saved status) + * @since 0.9.15 + */ + public void setUploaded(long up) { + uploaded = up; + } + /** * Returns the total number of downloaded bytes of all peers. */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 9358623b7..740b7c2be 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -237,6 +237,7 @@ public class Snark private volatile boolean _autoStoppable; // String indicating main activity private volatile String activity = "Not started"; + private final long savedUploaded; /** @@ -463,6 +464,7 @@ public class Snark trackerclient = new TrackerClient(meta, coordinator); */ + savedUploaded = (completeListener != null) ? completeListener.getSavedUploaded(this) : 0; if (start) startTorrent(); } @@ -488,6 +490,7 @@ public class Snark this.infoHash = ih; this.additionalTrackerURL = trackerURL; this.rootDataDir = rootDir != null ? new File(rootDir) : null; // null only for FetchAndAdd extension + savedUploaded = 0; stopped = true; id = generateID(); @@ -556,6 +559,7 @@ public class Snark _log.info("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient"); activity = "Collecting pieces"; coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this); + coordinator.setUploaded(savedUploaded); if (_peerCoordinatorSet != null) { // multitorrent _peerCoordinatorSet.add(coordinator); @@ -619,7 +623,7 @@ public class Snark pc.halt(); Storage st = storage; if (st != null) { - boolean changed = storage.isChanged(); + boolean changed = storage.isChanged() || getUploaded() != savedUploaded; try { storage.close(); } catch (IOException ioe) { @@ -773,7 +777,7 @@ public class Snark PeerCoordinator coord = coordinator; if (coord != null) return coord.getUploaded(); - return 0; + return savedUploaded; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index b910e32d5..8ee32d985 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -92,6 +92,7 @@ public class SnarkManager implements CompleteListener { private static final String PROP_META_BITFIELD = "bitfield"; private static final String PROP_META_PRIORITY = "priority"; private static final String PROP_META_PRESERVE_NAMES = "preserveFileNames"; + private static final String PROP_META_UPLOADED = "uploaded"; //private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; //private static final String PROP_META_PRIORITY_SUFFIX = ".priority"; private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet."; @@ -128,6 +129,9 @@ public class SnarkManager implements CompleteListener { /** * "name", "announceURL=websiteURL" pairs * '=' in announceURL must be escaped as , + * + * Please use host name, not b32 or full dest, in announce URL. Ensure in default hosts.txt. + * Please use host name, not b32 or full dest, in website URL. Ensure in default hosts.txt. */ private static final String DEFAULT_TRACKERS[] = { // "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/" @@ -1357,7 +1361,7 @@ public class SnarkManager implements CompleteListener { return false; } // so addTorrent won't recheck - saveTorrentStatus(metainfo, bitfield, null, baseFile, true); // no file priorities + saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0); // no file priorities try { locked_writeMetaInfo(metainfo, filename, areFilesPublic()); // hold the lock for a long time @@ -1522,6 +1526,21 @@ public class SnarkManager implements CompleteListener { Properties config = getConfig(snark); return Boolean.parseBoolean(config.getProperty(PROP_META_PRESERVE_NAMES)); } + + /** + * Get setting for a torrent from the config file. + * @return setting, 0 if not found + * @since 0.9.15 + */ + public long getSavedUploaded(Snark snark) { + Properties config = getConfig(snark); + if (config != null) { + try { + return Long.parseLong(config.getProperty(PROP_META_UPLOADED)); + } catch (NumberFormatException nfe) {} + } + return 0; + } /** * Save the completion status of a torrent and other data in the config file @@ -1535,7 +1554,8 @@ public class SnarkManager implements CompleteListener { if (meta == null || storage == null) return; saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), - storage.getBase(), storage.getPreserveFileNames()); + storage.getBase(), storage.getPreserveFileNames(), + snark.getUploaded()); } /** @@ -1550,14 +1570,14 @@ public class SnarkManager implements CompleteListener { * @param base may be null */ private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, - File base, boolean preserveNames) { + File base, boolean preserveNames, long uploaded) { synchronized (_configLock) { - locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames); + locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames, uploaded); } } private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, - File base, boolean preserveNames) { + File base, boolean preserveNames, long uploaded) { byte[] ih = metainfo.getInfoHash(); String bfs; if (bitfield.complete()) { @@ -1570,6 +1590,7 @@ public class SnarkManager implements CompleteListener { config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis())); config.setProperty(PROP_META_BITFIELD, bfs); config.setProperty(PROP_META_PRESERVE_NAMES, Boolean.toString(preserveNames)); + config.setProperty(PROP_META_UPLOADED, Long.toString(uploaded)); if (base != null) config.setProperty(PROP_META_BASE, base.getAbsolutePath()); @@ -1826,7 +1847,7 @@ public class SnarkManager implements CompleteListener { Storage storage = snark.getStorage(); if (meta != null && storage != null) saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), - storage.getBase(), storage.getPreserveFileNames()); + storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded()); } /** @@ -1849,7 +1870,7 @@ public class SnarkManager implements CompleteListener { return null; } saveTorrentStatus(meta, storage.getBitField(), null, - storage.getBase(), storage.getPreserveFileNames()); // no file priorities + storage.getBase(), storage.getPreserveFileNames(), 0); // temp for addMessage() in case canonical throws String name = storage.getBaseName(); try { diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index edcab93cb..04b9f7c11 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -316,15 +316,34 @@ public class Storage } /** + * Get index to pass to remaining(), getPriority(), setPriority() + * * @param file non-canonical path (non-directory) + * @return internal index of file; -1 if unknown file + * @since 0.9.15 + */ + public int indexOf(File file) { + for (int i = 0; i < _torrentFiles.size(); i++) { + File f = _torrentFiles.get(i).RAFfile; + if (f.equals(file)) + return i; + } + return -1; + } + + /** + * @param fileIndex as obtained from indexOf * @return number of bytes remaining; -1 if unknown file * @since 0.7.14 */ - public long remaining(File file) { + public long remaining(int fileIndex) { + if (fileIndex < 0 || fileIndex >= _torrentFiles.size()) + return -1; long bytes = 0; - for (TorrentFile tf : _torrentFiles) { - File f = tf.RAFfile; - if (f.equals(file)) { + for (int i = 0; i < _torrentFiles.size(); i++) { + TorrentFile tf = _torrentFiles.get(i); + if (i == fileIndex) { + File f = tf.RAFfile; if (complete()) return 0; int psz = piece_size; @@ -350,37 +369,30 @@ public class Storage } /** - * @param file non-canonical path (non-directory) + * @param fileIndex as obtained from indexOf * @since 0.8.1 */ - public int getPriority(File file) { + public int getPriority(int fileIndex) { if (complete() || metainfo.getFiles() == null) return 0; - for (TorrentFile tf : _torrentFiles) { - File f = tf.RAFfile; - if (f.equals(file)) - return tf.priority; - } - return 0; + if (fileIndex < 0 || fileIndex >= _torrentFiles.size()) + return 0; + return _torrentFiles.get(fileIndex).priority; } /** * Must call Snark.updatePiecePriorities() * (which calls getPiecePriorities()) after calling this. - * @param file non-canonical path (non-directory) + * @param fileIndex as obtained from indexOf * @param pri default 0; <0 to disable * @since 0.8.1 */ - public void setPriority(File file, int pri) { + public void setPriority(int fileIndex, int pri) { if (complete() || metainfo.getFiles() == null) return; - for (TorrentFile tf : _torrentFiles) { - File f = tf.RAFfile; - if (f.equals(file)) { - tf.priority = pri; - return; - } - } + if (fileIndex < 0 || fileIndex >= _torrentFiles.size()) + return; + _torrentFiles.get(fileIndex).priority = pri; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index c30ccb0cc..c9af416ec 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -72,6 +72,8 @@ public class TrackerClient implements Runnable { private static final String STOPPED_EVENT = "stopped"; private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon private static final String NOT_REGISTERED_2 = "torrent not found"; // diftracker + private static final String NOT_REGISTERED_3 = "torrent unauthorised"; // vuze + private static final String ERROR_GOT_HTML = "received html"; // fake return /** this is our equivalent to router.utorrent.com for bootstrap */ private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a"; @@ -577,14 +579,20 @@ public class TrackerClient implements Runnable { if (tr.isPrimary) snark.setTrackerProblems(tr.trackerProblems); String tplc = tr.trackerProblems.toLowerCase(Locale.US); - if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2)) { + if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2) || + tplc.startsWith(NOT_REGISTERED_3) || tplc.startsWith(ERROR_GOT_HTML)) { // Give a guy some time to register it if using opentrackers too //if (trckrs.size() == 1) { // stop = true; // snark.stopTorrent(); //} else { // hopefully each on the opentrackers list is really open if (tr.registerFails++ > MAX_REGISTER_FAILS || + !completed || // no use retrying if we aren't seeding + tplc.startsWith(ERROR_GOT_HTML) || // fake msg from doRequest() (!tr.isPrimary && tr.registerFails > MAX_REGISTER_FAILS / 2)) + if (_log.shouldLog(Log.WARN)) + _log.warn("Not longer announcing to " + tr.announce + " : " + + tr.trackerProblems + " after " + tr.registerFails + " failures"); tr.stop = true; // } @@ -797,10 +805,15 @@ public class TrackerClient implements Runnable { tr.lastRequestTime = System.currentTimeMillis(); // Don't wait for a response to stopped when shutting down boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT); - byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024); - if (fetched == null) { - throw new IOException("Error fetching " + s); - } + byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 32*1024); + if (fetched == null) + throw new IOException("Error fetching"); + if (fetched.length == 0) + throw new IOException("No data"); + // The HTML check only works if we didn't exceed the maxium fetch size specified in get(), + // otherwise we already threw an IOE. + if (fetched[0] == '<') + throw new IOException(ERROR_GOT_HTML); InputStream in = new ByteArrayInputStream(fetched); diff --git a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java index db35a6910..d7f3b6690 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java +++ b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java @@ -295,6 +295,10 @@ class UpdateRunner implements UpdateTask, CompleteListener { return _smgr.getSavedPreserveNamesSetting(snark); } + public long getSavedUploaded(Snark snark) { + return _smgr.getSavedUploaded(snark); + } + //////// end CompleteListener methods private static String linkify(String url) { 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 b3e5f3038..b8ae5236f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -25,8 +25,10 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import net.i2p.data.Base32; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.data.Hash; import net.i2p.util.Log; import org.klomp.snark.I2PSnarkUtil; @@ -498,7 +500,7 @@ public class I2PSnarkServlet extends BasicServlet { out.write(_("RX")); out.write("\">"); out.write("\n"); - if (_manager.util().connected() && !snarks.isEmpty()) { + if (!snarks.isEmpty()) { out.write("\"");"); + out.write(" onclick=\"document.location='" + encodedBaseName + "/';\">"); } else { out.write('>'); } @@ -1465,15 +1466,15 @@ public class I2PSnarkServlet extends BasicServlet { // out.write("??"); // no meta size yet out.write("\n\t"); out.write(""); - if(isRunning && isValid) + if (isValid && uploaded > 0) out.write(formatSize(uploaded)); out.write("\n\t"); out.write(""); - if(isRunning && needed > 0) + if (isRunning && needed > 0) out.write(formatSize(downBps) + "ps"); out.write("\n\t"); out.write(""); - if(isRunning && isValid) + if (isRunning && isValid) out.write(formatSize(upBps) + "ps"); out.write("\n\t"); out.write(""); @@ -1717,8 +1718,10 @@ public class I2PSnarkServlet extends BasicServlet { } /** - * Start of anchor only, caller must add anchor text or img and close anchor - * @return string or null + * Generate link to details page if we know it supports it. + * Start of anchor only, caller must add anchor text or img and close anchor. + * + * @return string or null if unknown tracker * @since 0.8.4 */ private String getTrackerLinkUrl(String announce, byte[] infohash) { @@ -1745,8 +1748,8 @@ public class I2PSnarkServlet extends BasicServlet { } /** - * Full anchor with img - * @return string or null + * Full link to details page with img + * @return string or null if details page unsupported * @since 0.8.4 */ private String getTrackerLink(String announce, byte[] infohash) { @@ -1762,7 +1765,7 @@ public class I2PSnarkServlet extends BasicServlet { } /** - * Full anchor with shortened URL as anchor text + * Full anchor to home page or details page with shortened host name as anchor text * @return string, non-null * @since 0.9.5 */ @@ -1771,14 +1774,37 @@ public class I2PSnarkServlet extends BasicServlet { String trackerLinkUrl = getTrackerLinkUrl(announce, infohash); if (announce.startsWith("http://")) announce = announce.substring(7); + // strip path int slsh = announce.indexOf('/'); if (slsh > 0) announce = announce.substring(0, slsh); - if (trackerLinkUrl != null) + if (trackerLinkUrl != null) { buf.append(trackerLinkUrl); - else - // TODO encode - buf.append(""); + } else { + // browsers don't like a full b64 dest, so convert it to b32 + String host = announce; + if (host.length() >= 516) { + int colon = announce.indexOf(':'); + String port = ""; + if (colon > 0) { + port = host.substring(colon); + host = host.substring(0, colon); + } + if (host.endsWith(".i2p")) + host = host.substring(0, host.length() - 4); + byte[] b = Base64.decode(host); + if (b != null) { + Hash h = _context.sha().calculateHash(b); + // should we add the port back or strip it? + host = Base32.encode(h.getData()) + ".b32.i2p" + port; + } + } + buf.append(""); + } + // strip port + int colon = announce.indexOf(':'); + if (colon > 0) + announce = announce.substring(0, colon); if (announce.length() > 67) announce = DataHelper.escapeHTML(announce.substring(0, 40)) + "…" + DataHelper.escapeHTML(announce.substring(announce.length() - 8)); @@ -2673,6 +2699,7 @@ public class I2PSnarkServlet extends BasicServlet { boolean complete = false; String status = ""; long length = item.length(); + int fileIndex = -1; int priority = 0; if (item.isDirectory()) { complete = true; @@ -2684,8 +2711,9 @@ public class I2PSnarkServlet extends BasicServlet { status = toImg("cancel") + ' ' + _("Torrent not found?"); } else { Storage storage = snark.getStorage(); + fileIndex = storage.indexOf(item); - long remaining = storage.remaining(item); + long remaining = storage.remaining(fileIndex); if (remaining < 0) { complete = true; status = toImg("cancel") + ' ' + _("File not found in torrent?"); @@ -2693,7 +2721,7 @@ public class I2PSnarkServlet extends BasicServlet { complete = true; status = toImg("tick") + ' ' + _("Complete"); } else { - priority = storage.getPriority(item); + priority = storage.getPriority(fileIndex); if (priority < 0) status = toImg("cancel"); else if (priority == 0) @@ -2745,17 +2773,17 @@ public class I2PSnarkServlet extends BasicServlet { if (showPriority) { buf.append(""); if ((!complete) && (!item.isDirectory())) { - buf.append("\n 0) buf.append("checked=\"checked\""); buf.append('>').append(_("High")); - buf.append("\n').append(_("Normal")); - buf.append("\n').append(_("Skip")); @@ -2857,10 +2885,10 @@ public class I2PSnarkServlet extends BasicServlet { String key = entry.getKey(); if (key.startsWith("pri.")) { try { - File file = new File(key.substring(4)); + int fileIndex = Integer.parseInt(key.substring(4)); String val = entry.getValue()[0]; // jetty arrays int pri = Integer.parseInt(val); - storage.setPriority(file, pri); + storage.setPriority(fileIndex, pri); //System.err.println("Priority now " + pri + " for " + file); } catch (Throwable t) { t.printStackTrace(); } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java index aae95b515..10f33b022 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java @@ -84,6 +84,7 @@ class URIUtil case '<': case '>': case ' ': + case ':': buf=new StringBuilder(path.length()*2); break loop; default: @@ -139,6 +140,9 @@ class URIUtil case 0x7f: buf.append("%7F"); continue; + case ':': + buf.append("%3A"); + continue; default: if (c <= 0x1f) // includes negative toHex(c,buf); @@ -183,6 +187,9 @@ class URIUtil case ' ': buf.append("%20"); continue; + case ':': + buf.append("%3A"); + continue; default: if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || Character.isSpaceChar(c)) toHex(c,buf); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index 128b1565d..ac1d2d39a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -182,6 +182,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { if (getTunnel() != tunnel) return; setupPostThrottle(); + Properties props = tunnel.getClientOptions(); + // see TunnelController.setSessionOptions() + String spoofHost = props.getProperty(TunnelController.PROP_SPOOFED_HOST); + _spoofHost = (spoofHost != null && spoofHost.trim().length() > 0) ? spoofHost.trim() : null; super.optionsUpdated(tunnel); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 0d2667d60..dfec31bc3 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -16,6 +16,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.Map; import java.util.Properties; @@ -50,8 +51,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { protected final Object slock = new Object(); protected final Object sslLock = new Object(); - protected final InetAddress remoteHost; - protected final int remotePort; + protected InetAddress remoteHost; + protected int remotePort; private final boolean _usePool; protected final Logging l; private I2PSSLSocketFactory _sslFactory; @@ -265,6 +266,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { * Copy input stream to a byte array, so we can retry * @since 0.7.10 */ +/**** private static ByteArrayInputStream copyOfInputStream(InputStream is) throws IOException { byte[] buf = new byte[128]; ByteArrayOutputStream os = new ByteArrayOutputStream(768); @@ -279,6 +281,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { } return new ByteArrayInputStream(os.toByteArray()); } +****/ /** * Start running the I2PTunnelServer. @@ -348,6 +351,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { /** * Update the I2PSocketManager. + * And since 0.9.15, the target host and port. * * @since 0.9.1 */ @@ -357,6 +361,27 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { return; Properties props = tunnel.getClientOptions(); sockMgr.setDefaultOptions(sockMgr.buildOptions(props)); + // see TunnelController.setSessionOptions() + String h = props.getProperty(TunnelController.PROP_TARGET_HOST); + if (h != null) { + try { + remoteHost = InetAddress.getByName(h); + } catch (UnknownHostException uhe) { + l.log("Unknown host: " + h); + } + } + String p = props.getProperty(TunnelController.PROP_TARGET_PORT); + if (p != null) { + try { + int port = Integer.parseInt(p); + if (port > 0 && port <= 65535) + remotePort = port; + else + l.log("Bad port: " + port); + } catch (NumberFormatException nfe) { + l.log("Bad port: " + p); + } + } buildSocketMap(props); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 96fab6213..32b8b85af 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -487,6 +487,17 @@ public class TunnelController implements Logging { String proxies = getProxyList(); if (proxies != null) opts.setProperty(PROP_PROXIES, proxies); + // Ditto spoof host. Since 0.9.15. + String spoofhost = getSpoofedHost(); + if (spoofhost != null) + opts.setProperty(PROP_SPOOFED_HOST, spoofhost); + // Ditto target host/port. Since 0.9.15. + String targethost = getTargetHost(); + if (targethost != null) + opts.setProperty(PROP_TARGET_HOST, targethost); + String targetport = getTargetPort(); + if (targetport != null) + opts.setProperty(PROP_TARGET_PORT, targetport); _tunnel.setClientOptions(opts); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index cda31bdf6..30aee1eb4 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -258,6 +258,7 @@ public class IndexBean { // give the messages a chance to make it to the window try { Thread.sleep(1000); } catch (InterruptedException ie) {} // and give them something to look at in any case + // FIXME name will be HTML escaped twice return _("Starting tunnel") + ' ' + getTunnelName(_tunnel) + "..."; } @@ -271,6 +272,7 @@ public class IndexBean { // give the messages a chance to make it to the window try { Thread.sleep(1000); } catch (InterruptedException ie) {} // and give them something to look at in any case + // FIXME name will be HTML escaped twice return _("Stopping tunnel") + ' ' + getTunnelName(_tunnel) + "..."; } @@ -352,6 +354,7 @@ public class IndexBean { List msgs = doSave(); if (ksMsg != null) msgs.add(ksMsg); + // FIXME name will be HTML escaped twice return getMessages(msgs); } @@ -402,7 +405,8 @@ public class IndexBean { name = Long.toString(_context.clock().now()); } } - name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + _context.clock().now() + "-privkeys.dat"; + name = name.replace(' ', '_').replace(':', '_').replace("..", "_").replace('/', '_').replace('\\', '_'); + name = "i2ptunnel-deleted-" + name + '-' + _context.clock().now() + "-privkeys.dat"; File backupDir = new SecureFile(_context.getConfigDir(), TunnelController.KEY_BACKUP_DIR); File to; if (backupDir.isDirectory() || backupDir.mkdir()) @@ -451,13 +455,11 @@ public class IndexBean { } public boolean allowCSS() { - String css = _context.getProperty(PROP_CSS_DISABLED); - return (css == null); + return !_context.getBooleanProperty(PROP_CSS_DISABLED); } public boolean allowJS() { - String js = _context.getProperty(PROP_JS_DISABLED); - return (js == null); + return !_context.getBooleanProperty(PROP_JS_DISABLED); } public int getTunnelCount() { @@ -727,8 +729,9 @@ public class IndexBean { _name = (name != null ? name.trim() : null); } /** one line description */ - public void setDescription(String description) { - _description = (description != null ? description.trim() : null); + public void setNofilter_description(String description) { + // '#' will blow up DataHelper.storeProps() + _description = (description != null ? description.replace('#', ' ').trim() : null); } /** I2CP host the router is on, ignored when in router context */ public void setClientHost(String host) { diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index 05c091b19..8fa03c96c 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -79,7 +79,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; } - +
@@ -341,6 +341,23 @@ input.default { width: 1px; height: 1px; visibility: hidden; }

+ +
+ +
+
+ + class="tickbox" /> +
+ <% } // !streamrclient %> + +
+
+
-
- -
-
- - class="tickbox" /> -
- <% } // !streamrclient %> - -
-
-
- <% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType) || "socksirctunnel".equals(tunnelType) || "sockstunnel".equals(tunnelType)) { %>
<% } else { %>" /> - " /><% + " /><% } /* curPage 3 */ /* End page 3 */ %> @@ -484,7 +484,7 @@ - + <% if (!"streamrclient".equals(tunnelType)) { @@ -501,9 +501,9 @@ } if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType) || "sockstunnel".equals(tunnelType) || "socksirctunnel".equals(tunnelType)) { %> - + - <% + <% } if ("httpclient".equals(tunnelType)) { %> updateSources = new ArrayList(2); try { + // TODO SU3 updateSources.add(new URI(ConfigUpdateHelper.getNewsURL(_context))); } catch (URISyntaxException use) {} try { + // TODO + //updateSources.add(new URI(BACKUP_NEWS_URL_SU3)); updateSources.add(new URI(BACKUP_NEWS_URL)); } catch (URISyntaxException use) {} UpdateRunner update = new NewsFetcher(_context, _mgr, updateSources); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java index 48343ff19..729a4b14d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java @@ -59,7 +59,7 @@ public class ConfigClientsHandler extends FormHandler { } if (_action.equals(_("Install Plugin"))) { if (pluginsEnabled && - (_context.getBooleanProperty(ConfigClientsHelper.PROP_ENABLE_PLUGIN_INSTALL) || + (_context.getBooleanPropertyDefaultTrue(ConfigClientsHelper.PROP_ENABLE_PLUGIN_INSTALL) || isAdvanced())) installPlugin(); else diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index c9620cb7a..ab83bcbd2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -39,7 +39,12 @@ public class ConfigClientsHelper extends HelperBase { /** @since 0.9.14.1 */ public boolean isPluginInstallEnabled() { return PluginStarter.pluginsEnabled(_context) && - (_context.getBooleanProperty(PROP_ENABLE_PLUGIN_INSTALL) || isAdvanced()); + (_context.getBooleanPropertyDefaultTrue(PROP_ENABLE_PLUGIN_INSTALL) || isAdvanced()); + } + + /** @since 0.9.15 */ + public boolean isPluginUpdateEnabled() { + return !PluginStarter.getPlugins().isEmpty(); } /** @since 0.8.3 */ diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 6ff7f3564..e33ab2bc7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -32,6 +32,7 @@ public class ConfigUpdateHandler extends FormHandler { // public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD"; public static final String OLD_DEFAULT_NEWS_URL = "http://complication.i2p/news.xml"; public static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml"; + public static final String DEFAULT_NEWS_URL_SU3 = "http://echelon.i2p/i2p/news.su3"; public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency"; public static final long DEFAULT_REFRESH_FREQ = 36*60*60*1000l; public static final String DEFAULT_REFRESH_FREQUENCY = Long.toString(DEFAULT_REFRESH_FREQ); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java index 4b7281fd9..4f5daab18 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java @@ -32,6 +32,7 @@ public class ConfigUpdateHelper extends HelperBase { /** hack to replace the old news location with the new one, even if they have saved the update page at some point */ public static String getNewsURL(I2PAppContext ctx) { + // TODO SU3 String url = ctx.getProperty(ConfigUpdateHandler.PROP_NEWS_URL); if (url != null && !url.equals(ConfigUpdateHandler.OLD_DEFAULT_NEWS_URL)) return url; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java index 062ab73d5..e3845df9d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import net.i2p.I2PAppContext; +import net.i2p.crypto.SigType; import net.i2p.util.FileUtil; import net.i2p.util.VersionComparator; @@ -29,6 +30,17 @@ public class LogsHelper extends HelperBase { return Server.getVersion(); } + /** @since 0.9.15 */ + public String getUnavailableCrypto() { + StringBuilder buf = new StringBuilder(128); + for (SigType t : SigType.values()) { + if (!t.isAvailable()) { + buf.append("Crypto: ").append(t.toString()).append(" unavailable
"); + } + } + return buf.toString(); + } + /** * Does not call logManager.flush(); call getCriticalLogs() first to flush */ diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp index 8bbfef188..a6d049ed7 100644 --- a/apps/routerconsole/jsp/configclients.jsp +++ b/apps/routerconsole/jsp/configclients.jsp @@ -105,7 +105,10 @@ input.default { width: 1px; height: 1px; visibility: hidden; } " />
-<% if (clientshelper.showPlugins()) { %> +<% + if (clientshelper.showPlugins()) { + if (clientshelper.isPluginUpdateEnabled()) { +%>

<%=intl._("Plugin Configuration")%>

<%=intl._("The plugins listed below are started by the webConsole client.")%>

@@ -116,25 +119,47 @@ input.default { width: 1px; height: 1px; visibility: hidden; } " /> " />
- -<% if (clientshelper.isPluginInstallEnabled()) { %> +<% + } // pluginUpdateEnabled + if (clientshelper.isPluginInstallEnabled()) { +%>

<%=intl._("Plugin Installation")%>

<%=intl._("Look for available plugins on {0}.", "plugins.i2p")%> <%=intl._("To install a plugin, enter the download URL:")%> -

+

+<% + } // pluginInstallEnabled + if (clientshelper.isPluginInstallEnabled() || clientshelper.isPluginUpdateEnabled()) { +%> +
+<% + if (clientshelper.isPluginInstallEnabled()) { +%>


" /> " /> " /> -

- " /> -
+
<% - } // pluginInstallEnabled - } // showPlugins + } // pluginInstallEnabled +%> + +<% + if (clientshelper.isPluginUpdateEnabled()) { +%> +
+ " /> +
+<% + } // pluginUpdateEnabled +%> + +<% + } // pluginInstallEnabled || pluginUpdateEnabled + } // showPlugins %> diff --git a/apps/routerconsole/jsp/error500.jsp b/apps/routerconsole/jsp/error500.jsp index 2a965fc61..70e82e9d2 100644 --- a/apps/routerconsole/jsp/error500.jsp +++ b/apps/routerconsole/jsp/error500.jsp @@ -60,9 +60,10 @@

I2P version: <%=net.i2p.router.RouterVersion.FULL_VERSION%>
Java version: <%=System.getProperty("java.vendor")%> <%=System.getProperty("java.version")%> (<%=System.getProperty("java.runtime.name")%> <%=System.getProperty("java.runtime.version")%>)
-Wrapper version: <%=System.getProperty("wrapper.version", "none")%>
" /> + +Wrapper version: <%=System.getProperty("wrapper.version", "none")%>
Server version:
Servlet version: <%=getServletInfo()%>
Platform: <%=System.getProperty("os.name")%> <%=System.getProperty("os.arch")%> <%=System.getProperty("os.version")%>
diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp index a028d9334..99d518cbb 100644 --- a/apps/routerconsole/jsp/logs.jsp +++ b/apps/routerconsole/jsp/logs.jsp @@ -24,16 +24,18 @@

I2P version: <%=net.i2p.router.RouterVersion.FULL_VERSION%>
Java version: <%=System.getProperty("java.vendor")%> <%=System.getProperty("java.version")%> (<%=System.getProperty("java.runtime.name")%> <%=System.getProperty("java.runtime.version")%>)
-Wrapper version: <%=System.getProperty("wrapper.version", "none")%>
" /> + +Wrapper version: <%=System.getProperty("wrapper.version", "none")%>
Server version:
Servlet version: <%=getServletInfo()%>
Platform: <%=System.getProperty("os.name")%> <%=System.getProperty("os.arch")%> <%=System.getProperty("os.version")%>
Processor: <%=net.i2p.util.NativeBigInteger.cpuModel()%> (<%=net.i2p.util.NativeBigInteger.cpuType()%>)
Jbigi: <%=net.i2p.util.NativeBigInteger.loadStatus()%>
Encoding: <%=System.getProperty("file.encoding")%>
-Charset: <%=java.nio.charset.Charset.defaultCharset().name()%>

+Charset: <%=java.nio.charset.Charset.defaultCharset().name()%>
+

<%=intl._("Note that system information, log timestamps, and log messages may provide clues to your location; please review everything you include in a bug report.")%>

<%=intl._("Critical Logs")%>

diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java index 2de71035b..032f8117a 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java @@ -13,6 +13,7 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; /** @@ -314,10 +315,16 @@ class Packet { private void setFlags(int flags) { _flags = flags; } - /** the signature on the packet (only included if the flag for it is set) + /** + * The signature on the packet (only included if the flag for it is set) + * + * Warning, may be typed wrong on incoming packets for EdDSA + * before verifySignature() is called. + * * @return signature on the packet if the flag for signatures is set */ public Signature getOptionalSignature() { return _optionSignature; } + public void setOptionalSignature(Signature sig) { setFlag(FLAG_SIGNATURE_INCLUDED, sig != null); _optionSignature = sig; @@ -327,6 +334,7 @@ class Packet { * @return the sending Destination */ public Destination getOptionalFrom() { return _optionFrom; } + public void setOptionalFrom(Destination from) { setFlag(FLAG_FROM_INCLUDED, from != null); if (from == null) throw new RuntimeException("from is null!?"); @@ -340,6 +348,7 @@ class Packet { * @return How long the sender wants the recipient to wait before sending any more data in ms. */ public int getOptionalDelay() { return _optionDelay; } + public void setOptionalDelay(int delayMs) { if (delayMs > MAX_DELAY_REQUEST) _optionDelay = MAX_DELAY_REQUEST; @@ -507,20 +516,21 @@ class Packet { * @throws IllegalStateException */ private int writtenSize() { - int size = 0; - size += 4; // _sendStreamId.length; - size += 4; // _receiveStreamId.length; - size += 4; // sequenceNum - size += 4; // ackThrough + //int size = 0; + //size += 4; // _sendStreamId.length; + //size += 4; // _receiveStreamId.length; + //size += 4; // sequenceNum + //size += 4; // ackThrough + // size++; // nacks length + //size++; // resendDelay + //size += 2; // flags + //size += 2; // option size + int size = 22; + if (_nacks != null) { - size++; // nacks length // if max win is ever > 255, limit to 255 size += 4 * _nacks.length; - } else { - size++; // nacks length } - size++; // resendDelay - size += 2; // flags if (isFlagSet(FLAG_DELAY_REQUESTED)) size += 2; @@ -531,8 +541,6 @@ class Packet { if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) size += _optionSignature.length(); - size += 2; // option size - if (_payload != null) { size += _payload.getValid(); } @@ -632,6 +640,9 @@ class Packet { // super cheat for now, look for correct type, // assume no more options. If we add to the options // we will have to ask the manager. + // We will get this wrong for Ed25519, same length as P256... + // See verifySignature() below where we will recast the signature to + // the correct type if necessary int siglen = payloadBegin - cur; SigType type = null; for (SigType t : SigType.values()) { @@ -677,12 +688,27 @@ class Packet { if (buffer == null) buffer = new byte[size]; - int written = writePacket(buffer, 0, from.getSigningPublicKey().getType().getSigLen()); + SigningPublicKey spk = from.getSigningPublicKey(); + SigType type = spk.getType(); + if (type == null) { + Log l = ctx.logManager().getLog(Packet.class); + if (l.shouldLog(Log.WARN)) + l.warn("Unknown sig type in " + from + " cannot verify " + toString()); + return false; + } + int written = writePacket(buffer, 0, type.getSigLen()); if (written != size) { ctx.logManager().getLog(Packet.class).error("Written " + written + " size " + size + " for " + toString(), new Exception("moo")); return false; } - boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey()); + + // Fixup of signature if we guessed wrong on the type in readPacket(), which could happen + // on a close or reset packet where we have a signature without a FROM + if (type != _optionSignature.getType() && + type.getSigLen() == _optionSignature.length()) + _optionSignature = new Signature(type, _optionSignature.getData()); + + boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, spk); if (!ok) { Log l = ctx.logManager().getLog(Packet.class); if (l.shouldLog(Log.WARN)) diff --git a/build.properties b/build.properties index 8ee39009b..7479e174e 100644 --- a/build.properties +++ b/build.properties @@ -91,3 +91,9 @@ javac.version=1.6 # Optional properties used in tests to enable additional tools. #with.cobertura=/PATH/TO/cobertura.jar #with.clover=/PATH/TO/clover.jar + +### Bundle router infos ### +# Set to bundle router infos from your local I2P install in the package +#bundle.routerInfos=true +#bundle.routerInfos.count=200 +#bundle.routerInfos.i2pConfigDir=/PATH/TO/.i2p diff --git a/build.xml b/build.xml index 6405ddd67..f584aeab8 100644 --- a/build.xml +++ b/build.xml @@ -960,7 +960,7 @@ - + @@ -1017,6 +1017,9 @@ + + + @@ -1051,6 +1054,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -1237,11 +1260,6 @@ - -EnglishEnglish 中文 Deutsch Español diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index c199aca2b..3afd99de2 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -314,6 +314,7 @@ public class Router implements RouterClock.ClockShiftListener { if (f.exists()) SecureFileOutputStream.setPerms(f); } + CryptoChecker.warnUnavailableCrypto(_context); _routerInfo = null; _higherVersionSeen = false; diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index f2522cd4e..421ff4f63 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,10 +18,10 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 9; + public final static long BUILD = 16; /** for example "-test" */ - public final static String EXTRA = ""; + public final static String EXTRA = "-rc"; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA; public static void main(String args[]) { System.out.println("I2P Router version: " + FULL_VERSION); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java b/router/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java new file mode 100644 index 000000000..ebcde49c6 --- /dev/null +++ b/router/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java @@ -0,0 +1,249 @@ +package net.i2p.router.networkdb.kademlia; +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2003 and released into the public domain + * with no warranty of any kind, either expressed or implied. + * It probably won't make your computer catch on fire, or eat + * your children, but it might. Use at your own risk. + * + */ + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import gnu.getopt.Getopt; + +import net.i2p.I2PAppContext; +import net.i2p.data.Hash; +import net.i2p.data.RouterAddress; +import net.i2p.data.RouterInfo; +import net.i2p.router.transport.BadCountries; +import net.i2p.router.transport.GeoIP; +import net.i2p.util.FileUtil; + +/** + * Copy a random selection of 'count' router infos from configDir/netDb + * to 'toDir'. Skip your own router info, and old, hidden, unreachable, and + * introduced routers, and those from bad countries. + * + * Used in the build process. + * + * @since 0.9.15 + * + */ +public class BundleRouterInfos { + + /** + * Usage: PersistentDataStore -i configDir -o toDir -c count + * + * Copy a random selection of 'count' router infos from configDir/netDb + * to 'toDir'. Skip your own router info, and old, hidden, unreachable, and + * introduced routers, and those from bad countries. + * + * @since 0.9.15 + */ + public static void main(String[] args) { + Getopt g = new Getopt("PersistentDataStore", args, "i:o:c:"); + String in = System.getProperty("user.home") + "/.i2p"; + String out = "netDb"; + int count = 200; + boolean error = false; + int c; + while ((c = g.getopt()) != -1) { + switch (c) { + case 'i': + in = g.getOptarg(); + break; + + case 'o': + out = g.getOptarg(); + break; + + case 'c': + String scount = g.getOptarg(); + try { + count = Integer.parseInt(scount); + } catch (NumberFormatException nfe) { + error = true; + } + break; + + case '?': + case ':': + default: + error = true; + } + } + if (error) { + usage(); + System.exit(1); + } + + Properties props = new Properties(); + props.setProperty(GeoIP.PROP_GEOIP_DIR, System.getProperty("user.dir") + "/installer/resources"); + GeoIP geoIP = new GeoIP(new I2PAppContext(props)); + + File confDir = new File(in); + File dbDir = new File(confDir, "netDb"); + if (!dbDir.exists()) { + System.out.println("NetDB directory " + dbDir + " does not exist"); + System.exit(1); + } + File myFile = new File(confDir, "router.info"); + File toDir = new File(out); + toDir.mkdirs(); + InputStream fis = null; + Hash me = null; + try { + fis = new BufferedInputStream(new FileInputStream(myFile)); + RouterInfo ri = new RouterInfo(); + ri.readBytes(fis, true); // true = verify sig on read + me = ri.getIdentity().getHash(); + } catch (Exception e) { + //System.out.println("Can't determine our identity"); + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + + int routerCount = 0; + List toRead = new ArrayList(2048); + for (int j = 0; j < PersistentDataStore.B64.length(); j++) { + File subdir = new File(dbDir, PersistentDataStore.DIR_PREFIX + PersistentDataStore.B64.charAt(j)); + File[] files = subdir.listFiles(PersistentDataStore.RouterInfoFilter.getInstance()); + if (files == null) + continue; + routerCount += files.length; + for (int i = 0; i < files.length; i++) { + toRead.add(files[i]); + } + } + if (toRead.isEmpty()) { + System.out.println("No files to copy in " + dbDir); + System.exit(1); + } + Collections.shuffle(toRead); + int copied = 0; + long tooOld = System.currentTimeMillis() - 7*24*60*60*1000L; + Map ipMap = new HashMap(count); + for (File file : toRead) { + if (copied >= count) + break; + Hash key = PersistentDataStore.getRouterInfoHash(file.getName()); + if (key == null) { + System.out.println("Skipping bad " + file); + continue; + } + if (key.equals(me)) { + System.out.println("Skipping my RI"); + continue; + } + fis = null; + try { + fis = new BufferedInputStream(new FileInputStream(file)); + RouterInfo ri = new RouterInfo(); + ri.readBytes(fis, true); // true = verify sig on read + try { fis.close(); } catch (IOException ioe) {} + fis = null; + if (ri.getPublished() < tooOld) { + System.out.println("Skipping too old " + key); + continue; + } + if (ri.getCapabilities().contains("U")) { + System.out.println("Skipping unreachable " + key); + continue; + } + if (ri.getCapabilities().contains("K")) { + System.out.println("Skipping slow " + key); + continue; + } + Collection addrs = ri.getAddresses(); + if (addrs.isEmpty()) { + System.out.println("Skipping hidden " + key); + continue; + } + boolean hasIntro = false; + boolean hasIPv4 = false; + boolean dupIP = false; + for (RouterAddress addr : addrs) { + if ("SSU".equals(addr.getTransportStyle()) && addr.getOption("ihost0") != null) { + hasIntro = true; + break; + } + String host = addr.getHost(); + if (host != null && host.contains(".")) { + hasIPv4 = true; + geoIP.add(host); + String old = ipMap.put(host, file.getName()); + if (old != null && !old.equals(file.getName())) { + dupIP = true; + break; + } + } + } + if (dupIP) { + System.out.println("Skipping dup IP " + key); + continue; + } + if (hasIntro) { + System.out.println("Skipping introduced " + key); + continue; + } + if (!hasIPv4) { + System.out.println("Skipping IPv6-only " + key); + continue; + } + File toFile = new File(toDir, file.getName()); + // We could call ri.write() to avoid simultaneous change by the router + boolean ok = FileUtil.copy(file, toFile, true, true); + if (ok) + copied++; + else + System.out.println("Failed copy of " + file + " to " + toDir); + } catch (Exception e) { + System.out.println("Skipping bad " + file); + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } + if (copied > 0) { + // now do all the geoip lookups, and delete any bad countries + geoIP.blockingLookup(); + for (Map.Entry e : ipMap.entrySet()) { + String co = geoIP.get(e.getKey()); + if (co != null) { + if (BadCountries.contains(co)) { + String name = e.getValue(); + File toFile = new File(toDir, name); + if (toFile.delete()) { + String full = geoIP.fullName(co); + if (full == null) + full = co; + System.out.println("Skipping " + full + ": " + name); + copied--; + } + } + } + } + } + if (copied > 0) { + System.out.println("Copied " + copied + " router info files to " + toDir); + } else { + System.out.println("Failed to copy any files to " + toDir); + System.exit(1); + } + } + + private static void usage() { + System.err.println("Usage: PersistentDataStore [-i $HOME/.i2p] [-o netDb/] [-c 200]"); + } +} diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 2a7713cff..6c60e3a41 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -54,8 +54,8 @@ class PersistentDataStore extends TransientDataStore { private final static int READ_DELAY = 2*60*1000; private static final String PROP_FLAT = "router.networkDatabase.flat"; - private static final String DIR_PREFIX = "r"; - private static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"; + static final String DIR_PREFIX = "r"; + static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"; /** * @param dbDir relative path @@ -614,7 +614,7 @@ class PersistentDataStore extends TransientDataStore { return DIR_PREFIX + b64.charAt(0) + File.separatorChar + ROUTERINFO_PREFIX + b64 + ROUTERINFO_SUFFIX; } - private static Hash getRouterInfoHash(String filename) { + static Hash getRouterInfoHash(String filename) { return getHash(filename, ROUTERINFO_PREFIX, ROUTERINFO_SUFFIX); } @@ -651,7 +651,7 @@ class PersistentDataStore extends TransientDataStore { } } - private final static class RouterInfoFilter implements FilenameFilter { + static class RouterInfoFilter implements FilenameFilter { private static final FilenameFilter _instance = new RouterInfoFilter(); public static final FilenameFilter getInstance() { return _instance; } public boolean accept(File dir, String name) { 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 f2c6fb864..b25641e0a 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -74,8 +74,7 @@ public class Reseeder { "http://reseed.i2p-projekt.de/" + "," + //"http://euve5653.vserver.de/netDb/" + "," + "http://cowpuncher.drollette.com/netdb/" + "," + - //Temp disabled (#1351) - //"http://i2p.mooo.com/netDb/" + "," + + "http://i2p.mooo.com/netDb/" + "," + "http://193.150.121.66/netDb/" + "," + "http://netdb.i2p2.no/" + "," + "http://reseed.info/" + "," + @@ -93,8 +92,7 @@ public class Reseeder { "https://reseed.i2p-projekt.de/" + "," + //"https://euve5653.vserver.de/netDb/" + "," + "https://cowpuncher.drollette.com/netdb/" + "," + - //Temp disabled (#1351) - //"https://i2p.mooo.com/netDb/" + "," + + "https://i2p.mooo.com/netDb/" + "," + "https://193.150.121.66/netDb/" + "," + "https://netdb.i2p2.no/" + "," + "https://reseed.info/" + "," + diff --git a/router/java/src/net/i2p/router/startup/WorkingDir.java b/router/java/src/net/i2p/router/startup/WorkingDir.java index 15a3e7fb4..23149c759 100644 --- a/router/java/src/net/i2p/router/startup/WorkingDir.java +++ b/router/java/src/net/i2p/router/startup/WorkingDir.java @@ -271,6 +271,8 @@ public class WorkingDir { // We don't currently have a default addressbook/ in the base distribution, // but distros might put one in "addressbook,eepsite," + + // 0.9.15 support bundled router infos + "netDb," + // base install - files // We don't currently have a default router.config, logger.config, susimail.config, or webapps.config in the base distribution, // but distros might put one in diff --git a/router/java/src/net/i2p/router/tasks/CryptoChecker.java b/router/java/src/net/i2p/router/tasks/CryptoChecker.java new file mode 100644 index 000000000..9e110bd93 --- /dev/null +++ b/router/java/src/net/i2p/router/tasks/CryptoChecker.java @@ -0,0 +1,101 @@ +package net.i2p.router.tasks; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import net.i2p.crypto.SigType; +import net.i2p.router.RouterContext; +import net.i2p.util.Log; +import net.i2p.util.SystemVersion; + +/** + * Warn about unavailable crypto to router and wrapper logs + * + * @since 0.9.15 + */ +public class CryptoChecker { + + private static String JRE6 = "http://www.oracle.com/technetwork/java/javase/downloads/index.html"; + // these two are US-only and can change? + //private static String JRE7 = "http://www.oracle.com/technetwork/java/javase/documentation/java-se-7-doc-download-435117.html"; + //private static String JRE8 = "http://www.oracle.com/technetwork/java/javase/documentation/jdk8-doc-downloads-2133158.html"; + + /** + * @param ctx if null, logs only to System.out (called from main) + */ + public static void warnUnavailableCrypto(RouterContext ctx) { + if (SystemVersion.isAndroid()) + return; + boolean unavail = false; + Log log = null; + for (SigType t : SigType.values()) { + if (!t.isAvailable()) { + if (!unavail) { + unavail = true; + if (ctx != null) + log = ctx.logManager().getLog(CryptoChecker.class); + } + String s = "Crypto " + t + " is not available"; + if (log != null) + log.logAlways(log.WARN, s); + System.out.println("Warning: " + s); + } + } + if (unavail) { + if (!SystemVersion.isJava7()) { + String s = "Java version: " + System.getProperty("java.version") + " Please consider upgrading to Java 7"; + if (log != null) + log.logAlways(log.WARN, s); + System.out.println(s); + } + if (!isUnlimited()) { + String s = "Please consider installing the Java Cryptography Unlimited Strength Jurisdiction Policy Files from "; + //if (SystemVersion.isJava8()) + // s += JRE8; + //else if (SystemVersion.isJava7()) + // s += JRE7; + //else + s += JRE6; + if (log != null) + log.logAlways(log.WARN, s); + System.out.println(s); + } + String s = "This crypto will be required in a future release"; + if (log != null) + log.logAlways(log.WARN, s); + System.out.println("Warning: " + s); + } else if (ctx == null) { + // called from main() + System.out.println("All crypto available"); + } + } + + /** + * Copied from CryptixAESEngine + */ + private static boolean isUnlimited() { + try { + if (Cipher.getMaxAllowedKeyLength("AES") < 256) + return false; + } catch (NoSuchAlgorithmException e) { + return false; + } catch (NoSuchMethodError e) { + // JamVM, gij + try { + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key = new SecretKeySpec(new byte[32], "AES"); + cipher.init(Cipher.ENCRYPT_MODE, key); + } catch (GeneralSecurityException gse) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + warnUnavailableCrypto(null); + } +} + diff --git a/router/java/src/net/i2p/router/transport/BadCountries.java b/router/java/src/net/i2p/router/transport/BadCountries.java index 4a85f2644..dc848d859 100644 --- a/router/java/src/net/i2p/router/transport/BadCountries.java +++ b/router/java/src/net/i2p/router/transport/BadCountries.java @@ -9,7 +9,7 @@ import java.util.Set; * Maintain a list of bad places. * @since 0.8.13 */ -abstract class BadCountries { +public abstract class BadCountries { private static final Set _countries; diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java index 3f0990dd3..e379ce05f 100644 --- a/router/java/src/net/i2p/router/transport/GeoIP.java +++ b/router/java/src/net/i2p/router/transport/GeoIP.java @@ -16,6 +16,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import net.i2p.I2PAppContext; import net.i2p.data.Hash; import net.i2p.router.Router; import net.i2p.router.RouterContext; @@ -38,11 +39,9 @@ import net.i2p.util.Log; * * @author zzz */ -class GeoIP { +public class GeoIP { private final Log _log; - // change to test with main() - //private final I2PAppContext _context; - private final RouterContext _context; + private final I2PAppContext _context; private final Map _codeToName; /** code to itself to prevent String proliferation */ private final Map _codeCache; @@ -56,8 +55,10 @@ class GeoIP { private final AtomicBoolean _lock; private int _lookupRunCount; - //public GeoIP(I2PAppContext context) { - public GeoIP(RouterContext context) { + /** + * @param context RouterContext in production, I2PAppContext for testing only + */ + public GeoIP(I2PAppContext context) { _context = context; _log = context.logManager().getLog(GeoIP.class); _codeToName = new ConcurrentHashMap(512); @@ -71,6 +72,7 @@ class GeoIP { } static final String PROP_GEOIP_ENABLED = "routerconsole.geoip.enable"; + public static final String PROP_GEOIP_DIR = "geoip.dir"; static final String GEOIP_DIR_DEFAULT = "geoip"; static final String GEOIP_FILE_DEFAULT = "geoip.txt"; static final String COUNTRY_FILE_DEFAULT = "countries.txt"; @@ -187,7 +189,10 @@ class GeoIP { * */ private void readCountryFile() { - File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); + String geoDir = _context.getProperty(PROP_GEOIP_DIR, GEOIP_DIR_DEFAULT); + File geoFile = new File(geoDir); + if (!geoFile.isAbsolute()) + geoFile = new File(_context.getBaseDir(), geoDir); geoFile = new File(geoFile, COUNTRY_FILE_DEFAULT); if (!geoFile.exists()) { if (_log.shouldLog(Log.WARN)) @@ -246,7 +251,10 @@ class GeoIP { * */ private String[] readGeoIPFile(Long[] search) { - File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT); + String geoDir = _context.getProperty(PROP_GEOIP_DIR, GEOIP_DIR_DEFAULT); + File geoFile = new File(geoDir); + if (!geoFile.isAbsolute()) + geoFile = new File(_context.getBaseDir(), geoDir); geoFile = new File(geoFile, GEOIP_FILE_DEFAULT); if (!geoFile.exists()) { if (_log.shouldLog(Log.WARN)) @@ -300,24 +308,28 @@ class GeoIP { /** * Put our country code in the config, where others (such as Timestamper) can get it, * and it will be there next time at startup. + * + * Does nothing in I2PAppContext */ private void updateOurCountry() { - /**** comment out to test with main() */ - String oldCountry = _context.router().getConfigSetting(PROP_IP_COUNTRY); - Hash ourHash = _context.routerHash(); + if (! (_context instanceof RouterContext)) + return; + RouterContext ctx = (RouterContext) _context; + String oldCountry = ctx.router().getConfigSetting(PROP_IP_COUNTRY); + Hash ourHash = ctx.routerHash(); // we should always have a RouterInfo by now, but we had one report of an NPE here if (ourHash == null) return; - String country = _context.commSystem().getCountry(ourHash); + String country = ctx.commSystem().getCountry(ourHash); if (country != null && !country.equals(oldCountry)) { - _context.router().saveConfig(PROP_IP_COUNTRY, country); - if (_context.commSystem().isInBadCountry() && _context.getProperty(Router.PROP_HIDDEN_HIDDEN) == null) { + ctx.router().saveConfig(PROP_IP_COUNTRY, country); + if (ctx.commSystem().isInBadCountry() && ctx.getProperty(Router.PROP_HIDDEN_HIDDEN) == null) { String name = fullName(country); if (name == null) name = country; _log.logAlways(Log.WARN, "Setting hidden mode to protect you in " + name + ", you may override on the network configuration page"); - _context.router().rebuildRouterInfo(); + ctx.router().rebuildRouterInfo(); } } /****/ diff --git a/router/java/src/org/cybergarage/upnp/ControlPoint.java b/router/java/src/org/cybergarage/upnp/ControlPoint.java index a0e62360b..0da72f676 100644 --- a/router/java/src/org/cybergarage/upnp/ControlPoint.java +++ b/router/java/src/org/cybergarage/upnp/ControlPoint.java @@ -292,7 +292,14 @@ public class ControlPoint implements HTTPRequestListener DeviceList devList = new DeviceList(); int nRoots = devNodeList.size(); for (int n=0; n /dev/null 2>&1; then + echo "********* Trailing whitespace found in file $i *********" + FAIL=1 + fi + if grep '^\s' $i > /dev/null 2>&1; then + echo "********* Leading whitespace found in file $i *********" + FAIL=1 + fi done if [ -n "$FAIL" ]; then