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