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