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