001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2014-2016 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.sasl;
018
019import javax.net.ssl.SSLSession;
020import javax.security.auth.callback.CallbackHandler;
021
022import org.jivesoftware.smack.ConnectionConfiguration;
023import org.jivesoftware.smack.SmackException;
024import org.jivesoftware.smack.SmackException.NotConnectedException;
025import org.jivesoftware.smack.XMPPConnection;
026import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
027import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
028import org.jivesoftware.smack.util.StringTransformer;
029import org.jivesoftware.smack.util.StringUtils;
030import org.jivesoftware.smack.util.stringencoder.Base64;
031
032import org.jxmpp.jid.DomainBareJid;
033import org.jxmpp.jid.EntityBareJid;
034
035/**
036 * Base class for SASL mechanisms.
037 * Subclasses will likely want to implement their own versions of these methods:
038 * <ul>
039 *  <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid, SSLSession)} -- Initiate authentication stanza using the
040 *  deprecated method.</li>
041 *  <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} -- Initiate authentication stanza
042 *  using the CallbackHandler method.</li>
043 *  <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
044 * </ul>
045 *
046 * @author Jay Kline
047 * @author Florian Schmaus
048 */
049public abstract class SASLMechanism implements Comparable<SASLMechanism> {
050
051    public static final String CRAMMD5 = "CRAM-MD5";
052    public static final String DIGESTMD5 = "DIGEST-MD5";
053    public static final String EXTERNAL = "EXTERNAL";
054    public static final String GSSAPI = "GSSAPI";
055    public static final String PLAIN = "PLAIN";
056
057    // TODO Remove once Smack's min Android API is 9, where java.text.Normalizer is available
058    private static StringTransformer saslPrepTransformer;
059
060    /**
061     * Set the SASLPrep StringTransformer.
062     * <p>
063     * A simple SASLPrep StringTransformer would be for example: <code>java.text.Normalizer.normalize(string, Form.NFKC);</code>
064     * </p>
065     * 
066     * @param stringTransformer set StringTransformer to use for SASLPrep.
067     * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a>
068     */
069    public static void setSaslPrepTransformer(StringTransformer stringTransformer) {
070        saslPrepTransformer = stringTransformer;
071    }
072
073    protected XMPPConnection connection;
074
075    protected ConnectionConfiguration connectionConfiguration;
076
077    /**
078     * Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a
079     * "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an
080     * localpart. But it usually is the localpart of the client JID, although sometimes other formats are used (e.g. the
081     * full JID).
082     * <p>
083     * Not to be confused with the authzid (see RFC 6120 § 6.3.8).
084     * </p>
085     */
086    protected String authenticationId;
087
088    /**
089     * The authorization identifier (authzid).
090     * This is always a bare Jid, but can be null.
091     */
092    protected EntityBareJid authorizationId;
093
094    /**
095     * The name of the XMPP service
096     */
097    protected DomainBareJid serviceName;
098
099    /**
100     * The users password
101     */
102    protected String password;
103    protected String host;
104
105    /**
106     * The used SSL/TLS session (if any).
107     */
108    protected SSLSession sslSession;
109
110    /**
111     * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
112     * authentication is not recommended, since it is very inflexible. Use
113     * {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} whenever possible.
114     * 
115     * Explanation of auth stanza:
116     * 
117     * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName 
118     * From RFC-2831: 
119     * digest-uri = "digest-uri" "=" digest-uri-value
120     * digest-uri-value = serv-type "/" host [ "/" serv-name ]
121     * 
122     * digest-uri: 
123     * Indicates the principal name of the service with which the client 
124     * wishes to connect, formed from the serv-type, host, and serv-name. 
125     * For example, the FTP service
126     * on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP
127     * server from the example above would have a "digest-uri" value of
128     * "smtp/mail3.example.com/example.com".
129     * 
130     * host:
131     * The DNS host name or IP address for the service requested. The DNS host name
132     * must be the fully-qualified canonical name of the host. The DNS host name is the
133     * preferred form; see notes on server processing of the digest-uri.
134     * 
135     * serv-name: 
136     * Indicates the name of the service if it is replicated. The service is
137     * considered to be replicated if the client's service-location process involves resolution
138     * using standard DNS lookup operations, and if these operations involve DNS records (such
139     * as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case,
140     * the initial name used by the client is the "serv-name", and the final name is the "host"
141     * component. For example, the incoming mail service for "example.com" may be replicated
142     * through the use of MX records stored in the DNS, one of which points at an SMTP server
143     * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be
144     * "mail3.example.com". If the service is not replicated, or the serv-name is identical to
145     * the host, then the serv-name component MUST be omitted
146     *
147     * digest-uri verification is needed for ejabberd 2.0.3 and higher
148     *
149     * @param username the username of the user being authenticated.
150     * @param host the hostname where the user account resides.
151     * @param serviceName the xmpp service location - used by the SASL client in digest-uri creation
152     * serviceName format is: host [ "/" serv-name ] as per RFC-2831
153     * @param password the password for this account.
154     * @param authzid the optional authorization identity.
155     * @param sslSession the optional SSL/TLS session (if one was established)
156     * @throws SmackException If a network error occurs while authenticating.
157     * @throws NotConnectedException 
158     * @throws InterruptedException 
159     */
160    public final void authenticate(String username, String host, DomainBareJid serviceName, String password,
161                    EntityBareJid authzid, SSLSession sslSession)
162                    throws SmackException, NotConnectedException, InterruptedException {
163        this.authenticationId = username;
164        this.host = host;
165        this.serviceName = serviceName;
166        this.password = password;
167        this.authorizationId = authzid;
168        this.sslSession = sslSession;
169        assert (authorizationId == null || authzidSupported());
170        authenticateInternal();
171        authenticate();
172    }
173
174    /**
175     * @throws SmackException
176     */
177    protected void authenticateInternal() throws SmackException {
178    }
179
180    /**
181     * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
182     * any additional information, such as the authentication ID or realm, if it is needed.
183     *
184     * @param host     the hostname where the user account resides.
185     * @param serviceName the xmpp service location
186     * @param cbh      the CallbackHandler to obtain user information.
187     * @param authzid the optional authorization identity.
188     * @param sslSession the optional SSL/TLS session (if one was established)
189     * @throws SmackException
190     * @throws NotConnectedException 
191     * @throws InterruptedException 
192     */
193    public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid, SSLSession sslSession)
194                    throws SmackException, NotConnectedException, InterruptedException {
195        this.host = host;
196        this.serviceName = serviceName;
197        this.authorizationId = authzid;
198        this.sslSession = sslSession;
199        assert (authorizationId == null || authzidSupported());
200        authenticateInternal(cbh);
201        authenticate();
202    }
203
204    protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;
205
206    private final void authenticate() throws SmackException, NotConnectedException, InterruptedException {
207        byte[] authenticationBytes = getAuthenticationText();
208        String authenticationText;
209        // Some SASL mechanisms do return an empty array (e.g. EXTERNAL from javax), so check that
210        // the array is not-empty. Mechanisms are allowed to return either 'null' or an empty array
211        // if there is no authentication text.
212        if (authenticationBytes != null && authenticationBytes.length > 0) {
213            authenticationText = Base64.encodeToString(authenticationBytes);
214        } else {
215            // RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response,
216            // it MUST transmit the response as a single equals sign character ("="), which
217            // indicates that the response is present but contains no data."
218            authenticationText = "=";
219        }
220        // Send the authentication to the server
221        connection.sendNonza(new AuthMechanism(getName(), authenticationText));
222    }
223
224    /**
225     * Should return the initial response of the SASL mechanism. The returned byte array will be
226     * send base64 encoded to the server. SASL mechanism are free to return <code>null</code> or an
227     * empty array here.
228     * 
229     * @return the initial response or null
230     * @throws SmackException
231     */
232    protected abstract byte[] getAuthenticationText() throws SmackException;
233
234    /**
235     * The server is challenging the SASL mechanism for the stanza he just sent. Send a
236     * response to the server's challenge.
237     *
238     * @param challengeString a base64 encoded string representing the challenge.
239     * @param finalChallenge true if this is the last challenge send by the server within the success stanza
240     * @throws SmackException exception
241     * @throws InterruptedException if the connection is interrupted
242     */
243    public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, InterruptedException {
244        byte[] challenge = Base64.decode((challengeString != null && challengeString.equals("=")) ? "" : challengeString);
245        byte[] response = evaluateChallenge(challenge);
246        if (finalChallenge) {
247            return;
248        }
249
250        Response responseStanza;
251        if (response == null) {
252            responseStanza = new Response();
253        }
254        else {
255            responseStanza = new Response(Base64.encodeToString(response));
256        }
257
258        // Send the authentication to the server
259        connection.sendNonza(responseStanza);
260    }
261
262    /**
263     * Evaluate the SASL challenge.
264     *
265     * @param challenge challenge to evaluate.
266     *
267     * @return null.
268     * @throws SmackException in case of an error.
269     */
270    protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
271        return null;
272    }
273
274    @Override
275    public final int compareTo(SASLMechanism other) {
276        // Switch to Integer.compare(int, int) once Smack is on Android 19 or higher.
277        Integer ourPriority = getPriority();
278        return ourPriority.compareTo(other.getPriority());
279    }
280
281    /**
282     * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI.
283     *
284     * @return the common name of the SASL mechanism.
285     */
286    public abstract String getName();
287
288    /**
289     * Get the priority of this SASL mechanism. Lower values mean higher priority.
290     *
291     * @return the priority of this SASL mechanism.
292     */
293    public abstract int getPriority();
294
295    public abstract void checkIfSuccessfulOrThrow() throws SmackException;
296
297    public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
298        SASLMechanism saslMechansim = newInstance();
299        saslMechansim.connection = connection;
300        saslMechansim.connectionConfiguration = connectionConfiguration;
301        return saslMechansim;
302    }
303
304    public boolean authzidSupported() {
305        return false;
306    }
307
308    protected abstract SASLMechanism newInstance();
309
310    protected static byte[] toBytes(String string) {
311        return StringUtils.toBytes(string);
312    }
313
314    /**
315     * SASLprep the given String. The resulting String is in UTF-8.
316     * 
317     * @param string the String to sasl prep.
318     * @return the given String SASL preped
319     * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a>
320     */
321    protected static String saslPrep(String string) {
322        StringTransformer stringTransformer = saslPrepTransformer;
323        if (stringTransformer != null) {
324            return stringTransformer.transform(string);
325        }
326        return string;
327    }
328
329    @Override
330    public final String toString() {
331        return "SASL Mech: " + getName() + ", Prio: " + getPriority();
332    }
333}