1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.reactor;
29
30 import java.io.IOException;
31 import java.net.Inet4Address;
32 import java.net.Inet6Address;
33 import java.net.InetAddress;
34 import java.net.InetSocketAddress;
35 import java.nio.BufferOverflowException;
36 import java.nio.ByteBuffer;
37 import java.nio.channels.ByteChannel;
38 import java.nio.channels.SelectionKey;
39 import java.nio.charset.StandardCharsets;
40
41 import org.apache.hc.core5.http.nio.command.CommandSupport;
42 import org.apache.hc.core5.io.CloseMode;
43 import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
44 import org.apache.hc.core5.net.InetAddressUtils;
45 import org.apache.hc.core5.util.Timeout;
46
47
48
49
50
51 final class SocksProxyProtocolHandler implements IOEventHandler {
52
53 private static final int MAX_DNS_NAME_LENGTH = 255;
54
55 private static final int MAX_COMMAND_CONNECT_LENGTH = 6 + MAX_DNS_NAME_LENGTH + 1;
56
57 private static final byte CLIENT_VERSION = 5;
58
59 private static final byte NO_AUTHENTICATION_REQUIRED = 0;
60
61 private static final byte USERNAME_PASSWORD = 2;
62
63 private static final byte USERNAME_PASSWORD_VERSION = 1;
64
65 private static final byte SUCCESS = 0;
66
67 private static final byte COMMAND_CONNECT = 1;
68
69 private static final byte ATYP_DOMAINNAME = 3;
70
71
72 private enum State {
73 SEND_AUTH, RECEIVE_AUTH_METHOD, SEND_USERNAME_PASSWORD, RECEIVE_AUTH, SEND_CONNECT, RECEIVE_RESPONSE_CODE, RECEIVE_ADDRESS_TYPE, RECEIVE_ADDRESS, COMPLETE
74 }
75
76 private final InternalDataChannel dataChannel;
77 private final IOSessionRequest sessionRequest;
78 private final IOEventHandlerFactory eventHandlerFactory;
79 private final IOReactorConfig reactorConfig;
80
81 private ByteBuffer buffer = ByteBuffer.allocate(512);
82 private State state = State.SEND_AUTH;
83 SocksProxyProtocolHandler(final InternalDataChannel dataChannel,
84 final IOSessionRequest sessionRequest,
85 final IOEventHandlerFactory eventHandlerFactory,
86 final IOReactorConfig reactorConfig) {
87 this.dataChannel = dataChannel;
88 this.sessionRequest = sessionRequest;
89 this.eventHandlerFactory = eventHandlerFactory;
90 this.reactorConfig = reactorConfig;
91 }
92
93 @Override
94 public void connected(final IOSession session) throws IOException {
95 this.buffer.put(CLIENT_VERSION);
96 this.buffer.put((byte) 1);
97 this.buffer.put(NO_AUTHENTICATION_REQUIRED);
98 this.buffer.flip();
99 session.setEventMask(SelectionKey.OP_WRITE);
100 }
101
102 @Override
103 public void outputReady(final IOSession session) throws IOException {
104 switch (this.state) {
105 case SEND_AUTH:
106 if (writeAndPrepareRead(session, 2)) {
107 session.setEventMask(SelectionKey.OP_READ);
108 this.state = State.RECEIVE_AUTH_METHOD;
109 }
110 break;
111 case SEND_USERNAME_PASSWORD:
112 if (writeAndPrepareRead(session, 2)) {
113 session.setEventMask(SelectionKey.OP_READ);
114 this.state = State.RECEIVE_AUTH;
115 }
116 break;
117 case SEND_CONNECT:
118 if (writeAndPrepareRead(session, 2)) {
119 session.setEventMask(SelectionKey.OP_READ);
120 this.state = State.RECEIVE_RESPONSE_CODE;
121 }
122 break;
123 case RECEIVE_AUTH_METHOD:
124 case RECEIVE_AUTH:
125 case RECEIVE_ADDRESS:
126 case RECEIVE_ADDRESS_TYPE:
127 case RECEIVE_RESPONSE_CODE:
128 session.setEventMask(SelectionKey.OP_READ);
129 break;
130 case COMPLETE:
131 break;
132 }
133 }
134
135 private byte[] cred(final String cred) throws IOException {
136 if (cred == null) {
137 return new byte[] {};
138 }
139 final byte[] bytes = cred.getBytes(StandardCharsets.ISO_8859_1);
140 if (bytes.length >= 255) {
141 throw new IOException("SOCKS username / password are too long");
142 }
143 return bytes;
144 }
145
146 @Override
147 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
148 if (src != null) {
149 try {
150 this.buffer.put(src);
151 } catch (final BufferOverflowException ex) {
152 throw new IOException("Unexpected input data");
153 }
154 }
155 switch (this.state) {
156 case RECEIVE_AUTH_METHOD:
157 if (fillBuffer(session)) {
158 this.buffer.flip();
159 final byte serverVersion = this.buffer.get();
160 final byte serverMethod = this.buffer.get();
161 if (serverVersion != CLIENT_VERSION) {
162 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
163 }
164 if (serverMethod == USERNAME_PASSWORD) {
165 this.buffer.clear();
166 final byte[] username = cred(reactorConfig.getSocksProxyUsername());
167 final byte[] password = cred(reactorConfig.getSocksProxyPassword());
168 setBufferLimit(username.length + password.length + 3);
169 this.buffer.put(USERNAME_PASSWORD_VERSION);
170 this.buffer.put((byte) username.length);
171 this.buffer.put(username);
172 this.buffer.put((byte) password.length);
173 this.buffer.put(password);
174 session.setEventMask(SelectionKey.OP_WRITE);
175 this.state = State.SEND_USERNAME_PASSWORD;
176 } else if (serverMethod == NO_AUTHENTICATION_REQUIRED) {
177 prepareConnectCommand();
178 session.setEventMask(SelectionKey.OP_WRITE);
179 this.state = State.SEND_CONNECT;
180 } else {
181 throw new IOException("SOCKS server return unsupported authentication method: " + serverMethod);
182 }
183 }
184 break;
185 case RECEIVE_AUTH:
186 if (fillBuffer(session)) {
187 this.buffer.flip();
188 this.buffer.get();
189 final byte status = this.buffer.get();
190 if (status != SUCCESS) {
191 throw new IOException("Authentication failed for external SOCKS proxy");
192 }
193 prepareConnectCommand();
194 session.setEventMask(SelectionKey.OP_WRITE);
195 this.state = State.SEND_CONNECT;
196 }
197 break;
198 case RECEIVE_RESPONSE_CODE:
199 if (fillBuffer(session)) {
200 this.buffer.flip();
201 final byte serverVersion = this.buffer.get();
202 final byte responseCode = this.buffer.get();
203 if (serverVersion != CLIENT_VERSION) {
204 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
205 }
206 switch (responseCode) {
207 case SUCCESS:
208 break;
209 case 1:
210 throw new IOException("SOCKS: General SOCKS server failure");
211 case 2:
212 throw new IOException("SOCKS5: Connection not allowed by ruleset");
213 case 3:
214 throw new IOException("SOCKS5: Network unreachable");
215 case 4:
216 throw new IOException("SOCKS5: Host unreachable");
217 case 5:
218 throw new IOException("SOCKS5: Connection refused");
219 case 6:
220 throw new IOException("SOCKS5: TTL expired");
221 case 7:
222 throw new IOException("SOCKS5: Command not supported");
223 case 8:
224 throw new IOException("SOCKS5: Address type not supported");
225 default:
226 throw new IOException("SOCKS5: Unexpected SOCKS response code " + responseCode);
227 }
228 this.buffer.compact();
229 this.buffer.limit(3);
230 this.state = State.RECEIVE_ADDRESS_TYPE;
231
232 } else {
233 break;
234 }
235 case RECEIVE_ADDRESS_TYPE:
236 if (fillBuffer(session)) {
237 this.buffer.flip();
238 this.buffer.get();
239 final byte aType = this.buffer.get();
240 final int addressSize;
241 if (aType == InetAddressUtils.IPV4) {
242 addressSize = 4;
243 } else if (aType == InetAddressUtils.IPV6) {
244 addressSize = 16;
245 } else if (aType == ATYP_DOMAINNAME) {
246
247 addressSize = this.buffer.get() & 0xFF;
248 } else {
249 throw new IOException("SOCKS server returned unsupported address type: " + aType);
250 }
251 final int remainingResponseSize = addressSize + 2;
252 this.buffer.compact();
253
254 this.buffer.limit(remainingResponseSize);
255 this.state = State.RECEIVE_ADDRESS;
256
257 } else {
258 break;
259 }
260 case RECEIVE_ADDRESS:
261 if (fillBuffer(session)) {
262 this.buffer.clear();
263 this.state = State.COMPLETE;
264 final IOEventHandler newHandler = this.eventHandlerFactory.createHandler(dataChannel, sessionRequest.attachment);
265 dataChannel.upgrade(newHandler);
266 sessionRequest.completed(dataChannel);
267 dataChannel.handleIOEvent(SelectionKey.OP_CONNECT);
268 }
269 break;
270 case SEND_AUTH:
271 case SEND_USERNAME_PASSWORD:
272 case SEND_CONNECT:
273 session.setEventMask(SelectionKey.OP_WRITE);
274 break;
275 case COMPLETE:
276 break;
277 }
278 }
279
280 private void prepareConnectCommand() throws IOException {
281 this.buffer.clear();
282 setBufferLimit(MAX_COMMAND_CONNECT_LENGTH);
283 this.buffer.put(CLIENT_VERSION);
284 this.buffer.put(COMMAND_CONNECT);
285 this.buffer.put((byte) 0);
286 if (!(sessionRequest.remoteAddress instanceof InetSocketAddress)) {
287 throw new IOException("Unsupported address class: " + sessionRequest.remoteAddress.getClass());
288 }
289 final InetSocketAddress targetAddress = ((InetSocketAddress) sessionRequest.remoteAddress);
290 if (targetAddress.isUnresolved()) {
291 this.buffer.put(ATYP_DOMAINNAME);
292 final String hostName = targetAddress.getHostName();
293 final byte[] hostnameBytes = hostName.getBytes(StandardCharsets.US_ASCII);
294 if (hostnameBytes.length > MAX_DNS_NAME_LENGTH) {
295 throw new IOException("Host name exceeds " + MAX_DNS_NAME_LENGTH + " bytes");
296 }
297 this.buffer.put((byte) hostnameBytes.length);
298 this.buffer.put(hostnameBytes);
299 } else {
300 final InetAddress address = targetAddress.getAddress();
301 if (address instanceof Inet4Address) {
302 this.buffer.put(InetAddressUtils.IPV4);
303 } else if (address instanceof Inet6Address) {
304 this.buffer.put(InetAddressUtils.IPV6);
305 } else {
306 throw new IOException("Unsupported remote address class: " + address.getClass().getName());
307 }
308 this.buffer.put(address.getAddress());
309 }
310 final int port = targetAddress.getPort();
311 this.buffer.putShort((short) port);
312 this.buffer.flip();
313 }
314
315 private void setBufferLimit(final int newLimit) {
316 if (this.buffer.capacity() < newLimit) {
317 final ByteBuffer newBuffer = ByteBuffer.allocate(newLimit);
318 this.buffer.flip();
319 newBuffer.put(this.buffer);
320 this.buffer = newBuffer;
321 } else {
322 this.buffer.limit(newLimit);
323 }
324 }
325
326 private boolean writeAndPrepareRead(final ByteChannel channel, final int readSize) throws IOException {
327 if (writeBuffer(channel)) {
328 this.buffer.clear();
329 setBufferLimit(readSize);
330 return true;
331 }
332 return false;
333 }
334
335 private boolean writeBuffer(final ByteChannel channel) throws IOException {
336 if (this.buffer.hasRemaining()) {
337 channel.write(this.buffer);
338 }
339 return !this.buffer.hasRemaining();
340 }
341
342 private boolean fillBuffer(final ByteChannel channel) throws IOException {
343 if (this.buffer.hasRemaining()) {
344 channel.read(this.buffer);
345 }
346 return !this.buffer.hasRemaining();
347 }
348
349 @Override
350 public void timeout(final IOSession session, final Timeout timeout) throws IOException {
351 exception(session, SocketTimeoutExceptionFactory.create(timeout));
352 }
353
354 @Override
355 public void exception(final IOSession session, final Exception cause) {
356 try {
357 sessionRequest.failed(cause);
358 } finally {
359 session.close(CloseMode.IMMEDIATE);
360 CommandSupport.failCommands(session, cause);
361 }
362 }
363
364 @Override
365 public void disconnected(final IOSession session) {
366 sessionRequest.cancel();
367 CommandSupport.cancelCommands(session);
368 }
369
370 }