/** * Act as a SOCKS 5 client to connect to an outproxy * * @return open socket or throws error * @since 0.8.2 */ private I2PSocket outproxyConnect(I2PSOCKSTunnel tun, String proxy) throws IOException, SOCKSException, DataFormatException, I2PException { Properties overrides = new Properties(); overrides.setProperty("option.i2p.streaming.connectDelay", "1000"); I2PSocketOptions proxyOpts = tun.buildOptions(overrides); Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy); if (dest == null) throw new SOCKSException("Outproxy not found"); I2PSocket destSock = tun.createI2PSocket( I2PAppContext.getGlobalContext().namingService().lookup(proxy), proxyOpts); try { DataOutputStream out = new DataOutputStream(destSock.getOutputStream()); boolean authAvail = Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH)); String configUser = null; String configPW = null; if (authAvail) { configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER_PREFIX + proxy); configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW_PREFIX + proxy); if (configUser == null || configPW == null) { configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER); configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW); if (configUser == null || configPW == null) authAvail = false; } } // send the init out.writeByte(SOCKS_VERSION_5); if (authAvail) { out.writeByte(2); out.writeByte(Method.NO_AUTH_REQUIRED); out.writeByte(Method.USERNAME_PASSWORD); } else { out.writeByte(1); out.writeByte(Method.NO_AUTH_REQUIRED); } out.flush(); // read init reply DataInputStream in = new DataInputStream(destSock.getInputStream()); // is this right or should we not try to do 5-to-4 conversion? int hisVersion = in.readByte(); if (hisVersion != SOCKS_VERSION_5 /* && addrtype == AddressType.DOMAINNAME */) throw new SOCKSException("SOCKS Outproxy is not Version 5"); // else if (hisVersion != 4) // throw new SOCKSException("Unsupported SOCKS Outproxy Version"); int method = in.readByte(); if (method == Method.NO_AUTH_REQUIRED) { // good } else if (method == Method.USERNAME_PASSWORD) { if (authAvail) { // send the auth out.writeByte(AUTH_VERSION); byte[] user = configUser.getBytes("UTF-8"); byte[] pw = configPW.getBytes("UTF-8"); out.writeByte(user.length); out.write(user); out.writeByte(pw.length); out.write(pw); out.flush(); // read the auth reply if (in.readByte() != AUTH_VERSION) throw new SOCKSException("Bad auth version from outproxy"); if (in.readByte() != AUTH_SUCCESS) throw new SOCKSException("Outproxy authorization failure"); } else { throw new SOCKSException( "Outproxy requires authorization, please configure username/password"); } } else { throw new SOCKSException("Outproxy authorization failure"); } // send the connect command out.writeByte(SOCKS_VERSION_5); out.writeByte(Command.CONNECT); out.writeByte(0); // reserved out.writeByte(addressType); if (addressType == AddressType.IPV4) { out.write(InetAddress.getByName(connHostName).getAddress()); } else if (addressType == AddressType.DOMAINNAME) { byte[] d = connHostName.getBytes("ISO-8859-1"); out.writeByte(d.length); out.write(d); } else { // shouldn't happen throw new SOCKSException("Unknown address type for outproxy?"); } out.writeShort(connPort); out.flush(); // read the connect reply hisVersion = in.readByte(); if (hisVersion != SOCKS_VERSION_5) throw new SOCKSException("Outproxy response is not Version 5"); int reply = in.readByte(); in.readByte(); // reserved int type = in.readByte(); int count = 0; if (type == AddressType.IPV4) { count = 4; } else if (type == AddressType.DOMAINNAME) { count = in.readUnsignedByte(); } else if (type == AddressType.IPV6) { count = 16; } else { throw new SOCKSException("Unsupported address type in outproxy response"); } byte[] addr = new byte[count]; in.readFully(addr); // address in.readUnsignedShort(); // port if (reply != Reply.SUCCEEDED) throw new SOCKSException("Outproxy rejected request, response = " + reply); // throw away the address in the response // todo pass the response through? } catch (IOException e) { try { destSock.close(); } catch (IOException ioe) { } throw e; } catch (SOCKSException e) { try { destSock.close(); } catch (IOException ioe) { } throw e; } // that's it, caller will send confirmation to our client return destSock; }
/** * Get an I2PSocket that can be used to send/receive 8-bit clean data to/from the destination of * the SOCKS connection. * * @return an I2PSocket connected with the destination */ public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException { setupServer(); if (connHostName == null) { _log.error("BUG: destination host name has not been initialized!"); throw new SOCKSException("BUG! See the logs!"); } if (connPort == 0) { _log.error("BUG: destination port has not been initialized!"); throw new SOCKSException("BUG! See the logs!"); } DataOutputStream out; // for errors try { out = new DataOutputStream(clientSock.getOutputStream()); } catch (IOException e) { throw new SOCKSException("Connection error: " + e); } // FIXME: here we should read our config file, select an // outproxy, and instantiate the proper socket class that // handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...). I2PSocket destSock; try { if (connHostName.toLowerCase(Locale.US).endsWith(".i2p")) { _log.debug("connecting to " + connHostName + "..."); // Let's not due 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 (dest == null) { try { sendRequestReply( Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw new SOCKSException("Host not found"); } Properties overrides = new Properties(); I2PSocketOptions sktOpts = t.buildOptions(overrides); sktOpts.setPort(connPort); destSock = t.createI2PSocket( I2PAppContext.getGlobalContext().namingService().lookup(connHostName), sktOpts); } else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) { String err = "No localhost accesses allowed through the Socks Proxy"; _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); /** * ** } 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) // use * eepProxy configured outproxies? String err = "No handler for HTTP outproxy implemented"; * _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); ** */ } 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 = 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) { try { sendRequestReply( Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw se; } } confirmConnection(); _log.debug("connection confirmed - exchanging data..."); } catch (DataFormatException e) { if (_log.shouldLog(Log.WARN)) _log.warn("socks error", e); try { sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw new SOCKSException("Error in destination format"); } catch (SocketException e) { if (_log.shouldLog(Log.WARN)) _log.warn("socks error", e); try { sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw new SOCKSException("Error connecting: " + e); } catch (IOException e) { if (_log.shouldLog(Log.WARN)) _log.warn("socks error", e); try { sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw new SOCKSException("Error connecting: " + e); } catch (I2PException e) { if (_log.shouldLog(Log.WARN)) _log.warn("socks error", e); try { sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out); } catch (IOException ioe) { } throw new SOCKSException("Error connecting: " + e); } return destSock; }