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