1 /* 2 * Copyright 2005-2009 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package sun.net.www.protocol.http; 27 28 import java.util.HashMap; 29 30 import sun.net.www.HeaderParser; 31 import sun.misc.BASE64Decoder; 32 import sun.misc.BASE64Encoder; 33 import sun.util.logging.PlatformLogger; 34 35 import java.net.URL; 36 import java.io.IOException; 37 import java.net.Authenticator.RequestorType; 38 import java.lang.reflect.Constructor; 39 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE; 40 import static sun.net.www.protocol.http.AuthScheme.KERBEROS; 41 42 /** 43 * NegotiateAuthentication: 44 * 45 * @author weijun.wang@sun.com 46 * @since 1.6 47 */ 48 49 class NegotiateAuthentication extends AuthenticationInfo { 50 51 private static final long serialVersionUID = 100L; 52 53 final private HttpCallerInfo hci; 54 55 // These maps are used to manage the GSS availability for diffrent 56 // hosts. The key for both maps is the host name. 57 // <code>supported</code> is set when isSupported is checked, 58 // if it's true, a cached Negotiator is put into <code>cache</code>. 59 // the cache can be used only once, so after the first use, it's cleaned. 60 static HashMap <String, Boolean> supported = null; 61 static HashMap <String, Negotiator> cache = null; 62 63 // The HTTP Negotiate Helper 64 private Negotiator negotiator = null; 65 66 /** 67 * Constructor used for both WWW and proxy entries. 68 * @param hci a schemed object. 69 */ 70 public NegotiateAuthentication(HttpCallerInfo hci) { 71 super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, 72 hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS, 73 hci.url, 74 ""); 75 this.hci = hci; 76 } 77 78 /** 79 * @return true if this authentication supports preemptive authorization 80 */ 81 boolean supportsPreemptiveAuthorization() { 82 return false; 83 } 84 85 /** 86 * Find out if the HttpCallerInfo supports Negotiate protocol. In order to 87 * find out yes or no, an initialization of a Negotiator object against it 88 * is tried. The generated object will be cached under the name of ths 89 * hostname at a success try.<br> 90 * 91 * If this method is called for the second time on an HttpCallerInfo with 92 * the same hostname, the answer is retrieved from cache. 93 * 94 * @return true if supported 95 */ 96 synchronized public static boolean isSupported(HttpCallerInfo hci) { 97 if (supported == null) { 98 supported = new HashMap <String, Boolean>(); 99 cache = new HashMap <String, Negotiator>(); 100 } 101 String hostname = hci.host; 102 hostname = hostname.toLowerCase(); 103 if (supported.containsKey(hostname)) { 104 return supported.get(hostname); 105 } 106 107 try { 108 Negotiator neg = Negotiator.getSupported(hci); 109 supported.put(hostname, true); 110 // the only place cache.put is called. here we can make sure 111 // the object is valid and the oneToken inside is not null 112 cache.put(hostname, neg); 113 return true; 114 } catch(Exception e) { 115 supported.put(hostname, false); 116 return false; 117 } 118 } 119 120 /** 121 * @return the name of the HTTP header this authentication wants to set 122 */ 123 String getHeaderName() { 124 if (type == SERVER_AUTHENTICATION) { 125 return "Authorization"; 126 } else { 127 return "Proxy-Authorization"; 128 } 129 } 130 131 /** 132 * Not supported. Must use the setHeaders() method 133 */ 134 String getHeaderValue(URL url, String method) { 135 throw new RuntimeException ("getHeaderValue not supported"); 136 } 137 138 /** 139 * Check if the header indicates that the current auth. parameters are stale. 140 * If so, then replace the relevant field with the new value 141 * and return true. Otherwise return false. 142 * returning true means the request can be retried with the same userid/password 143 * returning false means we have to go back to the user to ask for a new 144 * username password. 145 */ 146 boolean isAuthorizationStale (String header) { 147 return false; /* should not be called for Negotiate */ 148 } 149 150 /** 151 * Set header(s) on the given connection. 152 * @param conn The connection to apply the header(s) to 153 * @param p A source of header values for this connection, not used because 154 * HeaderParser converts the fields to lower case, use raw instead 155 * @param raw The raw header field. 156 * @return true if all goes well, false if no headers were set. 157 */ 158 synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { 159 160 try { 161 String response; 162 byte[] incoming = null; 163 String[] parts = raw.split("\\s+"); 164 if (parts.length > 1) { 165 incoming = new BASE64Decoder().decodeBuffer(parts[1]); 166 } 167 response = hci.scheme + " " + new B64Encoder().encode( 168 incoming==null?firstToken():nextToken(incoming)); 169 170 conn.setAuthenticationProperty(getHeaderName(), response); 171 return true; 172 } catch (IOException e) { 173 return false; 174 } 175 } 176 177 /** 178 * return the first token. 179 * @returns the token 180 * @throws IOException if <code>Negotiator.getSupported()</code> or 181 * <code>Negotiator.firstToken()</code> failed. 182 */ 183 private byte[] firstToken() throws IOException { 184 negotiator = null; 185 if (cache != null) { 186 synchronized(cache) { 187 negotiator = cache.get(getHost()); 188 if (negotiator != null) { 189 cache.remove(getHost()); // so that it is only used once 190 } 191 } 192 } 193 if (negotiator == null) { 194 try { 195 negotiator = Negotiator.getSupported(hci); 196 } catch(Exception e) { 197 IOException ioe = new IOException("Cannot initialize Negotiator"); 198 ioe.initCause(e); 199 throw ioe; 200 } 201 } 202 203 return negotiator.firstToken(); 204 } 205 206 /** 207 * return more tokens 208 * @param token the token to be fed into <code>negotiator.nextToken()</code> 209 * @returns the token 210 * @throws IOException if <code>negotiator.nextToken()</code> throws Exception. 211 * May happen if the input token is invalid. 212 */ 213 private byte[] nextToken(byte[] token) throws IOException { 214 return negotiator.nextToken(token); 215 } 216 217 class B64Encoder extends BASE64Encoder { 218 protected int bytesPerLine () { 219 return 100000; // as big as it can be, maybe INT_MAX 220 } 221 } 222 223 // MS will send a final WWW-Authenticate even if the status is already 224 // 200 OK. The token can be fed into initSecContext() again to determine 225 // if the server can be trusted. This is not the same concept as Digest's 226 // Authentication-Info header. 227 // 228 // Currently we ignore this header. 229 230 } 231 232 /** 233 * This abstract class is a bridge to connect NegotiteAuthentication and 234 * NegotiatorImpl, so that JAAS and JGSS calls can be made 235 */ 236 abstract class Negotiator { 237 static Negotiator getSupported(HttpCallerInfo hci) 238 throws Exception { 239 240 // These lines are equivalent to 241 // return new NegotiatorImpl(hci); 242 // The current implementation will make sure NegotiatorImpl is not 243 // directly referenced when compiling, thus smooth the way of building 244 // the J2SE platform where HttpURLConnection is a bootstrap class. 245 // 246 // Makes NegotiatorImpl, and the security classes it references, a 247 // runtime dependency rather than a static one. 248 249 Class clazz; 250 Constructor c; 251 try { 252 clazz = Class.forName("sun.net.www.protocol.http.NegotiatorImpl", true, null); 253 c = clazz.getConstructor(HttpCallerInfo.class); 254 } catch (ClassNotFoundException cnfe) { 255 finest(cnfe); 256 throw cnfe; 257 } catch (ReflectiveOperationException roe) { 258 // if the class is there then something seriously wrong if 259 // the constructor is not. 260 throw new AssertionError(roe); 261 } 262 263 try { 264 return (Negotiator) (c.newInstance(hci)); 265 } catch (ReflectiveOperationException roe) { 266 finest(roe); 267 Throwable t = roe.getCause(); 268 if (t != null && t instanceof Exception) 269 finest((Exception)t); 270 throw roe; 271 } 272 } 273 274 abstract byte[] firstToken() throws IOException; 275 276 abstract byte[] nextToken(byte[] in) throws IOException; 277 278 static void finest(Exception e) { 279 PlatformLogger logger = HttpURLConnection.getHttpLogger(); 280 logger.finest("NegotiateAuthentication: " + e); 281 } 282 }