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