Frames | No Frames |
1: /* ConfigFileParser.java -- JAAS Login Configuration default syntax parser 2: Copyright (C) 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package gnu.javax.security.auth.login; 40: 41: import gnu.java.security.Configuration; 42: 43: import java.io.IOException; 44: import java.io.Reader; 45: import java.util.ArrayList; 46: import java.util.HashMap; 47: import java.util.List; 48: import java.util.Map; 49: import java.util.logging.Logger; 50: 51: import javax.security.auth.login.AppConfigurationEntry; 52: 53: /** 54: * A parser that knows how to interpret JAAS Login Module Configuration files 55: * written in the <i>default syntax</i> which is interpreted as adhering to 56: * the following grammar: 57: * 58: * <pre> 59: * CONFIG ::= APP_OR_OTHER_ENTRY+ 60: * APP_OR_OTHER_ENTRY ::= APP_NAME_OR_OTHER JAAS_CONFIG_BLOCK 61: * APP_NAME_OR_OTHER ::= APP_NAME 62: * | 'other' 63: * JAAS_CONFIG_BLOCK ::= '{' (LOGIN_MODULE_ENTRY ';')+ '}' ';' 64: * LOGIN_MODULE_ENTRY ::= MODULE_CLASS FLAG MODULE_OPTION* ';' 65: * FLAG ::= 'required' 66: * | 'requisite' 67: * | 'sufficient' 68: * | 'optional' 69: * MODULE_OPTION ::= PARAM_NAME '=' PARAM_VALUE 70: * 71: * APP_NAME ::= JAVA_IDENTIFIER 72: * MODULE_CLASS ::= JAVA_IDENTIFIER ('.' JAVA_IDENTIFIER)* 73: * PARAM_NAME ::= STRING 74: * PARAM_VALUE ::= '"' STRING '"' | ''' STRING ''' | STRING 75: * </pre> 76: * 77: * <p>This parser handles UTF-8 entities when used as APP_NAME and PARAM_VALUE. 78: * It also checks for the use of Java identifiers used in MODULE_CLASS, thus 79: * minimizing the risks of having {@link java.lang.ClassCastException}s thrown 80: * at runtime due to syntactically invalid names.</p> 81: * 82: * <p>In the above context, a JAVA_IDENTIFIER is a sequence of tokens, 83: * separated by the character '.'. Each of these tokens obeys the following:</p> 84: * 85: * <ol> 86: * <li>its first character yields <code>true</code> when used as an input to 87: * the {@link java.lang.Character#isJavaIdentifierStart(char)}, and</li> 88: * <li>all remaining characters, yield <code>true</code> when used as an 89: * input to {@link java.lang.Character#isJavaIdentifierPart(char)}.</li> 90: * </ol> 91: */ 92: public final class ConfigFileParser 93: { 94: private static final Logger log = Logger.getLogger(ConfigFileParser.class.getName()); 95: private ConfigFileTokenizer cft; 96: private Map map = new HashMap(); 97: 98: // default 0-arguments constructor 99: 100: /** 101: * Returns the parse result as a {@link Map} where the keys are application 102: * names, and the entries are {@link List}s of {@link AppConfigurationEntry} 103: * entries, one for each login module entry, in the order they were 104: * encountered, for that application name in the just parsed configuration 105: * file. 106: */ 107: public Map getLoginModulesMap() 108: { 109: return map; 110: } 111: 112: /** 113: * Parses the {@link Reader}'s contents assuming it is in the <i>default 114: * syntax</i>. 115: * 116: * @param r the {@link Reader} whose contents are assumed to be a JAAS Login 117: * Configuration Module file written in the <i>default syntax</i>. 118: * @throws IOException if an exception occurs while parsing the input. 119: */ 120: public void parse(Reader r) throws IOException 121: { 122: initParser(r); 123: 124: while (parseAppOrOtherEntry()) 125: ; // do nothing 126: } 127: 128: private void initParser(Reader r) throws IOException 129: { 130: map.clear(); 131: 132: cft = new ConfigFileTokenizer(r); 133: } 134: 135: /** 136: * @return <code>true</code> if an APP_OR_OTHER_ENTRY was correctly parsed. 137: * Returns <code>false</code> otherwise. 138: * @throws IOException if an exception occurs while parsing the input. 139: */ 140: private boolean parseAppOrOtherEntry() throws IOException 141: { 142: int c = cft.nextToken(); 143: if (c == ConfigFileTokenizer.TT_EOF) 144: return false; 145: 146: if (c != ConfigFileTokenizer.TT_WORD) 147: { 148: cft.pushBack(); 149: return false; 150: } 151: 152: String appName = cft.sval; 153: if (Configuration.DEBUG) 154: log.fine("APP_NAME_OR_OTHER = " + appName); 155: if (cft.nextToken() != '{') 156: abort("Missing '{' after APP_NAME_OR_OTHER"); 157: 158: List lmis = new ArrayList(); 159: while (parseACE(lmis)) 160: ; // do nothing 161: 162: c = cft.nextToken(); 163: if (c != '}') 164: abort("Was expecting '}' but found " + (char) c); 165: 166: c = cft.nextToken(); 167: if (c != ';') 168: abort("Was expecting ';' but found " + (char) c); 169: 170: List listOfACEs = (List) map.get(appName); 171: if (listOfACEs == null) 172: { 173: listOfACEs = new ArrayList(); 174: map.put(appName, listOfACEs); 175: } 176: listOfACEs.addAll(lmis); 177: return !appName.equalsIgnoreCase("other"); 178: } 179: 180: /** 181: * @return <code>true</code> if a LOGIN_MODULE_ENTRY was correctly parsed. 182: * Returns <code>false</code> otherwise. 183: * @throws IOException if an exception occurs while parsing the input. 184: */ 185: private boolean parseACE(List listOfACEs) throws IOException 186: { 187: int c = cft.nextToken(); 188: if (c != ConfigFileTokenizer.TT_WORD) 189: { 190: cft.pushBack(); 191: return false; 192: } 193: 194: String clazz = validateClassName(cft.sval); 195: if (Configuration.DEBUG) 196: log.fine("MODULE_CLASS = " + clazz); 197: 198: if (cft.nextToken() != ConfigFileTokenizer.TT_WORD) 199: abort("Was expecting FLAG but found none"); 200: 201: String flag = cft.sval; 202: if (Configuration.DEBUG) 203: log.fine("DEBUG: FLAG = " + flag); 204: AppConfigurationEntry.LoginModuleControlFlag f = null; 205: if (flag.equalsIgnoreCase("required")) 206: f = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; 207: else if (flag.equalsIgnoreCase("requisite")) 208: f = AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; 209: else if (flag.equalsIgnoreCase("sufficient")) 210: f = AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; 211: else if (flag.equalsIgnoreCase("optional")) 212: f = AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; 213: else 214: abort("Unknown Flag: " + flag); 215: 216: Map options = new HashMap(); 217: String paramName, paramValue; 218: c = cft.nextToken(); 219: while (c != ';') 220: { 221: if (c != ConfigFileTokenizer.TT_WORD) 222: abort("Was expecting PARAM_NAME but got '" + ((char) c) + "'"); 223: 224: paramName = cft.sval; 225: if (Configuration.DEBUG) 226: log.fine("PARAM_NAME = " + paramName); 227: if (cft.nextToken() != '=') 228: abort("Missing '=' after PARAM_NAME"); 229: 230: c = cft.nextToken(); 231: if (c != '"' && c != '\'') 232: { 233: if (Configuration.DEBUG) 234: log.fine("Was expecting a quoted string but got no quote character." 235: + " Assume unquoted string"); 236: } 237: paramValue = expandParamValue(cft.sval); 238: if (Configuration.DEBUG) 239: log.fine("PARAM_VALUE = " + paramValue); 240: options.put(paramName, paramValue); 241: 242: c = cft.nextToken(); 243: } 244: AppConfigurationEntry ace = new AppConfigurationEntry(clazz, f, options); 245: if (Configuration.DEBUG) 246: log.fine("LOGIN_MODULE_ENTRY = " + ace); 247: listOfACEs.add(ace); 248: return true; 249: } 250: 251: private void abort(String m) throws IOException 252: { 253: if (Configuration.DEBUG) 254: { 255: log.fine(m); 256: log.fine("Map (so far) = " + String.valueOf(map)); 257: } 258: throw new IOException(m); 259: } 260: 261: private String validateClassName(String cn) throws IOException 262: { 263: if (cn.startsWith(".") || cn.endsWith(".")) 264: abort("MODULE_CLASS MUST NOT start or end with a '.'"); 265: 266: String[] tokens = cn.split("\\."); 267: for (int i = 0; i < tokens.length; i++) 268: { 269: String t = tokens[i]; 270: if (! Character.isJavaIdentifierStart(t.charAt(0))) 271: abort("Class name [" + cn 272: + "] contains an invalid sub-package identifier: " + t); 273: 274: // we dont check the rest of the characters for isJavaIdentifierPart() 275: // because that's what the tokenizer does. 276: } 277: 278: return cn; 279: } 280: 281: /** 282: * The documentation of the {@link javax.security.auth.login.Configuration} 283: * states that: <i>"...If a String in the form, ${system.property}, occurs in 284: * the value, it will be expanded to the value of the system property."</i>. 285: * This method ensures this is the case. If such a string can not be expanded 286: * then it is left AS IS, assuming the LoginModule knows what to do with it. 287: * 288: * <p><b>IMPORTANT</b>: This implementation DOES NOT handle embedded ${} 289: * constructs. 290: * 291: * @param s the raw parameter value, incl. eventually strings of the form 292: * <code>${system.property}</code>. 293: * @return the input string with every occurence of 294: * <code>${system.property}</code> replaced with the value of the 295: * corresponding System property at the time of this method invocation. If 296: * the string is not a known System property name, then the complete sequence 297: * (incl. the ${} characters are passed AS IS. 298: */ 299: private String expandParamValue(String s) 300: { 301: String result = s; 302: try 303: { 304: int searchNdx = 0; 305: while (searchNdx < result.length()) 306: { 307: int i = s.indexOf("${", searchNdx); 308: if (i == -1) 309: break; 310: 311: int j = s.indexOf("}", i + 2); 312: if (j == -1) 313: { 314: if (Configuration.DEBUG) 315: log.fine("Found a ${ prefix with no } suffix. Ignore"); 316: break; 317: } 318: 319: String sysPropName = s.substring(i + 2, j); 320: if (Configuration.DEBUG) 321: log.fine("Found a reference to System property " + sysPropName); 322: String sysPropValue = System.getProperty(sysPropName); 323: if (Configuration.DEBUG) 324: log.fine("Resolved " + sysPropName + " to '" + sysPropValue + "'"); 325: if (sysPropValue != null) 326: { 327: result = s.substring(0, i) + sysPropValue + s.substring(j + 1); 328: searchNdx = i + sysPropValue.length(); 329: } 330: else 331: searchNdx = j + 1; 332: } 333: } 334: catch (Exception x) 335: { 336: if (Configuration.DEBUG) 337: log.fine("Exception (ignored) while expanding " + s + ": " + x); 338: } 339: 340: return result; 341: } 342: }