Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/KrbServiceLocator.java
41159 views
/*1* Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.krb5;2627import sun.security.krb5.internal.Krb5;2829import java.security.AccessController;30import java.security.PrivilegedActionException;31import java.security.PrivilegedExceptionAction;32import java.util.Arrays;33import java.util.Hashtable;34import java.util.Random;35import java.util.StringTokenizer;3637import javax.naming.*;38import javax.naming.directory.*;39import javax.naming.spi.NamingManager;4041/**42* This class discovers the location of Kerberos services by querying DNS,43* as defined in RFC 4120.44*45* @author Seema Malkani46* @since 1.747*/4849class KrbServiceLocator {5051private static final String SRV_RR = "SRV";52private static final String[] SRV_RR_ATTR = new String[] {SRV_RR};5354private static final String SRV_TXT = "TXT";55private static final String[] SRV_TXT_ATTR = new String[] {SRV_TXT};5657private static final Random random = new Random();5859private KrbServiceLocator() {60}6162/**63* Locates the KERBEROS service for a given domain.64* Queries DNS for a list of KERBEROS Service Text Records (TXT) for a65* given domain name.66* Information on the mapping of DNS hostnames and domain names67* to Kerberos realms is stored using DNS TXT records68*69* @param realmName A string realm name.70* @return An ordered list of hostports for the Kerberos service or null if71* the service has not been located.72*/73@SuppressWarnings("removal")74static String[] getKerberosService(String realmName) {7576// search realm in SRV TXT records77String dnsUrl = "dns:///_kerberos." + realmName;78String[] records = null;79try {80// Create the DNS context using NamingManager rather than using81// the initial context constructor. This avoids having the initial82// context constructor call itself (when processing the URL83// argument in the getAttributes call).84Context ctx = NamingManager.getURLContext("dns", new Hashtable<>(0));85if (!(ctx instanceof DirContext)) {86return null; // cannot create a DNS context87}88Attributes attrs = null;89try {90// both connect and accept are needed since DNS is thru UDP91attrs = AccessController.doPrivileged(92(PrivilegedExceptionAction<Attributes>)93() -> ((DirContext)ctx).getAttributes(94dnsUrl, SRV_TXT_ATTR),95null,96new java.net.SocketPermission("*", "connect,accept"));97} catch (PrivilegedActionException e) {98throw (NamingException)e.getCause();99}100Attribute attr;101102if (attrs != null && ((attr = attrs.get(SRV_TXT)) != null)) {103int numValues = attr.size();104int numRecords = 0;105String[] txtRecords = new String[numValues];106107// gather the text records108int i = 0;109int j = 0;110while (i < numValues) {111try {112txtRecords[j] = (String)attr.get(i);113j++;114} catch (Exception e) {115// ignore bad value116}117i++;118}119numRecords = j;120121// trim122if (numRecords < numValues) {123String[] trimmed = new String[numRecords];124System.arraycopy(txtRecords, 0, trimmed, 0, numRecords);125records = trimmed;126} else {127records = txtRecords;128}129}130} catch (NamingException e) {131// ignore132}133return records;134}135136/**137* Locates the KERBEROS service for a given domain.138* Queries DNS for a list of KERBEROS Service Location Records (SRV) for a139* given domain name.140*141* @param realmName A string realm name.142* @param protocol the protocol string, can be "_udp" or "_tcp"143* @return An ordered list of hostports for the Kerberos service or null if144* the service has not been located.145*/146@SuppressWarnings("removal")147static String[] getKerberosService(String realmName, String protocol) {148149String dnsUrl = "dns:///_kerberos." + protocol + "." + realmName;150String[] hostports = null;151152try {153// Create the DNS context using NamingManager rather than using154// the initial context constructor. This avoids having the initial155// context constructor call itself (when processing the URL156// argument in the getAttributes call).157Context ctx = NamingManager.getURLContext("dns", new Hashtable<>(0));158if (!(ctx instanceof DirContext)) {159return null; // cannot create a DNS context160}161162Attributes attrs = null;163try {164// both connect and accept are needed since DNS is thru UDP165attrs = AccessController.doPrivileged(166(PrivilegedExceptionAction<Attributes>)167() -> ((DirContext)ctx).getAttributes(168dnsUrl, SRV_RR_ATTR),169null,170new java.net.SocketPermission("*", "connect,accept"));171} catch (PrivilegedActionException e) {172throw (NamingException)e.getCause();173}174175Attribute attr;176177if (attrs != null && ((attr = attrs.get(SRV_RR)) != null)) {178int numValues = attr.size();179int numRecords = 0;180SrvRecord[] srvRecords = new SrvRecord[numValues];181182// create the service records183int i = 0;184int j = 0;185while (i < numValues) {186try {187srvRecords[j] = new SrvRecord((String) attr.get(i));188j++;189} catch (Exception e) {190// ignore bad value191}192i++;193}194numRecords = j;195196// trim197if (numRecords < numValues) {198SrvRecord[] trimmed = new SrvRecord[numRecords];199System.arraycopy(srvRecords, 0, trimmed, 0, numRecords);200srvRecords = trimmed;201}202203// Sort the service records in ascending order of their204// priority value. For records with equal priority, move205// those with weight 0 to the top of the list.206if (numRecords > 1) {207Arrays.sort(srvRecords);208}209210// extract the host and port number from each service record211hostports = extractHostports(srvRecords);212}213} catch (NamingException e) {214// e.printStackTrace();215// ignore216}217return hostports;218}219220/**221* Extract hosts and port numbers from a list of SRV records.222* An array of hostports is returned or null if none were found.223*/224private static String[] extractHostports(SrvRecord[] srvRecords) {225String[] hostports = null;226227int head = 0;228int tail = 0;229int sublistLength = 0;230int k = 0;231for (int i = 0; i < srvRecords.length; i++) {232if (hostports == null) {233hostports = new String[srvRecords.length];234}235// find the head and tail of the list of records having the same236// priority value.237head = i;238while (i < srvRecords.length - 1 &&239srvRecords[i].priority == srvRecords[i + 1].priority) {240i++;241}242tail = i;243244// select hostports from the sublist245sublistLength = (tail - head) + 1;246for (int j = 0; j < sublistLength; j++) {247hostports[k++] = selectHostport(srvRecords, head, tail);248}249}250return hostports;251}252253/*254* Randomly select a service record in the range [head, tail] and return255* its hostport value. Follows the algorithm in RFC 2782.256*/257private static String selectHostport(SrvRecord[] srvRecords, int head,258int tail) {259if (head == tail) {260return srvRecords[head].hostport;261}262263// compute the running sum for records between head and tail264int sum = 0;265for (int i = head; i <= tail; i++) {266if (srvRecords[i] != null) {267sum += srvRecords[i].weight;268srvRecords[i].sum = sum;269}270}271String hostport = null;272273// If all records have zero weight, select first available one;274// otherwise, randomly select a record according to its weight275int target = (sum == 0 ? 0 : random.nextInt(sum + 1));276for (int i = head; i <= tail; i++) {277if (srvRecords[i] != null && srvRecords[i].sum >= target) {278hostport = srvRecords[i].hostport;279srvRecords[i] = null; // make this record unavailable280break;281}282}283return hostport;284}285286/**287* This class holds a DNS service (SRV) record.288* See http://www.ietf.org/rfc/rfc2782.txt289*/290291static class SrvRecord implements Comparable<SrvRecord> {292293int priority;294int weight;295int sum;296String hostport;297298/**299* Creates a service record object from a string record.300* DNS supplies the string record in the following format:301* <pre>302* <Priority> " " <Weight> " " <Port> " " <Host>303* </pre>304*/305SrvRecord(String srvRecord) throws Exception {306StringTokenizer tokenizer = new StringTokenizer(srvRecord, " ");307String port;308309if (tokenizer.countTokens() == 4) {310priority = Integer.parseInt(tokenizer.nextToken());311weight = Integer.parseInt(tokenizer.nextToken());312port = tokenizer.nextToken();313hostport = tokenizer.nextToken() + ":" + port;314} else {315throw new IllegalArgumentException();316}317}318319/*320* Sort records in ascending order of priority value. For records with321* equal priority move those with weight 0 to the top of the list.322*/323public int compareTo(SrvRecord that) {324if (priority > that.priority) {325return 1; // this > that326} else if (priority < that.priority) {327return -1; // this < that328} else if (weight == 0 && that.weight != 0) {329return -1; // this < that330} else if (weight != 0 && that.weight == 0) {331return 1; // this > that332} else {333return 0; // this == that334}335}336}337}338339340