001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.ldap.handlers; 021 022 023 import java.util.Map; 024 025 import javax.security.sasl.SaslException; 026 import javax.security.sasl.SaslServer; 027 028 import org.apache.directory.server.core.CoreSession; 029 import org.apache.directory.server.core.DirectoryService; 030 import org.apache.directory.server.core.LdapPrincipal; 031 import org.apache.directory.server.core.entry.ClonedServerEntry; 032 import org.apache.directory.server.core.interceptor.context.BindOperationContext; 033 import org.apache.directory.server.i18n.I18n; 034 import org.apache.directory.server.ldap.LdapProtocolUtils; 035 import org.apache.directory.server.ldap.LdapSession; 036 import org.apache.directory.server.ldap.handlers.bind.MechanismHandler; 037 import org.apache.directory.server.ldap.handlers.bind.SaslConstants; 038 import org.apache.directory.shared.ldap.constants.SchemaConstants; 039 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException; 040 import org.apache.directory.shared.ldap.exception.LdapException; 041 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException; 042 import org.apache.directory.shared.ldap.exception.LdapUnwillingToPerformException; 043 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 044 import org.apache.directory.shared.ldap.message.internal.InternalBindRequest; 045 import org.apache.directory.shared.ldap.message.internal.InternalBindResponse; 046 import org.apache.directory.shared.ldap.message.internal.InternalLdapResult; 047 import org.apache.directory.shared.ldap.name.DN; 048 import org.apache.directory.shared.ldap.util.ExceptionUtils; 049 import org.apache.directory.shared.ldap.util.StringTools; 050 import org.slf4j.Logger; 051 import org.slf4j.LoggerFactory; 052 053 054 /** 055 * A single reply handler for {@link InternalBindRequest}s. 056 * 057 * Implements server-side of RFC 2222, sections 4.2 and 4.3. 058 * 059 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 060 * @version $Rev: 664302 $, $Date: 2008-06-07 04:44:00 -0400 (Sat, 07 Jun 2008) $ 061 */ 062 public class BindHandler extends LdapRequestHandler<InternalBindRequest> 063 { 064 private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class ); 065 066 /** A Hashed Adapter mapping SASL mechanisms to their handlers. */ 067 private Map<String, MechanismHandler> handlers; 068 069 070 /** 071 * Set the mechanisms handler map. 072 * 073 * @param handlers The associations btween a machanism and its handler 074 */ 075 public void setSaslMechanismHandlers( Map<String, MechanismHandler> handlers ) 076 { 077 this.handlers = handlers; 078 } 079 080 081 /** 082 * Handle the Simple authentication. 083 * 084 * @param session The associated Session 085 * @param message The BindRequest received 086 * @throws Exception If the authentication cannot be done 087 */ 088 public void handleSimpleAuth( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception 089 { 090 // if the user is already bound, we have to unbind him 091 if ( ldapSession.isAuthenticated() ) 092 { 093 // We already have a bound session for this user. We have to 094 // abandon it first. 095 ldapSession.getCoreSession().unbind(); 096 } 097 098 // Set the status to SimpleAuthPending 099 ldapSession.setSimpleAuthPending(); 100 101 // Now, bind the user 102 103 // create a new Bind context, with a null session, as we don't have 104 // any context yet. 105 BindOperationContext opContext = new BindOperationContext( null ); 106 107 // Stores the DN of the user to check, and its password 108 opContext.setDn( bindRequest.getName() ); 109 opContext.setCredentials( bindRequest.getCredentials() ); 110 111 // Stores the request controls into the operation context 112 LdapProtocolUtils.setRequestControls( opContext, bindRequest ); 113 114 try 115 { 116 /* 117 * Referral handling as specified by RFC 3296 here: 118 * 119 * http://www.faqs.org/rfcs/rfc3296.html 120 * 121 * See section 5.6.1 where if the bind principal DN is a referral 122 * we return an invalidCredentials result response. Optionally we 123 * could support delegated authentication in the future with this 124 * potential. See the following JIRA for more on this possibility: 125 * 126 * https://issues.apache.org/jira/browse/DIRSERVER-1217 127 * 128 * NOTE: if this is done then this handler should extend the 129 * a modified form of the ReferralAwareRequestHandler so it can 130 * detect conditions where ancestors of the DN are referrals 131 * and delegate appropriately. 132 */ 133 ClonedServerEntry principalEntry = null; 134 135 try 136 { 137 principalEntry = getLdapServer().getDirectoryService().getAdminSession().lookup( bindRequest.getName() ); 138 } 139 catch ( LdapException le ) 140 { 141 // this is OK 142 } 143 144 if ( principalEntry == null ) 145 { 146 LOG.info( "The {} principalDN cannot be found in the server : bind failure.", bindRequest.getName() ); 147 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult(); 148 result.setErrorMessage( "cannot bind the principalDn." ); 149 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); 150 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 151 return; 152 } 153 154 if ( principalEntry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT, 155 SchemaConstants.REFERRAL_OC ) ) 156 { 157 LOG.info( "Bind principalDn points to referral." ); 158 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult(); 159 result.setErrorMessage( "Bind principalDn points to referral." ); 160 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); 161 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 162 return; 163 } 164 165 // TODO - might cause issues since lookups are not returning all 166 // attributes right now - this is an optimization that can be 167 // enabled later after determining whether or not this will cause 168 // issues. 169 // reuse the looked up entry so we don't incur another lookup 170 // opContext.setEntry( principalEntry ); 171 172 // And call the OperationManager bind operation. 173 getLdapServer().getDirectoryService().getOperationManager().bind( opContext ); 174 175 // As a result, store the created session in the Core Session 176 ldapSession.setCoreSession( opContext.getSession() ); 177 178 // And set the current state accordingly 179 if ( !ldapSession.getCoreSession().isAnonymous() ) 180 { 181 ldapSession.setAuthenticated(); 182 } 183 else 184 { 185 ldapSession.setAnonymous(); 186 } 187 188 // Return the successful response 189 sendBindSuccess( ldapSession, bindRequest, null ); 190 } 191 catch ( Exception e ) 192 { 193 // Something went wrong. Write back an error message 194 // For BindRequest, it should be an InvalidCredentials, 195 // no matter what kind of exception we got. 196 ResultCodeEnum code = null; 197 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult(); 198 199 if ( e instanceof LdapUnwillingToPerformException ) 200 { 201 code = ResultCodeEnum.UNWILLING_TO_PERFORM; 202 result.setResultCode( code ); 203 } 204 else if ( e instanceof LdapInvalidDnException ) 205 { 206 code = ResultCodeEnum.INVALID_DN_SYNTAX; 207 result.setResultCode( code ); 208 } 209 else 210 { 211 code = ResultCodeEnum.INVALID_CREDENTIALS; 212 result.setResultCode( code ); 213 } 214 215 String msg = code.toString() + ": Bind failed: " + e.getLocalizedMessage(); 216 217 if ( LOG.isDebugEnabled() ) 218 { 219 msg += ":\n" + ExceptionUtils.getStackTrace( e ); 220 msg += "\n\nBindRequest = \n" + bindRequest.toString(); 221 } 222 223 DN dn = null; 224 225 if ( e instanceof LdapAuthenticationException ) 226 { 227 dn = ( ( LdapAuthenticationException ) e ).getResolvedDn(); 228 } 229 230 if ( ( dn != null ) 231 && ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM ) 232 || ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) ) 233 { 234 result.setMatchedDn( dn ); 235 } 236 237 result.setErrorMessage( msg ); 238 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 239 } 240 } 241 242 243 /** 244 * Check if the mechanism exists. 245 */ 246 private boolean checkMechanism( LdapSession ldapSession, String saslMechanism ) throws Exception 247 { 248 // Guard clause: Reject unsupported SASL mechanisms. 249 if ( !ldapServer.getSupportedMechanisms().contains( saslMechanism ) ) 250 { 251 LOG.error( I18n.err( I18n.ERR_160, saslMechanism ) ); 252 253 return false; 254 } 255 else 256 { 257 return true; 258 } 259 } 260 261 262 /** 263 * For challenge/response exchange, generate the challenge. 264 * If the exchange is complete then send bind success. 265 * 266 * @param ldapSession 267 * @param ss 268 * @param bindRequest 269 */ 270 private void generateSaslChallengeOrComplete( LdapSession ldapSession, SaslServer ss, 271 InternalBindRequest bindRequest ) throws Exception 272 { 273 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult(); 274 275 // SaslServer will throw an exception if the credentials are null. 276 if ( bindRequest.getCredentials() == null ) 277 { 278 bindRequest.setCredentials( StringTools.EMPTY_BYTES ); 279 } 280 281 try 282 { 283 // Compute the challenge 284 byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() ); 285 286 if ( ss.isComplete() ) 287 { 288 // This is the end of the C/R exchange 289 if ( tokenBytes != null ) 290 { 291 /* 292 * There may be a token to return to the client. We set it here 293 * so it will be returned in a SUCCESS message, after an LdapContext 294 * has been initialized for the client. 295 */ 296 ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes ); 297 } 298 299 LdapPrincipal ldapPrincipal = ( LdapPrincipal ) ldapSession 300 .getSaslProperty( SaslConstants.SASL_AUTHENT_USER ); 301 if ( ldapPrincipal != null ) 302 { 303 DirectoryService ds = ldapSession.getLdapServer().getDirectoryService(); 304 String saslMechanism = bindRequest.getSaslMechanism(); 305 CoreSession userSession = ds.getSession( ldapPrincipal.getClonedName(), ldapPrincipal 306 .getUserPassword(), saslMechanism, null ); 307 308 // Set the user session into the ldap session 309 ldapSession.setCoreSession( userSession ); 310 } 311 312 // Mark the user as authenticated 313 ldapSession.setAuthenticated(); 314 315 // Call the cleanup method for the selected mechanism 316 MechanismHandler handler = ( MechanismHandler ) ldapSession 317 .getSaslProperty( SaslConstants.SASL_MECH_HANDLER ); 318 handler.cleanup( ldapSession ); 319 320 // Return the successful response 321 sendBindSuccess( ldapSession, bindRequest, tokenBytes ); 322 } 323 else 324 { 325 // The SASL bind must continue, we are sending the computed challenge 326 LOG.info( "Continuation token had length " + tokenBytes.length ); 327 328 // Build the response 329 result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS ); 330 InternalBindResponse resp = ( InternalBindResponse ) bindRequest.getResultResponse(); 331 332 // Store the challenge 333 resp.setServerSaslCreds( tokenBytes ); 334 335 // Switch to SASLAuthPending 336 ldapSession.setSaslAuthPending(); 337 338 // And write back the response 339 ldapSession.getIoSession().write( resp ); 340 LOG.debug( "Returning final authentication data to client to complete context." ); 341 } 342 } 343 catch ( SaslException se ) 344 { 345 sendInvalidCredentials( ldapSession, bindRequest, se ); 346 } 347 } 348 349 350 /** 351 * Send back an AUTH-METH-NOT-SUPPORTED error message to the client 352 */ 353 private void sendAuthMethNotSupported( LdapSession ldapSession, InternalBindRequest bindRequest ) 354 { 355 // First, r-einit the state to Anonymous, and clear the 356 // saslProperty map 357 ldapSession.clearSaslProperties(); 358 ldapSession.setAnonymous(); 359 360 // And send the response to the client 361 InternalLdapResult bindResult = bindRequest.getResultResponse().getLdapResult(); 362 bindResult.setResultCode( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED ); 363 bindResult.setErrorMessage( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED.toString() + ": " 364 + bindRequest.getSaslMechanism() + " is not a supported mechanism." ); 365 366 // Write back the error 367 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 368 369 return; 370 } 371 372 373 /** 374 * Send back an INVALID-CREDENTIAL error message to the user. If we have an exception 375 * as a third argument, then send back the associated message to the client. 376 */ 377 private void sendInvalidCredentials( LdapSession ldapSession, InternalBindRequest bindRequest, Exception e ) 378 { 379 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult(); 380 381 String message = ""; 382 383 if ( e != null ) 384 { 385 message = ResultCodeEnum.INVALID_CREDENTIALS + ": " + e.getLocalizedMessage(); 386 } 387 else 388 { 389 message = ResultCodeEnum.INVALID_CREDENTIALS.toString(); 390 } 391 392 LOG.error( message ); 393 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); 394 result.setErrorMessage( message ); 395 396 // Reinitialize the state to Anonymous and clear the sasl properties 397 ldapSession.clearSaslProperties(); 398 ldapSession.setAnonymous(); 399 400 // Write back the error response 401 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 402 } 403 404 405 /** 406 * Send a SUCCESS message back to the client. 407 */ 408 private void sendBindSuccess( LdapSession ldapSession, InternalBindRequest bindRequest, byte[] tokenBytes ) 409 { 410 // Return the successful response 411 InternalBindResponse response = ( InternalBindResponse ) bindRequest.getResultResponse(); 412 response.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS ); 413 response.setServerSaslCreds( tokenBytes ); 414 415 if ( !ldapSession.getCoreSession().isAnonymous() ) 416 { 417 // If we have not been asked to authenticate as Anonymous, authenticate the user 418 ldapSession.setAuthenticated(); 419 } 420 else 421 { 422 // Otherwise, switch back to Anonymous 423 ldapSession.setAnonymous(); 424 } 425 426 // Clean the SaslProperties, we don't need them anymore 427 MechanismHandler handler = ( MechanismHandler ) ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER ); 428 429 if ( handler != null ) 430 { 431 handler.cleanup( ldapSession ); 432 } 433 434 ldapSession.getIoSession().write( response ); 435 436 LOG.debug( "Returned SUCCESS message: {}.", response ); 437 } 438 439 440 private void handleSaslAuthPending( LdapSession ldapSession, InternalBindRequest bindRequest, DirectoryService ds ) 441 throws Exception 442 { 443 // First, check that we have the same mechanism 444 String saslMechanism = bindRequest.getSaslMechanism(); 445 446 // The empty mechanism is also a request for a new Bind session 447 if ( StringTools.isEmpty( saslMechanism ) 448 || !ldapSession.getSaslProperty( SaslConstants.SASL_MECH ).equals( saslMechanism ) ) 449 { 450 sendAuthMethNotSupported( ldapSession, bindRequest ); 451 return; 452 } 453 454 // We have already received a first BindRequest, and sent back some challenge. 455 // First, check if the mechanism is the same 456 MechanismHandler mechanismHandler = handlers.get( saslMechanism ); 457 458 if ( mechanismHandler == null ) 459 { 460 String message = I18n.err( I18n.ERR_161, saslMechanism ); 461 462 // Clear the saslProperties, and move to the anonymous state 463 ldapSession.clearSaslProperties(); 464 ldapSession.setAnonymous(); 465 466 LOG.error( message ); 467 throw new IllegalArgumentException( message ); 468 } 469 470 // Get the previously created SaslServer instance 471 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest ); 472 473 generateSaslChallengeOrComplete( ldapSession, ss, bindRequest ); 474 } 475 476 477 /** 478 * Handle the SASL authentication. If the mechanism is known, we are 479 * facing three cases : 480 * <ul> 481 * <li>The user does not has a session yet</li> 482 * <li>The user already has a session</li> 483 * <li>The user has started a SASL negotiation</li> 484 * </lu><br/> 485 * 486 * In the first case, we initiate a SaslBind session, which will be used all 487 * along the negotiation.<br/> 488 * In the second case, we first have to unbind the user, and initiate a new 489 * SaslBind session.<br/> 490 * In the third case, we have sub cases : 491 * <ul> 492 * <li>The mechanism is not provided : that means the user want to reset the 493 * current negotiation. We move back to an Anonymous state</li> 494 * <li>The mechanism is provided : the user is initializing a new negotiation 495 * with another mechanism. The current SaslBind session is reinitialized</li> 496 * <li></li> 497 * </ul><br/> 498 * 499 * @param session The associated Session 500 * @param message The BindRequest received 501 * @throws Exception If the authentication cannot be done 502 */ 503 public void handleSaslAuth( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception 504 { 505 String saslMechanism = bindRequest.getSaslMechanism(); 506 DirectoryService ds = getLdapServer().getDirectoryService(); 507 508 // Case #2 : the user does have a session. We have to unbind him 509 if ( ldapSession.isAuthenticated() ) 510 { 511 // We already have a bound session for this user. We have to 512 // close the previous session first. 513 ldapSession.getCoreSession().unbind(); 514 515 // Reset the status to Anonymous 516 ldapSession.setAnonymous(); 517 518 // Clean the sasl properties 519 ldapSession.clearSaslProperties(); 520 521 // Now we can continue as if the client was Anonymous from the beginning 522 } 523 524 // case #1 : The user does not have a session. 525 if ( ldapSession.isAnonymous() ) 526 { 527 if ( !StringTools.isEmpty( saslMechanism ) ) 528 { 529 // fist check that the mechanism exists 530 if ( !checkMechanism( ldapSession, saslMechanism ) ) 531 { 532 // get out ! 533 sendAuthMethNotSupported( ldapSession, bindRequest ); 534 535 return; 536 } 537 538 // Store the mechanism in the ldap session 539 ldapSession.putSaslProperty( SaslConstants.SASL_MECH, saslMechanism ); 540 541 // Get the handler for this mechanism 542 MechanismHandler mechanismHandler = handlers.get( saslMechanism ); 543 544 // Store the mechanism handler in the salsProperties 545 ldapSession.putSaslProperty( SaslConstants.SASL_MECH_HANDLER, mechanismHandler ); 546 547 // Initialize the mechanism specific data 548 mechanismHandler.init( ldapSession ); 549 550 // Get the SaslServer instance which manage the C/R exchange 551 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest ); 552 553 // We have to generate a challenge 554 generateSaslChallengeOrComplete( ldapSession, ss, bindRequest ); 555 556 // And get back 557 return; 558 } 559 } 560 else if ( ldapSession.isAuthPending() ) 561 { 562 try 563 { 564 handleSaslAuthPending( ldapSession, bindRequest, ds ); 565 } 566 catch ( SaslException se ) 567 { 568 sendInvalidCredentials( ldapSession, bindRequest, se ); 569 } 570 571 return; 572 } 573 } 574 575 576 /** 577 * Deal with a received BindRequest 578 * 579 * @param session The current session 580 * @param bindRequest The received BindRequest 581 * @throws Exception If the authentication cannot be handled 582 */ 583 @Override 584 public void handle( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception 585 { 586 LOG.debug( "Received: {}", bindRequest ); 587 588 // Guard clause: LDAP version 3 589 if ( !bindRequest.getVersion3() ) 590 { 591 LOG.error( I18n.err( I18n.ERR_162 ) ); 592 InternalLdapResult bindResult = bindRequest.getResultResponse().getLdapResult(); 593 bindResult.setResultCode( ResultCodeEnum.PROTOCOL_ERROR ); 594 bindResult.setErrorMessage( I18n.err( I18n.ERR_163 ) ); 595 ldapSession.getIoSession().write( bindRequest.getResultResponse() ); 596 return; 597 } 598 599 // Deal with the two kinds of authentication : Simple and SASL 600 if ( bindRequest.isSimple() ) 601 { 602 handleSimpleAuth( ldapSession, bindRequest ); 603 } 604 else 605 { 606 handleSaslAuth( ldapSession, bindRequest ); 607 } 608 } 609 }