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.plugins;
028    
029    
030    
031    import java.util.List;
032    import java.util.Set;
033    
034    import org.opends.messages.Message;
035    import org.opends.server.admin.server.ConfigurationChangeListener;
036    import org.opends.server.admin.std.meta.PluginCfgDefn;
037    import org.opends.server.admin.std.server.SevenBitCleanPluginCfg;
038    import org.opends.server.admin.std.server.PluginCfg;
039    import org.opends.server.api.plugin.*;
040    import org.opends.server.config.ConfigException;
041    import org.opends.server.core.DirectoryServer;
042    import org.opends.server.types.Attribute;
043    import org.opends.server.types.AttributeType;
044    import org.opends.server.types.AttributeValue;
045    import org.opends.server.types.ByteString;
046    import org.opends.server.types.ConfigChangeResult;
047    import org.opends.server.types.DirectoryException;
048    import org.opends.server.types.DN;
049    import org.opends.server.types.Entry;
050    import org.opends.server.types.LDAPException;
051    import org.opends.server.types.LDIFImportConfig;
052    import org.opends.server.types.RawAttribute;
053    import org.opends.server.types.RawModification;
054    import org.opends.server.types.RDN;
055    import org.opends.server.types.ResultCode;
056    import org.opends.server.types.operation.PreParseAddOperation;
057    import org.opends.server.types.operation.PreParseModifyOperation;
058    import org.opends.server.types.operation.PreParseModifyDNOperation;
059    
060    import static org.opends.messages.PluginMessages.*;
061    
062    
063    
064    /**
065     * This class implements a Directory Server plugin that can be used to ensure
066     * that the values for a specified set of attributes (optionally, below a
067     * specified set of base DNs) are 7-bit clean (i.e., contain only ASCII
068     * characters).
069     */
070    public final class SevenBitCleanPlugin
071           extends DirectoryServerPlugin<SevenBitCleanPluginCfg>
072           implements ConfigurationChangeListener<SevenBitCleanPluginCfg>
073    {
074      /**
075       * The bitmask that will be used to make the comparisons.
076       */
077      private static final byte MASK = (byte) 0x7F;
078    
079    
080    
081      // The current configuration for this plugin.
082      private SevenBitCleanPluginCfg currentConfig;
083    
084    
085    
086      /**
087       * Creates a new instance of this Directory Server plugin.  Every plugin must
088       * implement a default constructor (it is the only one that will be used to
089       * create plugins defined in the configuration), and every plugin constructor
090       * must call {@code super()} as its first element.
091       */
092      public SevenBitCleanPlugin()
093      {
094        super();
095      }
096    
097    
098    
099      /**
100       * {@inheritDoc}
101       */
102      @Override()
103      public final void initializePlugin(Set<PluginType> pluginTypes,
104                                         SevenBitCleanPluginCfg configuration)
105             throws ConfigException
106      {
107        currentConfig = configuration;
108        configuration.addSevenBitCleanChangeListener(this);
109    
110        // Make sure that the plugin has been enabled for the appropriate types.
111        for (PluginType t : pluginTypes)
112        {
113          switch (t)
114          {
115            case LDIF_IMPORT:
116            case PRE_PARSE_ADD:
117            case PRE_PARSE_MODIFY:
118            case PRE_PARSE_MODIFY_DN:
119              // These are acceptable.
120              break;
121    
122    
123            default:
124              Message message =
125                  ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(t.toString());
126              throw new ConfigException(message);
127          }
128        }
129      }
130    
131    
132    
133      /**
134       * {@inheritDoc}
135       */
136      @Override()
137      public final void finalizePlugin()
138      {
139        currentConfig.removeSevenBitCleanChangeListener(this);
140      }
141    
142    
143    
144      /**
145       * {@inheritDoc}
146       */
147      @Override()
148      public final PluginResult.ImportLDIF
149                   doLDIFImport(LDIFImportConfig importConfig, Entry entry)
150      {
151        // Get the current configuration for this plugin.
152        SevenBitCleanPluginCfg config = currentConfig;
153    
154    
155        // Make sure that the entry is within the scope of this plugin.  While
156        // processing an LDIF import, we don't have access to the set of public
157        // naming contexts defined in the server, so if no explicit set of base DNs
158        // is defined, then assume that the entry is in scope.
159        Set<DN> baseDNs = config.getBaseDN();
160        if ((baseDNs != null) && (! baseDNs.isEmpty()))
161        {
162          boolean found = true;
163          for (DN baseDN : baseDNs)
164          {
165            if (baseDN.isAncestorOf(entry.getDN()))
166            {
167              found = true;
168              break;
169            }
170          }
171    
172          if (! found)
173          {
174            // The entry is out of scope, so we won't process it.
175            return PluginResult.ImportLDIF.continueEntryProcessing();
176          }
177        }
178    
179    
180        // Make sure all configured attributes have clean values.
181        for (AttributeType t : config.getAttributeType())
182        {
183          List<Attribute> attrList = entry.getAttribute(t);
184          if (attrList != null)
185          {
186            for (Attribute a : attrList)
187            {
188              for (AttributeValue v : a.getValues())
189              {
190                if (! is7BitClean(v.getValue()))
191                {
192                  Message rejectMessage =
193                       ERR_PLUGIN_7BIT_IMPORT_ATTR_NOT_CLEAN.get(
194                            a.getNameWithOptions());
195                  return PluginResult.ImportLDIF.stopEntryProcessing(rejectMessage);
196                }
197              }
198            }
199          }
200        }
201    
202    
203        // If we've gotten here, then everything is acceptable.
204        return PluginResult.ImportLDIF.continueEntryProcessing();
205      }
206    
207    
208    
209      /**
210       * {@inheritDoc}
211       */
212      @Override()
213      public final PluginResult.PreParse
214                   doPreParse(PreParseAddOperation addOperation)
215      {
216        // Get the current configuration for this plugin.
217        SevenBitCleanPluginCfg config = currentConfig;
218    
219    
220        // If the entry is within the scope of this plugin, then make sure all
221        // configured attributes have clean values.
222        DN entryDN;
223        try
224        {
225          entryDN = DN.decode(addOperation.getRawEntryDN());
226        }
227        catch (DirectoryException de)
228        {
229          return PluginResult.PreParse.stopProcessing(de.getResultCode(),
230              ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
231        }
232    
233        if (isInScope(config, entryDN))
234        {
235          for (RawAttribute rawAttr : addOperation.getRawAttributes())
236          {
237            Attribute a;
238            try
239            {
240              a = rawAttr.toAttribute();
241            }
242            catch (LDAPException le)
243            {
244              return PluginResult.PreParse.stopProcessing(
245                  ResultCode.valueOf(le.getResultCode()),
246                  ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
247                      rawAttr.getAttributeType(), le.getErrorMessage()));
248            }
249    
250            if (! config.getAttributeType().contains(a.getAttributeType()))
251            {
252              continue;
253            }
254    
255            for (AttributeValue v : a.getValues())
256            {
257              if (! is7BitClean(v.getValue()))
258              {
259                return PluginResult.PreParse.stopProcessing(
260                    ResultCode.CONSTRAINT_VIOLATION,
261                    ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
262                        rawAttr.getAttributeType()));
263              }
264            }
265          }
266        }
267    
268    
269        // If we've gotten here, then everything is acceptable.
270        return PluginResult.PreParse.continueOperationProcessing();
271      }
272    
273    
274    
275      /**
276       * {@inheritDoc}
277       */
278      @Override()
279      public final PluginResult.PreParse
280                        doPreParse(PreParseModifyOperation modifyOperation)
281      {
282        // Get the current configuration for this plugin.
283        SevenBitCleanPluginCfg config = currentConfig;
284    
285    
286        // If the target entry is within the scope of this plugin, then make sure
287        // all values that will be added during the modification will be acceptable.
288        DN entryDN;
289        try
290        {
291          entryDN = DN.decode(modifyOperation.getRawEntryDN());
292        }
293        catch (DirectoryException de)
294        {
295          return PluginResult.PreParse.stopProcessing(de.getResultCode(),
296              ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
297        }
298    
299        if (isInScope(config, entryDN))
300        {
301          for (RawModification m : modifyOperation.getRawModifications())
302          {
303            switch (m.getModificationType())
304            {
305              case ADD:
306              case REPLACE:
307                // These are modification types that we will process.
308                break;
309              default:
310                // This is not a modifiation type that we will process.
311                continue;
312            }
313    
314            RawAttribute rawAttr = m.getAttribute();
315            Attribute a;
316            try
317            {
318              a = rawAttr.toAttribute();
319            }
320            catch (LDAPException le)
321            {
322              return PluginResult.PreParse.stopProcessing(
323                  ResultCode.valueOf(le.getResultCode()),
324                  ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
325                      rawAttr.getAttributeType(), le.getErrorMessage()));
326            }
327    
328            if (! config.getAttributeType().contains(a.getAttributeType()))
329            {
330              continue;
331            }
332    
333            for (AttributeValue v : a.getValues())
334            {
335              if (! is7BitClean(v.getValue()))
336              {
337                return PluginResult.PreParse.stopProcessing(
338                    ResultCode.CONSTRAINT_VIOLATION,
339                    ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
340                        rawAttr.getAttributeType()));
341              }
342            }
343          }
344        }
345    
346    
347        // If we've gotten here, then everything is acceptable.
348        return PluginResult.PreParse.continueOperationProcessing();
349      }
350    
351    
352    
353      /**
354       * {@inheritDoc}
355       */
356      @Override()
357      public final PluginResult.PreParse
358                        doPreParse(PreParseModifyDNOperation modifyDNOperation)
359      {
360        // Get the current configuration for this plugin.
361        SevenBitCleanPluginCfg config = currentConfig;
362    
363    
364        // If the target entry is within the scope of this plugin, then make sure
365        // all values that will be added during the modification will be acceptable.
366        DN entryDN;
367        try
368        {
369          entryDN = DN.decode(modifyDNOperation.getRawEntryDN());
370        }
371        catch (DirectoryException de)
372        {
373          return PluginResult.PreParse.stopProcessing(de.getResultCode(),
374              ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
375        }
376    
377        if (isInScope(config, entryDN))
378        {
379          ByteString rawNewRDN = modifyDNOperation.getRawNewRDN();
380    
381          RDN newRDN;
382          try
383          {
384            newRDN = RDN.decode(rawNewRDN.stringValue());
385          }
386          catch (DirectoryException de)
387          {
388            return PluginResult.PreParse.stopProcessing(de.getResultCode(),
389                ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN.get(de.getMessageObject()));
390          }
391    
392          int numValues = newRDN.getNumValues();
393          for (int i=0; i < numValues; i++)
394          {
395            if (! config.getAttributeType().contains(newRDN.getAttributeType(i)))
396            {
397              continue;
398            }
399    
400            if (! is7BitClean(newRDN.getAttributeValue(i).getValue()))
401            {
402              return PluginResult.PreParse.stopProcessing(
403                  ResultCode.CONSTRAINT_VIOLATION,
404                  ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
405                      newRDN.getAttributeName(i)));
406            }
407          }
408        }
409    
410    
411        // If we've gotten here, then everything is acceptable.
412        return PluginResult.PreParse.continueOperationProcessing();
413      }
414    
415    
416    
417      /**
418       * Indicates whether the provided DN is within the scope of this plugin.
419       *
420       * @param  config  The configuration to use when making the determination.
421       * @param  dn      The DN for which to make the determination.
422       *
423       * @return  {@code true} if the provided DN is within the scope of this
424       *          plugin, or {@code false} if  not.
425       */
426      private final boolean isInScope(SevenBitCleanPluginCfg config, DN dn)
427      {
428        Set<DN> baseDNs = config.getBaseDN();
429        if ((baseDNs == null) || baseDNs.isEmpty())
430        {
431          baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
432        }
433    
434        boolean found = false;
435        for (DN baseDN: baseDNs)
436        {
437          if (dn.isDescendantOf(baseDN))
438          {
439            found = true;
440            break;
441          }
442        }
443    
444        return found;
445      }
446    
447    
448    
449      /**
450       * Indicates whether the provided value is 7-bit clean.
451       *
452       * @param  value  The value for which to make the determination.
453       *
454       * @return {@code true} if the provided value is 7-bit clean, or {@code false}
455       *         if it is not.
456       */
457      private final boolean is7BitClean(ByteString value)
458      {
459        for (byte b : value.value())
460        {
461          int i = (b & 0xFF);
462          if ((b & MASK) != b)
463          {
464            return false;
465          }
466        }
467    
468        return true;
469      }
470    
471    
472    
473      /**
474       * {@inheritDoc}
475       */
476      @Override()
477      public boolean isConfigurationAcceptable(PluginCfg configuration,
478                                               List<Message> unacceptableReasons)
479      {
480        SevenBitCleanPluginCfg cfg = (SevenBitCleanPluginCfg) configuration;
481        return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
482      }
483    
484    
485    
486      /**
487       * {@inheritDoc}
488       */
489      public boolean isConfigurationChangeAcceptable(
490                          SevenBitCleanPluginCfg configuration,
491                          List<Message> unacceptableReasons)
492      {
493        boolean configAcceptable = true;
494    
495        // Ensure that the set of plugin types is acceptable.
496        for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
497        {
498          switch (pluginType)
499          {
500            case LDIFIMPORT:
501            case PREPARSEADD:
502            case PREPARSEMODIFY:
503            case PREPARSEMODIFYDN:
504              // These are acceptable.
505              break;
506    
507    
508            default:
509              Message message = ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(
510                      pluginType.toString());
511              unacceptableReasons.add(message);
512              configAcceptable = false;
513          }
514        }
515    
516        return configAcceptable;
517      }
518    
519    
520    
521      /**
522       * {@inheritDoc}
523       */
524      public ConfigChangeResult applyConfigurationChange(
525                                     SevenBitCleanPluginCfg configuration)
526      {
527        currentConfig = configuration;
528        return new ConfigChangeResult(ResultCode.SUCCESS, false);
529      }
530    }
531