001 /*
002 * Created by IntelliJ IDEA.
003 * User: d@hd.org
004 * Date: 27-May-02
005 * Time: 23:23:08
006
007 Copyright (c) 1996-2012, Damon Hart-Davis
008 All rights reserved.
009
010 Redistribution and use in source and binary forms, with or without
011 modification, are permitted provided that the following conditions are
012 met:
013
014 * Redistributions of source code must retain the above copyright
015 notice, this list of conditions and the following disclaimer.
016
017 * Redistributions in binary form must reproduce the above copyright
018 notice, this list of conditions and the following disclaimer in the
019 documentation and/or other materials provided with the
020 distribution.
021
022 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
023 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
024 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
025 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
026 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
027 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
028 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
032 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033
034 */
035 package org.hd.d.pg2k.svrCore.props;
036
037 import java.io.BufferedInputStream;
038 import java.io.File;
039 import java.io.FileInputStream;
040 import java.io.IOException;
041 import java.io.InputStream;
042 import java.util.Collections;
043 import java.util.Enumeration;
044 import java.util.HashMap;
045 import java.util.Map;
046 import java.util.Properties;
047
048 import org.hd.d.pg2k.svrCore.CoreConsts;
049 import org.hd.d.pg2k.svrCore.ExhibitName;
050 import org.hd.d.pg2k.svrCore.FileTools;
051
052 /**Encapsulates the simplepass password file.
053 * This can not be serialised and can only be constructed
054 * from the file indicated by LocalProps.getSimplePassfile().
055 */
056 public final class SimplepassProps
057 {
058 /**Returns true if the quoted password is correct for the given author.
059 * This can only return true if simplepass passwords are enabled
060 * and are set up correctly in LocalProps and in a simplepass file.
061 * <p>
062 * Will return false if either parameter is null or zero-length.
063 *
064 * @param auth the author initials; must be correct syntactically
065 * @param pass the password; must be of a valid length
066 * @return true if the quoted password is correct
067 */
068 public final static boolean isAuthorUploadPasswordCorrect(final String auth,
069 final String pass)
070 {
071 final SimplepassProps spp = getSimplepassProps();
072 // If no simplepass, always fail.
073 if(spp == null) { return(false); }
074 // Check against actual passwords.
075 return(spp.authorUploadPasswordCorrect(auth, pass));
076 }
077
078 /**The current singleton SimplepassProps object, if any.
079 * This may be null if USE_SIMPLEPASS is null or if there
080 * is no simplepass value set in LocalProps or there is no file.
081 */
082 private static SimplepassProps singleton;
083
084 /**Get the current SimplepassProps object, if any.
085 * This object normally exists as a singleton,
086 * being replaced only if the simplepass file changes
087 * and can be read.
088 */
089 private static synchronized SimplepassProps getSimplepassProps()
090 {
091 // If simple passwords are enabled, we don't create a singleton.
092 if(!USE_SIMPLEPASS) { return(null); }
093
094 final String passfileName = LocalProps.getSimplePassfile();
095 if(passfileName != null)
096 {
097 final File pf = new File(passfileName);
098 final long timestamp = pf.lastModified();
099
100 if(pf.isFile())
101 {
102 // If the singleton object may be out of sync,
103 // try to create a new one.
104 if((singleton == null) || (singleton.timestamp != timestamp))
105 {
106 try { singleton = new SimplepassProps(pf); }
107 catch(final IOException e)
108 {
109 // In case of error, dump the problem.
110 e.printStackTrace();
111 }
112 }
113 }
114 }
115
116 return(singleton);
117 }
118
119 /**Construct a new, immutable, properties set.
120 * The properties must be non-null
121 * and the timestamp must be non-negative.
122 * <p>
123 * Minor problems with the properties themselves will
124 * be silently ignored, and defaults substituted
125 * for broken or missing values.
126 * <p>
127 * This is only designed to be fetched by the factory method.
128 *
129 * @param pf is the passfile
130 */
131 private SimplepassProps(final File pf)
132 throws IOException
133 {
134 Properties props;
135
136 // Read the properties file in buffered chunks for efficiency.
137 final InputStream is = new BufferedInputStream(
138 new FileInputStream(pf));
139 try { props = FileTools.loadProperties(is); }
140 finally { is.close(); } // Release the handle ASAP...
141
142 // Capture the timestamp.
143 timestamp = pf.lastModified();
144
145 // Parse properties.
146
147 // Set up simplepassMap.
148 if(!USE_SIMPLEPASS) { simplepassMap = null; }
149 else
150 {
151 // Use a HashMap for speed of lookup,
152 // though speed is probably not critical.
153 final HashMap<String,String> hm = new HashMap<String,String>();
154 for(final Enumeration en = props.propertyNames(); en.hasMoreElements(); )
155 {
156 final String name = (String) en.nextElement();
157 if(name.startsWith(SIMPLEPASS_PREFIX))
158 {
159 // Extract author initials and ensure that they are valid.
160 final String author =
161 name.substring(SIMPLEPASS_PREFIX.length());
162 if(!ExhibitName.validAuthorSyntax(author))
163 {
164 System.err.println("ERROR: invalid simplepass author in: "+name);
165 continue;
166 }
167 final String pass = props.getProperty(name);
168 // We could validate the password syntax/length here...
169 if((pass.length() < CoreConsts.MIN_PASSWORD_LEN) ||
170 (pass.length() > CoreConsts.MAX_PASSWORD_LEN))
171 {
172 System.err.println("ERROR: invalid simplepass password in: "+name);
173 continue;
174 }
175 if(hm.put(author, pass) != null)
176 {
177 System.err.println("ERROR: duplicate simplepass author in: "+name);
178 continue;
179 }
180 }
181 }
182 // Save immutable reference to map.
183 simplepassMap = Collections.unmodifiableMap(hm);
184 }
185 }
186
187 /**Timestamp of this properties set. */
188 public final long timestamp;
189
190
191 /**If true, allow use of pg2k.upload.simplepass.XXX simple passwords.
192 * This is a primative password system for author exhibit uploads.
193 * <p>
194 * These will only be used in any case if a more advanced and safer
195 * password store is not available.
196 * <p>
197 * Note that these passwords are:
198 * <ul>
199 * <li>plaintext
200 * <li>not maintainable/changeable by the user
201 * <li>only updatable slowly (when the props are re-read)
202 * </ul>
203 * so should be considered generally unacceptable for normal use.
204 */
205 private static final boolean USE_SIMPLEPASS = true;
206
207 /**The "simplepass" prefix, including the trailing dot.
208 * This is followed by the author initials with the value
209 * being the password. The password value must meet constraints
210 * set elsewhere (in the code) such as maximum and minimum length.
211 */
212 private static final String SIMPLEPASS_PREFIX = "pass.";
213
214 /**The immutable private map from author initials to plaintext password.
215 * It is a shame that these will be floating around in the heap
216 * unprotected.
217 * <p>
218 * This map is threadsafe because it is read-only.
219 * <p>
220 * The map is String to String.
221 * <p>
222 * Will be null if USE_SIMPLEPASS is false.
223 * <p>
224 * We don't directly disclose passwords, just check if they are valid
225 * or not. We hold a lock on the password while checking
226 * a password, and if it is wrong we wait a while still holding
227 * the lock, to make dictionary attacks harder at the risk of making
228 * some denial of service attacks easier.
229 */
230 private final Map<String,String> simplepassMap;
231
232 /**How long to wait (ms) on an unsuccessful lookup. */
233 private static final int SIMPLEPASS_FAILURE_PAUSE = 2000;
234
235 /**Returns true if presented author upload password is correct.
236 * The author name must be valid and the password non-null and
237 * non-zero-length to avoid trivial typo-related attacks.
238 */
239 public final boolean authorUploadPasswordCorrect(final String auth, final String pass)
240 {
241 if(!ExhibitName.validAuthorSyntax(auth)) { return(false); }
242 if((pass == null) || (pass.length() == 0)) { return(false); }
243
244 final String p = simplepassMap.get(auth);
245
246 try {
247 // If author not present at all, just wait.
248 if(p == null)
249 { Thread.sleep(SIMPLEPASS_FAILURE_PAUSE); }
250 else
251 {
252 synchronized(pass)
253 {
254 if(p.equals(pass)) { return(true); } // Correct password!
255 Thread.sleep(SIMPLEPASS_FAILURE_PAUSE);
256 }
257 }
258 }
259 catch(final InterruptedException e) { } // Ignore.
260
261 return(false); // Wrong password.
262 }
263 }