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.example.proxy;
21  
22  import java.io.DataInputStream;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.net.ServerSocket;
26  import java.net.Socket;
27  
28  import org.apache.mina.proxy.handlers.socks.SocksProxyConstants;
29  import org.apache.mina.proxy.utils.ByteUtilities;
30  import org.ietf.jgss.GSSContext;
31  import org.ietf.jgss.GSSCredential;
32  import org.ietf.jgss.GSSException;
33  import org.ietf.jgss.GSSManager;
34  import org.ietf.jgss.Oid;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * Socks5GSSAPITestServer.java - Basic test server for SOCKS5 GSSAPI authentication.
40   * 
41   * NOTE: Launch this program with the following params in a pre-configured Kerberos V env.
42   * Do not forget to replace < ... > vars with your own values.
43   * 
44   * -Djava.security.krb5.realm=<your_krb_realm> 
45   * -Djavax.security.auth.useSubjectCredsOnly=false 
46   * -Djava.security.krb5.kdc=<your_kdc_hostname>
47   * -Djava.security.auth.login.config=${workspace_loc}\Mina2Proxy\src\bcsLogin.conf
48   * -Dsun.security.krb5.debug=true 
49   * 
50   * @author The Apache MINA Project (dev@mina.apache.org)
51   * @version $Rev$, $Date$
52   * @since MINA 2.0.0-M3
53   */
54  public class Socks5GSSAPITestServer {
55  
56      private final static Logger logger = LoggerFactory
57              .getLogger(Socks5GSSAPITestServer.class);
58  
59      /**
60       * NOTE : change this to comply with your Kerberos environment.
61       */
62      protected final static String SERVICE_NAME = "host/myworkstation.local.network";
63  
64      /**
65       * Selected mechanism message: advertises client to use SocksV5 protocol with
66       * GSSAPI authentication.
67       */
68      public final static byte[] SELECT_GSSAPI_AUTH_MSG = new byte[] {
69              SocksProxyConstants.SOCKS_VERSION_5,
70              SocksProxyConstants.GSSAPI_AUTH };
71  
72      /**
73       * Simulates a Socks v5 server using only Kerberos V authentication.
74       * 
75       * @param localPort the local port used to bind the server
76       * @throws IOException
77       * @throws GSSException
78       */
79      private static void doHandShake(int localPort) throws IOException,
80              GSSException {
81          ServerSocket ss = new ServerSocket(localPort);
82          GSSManager manager = GSSManager.getInstance();
83  
84          /*
85           * Create a GSSContext to receive the incoming request from the client. 
86           * Use null for the server credentials passed in to tell the underlying 
87           * mechanism to use whatever credentials it has available that can be 
88           * used to accept this connection.
89           */
90          GSSCredential serverCreds = manager.createCredential(manager
91                  .createName(SERVICE_NAME, null),
92                  GSSCredential.DEFAULT_LIFETIME, new Oid(
93                          SocksProxyConstants.KERBEROS_V5_OID),
94                  GSSCredential.ACCEPT_ONLY);
95  
96          while (true) {
97              logger.debug("Waiting for incoming connection on port {} ...",
98                      localPort);
99              GSSContext context = manager.createContext(serverCreds);
100             Socket socket = ss.accept();
101 
102             try {
103                 DataInputStream inStream = new DataInputStream(socket
104                         .getInputStream());
105                 DataOutputStream outStream = new DataOutputStream(socket
106                         .getOutputStream());
107 
108                 logger.debug("Got connection from client @ {}", socket
109                         .getInetAddress());
110 
111                 // Read SOCKS5 greeting packet
112                 byte ver = (byte) inStream.read();
113                 if (ver != 0x05) {
114                     throw new IllegalStateException(
115                             "Wrong socks version received - " + ver);
116                 }
117                 byte nbAuthMethods = (byte) inStream.read();
118                 byte[] methods = new byte[nbAuthMethods];
119                 inStream.readFully(methods);
120 
121                 boolean found = false;
122                 for (byte b : methods) {
123                     if (b == SocksProxyConstants.GSSAPI_AUTH) {
124                         found = true;
125                         break;
126                     }
127                 }
128 
129                 if (!found) {
130                     throw new IllegalStateException(
131                             "Client does not support GSSAPI authentication");
132                 }
133 
134                 // Send selected mechanism message
135                 outStream.write(SELECT_GSSAPI_AUTH_MSG);
136                 outStream.flush();
137 
138                 // Do the context establishment loop
139                 byte[] token = null;
140 
141                 while (!context.isEstablished()) {
142                     byte authVersion = (byte) inStream.read();
143 
144                     if (authVersion != 0x01) {
145                         throw new IllegalStateException(
146                                 "Wrong socks GSSAPI auth version received: "
147                                         + authVersion);
148                     }
149 
150                     byte mtyp = (byte) inStream.read();
151                     if (mtyp != 0x01) {
152                         throw new IllegalArgumentException(
153                                 "Message type should be equal to 1.");
154                     }
155 
156                     int len = inStream.readShort();
157                     token = new byte[len];
158                     inStream.readFully(token);
159                     logger.debug("  Received Token[{}] = {}", len,
160                             ByteUtilities.asHex(token));
161 
162                     token = context.acceptSecContext(token, 0, token.length);
163 
164                     // Send a token to the peer if one was generated by acceptSecContext
165                     if (token != null) {
166                         logger.debug("	Sending Token[{}] = {}", token.length,
167                                 ByteUtilities.asHex(token));
168                         outStream.writeByte(authVersion);
169                         outStream.writeByte(mtyp);
170                         outStream.writeShort(token.length);
171                         outStream.write(token);
172                         outStream.flush();
173                     }
174                 }
175 
176                 logger.debug("Context Established !");
177                 logger.debug("Client is {}", context.getSrcName());
178                 logger.debug("Server is {}", context.getTargName());
179 
180                 /*
181                  * If mutual authentication did not take place, then
182                  * only the client was authenticated to the
183                  * server. Otherwise, both client and server were
184                  * authenticated to each other.	 
185                  */
186                 if (context.getMutualAuthState()) {
187                     logger.debug("Mutual authentication took place !");
188                 }
189 
190                 // We can now abort the process after a short time as auth is OK
191                 // and finally block will close session 			    
192                 Thread.sleep(500);
193             } catch (Exception ex) {
194                 //ex.printStackTrace();
195             } finally {
196                 context.dispose();
197                 socket.close();
198             }
199         }
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     public static void main(String[] args) throws Exception {
206         // Obtain the command-line arguments and parse the port number
207         if (args.length != 1) {
208             System.err
209                     .println("Usage: java <options> Socks5GSSAPITestServer <localPort>");
210             System.exit(-1);
211         }
212 
213         doHandShake(Integer.parseInt(args[0]));
214         System.exit(0);
215     }
216 }