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}