001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.plugins;
018
019import static org.opends.messages.PluginMessages.*;
020
021import java.util.List;
022import java.util.Set;
023
024import org.forgerock.i18n.LocalizableMessage;
025import org.forgerock.i18n.LocalizedIllegalArgumentException;
026import org.forgerock.opendj.config.server.ConfigChangeResult;
027import org.forgerock.opendj.config.server.ConfigException;
028import org.forgerock.opendj.ldap.AVA;
029import org.forgerock.opendj.ldap.ByteSequence;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.DN;
032import org.forgerock.opendj.ldap.RDN;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.AttributeType;
035import org.opends.server.admin.server.ConfigurationChangeListener;
036import org.opends.server.admin.std.meta.PluginCfgDefn;
037import org.opends.server.admin.std.server.PluginCfg;
038import org.opends.server.admin.std.server.SevenBitCleanPluginCfg;
039import org.opends.server.api.plugin.DirectoryServerPlugin;
040import org.opends.server.api.plugin.PluginResult;
041import org.opends.server.api.plugin.PluginType;
042import org.opends.server.core.DirectoryServer;
043import org.opends.server.types.Attribute;
044import org.opends.server.types.Entry;
045import org.opends.server.types.LDAPException;
046import org.opends.server.types.LDIFImportConfig;
047import org.opends.server.types.RawAttribute;
048import org.opends.server.types.RawModification;
049import org.opends.server.types.operation.PreParseAddOperation;
050import org.opends.server.types.operation.PreParseModifyDNOperation;
051import org.opends.server.types.operation.PreParseModifyOperation;
052
053/**
054 * This class implements a Directory Server plugin that can be used to ensure
055 * that the values for a specified set of attributes (optionally, below a
056 * specified set of base DNs) are 7-bit clean (i.e., contain only ASCII
057 * characters).
058 */
059public final class SevenBitCleanPlugin
060       extends DirectoryServerPlugin<SevenBitCleanPluginCfg>
061       implements ConfigurationChangeListener<SevenBitCleanPluginCfg>
062{
063  /** The bitmask that will be used to make the comparisons. */
064  private static final byte MASK = 0x7F;
065
066  /** The current configuration for this plugin. */
067  private SevenBitCleanPluginCfg currentConfig;
068
069  /**
070   * Creates a new instance of this Directory Server plugin.  Every plugin must
071   * implement a default constructor (it is the only one that will be used to
072   * create plugins defined in the configuration), and every plugin constructor
073   * must call {@code super()} as its first element.
074   */
075  public SevenBitCleanPlugin()
076  {
077    super();
078  }
079
080
081
082  /** {@inheritDoc} */
083  @Override
084  public final void initializePlugin(Set<PluginType> pluginTypes,
085                                     SevenBitCleanPluginCfg configuration)
086         throws ConfigException
087  {
088    currentConfig = configuration;
089    configuration.addSevenBitCleanChangeListener(this);
090
091    // Make sure that the plugin has been enabled for the appropriate types.
092    for (PluginType t : pluginTypes)
093    {
094      switch (t)
095      {
096        case LDIF_IMPORT:
097        case PRE_PARSE_ADD:
098        case PRE_PARSE_MODIFY:
099        case PRE_PARSE_MODIFY_DN:
100          // These are acceptable.
101          break;
102
103        default:
104          throw new ConfigException(ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(t));
105      }
106    }
107  }
108
109
110
111  /** {@inheritDoc} */
112  @Override
113  public final void finalizePlugin()
114  {
115    currentConfig.removeSevenBitCleanChangeListener(this);
116  }
117
118
119
120  /** {@inheritDoc} */
121  @Override
122  public final PluginResult.ImportLDIF
123               doLDIFImport(LDIFImportConfig importConfig, Entry entry)
124  {
125    // Get the current configuration for this plugin.
126    SevenBitCleanPluginCfg config = currentConfig;
127
128
129    // Make sure that the entry is within the scope of this plugin.  While
130    // processing an LDIF import, we don't have access to the set of public
131    // naming contexts defined in the server, so if no explicit set of base DNs
132    // is defined, then assume that the entry is in scope.
133    if (!isDescendantOfAny(entry.getName(), config.getBaseDN()))
134    {
135      // The entry is out of scope, so we won't process it.
136      return PluginResult.ImportLDIF.continueEntryProcessing();
137    }
138
139
140    // Make sure all configured attributes have clean values.
141    for (AttributeType t : config.getAttributeType())
142    {
143      for (Attribute a : entry.getAttribute(t))
144      {
145        for (ByteString v : a)
146        {
147          if (!is7BitClean(v))
148          {
149            LocalizableMessage rejectMessage =
150                 ERR_PLUGIN_7BIT_IMPORT_ATTR_NOT_CLEAN.get(a.getNameWithOptions());
151            return PluginResult.ImportLDIF.stopEntryProcessing(rejectMessage);
152          }
153        }
154      }
155    }
156
157
158    // If we've gotten here, then everything is acceptable.
159    return PluginResult.ImportLDIF.continueEntryProcessing();
160  }
161
162
163
164  /** {@inheritDoc} */
165  @Override
166  public final PluginResult.PreParse
167               doPreParse(PreParseAddOperation addOperation)
168  {
169    // Get the current configuration for this plugin.
170    SevenBitCleanPluginCfg config = currentConfig;
171
172
173    // If the entry is within the scope of this plugin, then make sure all
174    // configured attributes have clean values.
175    DN entryDN;
176    try
177    {
178      entryDN = DN.valueOf(addOperation.getRawEntryDN());
179    }
180    catch (LocalizedIllegalArgumentException e)
181    {
182      return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX,
183          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject()));
184    }
185
186    if (isInScope(config, entryDN))
187    {
188      for (RawAttribute rawAttr : addOperation.getRawAttributes())
189      {
190        Attribute a;
191        try
192        {
193          a = rawAttr.toAttribute();
194        }
195        catch (LDAPException le)
196        {
197          return PluginResult.PreParse.stopProcessing(
198              ResultCode.valueOf(le.getResultCode()),
199              ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
200                  rawAttr.getAttributeType(), le.getErrorMessage()));
201        }
202
203        if (!config.getAttributeType().contains(a.getAttributeDescription().getAttributeType()))
204        {
205          continue;
206        }
207
208        for (ByteString v : a)
209        {
210          if (!is7BitClean(v))
211          {
212            return PluginResult.PreParse.stopProcessing(
213                ResultCode.CONSTRAINT_VIOLATION,
214                ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
215                    rawAttr.getAttributeType()));
216          }
217        }
218      }
219    }
220
221
222    // If we've gotten here, then everything is acceptable.
223    return PluginResult.PreParse.continueOperationProcessing();
224  }
225
226
227
228  /** {@inheritDoc} */
229  @Override
230  public final PluginResult.PreParse
231                    doPreParse(PreParseModifyOperation modifyOperation)
232  {
233    // Get the current configuration for this plugin.
234    SevenBitCleanPluginCfg config = currentConfig;
235
236
237    // If the target entry is within the scope of this plugin, then make sure
238    // all values that will be added during the modification will be acceptable.
239    DN entryDN;
240    try
241    {
242      entryDN = DN.valueOf(modifyOperation.getRawEntryDN());
243    }
244    catch (LocalizedIllegalArgumentException e)
245    {
246      return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX,
247          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject()));
248    }
249
250    if (isInScope(config, entryDN))
251    {
252      for (RawModification m : modifyOperation.getRawModifications())
253      {
254        switch (m.getModificationType().asEnum())
255        {
256          case ADD:
257          case REPLACE:
258            // These are modification types that we will process.
259            break;
260          default:
261            // This is not a modification type that we will process.
262            continue;
263        }
264
265        RawAttribute rawAttr = m.getAttribute();
266        Attribute a;
267        try
268        {
269          a = rawAttr.toAttribute();
270        }
271        catch (LDAPException le)
272        {
273          return PluginResult.PreParse.stopProcessing(
274              ResultCode.valueOf(le.getResultCode()),
275              ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
276                  rawAttr.getAttributeType(), le.getErrorMessage()));
277        }
278
279        if (!config.getAttributeType().contains(a.getAttributeDescription().getAttributeType()))
280        {
281          continue;
282        }
283
284        for (ByteString v : a)
285        {
286          if (!is7BitClean(v))
287          {
288            return PluginResult.PreParse.stopProcessing(
289                ResultCode.CONSTRAINT_VIOLATION,
290                ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
291                    rawAttr.getAttributeType()));
292          }
293        }
294      }
295    }
296
297
298    // If we've gotten here, then everything is acceptable.
299    return PluginResult.PreParse.continueOperationProcessing();
300  }
301
302
303
304  /** {@inheritDoc} */
305  @Override
306  public final PluginResult.PreParse
307                    doPreParse(PreParseModifyDNOperation modifyDNOperation)
308  {
309    // Get the current configuration for this plugin.
310    SevenBitCleanPluginCfg config = currentConfig;
311
312
313    // If the target entry is within the scope of this plugin, then make sure
314    // all values that will be added during the modification will be acceptable.
315    DN entryDN;
316    try
317    {
318      entryDN = DN.valueOf(modifyDNOperation.getRawEntryDN());
319    }
320    catch (LocalizedIllegalArgumentException e)
321    {
322      return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX,
323          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject()));
324    }
325
326    if (isInScope(config, entryDN))
327    {
328      ByteString rawNewRDN = modifyDNOperation.getRawNewRDN();
329
330      RDN newRDN;
331      try
332      {
333        newRDN = RDN.valueOf(rawNewRDN.toString());
334      }
335      catch (LocalizedIllegalArgumentException e)
336      {
337        return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX,
338            ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN.get(e.getMessageObject()));
339      }
340
341      for (AVA ava : newRDN)
342      {
343        if (!config.getAttributeType().contains(ava.getAttributeType()))
344        {
345          continue;
346        }
347
348        if (!is7BitClean(ava.getAttributeValue()))
349        {
350          return PluginResult.PreParse.stopProcessing(
351              ResultCode.CONSTRAINT_VIOLATION,
352              ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(ava.getAttributeName()));
353        }
354      }
355    }
356
357
358    // If we've gotten here, then everything is acceptable.
359    return PluginResult.PreParse.continueOperationProcessing();
360  }
361
362
363
364  /**
365   * Indicates whether the provided DN is within the scope of this plugin.
366   *
367   * @param  config  The configuration to use when making the determination.
368   * @param  dn      The DN for which to make the determination.
369   *
370   * @return  {@code true} if the provided DN is within the scope of this
371   *          plugin, or {@code false} if  not.
372   */
373  private final boolean isInScope(SevenBitCleanPluginCfg config, DN dn)
374  {
375    Set<DN> baseDNs = config.getBaseDN();
376    if (baseDNs == null || baseDNs.isEmpty())
377    {
378      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
379    }
380    return isDescendantOfAny(dn, baseDNs);
381  }
382
383  private boolean isDescendantOfAny(DN dn, Set<DN> baseDNs)
384  {
385    if (baseDNs != null)
386    {
387      for (DN baseDN: baseDNs)
388      {
389        if (dn.isSubordinateOrEqualTo(baseDN))
390        {
391          return true;
392        }
393      }
394    }
395    return false;
396  }
397
398
399
400  /**
401   * Indicates whether the provided value is 7-bit clean.
402   *
403   * @param  value  The value for which to make the determination.
404   *
405   * @return {@code true} if the provided value is 7-bit clean, or {@code false}
406   *         if it is not.
407   */
408  private final boolean is7BitClean(ByteSequence value)
409  {
410    for (int i = 0; i < value.length(); i++)
411    {
412      byte b = value.byteAt(i);
413      if ((b & MASK) != b)
414      {
415        return false;
416      }
417    }
418    return true;
419  }
420
421
422
423  /** {@inheritDoc} */
424  @Override
425  public boolean isConfigurationAcceptable(PluginCfg configuration,
426                                           List<LocalizableMessage> unacceptableReasons)
427  {
428    SevenBitCleanPluginCfg cfg = (SevenBitCleanPluginCfg) configuration;
429    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
430  }
431
432
433
434  /** {@inheritDoc} */
435  @Override
436  public boolean isConfigurationChangeAcceptable(
437                      SevenBitCleanPluginCfg configuration,
438                      List<LocalizableMessage> unacceptableReasons)
439  {
440    boolean configAcceptable = true;
441
442    // Ensure that the set of plugin types is acceptable.
443    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
444    {
445      switch (pluginType)
446      {
447        case LDIFIMPORT:
448        case PREPARSEADD:
449        case PREPARSEMODIFY:
450        case PREPARSEMODIFYDN:
451          // These are acceptable.
452          break;
453
454
455        default:
456          unacceptableReasons.add(ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(pluginType));
457          configAcceptable = false;
458      }
459    }
460
461    return configAcceptable;
462  }
463
464
465
466  /** {@inheritDoc} */
467  @Override
468  public ConfigChangeResult applyConfigurationChange(
469                                 SevenBitCleanPluginCfg configuration)
470  {
471    currentConfig = configuration;
472    return new ConfigChangeResult();
473  }
474}