1package eu.siacs.conversations.utils;
2
3import com.google.common.io.ByteStreams;
4import com.google.common.net.InetAddresses;
5import eu.siacs.conversations.Config;
6import java.io.IOException;
7import java.io.InputStream;
8import java.io.OutputStream;
9import java.net.Inet4Address;
10import java.net.Inet6Address;
11import java.net.InetAddress;
12import java.net.InetSocketAddress;
13import java.net.Socket;
14import java.nio.ByteBuffer;
15
16public class SocksSocketFactory {
17
18 private static final byte[] LOCALHOST = new byte[] {127, 0, 0, 1};
19
20 public static void createSocksConnection(
21 final Socket socket, final String destination, final int port) throws IOException {
22 final InputStream proxyIs = socket.getInputStream();
23 final OutputStream proxyOs = socket.getOutputStream();
24 proxyOs.write(new byte[] {0x05, 0x01, 0x00});
25 proxyOs.flush();
26 final byte[] handshake = new byte[2];
27 ByteStreams.readFully(proxyIs, handshake);
28 if (handshake[0] != 0x05 || handshake[1] != 0x00) {
29 throw new SocksConnectionException("Socks 5 handshake failed");
30 }
31 final byte type;
32 final ByteBuffer request;
33 if (InetAddresses.isInetAddress(destination)) {
34 final var ip = InetAddresses.forString(destination);
35 final var dest = ip.getAddress();
36 request = ByteBuffer.allocate(6 + dest.length);
37 if (ip instanceof Inet4Address) {
38 type = 0x01;
39 } else if (ip instanceof Inet6Address) {
40 type = 0x04;
41 } else {
42 throw new IOException("IP address is of unknown subtype");
43 }
44 request.put(new byte[] {0x05, 0x01, 0x00, type});
45 request.put(dest);
46 } else {
47 final byte[] dest = destination.getBytes();
48 type = 0x03;
49 request = ByteBuffer.allocate(7 + dest.length);
50 request.put(new byte[] {0x05, 0x01, 0x00, type});
51 request.put((byte) dest.length);
52 request.put(dest);
53 }
54 request.putShort((short) port);
55 proxyOs.write(request.array());
56 proxyOs.flush();
57 final byte[] response = new byte[4];
58 ByteStreams.readFully(proxyIs, response);
59 final byte ver = response[0];
60 if (ver != 0x05) {
61 throw new IOException(String.format("Unknown Socks version %02X ", ver));
62 }
63 final byte status = response[1];
64 final byte bndAddressType = response[3];
65 final byte[] bndDestination = readDestination(bndAddressType, proxyIs);
66 final byte[] bndPort = new byte[2];
67 if (bndAddressType == 0x03) {
68 final String receivedDestination = new String(bndDestination);
69 if (!receivedDestination.equalsIgnoreCase(destination)) {
70 throw new IOException(
71 String.format(
72 "Destination mismatch. Received %s Expected %s",
73 receivedDestination, destination));
74 }
75 }
76 ByteStreams.readFully(proxyIs, bndPort);
77 if (status != 0x00) {
78 if (status == 0x04) {
79 throw new HostNotFoundException("Host unreachable");
80 }
81 if (status == 0x05) {
82 throw new HostNotFoundException("Connection refused");
83 }
84 throw new IOException(String.format("Unknown status code %02X ", status));
85 }
86 }
87
88 private static byte[] readDestination(final byte type, final InputStream inputStream)
89 throws IOException {
90 final byte[] bndDestination;
91 if (type == 0x01) {
92 bndDestination = new byte[4];
93 } else if (type == 0x03) {
94 final int length = inputStream.read();
95 bndDestination = new byte[length];
96 } else if (type == 0x04) {
97 bndDestination = new byte[16];
98 } else {
99 throw new IOException(String.format("Unknown Socks address type %02X ", type));
100 }
101 ByteStreams.readFully(inputStream, bndDestination);
102 return bndDestination;
103 }
104
105 public static boolean contains(byte needle, byte[] haystack) {
106 for (byte hay : haystack) {
107 if (hay == needle) {
108 return true;
109 }
110 }
111 return false;
112 }
113
114 private static Socket createSocket(InetSocketAddress address, String destination, int port)
115 throws IOException {
116 Socket socket = new Socket();
117 try {
118 socket.connect(address, Config.CONNECT_TIMEOUT * 1000);
119 } catch (IOException e) {
120 throw new SocksProxyNotFoundException();
121 }
122 createSocksConnection(socket, destination, port);
123 return socket;
124 }
125
126 public static Socket createSocketOverTor(String destination, int port) throws IOException {
127 return createSocket(
128 new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050),
129 destination,
130 port);
131 }
132
133 private static class SocksConnectionException extends IOException {
134 SocksConnectionException(String message) {
135 super(message);
136 }
137 }
138
139 public static class SocksProxyNotFoundException extends IOException {}
140
141 public static class HostNotFoundException extends SocksConnectionException {
142 HostNotFoundException(String message) {
143 super(message);
144 }
145 }
146}