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