Home | History | Annotate | Download | only in sasl
      1 /**
      2  * $RCSfile$
      3  * $Revision$
      4  * $Date$
      5  *
      6  * Copyright 2003-2007 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smack.sasl;
     22 
     23 import org.jivesoftware.smack.XMPPException;
     24 import org.jivesoftware.smack.SASLAuthentication;
     25 import org.jivesoftware.smack.packet.Packet;
     26 import org.jivesoftware.smack.util.StringUtils;
     27 
     28 import java.io.IOException;
     29 import java.util.Map;
     30 import java.util.HashMap;
     31 import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
     32 import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
     33 import org.apache.harmony.javax.security.auth.callback.Callback;
     34 import org.apache.harmony.javax.security.auth.callback.NameCallback;
     35 import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
     36 import org.apache.harmony.javax.security.sasl.RealmCallback;
     37 import org.apache.harmony.javax.security.sasl.RealmChoiceCallback;
     38 import de.measite.smack.Sasl;
     39 import org.apache.harmony.javax.security.sasl.SaslClient;
     40 import org.apache.harmony.javax.security.sasl.SaslException;
     41 
     42 /**
     43  * Base class for SASL mechanisms. Subclasses must implement these methods:
     44  * <ul>
     45  *  <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li>
     46  * </ul>
     47  * Subclasses will likely want to implement their own versions of these mthods:
     48  *  <li>{@link #authenticate(String, String, String)} -- Initiate authentication stanza using the
     49  *  deprecated method.</li>
     50  *  <li>{@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza
     51  *  using the CallbackHandler method.</li>
     52  *  <li>{@link #challengeReceived(String)} -- Handle a challenge from the server.</li>
     53  * </ul>
     54  *
     55  * @author Jay Kline
     56  */
     57 public abstract class SASLMechanism implements CallbackHandler {
     58 
     59     private SASLAuthentication saslAuthentication;
     60     protected SaslClient sc;
     61     protected String authenticationId;
     62     protected String password;
     63     protected String hostname;
     64 
     65 
     66     public SASLMechanism(SASLAuthentication saslAuthentication) {
     67         this.saslAuthentication = saslAuthentication;
     68     }
     69 
     70     /**
     71      * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
     72      * authentication is not recommended, since it is very inflexable.  Use
     73      * {@link #authenticate(String, String, CallbackHandler)} whenever possible.
     74      *
     75      * @param username the username of the user being authenticated.
     76      * @param host     the hostname where the user account resides.
     77      * @param password the password for this account.
     78      * @throws IOException If a network error occurs while authenticating.
     79      * @throws XMPPException If a protocol error occurs or the user is not authenticated.
     80      */
     81     public void authenticate(String username, String host, String password) throws IOException, XMPPException {
     82         //Since we were not provided with a CallbackHandler, we will use our own with the given
     83         //information
     84 
     85         //Set the authenticationID as the username, since they must be the same in this case.
     86         this.authenticationId = username;
     87         this.password = password;
     88         this.hostname = host;
     89 
     90         String[] mechanisms = { getName() };
     91         Map<String,String> props = new HashMap<String,String>();
     92         sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this);
     93         authenticate();
     94     }
     95 
     96     /**
     97      * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
     98      * any additional information, such as the authentication ID or realm, if it is needed.
     99      *
    100      * @param username the username of the user being authenticated.
    101      * @param host     the hostname where the user account resides.
    102      * @param cbh      the CallbackHandler to obtain user information.
    103      * @throws IOException If a network error occures while authenticating.
    104      * @throws XMPPException If a protocol error occurs or the user is not authenticated.
    105      */
    106     public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException {
    107         String[] mechanisms = { getName() };
    108         Map<String,String> props = new HashMap<String,String>();
    109         sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh);
    110         authenticate();
    111     }
    112 
    113     protected void authenticate() throws IOException, XMPPException {
    114         String authenticationText = null;
    115         try {
    116             if(sc.hasInitialResponse()) {
    117                 byte[] response = sc.evaluateChallenge(new byte[0]);
    118                 authenticationText = StringUtils.encodeBase64(response, false);
    119             }
    120         } catch (SaslException e) {
    121             throw new XMPPException("SASL authentication failed", e);
    122         }
    123 
    124         // Send the authentication to the server
    125         getSASLAuthentication().send(new AuthMechanism(getName(), authenticationText));
    126     }
    127 
    128 
    129     /**
    130      * The server is challenging the SASL mechanism for the stanza he just sent. Send a
    131      * response to the server's challenge.
    132      *
    133      * @param challenge a base64 encoded string representing the challenge.
    134      * @throws IOException if an exception sending the response occurs.
    135      */
    136     public void challengeReceived(String challenge) throws IOException {
    137         byte response[];
    138         if(challenge != null) {
    139             response = sc.evaluateChallenge(StringUtils.decodeBase64(challenge));
    140         } else {
    141             response = sc.evaluateChallenge(new byte[0]);
    142         }
    143 
    144         Packet responseStanza;
    145         if (response == null) {
    146             responseStanza = new Response();
    147         }
    148         else {
    149             responseStanza = new Response(StringUtils.encodeBase64(response, false));
    150         }
    151 
    152         // Send the authentication to the server
    153         getSASLAuthentication().send(responseStanza);
    154     }
    155 
    156     /**
    157      * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI.
    158      *
    159      * @return the common name of the SASL mechanism.
    160      */
    161     protected abstract String getName();
    162 
    163 
    164     protected SASLAuthentication getSASLAuthentication() {
    165         return saslAuthentication;
    166     }
    167 
    168     /**
    169      *
    170      */
    171     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    172         for (int i = 0; i < callbacks.length; i++) {
    173             if (callbacks[i] instanceof NameCallback) {
    174                 NameCallback ncb = (NameCallback)callbacks[i];
    175                 ncb.setName(authenticationId);
    176             } else if(callbacks[i] instanceof PasswordCallback) {
    177                 PasswordCallback pcb = (PasswordCallback)callbacks[i];
    178                 pcb.setPassword(password.toCharArray());
    179             } else if(callbacks[i] instanceof RealmCallback) {
    180                 RealmCallback rcb = (RealmCallback)callbacks[i];
    181                 rcb.setText(hostname);
    182             } else if(callbacks[i] instanceof RealmChoiceCallback){
    183                 //unused
    184                 //RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i];
    185             } else {
    186                throw new UnsupportedCallbackException(callbacks[i]);
    187             }
    188          }
    189     }
    190 
    191     /**
    192      * Initiating SASL authentication by select a mechanism.
    193      */
    194     public class AuthMechanism extends Packet {
    195         final private String name;
    196         final private String authenticationText;
    197 
    198         public AuthMechanism(String name, String authenticationText) {
    199             if (name == null) {
    200                 throw new NullPointerException("SASL mechanism name shouldn't be null.");
    201             }
    202             this.name = name;
    203             this.authenticationText = authenticationText;
    204         }
    205 
    206         public String toXML() {
    207             StringBuilder stanza = new StringBuilder();
    208             stanza.append("<auth mechanism=\"").append(name);
    209             stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
    210             if (authenticationText != null &&
    211                     authenticationText.trim().length() > 0) {
    212                 stanza.append(authenticationText);
    213             }
    214             stanza.append("</auth>");
    215             return stanza.toString();
    216         }
    217     }
    218 
    219     /**
    220      * A SASL challenge stanza.
    221      */
    222     public static class Challenge extends Packet {
    223         final private String data;
    224 
    225         public Challenge(String data) {
    226             this.data = data;
    227         }
    228 
    229         public String toXML() {
    230             StringBuilder stanza = new StringBuilder();
    231             stanza.append("<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
    232             if (data != null &&
    233                     data.trim().length() > 0) {
    234                 stanza.append(data);
    235             }
    236             stanza.append("</challenge>");
    237             return stanza.toString();
    238         }
    239     }
    240 
    241     /**
    242      * A SASL response stanza.
    243      */
    244     public class Response extends Packet {
    245         final private String authenticationText;
    246 
    247         public Response() {
    248             authenticationText = null;
    249         }
    250 
    251         public Response(String authenticationText) {
    252             if (authenticationText == null || authenticationText.trim().length() == 0) {
    253                 this.authenticationText = null;
    254             }
    255             else {
    256                 this.authenticationText = authenticationText;
    257             }
    258         }
    259 
    260         public String toXML() {
    261             StringBuilder stanza = new StringBuilder();
    262             stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
    263             if (authenticationText != null) {
    264                 stanza.append(authenticationText);
    265             }
    266             stanza.append("</response>");
    267             return stanza.toString();
    268         }
    269     }
    270 
    271     /**
    272      * A SASL success stanza.
    273      */
    274     public static class Success extends Packet {
    275         final private String data;
    276 
    277         public Success(String data) {
    278             this.data = data;
    279         }
    280 
    281         public String toXML() {
    282             StringBuilder stanza = new StringBuilder();
    283             stanza.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
    284             if (data != null &&
    285                     data.trim().length() > 0) {
    286                 stanza.append(data);
    287             }
    288             stanza.append("</success>");
    289             return stanza.toString();
    290         }
    291     }
    292 
    293     /**
    294      * A SASL failure stanza.
    295      */
    296     public static class Failure extends Packet {
    297         final private String condition;
    298 
    299         public Failure(String condition) {
    300             this.condition = condition;
    301         }
    302 
    303         /**
    304          * Get the SASL related error condition.
    305          *
    306          * @return the SASL related error condition.
    307          */
    308         public String getCondition() {
    309             return condition;
    310         }
    311 
    312         public String toXML() {
    313             StringBuilder stanza = new StringBuilder();
    314             stanza.append("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
    315             if (condition != null &&
    316                     condition.trim().length() > 0) {
    317                 stanza.append("<").append(condition).append("/>");
    318             }
    319             stanza.append("</failure>");
    320             return stanza.toString();
    321         }
    322     }
    323 }
    324