cURL / Mailing Lists / curl-library / Single Mail

curl-library

authp->picked not being cleared on failed Negotiate auth

From: Joe Mason <jmason_at_rim.com>
Date: Tue, 17 Jul 2012 18:47:51 +0000

I'm trying to set up an app to connect to a server that supports GSS-Negotiate and NTLM auth. If the client-side auth setup is incorrect I want to fall back to using NTLM.

So this is pretty simple:

void doRequest(const char* url, const char* username, const char* password, bool allowNegotiate)
{
    int numErrors = 0;

    CURL* handle = curl_easy_init();
    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_HTTPGET, true);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback); // increments numErrors on every 401
    curl_easy_setopt(handle, CURLOPT_HEADERDATA, &numErrors);
    curl_easy_setopt(handle, CURLOPT_USERNAME, username);
    curl_easy_setopt(handle, CURLOPT_PASSWORD, password);
    long allowedAuthSchemes = CURLAUTH_NTLM;
    if (allowNegotiate) {
        allowedAuthSchemes |= CURLAUTH_GSSNEGOTIATE;
    }
    curl_easy_setopt(handle, CURLOPT_HTTPAUTH, allowedAuthSchemes);
    curl_easy_perform(handle);
    curl_easy_cleanup(handle);

    // expect one 401 to be sent during auth setup; anything more than that means auth failed
    if (numErrors > 1 && allowNegotiate) {
        // try again without Negotiate
        doRequest(url, username, password, false);
    }
}

We set up a request allowing CURLAUTH_NTLM | CURLAUTH_GSSNEGOTIATE and, if it receives too many 401 errors, send it again with just CURLAUTH_NTLM.

To both requests, the server will send back:

WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM

The problem comes in Curl_http_input_auth:

    if(checkprefix("GSS-Negotiate", start) ||
       checkprefix("Negotiate", start)) {
      int neg;
      *availp |= CURLAUTH_GSSNEGOTIATE;
      authp->avail |= CURLAUTH_GSSNEGOTIATE;

      if(authp->picked == CURLAUTH_GSSNEGOTIATE) {
        if(data->state.negotiate.state == GSS_AUTHSENT) {
          /* if we sent GSS authentication in the outgoing request and we get
             this back, we're in trouble */
          infof(data, "Authentication problem. Ignoring this.\n");
          data->state.authproblem = TRUE;
        }
        else {
          neg = Curl_input_negotiate(conn, (bool)(httpcode == 407), start);
          if(neg == 0) {
            DEBUGASSERT(!data->req.newurl);
            data->req.newurl = strdup(data->change.url);
            if(!data->req.newurl)
              return CURLE_OUT_OF_MEMORY;
            data->state.authproblem = FALSE;
            /* we received GSS auth info and we dealt with it fine */
            data->state.negotiate.state = GSS_AUTHRECV;
          }
          else
            data->state.authproblem = TRUE;
        }
      }
    }

The first time through, it reads the Negotiate header, updates "avail", and then does nothing since "picked" is 0. After reading all the headers, it will set set "picked" to CURLAUTH_GSSNEGOTIATE because that's the highest priority, and attempt to output a negotiate header. (Which will fail because, for this test, GSS auth is set up wrong.)

The second time through, it reads the Negotiate header, updates "avail", and then goes into the "if" statement because "picked" is still CURLAUTH_NEGOTIATE from the first pass. Even though we're using a different handle! It then calls Curl_input_negotiate, which will call some gss functions and return an error code when they fail. It then sets authproblem to TRUE, which means that even though it reads the NTLM header it will not continue to send the NTLM auth as we want. It seems to me that it should be ignoring this Negotiate header entirely, since in this second pass CURLAUTH_GSSNEGOTIATE isn't even included in CURLOPT_HTTPAUTH.

(I've also tried adding CURLOPT_FRESH_CONNECT when allowNegotiate is false, to force it to use a new connection, but "picked" is still set.)

So it looks like "picked" should be cleared at some point when Negotiate auth fails, so that it doesn't keep trying to use it for further requests. But I'm not sure where this is should be done.

Another option would be to change that if statement to "if(authp->picked == CURLAUTH_GSSNEGOTIATE && authp->want & CURLAUTH_GSSNEGOTIATE)", so that if we don't want negotiate it just ignores the value of picked here, but I think that would just be hiding the problem.

Joe

---------------------------------------------------------------------
This transmission (including any attachments) may contain confidential information, privileged material (including material protected by the solicitor-client or other applicable privileges), or constitute non-public information. Any use of this information by anyone other than the intended recipient is prohibited. If you have received this transmission in error, please immediately reply to the sender and delete this information from your system. Use, dissemination, distribution, or reproduction of this transmission by unintended recipients is not authorized and may be unlawful.

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html
Received on 2012-07-17