001/**
002 *
003 * Copyright © 2014 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.javax;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022
023import javax.security.auth.callback.Callback;
024import javax.security.auth.callback.CallbackHandler;
025import javax.security.auth.callback.NameCallback;
026import javax.security.auth.callback.PasswordCallback;
027import javax.security.auth.callback.UnsupportedCallbackException;
028import javax.security.sasl.RealmCallback;
029import javax.security.sasl.RealmChoiceCallback;
030import javax.security.sasl.Sasl;
031import javax.security.sasl.SaslClient;
032import javax.security.sasl.SaslException;
033
034import org.jivesoftware.smack.SmackException;
035import org.jivesoftware.smack.sasl.SASLMechanism;
036
037public abstract class SASLJavaXMechanism extends SASLMechanism {
038
039    protected SaslClient sc;
040
041    @Override
042    public abstract String getName();
043
044    @Override
045    public final void checkIfSuccessfulOrThrow() throws SmackException {
046        if (!sc.isComplete()) {
047            throw new SmackException(getName() + " was not completed");
048        }
049    }
050
051    @Override
052    protected void authenticateInternal()
053                    throws SmackException {
054        String[] mechanisms = { getName() };
055        Map<String, String> props = getSaslProps();
056        try {
057            sc = Sasl.createSaslClient(mechanisms, null, "xmpp", getServerName(), props,
058                            new CallbackHandler() {
059                                @Override
060                                public void handle(Callback[] callbacks) throws IOException,
061                                                UnsupportedCallbackException {
062                                    for (int i = 0; i < callbacks.length; i++) {
063                                        if (callbacks[i] instanceof NameCallback) {
064                                            NameCallback ncb = (NameCallback) callbacks[i];
065                                            ncb.setName(authenticationId);
066                                        }
067                                        else if (callbacks[i] instanceof PasswordCallback) {
068                                            PasswordCallback pcb = (PasswordCallback) callbacks[i];
069                                            pcb.setPassword(password.toCharArray());
070                                        }
071                                        else if (callbacks[i] instanceof RealmCallback) {
072                                            RealmCallback rcb = (RealmCallback) callbacks[i];
073                                            // Retrieve the REALM from the challenge response that
074                                            // the server returned when the client initiated the
075                                            // authentication exchange. If this value is not null or
076                                            // empty, *this value* has to be sent back to the server
077                                            // in the client's response to the server's challenge
078                                            String text = rcb.getDefaultText();
079                                            // The SASL client (sc) created in smack uses
080                                            // rcb.getText when creating the negotiatedRealm to send
081                                            // it back to the server. Make sure that this value
082                                            // matches the server's realm
083                                            rcb.setText(text);
084                                        }
085                                        else if (callbacks[i] instanceof RealmChoiceCallback) {
086                                            // unused, prevents UnsupportedCallbackException
087                                            // RealmChoiceCallback rccb =
088                                            // (RealmChoiceCallback)callbacks[i];
089                                        }
090                                        else {
091                                            throw new UnsupportedCallbackException(callbacks[i]);
092                                        }
093                                    }
094                                }
095
096                            });
097        }
098        catch (SaslException e) {
099            throw new SmackException(e);
100        }
101    }
102
103    @Override
104    protected void authenticateInternal(CallbackHandler cbh)
105                    throws SmackException {
106        String[] mechanisms = { getName() };
107        Map<String, String> props = getSaslProps();
108        try {
109            sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
110        }
111        catch (SaslException e) {
112            throw new SmackException(e);
113        }
114    }
115
116    @Override
117    protected byte[] getAuthenticationText() throws SmackException {
118        if (sc.hasInitialResponse()) {
119            try {
120                return sc.evaluateChallenge(new byte[0]);
121            }
122            catch (SaslException e) {
123                throw new SmackException(e);
124            }
125        }
126        return null;
127    }
128
129    @Override
130    protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
131        try {
132            if (challenge != null) {
133                return sc.evaluateChallenge(challenge);
134            }
135            else {
136                return sc.evaluateChallenge(new byte[0]);
137            }
138        }
139        catch (SaslException e) {
140            throw new SmackException(e);
141        }
142    }
143
144    protected Map<String,String> getSaslProps() {
145        return new HashMap<String, String>();
146    }
147
148    protected String getServerName() {
149        return serviceName;
150    }
151}