001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.extensions;
028 import org.opends.messages.Message;
029
030
031
032 import java.security.PrivilegedExceptionAction;
033 import java.util.HashMap;
034 import javax.security.auth.Subject;
035 import javax.security.auth.callback.Callback;
036 import javax.security.auth.callback.CallbackHandler;
037 import javax.security.auth.callback.NameCallback;
038 import javax.security.auth.callback.UnsupportedCallbackException;
039 import javax.security.auth.login.LoginContext;
040 import javax.security.sasl.AuthorizeCallback;
041 import javax.security.sasl.Sasl;
042 import javax.security.sasl.SaslServer;
043
044 import org.opends.server.api.ClientConnection;
045 import org.opends.server.core.BindOperation;
046 import org.opends.server.core.DirectoryServer;
047 import org.opends.server.protocols.asn1.ASN1OctetString;
048 import org.opends.server.types.AuthenticationInfo;
049 import org.opends.server.types.ByteString;
050 import org.opends.server.types.DirectoryException;
051 import org.opends.server.types.Entry;
052 import org.opends.server.types.InitializationException;
053 import org.opends.server.types.ResultCode;
054
055 import static org.opends.server.loggers.debug.DebugLogger.*;
056 import org.opends.server.loggers.debug.DebugTracer;
057 import org.opends.server.types.DebugLogLevel;
058 import static org.opends.messages.ExtensionMessages.*;
059 import static org.opends.server.util.ServerConstants.*;
060 import static org.opends.server.util.StaticUtils.*;
061
062
063
064 /**
065 * This class defines a data structure that holds state information needed for
066 * processing a SASL GSSAPI bind from a client.
067 */
068 public class GSSAPIStateInfo
069 implements PrivilegedExceptionAction<Boolean>, CallbackHandler
070 {
071 /**
072 * The tracer object for the debug logger.
073 */
074 private static final DebugTracer TRACER = getTracer();
075
076
077
078
079 // The bind operation with which this state is associated.
080 private BindOperation bindOperation;
081
082 // The client connection with which this state is associated.
083 private ClientConnection clientConnection;
084
085 // The entry of the user that authenticated in this session.
086 private Entry userEntry;
087
088 // The GSSAPI authentication handler that created this state information.
089 private GSSAPISASLMechanismHandler gssapiHandler;
090
091 // The login context used to perform server-side authentication.
092 private LoginContext loginContext;
093
094 // The SASL server that will be used to actually perform the authentication.
095 private SaslServer saslServer;
096
097 // The protocol that the client is using to communicate with the server.
098 private String protocol;
099
100 // The FQDN of this system to use in the authentication process.
101 private String serverFQDN;
102
103
104
105
106 /**
107 * Creates a new GSSAPI state info structure with the provided information.
108 *
109 * @param gssapiHandler The GSSAPI authentication handler that created this
110 * state information.
111 * @param bindOperation The bind operation with which this state is
112 * associated.
113 * @param serverFQDN The fully-qualified domain name for the server to
114 * use in the authentication process.
115 *
116 * @throws InitializationException If it is not possible to authenticate to
117 * the KDC to verify the client credentials.
118 */
119 public GSSAPIStateInfo(GSSAPISASLMechanismHandler gssapiHandler,
120 BindOperation bindOperation, String serverFQDN)
121 throws InitializationException
122 {
123 this.gssapiHandler = gssapiHandler;
124 this.bindOperation = bindOperation;
125 this.serverFQDN = serverFQDN;
126
127 clientConnection = bindOperation.getClientConnection();
128 protocol = toLowerCase(clientConnection.getProtocol());
129 userEntry = null;
130
131
132 // Create the LoginContext and do the server-side authentication.
133 // FIXME -- Can this be moved to a one-time call in the GSSAPI handler
134 // rather than once per GSSAPI bind attempt?
135 try
136 {
137 loginContext =
138 new LoginContext(GSSAPISASLMechanismHandler.class.getName(), this);
139 }
140 catch (Exception e)
141 {
142 if (debugEnabled())
143 {
144 TRACER.debugCaught(DebugLogLevel.ERROR, e);
145 }
146
147 Message message = ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT.get(
148 getExceptionMessage(e));
149 throw new InitializationException(message, e);
150 }
151
152 try
153 {
154 loginContext.login();
155 }
156 catch (Exception e)
157 {
158 if (debugEnabled())
159 {
160 TRACER.debugCaught(DebugLogLevel.ERROR, e);
161 }
162
163 Message message =
164 ERR_SASLGSSAPI_CANNOT_AUTHENTICATE_SERVER.get(getExceptionMessage(e));
165 throw new InitializationException(message, e);
166 }
167
168
169 saslServer = null;
170 }
171
172
173
174 /**
175 * Sets the bind operation for the next stage of processing in the GSSAPI
176 * authentication. This must be called before the processing is performed so
177 * that the appropriate response may be sent to the client.
178 *
179 * @param bindOperation The bind operation for the next stage of processing
180 * in the GSSAPI authentication.
181 */
182 public void setBindOperation(BindOperation bindOperation)
183 {
184 this.bindOperation = bindOperation;
185 }
186
187
188
189 /**
190 * Retrieves the entry of the user that has authenticated on this GSSAPI
191 * session. This should only be available after a successful GSSAPI
192 * authentication. The return value of this method should be considered
193 * unreliable if GSSAPI authentication has not yet completed successfully.
194 *
195 * @return x
196 */
197 public Entry getUserEntry()
198 {
199 return userEntry;
200 }
201
202
203
204 /**
205 * Destroys any sensitive information that might be associated with the SASL
206 * server instance.
207 */
208 public void dispose()
209 {
210 try
211 {
212 saslServer.dispose();
213 }
214 catch (Exception e)
215 {
216 if (debugEnabled())
217 {
218 TRACER.debugCaught(DebugLogLevel.ERROR, e);
219 }
220 }
221 }
222
223
224
225 /**
226 * Processes the next stage of the GSSAPI bind process. This may be used for
227 * the first stage or any stage thereafter until the authentication is
228 * complete. It will automatically take care of the JAAS processing behind
229 * the scenes as necessary.
230 */
231 public void processAuthenticationStage()
232 {
233 try
234 {
235 Subject.doAs(loginContext.getSubject(), this);
236 }
237 catch (Exception e)
238 {
239 if (debugEnabled())
240 {
241 TRACER.debugCaught(DebugLogLevel.ERROR, e);
242 }
243 }
244 }
245
246
247
248 /**
249 * Processes a stage of the SASL GSSAPI bind request. The
250 * <CODE>setBindOperation</CODE> method must have been called to update the
251 * reference to the latest bind request before invoking this method through
252 * <CODE>doAs</CODE> or <CODE>doAsPrivileged</CODE>.
253 *
254 * @return <CODE>true</CODE> if there was no error during this stage of the
255 * bind and processing can continue, or <CODE>false</CODE> if an
256 * error occurred and and processing should not continue.
257 */
258 public Boolean run()
259 {
260 if (saslServer == null)
261 {
262 // Create the SASL server instance for use with this authentication
263 // attempt.
264 try
265 {
266 HashMap<String,String> saslProperties = new HashMap<String,String>();
267
268 // FIXME -- We need to add support for auth-int and auth-conf.
269 // propertyMap.put(Sasl.QOP, "auth,auth-int,auth-conf");
270 saslProperties.put(Sasl.QOP, "auth");
271
272 saslProperties.put(Sasl.REUSE, "false");
273
274 saslServer = Sasl.createSaslServer(SASL_MECHANISM_GSSAPI, protocol,
275 serverFQDN, saslProperties, this);
276 }
277 catch (Exception e)
278 {
279 if (debugEnabled())
280 {
281 TRACER.debugCaught(DebugLogLevel.ERROR, e);
282 }
283
284 Message message = ERR_SASLGSSAPI_CANNOT_CREATE_SASL_SERVER.get(
285 getExceptionMessage(e));
286
287 clientConnection.setSASLAuthStateInfo(null);
288 bindOperation.setAuthFailureReason(message);
289 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
290 return false;
291 }
292 }
293
294
295 // Get the SASL credentials from the bind request.
296 byte[] clientCredBytes;
297 ByteString clientCredentials = bindOperation.getSASLCredentials();
298 if (clientCredentials == null)
299 {
300 clientCredBytes = new byte[0];
301 }
302 else
303 {
304 clientCredBytes = clientCredentials.value();
305 }
306
307
308 // Process the client SASL credentials and get the data to include in the
309 // server SASL credentials of the response.
310 ASN1OctetString serverSASLCredentials;
311 try
312 {
313 byte[] serverCredBytes = saslServer.evaluateResponse(clientCredBytes);
314
315 if (serverCredBytes == null)
316 {
317 serverSASLCredentials = null;
318 }
319 else
320 {
321 serverSASLCredentials = new ASN1OctetString(serverCredBytes);
322 }
323 }
324 catch (Exception e)
325 {
326 if (debugEnabled())
327 {
328 TRACER.debugCaught(DebugLogLevel.ERROR, e);
329 }
330
331 try
332 {
333 saslServer.dispose();
334 }
335 catch (Exception e2)
336 {
337 if (debugEnabled())
338 {
339 TRACER.debugCaught(DebugLogLevel.ERROR, e2);
340 }
341 }
342
343 Message message = ERR_SASLGSSAPI_CANNOT_EVALUATE_RESPONSE.get(
344 getExceptionMessage(e));
345
346 clientConnection.setSASLAuthStateInfo(null);
347 bindOperation.setAuthFailureReason(message);
348 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
349 return false;
350 }
351
352
353 // If the authentication is not yet complete, then send a "SASL bind in
354 // progress" response to the client.
355 if (! saslServer.isComplete())
356 {
357 clientConnection.setSASLAuthStateInfo(saslServer);
358 bindOperation.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
359 bindOperation.setServerSASLCredentials(serverSASLCredentials);
360 return true;
361 }
362
363
364 // If the authentication is complete, then get the authorization ID from the
365 // SASL server and map that to a user in the directory.
366 String authzID = saslServer.getAuthorizationID();
367 if ((authzID == null) || (authzID.length() == 0))
368 {
369 try
370 {
371 saslServer.dispose();
372 }
373 catch (Exception e)
374 {
375 if (debugEnabled())
376 {
377 TRACER.debugCaught(DebugLogLevel.ERROR, e);
378 }
379 }
380
381 Message message = ERR_SASLGSSAPI_NO_AUTHZ_ID.get();
382
383 clientConnection.setSASLAuthStateInfo(null);
384 bindOperation.setAuthFailureReason(message);
385 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
386 return false;
387 }
388
389
390 try
391 {
392 userEntry = gssapiHandler.getUserForAuthzID(bindOperation, authzID);
393 }
394 catch (DirectoryException de)
395 {
396 if (debugEnabled())
397 {
398 TRACER.debugCaught(DebugLogLevel.ERROR, de);
399 }
400
401 try
402 {
403 saslServer.dispose();
404 }
405 catch (Exception e)
406 {
407 if (debugEnabled())
408 {
409 TRACER.debugCaught(DebugLogLevel.ERROR, e);
410 }
411 }
412
413 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
414 bindOperation.setAuthFailureReason(de.getMessageObject());
415 clientConnection.setSASLAuthStateInfo(null);
416 return false;
417 }
418
419
420 // If the user entry is null, then we couldn't map the authorization ID to
421 // a user.
422 if (userEntry == null)
423 {
424 try
425 {
426 saslServer.dispose();
427 }
428 catch (Exception e)
429 {
430 if (debugEnabled())
431 {
432 TRACER.debugCaught(DebugLogLevel.ERROR, e);
433 }
434 }
435
436 Message message = ERR_SASLGSSAPI_CANNOT_MAP_AUTHZID.get(authzID);
437
438 clientConnection.setSASLAuthStateInfo(null);
439 bindOperation.setAuthFailureReason(message);
440 bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
441 return false;
442 }
443 else
444 {
445 bindOperation.setSASLAuthUserEntry(userEntry);
446 }
447
448
449 // The authentication was successful, so set the proper state information
450 // in the client connection and return success.
451 AuthenticationInfo authInfo =
452 new AuthenticationInfo(userEntry, SASL_MECHANISM_GSSAPI,
453 DirectoryServer.isRootDN(userEntry.getDN()));
454 bindOperation.setAuthenticationInfo(authInfo);
455 bindOperation.setResultCode(ResultCode.SUCCESS);
456
457 // FIXME -- If we're using integrity or confidentiality, then we can't do
458 // this.
459 clientConnection.setSASLAuthStateInfo(null);
460 try
461 {
462 saslServer.dispose();
463 }
464 catch (Exception e)
465 {
466 if (debugEnabled())
467 {
468 TRACER.debugCaught(DebugLogLevel.ERROR, e);
469 }
470 }
471
472 return true;
473 }
474
475
476
477 /**
478 * Handles any callbacks that might be required in order to process a SASL
479 * GSSAPI bind on the server. In this case, if an authorization ID was
480 * provided, then a callback may be used to determine whether it is
481 * acceptable.
482 *
483 * @param callbacks The callbacks needed to provide information for the
484 * GSSAPI authentication process.
485 *
486 * @throws UnsupportedCallbackException If an unexpected callback is
487 * included in the provided set.
488 */
489 public void handle(Callback[] callbacks)
490 throws UnsupportedCallbackException
491 {
492 for (Callback callback : callbacks)
493 {
494 if (callback instanceof NameCallback)
495 {
496 String authID = toLowerCase(clientConnection.getProtocol()) + "/" +
497 serverFQDN;
498 ((NameCallback) callback).setName(authID);
499 }
500 else if (callback instanceof AuthorizeCallback)
501 {
502 // FIXME -- Should we allow an authzID different from the authID?
503 // FIXME -- Do we need to do anything else here?
504 AuthorizeCallback authzCallback = (AuthorizeCallback) callback;
505 String authID = authzCallback.getAuthenticationID();
506 String authzID = authzCallback.getAuthorizationID();
507
508 if (authID.equals(authzID))
509 {
510 authzCallback.setAuthorizedID(authzID);
511 authzCallback.setAuthorized(true);
512 }
513 else
514 {
515 Message message = ERR_SASLGSSAPI_DIFFERENT_AUTHID_AND_AUTHZID.get(
516 authID, authzID);
517 bindOperation.setAuthFailureReason(message);
518 authzCallback.setAuthorized(false);
519 }
520 }
521 else
522 {
523 // We weren't prepared for this type of callback.
524 Message message =
525 INFO_SASLGSSAPI_UNEXPECTED_CALLBACK.get(String.valueOf(callback));
526 throw new UnsupportedCallbackException(callback, message.toString());
527 }
528 }
529 }
530 }
531