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