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