Path: blob/master/src/java.base/share/classes/sun/security/provider/ConfigFile.java
41159 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider;2627import java.io.*;28import java.net.MalformedURLException;29import java.net.URI;30import java.net.URL;31import java.security.AccessController;32import java.security.PrivilegedAction;33import java.security.PrivilegedActionException;34import java.security.PrivilegedExceptionAction;35import java.security.Security;36import java.security.URIParameter;37import java.text.MessageFormat;38import java.util.*;39import javax.security.auth.AuthPermission;40import javax.security.auth.login.AppConfigurationEntry;41import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;42import javax.security.auth.login.Configuration;43import javax.security.auth.login.ConfigurationSpi;44import sun.security.util.Debug;45import sun.security.util.PropertyExpander;46import sun.security.util.ResourcesMgr;4748import static java.nio.charset.StandardCharsets.UTF_8;4950/**51* This class represents a default implementation for52* {@code javax.security.auth.login.Configuration}.53*54* <p> This object stores the runtime login configuration representation,55* and is the amalgamation of multiple static login configurations that56* resides in files. The algorithm for locating the login configuration57* file(s) and reading their information into this {@code Configuration}58* object is:59*60* <ol>61* <li>62* Loop through the security properties,63* <i>login.config.url.1</i>, <i>login.config.url.2</i>, ...,64* <i>login.config.url.X</i>.65* Each property value specifies a {@code URL} pointing to a66* login configuration file to be loaded. Read in and load67* each configuration.68*69* <li>70* The {@code java.lang.System} property71* <i>java.security.auth.login.config</i>72* may also be set to a {@code URL} pointing to another73* login configuration file74* (which is the case when a user uses the -D switch at runtime).75* If this property is defined, and its use is allowed by the76* security property file (the Security property,77* <i>policy.allowSystemProperty</i> is set to <i>true</i>),78* also load that login configuration.79*80* <li>81* If the <i>java.security.auth.login.config</i> property is defined using82* "==" (rather than "="), then ignore all other specified83* login configurations and only load this configuration.84*85* <li>86* If no system or security properties were set, try to read from the file,87* ${user.home}/.java.login.config, where ${user.home} is the value88* represented by the "user.home" System property.89* </ol>90*91* <p> The configuration syntax supported by this implementation92* is exactly that syntax specified in the93* {@code javax.security.auth.login.Configuration} class.94*95* @see javax.security.auth.login.LoginContext96* @see java.security.Security security properties97*/98public final class ConfigFile extends Configuration {99100private final Spi spi;101102public ConfigFile() {103spi = new Spi();104}105106@Override107public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {108return spi.engineGetAppConfigurationEntry(appName);109}110111@Override112public synchronized void refresh() {113spi.engineRefresh();114}115116public static final class Spi extends ConfigurationSpi {117118private URL url;119private boolean expandProp = true;120private Map<String, List<AppConfigurationEntry>> configuration;121private int linenum;122private StreamTokenizer st;123private int lookahead;124125private static Debug debugConfig = Debug.getInstance("configfile");126private static Debug debugParser = Debug.getInstance("configparser");127128/**129* Creates a new {@code ConfigurationSpi} object.130*131* @throws SecurityException if the {@code ConfigurationSpi} can not be132* initialized133*/134public Spi() {135try {136init();137} catch (IOException ioe) {138throw new SecurityException(ioe);139}140}141142/**143* Creates a new {@code ConfigurationSpi} object from the specified144* {@code URI}.145*146* @param uri the {@code URI}147* @throws SecurityException if the {@code ConfigurationSpi} can not be148* initialized149* @throws NullPointerException if {@code uri} is null150*/151public Spi(URI uri) {152// only load config from the specified URI153try {154url = uri.toURL();155init();156} catch (IOException ioe) {157throw new SecurityException(ioe);158}159}160161@SuppressWarnings("removal")162public Spi(final Configuration.Parameters params) throws IOException {163164// call in a doPrivileged165//166// we have already passed the Configuration.getInstance167// security check. also this class is not freely accessible168// (it is in the "sun" package).169170try {171AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {172public Void run() throws IOException {173if (params == null) {174init();175} else {176if (!(params instanceof URIParameter)) {177throw new IllegalArgumentException178("Unrecognized parameter: " + params);179}180URIParameter uriParam = (URIParameter)params;181url = uriParam.getURI().toURL();182init();183}184return null;185}186});187} catch (PrivilegedActionException pae) {188throw (IOException)pae.getException();189}190191// if init() throws some other RuntimeException,192// let it percolate up naturally.193}194195/**196* Read and initialize the entire login Configuration from the197* configured URL.198*199* @throws IOException if the Configuration can not be initialized200* @throws SecurityException if the caller does not have permission201* to initialize the Configuration202*/203private void init() throws IOException {204205boolean initialized = false;206207// For policy.expandProperties, check if either a security or system208// property is set to false (old code erroneously checked the system209// prop so we must check both to preserve compatibility).210String expand = Security.getProperty("policy.expandProperties");211if (expand == null) {212expand = System.getProperty("policy.expandProperties");213}214if ("false".equals(expand)) {215expandProp = false;216}217218// new configuration219Map<String, List<AppConfigurationEntry>> newConfig = new HashMap<>();220221if (url != null) {222/**223* If the caller specified a URI via Configuration.getInstance,224* we only read from that URI225*/226if (debugConfig != null) {227debugConfig.println("reading " + url);228}229init(url, newConfig);230configuration = newConfig;231return;232}233234/**235* Caller did not specify URI via Configuration.getInstance.236* Read from URLs listed in the java.security properties file.237*/238String allowSys = Security.getProperty("policy.allowSystemProperty");239240if ("true".equalsIgnoreCase(allowSys)) {241String extra_config = System.getProperty242("java.security.auth.login.config");243if (extra_config != null) {244boolean overrideAll = false;245if (extra_config.startsWith("=")) {246overrideAll = true;247extra_config = extra_config.substring(1);248}249try {250extra_config = PropertyExpander.expand(extra_config);251} catch (PropertyExpander.ExpandException peee) {252throw ioException("Unable.to.properly.expand.config",253extra_config);254}255256URL configURL = null;257try {258configURL = new URL(extra_config);259} catch (MalformedURLException mue) {260File configFile = new File(extra_config);261if (configFile.exists()) {262configURL = configFile.toURI().toURL();263} else {264throw ioException(265"extra.config.No.such.file.or.directory.",266extra_config);267}268}269270if (debugConfig != null) {271debugConfig.println("reading "+configURL);272}273init(configURL, newConfig);274initialized = true;275if (overrideAll) {276if (debugConfig != null) {277debugConfig.println("overriding other policies!");278}279configuration = newConfig;280return;281}282}283}284285int n = 1;286String config_url;287while ((config_url = Security.getProperty288("login.config.url."+n)) != null) {289try {290config_url = PropertyExpander.expand291(config_url).replace(File.separatorChar, '/');292if (debugConfig != null) {293debugConfig.println("\tReading config: " + config_url);294}295init(new URL(config_url), newConfig);296initialized = true;297} catch (PropertyExpander.ExpandException peee) {298throw ioException("Unable.to.properly.expand.config",299config_url);300}301n++;302}303304if (initialized == false && n == 1 && config_url == null) {305306// get the config from the user's home directory307if (debugConfig != null) {308debugConfig.println("\tReading Policy " +309"from ~/.java.login.config");310}311config_url = System.getProperty("user.home");312String userConfigFile = config_url + File.separatorChar +313".java.login.config";314315// No longer throws an exception when there's no config file316// at all. Returns an empty Configuration instead.317if (new File(userConfigFile).exists()) {318init(new File(userConfigFile).toURI().toURL(), newConfig);319}320}321322configuration = newConfig;323}324325private void init(URL config,326Map<String, List<AppConfigurationEntry>> newConfig)327throws IOException {328329try (InputStreamReader isr330= new InputStreamReader(getInputStream(config), UTF_8)) {331readConfig(isr, newConfig);332} catch (FileNotFoundException fnfe) {333if (debugConfig != null) {334debugConfig.println(fnfe.toString());335}336throw new IOException(ResourcesMgr.getAuthResourceString337("Configuration.Error.No.such.file.or.directory"));338}339}340341/**342* Retrieve an entry from the Configuration using an application name343* as an index.344*345* @param applicationName the name used to index the Configuration.346* @return an array of AppConfigurationEntries which correspond to347* the stacked configuration of LoginModules for this348* application, or null if this application has no configured349* LoginModules.350*/351@Override352public AppConfigurationEntry[] engineGetAppConfigurationEntry353(String applicationName) {354355List<AppConfigurationEntry> list = null;356synchronized (configuration) {357list = configuration.get(applicationName);358}359360if (list == null || list.size() == 0) {361return null;362}363364AppConfigurationEntry[] entries =365new AppConfigurationEntry[list.size()];366Iterator<AppConfigurationEntry> iterator = list.iterator();367for (int i = 0; iterator.hasNext(); i++) {368AppConfigurationEntry e = iterator.next();369entries[i] = new AppConfigurationEntry(e.getLoginModuleName(),370e.getControlFlag(),371e.getOptions());372}373return entries;374}375376/**377* Refresh and reload the Configuration by re-reading all of the378* login configurations.379*380* @throws SecurityException if the caller does not have permission381* to refresh the Configuration.382*/383@SuppressWarnings("removal")384@Override385public synchronized void engineRefresh() {386387SecurityManager sm = System.getSecurityManager();388if (sm != null) {389sm.checkPermission(390new AuthPermission("refreshLoginConfiguration"));391}392393AccessController.doPrivileged(new PrivilegedAction<Void>() {394public Void run() {395try {396init();397} catch (IOException ioe) {398throw new SecurityException(ioe.getLocalizedMessage(),399ioe);400}401return null;402}403});404}405406private void readConfig(Reader reader,407Map<String, List<AppConfigurationEntry>> newConfig)408throws IOException {409410linenum = 1;411412if (!(reader instanceof BufferedReader)) {413reader = new BufferedReader(reader);414}415416st = new StreamTokenizer(reader);417st.quoteChar('"');418st.wordChars('$', '$');419st.wordChars('_', '_');420st.wordChars('-', '-');421st.wordChars('*', '*');422st.lowerCaseMode(false);423st.slashSlashComments(true);424st.slashStarComments(true);425st.eolIsSignificant(true);426427lookahead = nextToken();428while (lookahead != StreamTokenizer.TT_EOF) {429parseLoginEntry(newConfig);430}431}432433private void parseLoginEntry(434Map<String, List<AppConfigurationEntry>> newConfig)435throws IOException {436437List<AppConfigurationEntry> configEntries = new LinkedList<>();438439// application name440String appName = st.sval;441lookahead = nextToken();442443if (debugParser != null) {444debugParser.println("\tReading next config entry: " + appName);445}446447match("{");448449// get the modules450while (peek("}") == false) {451// get the module class name452String moduleClass = match("module class name");453454// controlFlag (required, optional, etc)455LoginModuleControlFlag controlFlag;456String sflag = match("controlFlag").toUpperCase(Locale.ENGLISH);457switch (sflag) {458case "REQUIRED":459controlFlag = LoginModuleControlFlag.REQUIRED;460break;461case "REQUISITE":462controlFlag = LoginModuleControlFlag.REQUISITE;463break;464case "SUFFICIENT":465controlFlag = LoginModuleControlFlag.SUFFICIENT;466break;467case "OPTIONAL":468controlFlag = LoginModuleControlFlag.OPTIONAL;469break;470default:471throw ioException(472"Configuration.Error.Invalid.control.flag.flag",473sflag);474}475476// get the args477Map<String, String> options = new HashMap<>();478while (peek(";") == false) {479String key = match("option key");480match("=");481try {482options.put(key, expand(match("option value")));483} catch (PropertyExpander.ExpandException peee) {484throw new IOException(peee.getLocalizedMessage());485}486}487488lookahead = nextToken();489490// create the new element491if (debugParser != null) {492debugParser.println("\t\t" + moduleClass + ", " + sflag);493for (String key : options.keySet()) {494debugParser.println("\t\t\t" + key +495"=" + options.get(key));496}497}498configEntries.add(new AppConfigurationEntry(moduleClass,499controlFlag,500options));501}502503match("}");504match(";");505506// add this configuration entry507if (newConfig.containsKey(appName)) {508throw ioException(509"Configuration.Error.Can.not.specify.multiple.entries.for.appName",510appName);511}512newConfig.put(appName, configEntries);513}514515private String match(String expect) throws IOException {516517String value = null;518519switch(lookahead) {520case StreamTokenizer.TT_EOF:521throw ioException(522"Configuration.Error.expected.expect.read.end.of.file.",523expect);524525case '"':526case StreamTokenizer.TT_WORD:527if (expect.equalsIgnoreCase("module class name") ||528expect.equalsIgnoreCase("controlFlag") ||529expect.equalsIgnoreCase("option key") ||530expect.equalsIgnoreCase("option value")) {531value = st.sval;532lookahead = nextToken();533} else {534throw ioException(535"Configuration.Error.Line.line.expected.expect.found.value.",536linenum, expect, st.sval);537}538break;539540case '{':541if (expect.equalsIgnoreCase("{")) {542lookahead = nextToken();543} else {544throw ioException(545"Configuration.Error.Line.line.expected.expect.",546linenum, expect, st.sval);547}548break;549550case ';':551if (expect.equalsIgnoreCase(";")) {552lookahead = nextToken();553} else {554throw ioException(555"Configuration.Error.Line.line.expected.expect.",556linenum, expect, st.sval);557}558break;559560case '}':561if (expect.equalsIgnoreCase("}")) {562lookahead = nextToken();563} else {564throw ioException(565"Configuration.Error.Line.line.expected.expect.",566linenum, expect, st.sval);567}568break;569570case '=':571if (expect.equalsIgnoreCase("=")) {572lookahead = nextToken();573} else {574throw ioException(575"Configuration.Error.Line.line.expected.expect.",576linenum, expect, st.sval);577}578break;579580default:581throw ioException(582"Configuration.Error.Line.line.expected.expect.found.value.",583linenum, expect, st.sval);584}585return value;586}587588private boolean peek(String expect) {589switch (lookahead) {590case ',':591return expect.equalsIgnoreCase(",");592case ';':593return expect.equalsIgnoreCase(";");594case '{':595return expect.equalsIgnoreCase("{");596case '}':597return expect.equalsIgnoreCase("}");598default:599return false;600}601}602603private int nextToken() throws IOException {604int tok;605while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) {606linenum++;607}608return tok;609}610611private InputStream getInputStream(URL url) throws IOException {612if ("file".equalsIgnoreCase(url.getProtocol())) {613// Compatibility notes:614//615// Code changed from616// String path = url.getFile().replace('/', File.separatorChar);617// return new FileInputStream(path);618//619// The original implementation would search for "/tmp/a%20b"620// when url is "file:///tmp/a%20b". This is incorrect. The621// current codes fix this bug and searches for "/tmp/a b".622// For compatibility reasons, when the file "/tmp/a b" does623// not exist, the file named "/tmp/a%20b" will be tried.624//625// This also means that if both file exists, the behavior of626// this method is changed, and the current codes choose the627// correct one.628try {629return url.openStream();630} catch (Exception e) {631String file = url.getPath();632if (!url.getHost().isEmpty()) { // For Windows UNC633file = "//" + url.getHost() + file;634}635if (debugConfig != null) {636debugConfig.println("cannot read " + url +637", try " + file);638}639return new FileInputStream(file);640}641} else {642return url.openStream();643}644}645646private String expand(String value)647throws PropertyExpander.ExpandException, IOException {648649if (value.isEmpty()) {650return value;651}652653if (!expandProp) {654return value;655}656String s = PropertyExpander.expand(value);657if (s == null || s.isEmpty()) {658throw ioException(659"Configuration.Error.Line.line.system.property.value.expanded.to.empty.value",660linenum, value);661}662return s;663}664665private IOException ioException(String resourceKey, Object... args) {666MessageFormat form = new MessageFormat(667ResourcesMgr.getAuthResourceString(resourceKey));668return new IOException(form.format(args));669}670}671}672673674