1 /*
   2  * Copyright (c) 2010, 2012, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6844193
  27  * @compile -XDignore.symbol.file MaxRetries.java
  28  * @run main/othervm/timeout=300 -Dsun.net.spi.nameservice.provider.1=ns,mock MaxRetries
  29  * @summary support max_retries in krb5.conf
  30  */
  31 
  32 import javax.security.auth.login.LoginException;
  33 import java.io.*;
  34 import java.net.DatagramSocket;
  35 import java.security.Security;
  36 
  37 public class MaxRetries {
  38 
  39     static int idlePort = -1;
  40     static CommMatcher cm = new CommMatcher();
  41 
  42     public static void main(String[] args)
  43             throws Exception {
  44 
  45         System.setProperty("sun.security.krb5.debug", "true");
  46         OneKDC kdc = new OneKDC(null).writeJAASConf();
  47 
  48         // An idle UDP socket to prevent PortUnreachableException
  49         DatagramSocket ds = new DatagramSocket();
  50         idlePort = ds.getLocalPort();
  51 
  52         cm.addPort(idlePort);
  53         cm.addPort(kdc.getPort());
  54 
  55         System.setProperty("java.security.krb5.conf", "alternative-krb5.conf");
  56 
  57         Security.setProperty("krb5.kdc.bad.policy", "trylast");
  58 
  59         // We always make the real timeout to be 1 second
  60         BadKdc.setRatio(0.25f);
  61         rewriteMaxRetries(4);
  62 
  63         // Explanation: In this case, max_retries=4 and timeout=4s.
  64         // For AS-REQ without preauth, we will see 4 4s timeout on kdc#1
  65         // ("a4" repeat 4 times), and one 4s timeout on kdc#2 ("b4"). For
  66         // AS-REQ with preauth, one 4s timeout on kdc#2 (second "b4").
  67         // we tolerate 4 real timeout on kdc#2, so make it "(b4){2,6}".
  68         test1("a4a4a4a4b4b4", "a4a4a4a4(b4){2,6}");
  69         test1("b4b4", "(b4){2,6}");
  70 
  71         BadKdc.setRatio(1f);
  72         rewriteMaxRetries(1);
  73         // Explanation: Since max_retries=1 only, we could fail in 1st or 2nd
  74         // AS-REQ to kdc#2.
  75         String actual = test1("a1b1b1", "(a1b1b1|a1b1x|a1b1b1x)");
  76         if (actual.endsWith("x")) {
  77             // If 1st attempt fails, all bads are back available
  78             test1("a1b1b1", "(a1b1b1|a1b1x|a1b1b1x)");
  79         } else {
  80             test1("b1b1", "(b1b1|b1x|b1b1x)");
  81         }
  82 
  83         BadKdc.setRatio(0.2f);
  84         rewriteMaxRetries(-1);
  85         test1("a5a5a5b5b5", "a5a5a5(b5){2,4}");
  86         test1("b5b5", "(b5){2,4}");
  87 
  88         BadKdc.setRatio(0.25f);
  89         Security.setProperty("krb5.kdc.bad.policy",
  90                 "tryless:1,1000");
  91         rewriteMaxRetries(4);
  92         test1("a4a4a4a4b4a4b4", "a4a4a4a4(b4){1,3}a4(b4){1,3}");
  93         test1("a4b4a4b4", "a4(b4){1,3}a4(b4){1,3}");
  94 
  95         BadKdc.setRatio(1f);
  96         rewriteMaxRetries(1);
  97         actual = test1("a1b1a1b1", "(a1b1|a1b1x|a1b1a1b1|a1b1a1b1x)");
  98         if (actual.endsWith("x")) {
  99             test1("a1b1a1b1", "(a1b1|a1b1x|a1b1a1b1|a1b1a1b1x)");
 100         } else {
 101             test1("a1b1a1b1", "(a1b1|a1b1x|a1b1a1b1|a1b1a1b1x)");
 102         }
 103 
 104         BadKdc.setRatio(.2f);
 105         rewriteMaxRetries(-1);
 106         test1("a5a5a5b5a5b5", "a5a5a5(b5){1,2}a5(b5){1,2}");
 107         test1("a5b5a5b5", "a5(b5){1,2}a5(b5){1,2}");
 108 
 109         BadKdc.setRatio(1f);
 110         rewriteMaxRetries(2);
 111         if (BadKdc.toReal(2000) > 1000) {
 112             // Explanation: if timeout is longer than 1s in tryLess,
 113             // we will see "a1" at 2nd kdc#1 access
 114             test1("a2a2b2a1b2", "a2a2(b2){1,2}a1(b2){1,2}");
 115         } else {
 116             test1("a2a2b2a2b2", "a2a2(b2){1,2}a2(b2){1,2}");
 117         }
 118 
 119         BadKdc.setRatio(1f);
 120 
 121         rewriteUdpPrefLimit(-1, -1);    // default, no limit
 122         test2("UDP");
 123 
 124         rewriteUdpPrefLimit(10, -1);    // global rules
 125         test2("TCP");
 126 
 127         rewriteUdpPrefLimit(10, 10000); // realm rules
 128         test2("UDP");
 129 
 130         rewriteUdpPrefLimit(10000, 10); // realm rules
 131         test2("TCP");
 132 
 133         ds.close();
 134     }
 135 
 136     /**
 137      * One round of test for max_retries and timeout.
 138      *
 139      * @param exact the expected exact match, where no timeout
 140      *              happens for real KDCs
 141      * @param relaxed the expected relaxed match, where some timeout
 142      *                could happen for real KDCs
 143      * @return the actual result
 144      */
 145     private static String test1(String exact, String relaxed) throws Exception {
 146         ByteArrayOutputStream bo = new ByteArrayOutputStream();
 147         PrintStream oldout = System.out;
 148         System.setOut(new PrintStream(bo));
 149         boolean failed = false;
 150         long start = System.nanoTime();
 151         try {
 152             Context c = Context.fromJAAS("client");
 153         } catch (LoginException e) {
 154             failed = true;
 155         }
 156         System.setOut(oldout);
 157 
 158         String[] lines = new String(bo.toByteArray()).split("\n");
 159         System.out.println("----------------- TEST (" + exact
 160                 + ") -----------------");
 161 
 162         // Result, a series of timeout + kdc#
 163         StringBuilder sb = new StringBuilder();
 164         for (String line: lines) {
 165             if (cm.match(line)) {
 166                 System.out.println(line);
 167                 sb.append(cm.kdc()).append(cm.timeout());
 168             }
 169         }
 170         if (failed) {
 171             sb.append("x");
 172         }
 173         System.out.println("Time: " + (System.nanoTime() - start) / 1000000000d);
 174         String actual = sb.toString();
 175         System.out.println("Actual: " + actual);
 176         if (actual.equals(exact)) {
 177             System.out.println("Exact match: " + exact);
 178         } else if (actual.matches(relaxed)) {
 179             System.out.println("!!!! Tolerant match: " + relaxed);
 180         } else {
 181             throw new Exception("Match neither " + exact + " nor " + relaxed);
 182         }
 183         return actual;
 184     }
 185 
 186     /**
 187      * One round of test for udp_preference_limit.
 188      * @param proto the expected protocol used
 189      */
 190     private static void test2(String proto) throws Exception {
 191         ByteArrayOutputStream bo = new ByteArrayOutputStream();
 192         PrintStream oldout = System.out;
 193         System.setOut(new PrintStream(bo));
 194         Context c = Context.fromJAAS("client");
 195         System.setOut(oldout);
 196 
 197         int count = 2;
 198         String[] lines = new String(bo.toByteArray()).split("\n");
 199         System.out.println("----------------- TEST -----------------");
 200         for (String line: lines) {
 201             if (cm.match(line)) {
 202                 System.out.println(line);
 203                 count--;
 204                 if (!cm.protocol().equals(proto)) {
 205                     throw new Exception("Wrong protocol value");
 206                 }
 207             }
 208         }
 209         if (count != 0) {
 210             throw new Exception("Retry count is " + count + " less");
 211         }
 212     }
 213 
 214     /**
 215      * Set udp_preference_limit for global and realm
 216      */
 217     private static void rewriteUdpPrefLimit(int global, int realm)
 218             throws Exception {
 219         BufferedReader fr = new BufferedReader(new FileReader(OneKDC.KRB5_CONF));
 220         FileWriter fw = new FileWriter("alternative-krb5.conf");
 221         while (true) {
 222             String s = fr.readLine();
 223             if (s == null) {
 224                 break;
 225             }
 226             if (s.startsWith("[realms]")) {
 227                 // Reconfig global setting
 228                 fw.write("kdc_timeout = 5000\n");
 229                 if (global != -1) {
 230                     fw.write("udp_preference_limit = " + global + "\n");
 231                 }
 232             } else if (s.trim().startsWith("kdc = ")) {
 233                 if (realm != -1) {
 234                     // Reconfig for realm
 235                     fw.write("    udp_preference_limit = " + realm + "\n");
 236                 }
 237             }
 238             fw.write(s + "\n");
 239         }
 240         fr.close();
 241         fw.close();
 242         sun.security.krb5.Config.refresh();
 243     }
 244 
 245     /**
 246      * Set max_retries and timeout value for realm. The global value is always
 247      * 3 and 5000.
 248      *
 249      * @param value max_retries and timeout/1000 for a realm, -1 means none.
 250      */
 251     private static void rewriteMaxRetries(int value) throws Exception {
 252         BufferedReader fr = new BufferedReader(new FileReader(OneKDC.KRB5_CONF));
 253         FileWriter fw = new FileWriter("alternative-krb5.conf");
 254         while (true) {
 255             String s = fr.readLine();
 256             if (s == null) {
 257                 break;
 258             }
 259             if (s.startsWith("[realms]")) {
 260                 // Reconfig global setting
 261                 fw.write("max_retries = 3\n");
 262                 fw.write("kdc_timeout = " + BadKdc.toReal(5000) + "\n");
 263             } else if (s.trim().startsWith("kdc = ")) {
 264                 if (value != -1) {
 265                     // Reconfig for realm
 266                     fw.write("    max_retries = " + value + "\n");
 267                     fw.write("    kdc_timeout = " + BadKdc.toReal(value*1000) + "\n");
 268                 }
 269                 // Add a bad KDC as the first candidate
 270                 fw.write("    kdc = localhost:" + idlePort + "\n");
 271             }
 272             fw.write(s + "\n");
 273         }
 274         fr.close();
 275         fw.close();
 276         sun.security.krb5.Config.refresh();
 277     }
 278 }