forked from I2P_Developers/i2p.i2p
Compare commits
7 Commits
master
...
i2p.i2p.2.
Author | SHA1 | Date | |
---|---|---|---|
23a722d5d2 | |||
96e4337b09 | |||
2e451212fe | |||
a0c2952397 | |||
3f97e0cd75 | |||
8afd4c1853 | |||
e2a792c4c4 |
@ -6,12 +6,12 @@
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
@ -196,7 +196,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
Properties p = _context.getProperties();
|
||||
_clientOptions = p;
|
||||
_sessions = new CopyOnWriteArraySet<I2PSession>();
|
||||
|
||||
|
||||
addConnectionEventListener(lsnr);
|
||||
boolean gui = true;
|
||||
boolean checkRunByE = true;
|
||||
@ -359,13 +359,13 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
public List<I2PSession> getSessions() {
|
||||
if (_sessions.isEmpty())
|
||||
return Collections.emptyList();
|
||||
return new ArrayList<I2PSession>(_sessions);
|
||||
return new ArrayList<I2PSession>(_sessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param session null ok
|
||||
*/
|
||||
void addSession(I2PSession session) {
|
||||
void addSession(I2PSession session) {
|
||||
if (session == null) return;
|
||||
boolean added = _sessions.add(session);
|
||||
if (added && _log.shouldLog(Log.INFO))
|
||||
@ -375,27 +375,27 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
/**
|
||||
* @param session null ok
|
||||
*/
|
||||
void removeSession(I2PSession session) {
|
||||
void removeSession(I2PSession session) {
|
||||
if (session == null) return;
|
||||
boolean removed = _sessions.remove(session);
|
||||
if (removed && _log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + " session removed: " + session, new Exception());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic options used for clients and servers.
|
||||
* NOT a copy, Do NOT modify for per-connection options, make a copy.
|
||||
* @return non-null, NOT a copy, do NOT modify for per-connection options
|
||||
*/
|
||||
public Properties getClientOptions() { return _clientOptions; }
|
||||
|
||||
|
||||
/**
|
||||
* TunnelController that constructed this, or null.
|
||||
* @return controller or null
|
||||
* @since 0.9.48
|
||||
*/
|
||||
TunnelController getController() { return _controller; }
|
||||
|
||||
|
||||
private void addtask(I2PTunnelTask tsk) {
|
||||
tsk.setTunnel(this);
|
||||
if (tsk.isOpen()) {
|
||||
@ -445,6 +445,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
runClient(args, l);
|
||||
} else if ("httpclient".equals(cmdname)) {
|
||||
runHttpClient(args, l);
|
||||
} else if ("browserclient".equals(cmdname)) {
|
||||
runBrowserClient(args, l);
|
||||
} else if ("ircclient".equals(cmdname)) {
|
||||
runIrcClient(args, l);
|
||||
} else if ("sockstunnel".equals(cmdname)) {
|
||||
@ -528,11 +530,11 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
" streamrserver <port> <privkeyfile>\n" +
|
||||
" textserver <host> <port> <privkey>\n");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the extra I2CP options to use in any subsequent I2CP sessions.
|
||||
* Generic options used for clients and servers
|
||||
* Usage: "clientoptions[ key=value]*" .
|
||||
* Usage: "clientoptions[ key=value]*" .
|
||||
*
|
||||
* Sets the event "clientoptions_onResult" = "ok" after completion.
|
||||
*
|
||||
@ -755,7 +757,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
|
||||
String spoofedHost = args[2];
|
||||
|
||||
|
||||
privKeyFile = new File(args[3]);
|
||||
if (!privKeyFile.isAbsolute())
|
||||
privKeyFile = new File(_context.getConfigDir(), args[3]);
|
||||
@ -773,8 +775,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
return;
|
||||
} else {
|
||||
l.log("httpserver <host> <port> <spoofedhost> <privkeyfile>\n" +
|
||||
" creates an HTTP server that sends all incoming data\n"
|
||||
+ " of its destination to host:port., filtering the HTTP\n"
|
||||
" 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", Integer.valueOf(-1));
|
||||
}
|
||||
@ -968,7 +970,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an HTTP client on the given port number
|
||||
* Run an HTTP client on the given port number
|
||||
*
|
||||
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
@ -990,7 +992,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
if (clientPort <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
@ -1041,7 +1043,76 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a CONNECT client on the given port number
|
||||
* Run an BROWSER client on the given port number
|
||||
*
|
||||
* Sets the event "httpbrowserclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "httpbrowserclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
* Mutually exclusive with "Shared Clients"
|
||||
*
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runBrowserClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
int clientPort = -1;
|
||||
try {
|
||||
clientPort = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
if (clientPort <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
String proxy = "";
|
||||
if (args.length > 1) {
|
||||
if (Boolean.parseBoolean(args[1].trim())) {
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if ("false".equalsIgnoreCase(args[1].trim())) {
|
||||
if (args.length == 3)
|
||||
proxy = args[2];
|
||||
} else if (args.length == 3) {
|
||||
proxy = args[2];
|
||||
} else {
|
||||
proxy = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
// isShared not specified, default to false
|
||||
boolean isShared = false;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
I2PTunnelClientBase task = new I2PTunnelHTTPBrowserClient(clientPort, l, ownDest, proxy, this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = "Invalid I2PTunnel configuration to create an Browser Proxy connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + clientPort;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
|
||||
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
|
||||
// Otherwise, the tunnel stays up even though the port is down
|
||||
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
|
||||
// so this probably leaves the tunnel open if called from the CLI
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("browserclient <port> [<sharedClient>] [<proxy>]\n" +
|
||||
" Creates a HTTP client proxy on the specified port.\n" +
|
||||
" <sharedClient> (always false) Indicates if this client shares tunnels with other clients (true or false)\n" +
|
||||
" <proxy> (optional) Indicates a proxy server to be used\n" +
|
||||
" when trying to access an address out of the .i2p domain");
|
||||
notifyEvent("httpbrowserclientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a CONNECT client on the given port number
|
||||
*
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
@ -1057,7 +1128,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
@ -1105,7 +1176,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an IRC client on the given port number
|
||||
* Run an IRC client on the given port number
|
||||
*
|
||||
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
|
||||
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
|
||||
@ -1127,7 +1198,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
|
||||
boolean isShared = true;
|
||||
if (args.length > 2) {
|
||||
if (Boolean.parseBoolean(args[2].trim())) {
|
||||
@ -1168,9 +1239,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run an SOCKS tunnel on the given port number
|
||||
* Run an SOCKS tunnel on the given port number
|
||||
*
|
||||
* Sets the event "sockstunnelTaskId" = Integer(taskId) after the
|
||||
* tunnel has been started (or -1 on error). Also sets
|
||||
@ -1222,9 +1293,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Run an SOCKS IRC tunnel on the given port number
|
||||
* Run an SOCKS IRC tunnel on the given port number
|
||||
* @param args {portNumber [, sharedClient]} or (portNumber, ignored (false), privKeyFile)
|
||||
* @throws IllegalArgumentException on config problem
|
||||
* @since 0.7.12
|
||||
@ -1362,7 +1433,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the i2cp host and port
|
||||
* Specify the i2cp host and port
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* Sets the event "configResult" = "ok" or "error" after the configuration has been specified
|
||||
@ -1677,7 +1748,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
* Perform a lookup of the name specified
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* Sets the event "lookupResult" = base64 of the destination, or an error message
|
||||
* Sets the event "lookupResult" = base64 of the destination, or an error message
|
||||
*
|
||||
* @param args {name}
|
||||
* @param l logger to receive events and output
|
||||
@ -1844,7 +1915,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to actually close the given task number (optionally forcing
|
||||
* Helper method to actually close the given task number (optionally forcing
|
||||
* closure)
|
||||
*
|
||||
*/
|
||||
@ -1916,7 +1987,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new destination, storing the destination and its private keys where
|
||||
* Create a new destination, storing the destination and its private keys where
|
||||
* instructed.
|
||||
* Does NOT support non-default sig types.
|
||||
* Deprecated - only used by CLI
|
||||
@ -1980,8 +2051,8 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
/**
|
||||
* Generates a Destination from a name. Now only supports base64
|
||||
* names - may support naming servers later. "file:<filename>" is
|
||||
* also supported, where filename is a file that either contains a
|
||||
* binary Destination structure or the Base64 encoding of that
|
||||
* also supported, where filename is a file that either contains a
|
||||
* binary Destination structure or the Base64 encoding of that
|
||||
* structure.
|
||||
*
|
||||
* Since file:<filename> isn't really used, this method is deprecated,
|
||||
@ -2008,7 +2079,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
Log log = ctx.logManager().getLog(I2PTunnel.class);
|
||||
|
||||
|
||||
if (name.startsWith("file:")) {
|
||||
Destination result = new Destination();
|
||||
byte content[] = null;
|
||||
@ -2032,14 +2103,14 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
result.fromByteArray(content);
|
||||
return result;
|
||||
} catch (RuntimeException ex) {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("File is not a binary destination - trying base64");
|
||||
try {
|
||||
byte decoded[] = Base64.decode(new String(content));
|
||||
result.fromByteArray(decoded);
|
||||
return result;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("File is not a base64 destination either - failing!");
|
||||
return null;
|
||||
}
|
||||
@ -2078,7 +2149,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
session.connect();
|
||||
d = session.lookupDest(name);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Lookup via router failed", ise);
|
||||
} finally {
|
||||
if (session != null) {
|
||||
@ -2098,9 +2169,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
if (lsnr == null) return;
|
||||
listeners.remove(lsnr);
|
||||
}
|
||||
|
||||
|
||||
private String getPrefix() { return "[" + _tunnelId + "]: "; }
|
||||
|
||||
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,502 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.Outproxy;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.LookupResult;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.crypto.Blinding;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.BlindData;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.i2ptunnel.localServer.LocalHTTPServer;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
import net.i2p.util.DNSOverHTTPS;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.InternalSocket;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
/**
|
||||
* Act as a multiplexer of I2PTunnelHTTPClients with different ports on a
|
||||
* single port. Dynamically creates a new I2PTunnelHTTPClient on a per-host
|
||||
* basis. For each new host, it creates a new I2PTunnelHTTPClient. Each
|
||||
* I2PTunnelHTTPClient is used for requests from a single specific origin.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPBrowserClient extends I2PTunnelHTTPClient {
|
||||
HashMap<Destination, I2PTunnelHTTPClient> clients =
|
||||
new HashMap<Destination, I2PTunnelHTTPClient>();
|
||||
private InternalSocketRunner isr;
|
||||
private static final boolean DEFAULT_KEEPALIVE_BROWSER = true;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public I2PTunnelHTTPBrowserClient(int localPort, Logging l,
|
||||
I2PSocketManager sockMgr, I2PTunnel tunnel,
|
||||
EventDispatcher notifyThis, long clientId) {
|
||||
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
|
||||
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public I2PTunnelHTTPBrowserClient(int localPort, Logging l, boolean ownDest,
|
||||
String wwwProxy, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel)
|
||||
throws IllegalArgumentException {
|
||||
super(localPort, l, ownDest, wwwProxy, notifyThis, tunnel);
|
||||
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default options (using the default timeout, etc).
|
||||
* Warning, this does not make a copy of I2PTunnel's client options,
|
||||
* it modifies them directly.
|
||||
* unused?
|
||||
*
|
||||
* This will throw IAE on tunnel build failure
|
||||
*/
|
||||
@Override
|
||||
protected I2PSocketOptions getDefaultOptions() {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT)) {
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT,
|
||||
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
|
||||
}
|
||||
// if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
|
||||
// defaultOpts.setProperty("i2p.streaming.inactivityTimeout",
|
||||
// ""+DEFAULT_READ_TIMEOUT);
|
||||
// delayed start
|
||||
verifySocketManager();
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) {
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default options (using the default timeout, etc).
|
||||
* Warning, this does not make a copy of I2PTunnel's client options,
|
||||
* it modifies them directly.
|
||||
* Do not use overrides for per-socket options.
|
||||
*
|
||||
* This will throw IAE on tunnel build failure
|
||||
*/
|
||||
@Override
|
||||
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT)) {
|
||||
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT,
|
||||
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
|
||||
}
|
||||
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout")) {
|
||||
defaultOpts.setProperty("i2p.streaming.inactivityTimeout",
|
||||
"" + I2PTunnelHTTPClient.DEFAULT_READ_TIMEOUT);
|
||||
}
|
||||
// delayed start
|
||||
verifySocketManager();
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) {
|
||||
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually start working on incoming connections.
|
||||
* Overridden to start an internal socket too.
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void startRunning() {
|
||||
super.startRunning();
|
||||
if (open) {
|
||||
this.isr = new InternalSocketRunner(this);
|
||||
this.isr.start();
|
||||
int port = getLocalPort();
|
||||
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY_TABBED,
|
||||
getTunnel().listenHost, port);
|
||||
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY_TABBED,
|
||||
getTunnel().listenHost, port);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to close internal socket too.
|
||||
*/
|
||||
@Override
|
||||
public boolean close(boolean forced) {
|
||||
int port = getLocalPort();
|
||||
int reg = _context.portMapper().getPort(PortMapper.SVC_HTTP_PROXY_TABBED);
|
||||
if (reg == port) {
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTP_PROXY_TABBED);
|
||||
}
|
||||
reg = _context.portMapper().getPort(PortMapper.SVC_HTTPS_PROXY_TABBED);
|
||||
if (reg == port) {
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTPS_PROXY_TABBED);
|
||||
}
|
||||
boolean rv = super.close(forced);
|
||||
if (this.isr != null) {
|
||||
this.isr.stopRunning();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void mapPort(String hostname, int port) {
|
||||
_context.portMapper().register(PortMapper.SVC_HTTP_PROXY_TABBED + "@" +
|
||||
hostname,
|
||||
getTunnel().listenHost, port);
|
||||
_context.portMapper().register(PortMapper.SVC_HTTPS_PROXY_TABBED + "@" +
|
||||
hostname,
|
||||
getTunnel().listenHost, port);
|
||||
}
|
||||
|
||||
private void unmapPort(String hostname) {
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTP_PROXY_TABBED + "@" +
|
||||
hostname);
|
||||
_context.portMapper().unregister(PortMapper.SVC_HTTPS_PROXY_TABBED + "@" +
|
||||
hostname);
|
||||
}
|
||||
|
||||
private int findRandomOpenPort() throws IOException {
|
||||
try (ServerSocket socket = new ServerSocket(0);) {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPClient getI2PTunnelHTTPClient(URI uri) {
|
||||
String hostname = uri.getHost();
|
||||
return getI2PTunnelHTTPClient(hostname);
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPClient getI2PTunnelHTTPClient(String hostname) {
|
||||
if (hostname == null)
|
||||
return null;
|
||||
return clients.get(hostname);
|
||||
}
|
||||
|
||||
protected boolean mapNewClient(URI uri) {
|
||||
String hostname = uri.getHost();
|
||||
if (hostname == null)
|
||||
return false;
|
||||
Destination destination = _context.namingService().lookup(hostname);
|
||||
if (destination == null)
|
||||
return false;
|
||||
try {
|
||||
int port = findRandomOpenPort();
|
||||
I2PTunnelHTTPClient client = new I2PTunnelHTTPClient(
|
||||
port, l, _ownDest, hostname, getEventDispatcher(), getTunnel());
|
||||
clients.put(destination, client);
|
||||
getI2PTunnelHTTPClient(hostname).startRunning();
|
||||
mapPort(hostname, port);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to find a random open port", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean unmapClient(URI uri) {
|
||||
String hostname = uri.getHost();
|
||||
if (hostname == null)
|
||||
return false;
|
||||
getI2PTunnelHTTPClient(hostname).close(true);
|
||||
unmapPort(hostname);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
OutputStream out = null;
|
||||
|
||||
/**
|
||||
* The URL after fixup, always starting with http:// or https://
|
||||
*/
|
||||
String targetRequest = null;
|
||||
|
||||
// in-net outproxy
|
||||
boolean usingWWWProxy = false;
|
||||
// local outproxy plugin
|
||||
boolean usingInternalOutproxy = false;
|
||||
Outproxy outproxy = null;
|
||||
boolean usingInternalServer = false;
|
||||
String internalPath = null;
|
||||
String internalRawQuery = null;
|
||||
String currentProxy = null;
|
||||
long requestId = __requestId.incrementAndGet();
|
||||
boolean shout = false;
|
||||
boolean isConnect = false;
|
||||
boolean isHead = false;
|
||||
I2PSocket i2ps = null;
|
||||
try {
|
||||
s.setSoTimeout(INITIAL_SO_TIMEOUT);
|
||||
out = s.getOutputStream();
|
||||
InputReader reader = new InputReader(s.getInputStream());
|
||||
int requestCount = 0;
|
||||
// HTTP Persistent Connections (RFC 2616)
|
||||
// for the local browser-to-client-proxy socket.
|
||||
// Keep it very simple.
|
||||
// Will be set to false for non-GET/HEAD, non-HTTP/1.1,
|
||||
// Connection: close, InternalSocket,
|
||||
// or after analysis of the response headers in
|
||||
// HTTPResponseOutputStream, or on errors in I2PTunnelRunner.
|
||||
boolean keepalive =
|
||||
getBooleanOption(OPT_KEEPALIVE_BROWSER, DEFAULT_KEEPALIVE_BROWSER) &&
|
||||
!(s instanceof InternalSocket);
|
||||
|
||||
// indent
|
||||
do { // while (keepalive)
|
||||
// indent
|
||||
|
||||
if (requestCount > 0) {
|
||||
try {
|
||||
s.setSoTimeout(BROWSER_KEEPALIVE_TIMEOUT);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Socket closed before request #" + requestCount);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Keepalive, awaiting request #" + requestCount);
|
||||
}
|
||||
String line, method = null, protocol = null, host = null,
|
||||
destination = null;
|
||||
String hostLowerCase = null;
|
||||
StringBuilder newRequest = new StringBuilder();
|
||||
boolean ahelperPresent = false;
|
||||
boolean ahelperNew = false;
|
||||
String ahelperKey = null;
|
||||
String userAgent = null;
|
||||
String authorization = null;
|
||||
int remotePort = 0;
|
||||
String referer = null;
|
||||
URI origRequestURI = null;
|
||||
boolean preserveConnectionHeader = false;
|
||||
boolean allowGzip = false;
|
||||
while ((line = reader.readLine(method)) != null) {
|
||||
line = line.trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
|
||||
}
|
||||
|
||||
String lowercaseLine = line.toLowerCase(Locale.US);
|
||||
|
||||
if (method == null) {
|
||||
// first line GET/POST/etc.
|
||||
if (_log.shouldInfo())
|
||||
_log.info(getPrefix(requestId) + "req #" + requestCount +
|
||||
" first line [" + line + "]");
|
||||
|
||||
String[] params = DataHelper.split(line, " ", 3);
|
||||
if (params.length != 3) {
|
||||
break;
|
||||
}
|
||||
String request = params[1];
|
||||
|
||||
// various obscure fixups
|
||||
if (request.startsWith("/") &&
|
||||
getTunnel().getClientOptions().getProperty(
|
||||
"i2ptunnel.noproxy") != null) {
|
||||
// what is this for ???
|
||||
request = "http://i2p" + request;
|
||||
} else if (request.startsWith("/eepproxy/")) {
|
||||
// Deprecated
|
||||
// /eepproxy/foo.i2p/bar/baz.html
|
||||
String subRequest = request.substring("/eepproxy/".length());
|
||||
if (subRequest.indexOf('/') == -1) {
|
||||
subRequest += '/';
|
||||
}
|
||||
request = "http://" + subRequest;
|
||||
/****
|
||||
} else if
|
||||
(request.toLowerCase(Locale.US).startsWith("http://i2p/"))
|
||||
{
|
||||
// http://i2p/b64key/bar/baz.html
|
||||
// we can't do this now by setting the URI host to the
|
||||
b64key, as
|
||||
// it probably contains '=' and '~' which are illegal,
|
||||
// and a host may not include escaped octets
|
||||
// This will get undone below.
|
||||
String subRequest =
|
||||
request.substring("http://i2p/".length()); if
|
||||
(subRequest.indexOf("/") == -1) subRequest += "/";
|
||||
"http://" + "b64key/bar/baz.html"
|
||||
request = "http://" + subRequest;
|
||||
} else if
|
||||
(request.toLowerCase(Locale.US).startsWith("http://"))
|
||||
{
|
||||
// Unsupported
|
||||
// http://$b64key/...
|
||||
// This probably used to work, rewrite it so that
|
||||
// we can create a URI without illegal characters
|
||||
// This will get undone below.
|
||||
String oldPath = request.substring(7);
|
||||
int slash = oldPath.indexOf("/");
|
||||
if (slash < 0)
|
||||
slash = oldPath.length();
|
||||
if (slash >= 516 && !oldPath.substring(0,
|
||||
slash).contains(".")) request = "http://i2p/" +
|
||||
oldPath;
|
||||
****/
|
||||
}
|
||||
|
||||
method = params[0].toUpperCase(Locale.US);
|
||||
if (method.equals("HEAD")) {
|
||||
isHead = true;
|
||||
} else if (method.equals("CONNECT")) {
|
||||
// this makes things easier later, by spoofing a
|
||||
// protocol so the URI parser find the host and port
|
||||
// For in-net outproxy, will be fixed up below
|
||||
request = "https://" + request + '/';
|
||||
isConnect = true;
|
||||
keepalive = false;
|
||||
} else if (!method.equals("GET")) {
|
||||
// POST, PUT, ...
|
||||
keepalive = false;
|
||||
}
|
||||
|
||||
// Now use the Java URI parser
|
||||
// This will be the incoming URI but will then get modified
|
||||
// to be the outgoing URI (with http:// if going to
|
||||
// outproxy, otherwise without)
|
||||
URI requestURI = null;
|
||||
try {
|
||||
try {
|
||||
requestURI = new URI(request);
|
||||
} catch (URISyntaxException use) {
|
||||
// fixup []| in path/query not escaped by browsers,
|
||||
// see ticket #2130
|
||||
boolean error = true;
|
||||
// find 3rd /
|
||||
int idx = 0;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
idx = request.indexOf('/', idx);
|
||||
if (idx < 0)
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
if (idx > 0) {
|
||||
String schemeHostPort = request.substring(0, idx);
|
||||
String rest = request.substring(idx);
|
||||
// not escaped by all browsers, may be specific
|
||||
// to query, see ticket #2130
|
||||
rest = rest.replace("[", "%5B");
|
||||
rest = rest.replace("]", "%5D");
|
||||
rest = rest.replace("|", "%7C");
|
||||
rest = rest.replace("{", "%7B");
|
||||
rest = rest.replace("}", "%7D");
|
||||
String testRequest = schemeHostPort + rest;
|
||||
if (!testRequest.equals(request)) {
|
||||
try {
|
||||
requestURI = new URI(testRequest);
|
||||
request = testRequest;
|
||||
error = false;
|
||||
} catch (URISyntaxException use2) {
|
||||
// didn't work, give up
|
||||
}
|
||||
}
|
||||
}
|
||||
// guess it wasn't []|
|
||||
if (error)
|
||||
throw use;
|
||||
}
|
||||
origRequestURI = requestURI;
|
||||
String hostName = requestURI.getHost();
|
||||
if (hostName != null) {
|
||||
host = hostName;
|
||||
hostLowerCase = host.toLowerCase(Locale.US);
|
||||
}
|
||||
/**
|
||||
* This is where the host-specific logic happens
|
||||
*/
|
||||
} catch (URISyntaxException use) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn(getPrefix(requestId) + "Bad request [" + request +
|
||||
"]",
|
||||
use);
|
||||
}
|
||||
try {
|
||||
out.write(
|
||||
getErrorPage("baduri", ERR_BAD_URI).getBytes("UTF-8"));
|
||||
String msg = use.getLocalizedMessage();
|
||||
if (msg != null) {
|
||||
out.write(DataHelper.getASCII("<p>\n"));
|
||||
out.write(DataHelper.getUTF8(DataHelper.escapeHTML(msg)));
|
||||
out.write(DataHelper.getASCII("</p>\n"));
|
||||
}
|
||||
out.write(DataHelper.getASCII("</div>\n"));
|
||||
writeFooter(out);
|
||||
reader.drain();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (keepalive);
|
||||
} catch (IOException ex) {
|
||||
// This is normal for keepalive when the browser closed the socket,
|
||||
// or a SocketTimeoutException if we gave up first
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
}
|
||||
handleClientException(ex, out, targetRequest, usingWWWProxy, currentProxy,
|
||||
requestId);
|
||||
/*} catch(I2PException ex) {
|
||||
if(_log.shouldLog(Log.INFO)) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect",
|
||||
ex);
|
||||
}
|
||||
handleClientException(ex, out, targetRequest, usingWWWProxy,
|
||||
currentProxy, requestId);*/
|
||||
/*} catch(OutOfMemoryError oom) {
|
||||
IOException ex = new IOException("OOM");
|
||||
_log.error(getPrefix(requestId) + "Error trying to connect", oom);
|
||||
handleClientException(ex, out, targetRequest, usingWWWProxy,
|
||||
currentProxy, requestId);*/
|
||||
} finally {
|
||||
// only because we are running it inline
|
||||
closeSocket(s);
|
||||
if (i2ps != null)
|
||||
try {
|
||||
i2ps.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -192,7 +192,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
"The request uses a bad protocol. " +
|
||||
"The I2P HTTP Proxy supports HTTP and HTTPS requests only. Other protocols such as FTP are not allowed.<BR>";
|
||||
|
||||
private final static String ERR_BAD_URI =
|
||||
protected final static String ERR_BAD_URI =
|
||||
"HTTP/1.1 403 Bad URI\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-Control: no-cache\r\n" +
|
||||
@ -1687,7 +1687,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
|
||||
* Warning - DataHelper limits line length, BufferedReader does not
|
||||
* Todo: Limit line length for buffered reads, or go back to unbuffered for all
|
||||
*/
|
||||
private static class InputReader {
|
||||
protected static class InputReader {
|
||||
InputStream _s;
|
||||
|
||||
public InputReader(InputStream s) {
|
||||
|
@ -155,6 +155,7 @@ public class TunnelController implements Logging {
|
||||
public static final String TYPE_CONNECT = "connectclient";
|
||||
public static final String TYPE_HTTP_BIDIR_SERVER = "httpbidirserver";
|
||||
public static final String TYPE_HTTP_CLIENT = "httpclient";
|
||||
public static final String TYPE_BROWSER_HTTP_CLIENT = "browserclient";
|
||||
public static final String TYPE_HTTP_SERVER = "httpserver";
|
||||
public static final String TYPE_IRC_CLIENT = "ircclient";
|
||||
public static final String TYPE_IRC_SERVER = "ircserver";
|
||||
@ -470,6 +471,8 @@ public class TunnelController implements Logging {
|
||||
setSessionOptions();
|
||||
if (TYPE_HTTP_CLIENT.equals(type)) {
|
||||
startHttpClient();
|
||||
} else if (TYPE_BROWSER_HTTP_CLIENT.equals(type)) {
|
||||
startBrowserClient();
|
||||
} else if(TYPE_IRC_CLIENT.equals(type)) {
|
||||
startIrcClient();
|
||||
} else if(TYPE_SOCKS.equals(type)) {
|
||||
@ -519,6 +522,17 @@ public class TunnelController implements Logging {
|
||||
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
}
|
||||
|
||||
private void startBrowserClient() {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String proxyList = getProxyList();
|
||||
String sharedClient = getSharedClient();
|
||||
if (proxyList == null)
|
||||
_tunnel.runBrowserClient(new String[] { listenPort, sharedClient }, this);
|
||||
else
|
||||
_tunnel.runBrowserClient(new String[] { listenPort, sharedClient, proxyList }, this);
|
||||
}
|
||||
|
||||
private void startConnectClient() {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
@ -1072,6 +1086,7 @@ public class TunnelController implements Logging {
|
||||
public static boolean isClient(String type) {
|
||||
return TYPE_STD_CLIENT.equals(type) ||
|
||||
TYPE_HTTP_CLIENT.equals(type) ||
|
||||
TYPE_BROWSER_HTTP_CLIENT.equals(type) ||
|
||||
TYPE_SOCKS.equals(type) ||
|
||||
TYPE_SOCKS_IRC.equals(type) ||
|
||||
TYPE_CONNECT.equals(type) ||
|
||||
|
@ -493,6 +493,7 @@ public class IndexBean {
|
||||
public String getTypeName(String internalType) {
|
||||
if (TunnelController.TYPE_STD_CLIENT.equals(internalType)) return _t("Standard client");
|
||||
else if (TunnelController.TYPE_HTTP_CLIENT.equals(internalType)) return _t("HTTP/HTTPS client");
|
||||
else if (TunnelController.TYPE_BROWSER_HTTP_CLIENT.equals(internalType)) return _t("HTTP/HTTPS client");
|
||||
else if (TunnelController.TYPE_IRC_CLIENT.equals(internalType)) return _t("IRC client");
|
||||
else if (TunnelController.TYPE_STD_SERVER.equals(internalType)) return _t("Standard server");
|
||||
else if (TunnelController.TYPE_HTTP_SERVER.equals(internalType)) return _t("HTTP server");
|
||||
|
@ -375,6 +375,7 @@
|
||||
<select name="type">
|
||||
<option value="client"><%=intl._t("Standard")%></option>
|
||||
<option value="httpclient">HTTP/CONNECT</option>
|
||||
<option value="browserclient">Tabbed HTTP/CONNECT</option>
|
||||
<option value="ircclient">IRC</option>
|
||||
<option value="sockstunnel">SOCKS 4/4a/5</option>
|
||||
<option value="socksirctunnel">SOCKS IRC</option>
|
||||
|
@ -185,6 +185,11 @@
|
||||
<%=intl._t("Set your browser to use this tunnel as an http proxy, or set your \"http_proxy\" environment variable for command-line applications in GNU/Linux.")%>
|
||||
<%=intl._t("Websites outside I2P can also be reached if an HTTP proxy within I2P is known.")%>
|
||||
</td></tr>
|
||||
<tr><td><%=intl._t("Tabbed")%> HTTP/HTTPS</td><td>
|
||||
<%=intl._t("Like the HTTP/HTTPS tunnel, but designed to isolate each new origin under a new destination.")%>
|
||||
<%=intl._t("Especially useful when combined with a browser which displays multiple tabs.")%>
|
||||
<%=intl._t("Set your browser to use this tunnel as an http proxy, or set your \"http_proxy\" environment variable for command-line applications in GNU/Linux.")%>
|
||||
</td></tr>
|
||||
<tr><td>IRC</td><td>
|
||||
<%=intl._t("Customized client tunnel specific for IRC connections.")%>
|
||||
<%=intl._t("With this tunnel type, your IRC client will be able to connect to an IRC network inside I2P.")%>
|
||||
|
@ -29,6 +29,8 @@ public class PortMapper {
|
||||
public static final String SVC_HTTPS_CONSOLE = "https_console";
|
||||
public static final String SVC_HTTP_PROXY = "HTTP";
|
||||
public static final String SVC_HTTPS_PROXY = "HTTPS";
|
||||
public static final String SVC_HTTP_PROXY_TABBED = "HTTP_TABBED";
|
||||
public static final String SVC_HTTPS_PROXY_TABBED = "HTTPS_TABBED";
|
||||
public static final String SVC_EEPSITE = "eepsite";
|
||||
/** @since 0.9.34 */
|
||||
public static final String SVC_HTTPS_EEPSITE = "https_eepsite";
|
||||
|
Reference in New Issue
Block a user