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 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 029 030 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.List; 034 import java.util.SortedSet; 035 import java.util.StringTokenizer; 036 037 import org.opends.messages.Message; 038 import org.opends.server.admin.server.ConfigurationChangeListener; 039 import org.opends.server.admin.std.server.PasswordGeneratorCfg; 040 import org.opends.server.admin.std.server.RandomPasswordGeneratorCfg; 041 import org.opends.server.api.PasswordGenerator; 042 import org.opends.server.config.ConfigException; 043 import org.opends.server.core.DirectoryServer; 044 import org.opends.server.loggers.debug.DebugTracer; 045 import org.opends.server.types.ByteString; 046 import org.opends.server.types.ByteStringFactory; 047 import org.opends.server.types.ConfigChangeResult; 048 import org.opends.server.types.DebugLogLevel; 049 import org.opends.server.types.DirectoryException; 050 import org.opends.server.types.DN; 051 import org.opends.server.types.Entry; 052 import org.opends.server.types.InitializationException; 053 import org.opends.server.types.NamedCharacterSet; 054 import org.opends.server.types.ResultCode; 055 056 import static org.opends.messages.ExtensionMessages.*; 057 import static org.opends.server.loggers.debug.DebugLogger.*; 058 import static org.opends.server.util.StaticUtils.*; 059 060 061 062 /** 063 * This class provides an implementation of a Directory Server password 064 * generator that will create random passwords based on fixed-length strings 065 * built from one or more character sets. 066 */ 067 public class RandomPasswordGenerator 068 extends PasswordGenerator<RandomPasswordGeneratorCfg> 069 implements ConfigurationChangeListener<RandomPasswordGeneratorCfg> 070 { 071 /** 072 * The tracer object for the debug logger. 073 */ 074 private static final DebugTracer TRACER = getTracer(); 075 076 077 // The current configuration for this password validator. 078 private RandomPasswordGeneratorCfg currentConfig; 079 080 // The encoded list of character sets defined for this password generator. 081 private SortedSet<String> encodedCharacterSets; 082 083 // The DN of the configuration entry for this password generator. 084 private DN configEntryDN; 085 086 // The total length of the password that will be generated. 087 private int totalLength; 088 089 // The numbers of characters of each type that should be used to generate the 090 // passwords. 091 private int[] characterCounts; 092 093 // The character sets that should be used to generate the passwords. 094 private NamedCharacterSet[] characterSets; 095 096 // The lock to use to ensure that the character sets and counts are not 097 // altered while a password is being generated. 098 private Object generatorLock; 099 100 // The character set format string for this password generator. 101 private String formatString; 102 103 104 105 /** 106 * {@inheritDoc} 107 */ 108 @Override() 109 public void initializePasswordGenerator( 110 RandomPasswordGeneratorCfg configuration) 111 throws ConfigException, InitializationException 112 { 113 this.configEntryDN = configuration.dn(); 114 generatorLock = new Object(); 115 116 // Get the character sets for use in generating the password. At least one 117 // must have been provided. 118 HashMap<String,NamedCharacterSet> charsets = 119 new HashMap<String,NamedCharacterSet>(); 120 121 try 122 { 123 encodedCharacterSets = configuration.getPasswordCharacterSet(); 124 125 if (encodedCharacterSets.size() == 0) 126 { 127 Message message = 128 ERR_RANDOMPWGEN_NO_CHARSETS.get(String.valueOf(configEntryDN)); 129 throw new ConfigException(message); 130 } 131 for (NamedCharacterSet s : NamedCharacterSet 132 .decodeCharacterSets(encodedCharacterSets)) 133 { 134 if (charsets.containsKey(s.getName())) 135 { 136 Message message = ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get( 137 String.valueOf(configEntryDN), s.getName()); 138 throw new ConfigException(message); 139 } 140 else 141 { 142 charsets.put(s.getName(), s); 143 } 144 } 145 } 146 catch (ConfigException ce) 147 { 148 throw ce; 149 } 150 catch (Exception e) 151 { 152 if (debugEnabled()) 153 { 154 TRACER.debugCaught(DebugLogLevel.ERROR, e); 155 } 156 157 Message message = 158 ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get(getExceptionMessage(e)); 159 throw new InitializationException(message, e); 160 } 161 162 163 // Get the value that describes which character set(s) and how many 164 // characters from each should be used. 165 166 try 167 { 168 formatString = configuration.getPasswordFormat(); 169 StringTokenizer tokenizer = new StringTokenizer(formatString, ", "); 170 171 ArrayList<NamedCharacterSet> setList = new ArrayList<NamedCharacterSet>(); 172 ArrayList<Integer> countList = new ArrayList<Integer>(); 173 174 while (tokenizer.hasMoreTokens()) 175 { 176 String token = tokenizer.nextToken(); 177 178 try 179 { 180 int colonPos = token.indexOf(':'); 181 String name = token.substring(0, colonPos); 182 int count = Integer.parseInt(token.substring(colonPos + 1)); 183 184 NamedCharacterSet charset = charsets.get(name); 185 if (charset == null) 186 { 187 Message message = ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get( 188 String.valueOf(formatString), String.valueOf(name)); 189 throw new ConfigException(message); 190 } 191 else 192 { 193 setList.add(charset); 194 countList.add(count); 195 } 196 } 197 catch (ConfigException ce) 198 { 199 throw ce; 200 } 201 catch (Exception e) 202 { 203 if (debugEnabled()) 204 { 205 TRACER.debugCaught(DebugLogLevel.ERROR, e); 206 } 207 208 Message message = ERR_RANDOMPWGEN_INVALID_PWFORMAT.get( 209 String.valueOf(formatString)); 210 throw new ConfigException(message, e); 211 } 212 } 213 214 characterSets = new NamedCharacterSet[setList.size()]; 215 characterCounts = new int[characterSets.length]; 216 217 totalLength = 0; 218 for (int i = 0; i < characterSets.length; i++) 219 { 220 characterSets[i] = setList.get(i); 221 characterCounts[i] = countList.get(i); 222 totalLength += characterCounts[i]; 223 } 224 } 225 catch (ConfigException ce) 226 { 227 throw ce; 228 } 229 catch (Exception e) 230 { 231 if (debugEnabled()) 232 { 233 TRACER.debugCaught(DebugLogLevel.ERROR, e); 234 } 235 236 Message message = 237 ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get(getExceptionMessage(e)); 238 throw new InitializationException(message, e); 239 } 240 241 configuration.addRandomChangeListener(this) ; 242 currentConfig = configuration; 243 } 244 245 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override() 251 public void finalizePasswordGenerator() 252 { 253 currentConfig.removeRandomChangeListener(this); 254 } 255 256 257 258 /** 259 * Generates a password for the user whose account is contained in the 260 * specified entry. 261 * 262 * @param userEntry The entry for the user for whom the password is to be 263 * generated. 264 * 265 * @return The password that has been generated for the user. 266 * 267 * @throws DirectoryException If a problem occurs while attempting to 268 * generate the password. 269 */ 270 public ByteString generatePassword(Entry userEntry) 271 throws DirectoryException 272 { 273 StringBuilder buffer = new StringBuilder(totalLength); 274 275 synchronized (generatorLock) 276 { 277 for (int i=0; i < characterSets.length; i++) 278 { 279 characterSets[i].getRandomCharacters(buffer, characterCounts[i]); 280 } 281 } 282 283 return ByteStringFactory.create(buffer.toString()); 284 } 285 286 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override() 292 public boolean isConfigurationAcceptable(PasswordGeneratorCfg configuration, 293 List<Message> unacceptableReasons) 294 { 295 RandomPasswordGeneratorCfg config = 296 (RandomPasswordGeneratorCfg) configuration; 297 return isConfigurationChangeAcceptable(config, unacceptableReasons); 298 } 299 300 301 302 /** 303 * {@inheritDoc} 304 */ 305 public boolean isConfigurationChangeAcceptable( 306 RandomPasswordGeneratorCfg configuration, 307 List<Message> unacceptableReasons) 308 { 309 DN cfgEntryDN = configuration.dn(); 310 311 // Get the character sets for use in generating the password. At 312 // least one 313 // must have been provided. 314 HashMap<String,NamedCharacterSet> charsets = 315 new HashMap<String,NamedCharacterSet>(); 316 try 317 { 318 SortedSet<String> currentPasSet = configuration.getPasswordCharacterSet(); 319 if (currentPasSet.size() == 0) 320 { 321 Message message = 322 ERR_RANDOMPWGEN_NO_CHARSETS.get(String.valueOf(cfgEntryDN)); 323 throw new ConfigException(message); 324 } 325 326 for (NamedCharacterSet s : NamedCharacterSet 327 .decodeCharacterSets(currentPasSet)) 328 { 329 if (charsets.containsKey(s.getName())) 330 { 331 Message message = ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get( 332 String.valueOf(cfgEntryDN), s.getName()); 333 unacceptableReasons.add(message); 334 return false; 335 } 336 else 337 { 338 charsets.put(s.getName(), s); 339 } 340 } 341 } 342 catch (ConfigException ce) 343 { 344 unacceptableReasons.add(ce.getMessageObject()); 345 return false; 346 } 347 catch (Exception e) 348 { 349 if (debugEnabled()) 350 { 351 TRACER.debugCaught(DebugLogLevel.ERROR, e); 352 } 353 354 Message message = ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get( 355 getExceptionMessage(e)); 356 unacceptableReasons.add(message); 357 return false; 358 } 359 360 361 // Get the value that describes which character set(s) and how many 362 // characters from each should be used. 363 try 364 { 365 String formatString = configuration.getPasswordFormat() ; 366 StringTokenizer tokenizer = new StringTokenizer(formatString, ", "); 367 368 while (tokenizer.hasMoreTokens()) 369 { 370 String token = tokenizer.nextToken(); 371 372 try 373 { 374 int colonPos = token.indexOf(':'); 375 String name = token.substring(0, colonPos); 376 int count = Integer.parseInt(token.substring(colonPos+1)); 377 378 NamedCharacterSet charset = charsets.get(name); 379 if (charset == null) 380 { 381 Message message = ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get( 382 String.valueOf(formatString), String.valueOf(name)); 383 unacceptableReasons.add(message); 384 return false; 385 } 386 } 387 catch (Exception e) 388 { 389 if (debugEnabled()) 390 { 391 TRACER.debugCaught(DebugLogLevel.ERROR, e); 392 } 393 394 Message message = ERR_RANDOMPWGEN_INVALID_PWFORMAT.get( 395 String.valueOf(formatString)); 396 unacceptableReasons.add(message); 397 return false; 398 } 399 } 400 } 401 catch (Exception e) 402 { 403 if (debugEnabled()) 404 { 405 TRACER.debugCaught(DebugLogLevel.ERROR, e); 406 } 407 408 Message message = ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get( 409 getExceptionMessage(e)); 410 unacceptableReasons.add(message); 411 return false; 412 } 413 414 415 // If we've gotten here, then everything looks OK. 416 return true; 417 } 418 419 420 421 /** 422 * {@inheritDoc} 423 */ 424 public ConfigChangeResult applyConfigurationChange( 425 RandomPasswordGeneratorCfg configuration) 426 { 427 ResultCode resultCode = ResultCode.SUCCESS; 428 boolean adminActionRequired = false; 429 ArrayList<Message> messages = new ArrayList<Message>(); 430 431 432 // Get the character sets for use in generating the password. At least one 433 // must have been provided. 434 SortedSet<String> newEncodedCharacterSets = null; 435 HashMap<String,NamedCharacterSet> charsets = 436 new HashMap<String,NamedCharacterSet>(); 437 try 438 { 439 newEncodedCharacterSets = configuration.getPasswordCharacterSet(); 440 if (newEncodedCharacterSets.size() == 0) 441 { 442 messages.add(ERR_RANDOMPWGEN_NO_CHARSETS.get( 443 String.valueOf(configEntryDN))); 444 445 if (resultCode == ResultCode.SUCCESS) 446 { 447 resultCode = ResultCode.OBJECTCLASS_VIOLATION; 448 } 449 } 450 else 451 { 452 for (NamedCharacterSet s : 453 NamedCharacterSet.decodeCharacterSets(newEncodedCharacterSets)) 454 { 455 if (charsets.containsKey(s.getName())) 456 { 457 messages.add(ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get( 458 String.valueOf(configEntryDN), 459 s.getName())); 460 461 if (resultCode == ResultCode.SUCCESS) 462 { 463 resultCode = ResultCode.CONSTRAINT_VIOLATION; 464 } 465 } 466 else 467 { 468 charsets.put(s.getName(), s); 469 } 470 } 471 } 472 } 473 catch (ConfigException ce) 474 { 475 messages.add(ce.getMessageObject()); 476 477 if (resultCode == ResultCode.SUCCESS) 478 { 479 resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX; 480 } 481 } 482 catch (Exception e) 483 { 484 if (debugEnabled()) 485 { 486 TRACER.debugCaught(DebugLogLevel.ERROR, e); 487 } 488 489 messages.add(ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get( 490 getExceptionMessage(e))); 491 492 if (resultCode == ResultCode.SUCCESS) 493 { 494 resultCode = DirectoryServer.getServerErrorResultCode(); 495 } 496 } 497 498 499 // Get the value that describes which character set(s) and how many 500 // characters from each should be used. 501 ArrayList<NamedCharacterSet> newSetList = 502 new ArrayList<NamedCharacterSet>(); 503 ArrayList<Integer> newCountList = new ArrayList<Integer>(); 504 String newFormatString = null; 505 506 try 507 { 508 newFormatString = configuration.getPasswordFormat(); 509 StringTokenizer tokenizer = new StringTokenizer(newFormatString, ", "); 510 511 while (tokenizer.hasMoreTokens()) 512 { 513 String token = tokenizer.nextToken(); 514 515 try 516 { 517 int colonPos = token.indexOf(':'); 518 String name = token.substring(0, colonPos); 519 int count = Integer.parseInt(token.substring(colonPos + 1)); 520 521 NamedCharacterSet charset = charsets.get(name); 522 if (charset == null) 523 { 524 messages.add(ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get( 525 String.valueOf(newFormatString), 526 String.valueOf(name))); 527 528 if (resultCode == ResultCode.SUCCESS) 529 { 530 resultCode = ResultCode.CONSTRAINT_VIOLATION; 531 } 532 } 533 else 534 { 535 newSetList.add(charset); 536 newCountList.add(count); 537 } 538 } 539 catch (Exception e) 540 { 541 if (debugEnabled()) 542 { 543 TRACER.debugCaught(DebugLogLevel.ERROR, e); 544 } 545 546 messages.add(ERR_RANDOMPWGEN_INVALID_PWFORMAT.get( 547 String.valueOf(newFormatString))); 548 549 if (resultCode == ResultCode.SUCCESS) 550 { 551 resultCode = DirectoryServer.getServerErrorResultCode(); 552 } 553 } 554 } 555 } 556 catch (Exception e) 557 { 558 if (debugEnabled()) 559 { 560 TRACER.debugCaught(DebugLogLevel.ERROR, e); 561 } 562 563 messages.add(ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get( 564 getExceptionMessage(e))); 565 566 if (resultCode == ResultCode.SUCCESS) 567 { 568 resultCode = DirectoryServer.getServerErrorResultCode(); 569 } 570 } 571 572 573 // If everything looks OK, then apply the changes. 574 if (resultCode == ResultCode.SUCCESS) 575 { 576 synchronized (generatorLock) 577 { 578 encodedCharacterSets = newEncodedCharacterSets; 579 formatString = newFormatString; 580 581 characterSets = new NamedCharacterSet[newSetList.size()]; 582 characterCounts = new int[characterSets.length]; 583 584 totalLength = 0; 585 for (int i=0; i < characterCounts.length; i++) 586 { 587 characterSets[i] = newSetList.get(i); 588 characterCounts[i] = newCountList.get(i); 589 totalLength += characterCounts[i]; 590 } 591 } 592 } 593 594 595 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 596 } 597 } 598