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 }