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