1/*
2 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jndi.ldap.sasl;
27
28import java.io.*;
29import java.util.Vector;
30import java.util.Hashtable;
31import java.util.StringTokenizer;
32
33import javax.naming.AuthenticationException;
34import javax.naming.AuthenticationNotSupportedException;
35import javax.naming.NamingException;
36
37import javax.naming.ldap.Control;
38
39import javax.security.auth.callback.CallbackHandler;
40import javax.security.sasl.*;
41import com.sun.jndi.ldap.Connection;
42import com.sun.jndi.ldap.LdapClient;
43import com.sun.jndi.ldap.LdapResult;
44
45/**
46  * Handles SASL support.
47  *
48  * @author Vincent Ryan
49  * @author Rosanna Lee
50  */
51
52final public class LdapSasl {
53    // SASL stuff
54    private static final String SASL_CALLBACK = "java.naming.security.sasl.callback";
55    private static final String SASL_AUTHZ_ID =
56        "java.naming.security.sasl.authorizationId";
57    private static final String SASL_REALM =
58        "java.naming.security.sasl.realm";
59
60    private static final int LDAP_SUCCESS = 0;
61    private static final int LDAP_SASL_BIND_IN_PROGRESS = 14;   // LDAPv3
62
63    private LdapSasl() {
64    }
65
66    /**
67     * Performs SASL bind.
68     * Creates a SaslClient by using a default CallbackHandler
69     * that uses the Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS
70     * properties to satisfy the callbacks, and by using the
71     * SASL_AUTHZ_ID property as the authorization id. If the SASL_AUTHZ_ID
72     * property has not been set, Context.SECURITY_PRINCIPAL is used.
73     * If SASL_CALLBACK has been set, use that instead of the default
74     * CallbackHandler.
75     * <p>
76     * If bind is successful and the selected SASL mechanism has a security
77     * layer, set inStream and outStream to be filter streams that use
78     * the security layer. These will be used for subsequent communication
79     * with the server.
80     *
81     * @param conn The non-null connection to use for sending an LDAP BIND
82     * @param server Non-null string name of host to connect to
83     * @param dn Non-null DN to bind as; also used as authentication ID
84     * @param pw Possibly null password; can be byte[], char[] or String
85     * @param authMech A non-null space-separated list of SASL authentication
86     *        mechanisms.
87     * @param env The possibly null environment of the context, possibly containing
88     *        properties for used by SASL mechanisms
89     * @param bindCtls The possibly null controls to accompany the bind
90     * @return LdapResult containing status of the bind
91     */
92    @SuppressWarnings("unchecked")
93    public static LdapResult saslBind(LdapClient clnt, Connection conn,
94        String server, String dn, Object pw,
95        String authMech, Hashtable<?,?> env, Control[] bindCtls)
96        throws IOException, NamingException {
97
98        SaslClient saslClnt = null;
99        boolean cleanupHandler = false;
100
101        // Use supplied callback handler or create default
102        CallbackHandler cbh =
103            (env != null) ? (CallbackHandler)env.get(SASL_CALLBACK) : null;
104        if (cbh == null) {
105            cbh = new DefaultCallbackHandler(dn, pw, (String)env.get(SASL_REALM));
106            cleanupHandler = true;
107        }
108
109        // Prepare parameters for creating SASL client
110        String authzId = (env != null) ? (String)env.get(SASL_AUTHZ_ID) : null;
111        String[] mechs = getSaslMechanismNames(authMech);
112
113        try {
114            // Create SASL client to use using SASL package
115            saslClnt = Sasl.createSaslClient(
116                mechs, authzId, "ldap", server, (Hashtable<String, ?>)env, cbh);
117
118            if (saslClnt == null) {
119                throw new AuthenticationNotSupportedException(authMech);
120            }
121
122            LdapResult res;
123            String mechName = saslClnt.getMechanismName();
124            byte[] response = saslClnt.hasInitialResponse() ?
125                saslClnt.evaluateChallenge(NO_BYTES) : null;
126
127            res = clnt.ldapBind(null, response, bindCtls, mechName, true);
128
129            while (!saslClnt.isComplete() &&
130                (res.status == LDAP_SASL_BIND_IN_PROGRESS ||
131                 res.status == LDAP_SUCCESS)) {
132
133                response = saslClnt.evaluateChallenge(
134                    res.serverCreds != null? res.serverCreds : NO_BYTES);
135                if (res.status == LDAP_SUCCESS) {
136                    if (response != null) {
137                        throw new AuthenticationException(
138                            "SASL client generated response after success");
139                    }
140                    break;
141                }
142                res = clnt.ldapBind(null, response, bindCtls, mechName, true);
143            }
144
145            if (res.status == LDAP_SUCCESS) {
146                if (!saslClnt.isComplete()) {
147                    throw new AuthenticationException(
148                        "SASL authentication not complete despite server claims");
149                }
150
151                String qop = (String) saslClnt.getNegotiatedProperty(Sasl.QOP);
152
153                // If negotiated integrity or privacy,
154                if (qop != null && (qop.equalsIgnoreCase("auth-int")
155                    || qop.equalsIgnoreCase("auth-conf"))) {
156
157                    InputStream newIn = new SaslInputStream(saslClnt,
158                        conn.inStream);
159                    OutputStream newOut = new SaslOutputStream(saslClnt,
160                        conn.outStream);
161
162                    conn.replaceStreams(newIn, newOut);
163                } else {
164                    saslClnt.dispose();
165                }
166            }
167            return res;
168        } catch (SaslException e) {
169            NamingException ne = new AuthenticationException(
170                authMech);
171            ne.setRootCause(e);
172            throw ne;
173        } finally {
174            if (cleanupHandler) {
175                ((DefaultCallbackHandler)cbh).clearPassword();
176            }
177        }
178    }
179
180    /**
181      * Returns an array of SASL mechanisms given a string of space
182      * separated SASL mechanism names.
183      * @param The non-null string containing the mechanism names
184      * @return A non-null array of String; each element of the array
185      * contains a single mechanism name.
186      */
187    private static String[] getSaslMechanismNames(String str) {
188        StringTokenizer parser = new StringTokenizer(str);
189        Vector<String> mechs = new Vector<>(10);
190        while (parser.hasMoreTokens()) {
191            mechs.addElement(parser.nextToken());
192        }
193        String[] mechNames = new String[mechs.size()];
194        for (int i = 0; i < mechs.size(); i++) {
195            mechNames[i] = mechs.elementAt(i);
196        }
197        return mechNames;
198    }
199
200    private static final byte[] NO_BYTES = new byte[0];
201}
202