1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/http/http_auth_handler_negotiate.h" 6 7#include "base/logging.h" 8#include "base/string_util.h" 9#include "base/stringprintf.h" 10#include "base/utf_string_conversions.h" 11#include "net/base/address_family.h" 12#include "net/base/host_resolver.h" 13#include "net/base/net_errors.h" 14#include "net/http/http_auth_filter.h" 15#include "net/http/url_security_manager.h" 16 17namespace net { 18 19HttpAuthHandlerNegotiate::Factory::Factory() 20 : disable_cname_lookup_(false), 21 use_port_(false), 22#if defined(OS_WIN) 23 max_token_length_(0), 24 first_creation_(true), 25 is_unsupported_(false), 26#endif 27 auth_library_(NULL) { 28} 29 30HttpAuthHandlerNegotiate::Factory::~Factory() { 31} 32 33void HttpAuthHandlerNegotiate::Factory::set_host_resolver( 34 HostResolver* resolver) { 35 resolver_ = resolver; 36} 37 38int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler( 39 HttpAuth::ChallengeTokenizer* challenge, 40 HttpAuth::Target target, 41 const GURL& origin, 42 CreateReason reason, 43 int digest_nonce_count, 44 const BoundNetLog& net_log, 45 scoped_ptr<HttpAuthHandler>* handler) { 46#if defined(OS_WIN) 47 if (is_unsupported_ || reason == CREATE_PREEMPTIVE) 48 return ERR_UNSUPPORTED_AUTH_SCHEME; 49 if (max_token_length_ == 0) { 50 int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME, 51 &max_token_length_); 52 if (rv == ERR_UNSUPPORTED_AUTH_SCHEME) 53 is_unsupported_ = true; 54 if (rv != OK) 55 return rv; 56 } 57 // TODO(cbentzel): Move towards model of parsing in the factory 58 // method and only constructing when valid. 59 scoped_ptr<HttpAuthHandler> tmp_handler( 60 new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_, 61 url_security_manager(), resolver_, 62 disable_cname_lookup_, use_port_)); 63 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) 64 return ERR_INVALID_RESPONSE; 65 handler->swap(tmp_handler); 66 return OK; 67#elif defined(OS_POSIX) 68 // TODO(ahendrickson): Move towards model of parsing in the factory 69 // method and only constructing when valid. 70 scoped_ptr<HttpAuthHandler> tmp_handler( 71 new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(), 72 resolver_, disable_cname_lookup_, 73 use_port_)); 74 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) 75 return ERR_INVALID_RESPONSE; 76 handler->swap(tmp_handler); 77 return OK; 78#endif 79} 80 81HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate( 82 AuthLibrary* auth_library, 83#if defined(OS_WIN) 84 ULONG max_token_length, 85#endif 86 URLSecurityManager* url_security_manager, 87 HostResolver* resolver, 88 bool disable_cname_lookup, 89 bool use_port) 90#if defined(OS_WIN) 91 : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length), 92#elif defined(OS_POSIX) 93 : auth_system_(auth_library, "Negotiate", CHROME_GSS_KRB5_MECH_OID_DESC), 94#endif 95 disable_cname_lookup_(disable_cname_lookup), 96 use_port_(use_port), 97 ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_( 98 this, &HttpAuthHandlerNegotiate::OnIOComplete)), 99 resolver_(resolver), 100 already_called_(false), 101 has_username_and_password_(false), 102 user_callback_(NULL), 103 auth_token_(NULL), 104 next_state_(STATE_NONE), 105 url_security_manager_(url_security_manager) { 106} 107 108HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() { 109} 110 111std::wstring HttpAuthHandlerNegotiate::CreateSPN( 112 const AddressList& address_list, const GURL& origin) { 113 // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI, 114 // and in the form HTTP@<host>:<port> through GSSAPI 115 // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx 116 // 117 // However, reality differs from the specification. A good description of 118 // the problems can be found here: 119 // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx 120 // 121 // Typically the <host> portion should be the canonical FQDN for the service. 122 // If this could not be resolved, the original hostname in the URL will be 123 // attempted instead. However, some intranets register SPNs using aliases 124 // for the same canonical DNS name to allow multiple web services to reside 125 // on the same host machine without requiring different ports. IE6 and IE7 126 // have hotpatches that allow the default behavior to be overridden. 127 // http://support.microsoft.com/kb/911149 128 // http://support.microsoft.com/kb/938305 129 // 130 // According to the spec, the <port> option should be included if it is a 131 // non-standard port (i.e. not 80 or 443 in the HTTP case). However, 132 // historically browsers have not included the port, even on non-standard 133 // ports. IE6 required a hotpatch and a registry setting to enable 134 // including non-standard ports, and IE7 and IE8 also require the same 135 // registry setting, but no hotpatch. Firefox does not appear to have an 136 // option to include non-standard ports as of 3.6. 137 // http://support.microsoft.com/kb/908209 138 // 139 // Without any command-line flags, Chrome matches the behavior of Firefox 140 // and IE. Users can override the behavior so aliases are allowed and 141 // non-standard ports are included. 142 int port = origin.EffectiveIntPort(); 143 std::string server; 144 if (!address_list.GetCanonicalName(&server)) 145 server = origin.host(); 146#if defined(OS_WIN) 147 static const char kSpnSeparator = '/'; 148#elif defined(OS_POSIX) 149 static const char kSpnSeparator = '@'; 150#endif 151 if (port != 80 && port != 443 && use_port_) { 152 return ASCIIToWide(base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, 153 server.c_str(), port)); 154 } else { 155 return ASCIIToWide(base::StringPrintf("HTTP%c%s", kSpnSeparator, 156 server.c_str())); 157 } 158} 159 160HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge( 161 HttpAuth::ChallengeTokenizer* challenge) { 162 return auth_system_.ParseChallenge(challenge); 163} 164 165// Require identity on first pass instead of second. 166bool HttpAuthHandlerNegotiate::NeedsIdentity() { 167 return auth_system_.NeedsIdentity(); 168} 169 170bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() { 171 if (target_ == HttpAuth::AUTH_PROXY) 172 return true; 173 if (!url_security_manager_) 174 return false; 175 return url_security_manager_->CanUseDefaultCredentials(origin_); 176} 177 178// The Negotiate challenge header looks like: 179// WWW-Authenticate: NEGOTIATE auth-data 180bool HttpAuthHandlerNegotiate::Init(HttpAuth::ChallengeTokenizer* challenge) { 181#if defined(OS_POSIX) 182 if (!auth_system_.Init()) { 183 VLOG(1) << "can't initialize GSSAPI library"; 184 return false; 185 } 186 // GSSAPI does not provide a way to enter username/password to 187 // obtain a TGT. If the default credentials are not allowed for 188 // a particular site (based on whitelist), fall back to a 189 // different scheme. 190 if (!AllowsDefaultCredentials()) 191 return false; 192#endif 193 if (CanDelegate()) 194 auth_system_.Delegate(); 195 auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE; 196 score_ = 4; 197 properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED; 198 HttpAuth::AuthorizationResult auth_result = 199 auth_system_.ParseChallenge(challenge); 200 return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT); 201} 202 203int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl( 204 const string16* username, 205 const string16* password, 206 const HttpRequestInfo* request, 207 CompletionCallback* callback, 208 std::string* auth_token) { 209 DCHECK(user_callback_ == NULL); 210 DCHECK((username == NULL) == (password == NULL)); 211 DCHECK(auth_token_ == NULL); 212 auth_token_ = auth_token; 213 if (already_called_) { 214 DCHECK((!has_username_and_password_ && username == NULL) || 215 (has_username_and_password_ && *username == username_ && 216 *password == password_)); 217 next_state_ = STATE_GENERATE_AUTH_TOKEN; 218 } else { 219 already_called_ = true; 220 if (username) { 221 has_username_and_password_ = true; 222 username_ = *username; 223 password_ = *password; 224 } 225 next_state_ = STATE_RESOLVE_CANONICAL_NAME; 226 } 227 int rv = DoLoop(OK); 228 if (rv == ERR_IO_PENDING) 229 user_callback_ = callback; 230 return rv; 231} 232 233void HttpAuthHandlerNegotiate::OnIOComplete(int result) { 234 int rv = DoLoop(result); 235 if (rv != ERR_IO_PENDING) 236 DoCallback(rv); 237} 238 239void HttpAuthHandlerNegotiate::DoCallback(int rv) { 240 DCHECK(rv != ERR_IO_PENDING); 241 DCHECK(user_callback_); 242 CompletionCallback* callback = user_callback_; 243 user_callback_ = NULL; 244 callback->Run(rv); 245} 246 247int HttpAuthHandlerNegotiate::DoLoop(int result) { 248 DCHECK(next_state_ != STATE_NONE); 249 250 int rv = result; 251 do { 252 State state = next_state_; 253 next_state_ = STATE_NONE; 254 switch (state) { 255 case STATE_RESOLVE_CANONICAL_NAME: 256 DCHECK_EQ(OK, rv); 257 rv = DoResolveCanonicalName(); 258 break; 259 case STATE_RESOLVE_CANONICAL_NAME_COMPLETE: 260 rv = DoResolveCanonicalNameComplete(rv); 261 break; 262 case STATE_GENERATE_AUTH_TOKEN: 263 DCHECK_EQ(OK, rv); 264 rv = DoGenerateAuthToken(); 265 break; 266 case STATE_GENERATE_AUTH_TOKEN_COMPLETE: 267 rv = DoGenerateAuthTokenComplete(rv); 268 break; 269 default: 270 NOTREACHED() << "bad state"; 271 rv = ERR_FAILED; 272 break; 273 } 274 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); 275 276 return rv; 277} 278 279int HttpAuthHandlerNegotiate::DoResolveCanonicalName() { 280 next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE; 281 if (disable_cname_lookup_ || !resolver_) 282 return OK; 283 284 // TODO(cbentzel): Add reverse DNS lookup for numeric addresses. 285 DCHECK(!single_resolve_.get()); 286 HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0)); 287 info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME); 288 single_resolve_.reset(new SingleRequestHostResolver(resolver_)); 289 return single_resolve_->Resolve(info, &address_list_, &io_callback_, 290 net_log_); 291} 292 293int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) { 294 DCHECK_NE(ERR_IO_PENDING, rv); 295 if (rv != OK) { 296 // Even in the error case, try to use origin_.host instead of 297 // passing the failure on to the caller. 298 VLOG(1) << "Problem finding canonical name for SPN for host " 299 << origin_.host() << ": " << ErrorToString(rv); 300 rv = OK; 301 } 302 303 next_state_ = STATE_GENERATE_AUTH_TOKEN; 304 spn_ = CreateSPN(address_list_, origin_); 305 address_list_.Reset(); 306 return rv; 307} 308 309int HttpAuthHandlerNegotiate::DoGenerateAuthToken() { 310 next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; 311 string16* username = has_username_and_password_ ? &username_ : NULL; 312 string16* password = has_username_and_password_ ? &password_ : NULL; 313 // TODO(cbentzel): This should possibly be done async. 314 return auth_system_.GenerateAuthToken(username, password, spn_, auth_token_); 315} 316 317int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) { 318 DCHECK_NE(ERR_IO_PENDING, rv); 319 auth_token_ = NULL; 320 return rv; 321} 322 323bool HttpAuthHandlerNegotiate::CanDelegate() const { 324 // TODO(cbentzel): Should delegation be allowed on proxies? 325 if (target_ == HttpAuth::AUTH_PROXY) 326 return false; 327 if (!url_security_manager_) 328 return false; 329 return url_security_manager_->CanDelegate(origin_); 330} 331 332} // namespace net 333