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