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