Skip to content
Permalink
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
  • Loading branch information...
dhoelzl authored and bagder committed Sep 10, 2018
1 parent dd8a19f commit 6c6035532383e300c712e4c1cd9fdd749ed5cf59
Showing with 199 additions and 116 deletions.
  1. +76 −40 lib/http.c
  2. +86 −23 lib/http_negotiate.c
  3. +2 −2 lib/http_negotiate.h
  4. +5 −1 lib/multi.c
  5. +4 −0 lib/url.c
  6. +12 −6 lib/urldata.h
  7. +6 −1 lib/vauth/spnego_gssapi.c
  8. +5 −0 lib/vauth/spnego_sspi.c
  9. +1 −21 tests/data/test2056
  10. +2 −22 tests/data/test2057
@@ -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,26 +628,18 @@ 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;
(void)path;
#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,21 +855,18 @@ 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);
data->req.newurl = strdup(data->change.url);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
data->state.authproblem = FALSE;
/* we received a GSS auth token and we dealt with it fine */
negdata->state = GSS_AUTHRECV;
}
else
data->state.authproblem = TRUE;
CURLcode result = Curl_input_negotiate(conn, proxy, auth);
if(!result) {
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 a GSS auth token and we dealt with it fine */
negdata->state = GSS_AUTHRECV;
}
else
data->state.authproblem = TRUE;
}
}
}
@@ -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) {
@@ -56,15 +56,15 @@ 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;
passwdp = conn->passwd;
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,38 +111,96 @@ 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);
if(result)
return result;
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;
}
}

userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "",
base64);
if(neg_ctx->noauthpersist ||
(neg_ctx->state != GSS_AUTHDONE && neg_ctx->state != GSS_AUTHSUCC)) {

if(proxy) {
Curl_safefree(conn->allocptr.proxyuserpwd);
conn->allocptr.proxyuserpwd = userp;
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;

userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "",
base64);

if(proxy) {
Curl_safefree(conn->allocptr.proxyuserpwd);
conn->allocptr.proxyuserpwd = userp;
}
else {
Curl_safefree(conn->allocptr.userpwd);
conn->allocptr.userpwd = userp;
}

free(base64);

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
}
else {
Curl_safefree(conn->allocptr.userpwd);
conn->allocptr.userpwd = userp;

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;
}

free(base64);
neg_ctx->havenegdata = FALSE;

return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK;
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 */
@@ -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 */

@@ -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))) {
@@ -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! */
Oops, something went wrong.

0 comments on commit 6c60355

Please sign in to comment.
You can’t perform that action at this time.