001    /*
002     * Copyright (c) 2003, The Regents of the University of California, through
003     * Lawrence Berkeley National Laboratory (subject to receipt of any required
004     * approvals from the U.S. Dept. of Energy). All rights reserved.
005     */
006    package gov.lbl.dsd.sea.nio.auth;
007    
008    import gov.lbl.dsd.sea.nio.util.ExtendedProperties;
009    
010    import java.io.IOException;
011    import java.net.InetAddress;
012    import java.net.UnknownHostException;
013    import java.util.ArrayList;
014    import java.util.HashSet;
015    import java.util.Iterator;
016    import java.util.List;
017    import java.util.Set;
018    import java.util.regex.Pattern;
019    
020    /**
021     * Powerful authorization rules to be used by {@link SmartHostAuthorizer};
022     * Supports allow and deny rules based on exact or patterned DNS host names,
023     * exact or patterned IP addresses, as well as regular expressions on
024     * "hostName/IPaddress" pairs.
025     * <p>
026     * Can be configured programmatically or via a configuration file (see file
027     * "authorization.properties") for an example.
028     * 
029     * @author whoschek@lbl.gov
030     * @author $Author: hoschek3 $
031     * @version $Revision: 1.7 $, $Date: 2004/12/01 20:59:31 $
032     */
033    public class SmartHostAuthorizationRules implements HostAuthorizationRules, java.io.Serializable {
034            
035            /** Meta match all hosts (no matter what DNS name or IP address) */
036            public static final String ALL = "all";
037            
038            /** Relative to localhost, meta match loopback hosts (including localhost) */
039            public static final String LOOPBACK = "loopback";
040            
041            /** Relative to localhost, meta match hosts in the same DNS domain */
042            public static final String COMMON_DOMAIN = "common-domain";
043    
044            /** Relative to localhost, meta match hosts in the same IP subnet */
045            public static final String COMMON_SUBNET = "common-subnet";
046    
047            // the rules for various checks:
048            private Set metaDirectives = new HashSet();
049            private Set hostAddresses = new HashSet();
050            private Set hostAddressPatterns = new HashSet();
051            private Set hostNames = new HashSet();
052            private Set hostNamePatterns = new HashSet();
053            private List regexes = new ArrayList();
054            
055            private ExtendedProperties properties = new ExtendedProperties(); // keep around just for an easy toString()
056    
057            private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(SmartHostAuthorizationRules.class);
058    
059            /** 
060             * Creates an empty instance with no rules (isMatch(x) returns false).
061             */
062            public SmartHostAuthorizationRules() {}
063            
064            /**
065             * Creates an authorizer from the given configuration properties.
066             * 
067             * @param properties the configuration properties to use
068             */
069            public static SmartHostAuthorizer createHostAuthorizer(ExtendedProperties properties) {
070                    return new SmartHostAuthorizer(
071                            properties.getBoolean("allowBeforeDeny", true),
072                            new SmartHostAuthorizationRules().addRules("allow", properties),
073                            new SmartHostAuthorizationRules().addRules("deny", properties)
074                    );
075            }
076            
077            /**
078             * Creates an authorizer from the properties in the given configuration file.
079             * 
080             * @param fileName the configuration file to use
081             * @throws IOException
082             */
083            public static SmartHostAuthorizer createHostAuthorizer(String fileName) throws IOException {
084                    return createHostAuthorizer(new ExtendedProperties(fileName));
085            }
086            
087            /** 
088             * Adds the given host or pattern to the set of rules.
089             * @param host the host to pattern to add.
090             * <p>
091             *     Exact address example: "131.243.2.165", Prefix patterned address example: "131.243."
092             * <p>
093             *     Exact name    example: "doggy.lbl.gov", Suffix patterned name example: ".lbl.gov"  
094             * <p>
095             *     Meta pattern examples: "all", "loopback", "common-domain", "common-subnet"
096             * @return this (for convenience only)
097             */
098            public SmartHostAuthorizationRules addHost(String host) {
099                    this.checkString(host);
100                    host = host.toLowerCase();
101                    
102                    if (host.equals(ALL) || host.equals(LOOPBACK)) {
103                            this.metaDirectives.add(host);
104                            return this;
105                    }
106                    if (host.equals(COMMON_DOMAIN)) {
107                            String hostName = getLocalHost().getHostName();
108                            String domainPattern = hostName.substring(hostName.indexOf("."));
109                            //log.debug("hostName="+hostName);
110                            //log.debug("domainPattern="+domainPattern);
111                            this.hostNamePatterns.add(domainPattern);
112                            return this;
113                    }
114                    if (host.equals(COMMON_SUBNET)) {
115                            String hostAddress = getLocalHost().getHostAddress();
116                            String addressPattern = hostAddress.substring(0, hostAddress.lastIndexOf(".")+1);
117                            //log.debug("hostAddress="+hostAddress);
118                            //log.debug("addressPattern="+addressPattern);
119                            this.hostAddressPatterns.add(addressPattern);
120                            return this;
121                    }
122                    
123                    if (this.isHostName(host)) {
124                            if (host.startsWith("."))
125                                    this.hostNamePatterns.add(host);
126                            else 
127                                    this.hostNames.add(host);
128                            return this;
129                    }
130                    
131                    if (host.endsWith("."))
132                            this.hostAddressPatterns.add(host);
133                    else 
134                            this.hostAddresses.add(host);
135                    
136                    return this;
137            }
138                    
139            /** 
140             * Adds the given regular expression on "hostName/IPaddress" to the set of rules. 
141             * @param regex Example: "clusternode.+?\.lbl\.gov/.*"
142             * @return this (for convenience only)
143             */
144            public SmartHostAuthorizationRules addRegex(String regex) {
145                    this.checkString(regex);
146                    this.regexes.add(Pattern.compile(regex));
147                    return this;
148            }
149            
150            /**
151             * Reads all rules for the given mode from the given configuration
152             * properties and adds them to the current set of rules.
153             * 
154             * @param mode for example "allow" or "deny"
155             * @param properties the configuration properties to read from
156             * @return this (for convenience only)
157             */
158            protected SmartHostAuthorizationRules addRules(String mode, ExtendedProperties properties) {
159                    String[] strings;
160                    
161                    strings = properties.getStringArray(mode + "");
162                    for (int i=0; i < strings.length; i++) addHost(strings[i]);
163    
164                    strings = properties.getStringArray(mode + ".regex");
165                    for (int i=0; i < strings.length; i++) addRegex(strings[i]);
166                    
167                    this.properties = properties;
168                    return this;
169            }
170    
171            /**
172             * Returns whether or not the given host (aka InetAddress) matches ANY of
173             * the current rules.
174             * 
175             * @return true if at least one rule matches; false otherwise
176             */
177            public boolean isMatch(InetAddress address) {
178                    // compare meta wildcards
179                    if (metaDirectives.size() > 0) {
180                            if (metaDirectives.contains(ALL)) {
181                                    return true;
182                            }
183                            if (metaDirectives.contains(LOOPBACK) && address.isLoopbackAddress()) {
184                                    return true;
185                            }
186                    }
187                    
188                    // compare exact IP address
189                    if (hostAddresses.size() > 0) {
190                            String hostAddress = address.getHostAddress();
191                            if (hostAddresses.contains(hostAddress)) {
192                                    return true;
193                            }
194                    }
195                    
196                    // compare prefix patterned IP address
197                    if (hostAddressPatterns.size() > 0) {
198                            String hostAddress = address.getHostAddress();
199                            Iterator iter = hostAddressPatterns.iterator();
200                            while (iter.hasNext()) {
201                                    String pattern = (String) iter.next();
202                                    if (hostAddress.startsWith(pattern)) { 
203                                            return true;
204                                    }
205                            }
206                    }
207                    
208                    // compare exact host name
209                    if (hostNames.size() > 0) {
210                            String hostName = address.getHostName();
211                            if (hostNames.contains(hostName)) {
212                                    return true;
213                            }
214                    }
215                    
216                    // compare suffix patterned host name
217                    if (hostNamePatterns.size() > 0) {
218                            String hostName = address.getHostName();
219                            Iterator iter = hostNamePatterns.iterator();
220                            while (iter.hasNext()) {
221                                    String pattern = (String) iter.next();
222                                    if (hostName.endsWith(pattern)) { 
223                                            return true;
224                                    }
225                            }
226                    }
227                    
228                    // compare regular expressions on "hostName/IPaddress"
229                    if (regexes.size() > 0) {
230                            String nameAndAddress = address.getHostName() + "/" + address.getHostAddress();
231                            Iterator iter = regexes.iterator();
232                            while (iter.hasNext()) {
233                                    Pattern regex = (Pattern) iter.next();
234                                    if (regex.matcher(nameAndAddress).matches()) { 
235                                            return true;
236                                    }
237                            }
238                    }
239                            
240                    return false;
241            }
242            
243            /**
244             * Returns a summary string representation of the receiver.
245             */
246            public String toString() {
247                    return this.getClass().getName() + "[" + toString(this.properties) + "]";
248            }
249            
250            private boolean isHostName(String str) {
251                    for (int i=0; i < str.length(); i++) {
252                            if (Character.isLetter(str.charAt(i))) return true;
253                    }
254                    return false;
255            }
256            
257            /** Sanity check */ 
258            private void checkString(String str) {
259                    if (str==null || str.length()==0) throw new IllegalArgumentException("str=["+str+"]");
260            }
261            
262            private static InetAddress getLocalHost() {
263                    try {
264                            return InetAddress.getLocalHost();
265                    } catch (UnknownHostException e) {
266                            throw new RuntimeException("Oops, should never happen", e);
267                    }                       
268            }
269            
270            /**
271             * Converts properties to a summary string
272             */
273            private String toString(ExtendedProperties properties) {
274                    StringBuffer buf = new StringBuffer();
275                Iterator iter = properties.getKeys();
276                while (iter.hasNext()) {
277                    String key = (String) iter.next();
278                    Object value = properties.get(key);
279                    buf.append("\n" + key + " => " + value);
280                }
281                return buf.toString();
282            }
283            
284            /**
285             * Program to quickly test whether or not a given host is allowed;
286             * Useful to assist in becoming familiar with the configuration file syntax;
287             * Example usage: java [class] [configFileName] [hostName]
288             */
289            public static void main(String[] args) throws IOException {             
290                    String fileName = "authorization.properties";
291                    if (args.length > 0) fileName = args[0];
292                    String hostName = "doggy.lbl.gov";
293                    if (args.length > 1) hostName = args[1];
294                    int runs = 1;
295                    if (args.length > 2) runs = Integer.parseInt(args[2]);
296                    
297                    HostAuthorizer authorizer = createHostAuthorizer(fileName);
298                    InetAddress address = InetAddress.getByName(hostName);
299                    boolean isAllowed = false;
300                    long start = System.currentTimeMillis();
301                    for (int i=0; i < runs; i++) isAllowed = authorizer.isAllowed(address);
302                    long end = System.currentTimeMillis();
303                    
304                    System.out.println("isAllowed="+ isAllowed);
305                    System.out.println("inetAddress="+ address);
306                    System.out.println("inetAddress.getCanonicalHostName="+ address.getCanonicalHostName());
307                    System.out.println("localHost="+ InetAddress.getLocalHost());
308                    System.out.println("hostAuthorizer="+ authorizer);
309                    System.out.println("secs="+ (end-start) / 1000.0f);             
310                    System.out.println("iters/sec="+ runs / ((end-start) / 1000.0f));               
311            }
312    
313    //      private void logAddress(InetAddress address) {
314    //              log.fatal("hostName="+address.getHostName());
315    //              log.fatal("toString="+address.toString());
316    //              log.fatal("hostAddress="+address.getHostAddress());
317    //              log.fatal("canon="+address.getCanonicalHostName());
318    //              log.fatal("isLoopbackAddress="+address.isLoopbackAddress());
319    //              log.fatal("isSiteLocalAddress="+address.isSiteLocalAddress());
320    //              log.fatal("isAnyLocalAddress="+address.isAnyLocalAddress());
321    //              //InetAddressUtil util = new InetAddressUtil();
322    //              log.fatal("class="+InetAddressInfo.getSubnetClassString(address));
323    //              log.fatal("domainlevels="+InetAddressInfo.getDomainLevels(address));
324    //              log.fatal("subnetMask="+InetAddressInfo.getSubnetMaskString(address));
325    //              log.fatal("commonSubnet="+new InetAddressInfo(getLocalHost()).commonSubnet(address));
326    //              log.fatal("commonDomain="+(new InetAddressInfo(getLocalHost()).commonDomainLevels(address) == 2));
327    //              log.fatal("commonDomainLevels="+new InetAddressInfo(getLocalHost()).commonDomainLevels(address));
328    //              //log.fatal("domainName="+getDomainName(address));
329    //      }
330    
331    }