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}