1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.proxy.handlers.socks;
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.Inet4Address;
24 import java.net.Inet6Address;
25 import java.net.InetSocketAddress;
26
27 import org.apache.mina.core.buffer.IoBuffer;
28 import org.apache.mina.core.filterchain.IoFilter.NextFilter;
29 import org.apache.mina.proxy.session.ProxyIoSession;
30 import org.apache.mina.proxy.utils.ByteUtilities;
31 import org.ietf.jgss.GSSContext;
32 import org.ietf.jgss.GSSException;
33 import org.ietf.jgss.GSSManager;
34 import org.ietf.jgss.GSSName;
35 import org.ietf.jgss.Oid;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40
41
42
43
44
45 public class Socks5LogicHandler extends AbstractSocksLogicHandler {
46
47 private final static Logger LOGGER = LoggerFactory
48 .getLogger(Socks5LogicHandler.class);
49
50
51
52
53 private final static String SELECTED_AUTH_METHOD = Socks5LogicHandler.class
54 .getName()
55 + ".SelectedAuthMethod";
56
57
58
59
60 private final static String HANDSHAKE_STEP = Socks5LogicHandler.class
61 .getName()
62 + ".HandshakeStep";
63
64
65
66
67 private final static String GSS_CONTEXT = Socks5LogicHandler.class
68 .getName()
69 + ".GSSContext";
70
71
72
73
74 private final static String GSS_TOKEN = Socks5LogicHandler.class.getName()
75 + ".GSSToken";
76
77
78
79
80 public Socks5LogicHandler(final ProxyIoSession proxyIoSession) {
81 super(proxyIoSession);
82 getSession().setAttribute(HANDSHAKE_STEP,
83 SocksProxyConstants.SOCKS5_GREETING_STEP);
84 }
85
86
87
88
89
90
91 public synchronized void doHandshake(final NextFilter nextFilter) {
92 LOGGER.debug(" doHandshake()");
93
94
95 writeRequest(nextFilter, request, ((Integer) getSession().getAttribute(
96 HANDSHAKE_STEP)).intValue());
97 }
98
99
100
101
102
103
104
105 private IoBuffer encodeInitialGreetingPacket(final SocksProxyRequest request) {
106 byte nbMethods = (byte) SocksProxyConstants.SUPPORTED_AUTH_METHODS.length;
107 IoBuffer buf = IoBuffer.allocate(2 + nbMethods);
108
109 buf.put(request.getProtocolVersion());
110 buf.put(nbMethods);
111 buf.put(SocksProxyConstants.SUPPORTED_AUTH_METHODS);
112
113 return buf;
114 }
115
116
117
118
119
120
121
122
123
124 private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request)
125 throws UnsupportedEncodingException {
126 int len = 6;
127 InetSocketAddress adr = request.getEndpointAddress();
128 byte addressType = 0;
129 byte[] host = null;
130
131 if (adr != null && !adr.isUnresolved()) {
132 if (adr.getAddress() instanceof Inet6Address) {
133 len += 16;
134 addressType = SocksProxyConstants.IPV6_ADDRESS_TYPE;
135 } else if (adr.getAddress() instanceof Inet4Address) {
136 len += 4;
137 addressType = SocksProxyConstants.IPV4_ADDRESS_TYPE;
138 }
139 } else {
140 host = request.getHost() != null ?
141 request.getHost().getBytes("ASCII") : null;
142
143 if (host != null) {
144 len += 1 + host.length;
145 addressType = SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE;
146 } else {
147 throw new IllegalArgumentException("SocksProxyRequest object " +
148 "has no suitable endpoint information");
149 }
150 }
151
152 IoBuffer buf = IoBuffer.allocate(len);
153
154 buf.put(request.getProtocolVersion());
155 buf.put(request.getCommandCode());
156 buf.put((byte) 0x00);
157 buf.put(addressType);
158
159 if (host == null) {
160 buf.put(request.getIpAddress());
161 } else {
162 buf.put((byte) host.length);
163 buf.put(host);
164 }
165
166 buf.put(request.getPort());
167
168 return buf;
169 }
170
171
172
173
174
175
176
177
178
179
180
181 private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request)
182 throws UnsupportedEncodingException, GSSException {
183 byte method = ((Byte) getSession().getAttribute(
184 Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
185
186 switch (method) {
187 case SocksProxyConstants.NO_AUTH:
188
189
190 getSession().setAttribute(HANDSHAKE_STEP,
191 SocksProxyConstants.SOCKS5_REQUEST_STEP);
192 break;
193
194 case SocksProxyConstants.GSSAPI_AUTH:
195 return encodeGSSAPIAuthenticationPacket(request);
196
197 case SocksProxyConstants.BASIC_AUTH:
198
199 byte[] user = request.getUserName().getBytes("ASCII");
200 byte[] pwd = request.getPassword().getBytes("ASCII");
201 IoBuffer buf = IoBuffer.allocate(3 + user.length + pwd.length);
202
203 buf.put(SocksProxyConstants.BASIC_AUTH_SUBNEGOTIATION_VERSION);
204 buf.put((byte) user.length);
205 buf.put(user);
206 buf.put((byte) pwd.length);
207 buf.put(pwd);
208
209 return buf;
210 }
211
212 return null;
213 }
214
215
216
217
218
219
220
221
222 private IoBuffer encodeGSSAPIAuthenticationPacket(
223 final SocksProxyRequest request) throws GSSException {
224 GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
225 if (ctx == null) {
226
227 GSSManager manager = GSSManager.getInstance();
228 GSSName serverName = manager.createName(request
229 .getServiceKerberosName(), null);
230 Oid krb5OID = new Oid(SocksProxyConstants.KERBEROS_V5_OID);
231
232 if (LOGGER.isDebugEnabled()) {
233 LOGGER.debug("Available mechs:");
234 for (Oid o : manager.getMechs()) {
235 if (o.equals(krb5OID)) {
236 LOGGER.debug("Found Kerberos V OID available");
237 }
238 LOGGER.debug("{} with oid = {}",
239 manager.getNamesForMech(o), o);
240 }
241 }
242
243 ctx = manager.createContext(serverName, krb5OID, null,
244 GSSContext.DEFAULT_LIFETIME);
245
246 ctx.requestMutualAuth(true);
247 ctx.requestConf(false);
248 ctx.requestInteg(false);
249
250 getSession().setAttribute(GSS_CONTEXT, ctx);
251 }
252
253 byte[] token = (byte[]) getSession().getAttribute(GSS_TOKEN);
254 if (token != null) {
255 LOGGER.debug(" Received Token[{}] = {}", token.length,
256 ByteUtilities.asHex(token));
257 }
258 IoBuffer buf = null;
259
260 if (!ctx.isEstablished()) {
261
262 if (token == null) {
263 token = new byte[32];
264 }
265
266 token = ctx.initSecContext(token, 0, token.length);
267
268
269
270 if (token != null) {
271 LOGGER.debug(" Sending Token[{}] = {}", token.length,
272 ByteUtilities.asHex(token));
273
274 getSession().setAttribute(GSS_TOKEN, token);
275 buf = IoBuffer.allocate(4 + token.length);
276 buf.put(new byte[] {
277 SocksProxyConstants.GSSAPI_AUTH_SUBNEGOTIATION_VERSION,
278 SocksProxyConstants.GSSAPI_MSG_TYPE });
279
280 buf.put(ByteUtilities.intToNetworkByteOrder(token.length, 2));
281 buf.put(token);
282 }
283 }
284
285 return buf;
286 }
287
288
289
290
291
292
293
294
295
296 private void writeRequest(final NextFilter nextFilter,
297 final SocksProxyRequest request, int step) {
298 try {
299 IoBuffer buf = null;
300
301 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
302 buf = encodeInitialGreetingPacket(request);
303 } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
304
305 buf = encodeAuthenticationPacket(request);
306
307 if (buf == null) {
308 step = SocksProxyConstants.SOCKS5_REQUEST_STEP;
309 }
310 }
311
312 if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
313 buf = encodeProxyRequestPacket(request);
314 }
315
316 buf.flip();
317 writeData(nextFilter, buf);
318
319 } catch (Exception ex) {
320 closeSession("Unable to send Socks request: ", ex);
321 }
322 }
323
324
325
326
327
328
329
330
331 public synchronized void messageReceived(final NextFilter nextFilter,
332 final IoBuffer buf) {
333 try {
334 int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP))
335 .intValue();
336
337 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP
338 && buf.get(0) != SocksProxyConstants.SOCKS_VERSION_5) {
339 throw new IllegalStateException(
340 "Wrong socks version running on server");
341 }
342
343 if ((step == SocksProxyConstants.SOCKS5_GREETING_STEP ||
344 step == SocksProxyConstants.SOCKS5_AUTH_STEP)
345 && buf.remaining() >= 2) {
346 handleResponse(nextFilter, buf, step);
347 } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP
348 && buf.remaining() >= 5) {
349 handleResponse(nextFilter, buf, step);
350 }
351 } catch (Exception ex) {
352 closeSession("Proxy handshake failed: ", ex);
353 }
354 }
355
356
357
358
359
360
361
362
363 protected void handleResponse(final NextFilter nextFilter,
364 final IoBuffer buf, int step) throws Exception {
365 int len = 2;
366 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
367
368 byte method = buf.get(1);
369
370 if (method == SocksProxyConstants.NO_ACCEPTABLE_AUTH_METHOD) {
371 throw new IllegalStateException(
372 "No acceptable authentication method to use with " +
373 "the socks proxy server");
374 }
375
376 getSession().setAttribute(SELECTED_AUTH_METHOD, new Byte(method));
377
378 } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
379
380 byte method = ((Byte) getSession().getAttribute(
381 Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
382
383 if (method == SocksProxyConstants.GSSAPI_AUTH) {
384 int oldPos = buf.position();
385
386 if (buf.get(0) != 0x01) {
387 throw new IllegalStateException("Authentication failed");
388 }
389 if (buf.get(1) == 0xFF) {
390 throw new IllegalStateException(
391 "Authentication failed: GSS API Security Context Failure");
392 }
393
394 if (buf.remaining() >= 2) {
395 byte[] size = new byte[2];
396 buf.get(size);
397 int s = ByteUtilities.makeIntFromByte2(size);
398 if (buf.remaining() >= s) {
399 byte[] token = new byte[s];
400 buf.get(token);
401 getSession().setAttribute(GSS_TOKEN, token);
402 len = 0;
403 } else {
404
405 return;
406 }
407 } else {
408 buf.position(oldPos);
409 return;
410 }
411 } else if (buf.get(1) != SocksProxyConstants.V5_REPLY_SUCCEEDED) {
412 throw new IllegalStateException("Authentication failed");
413 }
414
415 } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
416
417 byte addressType = buf.get(3);
418 len = 6;
419 if (addressType == SocksProxyConstants.IPV6_ADDRESS_TYPE) {
420 len += 16;
421 } else if (addressType == SocksProxyConstants.IPV4_ADDRESS_TYPE) {
422 len += 4;
423 } else if (addressType == SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE) {
424 len += 1 + (buf.get(4));
425 } else {
426 throw new IllegalStateException("Unknwon address type");
427 }
428
429 if (buf.remaining() >= len) {
430
431 byte status = buf.get(1);
432 LOGGER.debug(" response status: {}", SocksProxyConstants
433 .getReplyCodeAsString(status));
434
435 if (status == SocksProxyConstants.V5_REPLY_SUCCEEDED) {
436 buf.position(buf.position() + len);
437 setHandshakeComplete();
438 return;
439 }
440
441 throw new Exception("Proxy handshake failed - Code: 0x"
442 + ByteUtilities.asHex(new byte[] { status }));
443 }
444
445 return;
446 }
447
448 if (len > 0) {
449 buf.position(buf.position() + len);
450 }
451
452
453
454 boolean isAuthenticating = false;
455 if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
456 byte method = ((Byte) getSession().getAttribute(
457 Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
458 if (method == SocksProxyConstants.GSSAPI_AUTH) {
459 GSSContext ctx = (GSSContext) getSession().getAttribute(
460 GSS_CONTEXT);
461 if (ctx == null || !ctx.isEstablished()) {
462 isAuthenticating = true;
463 }
464 }
465 }
466
467 if (!isAuthenticating) {
468 getSession().setAttribute(HANDSHAKE_STEP, ++step);
469 }
470
471 doHandshake(nextFilter);
472 }
473
474
475
476
477
478
479
480 @Override
481 protected void closeSession(String message) {
482 GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
483 if (ctx != null) {
484 try {
485 ctx.dispose();
486 } catch (GSSException e) {
487 e.printStackTrace();
488 super.closeSession(message, e);
489 return;
490 }
491 }
492 super.closeSession(message);
493 }
494 }