1 /* 2 * Copyright (c) 2009, 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 import java.io.*; 25 import java.net.BindException; 26 import java.net.DatagramPacket; 27 import java.net.DatagramSocket; 28 import java.net.InetAddress; 29 import java.util.regex.Matcher; 30 import java.util.regex.Pattern; 31 import javax.security.auth.login.LoginException; 32 import sun.security.krb5.Asn1Exception; 33 import sun.security.krb5.Config; 34 35 public class BadKdc { 36 37 // Matches the krb5 debug output: 38 // >>> KDCCommunication: kdc=kdc.rabbit.hole UDP:14319, timeout=2000,... 39 // ^ kdc# ^ timeout 40 static final Pattern re = Pattern.compile( 41 ">>> KDCCommunication: kdc=kdc.rabbit.hole UDP:(\\d)...., " + 42 "timeout=(\\d+),"); 43 44 // Ratio for timeout values of all timeout tests. Not final so that 45 // each test can choose their own. 46 static float ratio = 2f; 47 48 static void setRatio(float ratio) { 49 BadKdc.ratio = ratio; 50 } 51 52 static float getRatio() { 53 return ratio; 54 } 55 56 // Gets real timeout value. This method is called when writing krb5.conf 57 static int toReal(int from) { 58 return (int)(from * ratio + .5); 59 } 60 61 // De-ratio a millisecond value to second 62 static int toSymbolicSec(int from) { 63 return (int)(from / ratio / 1000f + 0.5); 64 } 65 66 /* 67 * There are several cases this test fails: 68 * 69 * 1. The random selected port is used by another process. No good way to 70 * prevent this happening, coz krb5.conf must be written before KDC starts. 71 * There are two different outcomes: 72 * 73 * a. Cannot start the KDC. A BindException thrown. 74 * b. When trying to access a non-existing KDC, a response is received! 75 * Most likely a Asn1Exception thrown 76 * 77 * 2. Even if a KDC is started, and more than 20 seconds pass by, a timeout 78 * can still happens for the first UDP request. In fact, the KDC did not 79 * received it at all. This happens on almost all platforms, especially 80 * solaris-i586 and solaris-x64. 81 * 82 * To avoid them: 83 * 84 * 1. Catch those exceptions and ignore 85 * 86 * 2. a. Make the timeout longer? useless 87 * b. Read the output carefully, if there is a timeout, it's OK. 88 * Just make sure the retries times and KDCs are correct. 89 * This is tough. 90 * c. Feed the KDC a UDP packet first. The current "solution". 91 */ 92 public static void go(String... expected) 93 throws Exception { 94 try { 95 go0(expected); 96 } catch (BindException be) { 97 System.out.println("The random port is used by another process"); 98 } catch (LoginException le) { 99 Throwable cause = le.getCause(); 100 if (cause instanceof Asn1Exception) { 101 System.out.println("Bad packet possibly from another process"); 102 return; 103 } 104 throw le; 105 } 106 } 107 108 public static void go0(String... expected) 109 throws Exception { 110 System.setProperty("sun.security.krb5.debug", "true"); 111 112 // Idle UDP sockets will trigger a SocketTimeoutException, without it, 113 // a PortUnreachableException will be thrown. 114 DatagramSocket d1 = null, d2 = null, d3 = null; 115 116 // Make sure KDCs' ports starts with 1 and 2 and 3, 117 // useful for checking debug output. 118 int p1 = 10000 + new java.util.Random().nextInt(10000); 119 int p2 = 20000 + new java.util.Random().nextInt(10000); 120 int p3 = 30000 + new java.util.Random().nextInt(10000); 121 122 FileWriter fw = new FileWriter("alternative-krb5.conf"); 123 124 fw.write("[libdefaults]\n" + 125 "default_realm = " + OneKDC.REALM + "\n" + 126 "kdc_timeout = " + toReal(2000) + "\n"); 127 fw.write("[realms]\n" + OneKDC.REALM + " = {\n" + 128 "kdc = " + OneKDC.KDCHOST + ":" + p1 + "\n" + 129 "kdc = " + OneKDC.KDCHOST + ":" + p2 + "\n" + 130 "kdc = " + OneKDC.KDCHOST + ":" + p3 + "\n" + 131 "}\n"); 132 133 fw.close(); 134 System.setProperty("java.security.krb5.conf", "alternative-krb5.conf"); 135 Config.refresh(); 136 137 // Turn on k3 only 138 d1 = new DatagramSocket(p1); 139 d2 = new DatagramSocket(p2); 140 KDC k3 = on(p3); 141 142 test(expected[0]); 143 test(expected[1]); 144 Config.refresh(); 145 test(expected[2]); 146 147 k3.terminate(); // shutdown k3 148 d3 = new DatagramSocket(p3); 149 150 d2.close(); 151 on(p2); // k2 is on 152 153 test(expected[3]); 154 d1.close(); 155 on(p1); // k1 and k2 is on 156 test(expected[4]); 157 158 d3.close(); 159 } 160 161 private static KDC on(int p) throws Exception { 162 KDC k = new KDC(OneKDC.REALM, OneKDC.KDCHOST, p, true); 163 k.addPrincipal(OneKDC.USER, OneKDC.PASS); 164 k.addPrincipalRandKey("krbtgt/" + OneKDC.REALM); 165 // Feed a packet to newly started KDC to warm it up 166 System.err.println("-------- IGNORE THIS ERROR MESSAGE --------"); 167 new DatagramSocket().send( 168 new DatagramPacket("Hello".getBytes(), 5, 169 InetAddress.getByName(OneKDC.KDCHOST), p)); 170 return k; 171 } 172 173 private static void test(String expected) throws Exception { 174 ByteArrayOutputStream bo = new ByteArrayOutputStream(); 175 System.out.println("----------------- TEST -----------------"); 176 try { 177 test0(bo, expected); 178 } catch (Exception e) { 179 System.out.println("----------------- ERROR -----------------"); 180 System.out.println(new String(bo.toByteArray())); 181 System.out.println("--------------- ERROR END ---------------"); 182 throw e; 183 } 184 } 185 186 /** 187 * One round of test for max_retries and timeout. 188 * @param expected the expected kdc# timeout kdc# timeout... 189 */ 190 private static void test0(ByteArrayOutputStream bo, String expected) 191 throws Exception { 192 PrintStream oldout = System.out; 193 boolean failed = false; 194 System.setOut(new PrintStream(bo)); 195 try { 196 Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false); 197 } catch (Exception e) { 198 failed = true; 199 } finally { 200 System.setOut(oldout); 201 } 202 203 String[] lines = new String(bo.toByteArray()).split("\n"); 204 StringBuilder sb = new StringBuilder(); 205 for (String line: lines) { 206 Matcher m = re.matcher(line); 207 if (m.find()) { 208 System.out.println(line); 209 sb.append(m.group(1)) 210 .append(toSymbolicSec(Integer.parseInt(m.group(2)))); 211 } 212 } 213 if (failed) sb.append('-'); 214 215 String output = sb.toString(); 216 System.out.println("Expected: " + expected + ", actual " + output); 217 if (!output.matches(expected)) { 218 throw new Exception("Does not match"); 219 } 220 } 221 }