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