Unverified Commit 6c603553 authored by Dominik Hölzl's avatar Dominik Hölzl Committed by Daniel Stenberg
Browse files

Negotiate: fix for HTTP POST with Negotiate

* Adjusted unit tests 2056, 2057
* do not generally close connections with CURLAUTH_NEGOTIATE after every request
* moved negotiatedata from UrlState to connectdata
* Added stream rewind logic for CURLAUTH_NEGOTIATE
* introduced negotiatedata::GSS_AUTHDONE and negotiatedata::GSS_AUTHSUCC
* Consider authproblem state for CURLAUTH_NEGOTIATE
* Consider reuse_forbid for CURLAUTH_NEGOTIATE
* moved and adjusted negotiate authentication state handling from
  output_auth_headers into Curl_output_negotiate
* Curl_output_negotiate: ensure auth done is always set
* Curl_output_negotiate: Set auth done also if result code is
  GSS_S_CONTINUE_NEEDED/SEC_I_CONTINUE_NEEDED as this result code may
  also indicate the last challenge request (only works with disabled
  Expect: 100-continue and CURLOPT_KEEP_SENDING_ON_ERROR -> 1)
* Consider "Persistent-Auth" header, detect if not present;
  Reset/Cleanup negotiate after authentication if no persistent
  authentication
* apply changes introduced with #2546 for negotiate rewind logic

Fixes #1261
Closes #1975
parent dd8a19f8
Loading
Loading
Loading
Loading
+76 −40
Original line number Diff line number Diff line
@@ -481,8 +481,36 @@ static CURLcode http_perhapsrewind(struct connectdata *conn)
            (curl_off_t)(expectsend - bytessent));
    }
#endif
#if defined(USE_SPNEGO)
    /* There is still data left to send */
    if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) ||
       (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) {
      if(((expectsend - bytessent) < 2000) ||
         (conn->negotiate.state != GSS_AUTHNONE) ||
         (conn->proxyneg.state != GSS_AUTHNONE)) {
        /* The NEGOTIATE-negotiation has started *OR*
        there is just a little (<2K) data left to send, keep on sending. */

        /* rewind data when completely done sending! */
        if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) {
          conn->bits.rewindaftersend = TRUE;
          infof(data, "Rewind stream after send\n");
        }

        return CURLE_OK;
      }

    /* This is not NTLM or many bytes left to send: close */
      if(conn->bits.close)
        /* this is already marked to get closed */
        return CURLE_OK;

      infof(data, "NEGOTIATE send, close instead of sending %"
        CURL_FORMAT_CURL_OFF_T " bytes\n",
        (curl_off_t)(expectsend - bytessent));
    }
#endif

    /* This is not NEGOTIATE/NTLM or many bytes left to send: close */
    streamclose(conn, "Mid-auth HTTP and much data left to send");
    data->req.size = 0; /* don't download any more than 0 bytes */

