Home | History | Annotate | Download | only in http
      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