Frames | No Frames |
1: /* ConfigFileParser.java -- JAAS Login Configuration default syntax parser 2: Copyright (C) 2006, 2010 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 = Configuration.DEBUG ? 95: Logger.getLogger(ConfigFileParser.class.getName()) : null; 96: 97: private ConfigFileTokenizer cft; 98: private final Map map = new HashMap(); 99: 100: // default 0-arguments constructor 101: 102: /** 103: * Returns the parse result as a {@link Map} where the keys are application 104: * names, and the entries are {@link List}s of {@link AppConfigurationEntry} 105: * entries, one for each login module entry, in the order they were 106: * encountered, for that application name in the just parsed configuration 107: * file. 108: */ 109: public Map getLoginModulesMap() 110: { 111: return map; 112: } 113: 114: /** 115: * Parses the {@link Reader}'s contents assuming it is in the <i>default 116: * syntax</i>. 117: * 118: * @param r the {@link Reader} whose contents are assumed to be a JAAS Login 119: * Configuration Module file written in the <i>default syntax</i>. 120: * @throws IOException if an exception occurs while parsing the input. 121: */ 122: public void parse(Reader r) throws IOException 123: { 124: initParser(r); 125: 126: while (parseAppOrOtherEntry()) 127: { 128: /* do nothing */ 129: } 130: } 131: 132: private void initParser(Reader r) throws IOException 133: { 134: map.clear(); 135: 136: cft = new ConfigFileTokenizer(r); 137: } 138: 139: /** 140: * @return <code>true</code> if an APP_OR_OTHER_ENTRY was correctly parsed. 141: * Returns <code>false</code> otherwise. 142: * @throws IOException if an exception occurs while parsing the input. 143: */ 144: private boolean parseAppOrOtherEntry() throws IOException 145: { 146: int c = cft.nextToken(); 147: if (c == ConfigFileTokenizer.TT_EOF) 148: return false; 149: 150: if (c != ConfigFileTokenizer.TT_WORD) 151: { 152: cft.pushBack(); 153: return false; 154: } 155: 156: String appName = cft.sval; 157: if (Configuration.DEBUG) 158: log.fine("APP_NAME_OR_OTHER = " + appName); 159: if (cft.nextToken() != '{') 160: abort("Missing '{' after APP_NAME_OR_OTHER"); 161: 162: List lmis = new ArrayList(); 163: while (parseACE(lmis)) 164: { 165: /* do nothing */ 166: } 167: 168: c = cft.nextToken(); 169: if (c != '}') 170: abort("Was expecting '}' but found " + (char) c); 171: 172: c = cft.nextToken(); 173: if (c != ';') 174: abort("Was expecting ';' but found " + (char) c); 175: 176: List listOfACEs = (List) map.get(appName); 177: if (listOfACEs == null) 178: { 179: listOfACEs = new ArrayList(); 180: map.put(appName, listOfACEs); 181: } 182: listOfACEs.addAll(lmis); 183: return !appName.equalsIgnoreCase("other"); 184: } 185: 186: /** 187: * @return <code>true</code> if a LOGIN_MODULE_ENTRY was correctly parsed. 188: * Returns <code>false</code> otherwise. 189: * @throws IOException if an exception occurs while parsing the input. 190: */ 191: private boolean parseACE(List listOfACEs) throws IOException 192: { 193: int c = cft.nextToken(); 194: if (c != ConfigFileTokenizer.TT_WORD) 195: { 196: cft.pushBack(); 197: return false; 198: } 199: 200: String clazz = validateClassName(cft.sval); 201: if (Configuration.DEBUG) 202: log.fine("MODULE_CLASS = " + clazz); 203: 204: if (cft.nextToken() != ConfigFileTokenizer.TT_WORD) 205: abort("Was expecting FLAG but found none"); 206: 207: String flag = cft.sval; 208: if (Configuration.DEBUG) 209: log.fine("DEBUG: FLAG = " + flag); 210: AppConfigurationEntry.LoginModuleControlFlag f = null; 211: if (flag.equalsIgnoreCase("required")) 212: f = AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; 213: else if (flag.equalsIgnoreCase("requisite")) 214: f = AppConfigurationEntry.LoginModuleControlFlag.REQUISITE; 215: else if (flag.equalsIgnoreCase("sufficient")) 216: f = AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT; 217: else if (flag.equalsIgnoreCase("optional")) 218: f = AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL; 219: else 220: abort("Unknown Flag: " + flag); 221: 222: Map options = new HashMap(); 223: String paramName, paramValue; 224: c = cft.nextToken(); 225: while (c != ';') 226: { 227: if (c != ConfigFileTokenizer.TT_WORD) 228: abort("Was expecting PARAM_NAME but got '" + ((char) c) + "'"); 229: 230: paramName = cft.sval; 231: if (Configuration.DEBUG) 232: log.fine("PARAM_NAME = " + paramName); 233: if (cft.nextToken() != '=') 234: abort("Missing '=' after PARAM_NAME"); 235: 236: c = cft.nextToken(); 237: if (c != '"' && c != '\'') 238: { 239: if (Configuration.DEBUG) 240: log.fine("Was expecting a quoted string but got no quote character." 241: + " Assume unquoted string"); 242: } 243: paramValue = expandParamValue(cft.sval); 244: if (Configuration.DEBUG) 245: log.fine("PARAM_VALUE = " + paramValue); 246: options.put(paramName, paramValue); 247: 248: c = cft.nextToken(); 249: } 250: AppConfigurationEntry ace = new AppConfigurationEntry(clazz, f, options); 251: if (Configuration.DEBUG) 252: log.fine("LOGIN_MODULE_ENTRY = " + ace); 253: listOfACEs.add(ace); 254: return true; 255: } 256: 257: private void abort(String m) throws IOException 258: { 259: if (Configuration.DEBUG) 260: { 261: log.fine(m); 262: log.fine("Map (so far) = " + String.valueOf(map)); 263: } 264: throw new IOException(m); 265: } 266: 267: private String validateClassName(String cn) throws IOException 268: { 269: if (cn.startsWith(".") || cn.endsWith(".")) 270: abort("MODULE_CLASS MUST NOT start or end with a '.'"); 271: 272: String[] tokens = cn.split("\\."); 273: for (int i = 0; i < tokens.length; i++) 274: { 275: String t = tokens[i]; 276: if (! Character.isJavaIdentifierStart(t.charAt(0))) 277: abort("Class name [" + cn 278: + "] contains an invalid sub-package identifier: " + t); 279: 280: // we dont check the rest of the characters for isJavaIdentifierPart() 281: // because that's what the tokenizer does. 282: } 283: 284: return cn; 285: } 286: 287: /** 288: * The documentation of the {@link javax.security.auth.login.Configuration} 289: * states that: <i>"...If a String in the form, ${system.property}, occurs in 290: * the value, it will be expanded to the value of the system property."</i>. 291: * This method ensures this is the case. If such a string can not be expanded 292: * then it is left AS IS, assuming the LoginModule knows what to do with it. 293: * 294: * <p><b>IMPORTANT</b>: This implementation DOES NOT handle embedded ${} 295: * constructs. 296: * 297: * @param s the raw parameter value, incl. eventually strings of the form 298: * <code>${system.property}</code>. 299: * @return the input string with every occurence of 300: * <code>${system.property}</code> replaced with the value of the 301: * corresponding System property at the time of this method invocation. If 302: * the string is not a known System property name, then the complete sequence 303: * (incl. the ${} characters are passed AS IS. 304: */ 305: private String expandParamValue(String s) 306: { 307: String result = s; 308: try 309: { 310: int searchNdx = 0; 311: while (searchNdx < result.length()) 312: { 313: int i = s.indexOf("${", searchNdx); 314: if (i == -1) 315: break; 316: 317: int j = s.indexOf("}", i + 2); 318: if (j == -1) 319: { 320: if (Configuration.DEBUG) 321: log.fine("Found a ${ prefix with no } suffix. Ignore"); 322: break; 323: } 324: 325: String sysPropName = s.substring(i + 2, j); 326: if (Configuration.DEBUG) 327: log.fine("Found a reference to System property " + sysPropName); 328: String sysPropValue = System.getProperty(sysPropName); 329: if (Configuration.DEBUG) 330: log.fine("Resolved " + sysPropName + " to '" + sysPropValue + "'"); 331: if (sysPropValue != null) 332: { 333: result = s.substring(0, i) + sysPropValue + s.substring(j + 1); 334: searchNdx = i + sysPropValue.length(); 335: } 336: else 337: searchNdx = j + 1; 338: } 339: } 340: catch (Exception x) 341: { 342: if (Configuration.DEBUG) 343: log.fine("Exception (ignored) while expanding " + s + ": " + x); 344: } 345: 346: return result; 347: } 348: }