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 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.workflowelement.localbackend; 028 029 030 031 import java.util.Iterator; 032 import java.util.LinkedHashSet; 033 import java.util.List; 034 import java.util.concurrent.locks.Lock; 035 036 import org.opends.messages.Message; 037 import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn; 038 import org.opends.server.api.Backend; 039 import org.opends.server.api.ClientConnection; 040 import org.opends.server.api.SASLMechanismHandler; 041 import org.opends.server.api.plugin.PluginResult; 042 import org.opends.server.controls.AuthorizationIdentityResponseControl; 043 import org.opends.server.controls.PasswordExpiredControl; 044 import org.opends.server.controls.PasswordExpiringControl; 045 import org.opends.server.controls.PasswordPolicyErrorType; 046 import org.opends.server.controls.PasswordPolicyResponseControl; 047 import org.opends.server.controls.PasswordPolicyWarningType; 048 import org.opends.server.core.AccessControlConfigManager; 049 import org.opends.server.core.BindOperation; 050 import org.opends.server.core.BindOperationWrapper; 051 import org.opends.server.core.DirectoryServer; 052 import org.opends.server.core.PasswordPolicy; 053 import org.opends.server.core.PasswordPolicyState; 054 import org.opends.server.core.PluginConfigManager; 055 import org.opends.server.loggers.debug.DebugTracer; 056 import org.opends.server.types.AccountStatusNotification; 057 import org.opends.server.types.AccountStatusNotificationType; 058 import org.opends.server.types.Attribute; 059 import org.opends.server.types.AttributeType; 060 import org.opends.server.types.AttributeValue; 061 import org.opends.server.types.AuthenticationInfo; 062 import org.opends.server.types.ByteString; 063 import org.opends.server.types.Control; 064 import org.opends.server.types.DebugLogLevel; 065 import org.opends.server.types.DirectoryException; 066 import org.opends.server.types.DN; 067 import org.opends.server.types.Entry; 068 import org.opends.server.types.LockManager; 069 import org.opends.server.types.ResultCode; 070 import org.opends.server.types.WritabilityMode; 071 import org.opends.server.types.operation.PostOperationBindOperation; 072 import org.opends.server.types.operation.PostResponseBindOperation; 073 import org.opends.server.types.operation.PreOperationBindOperation; 074 075 import static org.opends.messages.CoreMessages.*; 076 import static org.opends.server.config.ConfigConstants.*; 077 import static org.opends.server.loggers.ErrorLogger.*; 078 import static org.opends.server.loggers.debug.DebugLogger.*; 079 import static org.opends.server.util.ServerConstants.*; 080 import static org.opends.server.util.StaticUtils.*; 081 082 083 084 /** 085 * This class defines an operation used to bind against the Directory Server, 086 * with the bound user entry within a local backend. 087 */ 088 public class LocalBackendBindOperation 089 extends BindOperationWrapper 090 implements PreOperationBindOperation, PostOperationBindOperation, 091 PostResponseBindOperation 092 { 093 /** 094 * The tracer object for the debug logger. 095 */ 096 private static final DebugTracer TRACER = getTracer(); 097 098 099 100 // The backend in which the bind operation should be processed. 101 private Backend backend; 102 103 // Indicates whether the bind response should include the first warning for an 104 // upcoming password expiration. 105 private boolean isFirstWarning; 106 107 // Indicates whether this bind is using a grace login for the user. 108 private boolean isGraceLogin; 109 110 // Indicates whether the user must change his/her password before doing 111 // anything else. 112 private boolean mustChangePassword; 113 114 // Indicates whether the user requested the password policy control. 115 private boolean pwPolicyControlRequested; 116 117 // Indicates whether the server should return the authorization ID as a 118 // control in the bind response. 119 private boolean returnAuthzID; 120 121 // Indicates whether to execute post-operation plugins. 122 private boolean executePostOpPlugins; 123 124 // The client connection associated with this bind operation. 125 private ClientConnection clientConnection; 126 127 // The bind DN provided by the client. 128 private DN bindDN; 129 130 // The entry of the user that successfully authenticated during processing for 131 // this bind operation. 132 private Entry authenticatedUserEntry; 133 134 // The lookthrough limit that should be enforced for the user. 135 private int lookthroughLimit; 136 137 // The value to use for the password policy warning. 138 private int pwPolicyWarningValue; 139 140 // The size limit that should be enforced for the user. 141 private int sizeLimit; 142 143 // The time limit that should be enforced for the user. 144 private int timeLimit; 145 146 // The idle time limit that should be enforced for the user. 147 private long idleTimeLimit; 148 149 // The password policy that applies to the user. 150 private PasswordPolicy policy; 151 152 // The password policy state for the user. 153 private PasswordPolicyState pwPolicyState; 154 155 // The password policy error type for this bind operation. 156 private PasswordPolicyErrorType pwPolicyErrorType; 157 158 // The password policy warning type for this bind operation. 159 private PasswordPolicyWarningType pwPolicyWarningType; 160 161 // The plugin config manager for the Directory Server. 162 private PluginConfigManager pluginConfigManager; 163 164 // The SASL mechanism used for this bind operation. 165 private String saslMechanism; 166 167 168 169 /** 170 * Creates a new operation that may be used to bind where 171 * the bound user entry is stored in a local backend of the Directory Server. 172 * 173 * @param bind The operation to enhance. 174 */ 175 public LocalBackendBindOperation(BindOperation bind) 176 { 177 super(bind); 178 LocalBackendWorkflowElement.attachLocalOperation (bind, this); 179 } 180 181 182 183 /** 184 * Process this bind operation in a local backend. 185 * 186 * @param backend The backend in which the bind operation should be 187 * processed. 188 */ 189 void processLocalBind(Backend backend) 190 { 191 this.backend = backend; 192 193 // Initialize a number of variables for use during the bind processing. 194 clientConnection = getClientConnection(); 195 returnAuthzID = false; 196 executePostOpPlugins = false; 197 sizeLimit = DirectoryServer.getSizeLimit(); 198 timeLimit = DirectoryServer.getTimeLimit(); 199 lookthroughLimit = DirectoryServer.getLookthroughLimit(); 200 idleTimeLimit = DirectoryServer.getIdleTimeLimit(); 201 bindDN = getBindDN(); 202 saslMechanism = getSASLMechanism(); 203 pwPolicyState = null; 204 pwPolicyErrorType = null; 205 pwPolicyControlRequested = false; 206 isGraceLogin = false; 207 isFirstWarning = false; 208 mustChangePassword = false; 209 pwPolicyWarningType = null; 210 pwPolicyWarningValue = -1 ; 211 authenticatedUserEntry = null; 212 pluginConfigManager = DirectoryServer.getPluginConfigManager(); 213 214 215 // Create a labeled block of code that we can break out of if a problem is 216 // detected. 217 bindProcessing: 218 { 219 // Check to see if the client has permission to perform the 220 // bind. 221 222 // FIXME: for now assume that this will check all permission 223 // pertinent to the operation. This includes any controls 224 // specified. 225 if (! AccessControlConfigManager.getInstance().getAccessControlHandler(). 226 isAllowed(this)) 227 { 228 setResultCode(ResultCode.INVALID_CREDENTIALS); 229 setAuthFailureReason(ERR_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get( 230 String.valueOf(bindDN))); 231 break bindProcessing; 232 } 233 234 // Check to see if there are any controls in the request. If so, then see 235 // if there is any special processing required. 236 try 237 { 238 handleRequestControls(); 239 } 240 catch (DirectoryException de) 241 { 242 if (debugEnabled()) 243 { 244 TRACER.debugCaught(DebugLogLevel.ERROR, de); 245 } 246 247 setResponseData(de); 248 break bindProcessing; 249 } 250 251 252 // Check to see if this is a simple bind or a SASL bind and process 253 // accordingly. 254 switch (getAuthenticationType()) 255 { 256 case SIMPLE: 257 try 258 { 259 if (! processSimpleBind()) 260 { 261 break bindProcessing; 262 } 263 } 264 catch (DirectoryException de) 265 { 266 if (debugEnabled()) 267 { 268 TRACER.debugCaught(DebugLogLevel.ERROR, de); 269 } 270 271 if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS) 272 { 273 setResultCode(ResultCode.INVALID_CREDENTIALS); 274 setAuthFailureReason(de.getMessageObject()); 275 } 276 else 277 { 278 setResponseData(de); 279 } 280 break bindProcessing; 281 } 282 break; 283 284 285 case SASL: 286 try 287 { 288 if (! processSASLBind()) 289 { 290 break bindProcessing; 291 } 292 } 293 catch (DirectoryException de) 294 { 295 if (debugEnabled()) 296 { 297 TRACER.debugCaught(DebugLogLevel.ERROR, de); 298 } 299 300 if (de.getResultCode() == ResultCode.INVALID_CREDENTIALS) 301 { 302 setResultCode(ResultCode.INVALID_CREDENTIALS); 303 setAuthFailureReason(de.getMessageObject()); 304 } 305 else 306 { 307 setResponseData(de); 308 } 309 break bindProcessing; 310 } 311 break; 312 313 314 default: 315 // Send a protocol error response to the client and disconnect. 316 // NYI 317 setResultCode(ResultCode.PROTOCOL_ERROR); 318 } 319 } 320 321 322 // Update the user's account with any password policy changes that may be 323 // required. 324 try 325 { 326 if (pwPolicyState != null) 327 { 328 pwPolicyState.updateUserEntry(); 329 } 330 } 331 catch (DirectoryException de) 332 { 333 if (debugEnabled()) 334 { 335 TRACER.debugCaught(DebugLogLevel.ERROR, de); 336 } 337 338 setResponseData(de); 339 } 340 341 342 // Invoke the post-operation bind plugins. 343 if (executePostOpPlugins) 344 { 345 PluginResult.PostOperation postOpResult = 346 pluginConfigManager.invokePostOperationBindPlugins(this); 347 if (!postOpResult.continueProcessing()) 348 { 349 setResultCode(postOpResult.getResultCode()); 350 appendErrorMessage(postOpResult.getErrorMessage()); 351 setMatchedDN(postOpResult.getMatchedDN()); 352 setReferralURLs(postOpResult.getReferralURLs()); 353 } 354 } 355 356 357 // Update the authentication information for the user. 358 AuthenticationInfo authInfo = getAuthenticationInfo(); 359 if ((getResultCode() == ResultCode.SUCCESS) && (authInfo != null)) 360 { 361 authenticatedUserEntry = authInfo.getAuthenticationEntry(); 362 clientConnection.setAuthenticationInfo(authInfo); 363 clientConnection.setSizeLimit(sizeLimit); 364 clientConnection.setTimeLimit(timeLimit); 365 clientConnection.setIdleTimeLimit(idleTimeLimit); 366 clientConnection.setLookthroughLimit(lookthroughLimit); 367 clientConnection.setMustChangePassword(mustChangePassword); 368 369 if (returnAuthzID) 370 { 371 addResponseControl(new AuthorizationIdentityResponseControl( 372 authInfo.getAuthorizationDN())); 373 } 374 } 375 376 377 // See if we need to send a password policy control to the client. If so, 378 // then add it to the response. 379 if (getResultCode() == ResultCode.SUCCESS) 380 { 381 if (pwPolicyControlRequested) 382 { 383 PasswordPolicyResponseControl pwpControl = 384 new PasswordPolicyResponseControl(pwPolicyWarningType, 385 pwPolicyWarningValue, 386 pwPolicyErrorType); 387 addResponseControl(pwpControl); 388 } 389 else 390 { 391 if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) 392 { 393 addResponseControl(new PasswordExpiredControl()); 394 } 395 else if (pwPolicyWarningType == 396 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) 397 { 398 addResponseControl(new PasswordExpiringControl(pwPolicyWarningValue)); 399 } 400 } 401 } 402 else 403 { 404 if (pwPolicyControlRequested) 405 { 406 PasswordPolicyResponseControl pwpControl = 407 new PasswordPolicyResponseControl(pwPolicyWarningType, 408 pwPolicyWarningValue, 409 pwPolicyErrorType); 410 addResponseControl(pwpControl); 411 } 412 else 413 { 414 if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) 415 { 416 addResponseControl(new PasswordExpiredControl()); 417 } 418 } 419 } 420 } 421 422 423 424 /** 425 * Handles request control processing for this bind operation. 426 * 427 * @throws DirectoryException If there is a problem with any of the 428 * controls. 429 */ 430 private void handleRequestControls() 431 throws DirectoryException 432 { 433 List<Control> requestControls = getRequestControls(); 434 if ((requestControls != null) && (! requestControls.isEmpty())) 435 { 436 for (int i=0; i < requestControls.size(); i++) 437 { 438 Control c = requestControls.get(i); 439 String oid = c.getOID(); 440 441 if (! AccessControlConfigManager.getInstance(). 442 getAccessControlHandler(). isAllowed(bindDN, this, c)) 443 { 444 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 445 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 446 } 447 448 if (oid.equals(OID_AUTHZID_REQUEST)) 449 { 450 returnAuthzID = true; 451 } 452 else if (oid.equals(OID_PASSWORD_POLICY_CONTROL)) 453 { 454 pwPolicyControlRequested = true; 455 } 456 457 // NYI -- Add support for additional controls. 458 459 else if (c.isCritical()) 460 { 461 throw new DirectoryException( 462 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 463 ERR_BIND_UNSUPPORTED_CRITICAL_CONTROL.get(oid)); 464 } 465 } 466 } 467 } 468 469 470 471 /** 472 * Performs the processing necessary for a simple bind operation. 473 * 474 * @return {@code true} if processing should continue for the operation, or 475 * {@code false} if not. 476 * 477 * @throws DirectoryException If a problem occurs that should cause the bind 478 * operation to fail. 479 */ 480 private boolean processSimpleBind() 481 throws DirectoryException 482 { 483 // See if this is an anonymous bind. If so, then determine whether 484 // to allow it. 485 ByteString simplePassword = getSimplePassword(); 486 if ((simplePassword == null) || (simplePassword.value().length == 0)) 487 { 488 return processAnonymousSimpleBind(); 489 } 490 491 // See if the bind DN is actually one of the alternate root DNs 492 // defined in the server. If so, then replace it with the actual DN 493 // for that user. 494 DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN); 495 if (actualRootDN != null) 496 { 497 bindDN = actualRootDN; 498 } 499 500 // Get the user entry based on the bind DN. If it does not exist, 501 // then fail. 502 Lock userLock = null; 503 for (int i=0; i < 3; i++) 504 { 505 userLock = LockManager.lockRead(bindDN); 506 if (userLock != null) 507 { 508 break; 509 } 510 } 511 512 if (userLock == null) 513 { 514 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 515 ERR_BIND_OPERATION_CANNOT_LOCK_USER.get( 516 String.valueOf(bindDN))); 517 } 518 519 try 520 { 521 Entry userEntry; 522 try 523 { 524 userEntry = backend.getEntry(bindDN); 525 } 526 catch (DirectoryException de) 527 { 528 if (debugEnabled()) 529 { 530 TRACER.debugCaught(DebugLogLevel.ERROR, de); 531 } 532 533 userEntry = null; 534 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 535 de.getMessageObject()); 536 } 537 538 if (userEntry == null) 539 { 540 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 541 ERR_BIND_OPERATION_UNKNOWN_USER.get( 542 String.valueOf(bindDN))); 543 } 544 else 545 { 546 setUserEntryDN(userEntry.getDN()); 547 } 548 549 550 // Check to see if the user has a password. If not, then fail. 551 // FIXME -- We need to have a way to enable/disable debugging. 552 pwPolicyState = new PasswordPolicyState(userEntry, false); 553 policy = pwPolicyState.getPolicy(); 554 AttributeType pwType = policy.getPasswordAttribute(); 555 556 List<Attribute> pwAttr = userEntry.getAttribute(pwType); 557 if ((pwAttr == null) || (pwAttr.isEmpty())) 558 { 559 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 560 ERR_BIND_OPERATION_NO_PASSWORD.get( 561 String.valueOf(bindDN))); 562 } 563 564 565 // Perform a number of password policy state checks for the user. 566 checkPasswordPolicyState(userEntry, null); 567 568 569 // Invoke the pre-operation bind plugins. 570 executePostOpPlugins = true; 571 PluginResult.PreOperation preOpResult = 572 pluginConfigManager.invokePreOperationBindPlugins(this); 573 if (!preOpResult.continueProcessing()) 574 { 575 setResultCode(preOpResult.getResultCode()); 576 appendErrorMessage(preOpResult.getErrorMessage()); 577 setMatchedDN(preOpResult.getMatchedDN()); 578 setReferralURLs(preOpResult.getReferralURLs()); 579 return false; 580 } 581 582 583 // Determine whether the provided password matches any of the stored 584 // passwords for the user. 585 if (pwPolicyState.passwordMatches(simplePassword)) 586 { 587 setResultCode(ResultCode.SUCCESS); 588 589 boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN()); 590 if (DirectoryServer.lockdownMode() && (! isRoot)) 591 { 592 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 593 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 594 } 595 setAuthenticationInfo(new AuthenticationInfo(userEntry, 596 simplePassword, 597 isRoot)); 598 599 600 // Set resource limits for the authenticated user. 601 setResourceLimits(userEntry); 602 603 604 // Perform any remaining processing for a successful simple 605 // authentication. 606 pwPolicyState.handleDeprecatedStorageSchemes(simplePassword); 607 pwPolicyState.clearFailureLockout(); 608 609 if (isFirstWarning) 610 { 611 pwPolicyState.setWarnedTime(); 612 613 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 614 Message m = WARN_BIND_PASSWORD_EXPIRING.get( 615 secondsToTimeString(numSeconds)); 616 617 pwPolicyState.generateAccountStatusNotification( 618 AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m, 619 AccountStatusNotification.createProperties(pwPolicyState, 620 false, numSeconds, null, null)); 621 } 622 623 if (isGraceLogin) 624 { 625 pwPolicyState.updateGraceLoginTimes(); 626 } 627 628 pwPolicyState.setLastLoginTime(); 629 } 630 else 631 { 632 setResultCode(ResultCode.INVALID_CREDENTIALS); 633 setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get()); 634 635 if (policy.getLockoutFailureCount() > 0) 636 { 637 pwPolicyState.updateAuthFailureTimes(); 638 if (pwPolicyState.lockedDueToFailures()) 639 { 640 AccountStatusNotificationType notificationType; 641 Message m; 642 643 boolean tempLocked; 644 int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); 645 if (lockoutDuration > -1) 646 { 647 notificationType = AccountStatusNotificationType. 648 ACCOUNT_TEMPORARILY_LOCKED; 649 tempLocked = true; 650 651 m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get( 652 secondsToTimeString(lockoutDuration)); 653 } 654 else 655 { 656 notificationType = AccountStatusNotificationType. 657 ACCOUNT_PERMANENTLY_LOCKED; 658 tempLocked = false; 659 660 m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); 661 } 662 663 pwPolicyState.generateAccountStatusNotification( 664 notificationType, userEntry, m, 665 AccountStatusNotification.createProperties(pwPolicyState, 666 tempLocked, -1, null, null)); 667 } 668 } 669 } 670 671 return true; 672 } 673 finally 674 { 675 // No matter what, make sure to unlock the user's entry. 676 LockManager.unlock(bindDN, userLock); 677 } 678 } 679 680 681 682 /** 683 * Performs the processing necessary for an anonymous simple bind. 684 * 685 * @throws DirectoryException If a problem occurs that should cause the bind 686 * operation to fail. 687 */ 688 private boolean processAnonymousSimpleBind() 689 throws DirectoryException 690 { 691 // If the server is in lockdown mode, then fail. 692 if (DirectoryServer.lockdownMode()) 693 { 694 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 695 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 696 } 697 698 // If there is a bind DN, then see whether that is acceptable. 699 if (DirectoryServer.bindWithDNRequiresPassword() && 700 ((bindDN != null) && (! bindDN.isNullDN()))) 701 { 702 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 703 ERR_BIND_DN_BUT_NO_PASSWORD.get()); 704 } 705 706 707 // Invoke the pre-operation bind plugins. 708 executePostOpPlugins = true; 709 PluginResult.PreOperation preOpResult = 710 pluginConfigManager.invokePreOperationBindPlugins(this); 711 if (!preOpResult.continueProcessing()) 712 { 713 setResultCode(preOpResult.getResultCode()); 714 appendErrorMessage(preOpResult.getErrorMessage()); 715 setMatchedDN(preOpResult.getMatchedDN()); 716 setReferralURLs(preOpResult.getReferralURLs()); 717 return false; 718 } 719 720 setResultCode(ResultCode.SUCCESS); 721 setAuthenticationInfo(new AuthenticationInfo()); 722 return true; 723 } 724 725 726 727 /** 728 * Performs the processing necessary for a SASL bind operation. 729 * 730 * @return {@code true} if processing should continue for the operation, or 731 * {@code false} if not. 732 * 733 * @throws DirectoryException If a problem occurs that should cause the bind 734 * operation to fail. 735 */ 736 private boolean processSASLBind() 737 throws DirectoryException 738 { 739 // Get the appropriate authentication handler for this request based 740 // on the SASL mechanism. If there is none, then fail. 741 SASLMechanismHandler saslHandler = 742 DirectoryServer.getSASLMechanismHandler(saslMechanism); 743 if (saslHandler == null) 744 { 745 throw new DirectoryException(ResultCode.AUTH_METHOD_NOT_SUPPORTED, 746 ERR_BIND_OPERATION_UNKNOWN_SASL_MECHANISM.get( 747 saslMechanism)); 748 } 749 750 751 // Check to see if the client has sufficient permission to perform the bind. 752 // NYI 753 754 755 // Invoke the pre-operation bind plugins. 756 PluginResult.PreOperation preOpResult = 757 pluginConfigManager.invokePreOperationBindPlugins(this); 758 if (!preOpResult.continueProcessing()) 759 { 760 setResultCode(preOpResult.getResultCode()); 761 appendErrorMessage(preOpResult.getErrorMessage()); 762 setMatchedDN(preOpResult.getMatchedDN()); 763 setReferralURLs(preOpResult.getReferralURLs()); 764 return false; 765 } 766 767 // Actually process the SASL bind. 768 saslHandler.processSASLBind(this); 769 770 771 // If the server is operating in lockdown mode, then we will need to 772 // ensure that the authentication was successful and performed as a 773 // root user to continue. 774 Entry saslAuthUserEntry = getSASLAuthUserEntry(); 775 if (DirectoryServer.lockdownMode()) 776 { 777 ResultCode resultCode = getResultCode(); 778 if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS) 779 { 780 if ((resultCode != ResultCode.SUCCESS) || 781 (saslAuthUserEntry == null) || 782 (! DirectoryServer.isRootDN(saslAuthUserEntry.getDN()))) 783 { 784 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 785 ERR_BIND_REJECTED_LOCKDOWN_MODE.get()); 786 } 787 } 788 } 789 790 // Create the password policy state object. 791 if (saslAuthUserEntry == null) 792 { 793 pwPolicyState = null; 794 } 795 else 796 { 797 // FIXME -- Need to have a way to enable debugging. 798 pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false); 799 policy = pwPolicyState.getPolicy(); 800 setUserEntryDN(saslAuthUserEntry.getDN()); 801 802 803 // Perform password policy checks that will need to be completed 804 // regardless of whether the authentication was successful. 805 checkPasswordPolicyState(saslAuthUserEntry, saslHandler); 806 } 807 808 809 // Determine whether the authentication was successful and perform 810 // any remaining password policy processing accordingly. 811 ResultCode resultCode = getResultCode(); 812 if (resultCode == ResultCode.SUCCESS) 813 { 814 if (pwPolicyState != null) 815 { 816 if (saslHandler.isPasswordBased(saslMechanism) && 817 pwPolicyState.mustChangePassword()) 818 { 819 mustChangePassword = true; 820 } 821 822 if (isFirstWarning) 823 { 824 pwPolicyState.setWarnedTime(); 825 826 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 827 Message m = WARN_BIND_PASSWORD_EXPIRING.get( 828 secondsToTimeString(numSeconds)); 829 830 pwPolicyState.generateAccountStatusNotification( 831 AccountStatusNotificationType.PASSWORD_EXPIRING, 832 saslAuthUserEntry, m, 833 AccountStatusNotification.createProperties(pwPolicyState, 834 false, numSeconds, null, null)); 835 } 836 837 if (isGraceLogin) 838 { 839 pwPolicyState.updateGraceLoginTimes(); 840 } 841 842 pwPolicyState.setLastLoginTime(); 843 844 845 // Set appropriate resource limits for the user. 846 setResourceLimits(saslAuthUserEntry); 847 } 848 } 849 else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS) 850 { 851 // FIXME -- Is any special processing needed here? 852 return false; 853 } 854 else 855 { 856 if (pwPolicyState != null) 857 { 858 if (saslHandler.isPasswordBased(saslMechanism)) 859 { 860 861 if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0) 862 { 863 pwPolicyState.updateAuthFailureTimes(); 864 if (pwPolicyState.lockedDueToFailures()) 865 { 866 AccountStatusNotificationType notificationType; 867 boolean tempLocked; 868 Message m; 869 870 int lockoutDuration = pwPolicyState.getSecondsUntilUnlock(); 871 if (lockoutDuration > -1) 872 { 873 notificationType = AccountStatusNotificationType. 874 ACCOUNT_TEMPORARILY_LOCKED; 875 tempLocked = true; 876 m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get( 877 secondsToTimeString(lockoutDuration)); 878 } 879 else 880 { 881 notificationType = 882 AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED; 883 tempLocked = false; 884 m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get(); 885 } 886 887 pwPolicyState.generateAccountStatusNotification( 888 notificationType, saslAuthUserEntry, m, 889 AccountStatusNotification.createProperties( 890 pwPolicyState, tempLocked, -1, null, null)); 891 } 892 } 893 } 894 } 895 } 896 897 return true; 898 } 899 900 901 902 /** 903 * Validates a number of password policy state constraints for the user. 904 * 905 * @param userEntry The entry for the user that is authenticating. 906 * @param saslHandler The SASL mechanism handler if this is a SASL bind, or 907 * {@code null} for a simple bind. 908 * 909 * @throws DirectoryException If a problem occurs that should cause the bind 910 * to fail. 911 */ 912 private void checkPasswordPolicyState(Entry userEntry, 913 SASLMechanismHandler saslHandler) 914 throws DirectoryException 915 { 916 boolean isSASLBind = (saslHandler != null); 917 918 // If the password policy is configured to track authentication failures or 919 // keep the last login time and the associated backend is disabled, then we 920 // may need to reject the bind immediately. 921 if ((policy.getStateUpdateFailurePolicy() == 922 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) && 923 ((policy.getLockoutFailureCount() > 0) || 924 ((policy.getLastLoginTimeAttribute() != null) && 925 (policy.getLastLoginTimeFormat() != null))) && 926 ((DirectoryServer.getWritabilityMode() == WritabilityMode.DISABLED) || 927 (backend.getWritabilityMode() == WritabilityMode.DISABLED))) 928 { 929 // This policy isn't applicable to root users, so if it's a root 930 // user then ignore it. 931 if (! DirectoryServer.isRootDN(userEntry.getDN())) 932 { 933 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 934 ERR_BIND_OPERATION_WRITABILITY_DISABLED.get( 935 String.valueOf(userEntry.getDN()))); 936 } 937 } 938 939 940 // Check to see if the authentication must be done in a secure 941 // manner. If so, then the client connection must be secure. 942 if (policy.requireSecureAuthentication() && (! clientConnection.isSecure())) 943 { 944 if (isSASLBind) 945 { 946 if (! saslHandler.isSecure(saslMechanism)) 947 { 948 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 949 ERR_BIND_OPERATION_INSECURE_SASL_BIND.get( 950 saslMechanism, 951 String.valueOf(userEntry.getDN()))); 952 } 953 } 954 else 955 { 956 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 957 ERR_BIND_OPERATION_INSECURE_SIMPLE_BIND.get( 958 String.valueOf(userEntry.getDN()))); 959 } 960 } 961 962 963 // Check to see if the user is administratively disabled or locked. 964 if (pwPolicyState.isDisabled()) 965 { 966 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 967 ERR_BIND_OPERATION_ACCOUNT_DISABLED.get( 968 String.valueOf(userEntry.getDN()))); 969 } 970 else if (pwPolicyState.isAccountExpired()) 971 { 972 Message m = ERR_BIND_OPERATION_ACCOUNT_EXPIRED.get( 973 String.valueOf(userEntry.getDN())); 974 pwPolicyState.generateAccountStatusNotification( 975 AccountStatusNotificationType.ACCOUNT_EXPIRED, userEntry, m, 976 AccountStatusNotification.createProperties(pwPolicyState, 977 false, -1, null, null)); 978 979 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 980 } 981 else if (pwPolicyState.lockedDueToFailures()) 982 { 983 if (pwPolicyErrorType == null) 984 { 985 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 986 } 987 988 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, 989 ERR_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED.get( 990 String.valueOf(userEntry.getDN()))); 991 } 992 else if (pwPolicyState.lockedDueToIdleInterval()) 993 { 994 Message m = ERR_BIND_OPERATION_ACCOUNT_IDLE_LOCKED.get( 995 String.valueOf(userEntry.getDN())); 996 997 if (pwPolicyErrorType == null) 998 { 999 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 1000 } 1001 1002 pwPolicyState.generateAccountStatusNotification( 1003 AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, userEntry, m, 1004 AccountStatusNotification.createProperties(pwPolicyState, false, -1, 1005 null, null)); 1006 1007 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 1008 } 1009 1010 1011 // If it's a simple bind, or if it's a password-based SASL bind, then 1012 // perform a number of password-based checks. 1013 if ((! isSASLBind) || saslHandler.isPasswordBased(saslMechanism)) 1014 { 1015 // Check to see if the account is locked due to the maximum reset age. 1016 if (pwPolicyState.lockedDueToMaximumResetAge()) 1017 { 1018 Message m = ERR_BIND_OPERATION_ACCOUNT_RESET_LOCKED.get( 1019 String.valueOf(userEntry.getDN())); 1020 1021 if (pwPolicyErrorType == null) 1022 { 1023 pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED; 1024 } 1025 1026 pwPolicyState.generateAccountStatusNotification( 1027 AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, userEntry, m, 1028 AccountStatusNotification.createProperties(pwPolicyState, false, 1029 -1, null, null)); 1030 1031 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 1032 } 1033 1034 1035 // Determine whether the password is expired, or whether the user 1036 // should be warned about an upcoming expiration. 1037 if (pwPolicyState.isPasswordExpired()) 1038 { 1039 if (pwPolicyErrorType == null) 1040 { 1041 pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED; 1042 } 1043 1044 int maxGraceLogins = policy.getGraceLoginCount(); 1045 if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin()) 1046 { 1047 List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes(); 1048 if ((graceLoginTimes == null) || 1049 (graceLoginTimes.size() < maxGraceLogins)) 1050 { 1051 isGraceLogin = true; 1052 mustChangePassword = true; 1053 1054 if (pwPolicyWarningType == null) 1055 { 1056 pwPolicyWarningType = 1057 PasswordPolicyWarningType.GRACE_LOGINS_REMAINING; 1058 pwPolicyWarningValue = maxGraceLogins - 1059 (graceLoginTimes.size() + 1); 1060 } 1061 } 1062 else 1063 { 1064 Message m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( 1065 String.valueOf(userEntry.getDN())); 1066 1067 pwPolicyState.generateAccountStatusNotification( 1068 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 1069 AccountStatusNotification.createProperties(pwPolicyState, 1070 false, -1, null, 1071 null)); 1072 1073 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 1074 } 1075 } 1076 else 1077 { 1078 Message m = ERR_BIND_OPERATION_PASSWORD_EXPIRED.get( 1079 String.valueOf(userEntry.getDN())); 1080 1081 pwPolicyState.generateAccountStatusNotification( 1082 AccountStatusNotificationType.PASSWORD_EXPIRED, userEntry, m, 1083 AccountStatusNotification.createProperties(pwPolicyState, false, 1084 -1, null, null)); 1085 1086 throw new DirectoryException(ResultCode.INVALID_CREDENTIALS, m); 1087 } 1088 } 1089 else if (pwPolicyState.shouldWarn()) 1090 { 1091 int numSeconds = pwPolicyState.getSecondsUntilExpiration(); 1092 1093 if (pwPolicyWarningType == null) 1094 { 1095 pwPolicyWarningType = 1096 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION; 1097 pwPolicyWarningValue = numSeconds; 1098 } 1099 1100 isFirstWarning = pwPolicyState.isFirstWarning(); 1101 } 1102 1103 1104 // Check to see if the user's password has been reset. 1105 if (pwPolicyState.mustChangePassword()) 1106 { 1107 mustChangePassword = true; 1108 1109 if (pwPolicyErrorType == null) 1110 { 1111 pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET; 1112 } 1113 } 1114 } 1115 } 1116 1117 1118 1119 /** 1120 * Sets resource limits for the authenticated user. 1121 * 1122 * @param userEntry The entry for the authenticated user. 1123 */ 1124 private void setResourceLimits(Entry userEntry) 1125 { 1126 // See if the user's entry contains a custom size limit. 1127 AttributeType attrType = 1128 DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT, true); 1129 List<Attribute> attrList = userEntry.getAttribute(attrType); 1130 if ((attrList != null) && (attrList.size() == 1)) 1131 { 1132 Attribute a = attrList.get(0); 1133 LinkedHashSet<AttributeValue> values = a.getValues(); 1134 Iterator<AttributeValue> iterator = values.iterator(); 1135 if (iterator.hasNext()) 1136 { 1137 AttributeValue v = iterator.next(); 1138 if (iterator.hasNext()) 1139 { 1140 logError(WARN_BIND_MULTIPLE_USER_SIZE_LIMITS.get( 1141 String.valueOf(userEntry.getDN()))); 1142 } 1143 else 1144 { 1145 try 1146 { 1147 sizeLimit = Integer.parseInt(v.getStringValue()); 1148 } 1149 catch (Exception e) 1150 { 1151 if (debugEnabled()) 1152 { 1153 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1154 } 1155 1156 logError(WARN_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT.get( 1157 v.getStringValue(), 1158 String.valueOf(userEntry.getDN()))); 1159 } 1160 } 1161 } 1162 } 1163 1164 1165 // See if the user's entry contains a custom time limit. 1166 attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT, true); 1167 attrList = userEntry.getAttribute(attrType); 1168 if ((attrList != null) && (attrList.size() == 1)) 1169 { 1170 Attribute a = attrList.get(0); 1171 LinkedHashSet<AttributeValue> values = a.getValues(); 1172 Iterator<AttributeValue> iterator = values.iterator(); 1173 if (iterator.hasNext()) 1174 { 1175 AttributeValue v = iterator.next(); 1176 if (iterator.hasNext()) 1177 { 1178 logError(WARN_BIND_MULTIPLE_USER_TIME_LIMITS.get( 1179 String.valueOf(userEntry.getDN()))); 1180 } 1181 else 1182 { 1183 try 1184 { 1185 timeLimit = Integer.parseInt(v.getStringValue()); 1186 } 1187 catch (Exception e) 1188 { 1189 if (debugEnabled()) 1190 { 1191 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1192 } 1193 1194 logError(WARN_BIND_CANNOT_PROCESS_USER_TIME_LIMIT.get( 1195 v.getStringValue(), 1196 String.valueOf(userEntry.getDN()))); 1197 } 1198 } 1199 } 1200 } 1201 1202 1203 // See if the user's entry contains a custom idle time limit. 1204 attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_IDLE_TIME_LIMIT, 1205 true); 1206 attrList = userEntry.getAttribute(attrType); 1207 if ((attrList != null) && (attrList.size() == 1)) 1208 { 1209 Attribute a = attrList.get(0); 1210 LinkedHashSet<AttributeValue> values = a.getValues(); 1211 Iterator<AttributeValue> iterator = values.iterator(); 1212 if (iterator.hasNext()) 1213 { 1214 AttributeValue v = iterator.next(); 1215 if (iterator.hasNext()) 1216 { 1217 logError(WARN_BIND_MULTIPLE_USER_IDLE_TIME_LIMITS.get( 1218 String.valueOf(userEntry.getDN()))); 1219 } 1220 else 1221 { 1222 try 1223 { 1224 idleTimeLimit = 1000L * Long.parseLong(v.getStringValue()); 1225 } 1226 catch (Exception e) 1227 { 1228 if (debugEnabled()) 1229 { 1230 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1231 } 1232 1233 logError(WARN_BIND_CANNOT_PROCESS_USER_IDLE_TIME_LIMIT.get( 1234 v.getStringValue(), 1235 String.valueOf(userEntry.getDN()))); 1236 } 1237 } 1238 } 1239 } 1240 1241 1242 // See if the user's entry contains a custom lookthrough limit. 1243 attrType = DirectoryServer.getAttributeType(OP_ATTR_USER_LOOKTHROUGH_LIMIT, 1244 true); 1245 attrList = userEntry.getAttribute(attrType); 1246 if ((attrList != null) && (attrList.size() == 1)) 1247 { 1248 Attribute a = attrList.get(0); 1249 LinkedHashSet<AttributeValue> values = a.getValues(); 1250 Iterator<AttributeValue> iterator = values.iterator(); 1251 if (iterator.hasNext()) 1252 { 1253 AttributeValue v = iterator.next(); 1254 if (iterator.hasNext()) 1255 { 1256 logError(WARN_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS.get( 1257 String.valueOf(userEntry.getDN()))); 1258 } 1259 else 1260 { 1261 try 1262 { 1263 lookthroughLimit = Integer.parseInt(v.getStringValue()); 1264 } 1265 catch (Exception e) 1266 { 1267 if (debugEnabled()) 1268 { 1269 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1270 } 1271 1272 logError(WARN_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT.get( 1273 v.getStringValue(), 1274 String.valueOf(userEntry.getDN()))); 1275 } 1276 } 1277 } 1278 } 1279 } 1280 } 1281