blob: 3aeba86051826464a722a3d63dd4442aec918bc6 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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
21package org.jivesoftware.smack.sasl;
22
23import org.jivesoftware.smack.XMPPException;
24import org.jivesoftware.smack.SASLAuthentication;
25import org.jivesoftware.smack.packet.Packet;
26import org.jivesoftware.smack.util.StringUtils;
27
28import java.io.IOException;
29import java.util.Map;
30import java.util.HashMap;
31import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
32import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
33import org.apache.harmony.javax.security.auth.callback.Callback;
34import org.apache.harmony.javax.security.auth.callback.NameCallback;
35import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
36import org.apache.harmony.javax.security.sasl.RealmCallback;
37import org.apache.harmony.javax.security.sasl.RealmChoiceCallback;
38import de.measite.smack.Sasl;
39import org.apache.harmony.javax.security.sasl.SaslClient;
40import 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 */
57public 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}