package com.subgraph.orchid.socks; import java.io.IOException; import java.net.Socket; import java.util.logging.Logger; import com.subgraph.orchid.TorConfig; import com.subgraph.orchid.data.IPv4Address; public abstract class SocksRequest { private final static Logger logger = Logger.getLogger(SocksRequest.class.getName()); private final TorConfig config; private final Socket socket; private byte[] addressData; private IPv4Address address; private String hostname; private int port; private long lastWarningTimestamp = 0; protected SocksRequest(TorConfig config, Socket socket) { this.config = config; this.socket = socket; } abstract public void readRequest() throws SocksRequestException; abstract public int getCommandCode(); abstract public boolean isConnectRequest(); abstract void sendError(boolean isUnsupportedCommand) throws SocksRequestException; abstract void sendSuccess() throws SocksRequestException; abstract void sendConnectionRefused() throws SocksRequestException; public int getPort() { return port; } public IPv4Address getAddress() { return address; } public boolean hasHostname() { return hostname != null; } public String getHostname() { return hostname; } public String getTarget() { if(config.getSafeLogging()) { return "[scrubbed]:"+ port; } if(hostname != null) { return hostname + ":" + port; } else { return address + ":" + port; } } protected void setPortData(byte[] data) throws SocksRequestException { if(data.length != 2) throw new SocksRequestException(); port = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); } protected void setIPv4AddressData(byte[] data) throws SocksRequestException { logUnsafeSOCKS(); if(data.length != 4) throw new SocksRequestException(); addressData = data; int addressValue = 0; for(byte b: addressData) { addressValue <<= 8; addressValue |= (b & 0xFF); } address = new IPv4Address(addressValue); } private boolean testRateLimit() { final long now = System.currentTimeMillis(); final long diff = now - lastWarningTimestamp; lastWarningTimestamp = now; return diff > 5000; } private void logUnsafeSOCKS() throws SocksRequestException { if((config.getWarnUnsafeSocks() || config.getSafeSocks()) && testRateLimit()) { logger.warning("Your application is giving Orchid only "+ "an IP address. Applications that do DNS "+ "resolves themselves may leak information. "+ "Consider using Socks4a (e.g. via privoxy or socat) "+ "instead. For more information please see "+ "https://wiki.torproject.org/TheOnionRouter/TorFAQ#SOCKSAndDNS"); } if(config.getSafeSocks()) { throw new SocksRequestException("Rejecting unsafe SOCKS request"); } } protected void setHostname(String name) { hostname = name; } protected byte[] readPortData() throws SocksRequestException { final byte[] data = new byte[2]; readAll(data, 0, 2); return data; } protected byte[] readIPv4AddressData() throws SocksRequestException { final byte[] data = new byte[4]; readAll(data); return data; } protected byte[] readIPv6AddressData() throws SocksRequestException { final byte[] data = new byte[16]; readAll(data); return data; } protected String readNullTerminatedString() throws SocksRequestException { try { final StringBuilder sb = new StringBuilder(); while(true) { final int c = socket.getInputStream().read(); if(c == -1) throw new SocksRequestException(); if(c == 0) return sb.toString(); char ch = (char) c; sb.append(ch); } } catch (IOException e) { throw new SocksRequestException(e); } } protected int readByte() throws SocksRequestException { try { final int n = socket.getInputStream().read(); if(n == -1) throw new SocksRequestException(); return n; } catch (IOException e) { throw new SocksRequestException(e); } } protected void readAll(byte[] buffer) throws SocksRequestException { readAll(buffer, 0, buffer.length); } protected void readAll(byte[] buffer, int offset, int length) throws SocksRequestException { try { while(length > 0) { int n = socket.getInputStream().read(buffer, offset, length); if(n == -1) throw new SocksRequestException(); offset += n; length -= n; } } catch (IOException e) { throw new SocksRequestException(e); } } protected void socketWrite(byte[] buffer) throws SocksRequestException { try { socket.getOutputStream().write(buffer); } catch(IOException e) { throw new SocksRequestException(e); } } }