1 /*
   2  * Copyright (c) 2013, 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 /* @test
  25  * @bug 8004502
  26  * @summary Sanity check that NTLM will not be selected by the http protocol
  27  *    handler when running on a profile that does not support NTLM
  28  * @run main/othervm NoNTLM
  29  */
  30 
  31 import java.net.*;
  32 import java.io.*;
  33 import sun.net.www.MessageHeader;
  34 
  35 public class NoNTLM {
  36 
  37     static final String CRLF = "\r\n";
  38 
  39     static final String OKAY =
  40         "HTTP/1.1 200" + CRLF +
  41         "Content-Length: 0" + CRLF +
  42         "Connection: close" + CRLF +
  43         CRLF;
  44 
  45     static class Client implements Runnable {
  46         private final URL url;
  47         private volatile IOException ioe;
  48         private volatile int respCode;
  49 
  50         Client(int port) throws IOException {
  51             this.url = new URL("http://127.0.0.1:" + port + "/foo.html");
  52         }
  53 
  54         public void run() {
  55             try {
  56                 HttpURLConnection uc =
  57                     (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
  58                 try {
  59                     uc.getInputStream();
  60                 } catch (IOException x) {
  61                     respCode = uc.getResponseCode();
  62                     throw x;
  63                 }
  64                 uc.disconnect();
  65             } catch (IOException x) {
  66                 if (respCode == 0)
  67                     respCode = -1;
  68                 ioe = x;
  69             }
  70         }
  71 
  72         IOException ioException() {
  73             return ioe;
  74         }
  75 
  76         int respCode() {
  77             return respCode;
  78         }
  79 
  80         static void start(int port) throws IOException {
  81             Client client = new Client(port);
  82             new Thread(client).start();
  83         }
  84     }
  85 
  86     /**
  87      * Return the http response with WWW-Authenticate headers for the given
  88      * authentication schemes.
  89      */
  90     static String authReplyFor(String... schemes) {
  91         // construct the server reply
  92         String reply = "HTTP/1.1 401 Unauthorized" + CRLF +
  93                        "Content-Length: 0"+ CRLF +
  94                        "Connection: close" + CRLF;
  95         for (String s: schemes) {
  96             switch (s) {
  97                 case "Basic" :
  98                     reply += "WWW-Authenticate: Basic realm=\"wallyworld\"" + CRLF;
  99                     break;
 100                 case "Digest" :
 101                     reply += "WWW-Authenticate: Digest" +
 102                              " realm=\"wallyworld\"" +
 103                              " domain=/" +
 104                              " nonce=\"abcdefghijklmnopqrstuvwxyz\"" +
 105                              " qop=\"auth\"" + CRLF;
 106                     break;
 107                 case "NTLM" :
 108                     reply += "WWW-Authenticate: NTLM" + CRLF;
 109                     break;
 110                 default :
 111                     throw new RuntimeException("Should not get here");
 112             }
 113         }
 114         reply += CRLF;
 115         return reply;
 116     }
 117 
 118     /**
 119      * Test the http protocol handler with the given authentication schemes
 120      * in the WWW-Authenticate header.
 121      */
 122     static void test(String... schemes) throws IOException {
 123 
 124         // the authentication scheme that the client is expected to choose
 125         String expected = null;
 126         for (String s: schemes) {
 127             if (expected == null) {
 128                 expected = s;
 129             } else if (s.equals("Digest")) {
 130                 expected = s;
 131             }
 132         }
 133 
 134         // server reply
 135         String reply = authReplyFor(schemes);
 136 
 137         System.out.println("====================================");
 138         System.out.println("Expect client to choose: " + expected);
 139         System.out.println(reply);
 140 
 141         try (ServerSocket ss = new ServerSocket(0)) {
 142             Client.start(ss.getLocalPort());
 143 
 144             // client ---- GET ---> server
 145             // client <--- 401 ---- server
 146             try (Socket s = ss.accept()) {
 147                 new MessageHeader().parseHeader(s.getInputStream());
 148                 s.getOutputStream().write(reply.getBytes("US-ASCII"));
 149             }
 150 
 151             // client ---- GET ---> server
 152             // client <--- 200 ---- server
 153             String auth;
 154             try (Socket s = ss.accept()) {
 155                 MessageHeader mh = new MessageHeader();
 156                 mh.parseHeader(s.getInputStream());
 157                 s.getOutputStream().write(OKAY.getBytes("US-ASCII"));
 158                 auth = mh.findValue("Authorization");
 159             }
 160 
 161             // check Authorization header
 162             if (auth == null)
 163                 throw new RuntimeException("Authorization header not found");
 164             System.out.println("Server received Authorization header: " + auth);
 165             String[] values = auth.split(" ");
 166             if (!values[0].equals(expected))
 167                 throw new RuntimeException("Unexpected value");
 168         }
 169     }
 170 
 171     /**
 172      * Test the http protocol handler with one WWW-Authenticate header with
 173      * the value "NTLM".
 174      */
 175     static void testNTLM() throws Exception {
 176         // server reply
 177         String reply = authReplyFor("NTLM");
 178 
 179         System.out.println("====================================");
 180         System.out.println("Expect client to fail with 401 Unauthorized");
 181         System.out.println(reply);
 182 
 183         try (ServerSocket ss = new ServerSocket(0)) {
 184             Client client = new Client(ss.getLocalPort());
 185             Thread thr = new Thread(client);
 186             thr.start();
 187 
 188             // client ---- GET ---> server
 189             // client <--- 401 ---- client
 190             try (Socket s = ss.accept()) {
 191                 new MessageHeader().parseHeader(s.getInputStream());
 192                 s.getOutputStream().write(reply.getBytes("US-ASCII"));
 193             }
 194 
 195             // the client should fail with 401
 196             System.out.println("Waiting for client to terminate");
 197             thr.join();
 198             IOException ioe = client.ioException();
 199             if (ioe != null)
 200                 System.out.println("Client failed: " + ioe);
 201             int respCode = client.respCode();
 202             if (respCode != 0 && respCode != -1)
 203                 System.out.println("Client received HTTP response code: " + respCode);
 204             if (respCode != HttpURLConnection.HTTP_UNAUTHORIZED)
 205                 throw new RuntimeException("Unexpected response code");
 206         }
 207     }
 208 
 209     public static void main(String[] args) throws Exception {
 210         // assume NTLM is not supported when Kerberos is not available
 211         try {
 212             Class.forName("javax.security.auth.kerberos.KerberosPrincipal");
 213             System.out.println("Kerberos is present, assuming NTLM is supported too");
 214             return;
 215         } catch (ClassNotFoundException okay) { }
 216 
 217         // setup Authenticator
 218         Authenticator.setDefault(new Authenticator() {
 219             @Override
 220             protected PasswordAuthentication getPasswordAuthentication() {
 221                 return new PasswordAuthentication("user", "pass".toCharArray());
 222             }
 223         });
 224 
 225         // test combinations of authentication schemes
 226         test("Basic");
 227         test("Digest");
 228         test("Basic", "Digest");
 229         test("Basic", "NTLM");
 230         test("Digest", "NTLM");
 231         test("Basic", "Digest", "NTLM");
 232 
 233         // test NTLM only, this should fail with "401 Unauthorized"
 234         testNTLM();
 235 
 236         System.out.println();
 237         System.out.println("TEST PASSED");
 238     }
 239 }
 240