001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.example.proxy;
021
022import java.io.DataInputStream;
023import java.io.DataOutputStream;
024import java.io.IOException;
025import java.net.ServerSocket;
026import java.net.Socket;
027
028import org.apache.mina.proxy.handlers.socks.SocksProxyConstants;
029import org.apache.mina.proxy.utils.ByteUtilities;
030import org.ietf.jgss.GSSContext;
031import org.ietf.jgss.GSSCredential;
032import org.ietf.jgss.GSSException;
033import org.ietf.jgss.GSSManager;
034import org.ietf.jgss.Oid;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Socks5GSSAPITestServer.java - Basic test server for SOCKS5 GSSAPI authentication.
040 * 
041 * NOTE: Launch this program with the following params in a pre-configured Kerberos V env.
042 * Do not forget to replace < ... > vars with your own values.
043 * 
044 * -Djava.security.krb5.realm=<your_krb_realm> 
045 * -Djavax.security.auth.useSubjectCredsOnly=false 
046 * -Djava.security.krb5.kdc=<your_kdc_hostname>
047 * -Djava.security.auth.login.config=${workspace_loc}\Mina2Proxy\src\bcsLogin.conf
048 * -Dsun.security.krb5.debug=true 
049 * 
050 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
051 * @since MINA 2.0.0-M3
052 */
053public class Socks5GSSAPITestServer {
054
055    private final static Logger logger = LoggerFactory
056            .getLogger(Socks5GSSAPITestServer.class);
057
058    /**
059     * NOTE : change this to comply with your Kerberos environment.
060     */
061    protected final static String SERVICE_NAME = "host/myworkstation.local.network";
062
063    /**
064     * Selected mechanism message: advertises client to use SocksV5 protocol with
065     * GSSAPI authentication.
066     */
067    public final static byte[] SELECT_GSSAPI_AUTH_MSG = new byte[] {
068            SocksProxyConstants.SOCKS_VERSION_5,
069            SocksProxyConstants.GSSAPI_AUTH };
070
071    /**
072     * Simulates a Socks v5 server using only Kerberos V authentication.
073     * 
074     * @param localPort the local port used to bind the server
075     * @throws IOException
076     * @throws GSSException
077     */
078    private static void doHandShake(int localPort) throws IOException,
079            GSSException {
080        ServerSocket ss = new ServerSocket(localPort);
081        GSSManager manager = GSSManager.getInstance();
082
083        /*
084         * Create a GSSContext to receive the incoming request from the client. 
085         * Use null for the server credentials passed in to tell the underlying 
086         * mechanism to use whatever credentials it has available that can be 
087         * used to accept this connection.
088         */
089        GSSCredential serverCreds = manager.createCredential(manager
090                .createName(SERVICE_NAME, null),
091                GSSCredential.DEFAULT_LIFETIME, new Oid(
092                        SocksProxyConstants.KERBEROS_V5_OID),
093                GSSCredential.ACCEPT_ONLY);
094
095        while (true) {
096            logger.debug("Waiting for incoming connection on port {} ...",
097                    localPort);
098            GSSContext context = manager.createContext(serverCreds);
099            Socket socket = ss.accept();
100
101            try {
102                DataInputStream inStream = new DataInputStream(socket
103                        .getInputStream());
104                DataOutputStream outStream = new DataOutputStream(socket
105                        .getOutputStream());
106
107                logger.debug("Got connection from client @ {}", socket
108                        .getInetAddress());
109
110                // Read SOCKS5 greeting packet
111                byte ver = (byte) inStream.read();
112                if (ver != 0x05) {
113                    throw new IllegalStateException(
114                            "Wrong socks version received - " + ver);
115                }
116                byte nbAuthMethods = (byte) inStream.read();
117                byte[] methods = new byte[nbAuthMethods];
118                inStream.readFully(methods);
119
120                boolean found = false;
121                for (byte b : methods) {
122                    if (b == SocksProxyConstants.GSSAPI_AUTH) {
123                        found = true;
124                        break;
125                    }
126                }
127
128                if (!found) {
129                    throw new IllegalStateException(
130                            "Client does not support GSSAPI authentication");
131                }
132
133                // Send selected mechanism message
134                outStream.write(SELECT_GSSAPI_AUTH_MSG);
135                outStream.flush();
136
137                // Do the context establishment loop
138                byte[] token = null;
139
140                while (!context.isEstablished()) {
141                    byte authVersion = (byte) inStream.read();
142
143                    if (authVersion != 0x01) {
144                        throw new IllegalStateException(
145                                "Wrong socks GSSAPI auth version received: "
146                                        + authVersion);
147                    }
148
149                    byte mtyp = (byte) inStream.read();
150                    if (mtyp != 0x01) {
151                        throw new IllegalArgumentException(
152                                "Message type should be equal to 1.");
153                    }
154
155                    int len = inStream.readShort();
156                    token = new byte[len];
157                    inStream.readFully(token);
158                    logger.debug("  Received Token[{}] = {}", len,
159                            ByteUtilities.asHex(token));
160
161                    token = context.acceptSecContext(token, 0, token.length);
162
163                    // Send a token to the peer if one was generated by acceptSecContext
164                    if (token != null) {
165                        logger.debug("    Sending Token[{}] = {}", token.length,
166                                ByteUtilities.asHex(token));
167                        outStream.writeByte(authVersion);
168                        outStream.writeByte(mtyp);
169                        outStream.writeShort(token.length);
170                        outStream.write(token);
171                        outStream.flush();
172                    }
173                }
174
175                logger.debug("Context Established !");
176                logger.debug("Client is {}", context.getSrcName());
177                logger.debug("Server is {}", context.getTargName());
178
179                /*
180                 * If mutual authentication did not take place, then
181                 * only the client was authenticated to the
182                 * server. Otherwise, both client and server were
183                 * authenticated to each other. 
184                 */
185                if (context.getMutualAuthState()) {
186                    logger.debug("Mutual authentication took place !");
187                }
188
189                // We can now abort the process after a short time as auth is OK
190                // and finally block will close session
191                Thread.sleep(500);
192            } catch (Exception ex) {
193                //ex.printStackTrace();
194            } finally {
195                context.dispose();
196                socket.close();
197            }
198        }
199    }
200
201    /**
202     * {@inheritDoc}
203     */
204    public static void main(String[] args) throws Exception {
205        // Obtain the command-line arguments and parse the port number
206        if (args.length != 1) {
207            System.err
208                    .println("Usage: java <options> Socks5GSSAPITestServer <localPort>");
209            System.exit(-1);
210        }
211
212        doHandShake(Integer.parseInt(args[0]));
213        System.exit(0);
214    }
215}