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