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 if (this.reactorConfig.getSocksProxyUsername() != null && this.reactorConfig.getSocksProxyPassword() != null) {
97 this.buffer.put((byte) 2);
98 this.buffer.put(NO_AUTHENTICATION_REQUIRED);
99 this.buffer.put(USERNAME_PASSWORD);
100 } else {
101 this.buffer.put((byte) 1);
102 this.buffer.put(NO_AUTHENTICATION_REQUIRED);
103 }
104 this.buffer.flip();
105 session.setEventMask(SelectionKey.OP_WRITE);
106 }
107
108 @Override
109 public void outputReady(final IOSession session) throws IOException {
110 switch (this.state) {
111 case SEND_AUTH:
112 if (writeAndPrepareRead(session, 2)) {
113 session.setEventMask(SelectionKey.OP_READ);
114 this.state = State.RECEIVE_AUTH_METHOD;
115 }
116 break;
117 case SEND_USERNAME_PASSWORD:
118 if (writeAndPrepareRead(session, 2)) {
119 session.setEventMask(SelectionKey.OP_READ);
120 this.state = State.RECEIVE_AUTH;
121 }
122 break;
123 case SEND_CONNECT:
124 if (writeAndPrepareRead(session, 2)) {
125 session.setEventMask(SelectionKey.OP_READ);
126 this.state = State.RECEIVE_RESPONSE_CODE;
127 }
128 break;
129 case RECEIVE_AUTH_METHOD:
130 case RECEIVE_AUTH:
131 case RECEIVE_ADDRESS:
132 case RECEIVE_ADDRESS_TYPE:
133 case RECEIVE_RESPONSE_CODE:
134 session.setEventMask(SelectionKey.OP_READ);
135 break;
136 case COMPLETE:
137 break;
138 }
139 }
140
141 private byte[] cred(final String cred) throws IOException {
142 if (cred == null) {
143 return new byte[] {};
144 }
145
146
147 final byte[] bytes = cred.getBytes(StandardCharsets.ISO_8859_1);
148 if (bytes.length >= 255) {
149 throw new IOException("SOCKS username / password are too long");
150 }
151 return bytes;
152 }
153
154 @Override
155 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
156 if (src != null) {
157 try {
158 this.buffer.put(src);
159 } catch (final BufferOverflowException ex) {
160 throw new IOException("Unexpected input data");
161 }
162 }
163 switch (this.state) {
164 case RECEIVE_AUTH_METHOD:
165 if (fillBuffer(session)) {
166 this.buffer.flip();
167 final byte serverVersion = this.buffer.get();
168 final byte serverMethod = this.buffer.get();
169 if (serverVersion != CLIENT_VERSION) {
170 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
171 }
172 if (serverMethod == USERNAME_PASSWORD) {
173 this.buffer.clear();
174 final byte[] username = cred(reactorConfig.getSocksProxyUsername());
175 final byte[] password = cred(reactorConfig.getSocksProxyPassword());
176 setBufferLimit(username.length + password.length + 3);
177 this.buffer.put(USERNAME_PASSWORD_VERSION);
178 this.buffer.put((byte) username.length);
179 this.buffer.put(username);
180 this.buffer.put((byte) password.length);
181 this.buffer.put(password);
182 this.buffer.flip();
183 session.setEventMask(SelectionKey.OP_WRITE);
184 this.state = State.SEND_USERNAME_PASSWORD;
185 } else if (serverMethod == NO_AUTHENTICATION_REQUIRED) {
186 prepareConnectCommand();
187 session.setEventMask(SelectionKey.OP_WRITE);
188 this.state = State.SEND_CONNECT;
189 } else {
190 throw new IOException("SOCKS server return unsupported authentication method: " + serverMethod);
191 }
192 }
193 break;
194 case RECEIVE_AUTH:
195 if (fillBuffer(session)) {
196 this.buffer.flip();
197 this.buffer.get();
198 final byte status = this.buffer.get();
199 if (status != SUCCESS) {
200 throw new IOException("Authentication failed for external SOCKS proxy");
201 }
202 prepareConnectCommand();
203 session.setEventMask(SelectionKey.OP_WRITE);
204 this.state = State.SEND_CONNECT;
205 }
206 break;
207 case RECEIVE_RESPONSE_CODE:
208 if (fillBuffer(session)) {
209 this.buffer.flip();
210 final byte serverVersion = this.buffer.get();
211 final byte responseCode = this.buffer.get();
212 if (serverVersion != CLIENT_VERSION) {
213 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
214 }
215 switch (responseCode) {
216 case SUCCESS:
217 break;
218 case 1:
219 throw new IOException("SOCKS: General SOCKS server failure");
220 case 2:
221 throw new IOException("SOCKS5: Connection not allowed by ruleset");
222 case 3:
223 throw new IOException("SOCKS5: Network unreachable");
224 case 4:
225 throw new IOException("SOCKS5: Host unreachable");
226 case 5:
227 throw new IOException("SOCKS5: Connection refused");
228 case 6:
229 throw new IOException("SOCKS5: TTL expired");
230 case 7:
231 throw new IOException("SOCKS5: Command not supported");
232 case 8:
233 throw new IOException("SOCKS5: Address type not supported");
234 default:
235 throw new IOException("SOCKS5: Unexpected SOCKS response code " + responseCode);
236 }
237 this.buffer.compact();
238 this.buffer.limit(3);
239 this.state = State.RECEIVE_ADDRESS_TYPE;
240
241 } else {
242 break;
243 }
244 case RECEIVE_ADDRESS_TYPE:
245 if (fillBuffer(session)) {
246 this.buffer.flip();
247 this.buffer.get();
248 final byte aType = this.buffer.get();
249 final int addressSize;
250 if (aType == InetAddressUtils.IPV4) {
251 addressSize = 4;
252 } else if (aType == InetAddressUtils.IPV6) {
253 addressSize = 16;
254 } else if (aType == ATYP_DOMAINNAME) {
255
256 addressSize = this.buffer.get() & 0xFF;
257 } else {
258 throw new IOException("SOCKS server returned unsupported address type: " + aType);
259 }
260 final int remainingResponseSize = addressSize + 2;
261 this.buffer.compact();
262
263 this.buffer.limit(remainingResponseSize);
264 this.state = State.RECEIVE_ADDRESS;
265
266 } else {
267 break;
268 }
269 case RECEIVE_ADDRESS:
270 if (fillBuffer(session)) {
271 this.buffer.clear();
272 this.state = State.COMPLETE;
273 final IOEventHandler newHandler = this.eventHandlerFactory.createHandler(dataChannel, sessionRequest.attachment);
274 dataChannel.upgrade(newHandler);
275 sessionRequest.completed(dataChannel);
276 dataChannel.handleIOEvent(SelectionKey.OP_CONNECT);
277 }
278 break;
279 case SEND_AUTH:
280 case SEND_USERNAME_PASSWORD:
281 case SEND_CONNECT:
282 session.setEventMask(SelectionKey.OP_WRITE);
283 break;
284 case COMPLETE:
285 break;
286 }
287 }
288
289 private void prepareConnectCommand() throws IOException {
290 this.buffer.clear();
291 setBufferLimit(MAX_COMMAND_CONNECT_LENGTH);
292 this.buffer.put(CLIENT_VERSION);
293 this.buffer.put(COMMAND_CONNECT);
294 this.buffer.put((byte) 0);
295 if (!(sessionRequest.remoteAddress instanceof InetSocketAddress)) {
296 throw new IOException("Unsupported address class: " + sessionRequest.remoteAddress.getClass());
297 }
298 final InetSocketAddress targetAddress = ((InetSocketAddress) sessionRequest.remoteAddress);
299 if (targetAddress.isUnresolved()) {
300 this.buffer.put(ATYP_DOMAINNAME);
301 final String hostName = targetAddress.getHostName();
302 final byte[] hostnameBytes = hostName.getBytes(StandardCharsets.US_ASCII);
303 if (hostnameBytes.length > MAX_DNS_NAME_LENGTH) {
304 throw new IOException("Host name exceeds " + MAX_DNS_NAME_LENGTH + " bytes");
305 }
306 this.buffer.put((byte) hostnameBytes.length);
307 this.buffer.put(hostnameBytes);
308 } else {
309 final InetAddress address = targetAddress.getAddress();
310 if (address instanceof Inet4Address) {
311 this.buffer.put(InetAddressUtils.IPV4);
312 } else if (address instanceof Inet6Address) {
313 this.buffer.put(InetAddressUtils.IPV6);
314 } else {
315 throw new IOException("Unsupported remote address class: " + address.getClass().getName());
316 }
317 this.buffer.put(address.getAddress());
318 }
319 final int port = targetAddress.getPort();
320 this.buffer.putShort((short) port);
321 this.buffer.flip();
322 }
323
324 private void setBufferLimit(final int newLimit) {
325 if (this.buffer.capacity() < newLimit) {
326 final ByteBuffer newBuffer = ByteBuffer.allocate(newLimit);
327 this.buffer.flip();
328 newBuffer.put(this.buffer);
329 this.buffer = newBuffer;
330 } else {
331 this.buffer.limit(newLimit);
332 }
333 }
334
335 private boolean writeAndPrepareRead(final ByteChannel channel, final int readSize) throws IOException {
336 if (writeBuffer(channel)) {
337 this.buffer.clear();
338 setBufferLimit(readSize);
339 return true;
340 }
341 return false;
342 }
343
344 private boolean writeBuffer(final ByteChannel channel) throws IOException {
345 if (this.buffer.hasRemaining()) {
346 channel.write(this.buffer);
347 }
348 return !this.buffer.hasRemaining();
349 }
350
351 private boolean fillBuffer(final ByteChannel channel) throws IOException {
352 if (this.buffer.hasRemaining()) {
353 channel.read(this.buffer);
354 }
355 return !this.buffer.hasRemaining();
356 }
357
358 @Override
359 public void timeout(final IOSession session, final Timeout timeout) throws IOException {
360 exception(session, SocketTimeoutExceptionFactory.create(timeout));
361 }
362
363 @Override
364 public void exception(final IOSession session, final Exception cause) {
365 try {
366 sessionRequest.failed(cause);
367 } finally {
368 session.close(CloseMode.IMMEDIATE);
369 CommandSupport.failCommands(session, cause);
370 }
371 }
372
373 @Override
374 public void disconnected(final IOSession session) {
375 sessionRequest.cancel();
376 CommandSupport.cancelCommands(session);
377 }
378
379 }