blob: 013fd1da1365c4f092e54417ac4fe367f1e607ab [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Portions Copyright 2000-2007 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/*
27 *
28 * (C) Copyright IBM Corp. 1999 All Rights Reserved.
29 * Copyright 1997 The Open Group Research Institute. All rights reserved.
30 */
31package sun.security.krb5;
32
33import java.io.File;
34import java.io.FileInputStream;
35import java.util.Hashtable;
36import java.util.Vector;
37import java.util.ArrayList;
38import java.io.BufferedReader;
39import java.io.InputStreamReader;
40import java.io.IOException;
41import java.util.Enumeration;
42import java.util.List;
43import java.util.StringTokenizer;
44import java.net.InetAddress;
45import java.net.UnknownHostException;
46import sun.security.krb5.internal.crypto.EType;
47import sun.security.krb5.internal.ktab.*;
48import sun.security.krb5.internal.Krb5;
49
50/**
51 * This class maintains key-value pairs of Kerberos configurable constants
52 * from configuration file or from user specified system properties.
53 */
54
55public class Config {
56
57 /*
58 * Only allow a single instance of Config.
59 */
60 private static Config singleton = null;
61
62 /*
63 * Hashtable used to store configuration infomation.
64 */
65 private Hashtable<String,Object> stanzaTable;
66
67 private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
68
69 // these are used for hexdecimal calculation.
70 private static final int BASE16_0 = 1;
71 private static final int BASE16_1 = 16;
72 private static final int BASE16_2 = 16 * 16;
73 private static final int BASE16_3 = 16 * 16 * 16;
74 private String defaultRealm; // default kdc realm.
75
76 // used for native interface
77 private static native String getWindowsDirectory();
78
79
80 /**
81 * Gets an instance of Config class. One and only one instance (the
82 * singleton) is returned.
83 *
84 * @exception KrbException if error occurs when constructing a Config
85 * instance. Possible causes would be configuration file not
86 * found, either of java.security.krb5.realm or java.security.krb5.kdc
87 * not specified, error reading configuration file.
88 */
89 public static synchronized Config getInstance() throws KrbException {
90 if (singleton == null) {
91 singleton = new Config();
92 }
93 return singleton;
94 }
95
96 /**
97 * Refresh and reload the Configuration. This could involve,
98 * for example reading the Configuration file again or getting
99 * the java.security.krb5.* system properties again.
100 *
101 * @exception KrbException if error occurs when constructing a Config
102 * instance. Possible causes would be configuration file not
103 * found, either of java.security.krb5.realm or java.security.krb5.kdc
104 * not specified, error reading configuration file.
105 */
106
107 public static synchronized void refresh() throws KrbException {
108 singleton = new Config();
109 KeyTab.refresh();
110 }
111
112
113 /**
114 * Private constructor - can not be instantiated externally.
115 */
116 private Config() throws KrbException {
117 /*
118 * If these two system properties are being specified by the user,
119 * we ignore configuration file. If either one system property is
120 * specified, we throw exception. If neither of them are specified,
121 * we load the information from configuration file.
122 */
123 String kdchost =
124 java.security.AccessController.doPrivileged(
125 new sun.security.action.GetPropertyAction
126 ("java.security.krb5.kdc"));
127 defaultRealm =
128 java.security.AccessController.doPrivileged(
129 new sun.security.action.GetPropertyAction
130 ("java.security.krb5.realm"));
131 if ((kdchost == null && defaultRealm != null) ||
132 (defaultRealm == null && kdchost != null)) {
133 throw new KrbException
134 ("System property java.security.krb5.kdc and " +
135 "java.security.krb5.realm both must be set or " +
136 "neither must be set.");
137 }
138 if (kdchost != null) {
139 /*
140 * If configuration information is only specified by
141 * properties java.security.krb5.kdc and
142 * java.security.krb5.realm, we put both in the hashtable
143 * under [libdefaults].
144 */
145 Hashtable<String,String> kdcs = new Hashtable<String,String> ();
146 kdcs.put("default_realm", defaultRealm);
147 // The user can specify a list of kdc hosts separated by ":"
148 kdchost = kdchost.replace(':', ' ');
149 kdcs.put("kdc", kdchost);
150 stanzaTable = new Hashtable<String,Object> ();
151 stanzaTable.put("libdefaults", kdcs);
152 } else {
153 // Read the Kerberos configuration file
154 try {
155 Vector<String> configFile;
156 configFile = loadConfigFile();
157 stanzaTable = parseStanzaTable(configFile);
158 } catch (IOException ioe) {
159 KrbException ke = new KrbException("Could not load " +
160 "configuration file " +
161 ioe.getMessage());
162 ke.initCause(ioe);
163 throw(ke);
164 }
165 }
166 }
167
168 /**
169 * Gets the default int value for the specified name.
170 * @param name the name.
171 * @return the default Integer, null is returned if no such name and
172 * value are found in configuration file, or error occurs when parsing
173 * string to integer.
174 */
175 public int getDefaultIntValue(String name) {
176 String result = null;
177 int value = Integer.MIN_VALUE;
178 result = getDefault(name);
179 if (result != null) {
180 try {
181 value = parseIntValue(result);
182 } catch (NumberFormatException e) {
183 if (DEBUG) {
184 System.out.println("Exception in getting value of " +
185 name + " " +
186 e.getMessage());
187 System.out.println("Setting " + name +
188 " to minimum value");
189 }
190 value = Integer.MIN_VALUE;
191 }
192 }
193 return value;
194 }
195
196 /**
197 * Gets the default int value for the specified name in the specified
198 * section. <br>This method is quicker by using section name as the
199 * search key.
200 * @param name the name.
201 * @param sectio the name string of the section.
202 * @return the default Integer, null is returned if no such name and
203 * value are found in configuration file, or error occurs when parsing
204 * string to integer.
205 */
206 public int getDefaultIntValue(String name, String section) {
207 String result = null;
208 int value = Integer.MIN_VALUE;
209 result = getDefault(name, section);
210 if (result != null) {
211 try {
212 value = parseIntValue(result);
213 } catch (NumberFormatException e) {
214 if (DEBUG) {
215 System.out.println("Exception in getting value of " +
216 name +" in section " +
217 section + " " + e.getMessage());
218 System.out.println("Setting " + name +
219 " to minimum value");
220 }
221 value = Integer.MIN_VALUE;
222 }
223 }
224 return value;
225 }
226
227 /**
228 * Gets the default string value for the specified name.
229 * @param name the name.
230 * @return the default value, null is returned if it cannot be found.
231 */
232 public String getDefault(String name) {
233 if (stanzaTable == null) {
234 return null;
235 } else {
236 return getDefault(name, stanzaTable);
237 }
238 }
239
240 /**
241 * This method does the real job to recursively search through the
242 * stanzaTable.
243 * @param k the key string.
244 * @param t stanzaTable or sub hashtable within it.
245 * @return the value found in config file, returns null if no value
246 * matched with the key is found.
247 */
248 private String getDefault(String k, Hashtable t) {
249 String result = null;
250 String key;
251 if (stanzaTable != null) {
252 for (Enumeration e = t.keys(); e.hasMoreElements(); ) {
253 key = (String)e.nextElement();
254 Object ob = t.get(key);
255 if (ob instanceof Hashtable) {
256 result = getDefault(k, (Hashtable)ob);
257 if (result != null) {
258 return result;
259 }
260 } else if (key.equalsIgnoreCase(k)) {
261 if (ob instanceof String) {
262 return (String)(t.get(key));
263 } else if (ob instanceof Vector) {
264 result = "";
265 int length = ((Vector)ob).size();
266 for (int i = 0; i < length; i++) {
267 if (i == length -1) {
268 result +=
269 (String)(((Vector)ob).elementAt(i));
270 } else {
271 result +=
272 (String)(((Vector)ob).elementAt(i)) + " ";
273 }
274 }
275 return result;
276 }
277 }
278 }
279 }
280 return result;
281 }
282
283 /**
284 * Gets the default string value for the specified name in the
285 * specified section.
286 * <br>This method is quicker by using the section name as the search key.
287 * @param name the name.
288 * @param section the name of the section.
289 * @return the default value, null is returned if it cannot be found.
290 */
291 public String getDefault(String name, String section) {
292 String stanzaName;
293 String result = null;
294 Hashtable subTable;
295
296 /*
297 * In the situation when kdc is specified by
298 * java.security.krb5.kdc, we get the kdc from [libdefaults] in
299 * hashtable.
300 */
301 if (name.equalsIgnoreCase("kdc") &&
302 (!section.equalsIgnoreCase("libdefaults")) &&
303 (java.security.AccessController.doPrivileged(
304 new sun.security.action.
305 GetPropertyAction("java.security.krb5.kdc")) != null)) {
306 result = getDefault("kdc", "libdefaults");
307 return result;
308 }
309 if (stanzaTable != null) {
310 for (Enumeration e = stanzaTable.keys(); e.hasMoreElements(); ) {
311 stanzaName = (String)e.nextElement();
312 subTable = (Hashtable)stanzaTable.get(stanzaName);
313 if (stanzaName.equalsIgnoreCase(section)) {
314 if (subTable.containsKey(name)) {
315 return (String)(subTable.get(name));
316 }
317 } else if (subTable.containsKey(section)) {
318 Object ob = subTable.get(section);
319 if (ob instanceof Hashtable) {
320 Hashtable temp = (Hashtable)ob;
321 if (temp.containsKey(name)) {
322 Object object = temp.get(name);
323 if (object instanceof Vector) {
324 result = "";
325 int length = ((Vector)object).size();
326 for (int i = 0; i < length; i++) {
327 if (i == length - 1) {
328 result +=
329 (String)(((Vector)object).elementAt(i));
330 } else {
331 result +=
332 (String)(((Vector)object).elementAt(i))
333 + " ";
334 }
335 }
336 } else {
337 result = (String)object;
338 }
339 }
340 }
341 }
342 }
343 }
344 return result;
345 }
346
347 /**
348 * Gets the default boolean value for the specified name.
349 * @param name the name.
350 * @return the default boolean value, false is returned if it cannot be
351 * found.
352 */
353 public boolean getDefaultBooleanValue(String name) {
354 String val = null;
355 if (stanzaTable == null) {
356 val = null;
357 } else {
358 val = getDefault(name, stanzaTable);
359 }
360 if (val != null && val.equalsIgnoreCase("true")) {
361 return true;
362 } else {
363 return false;
364 }
365 }
366
367 /**
368 * Gets the default boolean value for the specified name in the
369 * specified section.
370 * <br>This method is quicker by using the section name as the search key.
371 * @param name the name.
372 * @param section the name of the section.
373 * @return the default boolean value, false is returned if it cannot be
374 * found.
375 */
376 public boolean getDefaultBooleanValue(String name, String section) {
377 String val = getDefault(name, section);
378 if (val != null && val.equalsIgnoreCase("true")) {
379 return true;
380 } else {
381 return false;
382 }
383 }
384
385 /**
386 * Parses a string to an integer. The convertible strings include the
387 * string representations of positive integers, negative integers, and
388 * hex decimal integers. Valid inputs are, e.g., -1234, +1234,
389 * 0x40000.
390 *
391 * @param input the String to be converted to an Integer.
392 * @return an numeric value represented by the string
393 * @exception NumberFormationException if the String does not contain a
394 * parsable integer.
395 */
396 private int parseIntValue(String input) throws NumberFormatException {
397 int value = 0;
398 if (input.startsWith("+")) {
399 String temp = input.substring(1);
400 return Integer.parseInt(temp);
401 } else if (input.startsWith("0x")) {
402 String temp = input.substring(2);
403 char[] chars = temp.toCharArray();
404 if (chars.length > 8) {
405 throw new NumberFormatException();
406 } else {
407 for (int i = 0; i < chars.length; i++) {
408 int index = chars.length - i - 1;
409 switch (chars[i]) {
410 case '0':
411 value += 0;
412 break;
413 case '1':
414 value += 1 * getBase(index);
415 break;
416 case '2':
417 value += 2 * getBase(index);
418 break;
419 case '3':
420 value += 3 * getBase(index);
421 break;
422 case '4':
423 value += 4 * getBase(index);
424 break;
425 case '5':
426 value += 5 * getBase(index);
427 break;
428 case '6':
429 value += 6 * getBase(index);
430 break;
431 case '7':
432 value += 7 * getBase(index);
433 break;
434 case '8':
435 value += 8 * getBase(index);
436 break;
437 case '9':
438 value += 9 * getBase(index);
439 break;
440 case 'a':
441 case 'A':
442 value += 10 * getBase(index);
443 break;
444 case 'b':
445 case 'B':
446 value += 11 * getBase(index);
447 break;
448 case 'c':
449 case 'C':
450 value += 12 * getBase(index);
451 break;
452 case 'd':
453 case 'D':
454 value += 13 * getBase(index);
455 break;
456 case 'e':
457 case 'E':
458 value += 14 * getBase(index);
459 break;
460 case 'f':
461 case 'F':
462 value += 15 * getBase(index);
463 break;
464 default:
465 throw new NumberFormatException("Invalid numerical format");
466 }
467 }
468 }
469 if (value < 0) {
470 throw new NumberFormatException("Data overflow.");
471 }
472 } else {
473 value = Integer.parseInt(input);
474 }
475 return value;
476 }
477
478 private int getBase(int i) {
479 int result = 16;
480 switch (i) {
481 case 0:
482 result = BASE16_0;
483 break;
484 case 1:
485 result = BASE16_1;
486 break;
487 case 2:
488 result = BASE16_2;
489 break;
490 case 3:
491 result = BASE16_3;
492 break;
493 default:
494 for (int j = 1; j < i; j++) {
495 result *= 16;
496 }
497 }
498 return result;
499 }
500
501 /**
502 * Finds the matching value in the hashtable.
503 */
504 private String find(String key1, String key2) {
505 String result;
506 if ((stanzaTable != null) &&
507 ((result = (String)
508 (((Hashtable)(stanzaTable.get(key1))).get(key2))) != null)) {
509 return result;
510 } else {
511 return "";
512 }
513 }
514
515 /**
516 * Reads name/value pairs to the memory from the configuration
517 * file. The default location of the configuration file is in java home
518 * directory.
519 *
520 * Configuration file contains information about the default realm,
521 * ticket parameters, location of the KDC and the admin server for
522 * known realms, etc. The file is divided into sections. Each section
523 * contains one or more name/value pairs with one pair per line. A
524 * typical file would be:
525 * [libdefaults]
526 * default_realm = EXAMPLE.COM
527 * default_tgs_enctypes = des-cbc-md5
528 * default_tkt_enctypes = des-cbc-md5
529 * [realms]
530 * EXAMPLE.COM = {
531 * kdc = kerberos.example.com
532 * kdc = kerberos-1.example.com
533 * admin_server = kerberos.example.com
534 * }
535 * SAMPLE_COM = {
536 * kdc = orange.sample.com
537 * admin_server = orange.sample.com
538 * }
539 * [domain_realm]
540 * blue.sample.com = TEST.SAMPLE.COM
541 * .backup.com = EXAMPLE.COM
542 */
543 private Vector<String> loadConfigFile() throws IOException {
544 try {
545 final String fileName = getFileName();
546 if (!fileName.equals("")) {
547 BufferedReader br = new BufferedReader(new InputStreamReader(
548 java.security.AccessController.doPrivileged(
549 new java.security.PrivilegedExceptionAction<FileInputStream> () {
550 public FileInputStream run() throws IOException {
551 return new FileInputStream(fileName);
552 }
553 })));
554 String Line;
555 Vector<String> v = new Vector<String> ();
556 String previous = null;
557 while ((Line = br.readLine()) != null) {
558 // ignore comments and blank line in the configuration file.
559 // Comments start with #.
560 if (!(Line.startsWith("#") || Line.trim().isEmpty())) {
561 String current = Line.trim();
562 // In practice, a subsection might look like:
563 // EXAMPLE.COM =
564 // {
565 // kdc = kerberos.example.com
566 // ...
567 // }
568 // Before parsed into stanza table, it needs to be
569 // converted into formal style:
570 // EXAMPLE.COM = {
571 // kdc = kerberos.example.com
572 // ...
573 // }
574 //
575 // So, if a line is "{", adhere to the previous line.
576 if (current.equals("{")) {
577 if (previous == null) {
578 throw new IOException(
579 "Config file should not start with \"{\"");
580 }
581 previous += " " + current;
582 } else {
583 if (previous != null) {
584 v.addElement(previous);
585 }
586 previous = current;
587 }
588 }
589 }
590 if (previous != null) {
591 v.addElement(previous);
592 }
593
594 br.close();
595 return v;
596 }
597 return null;
598 } catch (java.security.PrivilegedActionException pe) {
599 throw (IOException)pe.getException();
600 }
601 }
602
603
604 /**
605 * Parses stanza names and values from configuration file to
606 * stanzaTable (Hashtable). Hashtable key would be stanza names,
607 * (libdefaults, realms, domain_realms, etc), and the hashtable value
608 * would be another hashtable which contains the key-value pairs under
609 * a stanza name.
610 */
611 private Hashtable<String,Object> parseStanzaTable(Vector<String> v) throws KrbException {
612 if (v == null) {
613 throw new KrbException("I/O error while reading" +
614 " configuration file.");
615 }
616 Hashtable<String,Object> table = new Hashtable<String,Object> ();
617 for (int i = 0; i < v.size(); i++) {
618 String line = v.elementAt(i).trim();
619 if (line.equalsIgnoreCase("[realms]")) {
620 for (int count = i + 1; count < v.size() + 1; count++) {
621 // find the next stanza name
622 if ((count == v.size()) ||
623 (v.elementAt(count).startsWith("["))) {
624 Hashtable<String,Hashtable<String,Vector<String>>> temp =
625 new Hashtable<String,Hashtable<String,Vector<String>>>();
626 temp = parseRealmField(v, i + 1, count);
627 table.put("realms", temp);
628 i = count - 1;
629 break;
630 }
631 }
632 } else if (line.equalsIgnoreCase("[capaths]")) {
633 for (int count = i + 1; count < v.size() + 1; count++) {
634 // find the next stanza name
635 if ((count == v.size()) ||
636 (v.elementAt(count).startsWith("["))) {
637 Hashtable<String,Hashtable<String,Vector<String>>> temp =
638 new Hashtable<String,Hashtable<String,Vector<String>>>();
639 temp = parseRealmField(v, i + 1, count);
640 table.put("capaths", temp);
641 i = count - 1;
642 break;
643 }
644 }
645 } else if (line.startsWith("[") && line.endsWith("]")) {
646 String key = line.substring(1, line.length() - 1);
647 for (int count = i + 1; count < v.size() + 1; count++) {
648 // find the next stanza name
649 if ((count == v.size()) ||
650 (v.elementAt(count).startsWith("["))) {
651 Hashtable<String,String> temp =
652 parseField(v, i + 1, count);
653 table.put(key, temp);
654 i = count - 1;
655 break;
656 }
657 }
658 }
659 }
660 return table;
661 }
662
663 /**
664 * Gets the default configuration file name. The file will be searched
665 * in a list of possible loations in the following order:
666 * 1. the location and file name defined by system property
667 * "java.security.krb5.conf",
668 * 2. at Java home lib\security directory with "krb5.conf" name,
669 * 3. "krb5.ini" at Java home,
670 * 4. at windows directory with the name of "krb5.ini" for Windows,
671 * /etc/krb5/krb5.conf for Solaris, /etc/krb5.conf for Linux.
672 */
673 private String getFileName() {
674 String name =
675 java.security.AccessController.doPrivileged(
676 new sun.security.action.
677 GetPropertyAction("java.security.krb5.conf"));
678 if (name != null) {
679 boolean temp =
680 java.security.AccessController.doPrivileged(
681 new FileExistsAction(name));
682 if (temp)
683 return name;
684 } else {
685 name = java.security.AccessController.doPrivileged(
686 new sun.security.action.
687 GetPropertyAction("java.home")) + File.separator +
688 "lib" + File.separator + "security" +
689 File.separator + "krb5.conf";
690 boolean temp =
691 java.security.AccessController.doPrivileged(
692 new FileExistsAction(name));
693 if (temp) {
694 return name;
695 } else {
696 String osname =
697 java.security.AccessController.doPrivileged(
698 new sun.security.action.GetPropertyAction("os.name"));
699 if (osname.startsWith("Windows")) {
700 try {
701 Credentials.ensureLoaded();
702 } catch (Exception e) {
703 // ignore exceptions
704 }
705 if (Credentials.alreadyLoaded) {
706 if ((name = getWindowsDirectory()) == null) {
707 name = "c:\\winnt\\krb5.ini";
708 } else if (name.endsWith("\\")) {
709 name += "krb5.ini";
710 } else {
711 name += "\\krb5.ini";
712 }
713 } else {
714 name = "c:\\winnt\\krb5.ini";
715 }
716 } else if (osname.startsWith("SunOS")) {
717 name = "/etc/krb5/krb5.conf";
718 } else if (osname.startsWith("Linux")) {
719 name = "/etc/krb5.conf";
720 }
721 }
722 }
723 if (DEBUG) {
724 System.out.println("Config name: " + name);
725 }
726 return name;
727 }
728
729 /**
730 * Parses key-value pairs under a stanza name.
731 */
732 private Hashtable<String,String> parseField(Vector<String> v, int start, int end) {
733 Hashtable<String,String> table = new Hashtable<String,String> ();
734 String line;
735 for (int i = start; i < end; i++) {
736 line = v.elementAt(i);
737 for (int j = 0; j < line.length(); j++) {
738 if (line.charAt(j) == '=') {
739 String key = (line.substring(0, j)).trim();
740 String value = (line.substring(j + 1)).trim();
741 table.put(key, value);
742 break;
743 }
744 }
745 }
746 return table;
747 }
748
749 /**
750 * Parses key-value pairs under [realms]. The key would be the realm
751 * name, the value would be another hashtable which contains
752 * information for the realm given within a pair of braces.
753 */
754 private Hashtable<String,Hashtable<String,Vector<String>>> parseRealmField(Vector<String> v, int start, int end) {
755 Hashtable<String,Hashtable<String,Vector<String>>> table = new Hashtable<String,Hashtable<String,Vector<String>>> ();
756 String line;
757 for (int i = start; i < end; i++) {
758 line = v.elementAt(i).trim();
759 if (line.endsWith("{")) {
760 String key = "";
761 for (int j = 0; j < line.length(); j++) {
762 if (line.charAt(j) == '=') {
763 key = line.substring(0, j).trim();
764 // get the key
765 break;
766 }
767 }
768 for (int k = i + 1; k < end; k++) {
769 boolean found = false;
770 line = v.elementAt(k).trim();
771 for (int l = 0; l < line.length(); l++) {
772 if (line.charAt(l) == '}') {
773 found = true;
774 break;
775 }
776 }
777 if (found == true) {
778 Hashtable<String,Vector<String>> temp = parseRealmFieldEx(v, i + 1, k);
779 table.put(key, temp);
780 i = k;
781 found = false;
782 break;
783 }
784
785 }
786 }
787 }
788 return table;
789 }
790
791 /**
792 * Parses key-value pairs within each braces under [realms].
793 */
794 private Hashtable<String,Vector<String>> parseRealmFieldEx(Vector<String> v, int start, int end) {
795 Hashtable<String,Vector<String>> table =
796 new Hashtable<String,Vector<String>> ();
797 Vector<String> keyVector = new Vector<String> ();
798 Vector<String> nameVector = new Vector<String> ();
799 String line = "";
800 String key;
801 for (int i = start; i < end; i++) {
802 line = v.elementAt(i);
803 for (int j = 0; j < line.length(); j++) {
804 if (line.charAt(j) == '=') {
805 int index;
806 key = line.substring(0, j - 1).trim();
807 if (! exists(key, keyVector)) {
808 keyVector.addElement(key);
809 nameVector = new Vector<String> ();
810 } else {
811 nameVector = table.get(key);
812 }
813 nameVector.addElement((line.substring(j + 1)).trim());
814 table.put(key, nameVector);
815 break;
816 }
817 }
818 }
819 return table;
820 }
821
822 /**
823 * Compares the key with the known keys to see if it exists.
824 */
825 private boolean exists(String key, Vector v) {
826 boolean exists = false;
827 for (int i = 0; i < v.size(); i++) {
828 if (((String)(v.elementAt(i))).equals(key)) {
829 exists = true;
830 }
831 }
832 return exists;
833 }
834
835 /**
836 * For testing purpose. This method lists all information being parsed from
837 * the configuration file to the hashtable.
838 */
839 public void listTable() {
840 listTable(stanzaTable);
841 }
842
843 private void listTable(Hashtable table) {
844 Vector v = new Vector();
845 String key;
846 if (stanzaTable != null) {
847 for (Enumeration e = table.keys(); e.hasMoreElements(); ) {
848 key = (String)e.nextElement();
849 Object object = table.get(key);
850 if (table == stanzaTable) {
851 System.out.println("[" + key + "]");
852 }
853 if (object instanceof Hashtable) {
854 if (table != stanzaTable)
855 System.out.println("\t" + key + " = {");
856 listTable((Hashtable)object);
857 if (table != stanzaTable)
858 System.out.println("\t}");
859
860 } else if (object instanceof String) {
861 System.out.println("\t" + key + " = " +
862 (String)table.get(key));
863 } else if (object instanceof Vector) {
864 v = (Vector)object;
865 for (int i = 0; i < v.size(); i++) {
866 System.out.println("\t" + key + " = " +
867 (String)v.elementAt(i));
868 }
869 }
870 }
871 } else {
872 System.out.println("Configuration file not found.");
873 }
874 }
875
876 /**
877 * Returns the default encryption types.
878 *
879 */
880 public int[] defaultEtype(String enctypes) {
881 String default_enctypes;
882 default_enctypes = getDefault(enctypes, "libdefaults");
883 String delim = " ";
884 StringTokenizer st;
885 int[] etype;
886 if (default_enctypes == null) {
887 if (DEBUG) {
888 System.out.println("Using builtin default etypes for " +
889 enctypes);
890 }
891 etype = EType.getBuiltInDefaults();
892 } else {
893 for (int j = 0; j < default_enctypes.length(); j++) {
894 if (default_enctypes.substring(j, j + 1).equals(",")) {
895 // only two delimiters are allowed to use
896 // according to Kerberos DCE doc.
897 delim = ",";
898 break;
899 }
900 }
901 st = new StringTokenizer(default_enctypes, delim);
902 int len = st.countTokens();
903 ArrayList<Integer> ls = new ArrayList<Integer> (len);
904 int type;
905 for (int i = 0; i < len; i++) {
906 type = getType(st.nextToken());
907 if ((type != -1) &&
908 (EType.isSupported(type))) {
909 ls.add(type);
910 }
911 }
912 if (ls.size() == 0) {
913 if (DEBUG) {
914 System.out.println(
915 "no supported default etypes for " + enctypes);
916 }
917 return null;
918 } else {
919 etype = new int[ls.size()];
920 for (int i = 0; i < etype.length; i++) {
921 etype[i] = ls.get(i);
922 }
923 }
924 }
925
926 if (DEBUG) {
927 System.out.print("default etypes for " + enctypes + ":");
928 for (int i = 0; i < etype.length; i++) {
929 System.out.print(" " + etype[i]);
930 }
931 System.out.println(".");
932 }
933 return etype;
934 }
935
936
937 /**
938 * Get the etype and checksum value for the specified encryption and
939 * checksum type.
940 *
941 */
942 /*
943 * This method converts the string representation of encryption type and
944 * checksum type to int value that can be later used by EType and
945 * Checksum classes.
946 */
947 public int getType(String input) {
948 int result = -1;
949 if (input == null) {
950 return result;
951 }
952 if (input.startsWith("d") || (input.startsWith("D"))) {
953 if (input.equalsIgnoreCase("des-cbc-crc")) {
954 result = EncryptedData.ETYPE_DES_CBC_CRC;
955 } else if (input.equalsIgnoreCase("des-cbc-md5")) {
956 result = EncryptedData.ETYPE_DES_CBC_MD5;
957 } else if (input.equalsIgnoreCase("des-mac")) {
958 result = Checksum.CKSUMTYPE_DES_MAC;
959 } else if (input.equalsIgnoreCase("des-mac-k")) {
960 result = Checksum.CKSUMTYPE_DES_MAC_K;
961 } else if (input.equalsIgnoreCase("des-cbc-md4")) {
962 result = EncryptedData.ETYPE_DES_CBC_MD4;
963 } else if (input.equalsIgnoreCase("des3-cbc-sha1") ||
964 input.equalsIgnoreCase("des3-hmac-sha1") ||
965 input.equalsIgnoreCase("des3-cbc-sha1-kd") ||
966 input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) {
967 result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD;
968 }
969 } else if (input.startsWith("a") || (input.startsWith("A"))) {
970 // AES
971 if (input.equalsIgnoreCase("aes128-cts") ||
972 input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) {
973 result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96;
974 } else if (input.equalsIgnoreCase("aes256-cts") ||
975 input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) {
976 result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96;
977 // ARCFOUR-HMAC
978 } else if (input.equalsIgnoreCase("arcfour-hmac") ||
979 input.equalsIgnoreCase("arcfour-hmac-md5")) {
980 result = EncryptedData.ETYPE_ARCFOUR_HMAC;
981 }
982 // RC4-HMAC
983 } else if (input.equalsIgnoreCase("rc4-hmac")) {
984 result = EncryptedData.ETYPE_ARCFOUR_HMAC;
985 } else if (input.equalsIgnoreCase("CRC32")) {
986 result = Checksum.CKSUMTYPE_CRC32;
987 } else if (input.startsWith("r") || (input.startsWith("R"))) {
988 if (input.equalsIgnoreCase("rsa-md5")) {
989 result = Checksum.CKSUMTYPE_RSA_MD5;
990 } else if (input.equalsIgnoreCase("rsa-md5-des")) {
991 result = Checksum.CKSUMTYPE_RSA_MD5_DES;
992 }
993 } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) {
994 result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD;
995 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) {
996 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128;
997 } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) {
998 result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256;
999 } else if (input.equalsIgnoreCase("hmac-md5-rc4") ||
1000 input.equalsIgnoreCase("hmac-md5-arcfour") ||
1001 input.equalsIgnoreCase("hmac-md5-enc")) {
1002 result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR;
1003 } else if (input.equalsIgnoreCase("NULL")) {
1004 result = EncryptedData.ETYPE_NULL;
1005 }
1006
1007 return result;
1008 }
1009
1010 /**
1011 * Resets the default kdc realm.
1012 * We do not need to synchronize these methods since assignments are atomic
1013 */
1014 public void resetDefaultRealm(String realm) {
1015 defaultRealm = realm;
1016 if (DEBUG) {
1017 System.out.println(">>> Config reset default kdc " + defaultRealm);
1018 }
1019
1020 }
1021
1022 /**
1023 * Check to use addresses in tickets
1024 * use addresses if "no_addresses" or "noaddresses" is set to false
1025 */
1026 public boolean useAddresses() {
1027 boolean useAddr = false;
1028 // use addresses if "no_addresses" is set to false
1029 String value = getDefault("no_addresses", "libdefaults");
1030 useAddr = (value != null && value.equalsIgnoreCase("false"));
1031 if (useAddr == false) {
1032 // use addresses if "noaddresses" is set to false
1033 value = getDefault("noaddresses", "libdefaults");
1034 useAddr = (value != null && value.equalsIgnoreCase("false"));
1035 }
1036 return useAddr;
1037 }
1038
1039 /**
1040 * Check if need to use DNS to locate Kerberos services
1041 */
1042 public boolean useDNS(String name) {
1043 boolean value = getDefaultBooleanValue(name, "libdefaults");
1044 if (value == false) {
1045 value = getDefaultBooleanValue("dns_fallback", "libdefaults");
1046 }
1047 return value;
1048 }
1049
1050 /**
1051 * Check if need to use DNS to locate the KDC
1052 */
1053 public boolean useDNS_KDC() {
1054 return useDNS("dns_lookup_kdc");
1055 }
1056
1057 /*
1058 * Check if need to use DNS to locate the Realm
1059 */
1060 public boolean useDNS_Realm() {
1061 return useDNS("dns_lookup_realm");
1062 }
1063
1064 /**
1065 * Gets default realm.
1066 */
1067 public String getDefaultRealm() throws KrbException {
1068 String realm = getDefault("default_realm", "libdefaults");
1069 if ((realm == null) && useDNS_Realm()) {
1070 // use DNS to locate Kerberos realm
1071 realm = getRealmFromDNS();
1072 }
1073 return realm;
1074 }
1075
1076 /**
1077 * Returns a list of KDC's with each KDC separated by a space
1078 *
1079 * @param realm the realm for which the master KDC is desired
1080 * @return the list of KDCs
1081 */
1082 public String getKDCList(String realm) throws KrbException {
1083 if (realm == null) {
1084 realm = getDefaultRealm();
1085 }
1086 String kdcs = getDefault("kdc", realm);
1087 if ((kdcs == null) && useDNS_KDC()) {
1088 // use DNS to locate KDC
1089 kdcs = getKDCFromDNS(realm);
1090 }
1091 return kdcs;
1092 }
1093
1094 /**
1095 * Locate Kerberos realm using DNS
1096 *
1097 * @return the Kerberos realm
1098 */
1099 private String getRealmFromDNS() throws KrbException {
1100 // use DNS to locate Kerberos realm
1101 String realm = null;
1102 String hostName = null;
1103 try {
1104 hostName = InetAddress.getLocalHost().getHostName();
1105 } catch (UnknownHostException e) {
1106 KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC,
1107 "Unable to locate Kerberos realm: " + e.getMessage());
1108 ke.initCause(e);
1109 throw (ke);
1110 }
1111 // get the domain realm mapping from the configuration
1112 String mapRealm = PrincipalName.mapHostToRealm(hostName);
1113 String[] records = null;
1114 String newRealm = mapRealm;
1115 while ((records == null) && (newRealm != null)) {
1116 // locate DNS TXT record
1117 records = KrbServiceLocator.getKerberosService(newRealm);
1118 newRealm = Realm.parseRealmComponent(newRealm);
1119 // if no DNS TXT records found, try again using sub-realm
1120 }
1121 if (records == null) {
1122 // no DNS TXT records
1123 throw new KrbException(Krb5.KRB_ERR_GENERIC,
1124 "Unable to locate Kerberos realm");
1125 }
1126 boolean found = false;
1127 for (int i = 0; i < records.length; i++) {
1128 if (records[i].equals(mapRealm)) {
1129 found = true;
1130 realm = records[i];
1131 }
1132 }
1133 if (found == false) {
1134 throw new KrbException(Krb5.KRB_ERR_GENERIC,
1135 "Unable to locate Kerberos realm");
1136 }
1137 return realm;
1138 }
1139
1140 /**
1141 * Locate KDC using DNS
1142 *
1143 * @param realm the realm for which the master KDC is desired
1144 * @return the KDC
1145 */
1146 private String getKDCFromDNS(String realm) throws KrbException {
1147 // use DNS to locate KDC
1148 String kdcs = null;
1149 String[] srvs = null;
1150 // locate DNS SRV record using UDP
1151 srvs = KrbServiceLocator.getKerberosService(realm, "_udp.");
1152 if (srvs == null) {
1153 // locate DNS SRV record using TCP
1154 srvs = KrbServiceLocator.getKerberosService(realm, "_tcp.");
1155 }
1156 if (srvs == null) {
1157 // no DNS SRV records
1158 throw new KrbException(Krb5.KRB_ERR_GENERIC,
1159 "Unable to locate KDC for realm " + realm);
1160 }
1161 for (int i = 0; i < srvs.length; i++) {
1162 String value = srvs[i];
1163 for (int j = 0; j < srvs[i].length(); j++) {
1164 // filter the KDC name
1165 if (value.charAt(j) == ':') {
1166 kdcs = (value.substring(0, j)).trim();
1167 }
1168 }
1169 }
1170 return kdcs;
1171 }
1172
1173 static class FileExistsAction
1174 implements java.security.PrivilegedAction<Boolean> {
1175
1176 private String fileName;
1177
1178 public FileExistsAction(String fileName) {
1179 this.fileName = fileName;
1180 }
1181
1182 public Boolean run() {
1183 return new File(fileName).exists();
1184 }
1185 }
1186
1187}