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 }