Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/krb5/KdcComm.java
38830 views
/*1* Copyright (c) 2000, 2014, 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*/2425/*26*27* (C) Copyright IBM Corp. 1999 All Rights Reserved.28* Copyright 1997 The Open Group Research Institute. All rights reserved.29*/3031package sun.security.krb5;3233import java.security.PrivilegedAction;34import java.security.Security;35import java.util.Locale;36import sun.security.krb5.internal.Krb5;37import sun.security.krb5.internal.NetClient;38import java.io.IOException;39import java.net.SocketTimeoutException;40import java.util.StringTokenizer;41import java.security.AccessController;42import java.security.PrivilegedExceptionAction;43import java.security.PrivilegedActionException;44import java.util.ArrayList;45import java.util.List;46import java.util.Set;47import java.util.HashSet;48import java.util.Iterator;49import sun.security.krb5.internal.KRBError;5051/**52* KDC-REQ/KDC-REP communication. No more base class for KrbAsReq and53* KrbTgsReq. This class is now communication only.54*/55public final class KdcComm {5657// The following settings can be configured in [libdefaults]58// section of krb5.conf, which are global for all realms. Each of59// them can also be defined in a realm, which overrides value here.6061/**62* max retry time for a single KDC, default Krb5.KDC_RETRY_LIMIT (3)63*/64private static int defaultKdcRetryLimit;65/**66* timeout requesting a ticket from KDC, in millisec, default 30 sec67*/68private static int defaultKdcTimeout;69/**70* max UDP packet size, default unlimited (-1)71*/72private static int defaultUdpPrefLimit;7374private static final boolean DEBUG = Krb5.DEBUG;7576private static final String BAD_POLICY_KEY = "krb5.kdc.bad.policy";7778/**79* What to do when a KDC is unavailable, specified in the80* java.security file with key krb5.kdc.bad.policy.81* Possible values can be TRY_LAST or TRY_LESS. Reloaded when refreshed.82*/83private enum BpType {84NONE, TRY_LAST, TRY_LESS85}86private static int tryLessMaxRetries = 1;87private static int tryLessTimeout = 5000;8889private static BpType badPolicy;9091static {92initStatic();93}9495/**96* Read global settings97*/98public static void initStatic() {99String value = AccessController.doPrivileged(100new PrivilegedAction<String>() {101public String run() {102return Security.getProperty(BAD_POLICY_KEY);103}104});105if (value != null) {106value = value.toLowerCase(Locale.ENGLISH);107String[] ss = value.split(":");108if ("tryless".equals(ss[0])) {109if (ss.length > 1) {110String[] params = ss[1].split(",");111try {112int tmp0 = Integer.parseInt(params[0]);113if (params.length > 1) {114tryLessTimeout = Integer.parseInt(params[1]);115}116// Assign here in case of exception at params[1]117tryLessMaxRetries = tmp0;118} catch (NumberFormatException nfe) {119// Ignored. Please note that tryLess is recognized and120// used, parameters using default values121if (DEBUG) {122System.out.println("Invalid " + BAD_POLICY_KEY +123" parameter for tryLess: " +124value + ", use default");125}126}127}128badPolicy = BpType.TRY_LESS;129} else if ("trylast".equals(ss[0])) {130badPolicy = BpType.TRY_LAST;131} else {132badPolicy = BpType.NONE;133}134} else {135badPolicy = BpType.NONE;136}137138139int timeout = -1;140int max_retries = -1;141int udp_pref_limit = -1;142143try {144Config cfg = Config.getInstance();145String temp = cfg.get("libdefaults", "kdc_timeout");146timeout = parseTimeString(temp);147148temp = cfg.get("libdefaults", "max_retries");149max_retries = parsePositiveIntString(temp);150temp = cfg.get("libdefaults", "udp_preference_limit");151udp_pref_limit = parsePositiveIntString(temp);152} catch (Exception exc) {153// ignore any exceptions; use default values154if (DEBUG) {155System.out.println ("Exception in getting KDC communication " +156"settings, using default value " +157exc.getMessage());158}159}160defaultKdcTimeout = timeout > 0 ? timeout : 30*1000; // 30 seconds161defaultKdcRetryLimit =162max_retries > 0 ? max_retries : Krb5.KDC_RETRY_LIMIT;163164if (udp_pref_limit < 0) {165defaultUdpPrefLimit = Krb5.KDC_DEFAULT_UDP_PREF_LIMIT;166} else if (udp_pref_limit > Krb5.KDC_HARD_UDP_LIMIT) {167defaultUdpPrefLimit = Krb5.KDC_HARD_UDP_LIMIT;168} else {169defaultUdpPrefLimit = udp_pref_limit;170}171172KdcAccessibility.reset();173}174175/**176* The instance fields177*/178private String realm;179180public KdcComm(String realm) throws KrbException {181if (realm == null) {182realm = Config.getInstance().getDefaultRealm();183if (realm == null) {184throw new KrbException(Krb5.KRB_ERR_GENERIC,185"Cannot find default realm");186}187}188this.realm = realm;189}190191public byte[] send(byte[] obuf)192throws IOException, KrbException {193int udpPrefLimit = getRealmSpecificValue(194realm, "udp_preference_limit", defaultUdpPrefLimit);195196boolean useTCP = (udpPrefLimit > 0 &&197(obuf != null && obuf.length > udpPrefLimit));198199return send(obuf, useTCP);200}201202private byte[] send(byte[] obuf, boolean useTCP)203throws IOException, KrbException {204205if (obuf == null)206return null;207Config cfg = Config.getInstance();208209if (realm == null) {210realm = cfg.getDefaultRealm();211if (realm == null) {212throw new KrbException(Krb5.KRB_ERR_GENERIC,213"Cannot find default realm");214}215}216217String kdcList = cfg.getKDCList(realm);218if (kdcList == null) {219throw new KrbException("Cannot get kdc for realm " + realm);220}221// tempKdc may include the port number also222Iterator<String> tempKdc = KdcAccessibility.list(kdcList).iterator();223if (!tempKdc.hasNext()) {224throw new KrbException("Cannot get kdc for realm " + realm);225}226byte[] ibuf = null;227try {228ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);229} catch(Exception first) {230boolean ok = false;231while(tempKdc.hasNext()) {232try {233ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);234ok = true;235break;236} catch(Exception ignore) {}237}238if (!ok) throw first;239}240if (ibuf == null) {241throw new IOException("Cannot get a KDC reply");242}243return ibuf;244}245246// send the AS Request to the specified KDC247// failover to using TCP if useTCP is not set and response is too big248private byte[] sendIfPossible(byte[] obuf, String tempKdc, boolean useTCP)249throws IOException, KrbException {250251try {252byte[] ibuf = send(obuf, tempKdc, useTCP);253KRBError ke = null;254try {255ke = new KRBError(ibuf);256} catch (Exception e) {257// OK258}259if (ke != null && ke.getErrorCode() ==260Krb5.KRB_ERR_RESPONSE_TOO_BIG) {261ibuf = send(obuf, tempKdc, true);262}263KdcAccessibility.removeBad(tempKdc);264return ibuf;265} catch(Exception e) {266if (DEBUG) {267System.out.println(">>> KrbKdcReq send: error trying " +268tempKdc);269e.printStackTrace(System.out);270}271KdcAccessibility.addBad(tempKdc);272throw e;273}274}275276// send the AS Request to the specified KDC277278private byte[] send(byte[] obuf, String tempKdc, boolean useTCP)279throws IOException, KrbException {280281if (obuf == null)282return null;283284int port = Krb5.KDC_INET_DEFAULT_PORT;285int retries = getRealmSpecificValue(286realm, "max_retries", defaultKdcRetryLimit);287int timeout = getRealmSpecificValue(288realm, "kdc_timeout", defaultKdcTimeout);289if (badPolicy == BpType.TRY_LESS &&290KdcAccessibility.isBad(tempKdc)) {291if (retries > tryLessMaxRetries) {292retries = tryLessMaxRetries; // less retries293}294if (timeout > tryLessTimeout) {295timeout = tryLessTimeout; // less time296}297}298299String kdc = null;300String portStr = null;301302if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in []303int pos = tempKdc.indexOf(']', 1);304if (pos == -1) {305throw new IOException("Illegal KDC: " + tempKdc);306}307kdc = tempKdc.substring(1, pos);308if (pos != tempKdc.length() - 1) { // with port number309if (tempKdc.charAt(pos+1) != ':') {310throw new IOException("Illegal KDC: " + tempKdc);311}312portStr = tempKdc.substring(pos+2);313}314} else {315int colon = tempKdc.indexOf(':');316if (colon == -1) { // Hostname or IPv4 host only317kdc = tempKdc;318} else {319int nextColon = tempKdc.indexOf(':', colon+1);320if (nextColon > 0) { // >=2 ":", IPv6 with no port321kdc = tempKdc;322} else { // 1 ":", hostname or IPv4 with port323kdc = tempKdc.substring(0, colon);324portStr = tempKdc.substring(colon+1);325}326}327}328if (portStr != null) {329int tempPort = parsePositiveIntString(portStr);330if (tempPort > 0)331port = tempPort;332}333334if (DEBUG) {335System.out.println(">>> KrbKdcReq send: kdc=" + kdc336+ (useTCP ? " TCP:":" UDP:")337+ port + ", timeout="338+ timeout339+ ", number of retries ="340+ retries341+ ", #bytes=" + obuf.length);342}343344KdcCommunication kdcCommunication =345new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf);346try {347byte[] ibuf = AccessController.doPrivileged(kdcCommunication);348if (DEBUG) {349System.out.println(">>> KrbKdcReq send: #bytes read="350+ (ibuf != null ? ibuf.length : 0));351}352return ibuf;353} catch (PrivilegedActionException e) {354Exception wrappedException = e.getException();355if (wrappedException instanceof IOException) {356throw (IOException) wrappedException;357} else {358throw (KrbException) wrappedException;359}360}361}362363private static class KdcCommunication364implements PrivilegedExceptionAction<byte[]> {365366private String kdc;367private int port;368private boolean useTCP;369private int timeout;370private int retries;371private byte[] obuf;372373public KdcCommunication(String kdc, int port, boolean useTCP,374int timeout, int retries, byte[] obuf) {375this.kdc = kdc;376this.port = port;377this.useTCP = useTCP;378this.timeout = timeout;379this.retries = retries;380this.obuf = obuf;381}382383// The caller only casts IOException and KrbException so don't384// add any new ones!385386public byte[] run() throws IOException, KrbException {387388byte[] ibuf = null;389390for (int i=1; i <= retries; i++) {391String proto = useTCP?"TCP":"UDP";392if (DEBUG) {393System.out.println(">>> KDCCommunication: kdc=" + kdc394+ " " + proto + ":"395+ port + ", timeout="396+ timeout397+ ",Attempt =" + i398+ ", #bytes=" + obuf.length);399}400try (NetClient kdcClient = NetClient.getInstance(401proto, kdc, port, timeout)) {402kdcClient.send(obuf);403ibuf = kdcClient.receive();404break;405} catch (SocketTimeoutException se) {406if (DEBUG) {407System.out.println ("SocketTimeOutException with " +408"attempt: " + i);409}410if (i == retries) {411ibuf = null;412throw se;413}414}415}416return ibuf;417}418}419420/**421* Parses a time value string. If it ends with "s", parses as seconds.422* Otherwise, parses as milliseconds.423* @param s the time string424* @return the integer value in milliseconds, or -1 if input is null or425* has an invalid format426*/427private static int parseTimeString(String s) {428if (s == null) {429return -1;430}431if (s.endsWith("s")) {432int seconds = parsePositiveIntString(s.substring(0, s.length()-1));433return (seconds < 0) ? -1 : (seconds*1000);434} else {435return parsePositiveIntString(s);436}437}438439/**440* Returns krb5.conf setting of {@code key} for a specific realm,441* which can be:442* 1. defined in the sub-stanza for the given realm inside [realms], or443* 2. defined in [libdefaults], or444* 3. defValue445* @param realm the given realm in which the setting is requested. Returns446* the global setting if null447* @param key the key for the setting448* @param defValue default value449* @return a value for the key450*/451private int getRealmSpecificValue(String realm, String key, int defValue) {452int v = defValue;453454if (realm == null) return v;455456int temp = -1;457try {458String value =459Config.getInstance().get("realms", realm, key);460if (key.equals("kdc_timeout")) {461temp = parseTimeString(value);462} else {463temp = parsePositiveIntString(value);464}465} catch (Exception exc) {466// Ignored, defValue will be picked up467}468469if (temp > 0) v = temp;470471return v;472}473474private static int parsePositiveIntString(String intString) {475if (intString == null)476return -1;477478int ret = -1;479480try {481ret = Integer.parseInt(intString);482} catch (Exception exc) {483return -1;484}485486if (ret >= 0)487return ret;488489return -1;490}491492/**493* Maintains a KDC accessible list. Unavailable KDCs are put into a494* blacklist, when a KDC in the blacklist is available, it's removed495* from there. No insertion order in the blacklist.496*497* There are two methods to deal with KDCs in the blacklist. 1. Only try498* them when there's no KDC not on the blacklist. 2. Still try them, but499* with lesser number of retries and smaller timeout value.500*/501static class KdcAccessibility {502// Known bad KDCs503private static Set<String> bads = new HashSet<>();504505private static synchronized void addBad(String kdc) {506if (DEBUG) {507System.out.println(">>> KdcAccessibility: add " + kdc);508}509bads.add(kdc);510}511512private static synchronized void removeBad(String kdc) {513if (DEBUG) {514System.out.println(">>> KdcAccessibility: remove " + kdc);515}516bads.remove(kdc);517}518519private static synchronized boolean isBad(String kdc) {520return bads.contains(kdc);521}522523private static synchronized void reset() {524if (DEBUG) {525System.out.println(">>> KdcAccessibility: reset");526}527bads.clear();528}529530// Returns a preferred KDC list by putting the bad ones at the end531private static synchronized List<String> list(String kdcList) {532StringTokenizer st = new StringTokenizer(kdcList);533List<String> list = new ArrayList<>();534if (badPolicy == BpType.TRY_LAST) {535List<String> badkdcs = new ArrayList<>();536while (st.hasMoreTokens()) {537String t = st.nextToken();538if (bads.contains(t)) badkdcs.add(t);539else list.add(t);540}541// Bad KDCs are put at last542list.addAll(badkdcs);543} else {544// All KDCs are returned in their original order,545// This include TRY_LESS and NONE546while (st.hasMoreTokens()) {547list.add(st.nextToken());548}549}550return list;551}552}553}554555556557