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 189425f0e042ed4f13f06c84591488e8c5cca7f0..f1bc4874c2c660a192cf25f98609cd748a35c7bf 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); + SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(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 3a25b489f16de1506c0538ec4133ab54f60adb40..564085ca2e53e03c5c2039573c2bc400260e6474 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/I2PSOCKSTunnel.java @@ -51,7 +51,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase { protected void clientConnectionRun(Socket s) { try { - SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s); + SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions()); Socket clientSock = serv.getClientSocket(); I2PSocket destSock = serv.getDestinationI2PSocket(this); new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets); 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 c87a320dd3c17cbdbfd4fce37f7424bad303d621..22f8f8b0ed4580e9131cc6b531dfa506cb064052 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -16,12 +16,14 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.streaming.I2PSocket; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; +import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.util.HexDump; import net.i2p.util.Log; @@ -37,8 +39,10 @@ public class SOCKS5Server extends SOCKSServer { private static final int SOCKS_VERSION_5 = 0x05; - private Socket clientSock = null; + private final Socket clientSock; + private final Properties props; private boolean setupCompleted = false; + private final boolean authRequired; /** * Create a SOCKS5 server that communicates with the client using @@ -49,9 +53,15 @@ public class SOCKS5Server extends SOCKSServer { * client socket. * * @param clientSock client socket + * @param props non-null */ - public SOCKS5Server(Socket clientSock) { + public SOCKS5Server(Socket clientSock, Properties props) { this.clientSock = clientSock; + this.props = props; + this.authRequired = + Boolean.valueOf(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)).booleanValue() && + props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) && + props.containsKey(I2PTunnelHTTPClientBase.PROP_PW); } public Socket getClientSocket() throws SOCKSException { @@ -90,25 +100,64 @@ public class SOCKS5Server extends SOCKSServer { for (int i = 0; i < nMethods; ++i) { int meth = in.readByte() & 0xff; - if (meth == Method.NO_AUTH_REQUIRED) { + if (((!authRequired) && meth == Method.NO_AUTH_REQUIRED) || + (authRequired && meth == Method.USERNAME_PASSWORD)) { // That's fine, we do support this method method = meth; } } - boolean canContinue = false; switch (method) { - case Method.NO_AUTH_REQUIRED: + case Method.USERNAME_PASSWORD: + _log.debug("username/password authentication required"); + sendInitReply(Method.USERNAME_PASSWORD, out); + verifyPassword(in, out); + return; + case Method.NO_AUTH_REQUIRED: _log.debug("no authentication required"); sendInitReply(Method.NO_AUTH_REQUIRED, out); return; - default: + default: _log.debug("no suitable authentication methods found (" + Integer.toHexString(method) + ")"); sendInitReply(Method.NO_ACCEPTABLE_METHODS, out); throw new SOCKSException("Unsupported authentication method"); } } + /** + * Wait for the username/password message and verify or throw SOCKSException on failure + * @since 0.8.2 + */ + private void verifyPassword(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException { + int c = in.readByte() & 0xff; + if (c != AUTH_VERSION) + throw new SOCKSException("Unsupported authentication version"); + c = in.readByte() & 0xff; + if (c <= 0) + throw new SOCKSException("Bad authentication"); + byte[] user = new byte[c]; + in.readFully(user); + c = in.readByte() & 0xff; + if (c <= 0) + throw new SOCKSException("Bad authentication"); + byte[] pw = new byte[c]; + in.readFully(pw); + // Hopefully these are in UTF-8, since that's what our config file is in + // these throw UnsupportedEncodingException which is an IOE + String u = new String(user, "UTF-8"); + String p = new String(pw, "UTF-8"); + String configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_USER); + String configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_PW); + if ((!u.equals(configUser)) || (!p.equals(configPW))) { + _log.error("SOCKS authorization failure"); + sendAuthReply(AUTH_FAILURE, out); + throw new SOCKSException("SOCKS authorization failure"); + } + if (_log.shouldLog(Log.INFO)) + _log.info("SOCKS authorization success, user: " + u); + sendAuthReply(AUTH_SUCCESS, out); + } + /** * SOCKS5 request management. This method assumes that all the * stuff preceding or enveloping the actual request (e.g. protocol @@ -213,17 +262,24 @@ public class SOCKS5Server extends SOCKSServer { * Send the specified reply during SOCKS5 initialization */ private void sendInitReply(int replyCode, DataOutputStream out) throws IOException { - ByteArrayOutputStream reps = new ByteArrayOutputStream(); - - reps.write(SOCKS_VERSION_5); - reps.write(replyCode); - - byte[] reply = reps.toByteArray(); - - if (_log.shouldLog(Log.DEBUG)) { + byte[] reply = new byte[2]; + reply[0] = SOCKS_VERSION_5; + reply[1] = (byte) replyCode; + if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending init reply:\n" + HexDump.dump(reply)); - } + out.write(reply); + } + /** + * Send the specified reply during SOCKS5 authorization + * @since 0.8.2 + */ + private void sendAuthReply(int replyCode, DataOutputStream out) throws IOException { + byte[] reply = new byte[2]; + reply[0] = AUTH_VERSION; + reply[1] = (byte) replyCode; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Sending auth reply:\n" + HexDump.dump(reply)); out.write(reply); } @@ -435,6 +491,7 @@ public class SOCKS5Server extends SOCKSServer { */ private static class Method { private static final int NO_AUTH_REQUIRED = 0x00; + private static final int USERNAME_PASSWORD = 0x02; private static final int NO_ACCEPTABLE_METHODS = 0xff; } @@ -461,4 +518,8 @@ public class SOCKS5Server extends SOCKSServer { private static final int COMMAND_NOT_SUPPORTED = 0x07; private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08; } + + private static final int AUTH_VERSION = 1; + private static final int AUTH_SUCCESS = 0; + private static final int AUTH_FAILURE = 1; } 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 e05260795641ae4df59feb7a7c66140ee7d48920..8a373b6794ddeb8671f155018af7655b05e030ec 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServerFactory.java @@ -10,7 +10,9 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; +import java.util.Properties; +import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.util.Log; /** @@ -35,8 +37,9 @@ public class SOCKSServerFactory { * provided sockets's input stream. * * @param s a Socket used to choose the SOCKS server type + * @param props non-null */ - public static SOCKSServer createSOCKSServer(Socket s) throws SOCKSException { + public static SOCKSServer createSOCKSServer(Socket s, Properties props) throws SOCKSException { SOCKSServer serv; try { @@ -46,11 +49,16 @@ public class SOCKSServerFactory { switch (socksVer) { case 0x04: // SOCKS version 4/4a + if (Boolean.valueOf(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)).booleanValue() && + props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) && + props.containsKey(I2PTunnelHTTPClientBase.PROP_PW)) { + throw new SOCKSException("SOCKS 4/4a not supported when authorization is required"); + } serv = new SOCKS4aServer(s); break; case 0x05: // SOCKS version 5 - serv = new SOCKS5Server(s); + serv = new SOCKS5Server(s, props); break; case 'C': case 'G':