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