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.forgerock.opendj.config.server.ConfigurationChangeListener; 036import org.forgerock.opendj.server.config.meta.PluginCfgDefn; 037import org.forgerock.opendj.server.config.server.SevenBitCleanPluginCfg; 038import org.forgerock.opendj.server.config.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.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 @Override 081 public final void initializePlugin(Set<PluginType> pluginTypes, 082 SevenBitCleanPluginCfg configuration) 083 throws ConfigException 084 { 085 currentConfig = configuration; 086 configuration.addSevenBitCleanChangeListener(this); 087 088 // Make sure that the plugin has been enabled for the appropriate types. 089 for (PluginType t : pluginTypes) 090 { 091 switch (t) 092 { 093 case LDIF_IMPORT: 094 case PRE_PARSE_ADD: 095 case PRE_PARSE_MODIFY: 096 case PRE_PARSE_MODIFY_DN: 097 // These are acceptable. 098 break; 099 100 default: 101 throw new ConfigException(ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(t)); 102 } 103 } 104 } 105 106 @Override 107 public final void finalizePlugin() 108 { 109 currentConfig.removeSevenBitCleanChangeListener(this); 110 } 111 112 @Override 113 public final PluginResult.ImportLDIF 114 doLDIFImport(LDIFImportConfig importConfig, Entry entry) 115 { 116 // Get the current configuration for this plugin. 117 SevenBitCleanPluginCfg config = currentConfig; 118 119 // Make sure that the entry is within the scope of this plugin. While 120 // processing an LDIF import, we don't have access to the set of public 121 // naming contexts defined in the server, so if no explicit set of base DNs 122 // is defined, then assume that the entry is in scope. 123 if (!isDescendantOfAny(entry.getName(), config.getBaseDN())) 124 { 125 // The entry is out of scope, so we won't process it. 126 return PluginResult.ImportLDIF.continueEntryProcessing(); 127 } 128 129 // Make sure all configured attributes have clean values. 130 for (AttributeType t : config.getAttributeType()) 131 { 132 for (Attribute a : entry.getAttribute(t)) 133 { 134 for (ByteString v : a) 135 { 136 if (!is7BitClean(v)) 137 { 138 LocalizableMessage rejectMessage = 139 ERR_PLUGIN_7BIT_IMPORT_ATTR_NOT_CLEAN.get(a.getAttributeDescription()); 140 return PluginResult.ImportLDIF.stopEntryProcessing(rejectMessage); 141 } 142 } 143 } 144 } 145 146 // If we've gotten here, then everything is acceptable. 147 return PluginResult.ImportLDIF.continueEntryProcessing(); 148 } 149 150 @Override 151 public final PluginResult.PreParse 152 doPreParse(PreParseAddOperation addOperation) 153 { 154 // Get the current configuration for this plugin. 155 SevenBitCleanPluginCfg config = currentConfig; 156 157 // If the entry is within the scope of this plugin, then make sure all 158 // configured attributes have clean values. 159 DN entryDN; 160 try 161 { 162 entryDN = DN.valueOf(addOperation.getRawEntryDN()); 163 } 164 catch (LocalizedIllegalArgumentException e) 165 { 166 return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX, 167 ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject())); 168 } 169 170 if (isInScope(config, entryDN)) 171 { 172 for (RawAttribute rawAttr : addOperation.getRawAttributes()) 173 { 174 Attribute a; 175 try 176 { 177 a = rawAttr.toAttribute(); 178 } 179 catch (LDAPException le) 180 { 181 return PluginResult.PreParse.stopProcessing( 182 ResultCode.valueOf(le.getResultCode()), 183 ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get( 184 rawAttr.getAttributeType(), le.getErrorMessage())); 185 } 186 187 if (!config.getAttributeType().contains(a.getAttributeDescription().getAttributeType())) 188 { 189 continue; 190 } 191 192 for (ByteString v : a) 193 { 194 if (!is7BitClean(v)) 195 { 196 return PluginResult.PreParse.stopProcessing( 197 ResultCode.CONSTRAINT_VIOLATION, 198 ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get( 199 rawAttr.getAttributeType())); 200 } 201 } 202 } 203 } 204 205 // If we've gotten here, then everything is acceptable. 206 return PluginResult.PreParse.continueOperationProcessing(); 207 } 208 209 @Override 210 public final PluginResult.PreParse 211 doPreParse(PreParseModifyOperation modifyOperation) 212 { 213 // Get the current configuration for this plugin. 214 SevenBitCleanPluginCfg config = currentConfig; 215 216 // If the target entry is within the scope of this plugin, then make sure 217 // all values that will be added during the modification will be acceptable. 218 DN entryDN; 219 try 220 { 221 entryDN = DN.valueOf(modifyOperation.getRawEntryDN()); 222 } 223 catch (LocalizedIllegalArgumentException e) 224 { 225 return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX, 226 ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject())); 227 } 228 229 if (isInScope(config, entryDN)) 230 { 231 for (RawModification m : modifyOperation.getRawModifications()) 232 { 233 switch (m.getModificationType().asEnum()) 234 { 235 case ADD: 236 case REPLACE: 237 // These are modification types that we will process. 238 break; 239 default: 240 // This is not a modification type that we will process. 241 continue; 242 } 243 244 RawAttribute rawAttr = m.getAttribute(); 245 Attribute a; 246 try 247 { 248 a = rawAttr.toAttribute(); 249 } 250 catch (LDAPException le) 251 { 252 return PluginResult.PreParse.stopProcessing( 253 ResultCode.valueOf(le.getResultCode()), 254 ERR_PLUGIN_7BIT_CANNOT_DECODE_ATTR.get( 255 rawAttr.getAttributeType(), le.getErrorMessage())); 256 } 257 258 if (!config.getAttributeType().contains(a.getAttributeDescription().getAttributeType())) 259 { 260 continue; 261 } 262 263 for (ByteString v : a) 264 { 265 if (!is7BitClean(v)) 266 { 267 return PluginResult.PreParse.stopProcessing( 268 ResultCode.CONSTRAINT_VIOLATION, 269 ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get( 270 rawAttr.getAttributeType())); 271 } 272 } 273 } 274 } 275 276 // If we've gotten here, then everything is acceptable. 277 return PluginResult.PreParse.continueOperationProcessing(); 278 } 279 280 @Override 281 public final PluginResult.PreParse 282 doPreParse(PreParseModifyDNOperation modifyDNOperation) 283 { 284 // Get the current configuration for this plugin. 285 SevenBitCleanPluginCfg config = currentConfig; 286 287 // If the target entry is within the scope of this plugin, then make sure 288 // all values that will be added during the modification will be acceptable. 289 DN entryDN; 290 try 291 { 292 entryDN = DN.valueOf(modifyDNOperation.getRawEntryDN()); 293 } 294 catch (LocalizedIllegalArgumentException e) 295 { 296 return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX, 297 ERR_PLUGIN_7BIT_CANNOT_DECODE_DN.get(e.getMessageObject())); 298 } 299 300 if (isInScope(config, entryDN)) 301 { 302 ByteString rawNewRDN = modifyDNOperation.getRawNewRDN(); 303 304 RDN newRDN; 305 try 306 { 307 newRDN = RDN.valueOf(rawNewRDN.toString()); 308 } 309 catch (LocalizedIllegalArgumentException e) 310 { 311 return PluginResult.PreParse.stopProcessing(ResultCode.INVALID_DN_SYNTAX, 312 ERR_PLUGIN_7BIT_CANNOT_DECODE_NEW_RDN.get(e.getMessageObject())); 313 } 314 315 for (AVA ava : newRDN) 316 { 317 if (!config.getAttributeType().contains(ava.getAttributeType())) 318 { 319 continue; 320 } 321 322 if (!is7BitClean(ava.getAttributeValue())) 323 { 324 return PluginResult.PreParse.stopProcessing( 325 ResultCode.CONSTRAINT_VIOLATION, 326 ERR_PLUGIN_7BIT_MODIFYDN_ATTR_NOT_CLEAN.get(ava.getAttributeName())); 327 } 328 } 329 } 330 331 // If we've gotten here, then everything is acceptable. 332 return PluginResult.PreParse.continueOperationProcessing(); 333 } 334 335 /** 336 * Indicates whether the provided DN is within the scope of this plugin. 337 * 338 * @param config The configuration to use when making the determination. 339 * @param dn The DN for which to make the determination. 340 * 341 * @return {@code true} if the provided DN is within the scope of this 342 * plugin, or {@code false} if not. 343 */ 344 private final boolean isInScope(SevenBitCleanPluginCfg config, DN dn) 345 { 346 Set<DN> baseDNs = config.getBaseDN(); 347 if (baseDNs == null || baseDNs.isEmpty()) 348 { 349 baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 350 } 351 return isDescendantOfAny(dn, baseDNs); 352 } 353 354 private boolean isDescendantOfAny(DN dn, Set<DN> baseDNs) 355 { 356 if (baseDNs != null) 357 { 358 for (DN baseDN: baseDNs) 359 { 360 if (dn.isSubordinateOrEqualTo(baseDN)) 361 { 362 return true; 363 } 364 } 365 } 366 return false; 367 } 368 369 /** 370 * Indicates whether the provided value is 7-bit clean. 371 * 372 * @param value The value for which to make the determination. 373 * 374 * @return {@code true} if the provided value is 7-bit clean, or {@code false} 375 * if it is not. 376 */ 377 private final boolean is7BitClean(ByteSequence value) 378 { 379 for (int i = 0; i < value.length(); i++) 380 { 381 byte b = value.byteAt(i); 382 if ((b & MASK) != b) 383 { 384 return false; 385 } 386 } 387 return true; 388 } 389 390 @Override 391 public boolean isConfigurationAcceptable(PluginCfg configuration, 392 List<LocalizableMessage> unacceptableReasons) 393 { 394 SevenBitCleanPluginCfg cfg = (SevenBitCleanPluginCfg) configuration; 395 return isConfigurationChangeAcceptable(cfg, unacceptableReasons); 396 } 397 398 @Override 399 public boolean isConfigurationChangeAcceptable( 400 SevenBitCleanPluginCfg configuration, 401 List<LocalizableMessage> unacceptableReasons) 402 { 403 boolean configAcceptable = true; 404 405 // Ensure that the set of plugin types is acceptable. 406 for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType()) 407 { 408 switch (pluginType) 409 { 410 case LDIFIMPORT: 411 case PREPARSEADD: 412 case PREPARSEMODIFY: 413 case PREPARSEMODIFYDN: 414 // These are acceptable. 415 break; 416 417 default: 418 unacceptableReasons.add(ERR_PLUGIN_7BIT_INVALID_PLUGIN_TYPE.get(pluginType)); 419 configAcceptable = false; 420 } 421 } 422 423 return configAcceptable; 424 } 425 426 @Override 427 public ConfigChangeResult applyConfigurationChange( 428 SevenBitCleanPluginCfg configuration) 429 { 430 currentConfig = configuration; 431 return new ConfigChangeResult(); 432 } 433}