Home | History | Annotate | Download | only in http
      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 
     17 namespace net {
     18 
     19 HttpAuthHandlerNegotiate::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 
     30 HttpAuthHandlerNegotiate::Factory::~Factory() {
     31 }
     32 
     33 void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
     34     HostResolver* resolver) {
     35   resolver_ = resolver;
     36 }
     37 
     38 int 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 
     81 HttpAuthHandlerNegotiate::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 
    108 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
    109 }
    110 
    111 std::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 
    160 HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
    161     HttpAuth::ChallengeTokenizer* challenge) {
    162   return auth_system_.ParseChallenge(challenge);
    163 }
    164 
    165 // Require identity on first pass instead of second.
    166 bool HttpAuthHandlerNegotiate::NeedsIdentity() {
    167   return auth_system_.NeedsIdentity();
    168 }
    169 
    170 bool 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
    180 bool 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 
    203 int 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 
    233 void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
    234   int rv = DoLoop(result);
    235   if (rv != ERR_IO_PENDING)
    236     DoCallback(rv);
    237 }
    238 
    239 void 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 
    247 int 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 
    279 int 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 
    293 int 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 
    309 int 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 
    317 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
    318   DCHECK_NE(ERR_IO_PENDING, rv);
    319   auth_token_ = NULL;
    320   return rv;
    321 }
    322 
    323 bool 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