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