001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.props;
030
031 import java.io.BufferedInputStream;
032 import java.io.File;
033 import java.io.FileInputStream;
034 import java.io.IOException;
035 import java.net.InetAddress;
036 import java.util.Enumeration;
037 import java.util.Properties;
038 import java.util.StringTokenizer;
039
040 import org.hd.d.pg2k.svrCore.CoreConsts;
041 import org.hd.d.pg2k.svrCore.FileTools;
042
043 /**This holds some security properties for the server.
044 * These data are potentially sensitive, such as authentication keys for slaves,
045 * but not massively secret (such as personal data), but we take care not to
046 * ship them across any public network.
047 * We also make it a little harder to get at some of
048 * the security information even when in possession of
049 * an instance of this object.
050 * <p>
051 * We are prepared to hand out a (read-only) copy of any properties
052 * with prefix GENSECPROPS_PREFIX for pluggable security
053 * components to examine. This data can be shipped over
054 * the network, but should be protected if possible.
055 * <p>
056 * FIXME: Should do validation on deserialisation.
057 */
058 public final class SecurityProps implements java.io.Serializable
059 {
060 /**Construct a default, zero-timestamp set of generic properties.
061 */
062 public SecurityProps()
063 { this(new Properties(), 0); }
064
065 /**Construct a new, immutable, properties set.
066 * The properties must be non-null
067 * and the timestamp must be non-negative.
068 * <p>
069 * Minor problems with the properties themselves will
070 * be silently ignored, and defaults substituted
071 * for broken or missing values.
072 */
073 public SecurityProps(final Properties props, final long _timestamp)
074 {
075 if((props == null) || (_timestamp < 0))
076 { throw new IllegalArgumentException(); }
077 timestamp = _timestamp;
078
079 // Parse properties.
080 // Set up some working variable to help with parsing.
081 // int iTmp;
082 String sTmp;
083
084 // iTmp = -1;
085 // try { iTmp = Integer.parseInt(props.getProperty(PNAME_WEBSVR_MIN_EX_IMATTR_RECHECK_MS, "120000"), 10); }
086 // catch(Exception e) { } // Ignore errors.
087 // // Constrain to range approx 1s to 6h, default 2 mins.
088 // WEBSVR_MIN_EX_IMATTR_RECHECK_MS = Math.max(1001, Math.min(6 * 3600 * 1000, iTmp));
089
090 sTmp = null;
091 try { sTmp = props.getProperty(PNAME_TUNNEL_CLIENT_LIST, "").trim(); }
092 catch(final Exception e) { } // Ignore errors.
093 // Default is no clients accepted.
094 TUNNEL_CLIENT_LIST = sTmp;
095
096
097 // Set up GENSECPROPS_PREFIX set.
098 final Properties gs = new Properties();
099 for(final Enumeration en = props.propertyNames(); en.hasMoreElements(); )
100 {
101 final String name = (String) en.nextElement();
102 if(name.startsWith(GENSECPROPS_PREFIX))
103 { gs.put(name, props.getProperty(name)); }
104 }
105 gs.put(PNAME_GENSECPROPS_TIMESTAMP, String.valueOf(_timestamp));
106 gensec = gs;
107 }
108
109 /**Timestamp of this properties set. */
110 public final long timestamp;
111
112 /**Get (private) security properties from filesystem.
113 * This uses LocalProps to get the root of the file area.
114 * <p>
115 * This does not cache its result but will return null if the stamp
116 * passed in is the same as the file and not -1.
117 * <p>
118 * The properties retrieved this way are not to be transmitted out of
119 * this JVM across the network as they may be private/sensitive.
120 * The most sensitive items may be marked transient to prevent them
121 * being shipped out of a JVM, but that may make them impossible to
122 * use throughout a multi-VM J2EE system, for example.
123 *
124 * @throws IOException if security props cannot be read/parsed
125 */
126 public static SecurityProps getSecurityPropsUncachedFromFilesystem(final long oldStamp)
127 throws IOException
128 {
129 final File file;
130 try { file = new File(LocalProps.getConfDir(), CoreConsts.FS_CONF_SECPROPS); }
131 catch(final UnsupportedOperationException e) { throw new IOException(e); }
132
133 // If the stamp presented is < 0 or different to that of the file,
134 // reread the file now.
135 final long newStamp = file.lastModified();
136 if((oldStamp < 0) || (oldStamp != newStamp))
137 {
138 try
139 {
140 final FileInputStream in = new FileInputStream(file);
141 try
142 {
143 final Properties props = FileTools.loadProperties(
144 new BufferedInputStream(in));
145 return(new SecurityProps(props, newStamp));
146 }
147 finally
148 {
149 in.close(); // Ensure that file is closed.
150 }
151 }
152 catch(final IOException e)
153 {
154 e.printStackTrace(); // Dump a diagnostic...
155 throw e; // Pass the error on.
156 }
157 }
158
159 return(null); // Caller's copy seems to be up-to-date.
160 }
161
162 /**Prefix of subset of properties we hand out with getGenSecProps. */
163 public static final String GENSECPROPS_PREFIX = "pg2k.gensec.";
164 /**Name of the timestamp property (a long; copied from the SecurityProps object) that we insert in the GENSECPROPS_PREFIX properties. */
165 public static final String PNAME_GENSECPROPS_TIMESTAMP = GENSECPROPS_PREFIX + "timestamp";
166 /**Internal private copy of GENSECPROPS_PREFIX properties. */
167 private final Properties gensec;
168 /**Get a private copy of the GENSECPROPS_PREFIX properties.
169 * Wrapping an existing Properties object as the defaults
170 * for an otherwise empty new Properties set is assumed to
171 * make a private copy with copy-on-write style semantics.
172 */
173 public java.util.Properties getGenSecProps() { return(new Properties(gensec)); }
174
175 /**Name of tunnel-client list property. */
176 public static final String PNAME_TUNNEL_CLIENT_LIST = "pg2k.sec.tunnelclients";
177 /**Web server permitted tunnel client list by IP address, space separated, never null. */
178 private final String TUNNEL_CLIENT_LIST;
179
180 /**Check if the IP of a given tunnel client is OK.
181 * We <em>do not</em> just disclose the whole list.
182 * <p>
183 * We take each listed allowed address in turn by parsing from
184 * the source string. We convert it to an InetAddress,
185 * skipping any that cause errors. If any of the addresses
186 * corresponding to an item on the list match any of the
187 * addresses passed (assumed to be a raw IP address with no name),
188 * then this returns OK.
189 * <p>
190 * TODO: possibly needs optimisation as may be very inefficient/slow.
191 */
192 public final boolean tunnelClientIsOK(final InetAddress addr)
193 {
194 final StringTokenizer st = new StringTokenizer(TUNNEL_CLIENT_LIST);
195 while(st.hasMoreTokens())
196 {
197 try {
198 final String name = st.nextToken();
199 final InetAddress[] ia = InetAddress.getAllByName(name);
200 if(ia == null) { continue; }
201 for(int i = ia.length; --i >= 0; )
202 {
203 // If we have a match, we are done!
204 if(ia[i].equals(addr)) { return(true); }
205 }
206 }
207 // Silently ignore any errors.
208 catch(final Exception e) { }
209 }
210
211 // No match, so rejected...
212 return(false);
213 }
214
215
216 /**Unique Serialisation class ID generated by http://random.hd.org/. */
217 private static final long serialVersionUID = -7930248582951598929L;
218 }