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