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.HashSet;
033    import java.util.Iterator;
034    import java.util.LinkedHashSet;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.concurrent.CopyOnWriteArrayList;
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.PasswordValidator;
048    import org.opends.server.api.SynchronizationProvider;
049    import org.opends.server.api.plugin.PluginResult;
050    import org.opends.server.controls.LDAPAssertionRequestControl;
051    import org.opends.server.controls.LDAPPostReadRequestControl;
052    import org.opends.server.controls.LDAPPostReadResponseControl;
053    import org.opends.server.controls.PasswordPolicyErrorType;
054    import org.opends.server.controls.PasswordPolicyResponseControl;
055    import org.opends.server.controls.ProxiedAuthV1Control;
056    import org.opends.server.controls.ProxiedAuthV2Control;
057    import org.opends.server.core.AccessControlConfigManager;
058    import org.opends.server.core.AddOperation;
059    import org.opends.server.core.AddOperationWrapper;
060    import org.opends.server.core.DirectoryServer;
061    import org.opends.server.core.PasswordPolicy;
062    import org.opends.server.core.PluginConfigManager;
063    import org.opends.server.loggers.debug.DebugTracer;
064    import org.opends.server.protocols.asn1.ASN1OctetString;
065    import org.opends.server.schema.AuthPasswordSyntax;
066    import org.opends.server.schema.BooleanSyntax;
067    import org.opends.server.schema.UserPasswordSyntax;
068    import org.opends.server.types.Attribute;
069    import org.opends.server.types.AttributeType;
070    import org.opends.server.types.AttributeValue;
071    import org.opends.server.types.ByteString;
072    import org.opends.server.types.CanceledOperationException;
073    import org.opends.server.types.Control;
074    import org.opends.server.types.DebugLogLevel;
075    import org.opends.server.types.DirectoryException;
076    import org.opends.server.types.DN;
077    import org.opends.server.types.Entry;
078    import org.opends.server.types.LDAPException;
079    import org.opends.server.types.LockManager;
080    import org.opends.server.types.ObjectClass;
081    import org.opends.server.types.Privilege;
082    import org.opends.server.types.RDN;
083    import org.opends.server.types.ResultCode;
084    import org.opends.server.types.SearchFilter;
085    import org.opends.server.types.SearchResultEntry;
086    import org.opends.server.types.SynchronizationProviderResult;
087    import org.opends.server.types.operation.PostOperationAddOperation;
088    import org.opends.server.types.operation.PostResponseAddOperation;
089    import org.opends.server.types.operation.PreOperationAddOperation;
090    import org.opends.server.types.operation.PostSynchronizationAddOperation;
091    import org.opends.server.util.TimeThread;
092    
093    import static org.opends.messages.CoreMessages.*;
094    import static org.opends.server.loggers.ErrorLogger.*;
095    import static org.opends.server.loggers.debug.DebugLogger.*;
096    import static org.opends.server.config.ConfigConstants.*;
097    import static org.opends.server.util.ServerConstants.*;
098    import static org.opends.server.util.StaticUtils.*;
099    
100    
101    
102    /**
103     * This class defines an operation used to add an entry in a local backend
104     * of the Directory Server.
105     */
106    public class LocalBackendAddOperation
107           extends AddOperationWrapper
108           implements PreOperationAddOperation, PostOperationAddOperation,
109                      PostResponseAddOperation, PostSynchronizationAddOperation
110    {
111      /**
112       * The tracer object for the debug logger.
113       */
114      private static final DebugTracer TRACER = getTracer();
115    
116    
117    
118      // The backend in which the entry is to be added.
119      private Backend backend;
120    
121      // Indicates whether the request includes the LDAP no-op control.
122      private boolean noOp;
123    
124      // The DN of the entry to be added.
125      private DN entryDN;
126    
127      // The entry being added to the server.
128      private Entry entry;
129    
130      // The post-read request control included in the request, if applicable.
131      LDAPPostReadRequestControl postReadRequest;
132    
133      // The set of object classes for the entry to add.
134      private Map<ObjectClass, String> objectClasses;
135    
136      // The set of operational attributes for the entry to add.
137      private Map<AttributeType,List<Attribute>> operationalAttributes;
138    
139      // The set of user attributes for the entry to add.
140      private Map<AttributeType,List<Attribute>> userAttributes;
141    
142    
143    
144      /**
145       * Creates a new operation that may be used to add a new entry in a
146       * local backend of the Directory Server.
147       *
148       * @param add The operation to enhance.
149       */
150      public LocalBackendAddOperation(AddOperation add)
151      {
152        super(add);
153    
154        LocalBackendWorkflowElement.attachLocalOperation (add, this);
155      }
156    
157    
158    
159      /**
160       * Retrieves the entry to be added to the server.  Note that this will not be
161       * available to pre-parse plugins or during the conflict resolution portion of
162       * the synchronization processing.
163       *
164       * @return  The entry to be added to the server, or <CODE>null</CODE> if it is
165       *          not yet available.
166       */
167      public final Entry getEntryToAdd()
168      {
169        return entry;
170      }
171    
172    
173    
174      /**
175       * Process this add operation against a local backend.
176       *
177       * @param  backend  The backend in which the add operation should be
178       *                  processed.
179       *
180       * @throws CanceledOperationException if this operation should be
181       * cancelled
182       */
183      void processLocalAdd(Backend backend) throws CanceledOperationException {
184        boolean executePostOpPlugins = false;
185    
186        this.backend = backend;
187        ClientConnection clientConnection = getClientConnection();
188    
189        // Get the plugin config manager that will be used for invoking plugins.
190        PluginConfigManager pluginConfigManager =
191             DirectoryServer.getPluginConfigManager();
192    
193        // Check for a request to cancel this operation.
194        checkIfCanceled(false);
195    
196        // Create a labeled block of code that we can break out of if a problem is
197        // detected.
198    addProcessing:
199        {
200          // Process the entry DN and set of attributes to convert them from their
201          // raw forms as provided by the client to the forms required for the rest
202          // of the add processing.
203          entryDN = getEntryDN();
204          if (entryDN == null)
205          {
206            break addProcessing;
207          }
208    
209          objectClasses = getObjectClasses();
210          userAttributes = getUserAttributes();
211          operationalAttributes = getOperationalAttributes();
212    
213          if ((objectClasses == null ) || (userAttributes == null) ||
214              (operationalAttributes == null))
215          {
216            break addProcessing;
217          }
218    
219          // Check for a request to cancel this operation.
220          checkIfCanceled(false);
221    
222    
223          // Grab a read lock on the parent entry, if there is one.  We need to do
224          // this to ensure that the parent is not deleted or renamed while this add
225          // is in progress, and we could also need it to check the entry against
226          // a DIT structure rule.
227          Lock parentLock = null;
228          Lock entryLock  = null;
229    
230          DN parentDN = entryDN.getParentDNInSuffix();
231          try
232          {
233            parentLock = lockParent(parentDN);
234          }
235          catch (DirectoryException de)
236          {
237            if (debugEnabled())
238            {
239              TRACER.debugCaught(DebugLogLevel.ERROR, de);
240            }
241    
242            setResponseData(de);
243            break addProcessing;
244          }
245    
246          try
247          {
248            // Check for a request to cancel this operation.
249            checkIfCanceled(false);
250    
251    
252            // Grab a write lock on the target entry.  We'll need to do this
253            // eventually anyway, and we want to make sure that the two locks are
254            // always released when exiting this method, no matter what.  Since
255            // the entry shouldn't exist yet, locking earlier than necessary
256            // shouldn't cause a problem.
257            for (int i=0; i < 3; i++)
258            {
259              entryLock = LockManager.lockWrite(entryDN);
260              if (entryLock != null)
261              {
262                break;
263              }
264            }
265    
266            if (entryLock == null)
267            {
268              setResultCode(DirectoryServer.getServerErrorResultCode());
269              appendErrorMessage(ERR_ADD_CANNOT_LOCK_ENTRY.get(
270                                      String.valueOf(entryDN)));
271    
272              break addProcessing;
273            }
274    
275    
276            // Invoke any conflict resolution processing that might be needed by the
277            // synchronization provider.
278            for (SynchronizationProvider provider :
279                 DirectoryServer.getSynchronizationProviders())
280            {
281              try
282              {
283                SynchronizationProviderResult result =
284                    provider.handleConflictResolution(this);
285                if (! result.continueProcessing())
286                {
287                  setResultCode(result.getResultCode());
288                  appendErrorMessage(result.getErrorMessage());
289                  setMatchedDN(result.getMatchedDN());
290                  setReferralURLs(result.getReferralURLs());
291                  break addProcessing;
292                }
293              }
294              catch (DirectoryException de)
295              {
296                if (debugEnabled())
297                {
298                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
299                }
300    
301                logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
302                              getConnectionID(), getOperationID(),
303                              getExceptionMessage(de)));
304    
305                setResponseData(de);
306                break addProcessing;
307              }
308            }
309    
310            for (AttributeType at : userAttributes.keySet())
311            {
312              // If the attribute type is marked "NO-USER-MODIFICATION" then fail
313              // unless this is an internal operation or is related to
314              // synchronization in some way.
315              // This must be done before running the password policy code
316              // and any other code that may add attributes marked as
317              // "NO-USER-MODIFICATION"
318              //
319              // Note that doing this checks at this time
320              // of the processing does not make it possible for pre-parse plugins
321              // to add NO-USER-MODIFICATION attributes to the entry.
322              if (at.isNoUserModification())
323              {
324                if (! (isInternalOperation() || isSynchronizationOperation()))
325                {
326                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
327                  appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
328                                          String.valueOf(entryDN),
329                                          at.getNameOrOID()));
330    
331                  break addProcessing;
332                }
333              }
334            }
335    
336            for (AttributeType at : operationalAttributes.keySet())
337            {
338              if (at.isNoUserModification())
339              {
340                if (! (isInternalOperation() || isSynchronizationOperation()))
341                {
342                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
343                  appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
344                                          String.valueOf(entryDN),
345                                          at.getNameOrOID()));
346    
347                  break addProcessing;
348                }
349              }
350            }
351    
352            // Check to see if the entry already exists.  We do this before
353            // checking whether the parent exists to ensure a referral entry
354            // above the parent results in a correct referral.
355            try
356            {
357              if (DirectoryServer.entryExists(entryDN))
358              {
359                setResultCode(ResultCode.ENTRY_ALREADY_EXISTS);
360                appendErrorMessage(ERR_ADD_ENTRY_ALREADY_EXISTS.get(
361                                        String.valueOf(entryDN)));
362                break addProcessing;
363              }
364            }
365            catch (DirectoryException de)
366            {
367              if (debugEnabled())
368              {
369                TRACER.debugCaught(DebugLogLevel.ERROR, de);
370              }
371    
372              setResponseData(de);
373              break addProcessing;
374            }
375    
376    
377            // Get the parent entry, if it exists.
378            Entry parentEntry = null;
379            if (parentDN != null)
380            {
381              try
382              {
383                parentEntry = DirectoryServer.getEntry(parentDN);
384    
385                if (parentEntry == null)
386                {
387                  DN matchedDN = parentDN.getParentDNInSuffix();
388                  while (matchedDN != null)
389                  {
390                    try
391                    {
392                      if (DirectoryServer.entryExists(matchedDN))
393                      {
394                        setMatchedDN(matchedDN);
395                        break;
396                      }
397                    }
398                    catch (Exception e)
399                    {
400                      if (debugEnabled())
401                      {
402                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
403                      }
404                      break;
405                    }
406    
407                    matchedDN = matchedDN.getParentDNInSuffix();
408                  }
409    
410    
411                  // The parent doesn't exist, so this add can't be successful.
412                  setResultCode(ResultCode.NO_SUCH_OBJECT);
413                  appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
414                                          String.valueOf(parentDN)));
415                  break addProcessing;
416                }
417              }
418              catch (DirectoryException de)
419              {
420                if (debugEnabled())
421                {
422                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
423                }
424    
425                setResponseData(de);
426                break addProcessing;
427              }
428            }
429    
430    
431            // Check to make sure that all of the RDN attributes are included as
432            // attribute values.  If not, then either add them or report an error.
433            try
434            {
435              addRDNAttributesIfNecessary();
436            }
437            catch (DirectoryException de)
438            {
439              if (debugEnabled())
440              {
441                TRACER.debugCaught(DebugLogLevel.ERROR, de);
442              }
443    
444              setResponseData(de);
445              break addProcessing;
446            }
447    
448    
449            // Check to make sure that all objectclasses have their superior classes
450            // listed in the entry.  If not, then add them.
451            HashSet<ObjectClass> additionalClasses = null;
452            for (ObjectClass oc : objectClasses.keySet())
453            {
454              ObjectClass superiorClass = oc.getSuperiorClass();
455              if ((superiorClass != null) &&
456                  (! objectClasses.containsKey(superiorClass)))
457              {
458                if (additionalClasses == null)
459                {
460                  additionalClasses = new HashSet<ObjectClass>();
461                }
462    
463                additionalClasses.add(superiorClass);
464              }
465            }
466    
467            if (additionalClasses != null)
468            {
469              for (ObjectClass oc : additionalClasses)
470              {
471                addObjectClassChain(oc);
472              }
473            }
474    
475    
476            // Create an entry object to encapsulate the set of attributes and
477            // objectclasses.
478            entry = new Entry(entryDN, objectClasses, userAttributes,
479                              operationalAttributes);
480    
481            // Check to see if the entry includes a privilege specification.  If so,
482            // then the requester must have the PRIVILEGE_CHANGE privilege.
483            AttributeType privType =
484                 DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
485            if (entry.hasAttribute(privType) &&
486                (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
487            {
488    
489              appendErrorMessage(
490                   ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
491              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
492              break addProcessing;
493            }
494    
495    
496            // If it's not a synchronization operation, then check
497            // to see if the entry contains one or more passwords and if they
498            // are valid in accordance with the password policies associated with
499            // the user.  Also perform any encoding that might be required by
500            // password storage schemes.
501            if (! isSynchronizationOperation())
502            {
503              try
504              {
505                handlePasswordPolicy();
506              }
507              catch (DirectoryException de)
508              {
509                if (debugEnabled())
510                {
511                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
512                }
513    
514                setResponseData(de);
515                break addProcessing;
516              }
517            }
518    
519    
520            // If the server is configured to check schema and the
521            // operation is not a synchronization operation,
522            // check to see if the entry is valid according to the server schema,
523            // and also whether its attributes are valid according to their syntax.
524            if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
525            {
526              try
527              {
528                checkSchema(parentEntry);
529              }
530              catch (DirectoryException de)
531              {
532                if (debugEnabled())
533                {
534                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
535                }
536    
537                setResponseData(de);
538                break addProcessing;
539              }
540            }
541    
542    
543            // Get the backend in which the add is to be performed.
544            if (backend == null)
545            {
546              setResultCode(ResultCode.NO_SUCH_OBJECT);
547              appendErrorMessage(Message.raw("No backend for entry " +
548                                             entryDN.toString())); // TODO: i18n
549              break addProcessing;
550            }
551    
552    
553            // Check to see if there are any controls in the request. If so, then
554            // see if there is any special processing required.
555            try
556            {
557              processControls(parentDN);
558            }
559            catch (DirectoryException de)
560            {
561              if (debugEnabled())
562              {
563                TRACER.debugCaught(DebugLogLevel.ERROR, de);
564              }
565    
566              setResponseData(de);
567              break addProcessing;
568            }
569    
570    
571            // Check to see if the client has permission to perform the add.
572    
573            // FIXME: for now assume that this will check all permission
574            // pertinent to the operation. This includes proxy authorization
575            // and any other controls specified.
576    
577            // FIXME: earlier checks to see if the entry already exists or
578            // if the parent entry does not exist may have already exposed
579            // sensitive information to the client.
580            if (AccessControlConfigManager.getInstance().getAccessControlHandler().
581                     isAllowed(this) == false)
582            {
583              setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
584              appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
585                                      String.valueOf(entryDN)));
586              break addProcessing;
587            }
588    
589            // Check for a request to cancel this operation.
590            checkIfCanceled(false);
591    
592            // If the operation is not a synchronization operation,
593            // Invoke the pre-operation add plugins.
594            if (! isSynchronizationOperation())
595            {
596              executePostOpPlugins = true;
597              PluginResult.PreOperation preOpResult =
598                pluginConfigManager.invokePreOperationAddPlugins(this);
599              if (!preOpResult.continueProcessing())
600              {
601                setResultCode(preOpResult.getResultCode());
602                appendErrorMessage(preOpResult.getErrorMessage());
603                setMatchedDN(preOpResult.getMatchedDN());
604                setReferralURLs(preOpResult.getReferralURLs());
605                break addProcessing;
606              }
607            }
608    
609    
610            // Check for a request to cancel this operation.
611            checkIfCanceled(true);
612    
613    
614            // If it is not a private backend, then check to see if the server or
615            // backend is operating in read-only mode.
616            if (! backend.isPrivateBackend())
617            {
618              switch (DirectoryServer.getWritabilityMode())
619              {
620                case DISABLED:
621                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
622                  appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
623                                          String.valueOf(entryDN)));
624                  break addProcessing;
625    
626                case INTERNAL_ONLY:
627                  if (! (isInternalOperation() || isSynchronizationOperation()))
628                  {
629                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
630                    appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
631                                            String.valueOf(entryDN)));
632                    break addProcessing;
633                  }
634                  break;
635              }
636    
637              switch (backend.getWritabilityMode())
638              {
639                case DISABLED:
640                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
641                  appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
642                                          String.valueOf(entryDN)));
643                  break addProcessing;
644    
645                case INTERNAL_ONLY:
646                  if (! (isInternalOperation() || isSynchronizationOperation()))
647                  {
648                    setResultCode(ResultCode.UNWILLING_TO_PERFORM);
649                    appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
650                                            String.valueOf(entryDN)));
651                    break addProcessing;
652                  }
653                  break;
654              }
655            }
656    
657    
658            try
659            {
660              if (noOp)
661              {
662                appendErrorMessage(INFO_ADD_NOOP.get());
663                setResultCode(ResultCode.NO_OPERATION);
664              }
665              else
666              {
667                for (SynchronizationProvider provider :
668                     DirectoryServer.getSynchronizationProviders())
669                {
670                  try
671                  {
672                    SynchronizationProviderResult result =
673                        provider.doPreOperation(this);
674                    if (! result.continueProcessing())
675                    {
676                      setResultCode(result.getResultCode());
677                      appendErrorMessage(result.getErrorMessage());
678                      setMatchedDN(result.getMatchedDN());
679                      setReferralURLs(result.getReferralURLs());
680                      break addProcessing;
681                    }
682                  }
683                  catch (DirectoryException de)
684                  {
685                    if (debugEnabled())
686                    {
687                      TRACER.debugCaught(DebugLogLevel.ERROR, de);
688                    }
689    
690                    logError(ERR_ADD_SYNCH_PREOP_FAILED.get(getConnectionID(),
691                                  getOperationID(), getExceptionMessage(de)));
692                    setResponseData(de);
693                    break addProcessing;
694                  }
695                }
696    
697                backend.addEntry(entry, this);
698              }
699    
700              if (postReadRequest != null)
701              {
702                addPostReadResponse();
703              }
704    
705    
706              if (! noOp)
707              {
708                setResultCode(ResultCode.SUCCESS);
709              }
710            }
711            catch (DirectoryException de)
712            {
713              if (debugEnabled())
714              {
715                TRACER.debugCaught(DebugLogLevel.ERROR, de);
716              }
717    
718              setResponseData(de);
719              break addProcessing;
720            }
721          }
722          finally
723          {
724            if (entryLock != null)
725            {
726              LockManager.unlock(entryDN, entryLock);
727            }
728    
729            if (parentLock != null)
730            {
731              LockManager.unlock(parentDN, parentLock);
732            }
733          }
734        }
735    
736        for (SynchronizationProvider provider :
737            DirectoryServer.getSynchronizationProviders())
738        {
739          try
740          {
741            provider.doPostOperation(this);
742          }
743          catch (DirectoryException de)
744          {
745            if (debugEnabled())
746            {
747              TRACER.debugCaught(DebugLogLevel.ERROR, de);
748            }
749    
750            logError(ERR_ADD_SYNCH_POSTOP_FAILED.get(getConnectionID(),
751                getOperationID(), getExceptionMessage(de)));
752            setResponseData(de);
753            break;
754          }
755        }
756    
757        // Invoke the post-operation or post-synchronization add plugins.
758        if (isSynchronizationOperation())
759        {
760          if (getResultCode() == ResultCode.SUCCESS)
761          {
762            pluginConfigManager.invokePostSynchronizationAddPlugins(this);
763          }
764        }
765        else if (executePostOpPlugins)
766        {
767          // FIXME -- Should this also be done while holding the locks?
768          PluginResult.PostOperation postOpResult =
769              pluginConfigManager.invokePostOperationAddPlugins(this);
770          if(!postOpResult.continueProcessing())
771          {
772            setResultCode(postOpResult.getResultCode());
773            appendErrorMessage(postOpResult.getErrorMessage());
774            setMatchedDN(postOpResult.getMatchedDN());
775            setReferralURLs(postOpResult.getReferralURLs());
776            return;
777          }
778        }
779    
780    
781        // Notify any change notification listeners that might be registered with
782        // the server.
783        if ((getResultCode() == ResultCode.SUCCESS) && (entry != null))
784        {
785          for (ChangeNotificationListener changeListener :
786               DirectoryServer.getChangeNotificationListeners())
787          {
788            try
789            {
790              changeListener.handleAddOperation(this, entry);
791            }
792            catch (Exception e)
793            {
794              if (debugEnabled())
795              {
796                TRACER.debugCaught(DebugLogLevel.ERROR, e);
797              }
798    
799              logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER.get(
800                            getExceptionMessage(e)));
801            }
802          }
803        }
804      }
805    
806    
807    
808      /**
809       * Acquire a read lock on the parent of the entry to add.
810       *
811       * @return  The acquired read lock.
812       *
813       * @throws  DirectoryException  If a problem occurs while attempting to
814       *                              acquire the lock.
815       */
816      private Lock lockParent(DN parentDN)
817              throws DirectoryException
818      {
819        Lock parentLock = null;
820    
821        if (parentDN == null)
822        {
823          // Either this entry is a suffix or doesn't belong in the directory.
824          if (DirectoryServer.isNamingContext(entryDN))
825          {
826            // This is fine.  This entry is one of the configured suffixes.
827            parentLock = null;
828          }
829          else if (entryDN.isNullDN())
830          {
831            // This is not fine.  The root DSE cannot be added.
832            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
833                                         ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
834          }
835          else
836          {
837            // The entry doesn't have a parent but isn't a suffix.  This is not
838            // allowed.
839            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
840                                         ERR_ADD_ENTRY_NOT_SUFFIX.get(
841                                              String.valueOf(entryDN)));
842          }
843        }
844        else
845        {
846          for (int i=0; i < 3; i++)
847          {
848            parentLock = LockManager.lockRead(parentDN);
849            if (parentLock != null)
850            {
851              break;
852            }
853          }
854    
855          if (parentLock == null)
856          {
857            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
858                                         ERR_ADD_CANNOT_LOCK_PARENT.get(
859                                              String.valueOf(entryDN),
860                                              String.valueOf(parentDN)));
861          }
862        }
863    
864        return parentLock;
865      }
866    
867    
868    
869      /**
870       * Adds any missing RDN attributes to the entry.
871       *
872       * @throws  DirectoryException  If the entry is missing one or more RDN
873       *                              attributes and the server is configured to
874       *                              reject such entries.
875       */
876      private void addRDNAttributesIfNecessary()
877              throws DirectoryException
878      {
879        RDN rdn = entryDN.getRDN();
880        int numAVAs = rdn.getNumValues();
881        for (int i=0; i < numAVAs; i++)
882        {
883          AttributeType  t = rdn.getAttributeType(i);
884          AttributeValue v = rdn.getAttributeValue(i);
885          String         n = rdn.getAttributeName(i);
886          if (t.isOperational())
887          {
888            List<Attribute> attrList = operationalAttributes.get(t);
889            if (attrList == null)
890            {
891              if (isSynchronizationOperation() ||
892                  DirectoryServer.addMissingRDNAttributes())
893              {
894                LinkedHashSet<AttributeValue> valueList =
895                     new LinkedHashSet<AttributeValue>(1);
896                valueList.add(v);
897    
898                attrList = new ArrayList<Attribute>();
899                attrList.add(new Attribute(t, n, valueList));
900    
901                operationalAttributes.put(t, attrList);
902              }
903              else
904              {
905                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
906                                             ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
907                                                  String.valueOf(entryDN), n));
908              }
909            }
910            else
911            {
912              boolean found = false;
913              for (Attribute a : attrList)
914              {
915                if (a.hasOptions())
916                {
917                  continue;
918                }
919                else
920                {
921                  if (! a.hasValue(v))
922                  {
923                    a.getValues().add(v);
924                  }
925    
926                  found = true;
927                  break;
928                }
929              }
930    
931              if (! found)
932              {
933                if (isSynchronizationOperation() ||
934                    DirectoryServer.addMissingRDNAttributes())
935                {
936                  LinkedHashSet<AttributeValue> valueList =
937                       new LinkedHashSet<AttributeValue>(1);
938                  valueList.add(v);
939                  attrList.add(new Attribute(t, n, valueList));
940                }
941                else
942                {
943                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
944                                               ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
945                                                    String.valueOf(entryDN), n));
946                }
947              }
948            }
949          }
950          else
951          {
952            List<Attribute> attrList = userAttributes.get(t);
953            if (attrList == null)
954            {
955              if (isSynchronizationOperation() ||
956                  DirectoryServer.addMissingRDNAttributes())
957              {
958                LinkedHashSet<AttributeValue> valueList =
959                     new LinkedHashSet<AttributeValue>(1);
960                valueList.add(v);
961    
962                attrList = new ArrayList<Attribute>();
963                attrList.add(new Attribute(t, n, valueList));
964    
965                userAttributes.put(t, attrList);
966              }
967              else
968              {
969                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
970                                             ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
971                                                  String.valueOf(entryDN),n));
972              }
973            }
974            else
975            {
976              boolean found = false;
977              for (Attribute a : attrList)
978              {
979                if (a.hasOptions())
980                {
981                  continue;
982                }
983                else
984                {
985                  if (! a.hasValue(v))
986                  {
987                    a.getValues().add(v);
988                  }
989    
990                  found = true;
991                  break;
992                }
993              }
994    
995              if (! found)
996              {
997                if (isSynchronizationOperation() ||
998                    DirectoryServer.addMissingRDNAttributes())
999                {
1000                  LinkedHashSet<AttributeValue> valueList =
1001                       new LinkedHashSet<AttributeValue>(1);
1002                  valueList.add(v);
1003                  attrList.add(new Attribute(t, n, valueList));
1004                }
1005                else
1006                {
1007                  throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1008                                               ERR_ADD_MISSING_RDN_ATTRIBUTE.get(
1009                                                    String.valueOf(entryDN),n));
1010                }
1011              }
1012            }
1013          }
1014        }
1015      }
1016    
1017    
1018    
1019      /**
1020       * Adds the provided objectClass to the entry, along with its superior classes
1021       * if appropriate.
1022       *
1023       * @param  objectClass  The objectclass to add to the entry.
1024       */
1025      public final void addObjectClassChain(ObjectClass objectClass)
1026      {
1027        Map<ObjectClass, String> objectClasses = getObjectClasses();
1028        if (objectClasses != null){
1029          if (! objectClasses.containsKey(objectClass))
1030          {
1031            objectClasses.put(objectClass, objectClass.getNameOrOID());
1032          }
1033    
1034          ObjectClass superiorClass = objectClass.getSuperiorClass();
1035          if ((superiorClass != null) &&
1036              (! objectClasses.containsKey(superiorClass)))
1037          {
1038            addObjectClassChain(superiorClass);
1039          }
1040        }
1041      }
1042    
1043    
1044    
1045      /**
1046       * Performs all password policy processing necessary for the provided add
1047       * operation.
1048       *
1049       * @throws  DirectoryException  If a problem occurs while performing password
1050       *                              policy processing for the add operation.
1051       */
1052      public final void handlePasswordPolicy()
1053             throws DirectoryException
1054      {
1055        // FIXME -- We need to check to see if the password policy subentry
1056        //          might be specified virtually rather than as a real
1057        //          attribute.
1058        PasswordPolicy passwordPolicy = null;
1059        List<Attribute> pwAttrList =
1060             entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
1061        if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
1062        {
1063          Attribute a = pwAttrList.get(0);
1064          LinkedHashSet<AttributeValue> valueSet = a.getValues();
1065          Iterator<AttributeValue> iterator = valueSet.iterator();
1066          if (iterator.hasNext())
1067          {
1068            DN policyDN;
1069            try
1070            {
1071              policyDN = DN.decode(iterator.next().getValue());
1072            }
1073            catch (DirectoryException de)
1074            {
1075              if (debugEnabled())
1076              {
1077                TRACER.debugCaught(DebugLogLevel.ERROR, de);
1078              }
1079    
1080              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1081                                           ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
1082                                                String.valueOf(entryDN),
1083                                               de.getMessageObject()));
1084            }
1085    
1086            passwordPolicy = DirectoryServer.getPasswordPolicy(policyDN);
1087            if (passwordPolicy == null)
1088            {
1089              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1090                                           ERR_ADD_NO_SUCH_PWPOLICY.get(
1091                                                String.valueOf(entryDN),
1092                                             String.valueOf(policyDN)));
1093            }
1094          }
1095        }
1096    
1097        if (passwordPolicy == null)
1098        {
1099          passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
1100        }
1101    
1102        // See if a password was specified.
1103        AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
1104        List<Attribute> attrList = entry.getAttribute(passwordAttribute);
1105        if ((attrList == null) || attrList.isEmpty())
1106        {
1107          // The entry doesn't have a password, so no action is required.
1108          return;
1109        }
1110        else if (attrList.size() > 1)
1111        {
1112          // This must mean there are attribute options, which we won't allow for
1113          // passwords.
1114          Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
1115              passwordAttribute.getNameOrOID());
1116          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1117        }
1118    
1119        Attribute passwordAttr = attrList.get(0);
1120        if (passwordAttr.hasOptions())
1121        {
1122          Message message = ERR_PWPOLICY_ATTRIBUTE_OPTIONS_NOT_ALLOWED.get(
1123              passwordAttribute.getNameOrOID());
1124          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1125        }
1126    
1127        LinkedHashSet<AttributeValue> values = passwordAttr.getValues();
1128        if (values.isEmpty())
1129        {
1130          // This will be treated the same as not having a password.
1131          return;
1132        }
1133    
1134        if ((! passwordPolicy.allowMultiplePasswordValues()) && (values.size() > 1))
1135        {
1136          // FIXME -- What if they're pre-encoded and might all be the same?
1137          addPWPolicyControl(PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED);
1138    
1139          Message message = ERR_PWPOLICY_MULTIPLE_PW_VALUES_NOT_ALLOWED.get(
1140              passwordAttribute.getNameOrOID());
1141          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1142        }
1143    
1144        CopyOnWriteArrayList<PasswordStorageScheme> defaultStorageSchemes =
1145             passwordPolicy.getDefaultStorageSchemes();
1146        LinkedHashSet<AttributeValue> newValues =
1147             new LinkedHashSet<AttributeValue>(defaultStorageSchemes.size());
1148        for (AttributeValue v : values)
1149        {
1150          ByteString value = v.getValue();
1151    
1152          // See if the password is pre-encoded.
1153          if (passwordPolicy.usesAuthPasswordSyntax())
1154          {
1155            if (AuthPasswordSyntax.isEncoded(value))
1156            {
1157              if (passwordPolicy.allowPreEncodedPasswords())
1158              {
1159                newValues.add(v);
1160                continue;
1161              }
1162              else
1163              {
1164                addPWPolicyControl(
1165                     PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1166    
1167                Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
1168                    passwordAttribute.getNameOrOID());
1169                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1170                                             message);
1171              }
1172            }
1173          }
1174          else
1175          {
1176            if (UserPasswordSyntax.isEncoded(value))
1177            {
1178              if (passwordPolicy.allowPreEncodedPasswords())
1179              {
1180                newValues.add(v);
1181                continue;
1182              }
1183              else
1184              {
1185                addPWPolicyControl(
1186                     PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1187    
1188                Message message = ERR_PWPOLICY_PREENCODED_NOT_ALLOWED.get(
1189                    passwordAttribute.getNameOrOID());
1190                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1191                                             message);
1192              }
1193            }
1194          }
1195    
1196    
1197          // See if the password passes validation.  We should only do this if
1198          // validation should be performed for administrators.
1199          if (! passwordPolicy.skipValidationForAdministrators())
1200          {
1201            // There are never any current passwords for an add operation.
1202            HashSet<ByteString> currentPasswords = new HashSet<ByteString>(0);
1203            MessageBuilder invalidReason = new MessageBuilder();
1204            for (PasswordValidator<?> validator :
1205                 passwordPolicy.getPasswordValidators().values())
1206            {
1207              if (! validator.passwordIsAcceptable(value, currentPasswords, this,
1208                                                   entry, invalidReason))
1209              {
1210                addPWPolicyControl(
1211                     PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY);
1212    
1213                Message message = ERR_PWPOLICY_VALIDATION_FAILED.
1214                    get(passwordAttribute.getNameOrOID(),
1215                        String.valueOf(invalidReason));
1216                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1217                                             message);
1218              }
1219            }
1220          }
1221    
1222    
1223          // Encode the password.
1224          if (passwordPolicy.usesAuthPasswordSyntax())
1225          {
1226            for (PasswordStorageScheme s : defaultStorageSchemes)
1227            {
1228              ByteString encodedValue = s.encodeAuthPassword(value);
1229              newValues.add(new AttributeValue(passwordAttribute, encodedValue));
1230            }
1231          }
1232          else
1233          {
1234            for (PasswordStorageScheme s : defaultStorageSchemes)
1235            {
1236              ByteString encodedValue = s.encodePasswordWithScheme(value);
1237              newValues.add(new AttributeValue(passwordAttribute, encodedValue));
1238            }
1239          }
1240        }
1241    
1242    
1243        // Put the new encoded values in the entry.
1244        passwordAttr.setValues(newValues);
1245    
1246    
1247        // Set the password changed time attribute.
1248        ByteString timeString =
1249             new ASN1OctetString(TimeThread.getGeneralizedTime());
1250        AttributeType changedTimeType =
1251             DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
1252        if (changedTimeType == null)
1253        {
1254          changedTimeType = DirectoryServer.getDefaultAttributeType(
1255                                                 OP_ATTR_PWPOLICY_CHANGED_TIME);
1256        }
1257    
1258        LinkedHashSet<AttributeValue> changedTimeValues =
1259             new LinkedHashSet<AttributeValue>(1);
1260        changedTimeValues.add(new AttributeValue(changedTimeType, timeString));
1261    
1262        ArrayList<Attribute> changedTimeList = new ArrayList<Attribute>(1);
1263        changedTimeList.add(new Attribute(changedTimeType,
1264                                          OP_ATTR_PWPOLICY_CHANGED_TIME,
1265                                          changedTimeValues));
1266    
1267        entry.putAttribute(changedTimeType, changedTimeList);
1268    
1269    
1270        // If we should force change on add, then set the appropriate flag.
1271        if (passwordPolicy.forceChangeOnAdd())
1272        {
1273          addPWPolicyControl(PasswordPolicyErrorType.CHANGE_AFTER_RESET);
1274    
1275          AttributeType resetType =
1276               DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
1277          if (resetType == null)
1278          {
1279            resetType = DirectoryServer.getDefaultAttributeType(
1280                                             OP_ATTR_PWPOLICY_RESET_REQUIRED);
1281          }
1282    
1283          LinkedHashSet<AttributeValue> resetValues = new
1284               LinkedHashSet<AttributeValue>(1);
1285          resetValues.add(BooleanSyntax.createBooleanValue(true));
1286    
1287          ArrayList<Attribute> resetList = new ArrayList<Attribute>(1);
1288          resetList.add(new Attribute(resetType, OP_ATTR_PWPOLICY_RESET_REQUIRED,
1289                                      resetValues));
1290          entry.putAttribute(resetType, resetList);
1291        }
1292      }
1293    
1294    
1295    
1296      /**
1297       * Adds a password policy response control if the corresponding request
1298       * control was included.
1299       *
1300       * @param  errorType  The error type to use for the response control.
1301       */
1302      private void addPWPolicyControl(PasswordPolicyErrorType errorType)
1303      {
1304        for (Control c : getRequestControls())
1305        {
1306          if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
1307          {
1308            addResponseControl(new PasswordPolicyResponseControl(null, 0,
1309                                                                 errorType));
1310          }
1311        }
1312      }
1313    
1314    
1315    
1316      /**
1317       * Verifies that the entry to be added conforms to the server schema.
1318       *
1319       * @param  parentEntry  The parent of the entry to add.
1320       *
1321       * @throws  DirectoryException  If the entry violates the server schema
1322       *                              configuration.
1323       */
1324      private void checkSchema(Entry parentEntry)
1325              throws DirectoryException
1326      {
1327        MessageBuilder invalidReason = new MessageBuilder();
1328        if (! entry.conformsToSchema(parentEntry, true, true, true, invalidReason))
1329        {
1330          throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
1331                                       invalidReason.toMessage());
1332        }
1333        else
1334        {
1335          switch (DirectoryServer.getSyntaxEnforcementPolicy())
1336          {
1337            case REJECT:
1338              invalidReason = new MessageBuilder();
1339              for (List<Attribute> attrList : userAttributes.values())
1340              {
1341                for (Attribute a : attrList)
1342                {
1343                  AttributeSyntax syntax = a.getAttributeType().getSyntax();
1344                  if (syntax != null)
1345                  {
1346                    for (AttributeValue v : a.getValues())
1347                    {
1348                      if (! syntax.valueIsAcceptable(v.getValue(), invalidReason))
1349                      {
1350                        Message message = WARN_ADD_OP_INVALID_SYNTAX.get(
1351                                               String.valueOf(entryDN),
1352                                               String.valueOf(v.getStringValue()),
1353                                               String.valueOf(a.getName()),
1354                                               String.valueOf(invalidReason));
1355    
1356                        throw new DirectoryException(
1357                                       ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1358                                       message);
1359                      }
1360                    }
1361                  }
1362                }
1363              }
1364    
1365              for (List<Attribute> attrList :
1366                   operationalAttributes.values())
1367              {
1368                for (Attribute a : attrList)
1369                {
1370                  AttributeSyntax syntax = a.getAttributeType().getSyntax();
1371                  if (syntax != null)
1372                  {
1373                    for (AttributeValue v : a.getValues())
1374                    {
1375                      if (! syntax.valueIsAcceptable(v.getValue(),
1376                                                     invalidReason))
1377                      {
1378                        Message message = WARN_ADD_OP_INVALID_SYNTAX.
1379                            get(String.valueOf(entryDN),
1380                                String.valueOf(v.getStringValue()),
1381                                String.valueOf(a.getName()),
1382                                String.valueOf(invalidReason));
1383    
1384                        throw new DirectoryException(
1385                                       ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1386                                       message);
1387                      }
1388                    }
1389                  }
1390                }
1391              }
1392    
1393              break;
1394    
1395    
1396            case WARN:
1397              invalidReason = new MessageBuilder();
1398              for (List<Attribute> attrList : userAttributes.values())
1399              {
1400                for (Attribute a : attrList)
1401                {
1402                  AttributeSyntax syntax = a.getAttributeType().getSyntax();
1403                  if (syntax != null)
1404                  {
1405                    for (AttributeValue v : a.getValues())
1406                    {
1407                      if (! syntax.valueIsAcceptable(v.getValue(),
1408                                                     invalidReason))
1409                      {
1410                        logError(WARN_ADD_OP_INVALID_SYNTAX.get(
1411                                      String.valueOf(entryDN),
1412                                      String.valueOf(v.getStringValue()),
1413                                      String.valueOf(a.getName()),
1414                                      String.valueOf(invalidReason)));
1415                      }
1416                    }
1417                  }
1418                }
1419              }
1420    
1421              for (List<Attribute> attrList : operationalAttributes.values())
1422              {
1423                for (Attribute a : attrList)
1424                {
1425                  AttributeSyntax syntax = a.getAttributeType().getSyntax();
1426                  if (syntax != null)
1427                  {
1428                    for (AttributeValue v : a.getValues())
1429                    {
1430                      if (! syntax.valueIsAcceptable(v.getValue(),
1431                                                     invalidReason))
1432                      {
1433                        logError(WARN_ADD_OP_INVALID_SYNTAX.get(
1434                                      String.valueOf(entryDN),
1435                                      String.valueOf(v.getStringValue()),
1436                                      String.valueOf(a.getName()),
1437                                      String.valueOf(invalidReason)));
1438                      }
1439                    }
1440                  }
1441                }
1442              }
1443    
1444              break;
1445          }
1446        }
1447    
1448    
1449        // See if the entry contains any attributes or object classes marked
1450        // OBSOLETE.  If so, then reject the entry.
1451        for (AttributeType at : userAttributes.keySet())
1452        {
1453          if (at.isObsolete())
1454          {
1455            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1456                                         WARN_ADD_ATTR_IS_OBSOLETE.get(
1457                                              String.valueOf(entryDN),
1458                                              at.getNameOrOID()));
1459          }
1460        }
1461    
1462        for (AttributeType at : operationalAttributes.keySet())
1463        {
1464          if (at.isObsolete())
1465          {
1466            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1467                                         WARN_ADD_ATTR_IS_OBSOLETE.get(
1468                                              String.valueOf(entryDN),
1469                                              at.getNameOrOID()));
1470          }
1471        }
1472    
1473        for (ObjectClass oc : objectClasses.keySet())
1474        {
1475          if (oc.isObsolete())
1476          {
1477            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1478                                         WARN_ADD_OC_IS_OBSOLETE.get(
1479                                              String.valueOf(entryDN),
1480                                              oc.getNameOrOID()));
1481          }
1482        }
1483      }
1484    
1485    
1486    
1487      /**
1488       * Processes the set of controls contained in the add request.
1489       *
1490       * @param  parentDN  The DN of the parent of the entry to add.
1491       *
1492       * @throws  DirectoryException  If there is a problem with any of the
1493       *                              request controls.
1494       */
1495      private void processControls(DN parentDN)
1496              throws DirectoryException
1497      {
1498        List<Control> requestControls = getRequestControls();
1499        if ((requestControls != null) && (! requestControls.isEmpty()))
1500        {
1501          for (int i=0; i < requestControls.size(); i++)
1502          {
1503            Control c   = requestControls.get(i);
1504            String  oid = c.getOID();
1505    
1506            if (!AccessControlConfigManager.getInstance().
1507                    getAccessControlHandler().isAllowed(parentDN, this, c))
1508            {
1509              throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1510                             ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
1511            }
1512    
1513            if (oid.equals(OID_LDAP_ASSERTION))
1514            {
1515              LDAPAssertionRequestControl assertControl;
1516              if (c instanceof LDAPAssertionRequestControl)
1517              {
1518                assertControl = (LDAPAssertionRequestControl) c;
1519              }
1520              else
1521              {
1522                try
1523                {
1524                  assertControl = LDAPAssertionRequestControl.decodeControl(c);
1525                  requestControls.set(i, assertControl);
1526                }
1527                catch (LDAPException le)
1528                {
1529                  if (debugEnabled())
1530                  {
1531                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
1532                  }
1533    
1534                  throw new DirectoryException(
1535                                 ResultCode.valueOf(le.getResultCode()),
1536                                 le.getMessageObject());
1537                }
1538              }
1539    
1540              try
1541              {
1542                // FIXME -- We need to determine whether the current user has
1543                //          permission to make this determination.
1544                SearchFilter filter = assertControl.getSearchFilter();
1545                if (! filter.matchesEntry(entry))
1546                {
1547                  throw new DirectoryException(ResultCode.ASSERTION_FAILED,
1548                                               ERR_ADD_ASSERTION_FAILED.get(
1549                                                    String.valueOf(entryDN)));
1550                }
1551              }
1552              catch (DirectoryException de)
1553              {
1554                if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
1555                {
1556                  throw de;
1557                }
1558    
1559                if (debugEnabled())
1560                {
1561                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
1562                }
1563    
1564                throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1565                               ERR_ADD_CANNOT_PROCESS_ASSERTION_FILTER.get(
1566                                    String.valueOf(entryDN),
1567                                    de.getMessageObject()));
1568              }
1569            }
1570            else if (oid.equals(OID_LDAP_NOOP_OPENLDAP_ASSIGNED))
1571            {
1572              noOp = true;
1573            }
1574            else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
1575            {
1576              if (c instanceof LDAPPostReadRequestControl)
1577              {
1578                postReadRequest = (LDAPPostReadRequestControl) c;
1579              }
1580              else
1581              {
1582                try
1583                {
1584                  postReadRequest = LDAPPostReadRequestControl.decodeControl(c);
1585                  requestControls.set(i, postReadRequest);
1586                }
1587                catch (LDAPException le)
1588                {
1589                  if (debugEnabled())
1590                  {
1591                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
1592                  }
1593    
1594                  throw new DirectoryException(
1595                                 ResultCode.valueOf(le.getResultCode()),
1596                                 le.getMessageObject());
1597                }
1598              }
1599            }
1600            else if (oid.equals(OID_PROXIED_AUTH_V1))
1601            {
1602              // The requester must have the PROXIED_AUTH privilige in order to
1603              // be able to use this control.
1604              if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
1605                                                       this))
1606              {
1607                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
1608                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
1609              }
1610    
1611    
1612              ProxiedAuthV1Control proxyControl;
1613              if (c instanceof ProxiedAuthV1Control)
1614              {
1615                proxyControl = (ProxiedAuthV1Control) c;
1616              }
1617              else
1618              {
1619                try
1620                {
1621                  proxyControl = ProxiedAuthV1Control.decodeControl(c);
1622                }
1623                catch (LDAPException le)
1624                {
1625                  if (debugEnabled())
1626                  {
1627                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
1628                  }
1629    
1630                  throw new DirectoryException(
1631                                 ResultCode.valueOf(le.getResultCode()),
1632                                 le.getMessageObject());
1633                }
1634              }
1635    
1636    
1637              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
1638              setAuthorizationEntry(authorizationEntry);
1639              if (authorizationEntry == null)
1640              {
1641                setProxiedAuthorizationDN(DN.nullDN());
1642              }
1643              else
1644              {
1645                setProxiedAuthorizationDN(authorizationEntry.getDN());
1646              }
1647            }
1648            else if (oid.equals(OID_PROXIED_AUTH_V2))
1649            {
1650              // The requester must have the PROXIED_AUTH privilige in order to
1651              // be able to use this control.
1652              if (! getClientConnection().hasPrivilege(Privilege.PROXIED_AUTH,
1653                                                       this))
1654              {
1655                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
1656                               ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
1657              }
1658    
1659    
1660              ProxiedAuthV2Control proxyControl;
1661              if (c instanceof ProxiedAuthV2Control)
1662              {
1663                proxyControl = (ProxiedAuthV2Control) c;
1664              }
1665              else
1666              {
1667                try
1668                {
1669                  proxyControl = ProxiedAuthV2Control.decodeControl(c);
1670                }
1671                catch (LDAPException le)
1672                {
1673                  if (debugEnabled())
1674                  {
1675                    TRACER.debugCaught(DebugLogLevel.ERROR, le);
1676                  }
1677    
1678                  throw new DirectoryException(
1679                                 ResultCode.valueOf(le.getResultCode()),
1680                                 le.getMessageObject());
1681                }
1682              }
1683    
1684    
1685              Entry authorizationEntry = proxyControl.getAuthorizationEntry();
1686              setAuthorizationEntry(authorizationEntry);
1687              if (authorizationEntry == null)
1688              {
1689                setProxiedAuthorizationDN(DN.nullDN());
1690              }
1691              else
1692              {
1693                setProxiedAuthorizationDN(authorizationEntry.getDN());
1694              }
1695            }
1696            else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
1697            {
1698              // We don't need to do anything here because it's already handled
1699              // in LocalBackendAddOperation.handlePasswordPolicy().
1700            }
1701    
1702            // NYI -- Add support for additional controls.
1703            else if (c.isCritical())
1704            {
1705              if ((backend == null) || (! backend.supportsControl(oid)))
1706              {
1707                throw new DirectoryException(
1708                               ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
1709                               ERR_ADD_UNSUPPORTED_CRITICAL_CONTROL.get(
1710                                    String.valueOf(entryDN), oid));
1711              }
1712            }
1713          }
1714        }
1715      }
1716    
1717    
1718    
1719      /**
1720       * Adds the post-read response control to the response.
1721       */
1722      private void addPostReadResponse()
1723      {
1724        Entry addedEntry = entry.duplicate(true);
1725    
1726        if (! postReadRequest.allowsAttribute(
1727                   DirectoryServer.getObjectClassAttributeType()))
1728        {
1729          addedEntry.removeAttribute(DirectoryServer.getObjectClassAttributeType());
1730        }
1731    
1732        if (! postReadRequest.returnAllUserAttributes())
1733        {
1734          Iterator<AttributeType> iterator =
1735               addedEntry.getUserAttributes().keySet().iterator();
1736          while (iterator.hasNext())
1737          {
1738            AttributeType attrType = iterator.next();
1739            if (! postReadRequest.allowsAttribute(attrType))
1740            {
1741              iterator.remove();
1742            }
1743          }
1744        }
1745    
1746        if (! postReadRequest.returnAllOperationalAttributes())
1747        {
1748          Iterator<AttributeType> iterator =
1749               addedEntry.getOperationalAttributes().keySet().iterator();
1750          while (iterator.hasNext())
1751          {
1752            AttributeType attrType = iterator.next();
1753            if (! postReadRequest.allowsAttribute(attrType))
1754            {
1755              iterator.remove();
1756            }
1757          }
1758        }
1759    
1760        // FIXME -- Check access controls on the entry to see if it should
1761        //          be returned or if any attributes need to be stripped
1762        //          out..
1763        SearchResultEntry searchEntry = new SearchResultEntry(addedEntry);
1764        LDAPPostReadResponseControl responseControl =
1765             new LDAPPostReadResponseControl(postReadRequest.getOID(),
1766                                             postReadRequest.isCritical(),
1767                                             searchEntry);
1768        addResponseControl(responseControl);
1769      }
1770    }
1771