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.extensions;
028 import org.opends.messages.Message;
029
030
031
032 import java.io.BufferedReader;
033 import java.io.File;
034 import java.io.FileReader;
035 import java.util.ArrayList;
036 import java.util.HashSet;
037 import java.util.List;
038 import java.util.Set;
039
040 import org.opends.server.admin.server.ConfigurationChangeListener;
041 import org.opends.server.admin.std.server.DictionaryPasswordValidatorCfg;
042 import org.opends.server.admin.std.server.PasswordValidatorCfg;
043 import org.opends.server.api.PasswordValidator;
044 import org.opends.server.config.ConfigException;
045 import org.opends.server.types.ConfigChangeResult;
046 import org.opends.server.types.ByteString;
047 import org.opends.server.types.DebugLogLevel;
048 import org.opends.server.types.DirectoryConfig;
049 import org.opends.server.types.Entry;
050 import org.opends.server.types.InitializationException;
051 import org.opends.server.types.Operation;
052 import org.opends.server.types.ResultCode;
053
054 import static org.opends.server.loggers.debug.DebugLogger.*;
055 import org.opends.server.loggers.debug.DebugTracer;
056 import static org.opends.messages.ExtensionMessages.*;
057 import org.opends.messages.MessageBuilder;
058 import static org.opends.server.util.StaticUtils.*;
059
060
061
062 /**
063 * This class provides an OpenDS password validator that may be used to ensure
064 * that proposed passwords are not contained in a specified dictionary.
065 */
066 public class DictionaryPasswordValidator
067 extends PasswordValidator<DictionaryPasswordValidatorCfg>
068 implements ConfigurationChangeListener<DictionaryPasswordValidatorCfg>
069 {
070 /**
071 * The tracer object for the debug logger.
072 */
073 private static final DebugTracer TRACER = getTracer();
074
075 // The current configuration for this password validator.
076 private DictionaryPasswordValidatorCfg currentConfig;
077
078 // The current dictionary that we should use when performing the validation.
079 private HashSet<String> dictionary;
080
081
082
083 /**
084 * Creates a new instance of this dictionary password validator.
085 */
086 public DictionaryPasswordValidator()
087 {
088 super();
089
090 // No implementation is required here. All initialization should be
091 // performed in the initializePasswordValidator() method.
092 }
093
094
095
096 /**
097 * {@inheritDoc}
098 */
099 @Override()
100 public void initializePasswordValidator(
101 DictionaryPasswordValidatorCfg configuration)
102 throws ConfigException, InitializationException
103 {
104 configuration.addDictionaryChangeListener(this);
105 currentConfig = configuration;
106
107 dictionary = loadDictionary(configuration);
108 }
109
110
111
112 /**
113 * {@inheritDoc}
114 */
115 @Override()
116 public void finalizePasswordValidator()
117 {
118 currentConfig.removeDictionaryChangeListener(this);
119 }
120
121
122
123 /**
124 * {@inheritDoc}
125 */
126 @Override()
127 public boolean passwordIsAcceptable(ByteString newPassword,
128 Set<ByteString> currentPasswords,
129 Operation operation, Entry userEntry,
130 MessageBuilder invalidReason)
131 {
132 // Get a handle to the current configuration.
133 DictionaryPasswordValidatorCfg config = currentConfig;
134 HashSet<String> dictionary = this.dictionary;
135
136
137 // Check to see if the provided password is in the dictionary in the order
138 // that it was provided.
139 String password = newPassword.stringValue();
140 if (! config.isCaseSensitiveValidation())
141 {
142 password = toLowerCase(password);
143 }
144
145 if (dictionary.contains(password))
146 {
147 invalidReason.append(
148 ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
149 return false;
150 }
151
152
153 // If we should try the reversed value, then do that as well.
154 if (config.isTestReversedPassword())
155 {
156 if (dictionary.contains(new StringBuilder(password).reverse().toString()))
157 {
158 invalidReason.append(
159 ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
160 return false;
161 }
162 }
163
164
165 // If we've gotten here, then the password is acceptable.
166 return true;
167 }
168
169
170
171 /**
172 * Loads the configured dictionary and returns it as a hash set.
173 *
174 * @param configuration the configuration for this password validator.
175 *
176 * @return The hash set containing the loaded dictionary data.
177 *
178 * @throws ConfigException If the configured dictionary file does not exist.
179 *
180 * @throws InitializationException If a problem occurs while attempting to
181 * read from the dictionary file.
182 */
183 private HashSet<String> loadDictionary(
184 DictionaryPasswordValidatorCfg configuration)
185 throws ConfigException, InitializationException
186 {
187 // Get the path to the dictionary file and make sure it exists.
188 File dictionaryFile = getFileForPath(configuration.getDictionaryFile());
189 if (! dictionaryFile.exists())
190 {
191 Message message = ERR_DICTIONARY_VALIDATOR_NO_SUCH_FILE.get(
192 configuration.getDictionaryFile());
193 throw new ConfigException(message);
194 }
195
196
197 // Read the contents of file into the dictionary as per the configuration.
198 BufferedReader reader = null;
199 HashSet<String> dictionary = new HashSet<String>();
200 try
201 {
202 reader = new BufferedReader(new FileReader(dictionaryFile));
203 String line = reader.readLine();
204 while (line != null)
205 {
206 if (! configuration.isCaseSensitiveValidation())
207 {
208 line = line.toLowerCase();
209 }
210
211 dictionary.add(line);
212 line = reader.readLine();
213 }
214 }
215 catch (Exception e)
216 {
217 if (debugEnabled())
218 {
219 TRACER.debugCaught(DebugLogLevel.ERROR, e);
220 }
221
222 Message message = ERR_DICTIONARY_VALIDATOR_CANNOT_READ_FILE.get(
223 configuration.getDictionaryFile(), String.valueOf(e));
224 throw new InitializationException(message);
225 }
226 finally
227 {
228 if (reader != null)
229 {
230 try
231 {
232 reader.close();
233 } catch (Exception e) {}
234 }
235 }
236
237 return dictionary;
238 }
239
240
241
242 /**
243 * {@inheritDoc}
244 */
245 @Override()
246 public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration,
247 List<Message> unacceptableReasons)
248 {
249 DictionaryPasswordValidatorCfg config =
250 (DictionaryPasswordValidatorCfg) configuration;
251 return isConfigurationChangeAcceptable(config, unacceptableReasons);
252 }
253
254
255
256 /**
257 * {@inheritDoc}
258 */
259 public boolean isConfigurationChangeAcceptable(
260 DictionaryPasswordValidatorCfg configuration,
261 List<Message> unacceptableReasons)
262 {
263 // Make sure that we can load the dictionary. If so, then we'll accept the
264 // new configuration.
265 try
266 {
267 loadDictionary(configuration);
268 }
269 catch (ConfigException ce)
270 {
271 unacceptableReasons.add(ce.getMessageObject());
272 return false;
273 }
274 catch (InitializationException ie)
275 {
276 unacceptableReasons.add(ie.getMessageObject());
277 return false;
278 }
279 catch (Exception e)
280 {
281 unacceptableReasons.add(getExceptionMessage(e));
282 return false;
283 }
284
285 return true;
286 }
287
288
289
290 /**
291 * {@inheritDoc}
292 */
293 public ConfigChangeResult applyConfigurationChange(
294 DictionaryPasswordValidatorCfg configuration)
295 {
296 ResultCode resultCode = ResultCode.SUCCESS;
297 boolean adminActionRequired = false;
298 ArrayList<Message> messages = new ArrayList<Message>();
299
300
301 // Make sure we can load the dictionary. If we can, then activate the new
302 // configuration.
303 try
304 {
305 dictionary = loadDictionary(configuration);
306 currentConfig = configuration;
307 }
308 catch (Exception e)
309 {
310 resultCode = DirectoryConfig.getServerErrorResultCode();
311 messages.add(getExceptionMessage(e));
312 }
313
314 return new ConfigChangeResult(resultCode, adminActionRequired, messages);
315 }
316 }
317