View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
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   * Socks5LogicHandler.java - SOCKS5 authentication mechanisms logic handler.
41   * 
42   * @author The Apache MINA Project (dev@mina.apache.org)
43   * @version $Rev: 685703 $, $Date: 2008-08-14 00:14:47 +0200 (Thu, 14 Aug 2008) $
44   * @since MINA 2.0.0-M3
45   */
46  public class Socks5LogicHandler extends AbstractSocksLogicHandler {
47  
48      private final static Logger logger = LoggerFactory
49              .getLogger(Socks5LogicHandler.class);
50  
51      /**
52       * The selected authentication method.
53       */
54      private final static String SELECTED_AUTH_METHOD = Socks5LogicHandler.class
55              .getName()
56              + ".SelectedAuthMethod";
57  
58      /**
59       * The current step in the handshake.
60       */
61      private final static String HANDSHAKE_STEP = Socks5LogicHandler.class
62              .getName()
63              + ".HandshakeStep";
64  
65      /**
66       * The Java GSS-API context.
67       */
68      private final static String GSS_CONTEXT = Socks5LogicHandler.class
69              .getName()
70              + ".GSSContext";
71  
72      /**
73       * Last GSS token received.
74       */
75      private final static String GSS_TOKEN = Socks5LogicHandler.class.getName()
76              + ".GSSToken";
77  
78      public Socks5LogicHandler(final ProxyIoSession proxyIoSession) {
79          super(proxyIoSession);
80          getSession().setAttribute(HANDSHAKE_STEP,
81                  SocksProxyConstants.SOCKS5_GREETING_STEP);
82      }
83  
84      /**
85       * Perform any handshaking processing.
86       */
87      public synchronized void doHandshake(final NextFilter nextFilter) {
88          logger.debug(" doHandshake()");
89  
90          // Send request
91          int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP))
92                  .intValue();
93          writeRequest(nextFilter, request, step);
94      }
95  
96      /**
97       * Encode the initial greeting packet.
98       * 
99       * @param request the socks proxy request data
100      * @return the encoded buffer
101      */
102     private IoBuffer encodeInitialGreetingPacket(final SocksProxyRequest request) {
103         byte nbMethods = (byte) SocksProxyConstants.SUPPORTED_AUTH_METHODS.length;
104         IoBuffer buf = IoBuffer.allocate(2 + nbMethods);
105 
106         buf.put(request.getProtocolVersion());
107         buf.put(nbMethods);
108         buf.put(SocksProxyConstants.SUPPORTED_AUTH_METHODS);
109 
110         return buf;
111     }
112 
113     /**
114      * Encode the proxy authorization request packet.
115      * 
116      * @param request the socks proxy request data
117      * @return the encoded buffer
118      * @throws UnsupportedEncodingException
119      */
120     private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request)
121             throws UnsupportedEncodingException {
122         int len = 6;
123         byte[] host = request.getHost() != null ? request.getHost().getBytes(
124                 "ASCII") : null;
125 
126         InetSocketAddress adr = request.getEndpointAddress();
127         byte addressType = 0;
128 
129         if (adr != null) {
130             if (adr.getAddress() instanceof Inet6Address) {
131                 len += 16;
132                 addressType = SocksProxyConstants.IPV6_ADDRESS_TYPE;
133             } else if (adr.getAddress() instanceof Inet4Address) {
134                 len += 4;
135                 addressType = SocksProxyConstants.IPV4_ADDRESS_TYPE;
136             }
137         } else {
138             len += 1 + host.length;
139             addressType = SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE;
140         }
141 
142         IoBuffer buf = IoBuffer.allocate(len);
143 
144         buf.put(request.getProtocolVersion());
145         buf.put(request.getCommandCode());
146         buf.put((byte) 0x00); // Reserved
147         buf.put(addressType);
148 
149         if (addressType == SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE) {
150             buf.put((byte) host.length);
151             buf.put(host);
152         } else {
153             buf.put(request.getIpAddress());
154         }
155 
156         buf.put(request.getPort());
157 
158         return buf;
159     }
160 
161     /**
162      * Encode the authentication packet for supported auth methods.
163      * 
164      * @param request the socks proxy request data
165      * @return the encoded buffer
166      * @throws UnsupportedEncodingException
167      * @throws GSSException 
168      */
169     private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request)
170             throws UnsupportedEncodingException, GSSException {
171         byte method = ((Byte) getSession().getAttribute(
172                 Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
173 
174         if (method == SocksProxyConstants.NO_AUTH) {
175             getSession().setAttribute(HANDSHAKE_STEP,
176                     SocksProxyConstants.SOCKS5_REQUEST_STEP);
177 
178         } else if (method == SocksProxyConstants.GSSAPI_AUTH) {
179             GSSContext ctx = (GSSContext) getSession()
180                     .getAttribute(GSS_CONTEXT);
181             if (ctx == null) {
182                 GSSManager manager = GSSManager.getInstance();
183                 GSSName serverName = manager.createName(request
184                         .getServiceKerberosName(), null);
185                 Oid krb5OID = new Oid(SocksProxyConstants.KERBEROS_V5_OID);
186 
187                 if (logger.isDebugEnabled()) {
188                     logger.debug("Available mechs:");
189                     for (Oid o : manager.getMechs()) {
190                         if (o.equals(krb5OID)) {
191                             logger.debug("Found Kerberos V OID available");
192                         }
193                         logger.debug("{} with oid = {}", manager
194                                 .getNamesForMech(o), o);
195                     }
196                 }
197 
198                 ctx = manager.createContext(serverName, krb5OID, null,
199                         GSSContext.DEFAULT_LIFETIME);
200 
201                 ctx.requestMutualAuth(true); // Mutual authentication
202                 ctx.requestConf(false);
203                 ctx.requestInteg(false);
204 
205                 getSession().setAttribute(GSS_CONTEXT, ctx);
206             }
207 
208             byte[] token = (byte[]) getSession().getAttribute(GSS_TOKEN);
209             if (token != null) {
210                 logger.debug("  Received Token[{}] = {}", token.length,
211                         ByteUtilities.asHex(token));
212             }
213             IoBuffer buf = null;
214 
215             if (!ctx.isEstablished()) {
216                 // token is ignored on the first call
217                 if (token == null) {
218                     token = new byte[32];
219                 }
220 
221                 token = ctx.initSecContext(token, 0, token.length);
222 
223                 // Send a token to the server if one was generated by
224                 // initSecContext
225                 if (token != null) {
226                     logger.debug("  Sending Token[{}] = {}", token.length,
227                             ByteUtilities.asHex(token));
228 
229                     getSession().setAttribute(GSS_TOKEN, token);
230                     buf = IoBuffer.allocate(4 + token.length);
231                     buf
232                             .put(new byte[] {
233                                     SocksProxyConstants.GSSAPI_AUTH_SUBNEGOTIATION_VERSION,
234                                     SocksProxyConstants.GSSAPI_MSG_TYPE });
235 
236                     buf.put(ByteUtilities.intToNetworkByteOrder(token.length,
237                             new byte[2], 0, 2));
238                     buf.put(token);
239                 }
240             }
241 
242             return buf;
243 
244         } else if (method == SocksProxyConstants.BASIC_AUTH) {
245             byte[] user = request.getUserName().getBytes("ASCII");
246             byte[] pwd = request.getPassword().getBytes("ASCII");
247             IoBuffer buf = IoBuffer.allocate(3 + user.length + pwd.length);
248 
249             buf.put(SocksProxyConstants.BASIC_AUTH_SUBNEGOTIATION_VERSION);
250             buf.put((byte) user.length);
251             buf.put(user);
252             buf.put((byte) pwd.length);
253             buf.put(pwd);
254 
255             return buf;
256         }
257 
258         return null;
259     }
260 
261     /**
262      * Encode a SOCKS5 request and send it to the proxy server.
263      */
264     private void writeRequest(final NextFilter nextFilter,
265             final SocksProxyRequest request, int step) {
266         try {
267             IoBuffer buf = null;
268 
269             if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
270                 buf = encodeInitialGreetingPacket(request);
271             } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
272                 buf = encodeAuthenticationPacket(request);
273                 if (buf == null) {
274                     step = SocksProxyConstants.SOCKS5_REQUEST_STEP;
275                 }
276             }
277 
278             if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
279                 buf = encodeProxyRequestPacket(request);
280             }
281 
282             buf.flip();
283             writeData(nextFilter, buf);
284 
285         } catch (Exception ex) {
286             closeSession("Unable to send Socks request: ", ex);
287         }
288     }
289 
290     /**
291      * Handle incoming data during the handshake process. Should consume only the
292      * handshake data from the buffer, leaving any extra data in place.
293      */
294     public synchronized void messageReceived(final NextFilter nextFilter,
295             final IoBuffer buf) {
296         try {
297             int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP))
298                     .intValue();
299 
300             if (step == SocksProxyConstants.SOCKS5_GREETING_STEP
301                     && buf.get(0) != SocksProxyConstants.SOCKS_VERSION_5) {
302                 throw new IllegalStateException(
303                         "Wrong socks version running on server");
304             }
305 
306             if ((step == SocksProxyConstants.SOCKS5_GREETING_STEP || step == SocksProxyConstants.SOCKS5_AUTH_STEP)
307                     && buf.remaining() >= 2) {
308                 handleResponse(nextFilter, buf, step);
309             } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP
310                     && buf.remaining() >= 5) {
311                 handleResponse(nextFilter, buf, step);
312             }
313         } catch (Exception ex) {
314             closeSession("Proxy handshake failed: ", ex);
315         }
316     }
317 
318     /**
319      * Handle a SOCKS v5 response from the proxy server.
320      */
321     protected void handleResponse(final NextFilter nextFilter,
322             final IoBuffer buf, int step) throws Exception {
323         int len = 2;
324         if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
325             // Send greeting message
326             byte method = buf.get(1);
327 
328             if (method == SocksProxyConstants.NO_ACCEPTABLE_AUTH_METHOD) {
329                 throw new IllegalStateException(
330                         "No acceptable authentication method to use the socks proxy server");
331             }
332 
333             getSession().setAttribute(SELECTED_AUTH_METHOD, new Byte(method));
334 
335         } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
336             // Authentication to the SOCKS server 
337             byte method = ((Byte) getSession().getAttribute(
338                     Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
339 
340             if (method == SocksProxyConstants.GSSAPI_AUTH) {
341                 int oldPos = buf.position();
342 
343                 if (buf.get(0) != 0x01) {
344                     throw new IllegalStateException("Authentication failed");
345                 }
346                 if (buf.get(1) == 0xFF) {
347                     throw new IllegalStateException(
348                             "Authentication failed: GSS API Security Context Failure");
349                 }
350 
351                 if (buf.remaining() >= 2) {
352                     byte[] size = new byte[2];
353                     buf.get(size);
354                     int s = ByteUtilities.makeIntFromByte2(size);
355                     if (buf.remaining() >= s) {
356                         byte[] token = new byte[s];
357                         buf.get(token);
358                         getSession().setAttribute(GSS_TOKEN, token);
359                         len = 0;
360                     } else {
361                         //buf.position(oldPos);
362                         return;
363                     }
364                 } else {
365                     buf.position(oldPos);
366                     return;
367                 }
368             } else if (buf.get(1) != SocksProxyConstants.V5_REPLY_SUCCEEDED) {
369                 throw new IllegalStateException("Authentication failed");
370             }
371 
372         } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
373             // Send the request
374             byte addressType = buf.get(3);
375             len = 6;
376             if (addressType == SocksProxyConstants.IPV6_ADDRESS_TYPE) {
377                 len += 16;
378             } else if (addressType == SocksProxyConstants.IPV4_ADDRESS_TYPE) {
379                 len += 4;
380             } else if (addressType == SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE) {
381                 len += 1 + ((short) buf.get(4));
382             } else {
383                 throw new IllegalStateException("Unknwon address type");
384             }
385 
386             if (buf.remaining() >= len) {
387                 // handle response
388                 byte status = buf.get(1);
389                 logger.debug("  response status: {}", SocksProxyConstants
390                         .getReplyCodeAsString(status));
391 
392                 if (status == SocksProxyConstants.V5_REPLY_SUCCEEDED) {
393                     buf.position(buf.position() + len);
394                     setHandshakeComplete();
395                     return;
396                 } else
397                     throw new Exception("Proxy handshake failed - Code: 0x"
398                             + ByteUtilities.asHex(new byte[] { status }));
399             } else
400                 return;
401         }
402 
403         if (len > 0) {
404             buf.position(buf.position() + len);
405         }
406 
407         // Move to the handshaking next step if not in the middle of
408         // the authentication process
409         boolean isAuthenticating = false;
410         if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
411             byte method = ((Byte) getSession().getAttribute(
412                     Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
413             if (method == SocksProxyConstants.GSSAPI_AUTH) {
414                 GSSContext ctx = (GSSContext) getSession().getAttribute(
415                         GSS_CONTEXT);
416                 if (ctx == null || !ctx.isEstablished()) {
417                     isAuthenticating = true;
418                 }
419             }
420         }
421 
422         if (!isAuthenticating) {
423             getSession().setAttribute(HANDSHAKE_STEP, ++step);
424         }
425 
426         doHandshake(nextFilter);
427     }
428 
429     @Override
430     protected void closeSession(String message) {
431         GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
432         if (ctx != null) {
433             try {
434                 ctx.dispose();
435             } catch (GSSException e) {
436                 e.printStackTrace();
437                 super.closeSession(message, e);
438                 return;
439             }
440         }
441         super.closeSession(message);
442     }
443 }