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