@@ -600,10 +628,6 @@ output_auth_headers(struct connectdata *conn,
#if !defined(CURL_DISABLE_VERBOSE_STRINGS) || defined(USE_SPNEGO)
  struct Curl_easy *data = conn->data;
#endif
#ifdef USE_SPNEGO
  struct negotiatedata *negdata = proxy ?
    &data->state.proxyneg : &data->state.negotiate;
#endif

#ifdef CURL_DISABLE_CRYPTO_AUTH
  (void)request;
@@ -611,15 +635,11 @@ output_auth_headers(struct connectdata *conn,
#endif

#ifdef USE_SPNEGO
  negdata->state = GSS_AUTHNONE;
  if((authstatus->picked == CURLAUTH_NEGOTIATE) &&
     negdata->context && !GSS_ERROR(negdata->status)) {
  if((authstatus->picked == CURLAUTH_NEGOTIATE)) {
    auth = "Negotiate";
    result = Curl_output_negotiate(conn, proxy);
    if(result)
      return result;
    authstatus->done = TRUE;
    negdata->state = GSS_AUTHSENT;
  }
  else
#endif
@@ -796,7 +816,7 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy,

#ifdef USE_SPNEGO
  struct negotiatedata *negdata = proxy?
    &data->state.proxyneg:&data->state.negotiate;
    &conn->proxyneg:&conn->negotiate;
#endif
  unsigned long *availp;
  struct auth *authp;
@@ -835,8 +855,6 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy,
        authp->avail |= CURLAUTH_NEGOTIATE;

        if(authp->picked == CURLAUTH_NEGOTIATE) {
          if(negdata->state == GSS_AUTHSENT ||
             negdata->state == GSS_AUTHNONE) {
          CURLcode result = Curl_input_negotiate(conn, proxy, auth);
          if(!result) {
            DEBUGASSERT(!data->req.newurl);
@@ -852,7 +870,6 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy,
        }
      }
    }
    }
    else
#endif
#ifdef USE_NTLM
@@ -1555,20 +1572,6 @@ CURLcode Curl_http_done(struct connectdata *conn,

  Curl_unencode_cleanup(conn);

#ifdef USE_SPNEGO
  if(data->state.proxyneg.state == GSS_AUTHSENT ||
     data->state.negotiate.state == GSS_AUTHSENT) {
    /* add forbid re-use if http-code != 401/407 as a WA only needed for
     * 401/407 that signal auth failure (empty) otherwise state will be RECV
     * with current code.
     * Do not close CONNECT_ONLY connections. */
    if((data->req.httpcode != 401) && (data->req.httpcode != 407) &&
       !data->set.connect_only)
      streamclose(conn, "Negotiate transfer completed");
    Curl_cleanup_negotiate(data);
  }
#endif

  /* set the proper values (possibly modified on POST) */
  conn->seek_func = data->set.seek_func; /* restore */
  conn->seek_client = data->set.seek_client; /* restore */
@@ -3376,7 +3379,24 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
        data->state.authproblem = TRUE;
      }
#endif

#if defined(USE_SPNEGO)
      if(conn->bits.close &&
        (((data->req.httpcode == 401) &&
          (conn->negotiate.state == GSS_AUTHRECV)) ||
         ((data->req.httpcode == 407) &&
          (conn->proxyneg.state == GSS_AUTHRECV)))) {
        infof(data, "Connection closure while negotiating auth (HTTP 1.0?)\n");
        data->state.authproblem = TRUE;
      }
      if((conn->negotiate.state == GSS_AUTHDONE) &&
         (data->req.httpcode != 401)) {
        conn->negotiate.state = GSS_AUTHSUCC;
      }
      if((conn->proxyneg.state == GSS_AUTHDONE) &&
         (data->req.httpcode != 407)) {
        conn->proxyneg.state = GSS_AUTHSUCC;
      }
#endif
      /*
       * When all the headers have been parsed, see if we should give
       * up and return an error.
@@ -3953,6 +3973,22 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
      if(result)
        return result;
    }
  #ifdef USE_SPNEGO
    else if(checkprefix("Persistent-Auth", k->p)) {
      struct negotiatedata *negdata = &conn->negotiate;
      struct auth *authp = &data->state.authhost;
      if(authp->picked == CURLAUTH_NEGOTIATE) {
        char *persistentauth = Curl_copy_header_value(k->p);
        if(!persistentauth)
          return CURLE_OUT_OF_MEMORY;
        negdata->noauthpersist = checkprefix("false", persistentauth);
        negdata->havenoauthpersist = TRUE;
        infof(data, "Negotiate: noauthpersist -> %d, header part: %s",
          negdata->noauthpersist, persistentauth);
        free(persistentauth);
      }
    }
  #endif
    else if((k->httpcode >= 300 && k->httpcode < 400) &&
            checkprefix("Location:", k->p) &&
            !data->req.location) {
+86 −23
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,
    service = data->set.str[STRING_PROXY_SERVICE_NAME] ?
              data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP";
    host = conn->http_proxy.host.name;
    neg_ctx = &data->state.proxyneg;
    neg_ctx = &conn->proxyneg;
  }
  else {
    userp = conn->user;
@@ -64,7 +64,7 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,
    service = data->set.str[STRING_SERVICE_NAME] ?
              data->set.str[STRING_SERVICE_NAME] : "HTTP";
    host = conn->host.name;
    neg_ctx = &data->state.negotiate;
    neg_ctx = &conn->negotiate;
  }

  /* Not set means empty */
@@ -80,11 +80,16 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,
    header++;

  len = strlen(header);
  neg_ctx->havenegdata = len != 0;
  if(!len) {
    /* Is this the first call in a new negotiation? */
    if(neg_ctx->context) {
      /* The server rejected our authentication and hasn't suppled any more
    if(neg_ctx->state == GSS_AUTHSUCC) {
      infof(conn->data, "Negotiate auth restarted\n");
      Curl_cleanup_negotiate(conn);
    }
    else if(neg_ctx->state != GSS_AUTHNONE) {
      /* The server rejected our authentication and hasn't supplied any more
      negotiation mechanisms */
      Curl_cleanup_negotiate(conn);
      return CURLE_LOGIN_DENIED;
    }
  }
@@ -106,14 +111,44 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,

CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy)
{
  struct negotiatedata *neg_ctx = proxy ? &conn->data->state.proxyneg :
    &conn->data->state.negotiate;
  struct negotiatedata *neg_ctx = proxy ? &conn->proxyneg :
    &conn->negotiate;
  struct auth *authp = proxy ? &conn->data->state.authproxy :
    &conn->data->state.authhost;
  char *base64 = NULL;
  size_t len = 0;
  char *userp;
  CURLcode result;

  result = Curl_auth_create_spnego_message(conn->data, neg_ctx, &base64, &len);
  authp->done = FALSE;

  if(neg_ctx->state == GSS_AUTHRECV) {
    if(neg_ctx->havenegdata) {
      neg_ctx->havemultiplerequests = TRUE;
    }
  }
  else if(neg_ctx->state == GSS_AUTHSUCC) {
    if(!neg_ctx->havenoauthpersist) {
      neg_ctx->noauthpersist = !neg_ctx->havemultiplerequests;
    }
  }

  if(neg_ctx->noauthpersist ||
    (neg_ctx->state != GSS_AUTHDONE && neg_ctx->state != GSS_AUTHSUCC)) {

    if(neg_ctx->noauthpersist && neg_ctx->state == GSS_AUTHSUCC) {
      infof(conn->data, "Curl_output_negotiate, "
       "no persistent authentication: cleanup existing context");
      Curl_auth_spnego_cleanup(neg_ctx);
    }
    if(!neg_ctx->context) {
      result = Curl_input_negotiate(conn, proxy, "Negotiate");
      if(result)
        return result;
    }

    result = Curl_auth_create_spnego_message(conn->data,
      neg_ctx, &base64, &len);
    if(result)
      return result;

@@ -131,13 +166,41 @@ CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy)

    free(base64);

  return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK;
    if(userp == NULL) {
      return CURLE_OUT_OF_MEMORY;
    }

    neg_ctx->state = GSS_AUTHSENT;
  #ifdef HAVE_GSSAPI
    if(neg_ctx->status == GSS_S_COMPLETE ||
       neg_ctx->status == GSS_S_CONTINUE_NEEDED) {
      neg_ctx->state = GSS_AUTHDONE;
    }
  #else
  #ifdef USE_WINDOWS_SSPI
    if(neg_ctx->status == SEC_E_OK ||
       neg_ctx->status == SEC_I_CONTINUE_NEEDED) {
      neg_ctx->state = GSS_AUTHDONE;
    }
  #endif
  #endif
  }

  if(neg_ctx->state == GSS_AUTHDONE || neg_ctx->state == GSS_AUTHSUCC) {
    /* connection is already authenticated,
     * don't send a header in future requests */
    authp->done = TRUE;
  }

  neg_ctx->havenegdata = FALSE;

  return CURLE_OK;
}

