1 /*
   2  * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.security.auth.login;
  27 
  28 import javax.security.auth.AuthPermission;
  29 import javax.security.auth.login.AppConfigurationEntry;
  30 import java.io.*;
  31 import java.util.*;
  32 import java.net.URI;
  33 import java.net.URL;
  34 import java.net.MalformedURLException;
  35 import java.text.MessageFormat;
  36 import sun.security.util.Debug;
  37 import sun.security.util.ResourcesMgr;
  38 import sun.security.util.PropertyExpander;
  39 
  40 /**
  41  * This class represents a default implementation for
  42  * <code>javax.security.auth.login.Configuration</code>.
  43  *
  44  * <p> This object stores the runtime login configuration representation,
  45  * and is the amalgamation of multiple static login
  46  * configurations that resides in files.
  47  * The algorithm for locating the login configuration file(s) and reading their
  48  * information into this <code>Configuration</code> object is:
  49  *
  50  * <ol>
  51  * <li>
  52  *   Loop through the <code>java.security.Security</code> properties,
  53  *   <i>login.config.url.1</i>, <i>login.config.url.2</i>, ...,
  54  *   <i>login.config.url.X</i>.  These properties are set
  55  *   in the Java security properties file, which is located in the file named
  56  *   &lt;JAVA_HOME&gt;/lib/security/java.security.
  57  *   &lt;JAVA_HOME&gt; refers to the value of the java.home system property,
  58  *   and specifies the directory where the JRE is installed.
  59  *   Each property value specifies a <code>URL</code> pointing to a
  60  *   login configuration file to be loaded.  Read in and load
  61  *   each configuration.
  62  *
  63  * <li>
  64  *   The <code>java.lang.System</code> property
  65  *   <i>java.security.auth.login.config</i>
  66  *   may also be set to a <code>URL</code> pointing to another
  67  *   login configuration file
  68  *   (which is the case when a user uses the -D switch at runtime).
  69  *   If this property is defined, and its use is allowed by the
  70  *   security property file (the Security property,
  71  *   <i>policy.allowSystemProperty</i> is set to <i>true</i>),
  72  *   also load that login configuration.
  73  *
  74  * <li>
  75  *   If the <i>java.security.auth.login.config</i> property is defined using
  76  *   "==" (rather than "="), then ignore all other specified
  77  *   login configurations and only load this configuration.
  78  *
  79  * <li>
  80  *   If no system or security properties were set, try to read from the file,
  81  *   ${user.home}/.java.login.config, where ${user.home} is the value
  82  *   represented by the "user.home" System property.
  83  * </ol>
  84  *
  85  * <p> The configuration syntax supported by this implementation
  86  * is exactly that syntax specified in the
  87  * <code>javax.security.auth.login.Configuration</code> class.
  88  *
  89  * @see javax.security.auth.login.LoginContext
  90  */
  91 public class ConfigFile extends javax.security.auth.login.Configuration {
  92 
  93     private StreamTokenizer st;
  94     private int lookahead;
  95     private int linenum;
  96     private HashMap<String, LinkedList<AppConfigurationEntry>> configuration;
  97     private boolean expandProp = true;
  98     private URL url;
  99 
 100     private static Debug debugConfig = Debug.getInstance("configfile");
 101     private static Debug debugParser = Debug.getInstance("configparser");
 102 
 103     /**
 104      * Create a new <code>Configuration</code> object.
 105      */
 106     public ConfigFile() {
 107         try {
 108             init(url);
 109         } catch (IOException ioe) {
 110             throw (SecurityException)
 111                 new SecurityException(ioe.getMessage()).initCause(ioe);
 112         }
 113     }
 114 
 115     /**
 116      * Create a new <code>Configuration</code> object from the specified URI.
 117      *
 118      * @param uri Create a new Configuration object from this URI.
 119      */
 120     public ConfigFile(URI uri) {
 121         // only load config from the specified URI
 122         try {
 123             url = uri.toURL();
 124             init(url);
 125         } catch (MalformedURLException mue) {
 126             throw (SecurityException)
 127                 new SecurityException(mue.getMessage()).initCause(mue);
 128         } catch (IOException ioe) {
 129             throw (SecurityException)
 130                 new SecurityException(ioe.getMessage()).initCause(ioe);
 131         }
 132     }
 133 
 134     /**
 135      * Read and initialize the entire login Configuration.
 136      *
 137      * <p>
 138      *
 139      * @exception IOException if the Configuration can not be initialized. <p>
 140      * @exception SecurityException if the caller does not have permission
 141      *                          to initialize the Configuration.
 142      */
 143     private void init(URL url) throws IOException {
 144 
 145         boolean initialized = false;
 146         FileReader fr = null;
 147         String sep = File.separator;
 148 
 149         if ("false".equals(System.getProperty("policy.expandProperties"))) {
 150             expandProp = false;
 151         }
 152 
 153         // new configuration
 154         HashMap<String, LinkedList<AppConfigurationEntry>> newConfig =
 155                 new HashMap<String, LinkedList<AppConfigurationEntry>>();
 156 
 157         if (url != null) {
 158 
 159             /**
 160              * If the caller specified a URI via Configuration.getInstance,
 161              * we only read from that URI
 162              */
 163             if (debugConfig != null) {
 164                 debugConfig.println("reading " + url);
 165             }
 166             init(url, newConfig);
 167             configuration = newConfig;
 168             return;
 169         }
 170 
 171         /**
 172          * Caller did not specify URI via Configuration.getInstance.
 173          * Read from URLs listed in the java.security properties file.
 174          */
 175 
 176         String allowSys = java.security.Security.getProperty
 177                                                 ("policy.allowSystemProperty");
 178 
 179         if ("true".equalsIgnoreCase(allowSys)) {
 180             String extra_config = System.getProperty
 181                                         ("java.security.auth.login.config");
 182             if (extra_config != null) {
 183                 boolean overrideAll = false;
 184                 if (extra_config.startsWith("=")) {
 185                     overrideAll = true;
 186                     extra_config = extra_config.substring(1);
 187                 }
 188                 try {
 189                     extra_config = PropertyExpander.expand(extra_config);
 190                 } catch (PropertyExpander.ExpandException peee) {
 191                     MessageFormat form = new MessageFormat
 192                         (ResourcesMgr.getString
 193                                 ("Unable.to.properly.expand.config",
 194                                 "sun.security.util.AuthResources"));
 195                     Object[] source = {extra_config};
 196                     throw new IOException(form.format(source));
 197                 }
 198 
 199                 URL configURL = null;
 200                 try {
 201                     configURL = new URL(extra_config);
 202                 } catch (java.net.MalformedURLException mue) {
 203                     File configFile = new File(extra_config);
 204                     if (configFile.exists()) {
 205                         configURL = configFile.toURI().toURL();
 206                     } else {
 207                         MessageFormat form = new MessageFormat
 208                             (ResourcesMgr.getString
 209                                 ("extra.config.No.such.file.or.directory.",
 210                                 "sun.security.util.AuthResources"));
 211                         Object[] source = {extra_config};
 212                         throw new IOException(form.format(source));
 213                     }
 214                 }
 215 
 216                 if (debugConfig != null) {
 217                     debugConfig.println("reading "+configURL);
 218                 }
 219                 init(configURL, newConfig);
 220                 initialized = true;
 221                 if (overrideAll) {
 222                     if (debugConfig != null) {
 223                         debugConfig.println("overriding other policies!");
 224                     }
 225                     configuration = newConfig;
 226                     return;
 227                 }
 228             }
 229         }
 230 
 231         int n = 1;
 232         String config_url;
 233         while ((config_url = java.security.Security.getProperty
 234                                         ("login.config.url."+n)) != null) {
 235             try {
 236                 config_url = PropertyExpander.expand
 237                         (config_url).replace(File.separatorChar, '/');
 238                 if (debugConfig != null) {
 239                     debugConfig.println("\tReading config: " + config_url);
 240                 }
 241                 init(new URL(config_url), newConfig);
 242                 initialized = true;
 243             } catch (PropertyExpander.ExpandException peee) {
 244                 MessageFormat form = new MessageFormat
 245                         (ResourcesMgr.getString
 246                                 ("Unable.to.properly.expand.config",
 247                                 "sun.security.util.AuthResources"));
 248                 Object[] source = {config_url};
 249                 throw new IOException(form.format(source));
 250             }
 251             n++;
 252         }
 253 
 254         if (initialized == false && n == 1 && config_url == null) {
 255 
 256             // get the config from the user's home directory
 257             if (debugConfig != null) {
 258                 debugConfig.println("\tReading Policy " +
 259                                 "from ~/.java.login.config");
 260             }
 261             config_url = System.getProperty("user.home");
 262             String userConfigFile = config_url +
 263                       File.separatorChar + ".java.login.config";
 264 
 265             // No longer throws an exception when there's no config file
 266             // at all. Returns an empty Configuration instead.
 267             if (new File(userConfigFile).exists()) {
 268                 init(new File(userConfigFile).toURI().toURL(),
 269                     newConfig);
 270             }
 271         }
 272 
 273         configuration = newConfig;
 274     }
 275 
 276     private void init(URL config,
 277         HashMap<String, LinkedList<AppConfigurationEntry>> newConfig)
 278         throws IOException {
 279 
 280         InputStreamReader isr = null;
 281         try {
 282             isr = new InputStreamReader(getInputStream(config), "UTF-8");
 283             readConfig(isr, newConfig);
 284         } catch (FileNotFoundException fnfe) {
 285             if (debugConfig != null) {
 286                 debugConfig.println(fnfe.toString());
 287             }
 288             throw new IOException(ResourcesMgr.getString
 289                     ("Configuration.Error.No.such.file.or.directory",
 290                     "sun.security.util.AuthResources"));
 291         } finally {
 292             if (isr != null) {
 293                 isr.close();
 294             }
 295         }
 296     }
 297 
 298     /**
 299      * Retrieve an entry from the Configuration using an application name
 300      * as an index.
 301      *
 302      * <p>
 303      *
 304      * @param applicationName the name used to index the Configuration.
 305      * @return an array of AppConfigurationEntries which correspond to
 306      *          the stacked configuration of LoginModules for this
 307      *          application, or null if this application has no configured
 308      *          LoginModules.
 309      */
 310     public AppConfigurationEntry[] getAppConfigurationEntry
 311     (String applicationName) {
 312 
 313         LinkedList<AppConfigurationEntry> list = null;
 314         synchronized (configuration) {
 315             list = configuration.get(applicationName);
 316         }
 317 
 318         if (list == null || list.size() == 0)
 319             return null;
 320 
 321         AppConfigurationEntry[] entries =
 322                                 new AppConfigurationEntry[list.size()];
 323         Iterator<AppConfigurationEntry> iterator = list.iterator();
 324         for (int i = 0; iterator.hasNext(); i++) {
 325             AppConfigurationEntry e = iterator.next();
 326             entries[i] = new AppConfigurationEntry(e.getLoginModuleName(),
 327                                                 e.getControlFlag(),
 328                                                 e.getOptions());
 329         }
 330         return entries;
 331     }
 332 
 333     /**
 334      * Refresh and reload the Configuration by re-reading all of the
 335      * login configurations.
 336      *
 337      * <p>
 338      *
 339      * @exception SecurityException if the caller does not have permission
 340      *                          to refresh the Configuration.
 341      */
 342     public synchronized void refresh() {
 343 
 344         java.lang.SecurityManager sm = System.getSecurityManager();
 345         if (sm != null)
 346             sm.checkPermission(new AuthPermission("refreshLoginConfiguration"));
 347 
 348         java.security.AccessController.doPrivileged
 349             (new java.security.PrivilegedAction<Void>() {
 350             public Void run() {
 351                 try {
 352                     init(url);
 353                 } catch (java.io.IOException ioe) {
 354                     throw (SecurityException) new SecurityException
 355                                 (ioe.getLocalizedMessage()).initCause(ioe);
 356                 }
 357                 return null;
 358             }
 359         });
 360     }
 361 
 362     private void readConfig(Reader reader,
 363         HashMap<String, LinkedList<AppConfigurationEntry>> newConfig)
 364         throws IOException {
 365 
 366         int linenum = 1;
 367 
 368         if (!(reader instanceof BufferedReader))
 369             reader = new BufferedReader(reader);
 370 
 371         st = new StreamTokenizer(reader);
 372         st.quoteChar('"');
 373         st.wordChars('$', '$');
 374         st.wordChars('_', '_');
 375         st.wordChars('-', '-');
 376         st.lowerCaseMode(false);
 377         st.slashSlashComments(true);
 378         st.slashStarComments(true);
 379         st.eolIsSignificant(true);
 380 
 381         lookahead = nextToken();
 382         while (lookahead != StreamTokenizer.TT_EOF) {
 383             parseLoginEntry(newConfig);
 384         }
 385     }
 386 
 387     private void parseLoginEntry(
 388         HashMap<String, LinkedList<AppConfigurationEntry>> newConfig)
 389         throws IOException {
 390 
 391         String appName;
 392         String moduleClass;
 393         String sflag;
 394         AppConfigurationEntry.LoginModuleControlFlag controlFlag;
 395         LinkedList<AppConfigurationEntry> configEntries =
 396                                 new LinkedList<AppConfigurationEntry>();
 397 
 398         // application name
 399         appName = st.sval;
 400         lookahead = nextToken();
 401 
 402         if (debugParser != null) {
 403             debugParser.println("\tReading next config entry: " + appName);
 404         }
 405 
 406         match("{");
 407 
 408         // get the modules
 409         while (peek("}") == false) {
 410             // get the module class name
 411             moduleClass = match("module class name");
 412 
 413             // controlFlag (required, optional, etc)
 414             sflag = match("controlFlag");
 415             if (sflag.equalsIgnoreCase("REQUIRED"))
 416                 controlFlag =
 417                         AppConfigurationEntry.LoginModuleControlFlag.REQUIRED;
 418             else if (sflag.equalsIgnoreCase("REQUISITE"))
 419                 controlFlag =
 420                         AppConfigurationEntry.LoginModuleControlFlag.REQUISITE;
 421             else if (sflag.equalsIgnoreCase("SUFFICIENT"))
 422                 controlFlag =
 423                         AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT;
 424             else if (sflag.equalsIgnoreCase("OPTIONAL"))
 425                 controlFlag =
 426                         AppConfigurationEntry.LoginModuleControlFlag.OPTIONAL;
 427             else {
 428                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 429                         ("Configuration.Error.Invalid.control.flag.flag",
 430                         "sun.security.util.AuthResources"));
 431                 Object[] source = {sflag};
 432                 throw new IOException(form.format(source));
 433             }
 434 
 435             // get the args
 436             HashMap<String, String> options = new HashMap<String, String>();
 437             String key;
 438             String value;
 439             while (peek(";") == false) {
 440                 key = match("option key");
 441                 match("=");
 442                 try {
 443                     value = expand(match("option value"));
 444                 } catch (PropertyExpander.ExpandException peee) {
 445                     throw new IOException(peee.getLocalizedMessage());
 446                 }
 447                 options.put(key, value);
 448             }
 449 
 450             lookahead = nextToken();
 451 
 452             // create the new element
 453             if (debugParser != null) {
 454                 debugParser.println("\t\t" + moduleClass + ", " + sflag);
 455                 java.util.Iterator<String> i = options.keySet().iterator();
 456                 while (i.hasNext()) {
 457                     key = i.next();
 458                     debugParser.println("\t\t\t" +
 459                                         key +
 460                                         "=" +
 461                                         options.get(key));
 462                 }
 463             }
 464             AppConfigurationEntry entry = new AppConfigurationEntry
 465                                                         (moduleClass,
 466                                                         controlFlag,
 467                                                         options);
 468             configEntries.add(entry);
 469         }
 470 
 471         match("}");
 472         match(";");
 473 
 474         // add this configuration entry
 475         if (newConfig.containsKey(appName)) {
 476             MessageFormat form = new MessageFormat(ResourcesMgr.getString
 477                 ("Configuration.Error.Can.not.specify.multiple.entries.for.appName",
 478                 "sun.security.util.AuthResources"));
 479             Object[] source = {appName};
 480             throw new IOException(form.format(source));
 481         }
 482         newConfig.put(appName, configEntries);
 483     }
 484 
 485     private String match(String expect) throws IOException {
 486 
 487         String value = null;
 488 
 489         switch(lookahead) {
 490         case StreamTokenizer.TT_EOF:
 491 
 492             MessageFormat form1 = new MessageFormat(ResourcesMgr.getString
 493                 ("Configuration.Error.expected.expect.read.end.of.file.",
 494                 "sun.security.util.AuthResources"));
 495             Object[] source1 = {expect};
 496             throw new IOException(form1.format(source1));
 497 
 498         case '"':
 499         case StreamTokenizer.TT_WORD:
 500 
 501             if (expect.equalsIgnoreCase("module class name") ||
 502                 expect.equalsIgnoreCase("controlFlag") ||
 503                 expect.equalsIgnoreCase("option key") ||
 504                 expect.equalsIgnoreCase("option value")) {
 505                 value = st.sval;
 506                 lookahead = nextToken();
 507             } else {
 508                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 509                         ("Configuration.Error.Line.line.expected.expect.found.value.",
 510                         "sun.security.util.AuthResources"));
 511                 Object[] source = {new Integer(linenum), expect, st.sval};
 512                 throw new IOException(form.format(source));
 513             }
 514             break;
 515 
 516         case '{':
 517 
 518             if (expect.equalsIgnoreCase("{")) {
 519                 lookahead = nextToken();
 520             } else {
 521                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 522                         ("Configuration.Error.Line.line.expected.expect.",
 523                         "sun.security.util.AuthResources"));
 524                 Object[] source = {new Integer(linenum), expect, st.sval};
 525                 throw new IOException(form.format(source));
 526             }
 527             break;
 528 
 529         case ';':
 530 
 531             if (expect.equalsIgnoreCase(";")) {
 532                 lookahead = nextToken();
 533             } else {
 534                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 535                         ("Configuration.Error.Line.line.expected.expect.",
 536                         "sun.security.util.AuthResources"));
 537                 Object[] source = {new Integer(linenum), expect, st.sval};
 538                 throw new IOException(form.format(source));
 539             }
 540             break;
 541 
 542         case '}':
 543 
 544             if (expect.equalsIgnoreCase("}")) {
 545                 lookahead = nextToken();
 546             } else {
 547                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 548                         ("Configuration.Error.Line.line.expected.expect.",
 549                         "sun.security.util.AuthResources"));
 550                 Object[] source = {new Integer(linenum), expect, st.sval};
 551                 throw new IOException(form.format(source));
 552             }
 553             break;
 554 
 555         case '=':
 556 
 557             if (expect.equalsIgnoreCase("=")) {
 558                 lookahead = nextToken();
 559             } else {
 560                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 561                         ("Configuration.Error.Line.line.expected.expect.",
 562                         "sun.security.util.AuthResources"));
 563                 Object[] source = {new Integer(linenum), expect, st.sval};
 564                 throw new IOException(form.format(source));
 565             }
 566             break;
 567 
 568         default:
 569             MessageFormat form = new MessageFormat(ResourcesMgr.getString
 570                         ("Configuration.Error.Line.line.expected.expect.found.value.",
 571                         "sun.security.util.AuthResources"));
 572             Object[] source = {new Integer(linenum), expect, st.sval};
 573             throw new IOException(form.format(source));
 574         }
 575         return value;
 576     }
 577 
 578     private boolean peek(String expect) {
 579         boolean found = false;
 580 
 581         switch (lookahead) {
 582         case ',':
 583             if (expect.equalsIgnoreCase(","))
 584                 found = true;
 585             break;
 586         case ';':
 587             if (expect.equalsIgnoreCase(";"))
 588                 found = true;
 589             break;
 590         case '{':
 591             if (expect.equalsIgnoreCase("{"))
 592                 found = true;
 593             break;
 594         case '}':
 595             if (expect.equalsIgnoreCase("}"))
 596                 found = true;
 597             break;
 598         default:
 599         }
 600         return found;
 601     }
 602 
 603     private int nextToken() throws IOException {
 604         int tok;
 605         while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) {
 606             linenum++;
 607         }
 608         return tok;
 609     }
 610 
 611     /*
 612      * Fast path reading from file urls in order to avoid calling
 613      * FileURLConnection.connect() which can be quite slow the first time
 614      * it is called. We really should clean up FileURLConnection so that
 615      * this is not a problem but in the meantime this fix helps reduce
 616      * start up time noticeably for the new launcher. -- DAC
 617      */
 618     private InputStream getInputStream(URL url) throws IOException {
 619         if ("file".equals(url.getProtocol())) {
 620             String path = url.getFile().replace('/', File.separatorChar);
 621             return new FileInputStream(path);
 622         } else {
 623             return url.openStream();
 624         }
 625     }
 626 
 627     private String expand(String value)
 628         throws PropertyExpander.ExpandException, IOException {
 629 
 630         if ("".equals(value)) {
 631             return value;
 632         }
 633 
 634         if (expandProp) {
 635 
 636             String s = PropertyExpander.expand(value);
 637 
 638             if (s == null || s.length() == 0) {
 639                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 640                         ("Configuration.Error.Line.line.system.property.value.expanded.to.empty.value",
 641                         "sun.security.util.AuthResources"));
 642                 Object[] source = {new Integer(linenum), value};
 643                 throw new IOException(form.format(source));
 644             }
 645             return s;
 646         } else {
 647             return value;
 648         }
 649     }
 650 }