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 * <JAVA_HOME>/lib/security/java.security. 57 * <JAVA_HOME> 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 }