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