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.ArrayList;
032    import java.util.Arrays;
033    import java.util.HashSet;
034    import java.util.Iterator;
035    import java.util.LinkedHashSet;
036    import java.util.LinkedList;
037    import java.util.List;
038    import java.util.concurrent.locks.Lock;
039    
040    import org.opends.messages.Message;
041    import org.opends.messages.MessageBuilder;
042    import org.opends.server.api.AttributeSyntax;
043    import org.opends.server.api.Backend;
044    import org.opends.server.api.ChangeNotificationListener;
045    import org.opends.server.api.ClientConnection;
046    import org.opends.server.api.PasswordStorageScheme;
047    import org.opends.server.api.SynchronizationProvider;
048    import org.opends.server.api.plugin.PluginResult;
049    import org.opends.server.controls.LDAPAssertionRequestControl;
050    import org.opends.server.controls.LDAPPostReadRequestControl;
051    import org.opends.server.controls.LDAPPostReadResponseControl;
052    import org.opends.server.controls.LDAPPreReadRequestControl;
053    import org.opends.server.controls.LDAPPreReadResponseControl;
054    import org.opends.server.controls.PasswordPolicyErrorType;
055    import org.opends.server.controls.PasswordPolicyResponseControl;
056    import org.opends.server.controls.ProxiedAuthV1Control;
057    import org.opends.server.controls.ProxiedAuthV2Control;
058    import org.opends.server.core.AccessControlConfigManager;
059    import org.opends.server.core.DirectoryServer;
060    import org.opends.server.core.ModifyOperation;
061    import org.opends.server.core.ModifyOperationWrapper;
062    import org.opends.server.core.PasswordPolicyState;
063    import org.opends.server.core.PluginConfigManager;
064    import org.opends.server.loggers.debug.DebugTracer;
065    import org.opends.server.protocols.asn1.ASN1OctetString;
066    import org.opends.server.schema.AuthPasswordSyntax;
067    import org.opends.server.schema.BooleanSyntax;
068    import org.opends.server.schema.UserPasswordSyntax;
069    import org.opends.server.types.AccountStatusNotification;
070    import org.opends.server.types.AccountStatusNotificationType;
071    import org.opends.server.types.AcceptRejectWarn;
072    import org.opends.server.types.Attribute;
073    import org.opends.server.types.AttributeType;
074    import org.opends.server.types.AttributeValue;
075    import org.opends.server.types.AuthenticationInfo;
076    import org.opends.server.types.ByteString;
077    import org.opends.server.types.CanceledOperationException;
078    import org.opends.server.types.Control;
079    import org.opends.server.types.DebugLogLevel;
080    import org.opends.server.types.DirectoryException;
081    import org.opends.server.types.DN;
082    import org.opends.server.types.Entry;
083    import org.opends.server.types.LDAPException;
084    import org.opends.server.types.LockManager;
085    import org.opends.server.types.Modification;
086    import org.opends.server.types.ModificationType;
087    import org.opends.server.types.Privilege;
088    import org.opends.server.types.RDN;
089    import org.opends.server.types.ResultCode;
090    import org.opends.server.types.SearchFilter;
091    import org.opends.server.types.SearchResultEntry;
092    import org.opends.server.types.SynchronizationProviderResult;
093    import org.opends.server.types.operation.PostOperationModifyOperation;
094    import org.opends.server.types.operation.PostResponseModifyOperation;
095    import org.opends.server.types.operation.PreOperationModifyOperation;
096    import org.opends.server.types.operation.PostSynchronizationModifyOperation;
097    import org.opends.server.util.TimeThread;
098    
099    import static org.opends.messages.CoreMessages.*;
100    import static org.opends.server.config.ConfigConstants.*;
101    import static org.opends.server.loggers.ErrorLogger.*;
102    import static org.opends.server.loggers.debug.DebugLogger.*;
103    import static org.opends.server.util.ServerConstants.*;
104    import static org.opends.server.util.StaticUtils.*;
105    
106    
107    
108    /**
109     * This class defines an operation used to modify an entry in a local backend
110     * of the Directory Server.
111     */
112    public class LocalBackendModifyOperation
113           extends ModifyOperationWrapper
114           implements PreOperationModifyOperation, PostOperationModifyOperation,
115                      PostResponseModifyOperation,
116                      PostSynchronizationModifyOperation
117    {
118      /**
119       * The tracer object for the debug logger.
120       */
121      private static final DebugTracer TRACER = getTracer();
122    
123    
124    
125      // The backend in which the target entry exists.
126      private Backend backend;
127    
128      // Indicates whether the request included the user's current password.
129      private boolean currentPasswordProvided;
130    
131      // Indicates whether the user's account has been enabled or disabled by this
132      // modify operation.
133      private boolean enabledStateChanged;
134    
135      // Indicates whether the user's account is currently enabled.
136      private boolean isEnabled;
137    
138      // Indicates whether the request included the LDAP no-op control.
139      private boolean noOp;
140    
141      // Indicates whether this modify operation includees a password change.
142      private boolean passwordChanged;
143    
144      // Indicates whether the request included the password policy request control.
145      private boolean pwPolicyControlRequested;
146    
147      // Indicates whether the password change is a self-change.
148      private boolean selfChange;
149    
150      // Indicates whether the user's account was locked before this change.
151      private boolean wasLocked;
152    
153      // The client connection associated with this operation.
154      private ClientConnection clientConnection;
155    
156      // The DN of the entry to modify.
157      private DN entryDN;
158    
159      // The current entry, before any changes are applied.
160      private Entry currentEntry = null;
161    
162      // The modified entry that will be stored in the backend.
163      private Entry modifiedEntry = null;
164    
165      // The number of passwords contained in the modify operation.
166      private int numPasswords;
167    
168      // The post-read request control, if present.
169      private LDAPPostReadRequestControl postReadRequest;
170    
171      // The pre-read request control, if present.
172      private LDAPPreReadRequestControl preReadRequest;
173    
174      // The set of clear-text current passwords (if any were provided).
175      private List<AttributeValue> currentPasswords = null;
176    
177      // The set of clear-text new passwords (if any were provided).
178      private List<AttributeValue> newPasswords = null;
179    
180      // The set of modifications contained in this request.
181      private List<Modification> modifications;
182    
183      // The password policy error type for this operation.
184      private PasswordPolicyErrorType pwpErrorType;
185    
186      // The password policy state for this modify operation.
187      private PasswordPolicyState pwPolicyState;
188    
189    
190    
191      /**
192       * Creates a new operation that may be used to modify an entry in a
193       * local backend of the Directory Server.
194       *
195       * @param modify The operation to enhance.
196       */
197      public LocalBackendModifyOperation(ModifyOperation modify)
198      {
199        super(modify);
200        LocalBackendWorkflowElement.attachLocalOperation (modify, this);
201      }
202    
203    
204    
205      /**
206       * Retrieves the current entry before any modifications are applied.  This
207       * will not be available to pre-parse plugins.
208       *
209       * @return  The current entry, or <CODE>null</CODE> if it is not yet
210       *          available.
211       */
212      public final Entry getCurrentEntry()
213      {
214        return currentEntry;
215      }
216    
217    
218    
219      /**
220       * Retrieves the set of clear-text current passwords for the user, if
221       * available.  This will only be available if the modify operation contains
222       * one or more delete elements that target the password attribute and provide
223       * the values to delete in the clear.  It will not be available to pre-parse
224       * plugins.
225       *
226       * @return  The set of clear-text current password values as provided in the
227       *          modify request, or <CODE>null</CODE> if there were none or this
228       *          information is not yet available.
229       */
230      public final List<AttributeValue> getCurrentPasswords()
231      {
232        return currentPasswords;
233      }
234    
235    
236    
237      /**
238       * Retrieves the modified entry that is to be written to the backend.  This
239       * will be available to pre-operation plugins, and if such a plugin does make
240       * a change to this entry, then it is also necessary to add that change to
241       * the set of modifications to ensure that the update will be consistent.
242       *
243       * @return  The modified entry that is to be written to the backend, or
244       *          <CODE>null</CODE> if it is not yet available.
245       */
246      public final Entry getModifiedEntry()
247      {
248        return modifiedEntry;
249      }
250    
251    
252    
253      /**
254       * Retrieves the set of clear-text new passwords for the user, if available.
255       * This will only be available if the modify operation contains one or more
256       * add or replace elements that target the password attribute and provide the
257       * values in the clear.  It will not be available to pre-parse plugins.
258       *
259       * @return  The set of clear-text new passwords as provided in the modify
260       *          request, or <CODE>null</CODE> if there were none or this
261       *          information is not yet available.
262       */
263      public final List<AttributeValue> getNewPasswords()
264      {
265        return newPasswords;
266      }
267    
268    
269    
270      /**
271       * Adds the provided modification to the set of modifications to this modify
272       * operation.
273       * In addition, the modification is applied to the modified entry.
274       *
275       * This may only be called by pre-operation plugins.
276       *
277       * @param  modification  The modification to add to the set of changes for
278       *                       this modify operation.
279       *
280       * @throws  DirectoryException  If an unexpected problem occurs while applying
281       *                              the modification to the entry.
282       */
283      public void addModification(Modification modification)
284        throws DirectoryException
285      {
286        modifiedEntry.applyModification(modification);
287        super.addModification(modification);
288      }
289    
290    
291    
292      /**
293       * Process this modify operation against a local backend.
294       *
295       * @param  backend  The backend in which the modify operation should be
296       *                  performed.
297       *
298       * @throws CanceledOperationException if this operation should be
299       * cancelled
300       */
301      void processLocalModify(Backend backend) throws CanceledOperationException {
302        boolean executePostOpPlugins = false;
303    
304        this.backend = backend;
305    
306        clientConnection = getClientConnection();
307    
308        // Get the plugin config manager that will be used for invoking plugins.
309        PluginConfigManager pluginConfigManager =
310          DirectoryServer.getPluginConfigManager();
311    
312        // Check for a request to cancel this operation.
313        checkIfCanceled(false);
314    
315        // Create a labeled block of code that we can break out of if a problem is
316        // detected.
317    modifyProcessing:
318        {
319          entryDN = getEntryDN();
320          if (entryDN == null){
321            break modifyProcessing;
322          }
323    
324          // Process the modifications to convert them from their raw form to the
325          // form required for the rest of the modify processing.
326          modifications = getModifications();
327          if (modifications == null)
328          {
329            break modifyProcessing;
330          }
331    
332          if (modifications.isEmpty())
333          {
334            setResultCode(ResultCode.CONSTRAINT_VIOLATION);
335            appendErrorMessage(ERR_MODIFY_NO_MODIFICATIONS.get(
336                                    String.valueOf(entryDN)));
337            break modifyProcessing;
338          }
339    
340    
341          // If the user must change their password before doing anything else, and
342          // if the target of the modify operation isn't the user's own entry, then
343          // reject the request.
344          if ((! isInternalOperation()) && clientConnection.mustChangePassword())
345          {
346            DN authzDN = getAuthorizationDN();
347            if ((authzDN != null) && (! authzDN.equals(entryDN)))
348            {
349              // The user will not be allowed to do anything else before the
350              // password gets changed.  Also note that we haven't yet checked the
351              // request controls so we need to do that now to see if the password
352              // policy request control was provided.
353              for (Control c : getRequestControls())
354              {
355                if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
356                {
357                  pwPolicyControlRequested = true;
358                  pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
359                  break;
360                }
361              }
362    
363              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
364              appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
365              break modifyProcessing;
366            }
367          }
368    
369    
370          // Check for a request to cancel this operation.
371          checkIfCanceled(false);
372    
373          // Acquire a write lock on the target entry.
374          Lock entryLock = null;
375          for (int i=0; i < 3; i++)
376          {
377            entryLock = LockManager.lockWrite(entryDN);
378            if (entryLock != null)
379            {
380              break;
381            }
382          }
383    
384          if (entryLock == null)
385          {
386            setResultCode(DirectoryServer.getServerErrorResultCode());
387            appendErrorMessage(ERR_MODIFY_CANNOT_LOCK_ENTRY.get(
388                                    String.valueOf(entryDN)));
389            break modifyProcessing;
390          }
391    
392    
393          try
394          {
395            // Check for a request to cancel this operation.
396            checkIfCanceled(false);
397    
398    
399            try
400            {
401              // Get the entry to modify.  If it does not exist, then fail.
402              currentEntry = backend.getEntry(entryDN);
403    
404              if (currentEntry == null)
405              {
406                setResultCode(ResultCode.NO_SUCH_OBJECT);
407                appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(
408                    String.valueOf(entryDN)));
409    
410                // See if one of the entry's ancestors exists.
411                try
412                {
413                  DN parentDN = entryDN.getParentDNInSuffix();
414                  while (parentDN != null)
415                  {
416                    if (DirectoryServer.entryExists(parentDN))
417                    {
418                      setMatchedDN(parentDN);
419                      break;
420                    }
421    
422                    parentDN = parentDN.getParentDNInSuffix();
423                  }
424                }
425                catch (Exception e)
426                {
427                  if (debugEnabled())
428                  {
429                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
430                  }
431                }
432    
433                break modifyProcessing;
434              }
435    
436              // Check to see if there are any controls in the request.  If so, then
437              // see if there is any special processing required.
438              processRequestControls();
439    
440              // Get the password policy state object for the entry that can be used
441              // to perform any appropriate password policy processing.  Also, see
442              // if the entry is being updated by the end user or an administrator.
443              selfChange = entryDN.equals(getAuthorizationDN());
444    
445              // FIXME -- Need a way to enable debug mode.
446              pwPolicyState = new PasswordPolicyState(currentEntry, false,
447                                                      TimeThread.getTime(), true);
448            }
449            catch (DirectoryException de)
450            {
451              if (debugEnabled())
452              {
453                TRACER.debugCaught(DebugLogLevel.ERROR, de);
454              }
455    
456              setResponseData(de);
457              break modifyProcessing;
458            }
459    
460    
461            // Create a duplicate of the entry and apply the changes to it.
462            modifiedEntry = currentEntry.duplicate(false);
463    
464            if (! noOp)
465            {
466              // Invoke any conflict resolution processing that might be needed by
467              // the synchronization provider.
468              for (SynchronizationProvider provider :
469                DirectoryServer.getSynchronizationProviders())
470              {
471                try
472                {
473                  SynchronizationProviderResult result =
474                      provider.handleConflictResolution(this);
475                  if (! result.continueProcessing())
476                  {
477                    setResultCode(result.getResultCode());
478                    appendErrorMessage(result.getErrorMessage());
479                    setMatchedDN(result.getMatchedDN());
480                    setReferralURLs(result.getReferralURLs());
481                    break modifyProcessing;
482                  }
483                }
484                catch (DirectoryException de)
485                {
486                  if (debugEnabled())
487                  {
488                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
489                  }
490    
491                  logError(ERR_MODIFY_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
492                                getConnectionID(), getOperationID(),
493                                getExceptionMessage(de)));
494                  setResponseData(de);
495                  break modifyProcessing;
496                }
497              }
498            }
499    
500    
501            try
502            {
503              handleSchemaProcessing();
504            }
505            catch (DirectoryException de)
506            {
507              if (debugEnabled())
508              {
509                TRACER.debugCaught(DebugLogLevel.ERROR, de);
510              }
511    
512              setResponseData(de);
513              break modifyProcessing;
514            }
515    
516    
517            // Check to see if the client has permission to perform the modify.
518            // The access control check is not made any earlier because the handler
519            // needs access to the modified entry.
520    
521            // FIXME: for now assume that this will check all permissions
522            // pertinent to the operation. This includes proxy authorization
523            // and any other controls specified.
524    
525            // FIXME: earlier checks to see if the entry already exists may have
526            // already exposed sensitive information to the client.
527            if (! AccessControlConfigManager.getInstance().
528                       getAccessControlHandler().isAllowed(this))
529            {
530              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
531              appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
532                                      String.valueOf(entryDN)));
533              break modifyProcessing;
534            }
535    
536    
537            try
538            {
539              handleInitialPasswordPolicyProcessing();
540    
541              wasLocked = false;
542              if (passwordChanged)
543              {
544                performAdditionalPasswordChangedProcessing();
545              }
546            }
547            catch (DirectoryException de)
548            {
549              if (debugEnabled())
550              {
551                TRACER.debugCaught(DebugLogLevel.ERROR, de);
552              }
553    
554              setResponseData(de);
555              break modifyProcessing;
556            }
557    
558    
559            if ((! passwordChanged) && (! isInternalOperation()) &&
560                pwPolicyState.mustChangePassword())
561            {
562              // The user will not be allowed to do anything else before the
563              // password gets changed.
564              pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
565              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
566              appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
567              break modifyProcessing;
568            }
569    
570    
571            // If the server is configured to check the schema and the
572            // operation is not a sycnhronization operation,
573            // make sure that the new entry is valid per the server schema.
574            if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
575            {
576              MessageBuilder invalidReason = new MessageBuilder();
577              if (! modifiedEntry.conformsToSchema(null, false, false, false,
578                  invalidReason))
579              {
580                setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
581                appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get(
582                                        String.valueOf(entryDN), invalidReason));
583                break modifyProcessing;
584              }
585            }
586    
587    
588            // Check for a request to cancel this operation.
589            checkIfCanceled(false);
590    
591            // If the operation is not a synchronization operation,
592            // Invoke the pre-operation modify plugins.
593            if (! isSynchronizationOperation())
594            {
595              executePostOpPlugins = true;
596              PluginResult.PreOperation preOpResult =
597                pluginConfigManager.invokePreOperationModifyPlugins(this);
598              if (!preOpResult.continueProcessing())
599              {
600                setResultCode(preOpResult.getResultCode());
601                appendErrorMessage(preOpResult.getErrorMessage());
602                setMatchedDN(preOpResult.getMatchedDN());
603                setReferralURLs(preOpResult.getReferralURLs());
604                break modifyProcessing;
605              }
606            }
607    
608    
609            // Check for a request to cancel this operation.
610            checkIfCanceled(true);
611    
612            // Actually perform the modify operation.  This should also include
613            // taking care of any synchronization that might be needed.
614            if (backend == null)
615            {
616              setResultCode(ResultCode.NO_SUCH_OBJECT);
617              appendErrorMessage(ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(
618                                      String.valueOf(entryDN)));
619              break modifyProcessing;
620            }
621    
622            try
623            {
624              try
625              {
626                checkWritability();
627              }
628              catch (DirectoryException de)
629              {
630                if (debugEnabled())
631                {
632                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
633                }
634    
635                setResponseData(de);
636                break modifyProcessing;
637              }
638    
639    
640              if (noOp)
641              {
642                appendErrorMessage(INFO_MODIFY_NOOP.get());
643                setResultCode(ResultCode.NO_OPERATION);
644              }
645              else
646              {
647                for (SynchronizationProvider provider :
648                  DirectoryServer.getSynchronizationProviders())
649                {
650                  try
651                  {
652                    SynchronizationProviderResult result =
653                        provider.doPreOperation(this);
654                    if (! result.continueProcessing())
655                    {
656                      setResultCode(result.getResultCode());
657                      appendErrorMessage(result.getErrorMessage());
658                      setMatchedDN(result.getMatchedDN());
659                      setReferralURLs(result.getReferralURLs());
660                      break modifyProcessing;
661                    }
662                  }
663                  catch (DirectoryException de)
664                  {
665                    if (debugEnabled())
666                    {
667                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
668                    }
669    
670                    logError(ERR_MODIFY_SYNCH_PREOP_FAILED.get(getConnectionID(),
671                                  getOperationID(), getExceptionMessage(de)));
672                    setResponseData(de);
673                    break modifyProcessing;
674                  }
675                }
676    
677                backend.replaceEntry(modifiedEntry, this);
678    
679    
680    
681                // See if we need to generate any account status notifications as a
682                // result of the changes.
683                if (passwordChanged || enabledStateChanged || wasLocked)
684                {
685                  handleAccountStatusNotifications();
686                }
687              }
688    
689    
690              // Handle any processing that may be needed for the pre-read and/or
691              // post-read controls.
692              handleReadEntryProcessing();
693    
694    
695              if (! noOp)
696              {
697                setResultCode(ResultCode.SUCCESS);
698              }
699            }
700            catch (DirectoryException de)
701            {
702              if (debugEnabled())
703              {
704                TRACER.debugCaught(DebugLogLevel.ERROR, de);
705              }
706    
707              setResponseData(de);
708              break modifyProcessing;
709            }
710          }
711          finally
712          {
713            LockManager.unlock(entryDN, entryLock);
714          }
715        }
716    
717        for (SynchronizationProvider provider :
718            DirectoryServer.getSynchronizationProviders())
719        {
720          try
721          {
722            provider.doPostOperation(this);
723          }
724          catch (DirectoryException de)
725          {
726            if (debugEnabled())
727            {
728              TRACER.debugCaught(DebugLogLevel.ERROR, de);
729            }
730    
731            logError(ERR_MODIFY_SYNCH_POSTOP_FAILED.get(getConnectionID(),
732                getOperationID(), getExceptionMessage(de)));
733            setResponseData(de);
734            break;
735          }
736        }
737    
738        // If the password policy request control was included, then make sure we
739        // send the corresponding response control.
740        if (pwPolicyControlRequested)
741        {
742          addResponseControl(new PasswordPolicyResponseControl(null, 0,
743                                                               pwpErrorType));
744        }
745    
746        // Invoke the post-operation or post-synchronization modify plugins.
747        if (isSynchronizationOperation())
748        {
749          if (getResultCode() == ResultCode.SUCCESS)
750          {
751            pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
752          }
753        }
754        else if (executePostOpPlugins)
755        {
756          // FIXME -- Should this also be done while holding the locks?
757          PluginResult.PostOperation postOpResult =
758               pluginConfigManager.invokePostOperationModifyPlugins(this);
759          if (!postOpResult.continueProcessing())
760          {
761            setResultCode(postOpResult.getResultCode());
762            appendErrorMessage(postOpResult.getErrorMessage());
763            setMatchedDN(postOpResult.getMatchedDN());
764            setReferralURLs(postOpResult.getReferralURLs());
765            return;
766          }
767        }
768    
769    
770        // Notify any change notification listeners that might be registered with
771        // the server.
772        if (getResultCode() == ResultCode.SUCCESS)
773        {
774          notifyChangeListeners();
775        }
776      }
777    
778    
779    
780      /**
781       * Processes any controls contained in the modify request.
782       *
783       * @throws  DirectoryException  If a problem is encountered with any of the
784       *                              controls.
785       */
786      private void processRequestControls()
787              throws DirectoryException
788      {
789        List<Control> requestControls = getRequestControls();
790        if ((requestControls != null) && (! requestControls.isEmpty()))
791        {
792          for (int i=0; i < requestControls.size(); i++)
793          {
794            Control c   = requestControls.get(i);
795            String  oid = c.getOID();
796    
797            if (! AccessControlConfigManager.getInstance().
798                       getAccessControlHandler().isAllowed(entryDN, this, c))
799            {
800              throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
801                             ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
802            }
803    
804    
805            if (oid.equals(OID_LDAP_ASSERTION))
806            {
807              LDAPAssertionRequestControl assertControl;
808              if (c instanceof LDAPAssertionRequestControl)
809              {
810                assertControl = (LDAPAssertionRequestControl) c;
811              }
812              else
813              {
814                try
815                {
816                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
817                  requestControls.set(i, assertControl);
818                }
819                catch (LDAPException le)
820                {
821                  if (debugEnabled())
822                  {
823                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
824                  }
825    
826                  throw new DirectoryException(
827                                 ResultCode.valueOf(le.getResultCode()),
828                                 le.getMessageObject());
829                }
830              }
831    
832              try
833              {
834                // FIXME -- We need to determine whether the current user has
835                //          permission to make this determination.
836                SearchFilter filter = assertControl.getSearchFilter();
837                if (! filter.matchesEntry(currentEntry))
838                {
839                  throw new DirectoryException(ResultCode.ASSERTION_FAILED,
840                                               ERR_MODIFY_ASSERTION_FAILED.get(
841                                                    String.valueOf(entryDN)));
842                }
843              }
844              catch (DirectoryException de)
845              {
846                if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
847                {
848                  throw de;
849                }
850    
851                if (debugEnabled())
852                {
853                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
854                }
855    
856                throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
857                               ERR_MODIFY_CANNOT_PROCESS_ASSERTION_FILTER.get(
858                                    String.valueOf(entryDN),
859                                    de.getMessageObject()));
860              }
861            }
862            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
863            {
864              noOp = true;
865            }
866            else if (oid.equals(OID_LDAP_READENTRY_PREREAD))
867            {
868              if (c instanceof LDAPPreReadRequestControl)
869              {
870                preReadRequest = (LDAPPreReadRequestControl) c;
871              }
872              else
873              {
874                try
875                {
876                  preReadRequest = LDAPPreReadRequestControl.decodeControl(c);
877                  requestControls.set(i, preReadRequest);
878                }
879                catch (LDAPException le)
880                {
881                  if (debugEnabled())
882                  {
883                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
884                  }
885    
886                  throw new DirectoryException(
887                                 ResultCode.valueOf(le.getResultCode()),
888                                 le.getMessageObject());
889                }
890              }
891            }
892            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
893            {
894              if (c instanceof LDAPPostReadRequestControl)
895              {
896                postReadRequest = (LDAPPostReadRequestControl) c;
897              }
898              else
899              {
900                try
901                {
902                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
903                  requestControls.set(i, postReadRequest);
904                }
905                catch (LDAPException le)
906                {
907                  if (debugEnabled())
908                  {
909                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
910                  }
911    
912                  throw new DirectoryException(
913                                 ResultCode.valueOf(le.getResultCode()),
914                                 le.getMessageObject());
915                }
916              }
917            }
918            else if (oid.equals(OID_PROXIED_AUTH_V1))
919            {
920              // The requester must have the PROXIED_AUTH privilige in order to
921              // be able to use this control.
922              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
923              {
924                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
925                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
926              }
927    
928    
929              ProxiedAuthV1Control proxyControl;
930              if (c instanceof ProxiedAuthV1Control)
931              {
932                proxyControl = (ProxiedAuthV1Control) c;
933              }
934              else
935              {
936                try
937                {
938                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
939                }
940                catch (LDAPException le)
941                {
942                  if (debugEnabled())
943                  {
944                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
945                  }
946    
947                  throw new DirectoryException(
948                                 ResultCode.valueOf(le.getResultCode()),
949                                 le.getMessageObject());
950                }
951              }
952    
953    
954              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
955              setAuthorizationEntry(authorizationEntry);
956              if (authorizationEntry == null)
957              {
958                setProxiedAuthorizationDN(DN.nullDN());
959              }
960              else
961              {
962                setProxiedAuthorizationDN(authorizationEntry.getDN());
963              }
964            }
965            else if (oid.equals(OID_PROXIED_AUTH_V2))
966            {
967              // The requester must have the PROXIED_AUTH privilige in order to
968              // be able to use this control.
969              if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
970              {
971                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
972                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
973              }
974    
975    
976              ProxiedAuthV2Control proxyControl;
977              if (c instanceof ProxiedAuthV2Control)
978              {
979                proxyControl = (ProxiedAuthV2Control) c;
980              }
981              else
982              {
983                try
984                {
985                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
986                }
987                catch (LDAPException le)
988                {
989                  if (debugEnabled())
990                  {
991                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
992                  }
993    
994                  throw new DirectoryException(
995                                 ResultCode.valueOf(le.getResultCode()),
996                                 le.getMessageObject());
997                }
998              }
999    
1000    
1001              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
1002              setAuthorizationEntry(authorizationEntry);
1003              if (authorizationEntry == null)
1004              {
1005                setProxiedAuthorizationDN(DN.nullDN());
1006              }
1007              else
1008              {
1009                setProxiedAuthorizationDN(authorizationEntry.getDN());
1010              }
1011            }
1012            else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
1013            {
1014              pwPolicyControlRequested = true;
1015            }
1016    
1017            // NYI -- Add support for additional controls.
1018            else if (c.isCritical())
1019            {
1020              if ((backend == null) || (! backend.supportsControl(oid)))
1021              {
1022                throw new DirectoryException(
1023                               ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
1024                               ERR_MODIFY_UNSUPPORTED_CRITICAL_CONTROL.get(
1025                                    String.valueOf(entryDN), oid));
1026              }
1027            }
1028          }
1029        }
1030      }
1031    
1032       /**
1033       * Handles schema processing for non-password modifications.
1034       *
1035       * @throws  DirectoryException  If a problem is encountered that should cause
1036       *                              the modify operation to fail.
1037       */
1038      private void handleSchemaProcessing() throws DirectoryException
1039      {
1040    
1041        for (Modification m : modifications)
1042        {
1043          Attribute     a = m.getAttribute();
1044          AttributeType t = a.getAttributeType();
1045    
1046    
1047          // If the attribute type is marked "NO-USER-MODIFICATION" then fail unless
1048          // this is an internal operation or is related to synchronization in some
1049          // way.
1050          if (t.isNoUserModification())
1051          {
1052            if (! (isInternalOperation() || isSynchronizationOperation() ||
1053                    m.isInternal()))
1054            {
1055              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1056                      ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(
1057                              String.valueOf(entryDN), a.getName()));
1058            }
1059          }
1060    
1061          // If the attribute type is marked "OBSOLETE" and the modification is
1062          // setting new values, then fail unless this is an internal operation or
1063          // is related to synchronization in some way.
1064          if (t.isObsolete())
1065          {
1066            if (a.hasValue() &&
1067                    (m.getModificationType() != ModificationType.DELETE))
1068            {
1069              if (! (isInternalOperation() || isSynchronizationOperation() ||
1070                      m.isInternal()))
1071              {
1072                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1073                        ERR_MODIFY_ATTR_IS_OBSOLETE.get(
1074                                String.valueOf(entryDN), a.getName()));
1075              }
1076            }
1077          }
1078    
1079    
1080          // See if the attribute is one which controls the privileges available for
1081          // a user.  If it is, then the client must have the PRIVILEGE_CHANGE
1082          // privilege.
1083          if (t.hasName(OP_ATTR_PRIVILEGE_NAME))
1084          {
1085            if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this))
1086            {
1087              throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1088                      ERR_MODIFY_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
1089            }
1090          }
1091    
1092          // If the modification is not updating the password attribute,
1093          // then check if the isEnabled flag should be set and then perform any
1094          // schema processing.
1095          boolean isPassword =
1096                  t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
1097          if (!isPassword )
1098          {
1099            // See if it's an attribute used to maintain the account
1100            // enabled/disabled state.
1101            AttributeType disabledAttr =
1102                   DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
1103            if (t.equals(disabledAttr))
1104            {
1105              enabledStateChanged = true;
1106              for (AttributeValue v : a.getValues())
1107              {
1108                try
1109                {
1110                  isEnabled =
1111                      (! BooleanSyntax.decodeBooleanValue(v.getNormalizedValue()));
1112                }
1113                catch (DirectoryException de)
1114                {
1115                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1116                          ERR_MODIFY_INVALID_DISABLED_VALUE.get(
1117                                  OP_ATTR_ACCOUNT_DISABLED,
1118                                  String.valueOf(de.getMessageObject())), de);
1119                }
1120              }
1121            }
1122    
1123            switch (m.getModificationType())
1124            {
1125              case ADD:
1126                processInitialAddSchema(a);
1127                break;
1128    
1129              case DELETE:
1130                processInitialDeleteSchema(a);
1131                break;
1132    
1133              case REPLACE:
1134                processInitialReplaceSchema(a);
1135                break;
1136    
1137              case INCREMENT:
1138                processInitialIncrementSchema(a);
1139                break;
1140            }
1141          }
1142        }
1143      }
1144    
1145      /**
1146       * Handles the initial set of password policy  for this modify operation.
1147       *
1148       * @throws  DirectoryException  If a problem is encountered that should cause
1149       *                              the modify operation to fail.
1150       */
1151      private void handleInitialPasswordPolicyProcessing()
1152              throws DirectoryException
1153      {
1154        // Declare variables used for password policy state processing.
1155        currentPasswordProvided = false;
1156        isEnabled = true;
1157        enabledStateChanged = false;
1158        if (currentEntry.hasAttribute(
1159                pwPolicyState.getPolicy().getPasswordAttribute()))
1160        {
1161          // It may actually have more than one, but we can't tell the difference if
1162          // the values are encoded, and its enough for our purposes just to know
1163          // that there is at least one.
1164          numPasswords = 1;
1165        }
1166        else
1167        {
1168          numPasswords = 0;
1169        }
1170    
1171    
1172        // If it's not an internal or synchronization operation, then iterate
1173        // through the set of modifications to see if a password is included in the
1174        // changes.  If so, then add the appropriate state changes to the set of
1175        // modifications.
1176        if (! (isInternalOperation() || isSynchronizationOperation()))
1177        {
1178          for (Modification m : modifications)
1179          {
1180            AttributeType t = m.getAttribute().getAttributeType();
1181            boolean isPassword =
1182                    t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
1183            if (isPassword)
1184            {
1185              passwordChanged = true;
1186              if (! selfChange)
1187              {
1188                if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, this))
1189                {
1190                  pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
1191                  throw new DirectoryException(
1192                          ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1193                          ERR_MODIFY_PWRESET_INSUFFICIENT_PRIVILEGES.get());
1194                }
1195              }
1196    
1197              break;
1198            }
1199          }
1200        }
1201    
1202    
1203        for (Modification m : modifications)
1204        {
1205          Attribute     a = m.getAttribute();
1206          AttributeType t = a.getAttributeType();
1207    
1208    
1209          // If the modification is updating the password attribute, then perform
1210          // any necessary password policy processing.  This processing should be
1211          // skipped for synchronization operations.
1212          boolean isPassword =
1213                  t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
1214          if (isPassword)
1215          {
1216            if (!isSynchronizationOperation())
1217            {
1218              // If the attribute contains any options, then reject it.  Passwords
1219              // will not be allowed to have options.
1220              // Skipped for internal operations.
1221              if (!isInternalOperation())
1222              {
1223                if (a.hasOptions())
1224                {
1225                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1226                      ERR_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS.get());
1227                }
1228    
1229    
1230                // If it's a self change, then see if that's allowed.
1231                if (selfChange &&
1232                    (! pwPolicyState.getPolicy().allowUserPasswordChanges()))
1233                {
1234                  pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
1235                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1236                      ERR_MODIFY_NO_USER_PW_CHANGES.get());
1237                }
1238    
1239    
1240                // If we require secure password changes, then makes sure it's a
1241                // secure communication channel.
1242                if (pwPolicyState.getPolicy().requireSecurePasswordChanges() &&
1243                    (! clientConnection.isSecure()))
1244                {
1245                  pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
1246                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1247                      ERR_MODIFY_REQUIRE_SECURE_CHANGES.get());
1248                }
1249    
1250    
1251                // If it's a self change and it's not been long enough since the
1252                // previous change, then reject it.
1253                if (selfChange && pwPolicyState.isWithinMinimumAge())
1254                {
1255                  pwpErrorType = PasswordPolicyErrorType.PASSWORD_TOO_YOUNG;
1256                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1257                      ERR_MODIFY_WITHIN_MINIMUM_AGE.get());
1258                }
1259              }
1260    
1261              // Check to see whether this will adding, deleting, or replacing
1262              // password values (increment doesn't make any sense for passwords).
1263              // Then perform the appropriate type of processing for that kind of
1264              // modification.
1265              boolean isAdd = (m.getModificationType() == ModificationType.ADD);
1266              LinkedHashSet<AttributeValue> pwValues = a.getValues();
1267              LinkedHashSet<AttributeValue> encodedValues =
1268                new LinkedHashSet<AttributeValue>();
1269              switch (m.getModificationType())
1270              {
1271              case ADD:
1272              case REPLACE:
1273                processInitialAddOrReplacePW(isAdd, pwValues, encodedValues, a);
1274                break;
1275    
1276              case DELETE:
1277                processInitialDeletePW(pwValues, encodedValues, a);
1278                break;
1279    
1280              default:
1281                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1282                    ERR_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD.get(
1283                        String.valueOf(m.getModificationType()),
1284                        a.getName()));
1285              }
1286            }
1287    
1288            switch (m.getModificationType())
1289            {
1290            case ADD:
1291              processInitialAddSchema(a);
1292              break;
1293    
1294            case DELETE:
1295              processInitialDeleteSchema(a);
1296              break;
1297    
1298            case REPLACE:
1299              processInitialReplaceSchema(a);
1300              break;
1301    
1302            case INCREMENT:
1303              processInitialIncrementSchema(a);
1304              break;
1305            }
1306          }
1307        }
1308      }
1309    
1310    
1311    
1312      /**
1313       * Performs the initial password policy add or replace processing.
1314       *
1315       * @param  isAdd          Indicates whether it is an add or replace update.
1316       * @param  pwValues       The set of password values as included in the
1317       *                        request.
1318       * @param  encodedValues  The set of encoded password values.
1319       * @param  pwAttr         The attribute involved in the password change.
1320       *
1321       * @throws  DirectoryException  If a problem occurs that should cause the
1322       *                              modify operation to fail.
1323       */
1324      private void processInitialAddOrReplacePW(boolean isAdd,
1325                        LinkedHashSet<AttributeValue> pwValues,
1326                        LinkedHashSet<AttributeValue> encodedValues,
1327                        Attribute pwAttr)
1328              throws DirectoryException
1329      {
1330        int passwordsToAdd = pwValues.size();
1331    
1332        if (isAdd)
1333        {
1334          numPasswords += passwordsToAdd;
1335        }
1336        else
1337        {
1338          numPasswords = passwordsToAdd;
1339        }
1340    
1341    
1342        // If there were multiple password values, then make sure that's OK.
1343        if ((! isInternalOperation()) &&
1344            (! pwPolicyState.getPolicy().allowMultiplePasswordValues()) &&
1345            (passwordsToAdd > 1))
1346        {
1347          pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
1348          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1349                         ERR_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED.get());
1350        }
1351    
1352    
1353        // Iterate through the password values and see if any of them are
1354        // pre-encoded.  If so, then check to see if we'll allow it.  Otherwise,
1355        // store the clear-text values for later validation and update the attribute
1356        // with the encoded values.
1357        for (AttributeValue v : pwValues)
1358        {
1359          if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
1360          {
1361            if ((! isInternalOperation()) &&
1362                ! pwPolicyState.getPolicy().allowPreEncodedPasswords())
1363            {
1364              pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
1365              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1366                             ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
1367            }
1368            else
1369            {
1370              encodedValues.add(v);
1371            }
1372          }
1373          else
1374          {
1375            if (isAdd)
1376            {
1377              // Make sure that the password value doesn't already exist.
1378              if (pwPolicyState.passwordMatches(v.getValue()))
1379              {
1380                pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
1381                throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
1382                                             ERR_MODIFY_PASSWORD_EXISTS.get());
1383              }
1384            }
1385    
1386            if (newPasswords == null)
1387            {
1388              newPasswords = new LinkedList<AttributeValue>();
1389            }
1390    
1391            newPasswords.add(v);
1392    
1393            for (ByteString s : pwPolicyState.encodePassword(v.getValue()))
1394            {
1395              encodedValues.add(new AttributeValue(pwAttr.getAttributeType(), s));
1396            }
1397          }
1398        }
1399    
1400        pwAttr.setValues(encodedValues);
1401      }
1402    
1403    
1404    
1405      /**
1406       * Performs the initial password policy delete processing.
1407       *
1408       * @param  pwValues       The set of password values as included in the
1409       *                        request.
1410       * @param  encodedValues  The set of encoded password values.
1411       * @param  pwAttr         The attribute involved in the password change.
1412       *
1413       * @throws  DirectoryException  If a problem occurs that should cause the
1414       *                              modify operation to fail.
1415       */
1416      private void processInitialDeletePW(LinkedHashSet<AttributeValue> pwValues,
1417                        LinkedHashSet<AttributeValue> encodedValues,
1418                        Attribute pwAttr)
1419              throws DirectoryException
1420      {
1421        // Iterate through the password values and see if any of them are
1422        // pre-encoded.  We will never allow pre-encoded passwords for user password
1423        // changes, but we will allow them for administrators.  For each clear-text
1424        // value, verify that at least one value in the entry matches and replace
1425        // the clear-text value with the appropriate encoded forms.
1426        for (AttributeValue v : pwValues)
1427        {
1428          if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
1429          {
1430            if ((! isInternalOperation()) && selfChange)
1431            {
1432              pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
1433              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1434                             ERR_MODIFY_NO_PREENCODED_PASSWORDS.get());
1435            }
1436            else
1437            {
1438              encodedValues.add(v);
1439            }
1440          }
1441          else
1442          {
1443            List<Attribute> attrList =
1444                 currentEntry.getAttribute(pwAttr.getAttributeType());
1445            if ((attrList == null) || (attrList.isEmpty()))
1446            {
1447              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1448                                           ERR_MODIFY_NO_EXISTING_VALUES.get());
1449            }
1450            boolean found = false;
1451            for (Attribute attr : attrList)
1452            {
1453              for (AttributeValue av : attr.getValues())
1454              {
1455                if (pwPolicyState.getPolicy().usesAuthPasswordSyntax())
1456                {
1457                  if (AuthPasswordSyntax.isEncoded(av.getValue()))
1458                  {
1459                    StringBuilder[] compoenents =
1460                         AuthPasswordSyntax.decodeAuthPassword(av.getStringValue());
1461                    PasswordStorageScheme scheme =
1462                         DirectoryServer.getAuthPasswordStorageScheme(
1463                              compoenents[0].toString());
1464                    if (scheme != null)
1465                    {
1466                      if (scheme.authPasswordMatches(v.getValue(),
1467                                                     compoenents[1].toString(),
1468                                                     compoenents[2].toString()))
1469                      {
1470                        encodedValues.add(av);
1471                        found = true;
1472                      }
1473                    }
1474                  }
1475                  else
1476                  {
1477                    if (av.equals(v))
1478                    {
1479                      encodedValues.add(v);
1480                      found = true;
1481                    }
1482                  }
1483                }
1484                else
1485                {
1486                  if (UserPasswordSyntax.isEncoded(av.getValue()))
1487                  {
1488                    String[] compoenents = UserPasswordSyntax.decodeUserPassword(
1489                                                av.getStringValue());
1490                    PasswordStorageScheme scheme =
1491                         DirectoryServer.getPasswordStorageScheme(
1492                              toLowerCase(compoenents[0]));
1493                    if (scheme != null)
1494                    {
1495                      if (scheme.passwordMatches(v.getValue(),
1496                                      new ASN1OctetString(compoenents[1])))
1497                      {
1498                        encodedValues.add(av);
1499                        found = true;
1500                      }
1501                    }
1502                  }
1503                  else
1504                  {
1505                    if (av.equals(v))
1506                    {
1507                      encodedValues.add(v);
1508                      found = true;
1509                    }
1510                  }
1511                }
1512              }
1513            }
1514    
1515            if (found)
1516            {
1517              if (currentPasswords == null)
1518              {
1519                currentPasswords = new LinkedList<AttributeValue>();
1520              }
1521              currentPasswords.add(v);
1522              numPasswords--;
1523            }
1524            else
1525            {
1526              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1527                                           ERR_MODIFY_INVALID_PASSWORD.get());
1528            }
1529    
1530            currentPasswordProvided = true;
1531          }
1532        }
1533    
1534        pwAttr.setValues(encodedValues);
1535      }
1536    
1537    
1538    
1539      /**
1540       * Performs the initial schema processing for an add modification and updates
1541       * the entry appropriately.
1542       *
1543       * @param  attr  The attribute being added.
1544       *
1545       * @throws  DirectoryException  If a problem occurs that should cause the
1546       *                              modify operation to fail.
1547       */
1548      private void processInitialAddSchema(Attribute attr)
1549              throws DirectoryException
1550      {
1551        // Make sure that one or more values have been provided for the attribute.
1552        LinkedHashSet<AttributeValue> newValues = attr.getValues();
1553        if ((newValues == null) || newValues.isEmpty())
1554        {
1555          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1556                         ERR_MODIFY_ADD_NO_VALUES.get(String.valueOf(entryDN),
1557                                                     attr.getName()));
1558        }
1559    
1560        // If the server is configured to check schema and the operation is not a
1561        // synchronization operation, make sure that all the new values are valid
1562        // according to the associated syntax.
1563        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
1564        {
1565          AcceptRejectWarn syntaxPolicy =
1566               DirectoryServer.getSyntaxEnforcementPolicy();
1567          AttributeSyntax syntax = attr.getAttributeType().getSyntax();
1568    
1569          if (syntaxPolicy == AcceptRejectWarn.REJECT)
1570          {
1571            MessageBuilder invalidReason = new MessageBuilder();
1572            for (AttributeValue v : newValues)
1573            {
1574              if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1575              {
1576                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1577                               ERR_MODIFY_ADD_INVALID_SYNTAX.get(
1578                                    String.valueOf(entryDN), attr.getName(),
1579                                    v.getStringValue(), invalidReason));
1580              }
1581            }
1582          }
1583          else if (syntaxPolicy == AcceptRejectWarn.WARN)
1584          {
1585            MessageBuilder invalidReason = new MessageBuilder();
1586            for (AttributeValue v : newValues)
1587            {
1588              if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1589              {
1590                setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
1591                logError(ERR_MODIFY_ADD_INVALID_SYNTAX.get(String.valueOf(entryDN),
1592                              attr.getName(), v.getStringValue(), invalidReason));
1593                invalidReason = new MessageBuilder();
1594              }
1595            }
1596          }
1597        }
1598    
1599    
1600        // Add the provided attribute or merge an existing attribute with
1601        // the values of the new attribute.  If there are any duplicates,
1602        // then fail.
1603        if (attr.getAttributeType().isObjectClassType())
1604        {
1605          modifiedEntry.addObjectClasses(newValues);
1606        }
1607        else
1608        {
1609          LinkedList<AttributeValue> duplicateValues =
1610               new LinkedList<AttributeValue>();
1611          modifiedEntry.addAttribute(attr, duplicateValues);
1612          if (! duplicateValues.isEmpty())
1613          {
1614            StringBuilder buffer = new StringBuilder();
1615            Iterator<AttributeValue> iterator = duplicateValues.iterator();
1616            buffer.append(iterator.next().getStringValue());
1617            while (iterator.hasNext())
1618            {
1619              buffer.append(", ");
1620              buffer.append(iterator.next().getStringValue());
1621            }
1622    
1623            throw new DirectoryException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
1624                           ERR_MODIFY_ADD_DUPLICATE_VALUE.get(
1625                                String.valueOf(entryDN), attr.getName(), buffer));
1626          }
1627        }
1628      }
1629    
1630    
1631    
1632      /**
1633       * Performs the initial schema processing for a delete modification and
1634       * updates the entry appropriately.
1635       *
1636       * @param  attr  The attribute being deleted.
1637       *
1638       * @throws  DirectoryException  If a problem occurs that should cause the
1639       *                              modify operation to fail.
1640       */
1641      private void processInitialDeleteSchema(Attribute attr)
1642              throws DirectoryException
1643      {
1644        // Remove the specified attribute values or the entire attribute from the
1645        // value.  If there are any specified values that were not present, then
1646        // fail.  If the RDN attribute value would be removed, then fail.
1647        LinkedList<AttributeValue> missingValues = new LinkedList<AttributeValue>();
1648        boolean attrExists = modifiedEntry.removeAttribute(attr, missingValues);
1649    
1650        if (attrExists)
1651        {
1652          if (missingValues.isEmpty())
1653          {
1654            AttributeType t = attr.getAttributeType();
1655    
1656            RDN rdn = modifiedEntry.getDN().getRDN();
1657            if ((rdn !=  null) && rdn.hasAttributeType(t) &&
1658                (! modifiedEntry.hasValue(t, attr.getOptions(),
1659                                          rdn.getAttributeValue(t))))
1660            {
1661              throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1662                                           ERR_MODIFY_DELETE_RDN_ATTR.get(
1663                                                String.valueOf(entryDN),
1664                                                attr.getName()));
1665            }
1666          }
1667          else
1668          {
1669            StringBuilder buffer = new StringBuilder();
1670            Iterator<AttributeValue> iterator = missingValues.iterator();
1671            buffer.append(iterator.next().getStringValue());
1672            while (iterator.hasNext())
1673            {
1674              buffer.append(", ");
1675              buffer.append(iterator.next().getStringValue());
1676            }
1677    
1678            throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
1679                           ERR_MODIFY_DELETE_MISSING_VALUES.get(
1680                                String.valueOf(entryDN), attr.getName(), buffer));
1681          }
1682        }
1683        else
1684        {
1685          throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
1686                         ERR_MODIFY_DELETE_NO_SUCH_ATTR.get(
1687                              String.valueOf(entryDN), attr.getName()));
1688        }
1689      }
1690    
1691    
1692    
1693      /**
1694       * Performs the initial schema processing for a replace modification and
1695       * updates the entry appropriately.
1696       *
1697       * @param  attr  The attribute being replaced.
1698       *
1699       * @throws  DirectoryException  If a problem occurs that should cause the
1700       *                              modify operation to fail.
1701       */
1702      private void processInitialReplaceSchema(Attribute attr)
1703              throws DirectoryException
1704      {
1705        // If it is the objectclass attribute, then treat that separately.
1706        if (attr.getAttributeType().isObjectClassType())
1707        {
1708          modifiedEntry.setObjectClasses(attr.getValues());
1709          return;
1710        }
1711    
1712    
1713        // If the provided attribute does not have any values, then we will simply
1714        // remove the attribute from the entry (if it exists).
1715        AttributeType t = attr.getAttributeType();
1716        if (! attr.hasValue())
1717        {
1718          modifiedEntry.removeAttribute(t, attr.getOptions());
1719          RDN rdn = modifiedEntry.getDN().getRDN();
1720          if ((rdn !=  null) && rdn.hasAttributeType(t) &&
1721              (! modifiedEntry.hasValue(t, attr.getOptions(),
1722                                        rdn.getAttributeValue(t))))
1723          {
1724            throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1725                           ERR_MODIFY_DELETE_RDN_ATTR.get(String.valueOf(entryDN),
1726                                                          attr.getName()));
1727          }
1728    
1729          return;
1730        }
1731    
1732        // If the server is configured to check schema and the operation is not a
1733        // synchronization operation, make sure that all the new values are valid
1734        // according to the associated syntax.
1735        LinkedHashSet<AttributeValue> newValues = attr.getValues();
1736        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
1737        {
1738          AcceptRejectWarn syntaxPolicy =
1739            DirectoryServer.getSyntaxEnforcementPolicy();
1740          AttributeSyntax syntax = t.getSyntax();
1741    
1742          if (syntaxPolicy == AcceptRejectWarn.REJECT)
1743          {
1744            MessageBuilder invalidReason = new MessageBuilder();
1745            for (AttributeValue v : newValues)
1746            {
1747              if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1748              {
1749                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1750                               ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
1751                                    String.valueOf(entryDN), attr.getName(),
1752                                    v.getStringValue(), invalidReason));
1753              }
1754            }
1755          }
1756          else if (syntaxPolicy == AcceptRejectWarn.WARN)
1757          {
1758            MessageBuilder invalidReason = new MessageBuilder();
1759            for (AttributeValue v : newValues)
1760            {
1761              if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1762              {
1763                setResultCode(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
1764                logError(ERR_MODIFY_REPLACE_INVALID_SYNTAX.get(
1765                              String.valueOf(entryDN), attr.getName(),
1766                              v.getStringValue(), invalidReason));
1767                invalidReason = new MessageBuilder();
1768              }
1769            }
1770          }
1771        }
1772    
1773    
1774        // If the provided attribute does not have any options, then we will simply
1775        // use it in place of any existing attribute of the provided type (or add it
1776        // if it doesn't exist).
1777        if (! attr.hasOptions())
1778        {
1779          List<Attribute> attrList = new ArrayList<Attribute>(1);
1780          attrList.add(attr);
1781          modifiedEntry.putAttribute(t, attrList);
1782    
1783          RDN rdn = modifiedEntry.getDN().getRDN();
1784          if ((rdn !=  null) && rdn.hasAttributeType(t) &&
1785              (! modifiedEntry.hasValue(t, attr.getOptions(),
1786                                        rdn.getAttributeValue(t))))
1787          {
1788            throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1789                                         ERR_MODIFY_DELETE_RDN_ATTR.get(
1790                                              String.valueOf(entryDN),
1791                                              attr.getName()));
1792          }
1793    
1794          return;
1795        }
1796    
1797    
1798        // See if there is an existing attribute of the provided type.  If not, then
1799        // we'll use the new one.
1800        List<Attribute> attrList = modifiedEntry.getAttribute(t);
1801        if ((attrList == null) || attrList.isEmpty())
1802        {
1803          attrList = new ArrayList<Attribute>(1);
1804          attrList.add(attr);
1805          modifiedEntry.putAttribute(t, attrList);
1806    
1807          RDN rdn = modifiedEntry.getDN().getRDN();
1808          if ((rdn !=  null) && rdn.hasAttributeType(t) &&
1809              (! modifiedEntry.hasValue(t, attr.getOptions(),
1810                                        rdn.getAttributeValue(t))))
1811          {
1812            throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1813                                         ERR_MODIFY_DELETE_RDN_ATTR.get(
1814                                              String.valueOf(entryDN),
1815                                              attr.getName()));
1816          }
1817    
1818          return;
1819        }
1820    
1821    
1822        // There must be an existing occurrence of the provided attribute in the
1823        // entry.  If there is a version with exactly the set of options provided,
1824        // then replace it.  Otherwise, add a new one.
1825        boolean found = false;
1826        for (int i=0; i < attrList.size(); i++)
1827        {
1828          if (attrList.get(i).optionsEqual(attr.getOptions()))
1829          {
1830            attrList.set(i, attr);
1831            found = true;
1832            break;
1833          }
1834        }
1835    
1836        if (! found)
1837        {
1838          attrList.add(attr);
1839        }
1840    
1841        RDN rdn = modifiedEntry.getDN().getRDN();
1842        if ((rdn !=  null) && rdn.hasAttributeType(t) &&
1843            (! modifiedEntry.hasValue(t, attr.getOptions(),
1844                                      rdn.getAttributeValue(t))))
1845        {
1846          throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1847                                       ERR_MODIFY_DELETE_RDN_ATTR.get(
1848                                            String.valueOf(entryDN),
1849                                            attr.getName()));
1850        }
1851      }
1852    
1853    
1854    
1855      /**
1856       * Performs the initial schema processing for an increment modification and
1857       * updates the entry appropriately.
1858       *
1859       * @param  attr  The attribute being incremented.
1860       *
1861       * @throws  DirectoryException  If a problem occurs that should cause the
1862       *                              modify operation to fail.
1863       */
1864      private void processInitialIncrementSchema(Attribute attr)
1865              throws DirectoryException
1866      {
1867        // The specified attribute type must not be an RDN attribute.
1868        AttributeType t = attr.getAttributeType();
1869        RDN rdn = modifiedEntry.getDN().getRDN();
1870        if ((rdn !=  null) && rdn.hasAttributeType(t))
1871        {
1872          throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_RDN,
1873                                       ERR_MODIFY_INCREMENT_RDN.get(
1874                                            String.valueOf(entryDN),
1875                                            attr.getName()));
1876        }
1877    
1878    
1879        // The provided attribute must have a single value, and it must be an
1880        // integer.
1881        LinkedHashSet<AttributeValue> values = attr.getValues();
1882        if ((values == null) || values.isEmpty())
1883        {
1884          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1885                                       ERR_MODIFY_INCREMENT_REQUIRES_VALUE.get(
1886                                            String.valueOf(entryDN),
1887                                            attr.getName()));
1888        }
1889        else if (values.size() > 1)
1890        {
1891          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1892                         ERR_MODIFY_INCREMENT_REQUIRES_SINGLE_VALUE.get(
1893                              String.valueOf(entryDN), attr.getName()));
1894        }
1895    
1896        AttributeValue v = values.iterator().next();
1897    
1898        long incrementValue;
1899        try
1900        {
1901          incrementValue = Long.parseLong(v.getNormalizedStringValue());
1902        }
1903        catch (Exception e)
1904        {
1905          if (debugEnabled())
1906          {
1907            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1908          }
1909    
1910          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1911                         ERR_MODIFY_INCREMENT_PROVIDED_VALUE_NOT_INTEGER.get(
1912                              String.valueOf(entryDN), attr.getName(),
1913                              v.getStringValue()), e);
1914        }
1915    
1916    
1917        // Get the corresponding attribute from the entry and make sure that it has
1918        // a single integer value.
1919        List<Attribute> attrList = modifiedEntry.getAttribute(t, attr.getOptions());
1920        if ((attrList == null) || attrList.isEmpty())
1921        {
1922          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1923                         ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
1924                              String.valueOf(entryDN), attr.getName()));
1925        }
1926    
1927        boolean updated = false;
1928        for (Attribute a : attrList)
1929        {
1930          LinkedHashSet<AttributeValue> valueList = a.getValues();
1931          if ((valueList == null) || valueList.isEmpty())
1932          {
1933            continue;
1934          }
1935    
1936          LinkedHashSet<AttributeValue> newValueList =
1937            new LinkedHashSet<AttributeValue>(valueList.size());
1938          for (AttributeValue existingValue : valueList)
1939          {
1940            long newIntValue;
1941            try
1942            {
1943              long existingIntValue =
1944                        Long.parseLong(existingValue.getStringValue());
1945              newIntValue = existingIntValue + incrementValue;
1946            }
1947            catch (Exception e)
1948            {
1949              if (debugEnabled())
1950              {
1951                TRACER.debugCaught(DebugLogLevel.ERROR, e);
1952              }
1953    
1954              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1955                             ERR_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE.get(
1956                                  String.valueOf(entryDN), a.getName(),
1957                                  existingValue.getStringValue()), e);
1958            }
1959    
1960            ByteString newValue = new ASN1OctetString(String.valueOf(newIntValue));
1961            newValueList.add(new AttributeValue(t, newValue));
1962          }
1963    
1964          a.setValues(newValueList);
1965          updated = true;
1966        }
1967    
1968        if (! updated)
1969        {
1970          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
1971                         ERR_MODIFY_INCREMENT_REQUIRES_EXISTING_VALUE.get(
1972                              String.valueOf(entryDN), attr.getName()));
1973        }
1974      }
1975    
1976    
1977    
1978      /**
1979       * Performs additional preliminary processing that is required for a password
1980       * change.
1981       *
1982       * @throws  DirectoryException  If a problem occurs that should cause the
1983       *                              modify operation to fail.
1984       */
1985      public void performAdditionalPasswordChangedProcessing()
1986             throws DirectoryException
1987      {
1988        // If it was a self change, then see if the current password was provided
1989        // and handle accordingly.
1990        if (selfChange &&
1991            pwPolicyState.getPolicy().requireCurrentPassword() &&
1992            (! currentPasswordProvided))
1993        {
1994          pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
1995    
1996          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1997                         ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
1998        }
1999    
2000    
2001        // If this change would result in multiple password values, then see if
2002        // that's OK.
2003        if ((numPasswords > 1) &&
2004            (! pwPolicyState.getPolicy().allowMultiplePasswordValues()))
2005        {
2006          pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
2007          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2008                         ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
2009        }
2010    
2011    
2012        // If any of the password values should be validated, then do so now.
2013        if (selfChange ||
2014            (! pwPolicyState.getPolicy().skipValidationForAdministrators()))
2015        {
2016          if (newPasswords != null)
2017          {
2018            HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
2019            clearPasswords.addAll(pwPolicyState.getClearPasswords());
2020    
2021            if (currentPasswords != null)
2022            {
2023              if (clearPasswords.isEmpty())
2024              {
2025                for (AttributeValue v : currentPasswords)
2026                {
2027                  clearPasswords.add(v.getValue());
2028                }
2029              }
2030              else
2031              {
2032                // NOTE:  We can't rely on the fact that Set doesn't allow
2033                // duplicates because technically it's possible that the values
2034                // aren't duplicates if they are ASN.1 elements with different types
2035                // (like 0x04 for a standard universal octet string type versus 0x80
2036                // for a simple password in a bind operation).  So we have to
2037                // manually check for duplicates.
2038                for (AttributeValue v : currentPasswords)
2039                {
2040                  ByteString pw = v.getValue();
2041    
2042                  boolean found = false;
2043                  for (ByteString s : clearPasswords)
2044                  {
2045                    if (Arrays.equals(s.value(), pw.value()))
2046                    {
2047                      found = true;
2048                      break;
2049                    }
2050                  }
2051    
2052                  if (! found)
2053                  {
2054                    clearPasswords.add(pw);
2055                  }
2056                }
2057              }
2058            }
2059    
2060            for (AttributeValue v : newPasswords)
2061            {
2062              MessageBuilder invalidReason = new MessageBuilder();
2063              if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
2064                                       v.getValue(), clearPasswords, invalidReason))
2065              {
2066                pwpErrorType =
2067                     PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
2068                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2069                                             ERR_MODIFY_PW_VALIDATION_FAILED.get(
2070                                                  invalidReason));
2071              }
2072            }
2073          }
2074        }
2075    
2076    
2077        // If we should check the password history, then do so now.
2078        if (pwPolicyState.maintainHistory())
2079        {
2080          if (newPasswords != null)
2081          {
2082            for (AttributeValue v : newPasswords)
2083            {
2084              if (pwPolicyState.isPasswordInHistory(v.getValue()))
2085              {
2086                if (selfChange || (! pwPolicyState.getPolicy().
2087                                          skipValidationForAdministrators()))
2088                {
2089                  pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
2090                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2091                                               ERR_MODIFY_PW_IN_HISTORY.get());
2092                }
2093              }
2094            }
2095    
2096            pwPolicyState.updatePasswordHistory();
2097          }
2098        }
2099    
2100    
2101        // See if the account was locked for any reason.
2102        wasLocked = pwPolicyState.lockedDueToIdleInterval() ||
2103                    pwPolicyState.lockedDueToMaximumResetAge() ||
2104                    pwPolicyState.lockedDueToFailures();
2105    
2106        // Update the password policy state attributes in the user's entry.  If the
2107        // modification fails, then these changes won't be applied.
2108        pwPolicyState.setPasswordChangedTime();
2109        pwPolicyState.clearFailureLockout();
2110        pwPolicyState.clearGraceLoginTimes();
2111        pwPolicyState.clearWarnedTime();
2112    
2113        if (pwPolicyState.getPolicy().forceChangeOnAdd() ||
2114            pwPolicyState.getPolicy().forceChangeOnReset())
2115        {
2116          if (selfChange)
2117          {
2118            pwPolicyState.setMustChangePassword(false);
2119          }
2120          else
2121          {
2122            if ((pwpErrorType == null) &&
2123                pwPolicyState.getPolicy().forceChangeOnReset())
2124            {
2125              pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
2126            }
2127    
2128            pwPolicyState.setMustChangePassword(
2129                 pwPolicyState.getPolicy().forceChangeOnReset());
2130          }
2131        }
2132    
2133        if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
2134        {
2135          pwPolicyState.setRequiredChangeTime();
2136        }
2137    
2138        modifications.addAll(pwPolicyState.getModifications());
2139        modifiedEntry.applyModifications(pwPolicyState.getModifications());
2140      }
2141    
2142    
2143    
2144      /**
2145       * Checks to ensure that both the Directory Server and the backend are
2146       * writable.
2147       *
2148       * @throws  DirectoryException  If the modify operation should not be allowed
2149       *                              as a result of the writability check.
2150       */
2151      private void checkWritability()
2152              throws DirectoryException
2153      {
2154        // If it is not a private backend, then check to see if the server or
2155        // backend is operating in read-only mode.
2156        if (! backend.isPrivateBackend())
2157        {
2158          switch (DirectoryServer.getWritabilityMode())
2159          {
2160            case DISABLED:
2161              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2162                                           ERR_MODIFY_SERVER_READONLY.get(
2163                                                String.valueOf(entryDN)));
2164    
2165            case INTERNAL_ONLY:
2166              if (! (isInternalOperation() || isSynchronizationOperation()))
2167              {
2168                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2169                                             ERR_MODIFY_SERVER_READONLY.get(
2170                                                  String.valueOf(entryDN)));
2171              }
2172          }
2173    
2174          switch (backend.getWritabilityMode())
2175          {
2176            case DISABLED:
2177              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2178                                           ERR_MODIFY_BACKEND_READONLY.get(
2179                                                String.valueOf(entryDN)));
2180    
2181            case INTERNAL_ONLY:
2182              if (! isInternalOperation() || isSynchronizationOperation())
2183              {
2184                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2185                                             ERR_MODIFY_BACKEND_READONLY.get(
2186                                                  String.valueOf(entryDN)));
2187              }
2188          }
2189        }
2190      }
2191    
2192    
2193    
2194      /**
2195       * Handles any account status notifications that may be needed as a result of
2196       * modify processing.
2197       */
2198      private void handleAccountStatusNotifications()
2199      {
2200        if (passwordChanged)
2201        {
2202          if (selfChange)
2203          {
2204            AuthenticationInfo authInfo = clientConnection.getAuthenticationInfo();
2205            if (authInfo.getAuthenticationDN().equals(modifiedEntry.getDN()))
2206            {
2207              clientConnection.setMustChangePassword(false);
2208            }
2209    
2210            Message message = INFO_MODIFY_PASSWORD_CHANGED.get();
2211            pwPolicyState.generateAccountStatusNotification(
2212                AccountStatusNotificationType.PASSWORD_CHANGED,
2213                modifiedEntry, message,
2214                AccountStatusNotification.createProperties(pwPolicyState, false, -1,
2215                     currentPasswords, newPasswords));
2216          }
2217          else
2218          {
2219            Message message = INFO_MODIFY_PASSWORD_RESET.get();
2220            pwPolicyState.generateAccountStatusNotification(
2221                AccountStatusNotificationType.PASSWORD_RESET, modifiedEntry,
2222                message,
2223                AccountStatusNotification.createProperties(pwPolicyState, false, -1,
2224                     currentPasswords, newPasswords));
2225          }
2226        }
2227    
2228        if (enabledStateChanged)
2229        {
2230          if (isEnabled)
2231          {
2232            Message message = INFO_MODIFY_ACCOUNT_ENABLED.get();
2233            pwPolicyState.generateAccountStatusNotification(
2234                AccountStatusNotificationType.ACCOUNT_ENABLED,
2235                modifiedEntry, message,
2236                AccountStatusNotification.createProperties(pwPolicyState, false, -1,
2237                     null, null));
2238          }
2239          else
2240          {
2241            Message message = INFO_MODIFY_ACCOUNT_DISABLED.get();
2242            pwPolicyState.generateAccountStatusNotification(
2243                AccountStatusNotificationType.ACCOUNT_DISABLED,
2244                modifiedEntry, message,
2245                AccountStatusNotification.createProperties(pwPolicyState, false, -1,
2246                     null, null));
2247          }
2248        }
2249    
2250        if (wasLocked)
2251        {
2252          Message message = INFO_MODIFY_ACCOUNT_UNLOCKED.get();
2253          pwPolicyState.generateAccountStatusNotification(
2254              AccountStatusNotificationType.ACCOUNT_UNLOCKED, modifiedEntry,
2255              message,
2256              AccountStatusNotification.createProperties(pwPolicyState, false, -1,
2257                   null, null));
2258        }
2259      }
2260    
2261    
2262    
2263      /**
2264       * Handles any processing that is required for the LDAP pre-read and/or
2265       * post-read controls.
2266       */
2267      private void handleReadEntryProcessing()
2268      {
2269        if (preReadRequest != null)
2270        {
2271          Entry entry = currentEntry.duplicate(true);
2272    
2273          if (! preReadRequest.allowsAttribute(
2274                     DirectoryServer.getObjectClassAttributeType()))
2275          {
2276            entry.removeAttribute(
2277                       DirectoryServer.getObjectClassAttributeType());
2278          }
2279    
2280          if (! preReadRequest.returnAllUserAttributes())
2281          {
2282            Iterator<AttributeType> iterator =
2283                 entry.getUserAttributes().keySet().iterator();
2284            while (iterator.hasNext())
2285            {
2286              AttributeType attrType = iterator.next();
2287              if (! preReadRequest.allowsAttribute(attrType))
2288              {
2289                iterator.remove();
2290              }
2291            }
2292          }
2293    
2294          if (! preReadRequest.returnAllOperationalAttributes())
2295          {
2296            Iterator<AttributeType> iterator =
2297                 entry.getOperationalAttributes().keySet().iterator();
2298            while (iterator.hasNext())
2299            {
2300              AttributeType attrType = iterator.next();
2301              if (! preReadRequest.allowsAttribute(attrType))
2302              {
2303                iterator.remove();
2304              }
2305            }
2306          }
2307    
2308          // FIXME -- Check access controls on the entry to see if it should be
2309          //          returned or if any attributes need to be stripped out..
2310          SearchResultEntry searchEntry = new SearchResultEntry(entry);
2311          LDAPPreReadResponseControl responseControl =
2312               new LDAPPreReadResponseControl(preReadRequest.getOID(),
2313                                              preReadRequest.isCritical(),
2314                                              searchEntry);
2315          getResponseControls().add(responseControl);
2316        }
2317    
2318        if (postReadRequest != null)
2319        {
2320          Entry entry = modifiedEntry.duplicate(true);
2321    
2322          if (! postReadRequest.allowsAttribute(
2323                     DirectoryServer.getObjectClassAttributeType()))
2324          {
2325            entry.removeAttribute(
2326                       DirectoryServer.getObjectClassAttributeType());
2327          }
2328    
2329          if (! postReadRequest.returnAllUserAttributes())
2330          {
2331            Iterator<AttributeType> iterator =
2332                 entry.getUserAttributes().keySet().iterator();
2333            while (iterator.hasNext())
2334            {
2335              AttributeType attrType = iterator.next();
2336              if (! postReadRequest.allowsAttribute(attrType))
2337              {
2338                iterator.remove();
2339              }
2340            }
2341          }
2342    
2343          if (! postReadRequest.returnAllOperationalAttributes())
2344          {
2345            Iterator<AttributeType> iterator =
2346                 entry.getOperationalAttributes().keySet().iterator();
2347            while (iterator.hasNext())
2348            {
2349              AttributeType attrType = iterator.next();
2350              if (! postReadRequest.allowsAttribute(attrType))
2351              {
2352                iterator.remove();
2353              }
2354            }
2355          }
2356    
2357          // FIXME -- Check access controls on the entry to see if it should be
2358          //          returned or if any attributes need to be stripped out..
2359          SearchResultEntry searchEntry = new SearchResultEntry(entry);
2360          LDAPPostReadResponseControl responseControl =
2361               new LDAPPostReadResponseControl(postReadRequest.getOID(),
2362                                               postReadRequest.isCritical(),
2363                                               searchEntry);
2364    
2365          getResponseControls().add(responseControl);
2366        }
2367      }
2368    
2369    
2370    
2371      /**
2372       * Notify any registered change listeners about this update.
2373       */
2374      private void notifyChangeListeners()
2375      {
2376        for (ChangeNotificationListener changeListener :
2377             DirectoryServer.getChangeNotificationListeners())
2378        {
2379          try
2380          {
2381            changeListener.handleModifyOperation(this, currentEntry, modifiedEntry);
2382          }
2383          catch (Exception e)
2384          {
2385            if (debugEnabled())
2386            {
2387              TRACER.debugCaught(DebugLogLevel.ERROR, e);
2388            }
2389    
2390            Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER.get(
2391                getExceptionMessage(e));
2392            logError(message);
2393          }
2394        }
2395      }
2396    }
2397