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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.plugins;
028
029import static org.opends.messages.PluginMessages.*;
030
031import java.util.List;
032import java.util.Set;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.opends.server.admin.server.ConfigurationChangeListener;
036import org.opends.server.admin.std.meta.PluginCfgDefn;
037import org.opends.server.admin.std.server.SevenBitCleanPluginCfg;
038import org.opends.server.admin.std.server.PluginCfg;
039import org.opends.server.api.plugin.DirectoryServerPlugin;
040import org.opends.server.api.plugin.PluginResult;
041import org.opends.server.api.plugin.PluginType;
042import org.forgerock.opendj.config.server.ConfigChangeResult;
043import org.forgerock.opendj.config.server.ConfigException;
044import org.opends.server.core.DirectoryServer;
045import org.opends.server.types.*;
046import org.forgerock.opendj.ldap.ResultCode;
047import org.forgerock.opendj.ldap.ByteString;
048import org.forgerock.opendj.ldap.ByteSequence;
049import org.opends.server.types.operation.PreParseAddOperation;
050import org.opends.server.types.operation.PreParseModifyOperation;
051import org.opends.server.types.operation.PreParseModifyDNOperation;
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      List<Attribute> attrList = entry.getAttribute(t);
144      if (attrList != null)
145      {
146        for (Attribute a : attrList)
147        {
148          for (ByteString v : a)
149          {
150            if (!is7BitClean(v))
151            {
152              LocalizableMessage rejectMessage =
153                   ERR_PLUGIN_7BIT_IMPORT_ATTR_NOT_CLEAN.get(a.getNameWithOptions());
154              return PluginResult.ImportLDIF.stopEntryProcessing(rejectMessage);
155            }
156          }
157        }
158      }
159    }
160
161
162    // If we've gotten here, then everything is acceptable.
163    return PluginResult.ImportLDIF.continueEntryProcessing();
164  }
165
166
167
168  /** {@inheritDoc} */
169  @Override
170  public final PluginResult.PreParse
171               doPreParse(PreParseAddOperation addOperation)
172  {
173    // Get the current configuration for this plugin.
174    SevenBitCleanPluginCfg config = currentConfig;
175
176
177    // If the entry is within the scope of this plugin, then make sure all
178    // configured attributes have clean values.
179    DN entryDN;
180    try
181    {
182      entryDN = DN.decode(addOperation.getRawEntryDN());
183    }
184    catch (DirectoryException de)
185    {
186      return PluginResult.PreParse.stopProcessing(de.getResultCode(),
187          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
188    }
189
190    if (isInScope(config, entryDN))
191    {
192      for (RawAttribute rawAttr : addOperation.getRawAttributes())
193      {
194        Attribute a;
195        try
196        {
197          a = rawAttr.toAttribute();
198        }
199        catch (LDAPException le)
200        {
201          return PluginResult.PreParse.stopProcessing(
202              ResultCode.valueOf(le.getResultCode()),
203              ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
204                  rawAttr.getAttributeType(), le.getErrorMessage()));
205        }
206
207        if (! config.getAttributeType().contains(a.getAttributeType()))
208        {
209          continue;
210        }
211
212        for (ByteString v : a)
213        {
214          if (!is7BitClean(v))
215          {
216            return PluginResult.PreParse.stopProcessing(
217                ResultCode.CONSTRAINT_VIOLATION,
218                ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
219                    rawAttr.getAttributeType()));
220          }
221        }
222      }
223    }
224
225
226    // If we've gotten here, then everything is acceptable.
227    return PluginResult.PreParse.continueOperationProcessing();
228  }
229
230
231
232  /** {@inheritDoc} */
233  @Override
234  public final PluginResult.PreParse
235                    doPreParse(PreParseModifyOperation modifyOperation)
236  {
237    // Get the current configuration for this plugin.
238    SevenBitCleanPluginCfg config = currentConfig;
239
240
241    // If the target entry is within the scope of this plugin, then make sure
242    // all values that will be added during the modification will be acceptable.
243    DN entryDN;
244    try
245    {
246      entryDN = DN.decode(modifyOperation.getRawEntryDN());
247    }
248    catch (DirectoryException de)
249    {
250      return PluginResult.PreParse.stopProcessing(de.getResultCode(),
251          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
252    }
253
254    if (isInScope(config, entryDN))
255    {
256      for (RawModification m : modifyOperation.getRawModifications())
257      {
258        switch (m.getModificationType().asEnum())
259        {
260          case ADD:
261          case REPLACE:
262            // These are modification types that we will process.
263            break;
264          default:
265            // This is not a modification type that we will process.
266            continue;
267        }
268
269        RawAttribute rawAttr = m.getAttribute();
270        Attribute a;
271        try
272        {
273          a = rawAttr.toAttribute();
274        }
275        catch (LDAPException le)
276        {
277          return PluginResult.PreParse.stopProcessing(
278              ResultCode.valueOf(le.getResultCode()),
279              ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get(
280                  rawAttr.getAttributeType(), le.getErrorMessage()));
281        }
282
283        if (! config.getAttributeType().contains(a.getAttributeType()))
284        {
285          continue;
286        }
287
288        for (ByteString v : a)
289        {
290          if (!is7BitClean(v))
291          {
292            return PluginResult.PreParse.stopProcessing(
293                ResultCode.CONSTRAINT_VIOLATION,
294                ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
295                    rawAttr.getAttributeType()));
296          }
297        }
298      }
299    }
300
301
302    // If we've gotten here, then everything is acceptable.
303    return PluginResult.PreParse.continueOperationProcessing();
304  }
305
306
307
308  /** {@inheritDoc} */
309  @Override
310  public final PluginResult.PreParse
311                    doPreParse(PreParseModifyDNOperation modifyDNOperation)
312  {
313    // Get the current configuration for this plugin.
314    SevenBitCleanPluginCfg config = currentConfig;
315
316
317    // If the target entry is within the scope of this plugin, then make sure
318    // all values that will be added during the modification will be acceptable.
319    DN entryDN;
320    try
321    {
322      entryDN = DN.decode(modifyDNOperation.getRawEntryDN());
323    }
324    catch (DirectoryException de)
325    {
326      return PluginResult.PreParse.stopProcessing(de.getResultCode(),
327          ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(de.getMessageObject()));
328    }
329
330    if (isInScope(config, entryDN))
331    {
332      ByteString rawNewRDN = modifyDNOperation.getRawNewRDN();
333
334      RDN newRDN;
335      try
336      {
337        newRDN = RDN.decode(rawNewRDN.toString());
338      }
339      catch (DirectoryException de)
340      {
341        return PluginResult.PreParse.stopProcessing(de.getResultCode(),
342            ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN.get(de.getMessageObject()));
343      }
344
345      int numValues = newRDN.getNumValues();
346      for (int i=0; i < numValues; i++)
347      {
348        if (! config.getAttributeType().contains(newRDN.getAttributeType(i)))
349        {
350          continue;
351        }
352
353        if (!is7BitClean(newRDN.getAttributeValue(i)))
354        {
355          return PluginResult.PreParse.stopProcessing(
356              ResultCode.CONSTRAINT_VIOLATION,
357              ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(
358                  newRDN.getAttributeName(i)));
359        }
360      }
361    }
362
363
364    // If we've gotten here, then everything is acceptable.
365    return PluginResult.PreParse.continueOperationProcessing();
366  }
367
368
369
370  /**
371   * Indicates whether the provided DN is within the scope of this plugin.
372   *
373   * @param  config  The configuration to use when making the determination.
374   * @param  dn      The DN for which to make the determination.
375   *
376   * @return  {@code true} if the provided DN is within the scope of this
377   *          plugin, or {@code false} if  not.
378   */
379  private final boolean isInScope(SevenBitCleanPluginCfg config, DN dn)
380  {
381    Set<DN> baseDNs = config.getBaseDN();
382    if (baseDNs == null || baseDNs.isEmpty())
383    {
384      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
385    }
386    return isDescendantOfAny(dn, baseDNs);
387  }
388
389  private boolean isDescendantOfAny(DN dn, Set<DN> baseDNs)
390  {
391    if (baseDNs != null)
392    {
393      for (DN baseDN: baseDNs)
394      {
395        if (dn.isDescendantOf(baseDN))
396        {
397          return true;
398        }
399      }
400    }
401    return false;
402  }
403
404
405
406  /**
407   * Indicates whether the provided value is 7-bit clean.
408   *
409   * @param  value  The value for which to make the determination.
410   *
411   * @return {@code true} if the provided value is 7-bit clean, or {@code false}
412   *         if it is not.
413   */
414  private final boolean is7BitClean(ByteSequence value)
415  {
416    for (int i = 0; i < value.length(); i++)
417    {
418      byte b = value.byteAt(i);
419      if ((b & MASK) != b)
420      {
421        return false;
422      }
423    }
424    return true;
425  }
426
427
428
429  /** {@inheritDoc} */
430  @Override
431  public boolean isConfigurationAcceptable(PluginCfg configuration,
432                                           List<LocalizableMessage> unacceptableReasons)
433  {
434    SevenBitCleanPluginCfg cfg = (SevenBitCleanPluginCfg) configuration;
435    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
436  }
437
438
439
440  /** {@inheritDoc} */
441  @Override
442  public boolean isConfigurationChangeAcceptable(
443                      SevenBitCleanPluginCfg configuration,
444                      List<LocalizableMessage> unacceptableReasons)
445  {
446    boolean configAcceptable = true;
447
448    // Ensure that the set of plugin types is acceptable.
449    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
450    {
451      switch (pluginType)
452      {
453        case LDIFIMPORT:
454        case PREPARSEADD:
455        case PREPARSEMODIFY:
456        case PREPARSEMODIFYDN:
457          // These are acceptable.
458          break;
459
460
461        default:
462          unacceptableReasons.add(ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(pluginType));
463          configAcceptable = false;
464      }
465    }
466
467    return configAcceptable;
468  }
469
470
471
472  /** {@inheritDoc} */
473  @Override
474  public ConfigChangeResult applyConfigurationChange(
475                                 SevenBitCleanPluginCfg configuration)
476  {
477    currentConfig = configuration;
478    return new ConfigChangeResult();
479  }
480}