1 /*
2 * Copyright 2000-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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".equalsIgnoreCase(url.getProtocol())) {
624 // Compatibility notes:
625 //
626 // Code changed from
627 // String path = url.getFile().replace('/', File.separatorChar);
628 // return new FileInputStream(path);
629 //
630 // The original implementation would search for "/tmp/a%20b"
631 // when url is "file:///tmp/a%20b". This is incorrect. The
632 // current codes fix this bug and searches for "/tmp/a b".
633 // For compatibility reasons, when the file "/tmp/a b" does
634 // not exist, the file named "/tmp/a%20b" will be tried.
635 //
636 // This also means that if both file exists, the behavior of
637 // this method is changed, and the current codes choose the
638 // correct one.
639 try {
640 return url.openStream();
641 } catch (Exception e) {
642 String file = url.getPath();
643 if (url.getHost().length() > 0) { // For Windows UNC
644 file = "//" + url.getHost() + file;
645 }
646 if (debugConfig != null) {
647 debugConfig.println("cannot read " + url +
648 ", try " + file);
649 }
650 return new FileInputStream(file);
651 }
652 } else {
653 return url.openStream();
654 }
655 }
656
657 private String expand(String value)
658 throws PropertyExpander.ExpandException, IOException {
659
660 if ("".equals(value)) {
661 return value;
662 }
663
664 if (expandProp) {
665
666 String s = PropertyExpander.expand(value);
667
668 if (s == null || s.length() == 0) {
669 MessageFormat form = new MessageFormat(ResourcesMgr.getString
670 ("Configuration Error:\n\tLine line: " +
671 "system property [value] expanded to empty value",
672 "sun.security.util.AuthResources"));
673 Object[] source = {new Integer(linenum), value};
674 throw new IOException(form.format(source));
675 }
676 return s;
677 } else {
678 return value;
679 }
680 }
681 }