jdk/test/sun/security/krb5/auto/BadKdc.java
changeset 44227 9379643900c6
parent 44226 815831f585ee
parent 44225 81102185f6fe
child 44228 e46434c65a2b
equal deleted inserted replaced
44226:815831f585ee 44227:9379643900c6
     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 }