blob: eee2579d64f81c9f157f77656808d09bd77d92b9 [file] [log] [blame]
/*
* Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.www.protocol.http;
import sun.net.www.*;
import java.util.Iterator;
import java.util.HashMap;
/**
* This class is used to parse the information in WWW-Authenticate: and Proxy-Authenticate:
* headers. It searches among multiple header lines and within each header line
* for the best currently supported scheme. It can also return a HeaderParser
* containing the challenge data for that particular scheme.
*
* Some examples:
*
* WWW-Authenticate: Basic realm="foo" Digest realm="bar" NTLM
* Note the realm parameter must be associated with the particular scheme.
*
* or
*
* WWW-Authenticate: Basic realm="foo"
* WWW-Authenticate: Digest realm="foo",qop="auth",nonce="thisisanunlikelynonce"
* WWW-Authenticate: NTLM
*
* or
*
* WWW-Authenticate: Basic realm="foo"
* WWW-Authenticate: NTLM ASKAJK9893289889QWQIOIONMNMN
*
* The last example shows how NTLM breaks the rules of rfc2617 for the structure of
* the authentication header. This is the reason why the raw header field is used for ntlm.
*
* At present, the class chooses schemes in following order :
* 1. Negotiate (if supported)
* 2. Kerberos (if supported)
* 3. Digest
* 4. NTLM (if supported)
* 5. Basic
*
* This choice can be modified by setting a system property:
*
* -Dhttp.auth.preference="scheme"
*
* which in this case, specifies that "scheme" should be used as the auth scheme when offered
* disregarding the default prioritisation. If scheme is not offered then the default priority
* is used.
*
* Attention: when http.auth.preference is set as SPNEGO or Kerberos, it's actually "Negotiate
* with SPNEGO" or "Negotiate with Kerberos", which means the user will prefer the Negotiate
* scheme with GSS/SPNEGO or GSS/Kerberos mechanism.
*
* This also means that the real "Kerberos" scheme can never be set as a preference.
*/
public class AuthenticationHeader {
MessageHeader rsp; // the response to be parsed
HeaderParser preferred;
String preferred_r; // raw Strings
private final HttpCallerInfo hci; // un-schemed, need check
// When set true, do not use Negotiate even if the response
// headers suggest so.
boolean dontUseNegotiate = false;
static String authPref=null;
public String toString() {
return "AuthenticationHeader: prefer " + preferred_r;
}
static {
authPref = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("http.auth.preference"));
// http.auth.preference can be set to SPNEGO or Kerberos.
// In fact they means "Negotiate with SPNEGO" and "Negotiate with
// Kerberos" separately, so here they are all translated into
// Negotiate. Read NegotiateAuthentication.java to see how they
// were used later.
if (authPref != null) {
authPref = authPref.toLowerCase();
if(authPref.equals("spnego") || authPref.equals("kerberos")) {
authPref = "negotiate";
}
}
}
String hdrname; // Name of the header to look for
/**
* parse a set of authentication headers and choose the preferred scheme
* that we support for a given host
*/
public AuthenticationHeader (String hdrname, MessageHeader response,
HttpCallerInfo hci, boolean dontUseNegotiate) {
this.hci = hci;
this.dontUseNegotiate = dontUseNegotiate;
rsp = response;
this.hdrname = hdrname;
schemes = new HashMap();
parse();
}
public HttpCallerInfo getHttpCallerInfo() {
return hci;
}
/* we build up a map of scheme names mapped to SchemeMapValue objects */
static class SchemeMapValue {
SchemeMapValue (HeaderParser h, String r) {raw=r; parser=h;}
String raw;
HeaderParser parser;
}
HashMap schemes;
/* Iterate through each header line, and then within each line.
* If multiple entries exist for a particular scheme (unlikely)
* then the last one will be used. The
* preferred scheme that we support will be used.
*/
private void parse () {
Iterator iter = rsp.multiValueIterator (hdrname);
while (iter.hasNext()) {
String raw = (String)iter.next();
HeaderParser hp = new HeaderParser (raw);
Iterator keys = hp.keys();
int i, lastSchemeIndex;
for (i=0, lastSchemeIndex = -1; keys.hasNext(); i++) {
keys.next();
if (hp.findValue(i) == null) { /* found a scheme name */
if (lastSchemeIndex != -1) {
HeaderParser hpn = hp.subsequence (lastSchemeIndex, i);
String scheme = hpn.findKey(0);
schemes.put (scheme, new SchemeMapValue (hpn, raw));
}
lastSchemeIndex = i;
}
}
if (i > lastSchemeIndex) {
HeaderParser hpn = hp.subsequence (lastSchemeIndex, i);
String scheme = hpn.findKey(0);
schemes.put (scheme, new SchemeMapValue (hpn, raw));
}
}
/* choose the best of them, the order is
* negotiate -> kerberos -> digest -> ntlm -> basic
*/
SchemeMapValue v = null;
if (authPref == null || (v=(SchemeMapValue)schemes.get (authPref)) == null) {
if(v == null && !dontUseNegotiate) {
SchemeMapValue tmp = (SchemeMapValue)schemes.get("negotiate");
if(tmp != null) {
if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Negotiate"))) {
tmp = null;
}
v = tmp;
}
}
if(v == null && !dontUseNegotiate) {
SchemeMapValue tmp = (SchemeMapValue)schemes.get("kerberos");
if(tmp != null) {
// the Kerberos scheme is only observed in MS ISA Server. In
// fact i think it's a Kerberos-mechnism-only Negotiate.
// Since the Kerberos scheme is always accompanied with the
// Negotiate scheme, so it seems impossible to reach this
// line. Even if the user explicitly set http.auth.preference
// as Kerberos, it means Negotiate with Kerberos, and the code
// will still tried to use Negotiate at first.
//
// The only chance this line get executed is that the server
// only suggest the Kerberos scheme.
if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Kerberos"))) {
tmp = null;
}
v = tmp;
}
}
if(v == null) {
if ((v=(SchemeMapValue)schemes.get ("digest")) == null) {
if (((v=(SchemeMapValue)schemes.get("ntlm"))==null)) {
v = (SchemeMapValue)schemes.get ("basic");
}
}
}
} else { // authPref != null && it's found in reponses'
if (dontUseNegotiate && authPref.equals("negotiate")) {
v = null;
}
}
if (v != null) {
preferred = v.parser;
preferred_r = v.raw;
}
}
/**
* return a header parser containing the preferred authentication scheme (only).
* The preferred scheme is the strongest of the schemes proposed by the server.
* The returned HeaderParser will contain the relevant parameters for that scheme
*/
public HeaderParser headerParser() {
return preferred;
}
/**
* return the name of the preferred scheme
*/
public String scheme() {
if (preferred != null) {
return preferred.findKey(0);
} else {
return null;
}
}
/* return the raw header field for the preferred/chosen scheme */
public String raw () {
return preferred_r;
}
/**
* returns true is the header exists and contains a recognised scheme
*/
public boolean isPresent () {
return preferred != null;
}
}