1 /*
   2  * Copyright (c) 2005, 2014, 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 java.net.URL;
  29 import java.io.IOException;
  30 import java.net.Authenticator.RequestorType;
  31 import java.util.Base64;
  32 import java.util.HashMap;
  33 import sun.net.www.HeaderParser;
  34 import sun.util.logging.PlatformLogger;
  35 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
  36 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
  37 
  38 /**
  39  * NegotiateAuthentication:
  40  *
  41  * @author weijun.wang@sun.com
  42  * @since 1.6
  43  */
  44 
  45 class NegotiateAuthentication extends AuthenticationInfo {
  46 
  47     private static final long serialVersionUID = 100L;
  48     private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
  49 
  50     final private HttpCallerInfo hci;
  51 
  52     // These maps are used to manage the GSS availability for diffrent
  53     // hosts. The key for both maps is the host name.
  54     // <code>supported</code> is set when isSupported is checked,
  55     // if it's true, a cached Negotiator is put into <code>cache</code>.
  56     // the cache can be used only once, so after the first use, it's cleaned.
  57     static HashMap <String, Boolean> supported = null;
  58     static HashMap <String, Negotiator> cache = null;
  59 
  60     // The HTTP Negotiate Helper
  61     private Negotiator negotiator = null;
  62 
  63    /**
  64     * Constructor used for both WWW and proxy entries.
  65     * @param hci a schemed object.
  66     */
  67     public NegotiateAuthentication(HttpCallerInfo hci) {
  68         super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
  69               hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS,
  70               hci.url,
  71               "");
  72         this.hci = hci;
  73     }
  74 
  75     /**
  76      * @return true if this authentication supports preemptive authorization
  77      */
  78     @Override
  79     public boolean supportsPreemptiveAuthorization() {
  80         return false;
  81     }
  82 
  83     /**
  84      * Find out if the HttpCallerInfo supports Negotiate protocol.
  85      * @return true if supported
  86      */
  87     public static boolean isSupported(HttpCallerInfo hci) {
  88         ClassLoader loader = null;
  89         try {
  90             loader = Thread.currentThread().getContextClassLoader();
  91         } catch (SecurityException se) {
  92             if (logger.isLoggable(PlatformLogger.Level.FINER)) {
  93                 logger.finer("NegotiateAuthentication: " +
  94                     "Attempt to get the context class loader failed - " + se);
  95             }
  96         }
  97 
  98         if (loader != null) {
  99             // Lock on the class loader instance to avoid the deadlock engaging
 100             // the lock in "ClassLoader.loadClass(String, boolean)" method.
 101             synchronized (loader) {
 102                 return isSupportedImpl(hci);
 103             }
 104         }
 105         return isSupportedImpl(hci);
 106     }
 107 
 108     /**
 109      * Find out if the HttpCallerInfo supports Negotiate protocol. In order to
 110      * find out yes or no, an initialization of a Negotiator object against it
 111      * is tried. The generated object will be cached under the name of ths
 112      * hostname at a success try.<br>
 113      *
 114      * If this method is called for the second time on an HttpCallerInfo with
 115      * the same hostname, the answer is retrieved from cache.
 116      *
 117      * @return true if supported
 118      */
 119     private static synchronized boolean isSupportedImpl(HttpCallerInfo hci) {
 120         if (supported == null) {
 121             supported = new HashMap <String, Boolean>();
 122             cache = new HashMap <String, Negotiator>();
 123         }
 124         String hostname = hci.host;
 125         hostname = hostname.toLowerCase();
 126         if (supported.containsKey(hostname)) {
 127             return supported.get(hostname);
 128         }
 129 
 130         Negotiator neg = Negotiator.getNegotiator(hci);
 131         if (neg != null) {
 132             supported.put(hostname, true);
 133             // the only place cache.put is called. here we can make sure
 134             // the object is valid and the oneToken inside is not null
 135             cache.put(hostname, neg);
 136             return true;
 137         } else {
 138             supported.put(hostname, false);
 139             return false;
 140         }
 141     }
 142 
 143     /**
 144      * Not supported. Must use the setHeaders() method
 145      */
 146     @Override
 147     public String getHeaderValue(URL url, String method) {
 148         throw new RuntimeException ("getHeaderValue not supported");
 149     }
 150 
 151     /**
 152      * Check if the header indicates that the current auth. parameters are stale.
 153      * If so, then replace the relevant field with the new value
 154      * and return true. Otherwise return false.
 155      * returning true means the request can be retried with the same userid/password
 156      * returning false means we have to go back to the user to ask for a new
 157      * username password.
 158      */
 159     @Override
 160     public boolean isAuthorizationStale (String header) {
 161         return false; /* should not be called for Negotiate */
 162     }
 163 
 164     /**
 165      * Set header(s) on the given connection.
 166      * @param conn The connection to apply the header(s) to
 167      * @param p A source of header values for this connection, not used because
 168      *          HeaderParser converts the fields to lower case, use raw instead
 169      * @param raw The raw header field.
 170      * @return true if all goes well, false if no headers were set.
 171      */
 172     @Override
 173     public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
 174 
 175         try {
 176             String response;
 177             byte[] incoming = null;
 178             String[] parts = raw.split("\\s+");
 179             if (parts.length > 1) {
 180                 incoming = Base64.getDecoder().decode(parts[1]);
 181             }
 182             response = hci.scheme + " " + Base64.getEncoder().encodeToString(
 183                         incoming==null?firstToken():nextToken(incoming));
 184 
 185             conn.setAuthenticationProperty(getHeaderName(), response);
 186             return true;
 187         } catch (IOException e) {
 188             return false;
 189         }
 190     }
 191 
 192     /**
 193      * return the first token.
 194      * @return the token
 195      * @throws IOException if <code>Negotiator.getNegotiator()</code> or
 196      *                     <code>Negotiator.firstToken()</code> failed.
 197      */
 198     private byte[] firstToken() throws IOException {
 199         negotiator = null;
 200         if (cache != null) {
 201             synchronized(cache) {
 202                 negotiator = cache.get(getHost());
 203                 if (negotiator != null) {
 204                     cache.remove(getHost()); // so that it is only used once
 205                 }
 206             }
 207         }
 208         if (negotiator == null) {
 209             negotiator = Negotiator.getNegotiator(hci);
 210             if (negotiator == null) {
 211                 IOException ioe = new IOException("Cannot initialize Negotiator");
 212                 throw ioe;
 213             }
 214         }
 215 
 216         return negotiator.firstToken();
 217     }
 218 
 219     /**
 220      * return more tokens
 221      * @param token the token to be fed into <code>negotiator.nextToken()</code>
 222      * @return the token
 223      * @throws IOException if <code>negotiator.nextToken()</code> throws Exception.
 224      *  May happen if the input token is invalid.
 225      */
 226     private byte[] nextToken(byte[] token) throws IOException {
 227         return negotiator.nextToken(token);
 228     }
 229 
 230     // MS will send a final WWW-Authenticate even if the status is already
 231     // 200 OK. The token can be fed into initSecContext() again to determine
 232     // if the server can be trusted. This is not the same concept as Digest's
 233     // Authentication-Info header.
 234     //
 235     // Currently we ignore this header.
 236 
 237 }