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