|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.provider; |
|
|
|
import java.io.*; |
|
import java.net.MalformedURLException; |
|
import java.net.URI; |
|
import java.net.URL; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.security.PrivilegedActionException; |
|
import java.security.PrivilegedExceptionAction; |
|
import java.security.Security; |
|
import java.security.URIParameter; |
|
import java.text.MessageFormat; |
|
import java.util.*; |
|
import javax.security.auth.AuthPermission; |
|
import javax.security.auth.login.AppConfigurationEntry; |
|
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; |
|
import javax.security.auth.login.Configuration; |
|
import javax.security.auth.login.ConfigurationSpi; |
|
import sun.security.util.Debug; |
|
import sun.security.util.PropertyExpander; |
|
import sun.security.util.ResourcesMgr; |
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class ConfigFile extends Configuration { |
|
|
|
private final Spi spi; |
|
|
|
public ConfigFile() { |
|
spi = new Spi(); |
|
} |
|
|
|
@Override |
|
public AppConfigurationEntry[] getAppConfigurationEntry(String appName) { |
|
return spi.engineGetAppConfigurationEntry(appName); |
|
} |
|
|
|
@Override |
|
public synchronized void refresh() { |
|
spi.engineRefresh(); |
|
} |
|
|
|
public static final class Spi extends ConfigurationSpi { |
|
|
|
private URL url; |
|
private boolean expandProp = true; |
|
private Map<String, List<AppConfigurationEntry>> configuration; |
|
private int linenum; |
|
private StreamTokenizer st; |
|
private int lookahead; |
|
|
|
private static Debug debugConfig = Debug.getInstance("configfile"); |
|
private static Debug debugParser = Debug.getInstance("configparser"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Spi() { |
|
try { |
|
init(); |
|
} catch (IOException ioe) { |
|
throw new SecurityException(ioe); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Spi(URI uri) { |
|
|
|
try { |
|
url = uri.toURL(); |
|
init(); |
|
} catch (IOException ioe) { |
|
throw new SecurityException(ioe); |
|
} |
|
} |
|
|
|
@SuppressWarnings("removal") |
|
public Spi(final Configuration.Parameters params) throws IOException { |
|
|
|
// call in a doPrivileged |
|
// |
|
// we have already passed the Configuration.getInstance |
|
// security check. also this class is not freely accessible |
|
// (it is in the "sun" package). |
|
|
|
try { |
|
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { |
|
public Void run() throws IOException { |
|
if (params == null) { |
|
init(); |
|
} else { |
|
if (!(params instanceof URIParameter)) { |
|
throw new IllegalArgumentException |
|
("Unrecognized parameter: " + params); |
|
} |
|
URIParameter uriParam = (URIParameter)params; |
|
url = uriParam.getURI().toURL(); |
|
init(); |
|
} |
|
return null; |
|
} |
|
}); |
|
} catch (PrivilegedActionException pae) { |
|
throw (IOException)pae.getException(); |
|
} |
|
|
|
// if init() throws some other RuntimeException, |
|
// let it percolate up naturally. |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void init() throws IOException { |
|
|
|
boolean initialized = false; |
|
|
|
// For policy.expandProperties, check if either a security or system |
|
// property is set to false (old code erroneously checked the system |
|
|
|
String expand = Security.getProperty("policy.expandProperties"); |
|
if (expand == null) { |
|
expand = System.getProperty("policy.expandProperties"); |
|
} |
|
if ("false".equals(expand)) { |
|
expandProp = false; |
|
} |
|
|
|
|
|
Map<String, List<AppConfigurationEntry>> newConfig = new HashMap<>(); |
|
|
|
if (url != null) { |
|
|
|
|
|
|
|
*/ |
|
if (debugConfig != null) { |
|
debugConfig.println("reading " + url); |
|
} |
|
init(url, newConfig); |
|
configuration = newConfig; |
|
return; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
String allowSys = Security.getProperty("policy.allowSystemProperty"); |
|
|
|
if ("true".equalsIgnoreCase(allowSys)) { |
|
String extra_config = System.getProperty |
|
("java.security.auth.login.config"); |
|
if (extra_config != null) { |
|
boolean overrideAll = false; |
|
if (extra_config.startsWith("=")) { |
|
overrideAll = true; |
|
extra_config = extra_config.substring(1); |
|
} |
|
try { |
|
extra_config = PropertyExpander.expand(extra_config); |
|
} catch (PropertyExpander.ExpandException peee) { |
|
throw ioException("Unable.to.properly.expand.config", |
|
extra_config); |
|
} |
|
|
|
URL configURL = null; |
|
try { |
|
configURL = new URL(extra_config); |
|
} catch (MalformedURLException mue) { |
|
File configFile = new File(extra_config); |
|
if (configFile.exists()) { |
|
configURL = configFile.toURI().toURL(); |
|
} else { |
|
throw ioException( |
|
"extra.config.No.such.file.or.directory.", |
|
extra_config); |
|
} |
|
} |
|
|
|
if (debugConfig != null) { |
|
debugConfig.println("reading "+configURL); |
|
} |
|
init(configURL, newConfig); |
|
initialized = true; |
|
if (overrideAll) { |
|
if (debugConfig != null) { |
|
debugConfig.println("overriding other policies!"); |
|
} |
|
configuration = newConfig; |
|
return; |
|
} |
|
} |
|
} |
|
|
|
int n = 1; |
|
String config_url; |
|
while ((config_url = Security.getProperty |
|
("login.config.url."+n)) != null) { |
|
try { |
|
config_url = PropertyExpander.expand |
|
(config_url).replace(File.separatorChar, '/'); |
|
if (debugConfig != null) { |
|
debugConfig.println("\tReading config: " + config_url); |
|
} |
|
init(new URL(config_url), newConfig); |
|
initialized = true; |
|
} catch (PropertyExpander.ExpandException peee) { |
|
throw ioException("Unable.to.properly.expand.config", |
|
config_url); |
|
} |
|
n++; |
|
} |
|
|
|
if (initialized == false && n == 1 && config_url == null) { |
|
|
|
|
|
if (debugConfig != null) { |
|
debugConfig.println("\tReading Policy " + |
|
"from ~/.java.login.config"); |
|
} |
|
config_url = System.getProperty("user.home"); |
|
String userConfigFile = config_url + File.separatorChar + |
|
".java.login.config"; |
|
|
|
// No longer throws an exception when there's no config file |
|
|
|
if (new File(userConfigFile).exists()) { |
|
init(new File(userConfigFile).toURI().toURL(), newConfig); |
|
} |
|
} |
|
|
|
configuration = newConfig; |
|
} |
|
|
|
private void init(URL config, |
|
Map<String, List<AppConfigurationEntry>> newConfig) |
|
throws IOException { |
|
|
|
try (InputStreamReader isr |
|
= new InputStreamReader(getInputStream(config), UTF_8)) { |
|
readConfig(isr, newConfig); |
|
} catch (FileNotFoundException fnfe) { |
|
if (debugConfig != null) { |
|
debugConfig.println(fnfe.toString()); |
|
} |
|
throw new IOException(ResourcesMgr.getAuthResourceString |
|
("Configuration.Error.No.such.file.or.directory")); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public AppConfigurationEntry[] engineGetAppConfigurationEntry |
|
(String applicationName) { |
|
|
|
List<AppConfigurationEntry> list = null; |
|
synchronized (configuration) { |
|
list = configuration.get(applicationName); |
|
} |
|
|
|
if (list == null || list.size() == 0) { |
|
return null; |
|
} |
|
|
|
AppConfigurationEntry[] entries = |
|
new AppConfigurationEntry[list.size()]; |
|
Iterator<AppConfigurationEntry> iterator = list.iterator(); |
|
for (int i = 0; iterator.hasNext(); i++) { |
|
AppConfigurationEntry e = iterator.next(); |
|
entries[i] = new AppConfigurationEntry(e.getLoginModuleName(), |
|
e.getControlFlag(), |
|
e.getOptions()); |
|
} |
|
return entries; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
@Override |
|
public synchronized void engineRefresh() { |
|
|
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) { |
|
sm.checkPermission( |
|
new AuthPermission("refreshLoginConfiguration")); |
|
} |
|
|
|
AccessController.doPrivileged(new PrivilegedAction<Void>() { |
|
public Void run() { |
|
try { |
|
init(); |
|
} catch (IOException ioe) { |
|
throw new SecurityException(ioe.getLocalizedMessage(), |
|
ioe); |
|
} |
|
return null; |
|
} |
|
}); |
|
} |
|
|
|
private void readConfig(Reader reader, |
|
Map<String, List<AppConfigurationEntry>> newConfig) |
|
throws IOException { |
|
|
|
linenum = 1; |
|
|
|
if (!(reader instanceof BufferedReader)) { |
|
reader = new BufferedReader(reader); |
|
} |
|
|
|
st = new StreamTokenizer(reader); |
|
st.quoteChar('"'); |
|
st.wordChars('$', '$'); |
|
st.wordChars('_', '_'); |
|
st.wordChars('-', '-'); |
|
st.wordChars('*', '*'); |
|
st.lowerCaseMode(false); |
|
st.slashSlashComments(true); |
|
st.slashStarComments(true); |
|
st.eolIsSignificant(true); |
|
|
|
lookahead = nextToken(); |
|
while (lookahead != StreamTokenizer.TT_EOF) { |
|
parseLoginEntry(newConfig); |
|
} |
|
} |
|
|
|
private void parseLoginEntry( |
|
Map<String, List<AppConfigurationEntry>> newConfig) |
|
throws IOException { |
|
|
|
List<AppConfigurationEntry> configEntries = new LinkedList<>(); |
|
|
|
|
|
String appName = st.sval; |
|
lookahead = nextToken(); |
|
|
|
if (debugParser != null) { |
|
debugParser.println("\tReading next config entry: " + appName); |
|
} |
|
|
|
match("{"); |
|
|
|
|
|
while (peek("}") == false) { |
|
|
|
String moduleClass = match("module class name"); |
|
|
|
|
|
LoginModuleControlFlag controlFlag; |
|
String sflag = match("controlFlag").toUpperCase(Locale.ENGLISH); |
|
switch (sflag) { |
|
case "REQUIRED": |
|
controlFlag = LoginModuleControlFlag.REQUIRED; |
|
break; |
|
case "REQUISITE": |
|
controlFlag = LoginModuleControlFlag.REQUISITE; |
|
break; |
|
case "SUFFICIENT": |
|
controlFlag = LoginModuleControlFlag.SUFFICIENT; |
|
break; |
|
case "OPTIONAL": |
|
controlFlag = LoginModuleControlFlag.OPTIONAL; |
|
break; |
|
default: |
|
throw ioException( |
|
"Configuration.Error.Invalid.control.flag.flag", |
|
sflag); |
|
} |
|
|
|
|
|
Map<String, String> options = new HashMap<>(); |
|
while (peek(";") == false) { |
|
String key = match("option key"); |
|
match("="); |
|
try { |
|
options.put(key, expand(match("option value"))); |
|
} catch (PropertyExpander.ExpandException peee) { |
|
throw new IOException(peee.getLocalizedMessage()); |
|
} |
|
} |
|
|
|
lookahead = nextToken(); |
|
|
|
|
|
if (debugParser != null) { |
|
debugParser.println("\t\t" + moduleClass + ", " + sflag); |
|
for (String key : options.keySet()) { |
|
debugParser.println("\t\t\t" + key + |
|
"=" + options.get(key)); |
|
} |
|
} |
|
configEntries.add(new AppConfigurationEntry(moduleClass, |
|
controlFlag, |
|
options)); |
|
} |
|
|
|
match("}"); |
|
match(";"); |
|
|
|
|
|
if (newConfig.containsKey(appName)) { |
|
throw ioException( |
|
"Configuration.Error.Can.not.specify.multiple.entries.for.appName", |
|
appName); |
|
} |
|
newConfig.put(appName, configEntries); |
|
} |
|
|
|
private String match(String expect) throws IOException { |
|
|
|
String value = null; |
|
|
|
switch(lookahead) { |
|
case StreamTokenizer.TT_EOF: |
|
throw ioException( |
|
"Configuration.Error.expected.expect.read.end.of.file.", |
|
expect); |
|
|
|
case '"': |
|
case StreamTokenizer.TT_WORD: |
|
if (expect.equalsIgnoreCase("module class name") || |
|
expect.equalsIgnoreCase("controlFlag") || |
|
expect.equalsIgnoreCase("option key") || |
|
expect.equalsIgnoreCase("option value")) { |
|
value = st.sval; |
|
lookahead = nextToken(); |
|
} else { |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.found.value.", |
|
linenum, expect, st.sval); |
|
} |
|
break; |
|
|
|
case '{': |
|
if (expect.equalsIgnoreCase("{")) { |
|
lookahead = nextToken(); |
|
} else { |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.", |
|
linenum, expect, st.sval); |
|
} |
|
break; |
|
|
|
case ';': |
|
if (expect.equalsIgnoreCase(";")) { |
|
lookahead = nextToken(); |
|
} else { |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.", |
|
linenum, expect, st.sval); |
|
} |
|
break; |
|
|
|
case '}': |
|
if (expect.equalsIgnoreCase("}")) { |
|
lookahead = nextToken(); |
|
} else { |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.", |
|
linenum, expect, st.sval); |
|
} |
|
break; |
|
|
|
case '=': |
|
if (expect.equalsIgnoreCase("=")) { |
|
lookahead = nextToken(); |
|
} else { |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.", |
|
linenum, expect, st.sval); |
|
} |
|
break; |
|
|
|
default: |
|
throw ioException( |
|
"Configuration.Error.Line.line.expected.expect.found.value.", |
|
linenum, expect, st.sval); |
|
} |
|
return value; |
|
} |
|
|
|
private boolean peek(String expect) { |
|
switch (lookahead) { |
|
case ',': |
|
return expect.equalsIgnoreCase(","); |
|
case ';': |
|
return expect.equalsIgnoreCase(";"); |
|
case '{': |
|
return expect.equalsIgnoreCase("{"); |
|
case '}': |
|
return expect.equalsIgnoreCase("}"); |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
private int nextToken() throws IOException { |
|
int tok; |
|
while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) { |
|
linenum++; |
|
} |
|
return tok; |
|
} |
|
|
|
private InputStream getInputStream(URL url) throws IOException { |
|
if ("file".equalsIgnoreCase(url.getProtocol())) { |
|
// Compatibility notes: |
|
// |
|
// Code changed from |
|
// String path = url.getFile().replace('/', File.separatorChar); |
|
// return new FileInputStream(path); |
|
// |
|
// The original implementation would search for "/tmp/a%20b" |
|
// when url is "file:///tmp/a%20b". This is incorrect. The |
|
// current codes fix this bug and searches for "/tmp/a b". |
|
// For compatibility reasons, when the file "/tmp/a b" does |
|
// not exist, the file named "/tmp/a%20b" will be tried. |
|
// |
|
// This also means that if both file exists, the behavior of |
|
// this method is changed, and the current codes choose the |
|
|
|
try { |
|
return url.openStream(); |
|
} catch (Exception e) { |
|
String file = url.getPath(); |
|
if (!url.getHost().isEmpty()) { |
|
file = "//" + url.getHost() + file; |
|
} |
|
if (debugConfig != null) { |
|
debugConfig.println("cannot read " + url + |
|
", try " + file); |
|
} |
|
return new FileInputStream(file); |
|
} |
|
} else { |
|
return url.openStream(); |
|
} |
|
} |
|
|
|
private String expand(String value) |
|
throws PropertyExpander.ExpandException, IOException { |
|
|
|
if (value.isEmpty()) { |
|
return value; |
|
} |
|
|
|
if (!expandProp) { |
|
return value; |
|
} |
|
String s = PropertyExpander.expand(value); |
|
if (s == null || s.isEmpty()) { |
|
throw ioException( |
|
"Configuration.Error.Line.line.system.property.value.expanded.to.empty.value", |
|
linenum, value); |
|
} |
|
return s; |
|
} |
|
|
|
private IOException ioException(String resourceKey, Object... args) { |
|
MessageFormat form = new MessageFormat( |
|
ResourcesMgr.getAuthResourceString(resourceKey)); |
|
return new IOException(form.format(args)); |
|
} |
|
} |
|
} |