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.channels.UnresolvedAddressException;
40 import java.nio.charset.StandardCharsets;
41
42 import org.apache.hc.core5.http.nio.command.CommandSupport;
43 import org.apache.hc.core5.io.CloseMode;
44 import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
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_COMMAND_CONNECT_LENGTH = 22;
54
55 private static final byte CLIENT_VERSION = 5;
56
57 private static final byte NO_AUTHENTICATION_REQUIRED = 0;
58
59 private static final byte USERNAME_PASSWORD = 2;
60
61 private static final byte USERNAME_PASSWORD_VERSION = 1;
62
63 private static final byte SUCCESS = 0;
64
65 private static final byte COMMAND_CONNECT = 1;
66
67 private static final byte ATYP_IPV4 = 1;
68
69 private static final byte ATYP_DOMAINNAME = 3;
70
71 private static final byte ATYP_IPV6 = 4;
72
73 private enum State {
74 SEND_AUTH, RECEIVE_AUTH_METHOD, SEND_USERNAME_PASSWORD, RECEIVE_AUTH, SEND_CONNECT, RECEIVE_RESPONSE_CODE, RECEIVE_ADDRESS_TYPE, RECEIVE_ADDRESS, COMPLETE
75 }
76
77 private final ProtocolIOSession ioSession;
78 private final Object attachment;
79 private final InetSocketAddress targetAddress;
80 private final String username;
81 private final String password;
82 private final IOEventHandlerFactory eventHandlerFactory;
83
84
85 private ByteBuffer buffer = ByteBuffer.allocate(32);
86 private State state = State.SEND_AUTH;
87 private int remainingResponseSize = -1;
88
89 SocksProxyProtocolHandler(final ProtocolIOSession ioSession, final Object attachment, final InetSocketAddress targetAddress,
90 final String username, final String password, final IOEventHandlerFactory eventHandlerFactory) {
91 this.ioSession = ioSession;
92 this.attachment = attachment;
93 this.targetAddress = targetAddress;
94 this.username = username;
95 this.password = password;
96 this.eventHandlerFactory = eventHandlerFactory;
97 }
98
99 @Override
100 public void connected(final IOSession session) throws IOException {
101 this.buffer.put(CLIENT_VERSION);
102 this.buffer.put((byte) 1);
103 this.buffer.put(NO_AUTHENTICATION_REQUIRED);
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 @Override
142 public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
143 if (src != null) {
144 try {
145 this.buffer.put(src);
146 } catch (final BufferOverflowException ex) {
147 throw new IOException("Unexpected input data");
148 }
149 }
150 switch (this.state) {
151 case RECEIVE_AUTH_METHOD:
152 if (fillBuffer(session)) {
153 this.buffer.flip();
154 final byte serverVersion = this.buffer.get();
155 final byte serverMethod = this.buffer.get();
156 if (serverVersion != CLIENT_VERSION) {
157 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
158 }
159 if (serverMethod == USERNAME_PASSWORD) {
160 this.buffer.clear();
161 setBufferLimit(this.username.length() + this.password.length() + 3);
162 this.buffer.put(USERNAME_PASSWORD_VERSION);
163 this.buffer.put((byte) this.username.length());
164 this.buffer.put(this.username.getBytes(StandardCharsets.ISO_8859_1));
165 this.buffer.put((byte) this.password.length());
166 this.buffer.put(this.password.getBytes(StandardCharsets.ISO_8859_1));
167 session.setEventMask(SelectionKey.OP_WRITE);
168 this.state = State.SEND_USERNAME_PASSWORD;
169 } else if (serverMethod == NO_AUTHENTICATION_REQUIRED) {
170 prepareConnectCommand();
171 session.setEventMask(SelectionKey.OP_WRITE);
172 this.state = State.SEND_CONNECT;
173 } else {
174 throw new IOException("SOCKS server return unsupported authentication method: " + serverMethod);
175 }
176 }
177 break;
178 case RECEIVE_AUTH:
179 if (fillBuffer(session)) {
180 this.buffer.flip();
181 this.buffer.get();
182 final byte status = this.buffer.get();
183 if (status != SUCCESS) {
184 throw new IOException("Authentication failed for external SOCKS proxy");
185 }
186 prepareConnectCommand();
187 session.setEventMask(SelectionKey.OP_WRITE);
188 this.state = State.SEND_CONNECT;
189 }
190 break;
191 case RECEIVE_RESPONSE_CODE:
192 if (fillBuffer(session)) {
193 this.buffer.flip();
194 final byte serverVersion = this.buffer.get();
195 final byte responseCode = this.buffer.get();
196 if (serverVersion != CLIENT_VERSION) {
197 throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
198 }
199 if (responseCode != SUCCESS) {
200 throw new IOException("SOCKS server was unable to establish connection returned error code: " + responseCode);
201 }
202 this.buffer.compact();
203 this.buffer.limit(3);
204 this.state = State.RECEIVE_ADDRESS_TYPE;
205
206 } else {
207 break;
208 }
209 case RECEIVE_ADDRESS_TYPE:
210 if (fillBuffer(session)) {
211 this.buffer.flip();
212 this.buffer.get();
213 final byte aType = this.buffer.get();
214 final int addressSize;
215 if (aType == ATYP_IPV4) {
216 addressSize = 4;
217 } else if (aType == ATYP_IPV6) {
218 addressSize = 16;
219 } else if (aType == ATYP_DOMAINNAME) {
220
221 addressSize = this.buffer.get() & 0xFF;
222 } else {
223 throw new IOException("SOCKS server returned unsupported address type: " + aType);
224 }
225 this.remainingResponseSize = addressSize + 2;
226 this.buffer.compact();
227
228 this.buffer.limit(this.remainingResponseSize);
229 this.state = State.RECEIVE_ADDRESS;
230
231 } else {
232 break;
233 }
234 case RECEIVE_ADDRESS:
235 if (fillBuffer(session)) {
236 this.buffer.clear();
237 this.state = State.COMPLETE;
238 final IOEventHandler newHandler = this.eventHandlerFactory.createHandler(this.ioSession, this.attachment);
239 this.ioSession.upgrade(newHandler);
240 newHandler.connected(this.ioSession);
241 }
242 break;
243 case SEND_AUTH:
244 case SEND_USERNAME_PASSWORD:
245 case SEND_CONNECT:
246 session.setEventMask(SelectionKey.OP_WRITE);
247 break;
248 case COMPLETE:
249 break;
250 }
251 }
252
253 private void prepareConnectCommand() throws IOException {
254 final InetAddress address = this.targetAddress.getAddress();
255 final int port = this.targetAddress.getPort();
256 if (address == null || port == 0) {
257 throw new UnresolvedAddressException();
258 }
259
260 this.buffer.clear();
261 setBufferLimit(MAX_COMMAND_CONNECT_LENGTH);
262 this.buffer.put(CLIENT_VERSION);
263 this.buffer.put(COMMAND_CONNECT);
264 this.buffer.put((byte) 0);
265 if (address instanceof Inet4Address) {
266 this.buffer.put(ATYP_IPV4);
267 this.buffer.put(address.getAddress());
268 } else if (address instanceof Inet6Address) {
269 this.buffer.put(ATYP_IPV6);
270 this.buffer.put(address.getAddress());
271 } else {
272 throw new IOException("Unsupported remote address class: " + address.getClass().getName());
273 }
274 this.buffer.putShort((short) port);
275 this.buffer.flip();
276 }
277
278 private void setBufferLimit(final int newLimit) {
279 if (this.buffer.capacity() < newLimit) {
280 final ByteBuffer newBuffer = ByteBuffer.allocate(newLimit);
281 this.buffer.flip();
282 newBuffer.put(this.buffer);
283 this.buffer = newBuffer;
284 } else {
285 this.buffer.limit(newLimit);
286 }
287 }
288
289 private boolean writeAndPrepareRead(final ByteChannel channel, final int readSize) throws IOException {
290 if (writeBuffer(channel)) {
291 this.buffer.clear();
292 setBufferLimit(readSize);
293 return true;
294 }
295 return false;
296 }
297
298 private boolean writeBuffer(final ByteChannel channel) throws IOException {
299 if (this.buffer.hasRemaining()) {
300 channel.write(this.buffer);
301 }
302 return !this.buffer.hasRemaining();
303 }
304
305 private boolean fillBuffer(final ByteChannel channel) throws IOException {
306 if (this.buffer.hasRemaining()) {
307 channel.read(this.buffer);
308 }
309 return !this.buffer.hasRemaining();
310 }
311
312 @Override
313 public void timeout(final IOSession session, final Timeout timeout) throws IOException {
314 exception(session, SocketTimeoutExceptionFactory.create(timeout));
315 }
316
317 @Override
318 public void exception(final IOSession session, final Exception cause) {
319 session.close(CloseMode.IMMEDIATE);
320 CommandSupport.failCommands(session, cause);
321 }
322
323 @Override
324 public void disconnected(final IOSession session) {
325 CommandSupport.cancelCommands(session);
326 }
327
328 }