1/*
2 * Copyright (c) 2002, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.net.www.protocol.http;
27
28import java.util.Collections;
29import java.util.Iterator;
30import java.util.HashMap;
31import java.util.Set;
32
33import sun.net.www.*;
34import sun.security.action.GetPropertyAction;
35
36/**
37 * This class is used to parse the information in WWW-Authenticate: and Proxy-Authenticate:
38 * headers. It searches among multiple header lines and within each header line
39 * for the best currently supported scheme. It can also return a HeaderParser
40 * containing the challenge data for that particular scheme.
41 *
42 * Some examples:
43 *
44 * WWW-Authenticate: Basic realm="foo" Digest realm="bar" NTLM
45 *  Note the realm parameter must be associated with the particular scheme.
46 *
47 * or
48 *
49 * WWW-Authenticate: Basic realm="foo"
50 * WWW-Authenticate: Digest realm="foo",qop="auth",nonce="thisisanunlikelynonce"
51 * WWW-Authenticate: NTLM
52 *
53 * or
54 *
55 * WWW-Authenticate: Basic realm="foo"
56 * WWW-Authenticate: NTLM ASKAJK9893289889QWQIOIONMNMN
57 *
58 * The last example shows how NTLM breaks the rules of rfc2617 for the structure of
59 * the authentication header. This is the reason why the raw header field is used for ntlm.
60 *
61 * At present, the class chooses schemes in following order :
62 *      1. Negotiate (if supported)
63 *      2. Kerberos (if supported)
64 *      3. Digest
65 *      4. NTLM (if supported)
66 *      5. Basic
67 *
68 * This choice can be modified by setting a system property:
69 *
70 *      -Dhttp.auth.preference="scheme"
71 *
72 * which in this case, specifies that "scheme" should be used as the auth scheme when offered
73 * disregarding the default prioritisation. If scheme is not offered, or explicitly
74 * disabled, by {@code disabledSchemes}, then the default priority is used.
75 *
76 * Attention: when http.auth.preference is set as SPNEGO or Kerberos, it's actually "Negotiate
77 * with SPNEGO" or "Negotiate with Kerberos", which means the user will prefer the Negotiate
78 * scheme with GSS/SPNEGO or GSS/Kerberos mechanism.
79 *
80 * This also means that the real "Kerberos" scheme can never be set as a preference.
81 */
82
83public class AuthenticationHeader {
84
85    MessageHeader rsp; // the response to be parsed
86    HeaderParser preferred;
87    String preferred_r; // raw Strings
88    private final HttpCallerInfo hci;   // un-schemed, need check
89
90    // When set true, do not use Negotiate even if the response
91    // headers suggest so.
92    boolean dontUseNegotiate = false;
93    static String authPref=null;
94
95    public String toString() {
96        return "AuthenticationHeader: prefer " + preferred_r;
97    }
98
99    static {
100        authPref = GetPropertyAction.privilegedGetProperty("http.auth.preference");
101
102        // http.auth.preference can be set to SPNEGO or Kerberos.
103        // In fact they means "Negotiate with SPNEGO" and "Negotiate with
104        // Kerberos" separately, so here they are all translated into
105        // Negotiate. Read NegotiateAuthentication.java to see how they
106        // were used later.
107
108        if (authPref != null) {
109            authPref = authPref.toLowerCase();
110            if(authPref.equals("spnego") || authPref.equals("kerberos")) {
111                authPref = "negotiate";
112            }
113        }
114    }
115
116    String hdrname; // Name of the header to look for
117
118    /**
119     * Parses a set of authentication headers and chooses the preferred scheme
120     * that is supported for a given host.
121     */
122    public AuthenticationHeader (String hdrname, MessageHeader response,
123            HttpCallerInfo hci, boolean dontUseNegotiate) {
124        this(hdrname, response, hci, dontUseNegotiate, Collections.emptySet());
125    }
126
127    /**
128     * Parses a set of authentication headers and chooses the preferred scheme
129     * that is supported for a given host.
130     *
131     * <p> The {@code disabledSchemes} parameter is a, possibly empty, set of
132     * authentication schemes that are disabled.
133     */
134    public AuthenticationHeader(String hdrname,
135                                MessageHeader response,
136                                HttpCallerInfo hci,
137                                boolean dontUseNegotiate,
138                                Set<String> disabledSchemes) {
139        this.hci = hci;
140        this.dontUseNegotiate = dontUseNegotiate;
141        this.rsp = response;
142        this.hdrname = hdrname;
143        this.schemes = new HashMap<>();
144        parse(disabledSchemes);
145    }
146
147    public HttpCallerInfo getHttpCallerInfo() {
148        return hci;
149    }
150    /* we build up a map of scheme names mapped to SchemeMapValue objects */
151    static class SchemeMapValue {
152        SchemeMapValue (HeaderParser h, String r) {raw=r; parser=h;}
153        String raw;
154        HeaderParser parser;
155    }
156
157    HashMap<String, SchemeMapValue> schemes;
158
159    /* Iterate through each header line, and then within each line.
160     * If multiple entries exist for a particular scheme (unlikely)
161     * then the last one will be used. The
162     * preferred scheme that we support will be used.
163     */
164    private void parse(Set<String> disabledSchemes) {
165        Iterator<String> iter = rsp.multiValueIterator(hdrname);
166        while (iter.hasNext()) {
167            String raw = iter.next();
168            // HeaderParser lower cases everything, so can be used case-insensitively
169            HeaderParser hp = new HeaderParser(raw);
170            Iterator<String> keys = hp.keys();
171            int i, lastSchemeIndex;
172            for (i=0, lastSchemeIndex = -1; keys.hasNext(); i++) {
173                keys.next();
174                if (hp.findValue(i) == null) { /* found a scheme name */
175                    if (lastSchemeIndex != -1) {
176                        HeaderParser hpn = hp.subsequence (lastSchemeIndex, i);
177                        String scheme = hpn.findKey(0);
178                        if (!disabledSchemes.contains(scheme))
179                            schemes.put(scheme, new SchemeMapValue (hpn, raw));
180                    }
181                    lastSchemeIndex = i;
182                }
183            }
184            if (i > lastSchemeIndex) {
185                HeaderParser hpn = hp.subsequence (lastSchemeIndex, i);
186                String scheme = hpn.findKey(0);
187                if (!disabledSchemes.contains(scheme))
188                    schemes.put(scheme, new SchemeMapValue (hpn, raw));
189            }
190        }
191
192        /* choose the best of them, the order is
193         * negotiate -> kerberos -> digest -> ntlm -> basic
194         */
195        SchemeMapValue v = null;
196        if (authPref == null || (v=schemes.get (authPref)) == null) {
197
198            if(v == null && !dontUseNegotiate) {
199                SchemeMapValue tmp = schemes.get("negotiate");
200                if(tmp != null) {
201                    if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Negotiate"))) {
202                        tmp = null;
203                    }
204                    v = tmp;
205                }
206            }
207
208            if(v == null && !dontUseNegotiate) {
209                SchemeMapValue tmp = schemes.get("kerberos");
210                if(tmp != null) {
211                    // the Kerberos scheme is only observed in MS ISA Server. In
212                    // fact i think it's a Kerberos-mechnism-only Negotiate.
213                    // Since the Kerberos scheme is always accompanied with the
214                    // Negotiate scheme, so it seems impossible to reach this
215                    // line. Even if the user explicitly set http.auth.preference
216                    // as Kerberos, it means Negotiate with Kerberos, and the code
217                    // will still tried to use Negotiate at first.
218                    //
219                    // The only chance this line get executed is that the server
220                    // only suggest the Kerberos scheme.
221                    if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Kerberos"))) {
222                        tmp = null;
223                    }
224                    v = tmp;
225                }
226            }
227
228            if(v == null) {
229                if ((v=schemes.get ("digest")) == null) {
230                    if (!NTLMAuthenticationProxy.supported
231                        || ((v=schemes.get("ntlm"))==null)) {
232                        v = schemes.get ("basic");
233                    }
234                }
235            }
236        } else {    // authPref != null && it's found in reponses'
237            if (dontUseNegotiate && authPref.equals("negotiate")) {
238                v = null;
239            }
240        }
241
242        if (v != null) {
243            preferred = v.parser;
244            preferred_r = v.raw;
245        }
246    }
247
248    /**
249     * return a header parser containing the preferred authentication scheme (only).
250     * The preferred scheme is the strongest of the schemes proposed by the server.
251     * The returned HeaderParser will contain the relevant parameters for that scheme
252     */
253    public HeaderParser headerParser() {
254        return preferred;
255    }
256
257    /**
258     * return the name of the preferred scheme
259     */
260    public String scheme() {
261        if (preferred != null) {
262            return preferred.findKey(0);
263        } else {
264            return null;
265        }
266    }
267
268    /* return the raw header field for the preferred/chosen scheme */
269
270    public String raw () {
271        return preferred_r;
272    }
273
274    /**
275     * returns true is the header exists and contains a recognised scheme
276     */
277    public boolean isPresent () {
278        return preferred != null;
279    }
280}
281