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     /**
 120      * Test the http protocol handler with the given authentication schemes
 121      * in the WWW-Authenticate header.
 122      */
 123     static void test(String... schemes) throws IOException {        
 124         
 125         // the authentication scheme that the client is expected to choose
 126         String expected = null;
 127         for (String s: schemes) {
 128             if (expected == null) {
 129                 expected = s;
 130             } else if (s.equals("Digest")) {
 131                 expected = s;
 132             }
 133         }
 134         
 135         // server reply
 136         String reply = authReplyFor(schemes);
 137         
 138         System.out.println("====================================");
 139         System.out.println("Expect client to choose: " + expected);
 140         System.out.println(reply);
 141         
 142         try (ServerSocket ss = new ServerSocket(0)) {   
 143             Client.start(ss.getLocalPort());
 144             
 145             // client ---- GET ---> server
 146             // client <--- 401 ---- server
 147             try (Socket s = ss.accept()) {
 148                 new MessageHeader().parseHeader(s.getInputStream());
 149                 s.getOutputStream().write(reply.getBytes("US-ASCII"));  
 150             }
 151 
 152             // client ---- GET ---> server
 153             // client <--- 200 ---- server
 154             String auth;
 155             try (Socket s = ss.accept()) {
 156                 MessageHeader mh = new MessageHeader();
 157                 mh.parseHeader(s.getInputStream());
 158                 s.getOutputStream().write(OKAY.getBytes("US-ASCII"));
 159                 auth = mh.findValue("Authorization");
 160             }
 161             
 162             // check Authorization header 
 163             if (auth == null)
 164                 throw new RuntimeException("Authorization header not found");
 165             System.out.println("Server received Authorization header: " + auth);
 166             String[] values = auth.split(" ");
 167             if (!values[0].equals(expected))
 168                 throw new RuntimeException("Unexpected value");
 169         }
 170     }
 171     
 172     /**
 173      * Test the http protocol handler with one WWW-Authenticate header with
 174      * the value "NTLM".
 175      */
 176     static void testNTLM() throws Exception {
 177         // server reply
 178         String reply = authReplyFor("NTLM");
 179         
 180         System.out.println("====================================");
 181         System.out.println("Expect client to fail with 401 Unauthorized");
 182         System.out.println(reply);
 183         
 184         try (ServerSocket ss = new ServerSocket(0)) {   
 185             Client client = new Client(ss.getLocalPort());
 186             Thread thr = new Thread(client);
 187             thr.start();
 188             
 189             // client ---- GET ---> server
 190             // client <--- 401 ---- client
 191             try (Socket s = ss.accept()) {
 192                 new MessageHeader().parseHeader(s.getInputStream());
 193                 s.getOutputStream().write(reply.getBytes("US-ASCII"));  
 194             }
 195 
 196             // the client should fail with 401
 197             System.out.println("Waiting for client to terminate");
 198             thr.join();
 199             IOException ioe = client.ioException();
 200             if (ioe != null)
 201                 System.out.println("Client failed: " + ioe);
 202             int respCode = client.respCode();
 203             if (respCode != 0 && respCode != -1)
 204                 System.out.println("Client received HTTP response code: " + respCode);
 205             if (respCode != HttpURLConnection.HTTP_UNAUTHORIZED)
 206                 throw new RuntimeException("Unexpected response code");
 207         }
 208     }
 209     
 210     public static void main(String[] args) throws Exception {
 211         // assume NTLM is not supported when Kerberos is not available
 212         try {
 213             Class.forName("javax.security.auth.kerberos.KerberosPrincipal");
 214             System.out.println("Kerberos is present, assuming NTLM is supported too");
 215             return;
 216         } catch (ClassNotFoundException okay) { }
 217         
 218         // setup Authenticator
 219         Authenticator.setDefault(new Authenticator() {
 220             @Override
 221             protected PasswordAuthentication getPasswordAuthentication() {
 222                 return new PasswordAuthentication("user", "pass".toCharArray());
 223             }
 224         });
 225         
 226         // test combinations of authentication schemes
 227         test("Basic");
 228         test("Digest");
 229         test("Basic", "Digest");
 230         test("Basic", "NTLM");
 231         test("Digest", "NTLM");
 232         test("Basic", "Digest", "NTLM");
 233         
 234         // test NTLM only, this should fail with "401 Unauthorized"
 235         testNTLM();
 236         
 237         System.out.println();
 238         System.out.println("TEST PASSED");
 239     }
 240 }
 241