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 }