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