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