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    
028    package org.opends.server.authorization.dseecompat;
029    import org.opends.messages.Message;
030    
031    import org.opends.server.workflowelement.localbackend.*;
032    import org.opends.server.api.ChangeNotificationListener;
033    import org.opends.server.api.BackendInitializationListener;
034    import org.opends.server.api.Backend;
035    import org.opends.server.api.AlertGenerator;
036    import org.opends.server.types.operation.PostResponseAddOperation;
037    import org.opends.server.types.operation.PostResponseDeleteOperation;
038    import org.opends.server.types.operation.PostResponseModifyOperation;
039    import org.opends.server.types.operation.PostResponseModifyDNOperation;
040    import org.opends.server.protocols.internal.InternalClientConnection;
041    import org.opends.server.protocols.internal.InternalSearchOperation;
042    import static org.opends.server.loggers.ErrorLogger.logError;
043    import static org.opends.server.loggers.debug.DebugLogger.*;
044    import org.opends.server.loggers.debug.DebugTracer;
045    import org.opends.server.types.*;
046    import static org.opends.messages.AccessControlMessages.*;
047    import org.opends.server.core.DirectoryServer;
048    import static org.opends.server.util.ServerConstants.*;
049    
050    import java.util.*;
051    
052    /**
053     * The AciListenerManager updates an ACI list after each
054     * modification operation. Also, updates ACI list when backends are initialized
055     * and finalized.
056     */
057    public class AciListenerManager
058            implements ChangeNotificationListener, BackendInitializationListener,
059                       AlertGenerator {
060      /**
061       * The tracer object for the debug logger.
062       */
063      private static final DebugTracer TRACER = getTracer();
064    
065    
066        /**
067         * The fully-qualified name of this class.
068         */
069        private static final String CLASS_NAME =
070             "org.opends.server.authorization.dseecompat.AciListenerManager";
071    
072        /*
073         *  The configuration DN.
074         */
075        private DN configurationDN;
076    
077    
078        /*
079         *  True if the server is in lockdown mode.
080         */
081        private boolean inLockDownMode=false;
082    
083        /*
084         * The AciList caches the ACIs.
085         */
086        private AciList aciList;
087    
088        /*
089         * Search filter used in context search for "aci" attribute types.
090         */
091        private static SearchFilter aciFilter;
092    
093        /*
094         * The aci attribute type is operational so we need to specify it to be
095         * returned.
096         */
097        private static LinkedHashSet<String> attrs = new LinkedHashSet<String>();
098    
099        static {
100            /*
101             * Set up the filter used to search private and public contexts.
102             */
103            try {
104                aciFilter=SearchFilter.createFilterFromString("(aci=*)");
105            } catch (DirectoryException ex) {
106                //TODO should never happen, error message?
107            }
108            attrs.add("aci");
109        }
110    
111        /**
112         * Save the list created by the AciHandler routine. Registers as an
113         * Alert Generator that can send alerts when the server is being put
114         * in lockdown  mode. Registers as backend initialization listener that is
115         * used to manage the ACI list cache when backends are
116         * initialized/finalized. Registers as a change notification listener that
117         * is used to manage the ACI list cache after ACI modifications have been
118         * performed.
119         *
120         * @param aciList The list object created and loaded by the handler.
121         * @param cfgDN The DN of the access control configuration entry.
122         */
123        public AciListenerManager(AciList aciList, DN cfgDN) {
124            this.aciList=aciList;
125            this.configurationDN=cfgDN;
126            DirectoryServer.registerChangeNotificationListener(this);
127            DirectoryServer.registerBackendInitializationListener(this);
128            DirectoryServer.registerAlertGenerator(this);
129        }
130    
131       /**
132        * Deregister from the change notification listener, the backend
133        * initialization listener and the alert generator.
134        */
135        public void finalizeListenerManager() {
136            DirectoryServer.deregisterChangeNotificationListener(this);
137            DirectoryServer.deregisterBackendInitializationListener(this);
138            DirectoryServer.deregisterAlertGenerator(this);
139        }
140    
141    
142        /**
143         * A delete operation succeeded. Remove any ACIs associated with the
144         * entry deleted.
145         * @param deleteOperation The delete operation.
146         * @param entry The entry being deleted.
147         */
148        public void handleDeleteOperation(PostResponseDeleteOperation
149                deleteOperation, Entry entry) {
150            boolean hasAci,  hasGlobalAci=false;
151            //This entry might have both global and aci attribute types.
152            if((hasAci=entry.hasOperationalAttribute(AciHandler.aciType)) ||
153                    (hasGlobalAci=entry.hasAttribute(AciHandler.globalAciType)))
154                aciList.removeAci(entry, hasAci, hasGlobalAci);
155        }
156    
157        /**
158         * An Add operation succeeded. Add any ACIs associated with the
159         * entry being added.
160         * @param addOperation  The add operation.
161         * @param entry   The entry being added.
162         */
163        public void handleAddOperation(PostResponseAddOperation addOperation,
164                                       Entry entry) {
165            boolean hasAci, hasGlobalAci=false;
166            //Ignore this list, the ACI syntax has already passed and it should be
167            //empty.
168            LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
169            //This entry might have both global and aci attribute types.
170            if((hasAci=entry.hasOperationalAttribute(AciHandler.aciType)) ||
171                    (hasGlobalAci=entry.hasAttribute(AciHandler.globalAciType)))
172                aciList.addAci(entry, hasAci, hasGlobalAci, failedACIMsgs);
173        }
174    
175        /**
176         * A modify operation succeeded. Adjust the ACIs by removing
177         * ACIs based on the oldEntry and then adding ACIs based on the new
178         * entry.
179         * @param modOperation  the modify operation.
180         * @param oldEntry The old entry to examine.
181         * @param newEntry  The new entry to examine.
182         */
183        public void handleModifyOperation(PostResponseModifyOperation modOperation,
184                                          Entry oldEntry, Entry newEntry)
185        {
186            // A change to the ACI list is expensive so let's first make sure that
187            // the modification included changes to the ACI. We'll check for
188            //both "aci" attribute types and global "ds-cfg-global-aci" attribute
189            //types.
190            boolean hasAci = false, hasGlobalAci=false;
191            List<Modification> mods = modOperation.getModifications();
192            for (Modification mod : mods) {
193                AttributeType attributeType=mod.getAttribute().getAttributeType();
194                if (attributeType.equals(AciHandler.aciType))
195                    hasAci = true;
196               else if(attributeType.equals(AciHandler.globalAciType))
197                    hasGlobalAci=true;
198                if(hasAci && hasGlobalAci)
199                   break;
200            }
201            if (hasAci || hasGlobalAci)
202                aciList.modAciOldNewEntry(oldEntry, newEntry, hasAci, hasGlobalAci);
203        }
204    
205        /**
206         * A modify DN operation has succeeded. Adjust the ACIs by moving ACIs
207         * under the old entry DN to the new entry DN.
208         * @param modifyDNOperation  The LDAP modify DN operation.
209         * @param oldEntry  The old entry.
210         * @param newEntry The new entry.
211         */
212        public void handleModifyDNOperation(
213                PostResponseModifyDNOperation modifyDNOperation,
214                Entry oldEntry, Entry newEntry)
215        {
216            aciList.renameAci(oldEntry.getDN(), newEntry.getDN());
217        }
218    
219        /**
220         * {@inheritDoc}  In this case, the server will search the backend to find
221         * all aci attribute type values that it may contain and add them to the
222         * ACI list.
223         */
224        public void performBackendInitializationProcessing(Backend backend) {
225          // Check to make sure that the backend has a presence index defined for
226          // the ACI attribute.  If it does not, then log a warning message because
227          // this processing could be very expensive.
228          AttributeType aciType = DirectoryServer.getAttributeType("aci", true);
229          if (! backend.isIndexed(aciType, IndexType.PRESENCE))
230          {
231            logError(WARN_ACI_ATTRIBUTE_NOT_INDEXED.get(backend.getBackendID(),
232                                                        "aci"));
233          }
234    
235    
236          InternalClientConnection conn =
237               InternalClientConnection.getRootConnection();
238          LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
239          //Add manageDsaIT control so any ACIs in referral entries will be
240          //picked up.
241          ArrayList<Control> controls = new ArrayList<Control>(1);
242          controls.add(new Control(OID_MANAGE_DSAIT_CONTROL, true));
243          for (DN baseDN : backend.getBaseDNs()) {
244            try {
245              if (! backend.entryExists(baseDN))  {
246                continue;
247              }
248            } catch (Exception e) {
249                if (debugEnabled())
250                {
251                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
252                }
253                continue;
254            }
255            InternalSearchOperation internalSearch =
256                 new InternalSearchOperation(
257                      conn,
258                      InternalClientConnection.nextOperationID(),
259                      InternalClientConnection.nextMessageID(),
260                      controls, baseDN, SearchScope.WHOLE_SUBTREE,
261                      DereferencePolicy.NEVER_DEREF_ALIASES,
262                      0, 0, false, aciFilter, attrs, null);
263            LocalBackendSearchOperation localInternalSearch =
264              new LocalBackendSearchOperation(internalSearch);
265            try  {
266              backend.search(localInternalSearch);
267            } catch (Exception e) {
268                if (debugEnabled())
269                {
270                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
271                }
272                continue;
273            }
274            if(!internalSearch.getSearchEntries().isEmpty()) {
275              int validAcis = aciList.addAci(
276                   internalSearch.getSearchEntries(), failedACIMsgs);
277              if(!failedACIMsgs.isEmpty())
278                        logMsgsSetLockDownMode(failedACIMsgs);
279              Message message = INFO_ACI_ADD_LIST_ACIS.get(
280                  Integer.toString(validAcis), String.valueOf(baseDN));
281              logError(message);
282            }
283          }
284        }
285    
286        /**
287         * {@inheritDoc}  In this case, the server will remove all aci attribute
288         * type values associated with entries in the provided backend.
289         */
290        public void performBackendFinalizationProcessing(Backend backend) {
291            aciList.removeAci(backend);
292        }
293    
294    
295    
296        /**
297         * Retrieves the fully-qualified name of the Java class for this alert
298         * generator implementation.
299         *
300         * @return  The fully-qualified name of the Java class for this alert
301         *          generator implementation.
302         */
303        public String getClassName()
304        {
305            return CLASS_NAME;
306        }
307    
308    
309        /**
310         * Retrieves the DN of the configuration entry used to configure the
311         * handler.
312         *
313         * @return  The DN of the configuration entry containing the Access Control
314         *          configuration information.
315         */
316        public DN getComponentEntryDN()
317        {
318          return this.configurationDN;
319        }
320    
321    
322        /**
323         * Retrieves information about the set of alerts that this generator may
324         * produce.  The map returned should be between the notification type for a
325         * particular notification and the human-readable description for that
326         * notification.  This alert generator must not generate any alerts with
327         * types that are not contained in this list.
328         *
329         * @return  Information about the set of alerts that this generator may
330         *          produce.
331         */
332        public LinkedHashMap<String,String> getAlerts()
333        {
334            LinkedHashMap<String,String> alerts =
335                    new LinkedHashMap<String,String>();
336            alerts.put(ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED,
337                    ALERT_DESCRIPTION_ACCESS_CONTROL_PARSE_FAILED);
338            return alerts;
339    
340        }
341    
342        /**
343         * Log the exception messages from the failed ACI decode and then put the
344         * server in lockdown mode -- if needed.
345         *
346         * @param failedACIMsgs  List of exception messages from failed ACI decodes.
347         */
348        public  void logMsgsSetLockDownMode(LinkedList<Message> failedACIMsgs) {
349    
350            for(Message msg : failedACIMsgs) {
351                Message message=WARN_ACI_SERVER_DECODE_FAILED.get(msg);
352                logError(message);
353            }
354            if(!inLockDownMode)
355                setLockDownMode();
356        }
357    
358    
359        /**
360         * Send an WARN_ACI_ENTER_LOCKDOWN_MODE alert notification and put the
361         * server in lockdown mode.
362         *
363         */
364        private void setLockDownMode() {
365            if(!inLockDownMode) {
366                inLockDownMode=true;
367                //Send ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED alert that
368                //lockdown is about to be entered.
369                Message lockDownMsg=WARN_ACI_ENTER_LOCKDOWN_MODE.get();
370                DirectoryServer.sendAlertNotification(this,
371                        ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED,
372                        lockDownMsg );
373                //Enter lockdown mode.
374                DirectoryServer.setLockdownMode(true);
375    
376            }
377        }
378    }