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