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        }