1 /* 2 * Copyright (c) 2002, 2009, 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 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 iter = rsp.multiValueIterator (hdrname); 148 while (iter.hasNext()) { 149 String raw = (String)iter.next(); 150 HeaderParser hp = new HeaderParser (raw); 151 Iterator 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=(SchemeMapValue)schemes.get (authPref)) == null) { 176 177 if(v == null && !dontUseNegotiate) { 178 SchemeMapValue tmp = (SchemeMapValue)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 = (SchemeMapValue)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=(SchemeMapValue)schemes.get ("digest")) == null) { 209 if (((v=(SchemeMapValue)schemes.get("ntlm"))==null)) { 210 v = (SchemeMapValue)schemes.get ("basic"); 211 } 212 } 213 } 214 } else { // authPref != null && it's found in reponses' 215 if (dontUseNegotiate && authPref.equals("negotiate")) { 216 v = null; 217 } 218 } 219 220 if (v != null) { 221 preferred = v.parser; 222 preferred_r = v.raw; 223 } 224 } 225 226 /** 227 * return a header parser containing the preferred authentication scheme (only). 228 * The preferred scheme is the strongest of the schemes proposed by the server. 229 * The returned HeaderParser will contain the relevant parameters for that scheme 230 */ 231 public HeaderParser headerParser() { 232 return preferred; 233 } 234 235 /** 236 * return the name of the preferred scheme 237 */ 238 public String scheme() { 239 if (preferred != null) { 240 return preferred.findKey(0); 241 } else { 242 return null; 243 } 244 } 245 246 /* return the raw header field for the preferred/chosen scheme */ 247 248 public String raw () { 249 return preferred_r; 250 } 251 252 /** 253 * returns true is the header exists and contains a recognised scheme 254 */ 255 public boolean isPresent () { 256 return preferred != null; 257 } 258 } 259