From cdab6f8b76eaabe69837bad2f792a6ef1bcf115f Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Fri, 12 Aug 2016 16:41:42 +0000 Subject: [PATCH] i2ptunnel: Add outproxy plugin support to SOCKS (ticket #1824) Make some classes package private Move some fields to SocksServer superclass --- .../i2ptunnel/socks/I2PSOCKSIRCTunnel.java | 2 +- .../i2p/i2ptunnel/socks/I2PSOCKSTunnel.java | 2 +- .../i2p/i2ptunnel/socks/SOCKS4aServer.java | 78 ++++++------ .../net/i2p/i2ptunnel/socks/SOCKS5Server.java | 68 +++++----- .../i2p/i2ptunnel/socks/SOCKSException.java | 7 +- .../net/i2p/i2ptunnel/socks/SOCKSServer.java | 43 ++++++- .../i2ptunnel/socks/SOCKSServerFactory.java | 9 +- .../i2p/i2ptunnel/socks/SocketWrapper.java | 119 ++++++++++++++++++ 8 files changed, 256 insertions(+), 72 deletions(-) create mode 100644 apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java index 8484ef0c23..8b9f705919 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSIRCTunnel.java @@ -47,7 +47,7 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel { protected void clientConnectionRun(Socket s) { try { //_log.error("SOCKS IRC Tunnel Start"); - SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions()); + SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions()); Socket clientSock = serv.getClientSocket(); I2PSocket destSock = serv.getDestinationI2PSocket(this); StringBuffer expectedPong = new StringBuffer(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java index 9167939417..ab6b7a7c1c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -48,7 +48,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { protected void clientConnectionRun(Socket s) { try { - SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions()); + SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(_context, s, getTunnel().getClientOptions()); Socket clientSock = serv.getClientSocket(); I2PSocket destSock = serv.getDestinationI2PSocket(this); Thread t = new I2PTunnelRunner(clientSock, destSock, sockLock, null, null, mySockets, diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java index 1a9586b6ef..f32cdabfc0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS4aServer.java @@ -19,6 +19,9 @@ import java.util.Properties; 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.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.DataFormatException; @@ -32,10 +35,8 @@ import net.i2p.util.Log; * * @author zzz modded from SOCKS5Server */ -public class SOCKS4aServer extends SOCKSServer { - private final Log _log; +class SOCKS4aServer extends SOCKSServer { - private final Socket clientSock; private boolean setupCompleted; /** @@ -49,15 +50,12 @@ public class SOCKS4aServer extends SOCKSServer { * @param clientSock client socket * @param props non-null */ - public SOCKS4aServer(Socket clientSock, Properties props) { - this.clientSock = clientSock; - this.props = props; - _log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS4aServer.class); + public SOCKS4aServer(I2PAppContext ctx, Socket clientSock, Properties props) { + super(ctx, clientSock, props); } public Socket getClientSocket() throws SOCKSException { setupServer(); - return clientSock; } @@ -211,12 +209,8 @@ public class SOCKS4aServer extends SOCKSServer { I2PSocket destSock; try { - if (connHostName.toLowerCase(Locale.US).endsWith(".i2p") || - connHostName.toLowerCase(Locale.US).endsWith(".onion")) { - // Let's not do a new Dest for every request, huh? - //I2PSocketManager sm = I2PSocketManagerFactory.createManager(); - //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null); - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName); + if (connHostName.toLowerCase(Locale.US).endsWith(".i2p")) { + Destination dest = _context.namingService().lookup(connHostName); if (dest == null) { try { sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); @@ -236,6 +230,7 @@ public class SOCKS4aServer extends SOCKSServer { sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); } catch (IOException ioe) {} throw new SOCKSException(err); + /**** } else if (connPort == 80) { // rewrite GET line to include hostname??? or add Host: line??? // or forward to local eepProxy (but that's a Socket not an I2PSocket) @@ -246,30 +241,43 @@ public class SOCKS4aServer extends SOCKSServer { sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); } catch (IOException ioe) {} throw new SOCKSException(err); + ****/ } else { - List<String> proxies = t.getProxies(connPort); - if (proxies == null || proxies.isEmpty()) { - String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName; - _log.error(err); + Outproxy outproxy = getOutproxyPlugin(); + if (outproxy != null) { try { - sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); - } catch (IOException ioe) {} - throw new SOCKSException(err); + destSock = new SocketWrapper(outproxy.connect(connHostName, connPort)); + } catch (IOException ioe) { + try { + sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); + } catch (IOException ioe2) {} + throw new SOCKSException("connect failed via outproxy plugin", ioe); + } + } else { + List<String> proxies = t.getProxies(connPort); + if (proxies == null || proxies.isEmpty()) { + String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName; + _log.error(err); + try { + sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); + } catch (IOException ioe) {} + throw new SOCKSException(err); + } + int p = _context.random().nextInt(proxies.size()); + String proxy = proxies.get(p); + Destination dest = _context.namingService().lookup(proxy); + if (dest == null) { + try { + sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); + } catch (IOException ioe) {} + throw new SOCKSException("Outproxy not found"); + } + if (_log.shouldDebug()) + _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "..."); + // this isn't going to work, these need to be socks outproxies so we need + // to do a socks session to them? + destSock = t.createI2PSocket(dest); } - int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size()); - String proxy = proxies.get(p); - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy); - if (dest == null) { - try { - sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out); - } catch (IOException ioe) {} - throw new SOCKSException("Outproxy not found"); - } - if (_log.shouldDebug()) - _log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "..."); - // this isn't going to work, these need to be socks outproxies so we need - // to do a socks session to them? - destSock = t.createI2PSocket(dest); } confirmConnection(); _log.debug("connection confirmed - exchanging data..."); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java index 9c2e19b849..63fbf6c286 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -21,6 +21,9 @@ import java.util.Properties; 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.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.DataFormatException; @@ -37,12 +40,10 @@ import net.i2p.util.Log; * * @author human */ -public class SOCKS5Server extends SOCKSServer { - private final Log _log; +class SOCKS5Server extends SOCKSServer { private static final int SOCKS_VERSION_5 = 0x05; - private final Socket clientSock; private boolean setupCompleted = false; private final boolean authRequired; @@ -57,14 +58,12 @@ public class SOCKS5Server extends SOCKSServer { * @param clientSock client socket * @param props non-null */ - public SOCKS5Server(Socket clientSock, Properties props) { - this.clientSock = clientSock; - this.props = props; + public SOCKS5Server(I2PAppContext ctx, Socket clientSock, Properties props) { + super(ctx, clientSock, props); this.authRequired = Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)) && props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) && props.containsKey(I2PTunnelHTTPClientBase.PROP_PW); - _log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKS5Server.class); } public Socket getClientSocket() throws SOCKSException { @@ -366,7 +365,7 @@ public class SOCKS5Server extends SOCKSServer { // Let's not do a new Dest for every request, huh? //I2PSocketManager sm = I2PSocketManagerFactory.createManager(); //destSock = sm.connect(I2PTunnel.destFromName(connHostName), null); - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName); + Destination dest = _context.namingService().lookup(connHostName); if (dest == null) { try { sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); @@ -399,27 +398,40 @@ public class SOCKS5Server extends SOCKSServer { throw new SOCKSException(err); ****/ } else { - List<String> proxies = t.getProxies(connPort); - if (proxies == null || proxies.isEmpty()) { - String err = "No outproxy configured for port " + connPort + " and no default configured either"; - _log.error(err); + Outproxy outproxy = getOutproxyPlugin(); + if (outproxy != null) { + // In HTTPClient, we use OutproxyRunner to run a Socket, + // but here, we wrap a Socket in a I2PSocket and use the regular Runner. try { - sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); - } catch (IOException ioe) {} - throw new SOCKSException(err); - } - int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size()); - String proxy = proxies.get(p); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort); - - try { - destSock = outproxyConnect(t, proxy); - } catch (SOCKSException se) { + destSock = new SocketWrapper(outproxy.connect(connHostName, connPort)); + } catch (IOException ioe) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe2) {} + throw new SOCKSException("connect failed via outproxy plugin", ioe); + } + } else { + List<String> proxies = t.getProxies(connPort); + if (proxies == null || proxies.isEmpty()) { + String err = "No outproxy configured for port " + connPort + " and no default configured either"; + _log.error(err); + try { + sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw new SOCKSException(err); + } + int p = _context.random().nextInt(proxies.size()); + String proxy = proxies.get(p); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort); try { - sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); - } catch (IOException ioe) {} - throw se; + destSock = outproxyConnect(t, proxy); + } catch (SOCKSException se) { + try { + sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); + } catch (IOException ioe) {} + throw se; + } } } confirmConnection(); @@ -466,7 +478,7 @@ public class SOCKS5Server extends SOCKSServer { Properties overrides = new Properties(); overrides.setProperty("option.i2p.streaming.connectDelay", "1000"); I2PSocketOptions proxyOpts = tun.buildOptions(overrides); - Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy); + Destination dest = _context.namingService().lookup(proxy); if (dest == null) throw new SOCKSException("Outproxy not found"); I2PSocket destSock = tun.createI2PSocket(dest, proxyOpts); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java index cddd019ab0..3b663dd252 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSException.java @@ -20,4 +20,9 @@ public class SOCKSException extends Exception { public SOCKSException(String s) { super(s); } -} \ No newline at end of file + + /** @since 0.9.27 */ + public SOCKSException(String s, Throwable t) { + super(s, t); + } +} diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java index 3e1506793e..b269b6798d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java @@ -9,14 +9,20 @@ package net.i2p.i2ptunnel.socks; import java.net.Socket; import java.util.Properties; +import net.i2p.I2PAppContext; +import net.i2p.app.ClientApp; +import net.i2p.app.ClientAppManager; +import net.i2p.app.Outproxy; import net.i2p.client.streaming.I2PSocket; +import net.i2p.i2ptunnel.I2PTunnelHTTPClient; +import net.i2p.util.Log; /** * Abstract base class used by all SOCKS servers. * * @author human */ -public abstract class SOCKSServer { +abstract class SOCKSServer { private static final String PROP_MAPPING_PREFIX = "ipmapping."; @@ -25,7 +31,18 @@ public abstract class SOCKSServer { protected int connPort; protected int addressType; - protected Properties props; + protected final I2PAppContext _context; + protected final Socket clientSock; + protected final Properties props; + protected final Log _log; + + /** @since 0.9.27 */ + protected SOCKSServer(I2PAppContext ctx, Socket clientSock, Properties props) { + _context = ctx; + this.clientSock = clientSock; + this.props = props; + _log = ctx.logManager().getLog(getClass()); + } /** * IP to domain name mapping support. This matches the given IP string @@ -68,4 +85,26 @@ public abstract class SOCKSServer { */ public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException; + /** + * @since 0.9.27 + */ + private boolean shouldUseOutproxyPlugin() { + return Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClient.PROP_USE_OUTPROXY_PLUGIN, "true")); + } + + /** + * @return null if disabled or not installed + * @since 0.9.27 + */ + protected Outproxy getOutproxyPlugin() { + if (shouldUseOutproxyPlugin()) { + ClientAppManager mgr = _context.clientAppManager(); + if (mgr != null) { + ClientApp op = mgr.getRegisteredApp(Outproxy.NAME); + if (op != null) + return (Outproxy) op; + } + } + return null; + } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java index 049d190b65..416c0a62be 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java @@ -12,13 +12,14 @@ import java.io.IOException; import java.net.Socket; import java.util.Properties; +import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; /** * Factory class for creating SOCKS forwarders through I2P */ -public class SOCKSServerFactory { +class SOCKSServerFactory { private final static String ERR_REQUEST_DENIED = "HTTP/1.1 403 Access Denied - This is a SOCKS proxy, not a HTTP proxy\r\n" + @@ -38,7 +39,7 @@ public class SOCKSServerFactory { * @param s a Socket used to choose the SOCKS server type * @param props non-null */ - public static SOCKSServer createSOCKSServer(Socket s, Properties props) throws SOCKSException { + public static SOCKSServer createSOCKSServer(I2PAppContext ctx, Socket s, Properties props) throws SOCKSException { SOCKSServer serv; try { @@ -53,11 +54,11 @@ public class SOCKSServerFactory { props.containsKey(I2PTunnelHTTPClientBase.PROP_PW)) { throw new SOCKSException("SOCKS 4/4a not supported when authorization is required"); } - serv = new SOCKS4aServer(s, props); + serv = new SOCKS4aServer(ctx, s, props); break; case 0x05: // SOCKS version 5 - serv = new SOCKS5Server(s, props); + serv = new SOCKS5Server(ctx, s, props); break; case 'C': case 'G': diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java new file mode 100644 index 0000000000..e6b3b15f65 --- /dev/null +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SocketWrapper.java @@ -0,0 +1,119 @@ +package net.i2p.i2ptunnel.socks; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.channels.SelectableChannel; + +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketOptions; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; + +/** + * Wrapper around the Socket obtained from the Outproxy, which is a + * wrapper around the Orchid Stream. + * + * @since 0.9.27 + */ +class SocketWrapper implements I2PSocket { + + private final Socket socket; + + private static final Destination DUMMY_DEST = new Destination(); + static { + try { + DUMMY_DEST.fromByteArray(new byte[387]); + } catch (DataFormatException dfe) { + throw new RuntimeException(dfe); + } + } + + public SocketWrapper(Socket sock) { + socket = sock; + } + + /** + * @return the Destination of this side of the socket. + */ + public Destination getThisDestination() { + return DUMMY_DEST; + } + + /** + * @return the destination of the peer. + */ + public Destination getPeerDestination() { + return DUMMY_DEST; + } + + public InputStream getInputStream() throws IOException { + return socket.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return socket.getOutputStream(); + } + + /** + * @return null always + */ + @Deprecated + public SelectableChannel getChannel() { + return null; + } + + /** + * @return null always + */ + public I2PSocketOptions getOptions() { + return null; + } + + /** + * Does nothing + */ + public void setOptions(I2PSocketOptions options) {} + + public long getReadTimeout() { + return -1; + } + + public void setReadTimeout(long ms) {} + + public void close() throws IOException { + socket.close(); + } + + public boolean isClosed() { + return socket.isClosed(); + } + + /** + * Deprecated, unimplemented, does nothing + */ + public void setSocketErrorListener(SocketErrorListener lsnr) {} + + /** + * The remote port. + * @return Default I2PSession.PORT_UNSPECIFIED (0) or PORT_ANY (0) + */ + public int getPort() { + try { + return socket.getPort(); + } catch (UnsupportedOperationException uoe) { + // prior to 1.2.2-0.2 + return 0; + } + } + + /** + * The local port. + * @return 0 always + */ + public int getLocalPort() { + return 0; + } +} -- GitLab