6844193: support max_retries in krb5.conf
authorweijun
Thu, 29 Apr 2010 15:51:10 +0800
changeset 5458 62f857d96000
parent 5457 d2782f1ecc9f
child 5459 e00814824f38
6844193: support max_retries in krb5.conf Reviewed-by: valeriep
jdk/src/share/classes/sun/security/krb5/Config.java
jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java
jdk/test/sun/security/krb5/auto/MaxRetries.java
--- a/jdk/src/share/classes/sun/security/krb5/Config.java	Thu Apr 29 15:50:40 2010 +0800
+++ b/jdk/src/share/classes/sun/security/krb5/Config.java	Thu Apr 29 15:51:10 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Portions Copyright 2000-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * Portions Copyright 2000-2010 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -109,7 +109,7 @@
     public static synchronized void refresh() throws KrbException {
         singleton = new Config();
         KeyTab.refresh();
-        KrbKdcReq.KdcAccessibility.reset();
+        KrbKdcReq.initStatic();
     }
 
 
--- a/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java	Thu Apr 29 15:50:40 2010 +0800
+++ b/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java	Thu Apr 29 15:51:10 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Portions Copyright 2000-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * Portions Copyright 2000-2010 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -51,28 +51,31 @@
 
 public abstract class KrbKdcReq {
 
-    // Currently there is no option to specify retries
-    // in the kerberos configuration file
-
-    private static final int DEFAULT_KDC_RETRY_LIMIT = Krb5.KDC_RETRY_LIMIT;
+    // The following settings can be configured in [libdefaults]
+    // section of krb5.conf, which are global for all realms. Each of
+    // them can also be defined in a realm, which overrides value here.
 
     /**
-     * Default timeout period when requesting a ticket from a KDC.
-     * If not specified in the configuration file,
-     * a value of 30 seconds is used.
+     * max retry time for a single KDC, default Krb5.KDC_RETRY_LIMIT (3)
+     */
+    private static int defaultKdcRetryLimit;
+    /**
+     * timeout requesting a ticket from KDC, in millisec, default 30 sec
      */
-    public static final int DEFAULT_KDC_TIMEOUT; // milliseconds
+    private static int defaultKdcTimeout;
+    /**
+     * max UDP packet size, default unlimited (-1)
+     */
+    private static int defaultUdpPrefLimit;
 
     private static final boolean DEBUG = Krb5.DEBUG;
 
-    private static int udpPrefLimit = -1;
-
     private static final String BAD_POLICY_KEY = "krb5.kdc.bad.policy";
 
     /**
      * What to do when a KDC is unavailable, specified in the
      * java.security file with key krb5.kdc.bad.policy.
-     * Possible values can be TRY_LAST or TRY_LESS
+     * Possible values can be TRY_LAST or TRY_LESS. Reloaded when refreshed.
      */
     private enum BpType {
         NONE, TRY_LAST, TRY_LESS
@@ -80,9 +83,16 @@
     private static int tryLessMaxRetries = 1;
     private static int tryLessTimeout = 5000;
 
-    private static final BpType badPolicy;
+    private static BpType badPolicy;
 
     static {
+        initStatic();
+    }
+
+    /**
+     * Read global settings
+     */
+    public static void initStatic() {
         String value = AccessController.doPrivileged(
         new PrivilegedAction<String>() {
             public String run() {
@@ -95,9 +105,21 @@
             if ("tryless".equals(ss[0])) {
                 if (ss.length > 1) {
                     String[] params = ss[1].split(",");
-                    tryLessMaxRetries = Integer.parseInt(params[0]);
-                    if (params.length > 1) {
-                        tryLessTimeout = Integer.parseInt(params[1]);
+                    try {
+                        int tmp0 = Integer.parseInt(params[0]);
+                        if (params.length > 1) {
+                            tryLessTimeout = Integer.parseInt(params[1]);
+                        }
+                        // Assign here in case of exception at params[1]
+                        tryLessMaxRetries = tmp0;
+                    } catch (NumberFormatException nfe) {
+                        // Ignored. Please note that tryLess is recognized and
+                        // used, parameters using default values
+                        if (DEBUG) {
+                            System.out.println("Invalid " + BAD_POLICY_KEY +
+                                    " parameter for tryLess: " +
+                                    value + ", use default");
+                        }
                     }
                 }
                 badPolicy = BpType.TRY_LESS;
@@ -110,30 +132,33 @@
             badPolicy = BpType.NONE;
         }
 
-        /*
-         * Get default timeout.
-         */
 
         int timeout = -1;
+        int max_retries = -1;
+        int udf_pref_limit = -1;
+
         try {
             Config cfg = Config.getInstance();
             String temp = cfg.getDefault("kdc_timeout", "libdefaults");
             timeout = parsePositiveIntString(temp);
+            temp = cfg.getDefault("max_retries", "libdefaults");
+            max_retries = parsePositiveIntString(temp);
             temp = cfg.getDefault("udp_preference_limit", "libdefaults");
-            udpPrefLimit = parsePositiveIntString(temp);
+            udf_pref_limit = parsePositiveIntString(temp);
         } catch (Exception exc) {
-           // ignore any exceptions; use the default time out values
+           // ignore any exceptions; use default values
            if (DEBUG) {
-                System.out.println ("Exception in getting kdc_timeout value, " +
-                                    "using default value " +
+                System.out.println ("Exception in getting KDC communication " +
+                                    "settings, using default value " +
                                     exc.getMessage());
            }
         }
+        defaultKdcTimeout = timeout > 0 ? timeout : 30*1000; // 30 seconds
+        defaultKdcRetryLimit =
+                max_retries > 0 ? max_retries : Krb5.KDC_RETRY_LIMIT;
+        defaultUdpPrefLimit = udf_pref_limit;
 
-        if (timeout > 0)
-            DEFAULT_KDC_TIMEOUT = timeout;
-        else
-            DEFAULT_KDC_TIMEOUT = 30*1000; // 30 seconds
+        KdcAccessibility.reset();
     }
 
     protected byte[] obuf;
@@ -151,6 +176,9 @@
 
     public String send(String realm)
         throws IOException, KrbException {
+        int udpPrefLimit = getRealmSpecificValue(
+                realm, "udp_preference_limit", defaultUdpPrefLimit);
+
         boolean useTCP = (udpPrefLimit > 0 &&
              (obuf != null && obuf.length > udpPrefLimit));
 
@@ -213,9 +241,10 @@
             return;
 
         int port = Krb5.KDC_INET_DEFAULT_PORT;
-        int retries = DEFAULT_KDC_RETRY_LIMIT;
-        int timeout = getKdcTimeout(realm);
-
+        int retries = getRealmSpecificValue(
+                realm, "max_retries", defaultKdcRetryLimit);
+        int timeout = getRealmSpecificValue(
+                realm, "kdc_timeout", defaultKdcTimeout);
         if (badPolicy == BpType.TRY_LESS &&
                 KdcAccessibility.isBad(tempKdc)) {
             if (retries > tryLessMaxRetries) {
@@ -322,6 +351,12 @@
 
             if (useTCP) {
                 TCPClient kdcClient = new TCPClient(kdc, port);
+                if (DEBUG) {
+                    System.out.println(">>> KDCCommunication: kdc=" + kdc
+                           + " TCP:"
+                           +  port
+                           + ", #bytes=" + obuf.length);
+                }
                 try {
                     /*
                      * Send the data to the kdc.
@@ -336,7 +371,7 @@
                 }
 
             } else {
-                // For each KDC we try DEFAULT_KDC_RETRY_LIMIT (3) times to
+                // For each KDC we try defaultKdcRetryLimit times to
                 // get the response
                 for (int i=1; i <= retries; i++) {
                     UDPClient kdcClient = new UDPClient(kdc, port, timeout);
@@ -382,37 +417,37 @@
     }
 
     /**
-     * Returns a timeout value for the KDC of the given realm.
-     * A KDC-specific timeout, if specified in the config file,
-     * overrides the default timeout (which may also be specified
-     * in the config file). Default timeout is returned if null
-     * is specified for realm.
-     * @param realm the realm which kdc's timeout is requested
-     * @return KDC timeout
+     * Returns krb5.conf setting of {@code key} for a specfic realm,
+     * which can be:
+     * 1. defined in the sub-stanza for the given realm inside [realms], or
+     * 2. defined in [libdefaults], or
+     * 3. defValue
+     * @param realm the given realm in which the setting is requested. Returns
+     * the global setting if null
+     * @param key the key for the setting
+     * @param defValue default value
+     * @return a value for the key
      */
-    private int getKdcTimeout(String realm)
-    {
-        int timeout = DEFAULT_KDC_TIMEOUT;
+    private int getRealmSpecificValue(String realm, String key, int defValue) {
+        int v = defValue;
 
-        if (realm == null)
-            return timeout;
+        if (realm == null) return v;
 
-        int tempTimeout = -1;
+        int temp = -1;
         try {
-            String temp =
-               Config.getInstance().getDefault("kdc_timeout", realm);
-            tempTimeout = parsePositiveIntString(temp);
+            String value =
+               Config.getInstance().getDefault(key, realm);
+            temp = parsePositiveIntString(value);
         } catch (Exception exc) {
+            // Ignored, defValue will be picked up
         }
 
-        if (tempTimeout > 0)
-            timeout = tempTimeout;
+        if (temp > 0) v = temp;
 
-        return timeout;
+        return v;
     }
 
-    private static int parsePositiveIntString(String intString)
-    {
+    private static int parsePositiveIntString(String intString) {
         if (intString == null)
             return -1;
 
@@ -461,7 +496,7 @@
             return bads.contains(kdc);
         }
 
-        public static synchronized void reset() {
+        private static synchronized void reset() {
             if (DEBUG) {
                 System.out.println(">>> KdcAccessibility: reset");
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/MaxRetries.java	Thu Apr 29 15:51:10 2010 +0800
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6844193
+ * @run main/timeout=300 MaxRetries
+ * @summary support max_retries in krb5.conf
+ */
+
+import java.io.*;
+import java.security.Security;
+
+public class MaxRetries {
+    public static void main(String[] args)
+            throws Exception {
+
+        System.setProperty("sun.security.krb5.debug", "true");
+        new OneKDC(null).writeJAASConf();
+        System.setProperty("java.security.krb5.conf", "alternative-krb5.conf");
+
+        // For tryLast
+        Security.setProperty("krb5.kdc.bad.policy", "trylast");
+        rewriteMaxRetries(4);
+        test1(4000, 6);         // 1 1 1 1 2 2
+        test1(4000, 2);         // 2 2
+
+        rewriteMaxRetries(1);
+        test1(1000, 3);         // 1 2 2
+        test1(1000, 2);         // 2 2
+
+        rewriteMaxRetries(-1);
+        test1(5000, 4);         // 1 1 2 2
+        test1(5000, 2);         // 2 2
+
+        // For tryLess
+        Security.setProperty("krb5.kdc.bad.policy", "tryless");
+        rewriteMaxRetries(4);
+        test1(4000, 7);         // 1 1 1 1 2 1 2
+        test1(4000, 4);         // 1 2 1 2
+
+        rewriteMaxRetries(1);
+        test1(1000, 4);         // 1 2 1 2
+        test1(1000, 4);         // 1 2 1 2
+
+        rewriteMaxRetries(-1);
+        test1(5000, 5);         // 1 1 2 1 2
+        test1(5000, 4);         // 1 2 1 2
+
+        rewriteUdpPrefLimit(-1, -1);    // default, no limit
+        test2("UDP");
+
+        rewriteUdpPrefLimit(10, -1);    // global rules
+        test2("TCP");
+
+        rewriteUdpPrefLimit(10, 10000); // realm rules
+        test2("UDP");
+
+        rewriteUdpPrefLimit(10000, 10); // realm rules
+        test2("TCP");
+    }
+
+    /**
+     * One round of test for max_retries and timeout.
+     * @param timeout the expected timeout
+     * @param count the expected total try
+     */
+    private static void test1(int timeout, int count) throws Exception {
+        String timeoutTag = "timeout=" + timeout;
+        ByteArrayOutputStream bo = new ByteArrayOutputStream();
+        PrintStream oldout = System.out;
+        System.setOut(new PrintStream(bo));
+        Context c = Context.fromJAAS("client");
+        System.setOut(oldout);
+
+        String[] lines = new String(bo.toByteArray()).split("\n");
+        System.out.println("----------------- TEST (" + timeout + "," +
+                count + ") -----------------");
+        for (String line: lines) {
+            if (line.startsWith(">>> KDCCommunication")) {
+                System.out.println(line);
+                if (line.indexOf(timeoutTag) < 0) {
+                    throw new Exception("Wrong timeout value");
+                }
+                count--;
+            }
+        }
+        if (count != 0) {
+            throw new Exception("Retry count is " + count + " less");
+        }
+    }
+
+    /**
+     * One round of test for udp_preference_limit.
+     * @param proto the expected protocol used
+     */
+    private static void test2(String proto) throws Exception {
+        ByteArrayOutputStream bo = new ByteArrayOutputStream();
+        PrintStream oldout = System.out;
+        System.setOut(new PrintStream(bo));
+        Context c = Context.fromJAAS("client");
+        System.setOut(oldout);
+
+        int count = 2;
+        String[] lines = new String(bo.toByteArray()).split("\n");
+        System.out.println("----------------- TEST -----------------");
+        for (String line: lines) {
+            if (line.startsWith(">>> KDCCommunication")) {
+                System.out.println(line);
+                count--;
+                if (line.indexOf(proto) < 0) {
+                    throw new Exception("Wrong timeout value");
+                }
+            }
+        }
+        if (count != 0) {
+            throw new Exception("Retry count is " + count + " less");
+        }
+    }
+
+    /**
+     * Set udp_preference_limit for global and realm
+     */
+    private static void rewriteUdpPrefLimit(int global, int realm)
+            throws Exception {
+        BufferedReader fr = new BufferedReader(new FileReader(OneKDC.KRB5_CONF));
+        FileWriter fw = new FileWriter("alternative-krb5.conf");
+        while (true) {
+            String s = fr.readLine();
+            if (s == null) {
+                break;
+            }
+            if (s.startsWith("[realms]")) {
+                // Reconfig global setting
+                if (global != -1) {
+                    fw.write("udp_preference_limit = " + global + "\n");
+                }
+            } else if (s.trim().startsWith("kdc = ")) {
+                if (realm != -1) {
+                    // Reconfig for realm
+                    fw.write("    udp_preference_limit = " + realm + "\n");
+                }
+            }
+            fw.write(s + "\n");
+        }
+        fr.close();
+        fw.close();
+        sun.security.krb5.Config.refresh();
+    }
+
+    /**
+     * Set max_retries and timeout value for realm. The global value is always
+     * 2 and 5000.
+     * @param value max_retries and timeout/1000 for a realm, -1 means none.
+     */
+    private static void rewriteMaxRetries(int value) throws Exception {
+        BufferedReader fr = new BufferedReader(new FileReader(OneKDC.KRB5_CONF));
+        FileWriter fw = new FileWriter("alternative-krb5.conf");
+        while (true) {
+            String s = fr.readLine();
+            if (s == null) {
+                break;
+            }
+            if (s.startsWith("[realms]")) {
+                // Reconfig global setting
+                fw.write("max_retries = 2\n");
+                fw.write("kdc_timeout = 5000\n");
+            } else if (s.trim().startsWith("kdc = ")) {
+                if (value != -1) {
+                    // Reconfig for realm
+                    fw.write("    max_retries = " + value + "\n");
+                    fw.write("    kdc_timeout = " + (value*1000) + "\n");
+                }
+                // Add a bad KDC as the first candidate
+                fw.write("    kdc = localhost:33333\n");
+            }
+            fw.write(s + "\n");
+        }
+        fr.close();
+        fw.close();
+        sun.security.krb5.Config.refresh();
+    }
+}