void Curl_cleanup_negotiate(struct Curl_easy *data)
void Curl_cleanup_negotiate(struct connectdata *conn)
{
  Curl_auth_spnego_cleanup(&data->state.negotiate);
  Curl_auth_spnego_cleanup(&data->state.proxyneg);
  Curl_auth_spnego_cleanup(&conn->negotiate);
  Curl_auth_spnego_cleanup(&conn->proxyneg);
}

#endif /* !CURL_DISABLE_HTTP && USE_SPNEGO */
+2 −2
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
@@ -31,7 +31,7 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,
/* this is for creating Negotiate header output */
CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy);

void Curl_cleanup_negotiate(struct Curl_easy *data);
void Curl_cleanup_negotiate(struct connectdata *conn);

#endif /* USE_SPNEGO */

+5 −1
Original line number Diff line number Diff line
@@ -600,7 +600,7 @@ static CURLcode multi_done(struct Curl_easy *data,

  /* if data->set.reuse_forbid is TRUE, it means the libcurl client has
     forced us to close this connection. This is ignored for requests taking
     place in a NTLM authentication handshake
     place in a NTLM/NEGOTIATE authentication handshake

     if conn->bits.close is TRUE, it means that the connection should be
     closed in spite of all our efforts to be nice, due to protocol
@@ -617,6 +617,10 @@ static CURLcode multi_done(struct Curl_easy *data,
#if defined(USE_NTLM)
      && !(conn->ntlm.state == NTLMSTATE_TYPE2 ||
           conn->proxyntlm.state == NTLMSTATE_TYPE2)
#endif
#if defined(USE_SPNEGO)
      && !(conn->negotiate.state == GSS_AUTHRECV ||
           conn->proxyneg.state == GSS_AUTHRECV)
#endif
     ) || conn->bits.close
       || (premature && !(conn->handler->flags & PROTOPT_STREAM))) {
+4 −0
Original line number Diff line number Diff line
@@ -806,6 +806,10 @@ CURLcode Curl_disconnect(struct Curl_easy *data,
  /* Cleanup NTLM connection-related data */
  Curl_http_ntlm_cleanup(conn);
#endif
#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO)
  /* Cleanup NEGOTIATE connection-related data */
  Curl_cleanup_negotiate(conn);
#endif

  /* the protocol specific disconnect handler and conn_shutdown need a transfer
     for the connection! */
Loading