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