Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/KdcComm.java
67696 views
/*1* Copyright (c) 2000, 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*/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;7576/**77* What to do when a KDC is unavailable, specified in the78* java.security file with key krb5.kdc.bad.policy.79* Possible values can be TRY_LAST or TRY_LESS. Reloaded when refreshed.80*/81private enum BpType {82NONE, TRY_LAST, TRY_LESS83}84private static int tryLessMaxRetries = 1;85private static int tryLessTimeout = 5000;8687private static BpType badPolicy;8889static {90initStatic();91}9293/**94* Read global settings95*/96public static void initStatic() {97@SuppressWarnings("removal")98String value = AccessController.doPrivileged(99new PrivilegedAction<String>() {100public String run() {101return Security.getProperty("krb5.kdc.bad.policy");102}103});104if (value != null) {105value = value.toLowerCase(Locale.ENGLISH);106String[] ss = value.split(":");107if ("tryless".equals(ss[0])) {108if (ss.length > 1) {109String[] params = ss[1].split(",");110try {111int tmp0 = Integer.parseInt(params[0]);112if (params.length > 1) {113tryLessTimeout = Integer.parseInt(params[1]);114}115// Assign here in case of exception at params[1]116tryLessMaxRetries = tmp0;117} catch (NumberFormatException nfe) {118// Ignored. Please note that tryLess is recognized and119// used, parameters using default values120if (DEBUG) {121System.out.println("Invalid krb5.kdc.bad.policy" +122" parameter for tryLess: " +123value + ", use default");124}125}126}127badPolicy = BpType.TRY_LESS;128} else if ("trylast".equals(ss[0])) {129badPolicy = BpType.TRY_LAST;130} else {131badPolicy = BpType.NONE;132}133} else {134badPolicy = BpType.NONE;135}136137138int timeout = -1;139int max_retries = -1;140int udp_pref_limit = -1;141142try {143Config cfg = Config.getInstance();144String temp = cfg.get("libdefaults", "kdc_timeout");145timeout = parseTimeString(temp);146147temp = cfg.get("libdefaults", "max_retries");148max_retries = parsePositiveIntString(temp);149temp = cfg.get("libdefaults", "udp_preference_limit");150udp_pref_limit = parsePositiveIntString(temp);151} catch (Exception exc) {152// ignore any exceptions; use default values153if (DEBUG) {154System.out.println ("Exception in getting KDC communication " +155"settings, using default value " +156exc.getMessage());157}158}159defaultKdcTimeout = timeout > 0 ? timeout : 30*1000; // 30 seconds160defaultKdcRetryLimit =161max_retries > 0 ? max_retries : Krb5.KDC_RETRY_LIMIT;162163if (udp_pref_limit < 0) {164defaultUdpPrefLimit = Krb5.KDC_DEFAULT_UDP_PREF_LIMIT;165} else if (udp_pref_limit > Krb5.KDC_HARD_UDP_LIMIT) {166defaultUdpPrefLimit = Krb5.KDC_HARD_UDP_LIMIT;167} else {168defaultUdpPrefLimit = udp_pref_limit;169}170171KdcAccessibility.reset();172}173174/**175* The instance fields176*/177private String realm;178179public KdcComm(String realm) throws KrbException {180if (realm == null) {181realm = Config.getInstance().getDefaultRealm();182if (realm == null) {183throw new KrbException(Krb5.KRB_ERR_GENERIC,184"Cannot find default realm");185}186}187this.realm = realm;188}189190public byte[] send(byte[] obuf)191throws IOException, KrbException {192int udpPrefLimit = getRealmSpecificValue(193realm, "udp_preference_limit", defaultUdpPrefLimit);194195boolean useTCP = (udpPrefLimit > 0 &&196(obuf != null && obuf.length > udpPrefLimit));197198return send(obuf, useTCP);199}200201private byte[] send(byte[] obuf, boolean useTCP)202throws IOException, KrbException {203204if (obuf == null)205return null;206Config cfg = Config.getInstance();207208if (realm == null) {209realm = cfg.getDefaultRealm();210if (realm == null) {211throw new KrbException(Krb5.KRB_ERR_GENERIC,212"Cannot find default realm");213}214}215216String kdcList = cfg.getKDCList(realm);217if (kdcList == null) {218throw new KrbException("Cannot get kdc for realm " + realm);219}220// tempKdc may include the port number also221Iterator<String> tempKdc = KdcAccessibility.list(kdcList).iterator();222if (!tempKdc.hasNext()) {223throw new KrbException("Cannot get kdc for realm " + realm);224}225byte[] ibuf = null;226try {227ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);228} catch(Exception first) {229boolean ok = false;230while(tempKdc.hasNext()) {231try {232ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);233ok = true;234break;235} catch(Exception ignore) {}236}237if (!ok) throw first;238}239if (ibuf == null) {240throw new IOException("Cannot get a KDC reply");241}242return ibuf;243}244245// send the AS Request to the specified KDC246// failover to using TCP if useTCP is not set and response is too big247private byte[] sendIfPossible(byte[] obuf, String tempKdc, boolean useTCP)248throws IOException, KrbException {249250try {251byte[] ibuf = send(obuf, tempKdc, useTCP);252KRBError ke = null;253try {254ke = new KRBError(ibuf);255} catch (Exception e) {256// OK257}258if (ke != null) {259if (ke.getErrorCode() ==260Krb5.KRB_ERR_RESPONSE_TOO_BIG) {261ibuf = send(obuf, tempKdc, true);262} else if (ke.getErrorCode() ==263Krb5.KDC_ERR_SVC_UNAVAILABLE) {264throw new KrbException("A service is not available");265}266}267KdcAccessibility.removeBad(tempKdc);268return ibuf;269} catch(Exception e) {270if (DEBUG) {271System.out.println(">>> KrbKdcReq send: error trying " +272tempKdc);273e.printStackTrace(System.out);274}275KdcAccessibility.addBad(tempKdc);276throw e;277}278}279280// send the AS Request to the specified KDC281282private byte[] send(byte[] obuf, String tempKdc, boolean useTCP)283throws IOException, KrbException {284285if (obuf == null)286return null;287288int port = Krb5.KDC_INET_DEFAULT_PORT;289int retries = getRealmSpecificValue(290realm, "max_retries", defaultKdcRetryLimit);291int timeout = getRealmSpecificValue(292realm, "kdc_timeout", defaultKdcTimeout);293if (badPolicy == BpType.TRY_LESS &&294KdcAccessibility.isBad(tempKdc)) {295if (retries > tryLessMaxRetries) {296retries = tryLessMaxRetries; // less retries297}298if (timeout > tryLessTimeout) {299timeout = tryLessTimeout; // less time300}301}302303String kdc = null;304String portStr = null;305306if (tempKdc.charAt(0) == '[') { // Explicit IPv6 in []307int pos = tempKdc.indexOf(']', 1);308if (pos == -1) {309throw new IOException("Illegal KDC: " + tempKdc);310}311kdc = tempKdc.substring(1, pos);312if (pos != tempKdc.length() - 1) { // with port number313if (tempKdc.charAt(pos+1) != ':') {314throw new IOException("Illegal KDC: " + tempKdc);315}316portStr = tempKdc.substring(pos+2);317}318} else {319int colon = tempKdc.indexOf(':');320if (colon == -1) { // Hostname or IPv4 host only321kdc = tempKdc;322} else {323int nextColon = tempKdc.indexOf(':', colon+1);324if (nextColon > 0) { // >=2 ":", IPv6 with no port325kdc = tempKdc;326} else { // 1 ":", hostname or IPv4 with port327kdc = tempKdc.substring(0, colon);328portStr = tempKdc.substring(colon+1);329}330}331}332if (portStr != null) {333int tempPort = parsePositiveIntString(portStr);334if (tempPort > 0)335port = tempPort;336}337338if (DEBUG) {339System.out.println(">>> KrbKdcReq send: kdc=" + kdc340+ (useTCP ? " TCP:":" UDP:")341+ port + ", timeout="342+ timeout343+ ", number of retries ="344+ retries345+ ", #bytes=" + obuf.length);346}347348KdcCommunication kdcCommunication =349new KdcCommunication(kdc, port, useTCP, timeout, retries, obuf);350try {351@SuppressWarnings("removal")352byte[] ibuf = AccessController.doPrivileged(kdcCommunication);353if (DEBUG) {354System.out.println(">>> KrbKdcReq send: #bytes read="355+ (ibuf != null ? ibuf.length : 0));356}357return ibuf;358} catch (PrivilegedActionException e) {359Exception wrappedException = e.getException();360if (wrappedException instanceof IOException) {361throw (IOException) wrappedException;362} else {363throw (KrbException) wrappedException;364}365}366}367368private static class KdcCommunication369implements PrivilegedExceptionAction<byte[]> {370371private String kdc;372private int port;373private boolean useTCP;374private int timeout;375private int retries;376private byte[] obuf;377378public KdcCommunication(String kdc, int port, boolean useTCP,379int timeout, int retries, byte[] obuf) {380this.kdc = kdc;381this.port = port;382this.useTCP = useTCP;383this.timeout = timeout;384this.retries = retries;385this.obuf = obuf;386}387388// The caller only casts IOException and KrbException so don't389// add any new ones!390391public byte[] run() throws IOException, KrbException {392393byte[] ibuf = null;394395for (int i=1; i <= retries; i++) {396String proto = useTCP?"TCP":"UDP";397if (DEBUG) {398System.out.println(">>> KDCCommunication: kdc=" + kdc399+ " " + proto + ":"400+ port + ", timeout="401+ timeout402+ ",Attempt =" + i403+ ", #bytes=" + obuf.length);404}405try (NetClient kdcClient = NetClient.getInstance(406proto, kdc, port, timeout)) {407kdcClient.send(obuf);408ibuf = kdcClient.receive();409break;410} catch (SocketTimeoutException se) {411if (DEBUG) {412System.out.println ("SocketTimeOutException with " +413"attempt: " + i);414}415if (i == retries) {416ibuf = null;417throw se;418}419}420}421return ibuf;422}423}424425/**426* Parses a time value string. If it ends with "s", parses as seconds.427* Otherwise, parses as milliseconds.428* @param s the time string429* @return the integer value in milliseconds, or -1 if input is null or430* has an invalid format431*/432private static int parseTimeString(String s) {433if (s == null) {434return -1;435}436if (s.endsWith("s")) {437int seconds = parsePositiveIntString(s.substring(0, s.length()-1));438return (seconds < 0) ? -1 : (seconds*1000);439} else {440return parsePositiveIntString(s);441}442}443444/**445* Returns krb5.conf setting of {@code key} for a specific realm,446* which can be:447* 1. defined in the sub-stanza for the given realm inside [realms], or448* 2. defined in [libdefaults], or449* 3. defValue450* @param realm the given realm in which the setting is requested. Returns451* the global setting if null452* @param key the key for the setting453* @param defValue default value454* @return a value for the key455*/456private int getRealmSpecificValue(String realm, String key, int defValue) {457int v = defValue;458459if (realm == null) return v;460461int temp = -1;462try {463String value =464Config.getInstance().get("realms", realm, key);465if (key.equals("kdc_timeout")) {466temp = parseTimeString(value);467} else {468temp = parsePositiveIntString(value);469}470} catch (Exception exc) {471// Ignored, defValue will be picked up472}473474if (temp > 0) v = temp;475476return v;477}478479private static int parsePositiveIntString(String intString) {480if (intString == null)481return -1;482483int ret = -1;484485try {486ret = Integer.parseInt(intString);487} catch (Exception exc) {488return -1;489}490491if (ret >= 0)492return ret;493494return -1;495}496497/**498* Maintains a KDC accessible list. Unavailable KDCs are put into a499* secondary KDC list. When a KDC in the secondary list is available,500* it is removed from there. No insertion order in the secondary KDC list.501*502* There are two methods to deal with KDCs in the secondary KDC list.503* 1. Only try them when they are the only known KDCs.504* 2. Still try them, but with fewer retries and a smaller timeout value.505*/506static class KdcAccessibility {507// Known bad KDCs508private static Set<String> bads = new HashSet<>();509510private static synchronized void addBad(String kdc) {511if (DEBUG) {512System.out.println(">>> KdcAccessibility: add " + kdc);513}514bads.add(kdc);515}516517private static synchronized void removeBad(String kdc) {518if (DEBUG) {519System.out.println(">>> KdcAccessibility: remove " + kdc);520}521bads.remove(kdc);522}523524private static synchronized boolean isBad(String kdc) {525return bads.contains(kdc);526}527528private static synchronized void reset() {529if (DEBUG) {530System.out.println(">>> KdcAccessibility: reset");531}532bads.clear();533}534535// Returns a preferred KDC list by putting the bad ones at the end536private static synchronized List<String> list(String kdcList) {537StringTokenizer st = new StringTokenizer(kdcList);538List<String> list = new ArrayList<>();539if (badPolicy == BpType.TRY_LAST) {540List<String> badkdcs = new ArrayList<>();541while (st.hasMoreTokens()) {542String t = st.nextToken();543if (bads.contains(t)) badkdcs.add(t);544else list.add(t);545}546// Bad KDCs are put at last547list.addAll(badkdcs);548} else {549// All KDCs are returned in their original order,550// This include TRY_LESS and NONE551while (st.hasMoreTokens()) {552list.add(st.nextToken());553}554}555return list;556}557}558}559560561562