diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index 3d4422690..775e61dd2 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -229,6 +229,8 @@ public class I2PTunnel implements Logging, EventDispatcher { runClientOptions(args, l); } else if ("server".equals(cmdname)) { runServer(args, l); + } else if ("httpserver".equals(cmdname)) { + runHttpServer(args, l); } else if ("textserver".equals(cmdname)) { runTextServer(args, l); } else if ("client".equals(cmdname)) { @@ -281,6 +283,7 @@ public class I2PTunnel implements Logging, EventDispatcher { l.log("owndest yes|no"); l.log("ping "); l.log("server "); + l.log("httpserver "); l.log("textserver "); l.log("genkeys []"); l.log("gentextkeys"); @@ -370,6 +373,65 @@ public class I2PTunnel implements Logging, EventDispatcher { } } + /** + * Run the HTTP server pointing at the host and port specified using the private i2p + * destination loaded from the specified file, replacing the HTTP headers + * so that the Host: specified is the one spoofed.

+ * + * Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error) + * Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after + * 'ok'). So, success = serverTaskId != -1 and openServerResult = ok. + * + * @param args {hostname, portNumber, spoofedHost, privKeyFilename} + * @param l logger to receive events and output + */ + public void runHttpServer(String args[], Logging l) { + if (args.length == 4) { + InetAddress serverHost = null; + int portNum = -1; + File privKeyFile = null; + try { + serverHost = InetAddress.getByName(args[0]); + } catch (UnknownHostException uhe) { + l.log("unknown host"); + _log.error(getPrefix() + "Error resolving " + args[0], uhe); + notifyEvent("serverTaskId", new Integer(-1)); + return; + } + + try { + portNum = Integer.parseInt(args[1]); + } catch (NumberFormatException nfe) { + l.log("invalid port"); + _log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe); + notifyEvent("serverTaskId", new Integer(-1)); + return; + } + + String spoofedHost = args[2]; + + privKeyFile = new File(args[3]); + if (!privKeyFile.canRead()) { + l.log("private key file does not exist"); + _log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]); + notifyEvent("serverTaskId", new Integer(-1)); + return; + } + I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this); + serv.setReadTimeout(readTimeout); + serv.startRunning(); + addtask(serv); + notifyEvent("serverTaskId", new Integer(serv.getId())); + return; + } else { + l.log("httpserver "); + l.log(" creates an HTTP server that sends all incoming data\n" + + " of its destination to host:port., filtering the HTTP\n" + + " headers so it looks like the request is to the spoofed host."); + notifyEvent("serverTaskId", new Integer(-1)); + } + } + /** * Run the server pointing at the host and port specified using the private i2p * destination loaded from the given base64 stream.

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java new file mode 100644 index 000000000..fa394248a --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -0,0 +1,162 @@ +/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java) + * (c) 2003 - 2004 mihi + */ +package net.i2p.i2ptunnel; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.Iterator; +import java.util.Properties; + +import net.i2p.I2PAppContext; +import net.i2p.I2PException; +import net.i2p.client.streaming.I2PServerSocket; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketManager; +import net.i2p.data.DataHelper; +import net.i2p.util.EventDispatcher; +import net.i2p.util.I2PThread; +import net.i2p.util.Log; + +/** + * Simple extension to the I2PTunnelServer that filters the HTTP + * headers sent from the client to the server, replacing the Host + * header with whatever this instance has been configured with. + * + */ +public class I2PTunnelHTTPServer extends I2PTunnelServer { + private final static Log _log = new Log(I2PTunnelHTTPServer.class); + /** what Host: should we seem to be to the webserver? */ + private String _spoofHost; + + public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { + super(host, port, privData, l, notifyThis, tunnel); + _spoofHost = spoofHost; + } + + public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { + super(host, port, privkey, privkeyname, l, notifyThis, tunnel); + _spoofHost = spoofHost; + } + + public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { + super(host, port, privData, privkeyname, l, notifyThis, tunnel); + _spoofHost = spoofHost; + } + + public void run() { + try { + I2PServerSocket i2pss = sockMgr.getServerSocket(); + while (true) { + I2PSocket i2ps = i2pss.accept(); + if (i2ps == null) throw new I2PException("I2PServerSocket closed"); + I2PThread t = new I2PThread(new Handler(i2ps)); + t.start(); + } + } catch (I2PException ex) { + _log.error("Error while waiting for I2PConnections", ex); + } catch (IOException ex) { + _log.error("Error while waiting for I2PConnections", ex); + } + } + + /** + * Async handler to keep .accept() from blocking too long. + * todo: replace with a thread pool so we dont get overrun by threads if/when + * receiving a lot of connection requests concurrently. + * + */ + private class Handler implements Runnable { + private I2PSocket _handleSocket; + public Handler(I2PSocket socket) { + _handleSocket = socket; + } + public void run() { + long afterAccept = I2PAppContext.getGlobalContext().clock().now(); + long afterSocket = -1; + + //local is fast, so synchronously. Does not need that many + //threads. + try { + _handleSocket.setReadTimeout(readTimeout); + String modifiedHeader = getModifiedHeader(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Modified header: [" + modifiedHeader + "]"); + + Socket s = new Socket(remoteHost, remotePort); + afterSocket = I2PAppContext.getGlobalContext().clock().now(); + new I2PTunnelRunner(s, _handleSocket, slock, null, modifiedHeader.getBytes(), null); + } catch (SocketException ex) { + try { + _handleSocket.close(); + } catch (IOException ioe) { + _log.error("Error while closing the received i2p con", ex); + } + } catch (IOException ex) { + _log.error("Error while waiting for I2PConnections", ex); + } + + long afterHandle = I2PAppContext.getGlobalContext().clock().now(); + long timeToHandle = afterHandle - afterAccept; + if (timeToHandle > 1000) + _log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + + (afterSocket-afterAccept) + "]"); + } + private String getModifiedHeader() throws IOException { + InputStream in = _handleSocket.getInputStream(); + + StringBuffer command = new StringBuffer(128); + Properties headers = readHeaders(in, command); + headers.setProperty("Host", _spoofHost); + headers.setProperty("Connection", "close"); + return formatHeaders(headers, command); + } + } + + private String formatHeaders(Properties headers, StringBuffer command) { + StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64); + buf.append(command.toString()).append('\n'); + for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + String val = headers.getProperty(name); + buf.append(name).append(": ").append(val).append('\n'); + } + buf.append('\n'); + return buf.toString(); + } + + private Properties readHeaders(InputStream in, StringBuffer command) throws IOException { + Properties headers = new Properties(); + StringBuffer buf = new StringBuffer(128); + + boolean ok = DataHelper.readLine(in, command); + if (!ok) throw new IOException("EOF reached while reading the HTTP command [" + command.toString() + "]"); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Read the http command [" + command.toString() + "]"); + + while (true) { + buf.setLength(0); + ok = DataHelper.readLine(in, buf); + if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]"); + if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) { + // end of headers reached + return headers; + } else { + int split = buf.indexOf(": "); + if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]"); + String name = buf.substring(0, split); + String value = buf.substring(split+2); // ": " + headers.setProperty(name, value); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Read the header [" + name + "] = [" + value + "]"); + } + } + } +} + diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java index 6b169f6ca..fa5a7ce3d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java @@ -40,7 +40,8 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL Object slock, finishLock = new Object(); boolean finished = false; HashMap ostreams, sockets; - byte[] initialData; + byte[] initialI2PData; + byte[] initialSocketData; /** when the last data was sent/received (or -1 if never) */ private long lastActivityOn; /** when the runner started up */ @@ -53,15 +54,22 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL private volatile long __forwarderId; - public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList) { - this(s, i2ps, slock, initialData, sockList, null); + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList) { + this(s, i2ps, slock, initialI2PData, null, sockList, null); } - public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialData, List sockList, Runnable onTimeout) { + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList) { + this(s, i2ps, slock, initialI2PData, initialSocketData, sockList, null); + } + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List sockList, Runnable onTimeout) { + this(s, i2ps, slock, initialI2PData, null, sockList, onTimeout); + } + public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, byte[] initialSocketData, List sockList, Runnable onTimeout) { this.sockList = sockList; this.s = s; this.i2ps = i2ps; this.slock = slock; - this.initialData = initialData; + this.initialI2PData = initialI2PData; + this.initialSocketData = initialSocketData; this.onTimeout = onTimeout; lastActivityOn = -1; startedOn = Clock.getInstance().now(); @@ -111,15 +119,19 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL i2ps.setSocketErrorListener(this); InputStream i2pin = i2ps.getInputStream(); OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE); - if (initialData != null) { + if (initialI2PData != null) { synchronized (slock) { - i2pout.write(initialData); + i2pout.write(initialI2PData); //i2pout.flush(); } } + if (initialSocketData != null) { + out.write(initialSocketData); + } if (_log.shouldLog(Log.DEBUG)) - _log.debug("Initial data " + (initialData != null ? initialData.length : 0) - + " written, starting forwarders"); + _log.debug("Initial data " + (initialI2PData != null ? initialI2PData.length : 0) + + " written to I2P, " + (initialSocketData != null ? initialSocketData.length : 0) + + " written to the socket, starting forwarders"); Thread t1 = new StreamForwarder(in, i2pout, true); Thread t2 = new StreamForwarder(i2pin, out, false); synchronized (finishLock) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index a214a66b0..a1bec018a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -31,19 +31,20 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { private final static Log _log = new Log(I2PTunnelServer.class); - private I2PSocketManager sockMgr; - private I2PServerSocket i2pss; + protected I2PSocketManager sockMgr; + protected I2PServerSocket i2pss; - private Object lock = new Object(), slock = new Object(); + private Object lock = new Object(); + protected Object slock = new Object(); - private InetAddress remoteHost; - private int remotePort; + protected InetAddress remoteHost; + protected int remotePort; private Logging l; private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000; /** default timeout to 3 minutes - override if desired */ - private long readTimeout = DEFAULT_READ_TIMEOUT; + protected long readTimeout = DEFAULT_READ_TIMEOUT; public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) { super(host + ":" + port + " <- " + privData, notifyThis, tunnel); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 219ad195b..9ab848f5b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -57,7 +57,7 @@ public class TunnelController implements Logging { setConfig(config, prefix); _messages = new ArrayList(4); _running = false; - if (createKey && ("server".equals(getType())) ) + if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) ) createPrivateKey(); _starting = getStartOnLoad(); } @@ -132,6 +132,8 @@ public class TunnelController implements Logging { startClient(); } else if ("server".equals(type)) { startServer(); + } else if ("httpserver".equals(type)) { + startHttpServer(); } else { if (_log.shouldLog(Log.ERROR)) _log.error("Cannot start tunnel - unknown type [" + type + "]"); @@ -206,6 +208,18 @@ public class TunnelController implements Logging { _running = true; } + private void startHttpServer() { + setI2CPOptions(); + setSessionOptions(); + String targetHost = getTargetHost(); + String targetPort = getTargetPort(); + String spoofedHost = getSpoofedHost(); + String privKeyFile = getPrivKeyFile(); + _tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this); + acquire(); + _running = true; + } + private void setListenOn() { String listenOn = getListenOnInterface(); if ( (listenOn != null) && (listenOn.length() > 0) ) { @@ -297,6 +311,7 @@ public class TunnelController implements Logging { public String getListenOnInterface() { return _config.getProperty("interface"); } public String getTargetHost() { return _config.getProperty("targetHost"); } public String getTargetPort() { return _config.getProperty("targetPort"); } + public String getSpoofedHost() { return _config.getProperty("spoofedHost"); } public String getPrivKeyFile() { return _config.getProperty("privKeyFile"); } public String getListenPort() { return _config.getProperty("listenPort"); } public String getTargetDestination() { return _config.getProperty("targetDestination"); } @@ -314,6 +329,8 @@ public class TunnelController implements Logging { getClientSummary(buf); else if ("server".equals(type)) getServerSummary(buf); + else if ("httpserver".equals(type)) + getHttpServerSummary(buf); else buf.append("Unknown type ").append(type); } @@ -367,6 +384,18 @@ public class TunnelController implements Logging { getOptionSummary(buf); } + private void getHttpServerSummary(StringBuffer buf) { + String description = getDescription(); + if ( (description != null) && (description.trim().length() > 0) ) + buf.append("").append(description).append("
\n"); + buf.append("Server tunnel pointing at port ").append(getTargetPort()); + buf.append(" on ").append(getTargetHost()); + buf.append(" for the site ").append(getSpoofedHost()); + buf.append("
\n"); + buf.append("Private destination loaded from ").append(getPrivKeyFile()).append("
\n"); + getOptionSummary(buf); + } + private void getOptionSummary(StringBuffer buf) { String opts = getClientOptions(); if ( (opts != null) && (opts.length() > 0) ) @@ -378,7 +407,7 @@ public class TunnelController implements Logging { Destination dest = session.getMyDestination(); if (dest != null) { buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("
\n"); - if ("server".equals(getType())) { + if ( ("server".equals(getType())) || ("httpserver".equals(getType())) ) { buf.append("Full destination: "); buf.append("\n"); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java index 4e46eef30..07e28ac2b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageFormGenerator.java @@ -18,6 +18,7 @@ class WebEditPageFormGenerator { "" + "" + "" + + "" + " " + "\n"; @@ -42,6 +43,8 @@ class WebEditPageFormGenerator { return getEditClientForm(controller, id); else if ("server".equals(type)) return getEditServerForm(controller, id); + else if ("httpserver".equals(type)) + return getEditHttpServerForm(controller, id); else return "WTF, unknown type [" + type + "]"; } @@ -129,6 +132,48 @@ class WebEditPageFormGenerator { return buf.toString(); } + private static String getEditHttpServerForm(TunnelController controller, String id) { + StringBuffer buf = new StringBuffer(1024); + addGeneral(buf, controller, id); + buf.append("Type: HTTP server tunnel
\n"); + + buf.append("Target host:
\n"); + + buf.append("Target port:
\n"); + + buf.append("Website hostname:
\n"); + + buf.append("Private key file:
"); + } else { + buf.append("myServer.privKey\" />
"); + buf.append(""); + } + + addOptions(buf, controller); + buf.append("\n"); + buf.append("\n"); + buf.append(" confirm removal: \n"); + buf.append("\n"); + return buf.toString(); + } + /** * Start off the form and add some common fields (name, num, description) * diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java index a8e002497..44463145e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/WebEditPageHelper.java @@ -38,6 +38,7 @@ public class WebEditPageHelper { private String _targetDestination; private String _targetHost; private String _targetPort; + private String _spoofedHost; private String _privKeyFile; private boolean _startOnLoad; private boolean _privKeyGenerate; @@ -139,6 +140,10 @@ public class WebEditPageHelper { public void setTargetPort(String port) { _targetPort = (port != null ? port.trim() : null); } + /** What host does this http server tunnel spoof */ + public void setSpoofedHost(String host) { + _spoofedHost = (host != null ? host.trim() : null); + } /** What filename is this server tunnel's private keys stored in */ public void setPrivKeyFile(String file) { _privKeyFile = (file != null ? file.trim() : null); @@ -320,6 +325,15 @@ public class WebEditPageHelper { config.setProperty("targetPort", _targetPort); if (_privKeyFile != null) config.setProperty("privKeyFile", _privKeyFile); + } else if ("httpserver".equals(_type)) { + if (_targetHost != null) + config.setProperty("targetHost", _targetHost); + if (_targetPort != null) + config.setProperty("targetPort", _targetPort); + if (_privKeyFile != null) + config.setProperty("privKeyFile", _privKeyFile); + if (_spoofedHost != null) + config.setProperty("spoofedHost", _spoofedHost); } else { return null; } diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index babfca1e3..13bfd8a74 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -25,6 +25,7 @@ + diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 77527f510..6ff2febe0 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -680,7 +680,19 @@ public class DataHelper { */ public static String readLine(InputStream in) throws IOException { StringBuffer buf = new StringBuffer(128); - + boolean ok = readLine(in, buf); + if (ok) + return buf.toString(); + else + return null; + } + /** + * Read in a line, placing it into the buffer (excluding the newline). + * + * @return true if the line was read, false if eof was reached before a + * newline was found + */ + public static boolean readLine(InputStream in, StringBuffer buf) throws IOException { int c = -1; while ( (c = in.read()) != -1) { if (c == '\n') @@ -688,9 +700,9 @@ public class DataHelper { buf.append((char)c); } if (c == -1) - return null; + return false; else - return buf.toString(); + return true; } diff --git a/history.txt b/history.txt index 559edd10e..9ad0ef2dd 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,10 @@ -$Id: history.txt,v 1.111 2004/12/17 23:07:13 jrandom Exp $ +$Id: history.txt,v 1.112 2004/12/19 01:25:27 scintilla Exp $ + +2004-12-19 jrandom + * Added a new i2ptunnel type: 'httpserver', allowing you to specify what + hostname should be sent to the webserver. By default, new installs will + have an httpserver pointing at their jetty instance with the spoofed + name 'mysite.i2p' (editable on the /i2ptunnel/edit.jsp page). 2004-12-19 scintilla * Convert native jcpuid code from C++ to C. This should alleviate build diff --git a/installer/resources/i2ptunnel.config b/installer/resources/i2ptunnel.config index 96fc30cdf..f76deda0d 100644 --- a/installer/resources/i2ptunnel.config +++ b/installer/resources/i2ptunnel.config @@ -42,9 +42,10 @@ tunnel.2.startOnLoad=false # local eepserver tunnel.3.name=eepsite tunnel.3.description=My eepsite -tunnel.3.type=server +tunnel.3.type=httpserver tunnel.3.targetHost=127.0.0.1 tunnel.3.targetPort=7658 +tunnel.3.spoofedHost=mysite.i2p tunnel.3.privKeyFile=eepsite/eepPriv.dat tunnel.3.i2cpHost=127.0.0.1 tunnel.3.i2cpPort=7654 diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 4ff287151..92e9f13ac 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.116 $ $Date: 2004/12/17 23:07:13 $"; + public final static String ID = "$Revision: 1.117 $ $Date: 2004/12/19 01:25:27 $"; public final static String VERSION = "0.4.2.4"; - public final static long BUILD = 1; + public final static long BUILD = 2; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION); System.out.println("Router ID: " + RouterVersion.ID);