1 /*
2  * Copyright (c) 2000, 2019, 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 
32 package sun.security.krb5;
33 
34 import sun.security.krb5.internal.*;
35 import sun.security.util.*;
36 import java.net.*;
37 import java.util.Vector;
38 import java.util.Locale;
39 import java.io.IOException;
40 import java.math.BigInteger;
41 import java.util.Arrays;
42 import sun.security.krb5.internal.ccache.CCacheOutputStream;
43 import sun.security.krb5.internal.util.KerberosString;
44 
45 
46 /**
47  * Implements the ASN.1 PrincipalName type and its realm in a single class.
48  * <pre>{@code
49  *    Realm           ::= KerberosString
50  *
51  *    PrincipalName   ::= SEQUENCE {
52  *            name-type       [0] Int32,
53  *            name-string     [1] SEQUENCE OF KerberosString
54  *    }
55  * }</pre>
56  * This class is immutable.
57  * @see Realm
58  */
59 public class PrincipalName implements Cloneable {
60 
61     //name types
62 
63     /**
64      * Name type not known
65      */
66     public static final int KRB_NT_UNKNOWN =   0;
67 
68     /**
69      * Just the name of the principal as in DCE, or for users
70      */
71     public static final int KRB_NT_PRINCIPAL = 1;
72 
73     /**
74      * Service and other unique instance (krbtgt)
75      */
76     public static final int KRB_NT_SRV_INST =  2;
77 
78     /**
79      * Service with host name as instance (telnet, rcommands)
80      */
81     public static final int KRB_NT_SRV_HST =   3;
82 
83     /**
84      * Service with host as remaining components
85      */
86     public static final int KRB_NT_SRV_XHST =  4;
87 
88     /**
89      * Unique ID
90      */
91     public static final int KRB_NT_UID = 5;
92 
93     /**
94      * Enterprise name (alias)
95      */
96     public static final int KRB_NT_ENTERPRISE = 10;
97 
98     /**
99      * TGS Name
100      */
101     public static final String TGS_DEFAULT_SRV_NAME = "krbtgt";
102     public static final int TGS_DEFAULT_NT = KRB_NT_SRV_INST;
103 
104     public static final char NAME_COMPONENT_SEPARATOR = '/';
105     public static final char NAME_REALM_SEPARATOR = '@';
106     public static final char REALM_COMPONENT_SEPARATOR = '.';
107 
108     public static final String NAME_COMPONENT_SEPARATOR_STR = "/";
109     public static final String NAME_REALM_SEPARATOR_STR = "@";
110     public static final String REALM_COMPONENT_SEPARATOR_STR = ".";
111 
112     // Instance fields.
113 
114     /**
115      * The name type, from PrincipalName's name-type field.
116      */
117     private final int nameType;
118 
119     /**
120      * The name strings, from PrincipalName's name-strings field. This field
121      * must be neither null nor empty. Each entry of it must also be neither
122      * null nor empty. Make sure to clone the field when it's passed in or out.
123      */
124     private final String[] nameStrings;
125 
126     /**
127      * The realm this principal belongs to.
128      */
129     private final Realm nameRealm;      // not null
130 
131 
132     /**
133      * When constructing a PrincipalName, whether the realm is included in
134      * the input, or deduced from default realm or domain-realm mapping.
135      */
136     private final boolean realmDeduced;
137 
138     // cached default salt, not used in clone
139     private transient String salt = null;
140 
141     // There are 3 basic constructors. All other constructors must call them.
142     // All basic constructors must call validateNameStrings.
143     // 1. From name components
144     // 2. From name
145     // 3. From DER encoding
146 
147     /**
148      * Creates a PrincipalName.
149      */
PrincipalName(int nameType, String[] nameStrings, Realm nameRealm)150     public PrincipalName(int nameType, String[] nameStrings, Realm nameRealm) {
151         if (nameRealm == null) {
152             throw new IllegalArgumentException("Null realm not allowed");
153         }
154         validateNameStrings(nameStrings);
155         this.nameType = nameType;
156         this.nameStrings = nameStrings.clone();
157         this.nameRealm = nameRealm;
158         this.realmDeduced = false;
159     }
160 
161     // Warning: called by NativeCreds.c
PrincipalName(String[] nameParts, String realm)162     public PrincipalName(String[] nameParts, String realm) throws RealmException {
163         this(KRB_NT_UNKNOWN, nameParts, new Realm(realm));
164     }
165 
166     // Validate a nameStrings argument
validateNameStrings(String[] ns)167     private static void validateNameStrings(String[] ns) {
168         if (ns == null) {
169             throw new IllegalArgumentException("Null nameStrings not allowed");
170         }
171         if (ns.length == 0) {
172             throw new IllegalArgumentException("Empty nameStrings not allowed");
173         }
174         for (String s: ns) {
175             if (s == null) {
176                 throw new IllegalArgumentException("Null nameString not allowed");
177             }
178             if (s.isEmpty()) {
179                 throw new IllegalArgumentException("Empty nameString not allowed");
180             }
181         }
182     }
183 
clone()184     public Object clone() {
185         try {
186             PrincipalName pName = (PrincipalName) super.clone();
187             UNSAFE.putReference(this, NAME_STRINGS_OFFSET, nameStrings.clone());
188             return pName;
189         } catch (CloneNotSupportedException ex) {
190             throw new AssertionError("Should never happen");
191         }
192     }
193 
194     private static final long NAME_STRINGS_OFFSET;
195     private static final jdk.internal.misc.Unsafe UNSAFE;
196     static {
197         try {
198             jdk.internal.misc.Unsafe unsafe = jdk.internal.misc.Unsafe.getUnsafe();
199             NAME_STRINGS_OFFSET = unsafe.objectFieldOffset(
200                     PrincipalName.class.getDeclaredField("nameStrings"));
201             UNSAFE = unsafe;
202         } catch (ReflectiveOperationException e) {
203             throw new Error(e);
204         }
205     }
206 
207     @Override
equals(Object o)208     public boolean equals(Object o) {
209         if (this == o) {
210             return true;
211         }
212         if (o instanceof PrincipalName) {
213             PrincipalName other = (PrincipalName)o;
214             return nameRealm.equals(other.nameRealm) &&
215                     Arrays.equals(nameStrings, other.nameStrings);
216         }
217         return false;
218     }
219 
220     /**
221      * Returns the ASN.1 encoding of the
222      * <pre>{@code
223      * PrincipalName    ::= SEQUENCE {
224      *          name-type       [0] Int32,
225      *          name-string     [1] SEQUENCE OF KerberosString
226      * }
227      *
228      * KerberosString   ::= GeneralString (IA5String)
229      * }</pre>
230      *
231      * <p>
232      * This definition reflects the Network Working Group RFC 4120
233      * specification available at
234      * <a href="http://www.ietf.org/rfc/rfc4120.txt">
235      * http://www.ietf.org/rfc/rfc4120.txt</a>.
236      *
237      * @param encoding DER-encoded PrincipalName (without Realm)
238      * @param realm the realm for this name
239      * @exception Asn1Exception if an error occurs while decoding
240      * an ASN1 encoded data.
241      * @exception Asn1Exception if there is an ASN1 encoding error
242      * @exception IOException if an I/O error occurs
243      * @exception IllegalArgumentException if encoding is null
244      * reading encoded data.
245      */
PrincipalName(DerValue encoding, Realm realm)246     public PrincipalName(DerValue encoding, Realm realm)
247             throws Asn1Exception, IOException {
248         if (realm == null) {
249             throw new IllegalArgumentException("Null realm not allowed");
250         }
251         realmDeduced = false;
252         nameRealm = realm;
253         DerValue der;
254         if (encoding == null) {
255             throw new IllegalArgumentException("Null encoding not allowed");
256         }
257         if (encoding.getTag() != DerValue.tag_Sequence) {
258             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
259         }
260         der = encoding.getData().getDerValue();
261         if ((der.getTag() & 0x1F) == 0x00) {
262             BigInteger bint = der.getData().getBigInteger();
263             nameType = bint.intValue();
264         } else {
265             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
266         }
267         der = encoding.getData().getDerValue();
268         if ((der.getTag() & 0x01F) == 0x01) {
269             DerValue subDer = der.getData().getDerValue();
270             if (subDer.getTag() != DerValue.tag_SequenceOf) {
271                 throw new Asn1Exception(Krb5.ASN1_BAD_ID);
272             }
273             Vector<String> v = new Vector<>();
274             DerValue subSubDer;
275             while(subDer.getData().available() > 0) {
276                 subSubDer = subDer.getData().getDerValue();
277                 String namePart = new KerberosString(subSubDer).toString();
278                 v.addElement(namePart);
279             }
280             nameStrings = new String[v.size()];
281             v.copyInto(nameStrings);
282             validateNameStrings(nameStrings);
283         } else  {
284             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
285         }
286     }
287 
288     /**
289      * Parse (unmarshal) a <code>PrincipalName</code> from a DER
290      * input stream.  This form
291      * parsing might be used when expanding a value which is part of
292      * a constructed sequence and uses explicitly tagged type.
293      *
294      * @exception Asn1Exception on error.
295      * @param data the Der input stream value, which contains one or
296      * more marshaled value.
297      * @param explicitTag tag number.
298      * @param optional indicate if this data field is optional
299      * @param realm the realm for the name
300      * @return an instance of <code>PrincipalName</code>, or null if the
301      * field is optional and missing.
302      */
parse(DerInputStream data, byte explicitTag, boolean optional, Realm realm)303     public static PrincipalName parse(DerInputStream data,
304                                       byte explicitTag, boolean
305                                       optional,
306                                       Realm realm)
307         throws Asn1Exception, IOException, RealmException {
308 
309         if ((optional) && (((byte)data.peekByte() & (byte)0x1F) !=
310                            explicitTag))
311             return null;
312         DerValue der = data.getDerValue();
313         if (explicitTag != (der.getTag() & (byte)0x1F)) {
314             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
315         } else {
316             DerValue subDer = der.getData().getDerValue();
317             if (realm == null) {
318                 realm = Realm.getDefault();
319             }
320             return new PrincipalName(subDer, realm);
321         }
322     }
323 
324 
325     // XXX Error checkin consistent with MIT krb5_parse_name
326     // Code repetition, realm parsed again by class Realm
parseName(String name)327     private static String[] parseName(String name) {
328 
329         Vector<String> tempStrings = new Vector<>();
330         String temp = name;
331         int i = 0;
332         int componentStart = 0;
333         String component;
334 
335         while (i < temp.length()) {
336             if (temp.charAt(i) == NAME_COMPONENT_SEPARATOR) {
337                 /*
338                  * If this separator is escaped then don't treat it
339                  * as a separator
340                  */
341                 if (i > 0 && temp.charAt(i - 1) == '\\') {
342                     temp = temp.substring(0, i - 1) +
343                         temp.substring(i, temp.length());
344                     continue;
345                 }
346                 else {
347                     if (componentStart <= i) {
348                         component = temp.substring(componentStart, i);
349                         tempStrings.addElement(component);
350                     }
351                     componentStart = i + 1;
352                 }
353             } else {
354                 if (temp.charAt(i) == NAME_REALM_SEPARATOR) {
355                     /*
356                      * If this separator is escaped then don't treat it
357                      * as a separator
358                      */
359                     if (i > 0 && temp.charAt(i - 1) == '\\') {
360                         temp = temp.substring(0, i - 1) +
361                             temp.substring(i, temp.length());
362                         continue;
363                     } else {
364                         if (componentStart < i) {
365                             component = temp.substring(componentStart, i);
366                             tempStrings.addElement(component);
367                         }
368                         componentStart = i + 1;
369                         break;
370                     }
371                 }
372             }
373             i++;
374         }
375 
376         if (i == temp.length()) {
377             component = temp.substring(componentStart, i);
378             tempStrings.addElement(component);
379         }
380 
381         String[] result = new String[tempStrings.size()];
382         tempStrings.copyInto(result);
383         return result;
384     }
385 
386     /**
387      * Constructs a PrincipalName from a string.
388      * @param name the name
389      * @param type the type
390      * @param realm the realm, null if not known. Note that when realm is not
391      * null, it will be always used even if there is a realm part in name. When
392      * realm is null, will read realm part from name, or try to map a realm
393      * (for KRB_NT_SRV_HST), or use the default realm, or fail
394      * @throws RealmException
395      */
PrincipalName(String name, int type, String realm)396     public PrincipalName(String name, int type, String realm)
397             throws RealmException {
398         if (name == null) {
399             throw new IllegalArgumentException("Null name not allowed");
400         }
401         String[] nameParts = parseName(name);
402         validateNameStrings(nameParts);
403         if (realm == null) {
404             realm = Realm.parseRealmAtSeparator(name);
405         }
406 
407         // No realm info from parameter and string, must deduce later
408         realmDeduced = realm == null;
409 
410         switch (type) {
411         case KRB_NT_SRV_HST:
412             if (nameParts.length >= 2) {
413                 String hostName = nameParts[1];
414                 Boolean option;
415                 try {
416                     // If true, try canonicalizing and accept it if it starts
417                     // with the short name. Otherwise, never. Default true.
418                     option = Config.getInstance().getBooleanObject(
419                             "libdefaults", "dns_canonicalize_hostname");
420                 } catch (KrbException e) {
421                     option = null;
422                 }
423                 if (option != Boolean.FALSE) {
424                     try {
425                         // RFC4120 does not recommend canonicalizing a hostname.
426                         // However, for compatibility reason, we will try
427                         // canonicalizing it and see if the output looks better.
428 
429                         String canonicalized = (InetAddress.getByName(hostName)).
430                                 getCanonicalHostName();
431 
432                         // Looks if canonicalized is a longer format of hostName,
433                         // we accept cases like
434                         //     bunny -> bunny.rabbit.hole
435                         if (canonicalized.toLowerCase(Locale.ENGLISH).startsWith(
436                                 hostName.toLowerCase(Locale.ENGLISH) + ".")) {
437                             hostName = canonicalized;
438                         }
439                     } catch (UnknownHostException | SecurityException e) {
440                         // not canonicalized or no permission to do so, use old
441                     }
442                     if (hostName.endsWith(".")) {
443                         hostName = hostName.substring(0, hostName.length() - 1);
444                     }
445                 }
446                 nameParts[1] = hostName.toLowerCase(Locale.ENGLISH);
447             }
448             nameStrings = nameParts;
449             nameType = type;
450 
451             if (realm != null) {
452                 nameRealm = new Realm(realm);
453             } else {
454                 // We will try to get realm name from the mapping in
455                 // the configuration. If it is not specified
456                 // we will use the default realm. This nametype does
457                 // not allow a realm to be specified. The name string must of
458                 // the form service@host and this is internally changed into
459                 // service/host by Kerberos
460                 String mapRealm =  mapHostToRealm(nameParts[1]);
461                 if (mapRealm != null) {
462                     nameRealm = new Realm(mapRealm);
463                 } else {
464                     nameRealm = Realm.getDefault();
465                 }
466             }
467             break;
468         case KRB_NT_UNKNOWN:
469         case KRB_NT_PRINCIPAL:
470         case KRB_NT_SRV_INST:
471         case KRB_NT_SRV_XHST:
472         case KRB_NT_UID:
473         case KRB_NT_ENTERPRISE:
474             nameStrings = nameParts;
475             nameType = type;
476             if (realm != null) {
477                 nameRealm = new Realm(realm);
478             } else {
479                 nameRealm = Realm.getDefault();
480             }
481             break;
482         default:
483             throw new IllegalArgumentException("Illegal name type");
484         }
485     }
486 
487     // Warning: called by nativeccache.c
PrincipalName(String name, int type)488     public PrincipalName(String name, int type) throws RealmException {
489         this(name, type, (String)null);
490     }
491 
PrincipalName(String name)492     public PrincipalName(String name) throws RealmException {
493         this(name, KRB_NT_UNKNOWN);
494     }
495 
PrincipalName(String name, String realm)496     public PrincipalName(String name, String realm) throws RealmException {
497         this(name, KRB_NT_UNKNOWN, realm);
498     }
499 
tgsService(String r1, String r2)500     public static PrincipalName tgsService(String r1, String r2)
501             throws KrbException {
502         return new PrincipalName(PrincipalName.KRB_NT_SRV_INST,
503                 new String[] {PrincipalName.TGS_DEFAULT_SRV_NAME, r1},
504                 new Realm(r2));
505     }
506 
getRealmAsString()507     public String getRealmAsString() {
508         return getRealmString();
509     }
510 
getPrincipalNameAsString()511     public String getPrincipalNameAsString() {
512         StringBuilder temp = new StringBuilder(nameStrings[0]);
513         for (int i = 1; i < nameStrings.length; i++)
514             temp.append(nameStrings[i]);
515         return temp.toString();
516     }
517 
hashCode()518     public int hashCode() {
519         return toString().hashCode();
520     }
521 
getName()522     public String getName() {
523         return toString();
524     }
525 
getNameType()526     public int getNameType() {
527         return nameType;
528     }
529 
getNameStrings()530     public String[] getNameStrings() {
531         return nameStrings.clone();
532     }
533 
toByteArray()534     public byte[][] toByteArray() {
535         byte[][] result = new byte[nameStrings.length][];
536         for (int i = 0; i < nameStrings.length; i++) {
537             result[i] = new byte[nameStrings[i].length()];
538             result[i] = nameStrings[i].getBytes();
539         }
540         return result;
541     }
542 
getRealmString()543     public String getRealmString() {
544         return nameRealm.toString();
545     }
546 
getRealm()547     public Realm getRealm() {
548         return nameRealm;
549     }
550 
getSalt()551     public String getSalt() {
552         if (salt == null) {
553             StringBuilder salt = new StringBuilder();
554             salt.append(nameRealm.toString());
555             for (int i = 0; i < nameStrings.length; i++) {
556                 salt.append(nameStrings[i]);
557             }
558             return salt.toString();
559         }
560         return salt;
561     }
562 
toString()563     public String toString() {
564         StringBuilder str = new StringBuilder();
565         for (int i = 0; i < nameStrings.length; i++) {
566             if (i > 0)
567                 str.append("/");
568             String n = nameStrings[i];
569             n = n.replace("@", "\\@");
570             str.append(n);
571         }
572         str.append("@");
573         str.append(nameRealm.toString());
574         return str.toString();
575     }
576 
getNameString()577     public String getNameString() {
578         StringBuilder str = new StringBuilder();
579         for (int i = 0; i < nameStrings.length; i++) {
580             if (i > 0)
581                 str.append("/");
582             str.append(nameStrings[i]);
583         }
584         return str.toString();
585     }
586 
587     /**
588      * Encodes a <code>PrincipalName</code> object. Note that only the type and
589      * names are encoded. To encode the realm, call getRealm().asn1Encode().
590      * @return the byte array of the encoded PrncipalName object.
591      * @exception Asn1Exception if an error occurs while decoding an ASN1 encoded data.
592      * @exception IOException if an I/O error occurs while reading encoded data.
593      *
594      */
asn1Encode()595     public byte[] asn1Encode() throws Asn1Exception, IOException {
596         DerOutputStream bytes = new DerOutputStream();
597         DerOutputStream temp = new DerOutputStream();
598         BigInteger bint = BigInteger.valueOf(this.nameType);
599         temp.putInteger(bint);
600         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), temp);
601         temp = new DerOutputStream();
602         DerValue[] der = new DerValue[nameStrings.length];
603         for (int i = 0; i < nameStrings.length; i++) {
604             der[i] = new KerberosString(nameStrings[i]).toDerValue();
605         }
606         temp.putSequence(der);
607         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), temp);
608         temp = new DerOutputStream();
609         temp.write(DerValue.tag_Sequence, bytes);
610         return temp.toByteArray();
611     }
612 
613 
614     /**
615      * Checks if two <code>PrincipalName</code> objects have identical values in their corresponding data fields.
616      *
617      * @param pname the other <code>PrincipalName</code> object.
618      * @return true if two have identical values, otherwise, return false.
619      */
620     // It is used in <code>sun.security.krb5.internal.ccache</code> package.
match(PrincipalName pname)621     public boolean match(PrincipalName pname) {
622         boolean matched = true;
623         //name type is just a hint, no two names can be the same ignoring name type.
624         // if (this.nameType != pname.nameType) {
625         //      matched = false;
626         // }
627         if ((this.nameRealm != null) && (pname.nameRealm != null)) {
628             if (!(this.nameRealm.toString().equalsIgnoreCase(pname.nameRealm.toString()))) {
629                 matched = false;
630             }
631         }
632         if (this.nameStrings.length != pname.nameStrings.length) {
633             matched = false;
634         } else {
635             for (int i = 0; i < this.nameStrings.length; i++) {
636                 if (!(this.nameStrings[i].equalsIgnoreCase(pname.nameStrings[i]))) {
637                     matched = false;
638                 }
639             }
640         }
641         return matched;
642     }
643 
644     /**
645      * Writes data field values of <code>PrincipalName</code> in FCC format to an output stream.
646      *
647      * @param cos a <code>CCacheOutputStream</code> for writing data.
648      * @exception IOException if an I/O exception occurs.
649      * @see sun.security.krb5.internal.ccache.CCacheOutputStream
650      */
writePrincipal(CCacheOutputStream cos)651     public void writePrincipal(CCacheOutputStream cos) throws IOException {
652         cos.write32(nameType);
653         cos.write32(nameStrings.length);
654         byte[] realmBytes = null;
655         realmBytes = nameRealm.toString().getBytes();
656         cos.write32(realmBytes.length);
657         cos.write(realmBytes, 0, realmBytes.length);
658         byte[] bytes = null;
659         for (int i = 0; i < nameStrings.length; i++) {
660             bytes = nameStrings[i].getBytes();
661             cos.write32(bytes.length);
662             cos.write(bytes, 0, bytes.length);
663         }
664     }
665 
666     /**
667      * Returns the instance component of a name.
668      * In a multi-component name such as a KRB_NT_SRV_INST
669      * name, the second component is returned.
670      * Null is returned if there are not two or more
671      * components in the name.
672      *
673      * @return instance component of a multi-component name.
674      */
getInstanceComponent()675     public String getInstanceComponent()
676     {
677         if (nameStrings != null && nameStrings.length >= 2)
678             {
679                 return new String(nameStrings[1]);
680             }
681 
682         return null;
683     }
684 
mapHostToRealm(String name)685     static String mapHostToRealm(String name) {
686         String result = null;
687         try {
688             String subname = null;
689             Config c = Config.getInstance();
690             if ((result = c.get("domain_realm", name)) != null)
691                 return result;
692             else {
693                 for (int i = 1; i < name.length(); i++) {
694                     if ((name.charAt(i) == '.') && (i != name.length() - 1)) { //mapping could be .ibm.com = AUSTIN.IBM.COM
695                         subname = name.substring(i);
696                         result = c.get("domain_realm", subname);
697                         if (result != null) {
698                             break;
699                         }
700                         else {
701                             subname = name.substring(i + 1);      //or mapping could be ibm.com = AUSTIN.IBM.COM
702                             result = c.get("domain_realm", subname);
703                             if (result != null) {
704                                 break;
705                             }
706                         }
707                     }
708                 }
709             }
710         } catch (KrbException e) {
711         }
712         return result;
713     }
714 
isRealmDeduced()715     public boolean isRealmDeduced() {
716         return realmDeduced;
717     }
718 }
719