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