1 /*
   2  * Copyright (c) 2000, 2006, 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:\n\tNo 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:\n\tInvalid 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:\n\t" +
 478                         "Can not specify multiple entries for appName",
 479                 "sun.security.util.AuthResources"));
 480             Object[] source = {appName};
 481             throw new IOException(form.format(source));
 482         }
 483         newConfig.put(appName, configEntries);
 484     }
 485 
 486     private String match(String expect) throws IOException {
 487 
 488         String value = null;
 489 
 490         switch(lookahead) {
 491         case StreamTokenizer.TT_EOF:
 492 
 493             MessageFormat form1 = new MessageFormat(ResourcesMgr.getString
 494                 ("Configuration Error:\n\texpected [expect], " +
 495                         "read [end of file]",
 496                 "sun.security.util.AuthResources"));
 497             Object[] source1 = {expect};
 498             throw new IOException(form1.format(source1));
 499 
 500         case '"':
 501         case StreamTokenizer.TT_WORD:
 502 
 503             if (expect.equalsIgnoreCase("module class name") ||
 504                 expect.equalsIgnoreCase("controlFlag") ||
 505                 expect.equalsIgnoreCase("option key") ||
 506                 expect.equalsIgnoreCase("option value")) {
 507                 value = st.sval;
 508                 lookahead = nextToken();
 509             } else {
 510                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 511                         ("Configuration Error:\n\tLine line: " +
 512                                 "expected [expect], found [value]",
 513                         "sun.security.util.AuthResources"));
 514                 Object[] source = {new Integer(linenum), expect, st.sval};
 515                 throw new IOException(form.format(source));
 516             }
 517             break;
 518 
 519         case '{':
 520 
 521             if (expect.equalsIgnoreCase("{")) {
 522                 lookahead = nextToken();
 523             } else {
 524                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 525                         ("Configuration Error:\n\tLine line: expected [expect]",
 526                         "sun.security.util.AuthResources"));
 527                 Object[] source = {new Integer(linenum), expect, st.sval};
 528                 throw new IOException(form.format(source));
 529             }
 530             break;
 531 
 532         case ';':
 533 
 534             if (expect.equalsIgnoreCase(";")) {
 535                 lookahead = nextToken();
 536             } else {
 537                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 538                         ("Configuration Error:\n\tLine line: expected [expect]",
 539                         "sun.security.util.AuthResources"));
 540                 Object[] source = {new Integer(linenum), expect, st.sval};
 541                 throw new IOException(form.format(source));
 542             }
 543             break;
 544 
 545         case '}':
 546 
 547             if (expect.equalsIgnoreCase("}")) {
 548                 lookahead = nextToken();
 549             } else {
 550                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 551                         ("Configuration Error:\n\tLine line: expected [expect]",
 552                         "sun.security.util.AuthResources"));
 553                 Object[] source = {new Integer(linenum), expect, st.sval};
 554                 throw new IOException(form.format(source));
 555             }
 556             break;
 557 
 558         case '=':
 559 
 560             if (expect.equalsIgnoreCase("=")) {
 561                 lookahead = nextToken();
 562             } else {
 563                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 564                         ("Configuration Error:\n\tLine line: expected [expect]",
 565                         "sun.security.util.AuthResources"));
 566                 Object[] source = {new Integer(linenum), expect, st.sval};
 567                 throw new IOException(form.format(source));
 568             }
 569             break;
 570 
 571         default:
 572             MessageFormat form = new MessageFormat(ResourcesMgr.getString
 573                         ("Configuration Error:\n\tLine line: " +
 574                                 "expected [expect], found [value]",
 575                         "sun.security.util.AuthResources"));
 576             Object[] source = {new Integer(linenum), expect, st.sval};
 577             throw new IOException(form.format(source));
 578         }
 579         return value;
 580     }
 581 
 582     private boolean peek(String expect) {
 583         boolean found = false;
 584 
 585         switch (lookahead) {
 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         case '}':
 599             if (expect.equalsIgnoreCase("}"))
 600                 found = true;
 601             break;
 602         default:
 603         }
 604         return found;
 605     }
 606 
 607     private int nextToken() throws IOException {
 608         int tok;
 609         while ((tok = st.nextToken()) == StreamTokenizer.TT_EOL) {
 610             linenum++;
 611         }
 612         return tok;
 613     }
 614 
 615     /*
 616      * Fast path reading from file urls in order to avoid calling
 617      * FileURLConnection.connect() which can be quite slow the first time
 618      * it is called. We really should clean up FileURLConnection so that
 619      * this is not a problem but in the meantime this fix helps reduce
 620      * start up time noticeably for the new launcher. -- DAC
 621      */
 622     private InputStream getInputStream(URL url) throws IOException {
 623         if ("file".equals(url.getProtocol())) {
 624             String path = url.getFile().replace('/', File.separatorChar);
 625             return new FileInputStream(path);
 626         } else {
 627             return url.openStream();
 628         }
 629     }
 630 
 631     private String expand(String value)
 632         throws PropertyExpander.ExpandException, IOException {
 633 
 634         if ("".equals(value)) {
 635             return value;
 636         }
 637 
 638         if (expandProp) {
 639 
 640             String s = PropertyExpander.expand(value);
 641 
 642             if (s == null || s.length() == 0) {
 643                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
 644                         ("Configuration Error:\n\tLine line: " +
 645                         "system property [value] expanded to empty value",
 646                         "sun.security.util.AuthResources"));
 647                 Object[] source = {new Integer(linenum), value};
 648                 throw new IOException(form.format(source));
 649             }
 650             return s;
 651         } else {
 652             return value;
 653         }
 654     }
 655 }