1
0
mirror of https://github.com/moparisthebest/curl synced 2025-02-28 09:21:50 -05:00

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
This commit is contained in:
Dominik Hölzl 2018-09-10 09:18:01 +02:00 committed by Daniel Stenberg
parent dd8a19f8a0
commit 6c60355323
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
10 changed files with 200 additions and 117 deletions

View File

@ -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. */
/* This is not NTLM or many bytes left to send: close */
/* 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;
}
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,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) {

View File

@ -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,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;
userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "",
base64);
if(proxy) {
Curl_safefree(conn->allocptr.proxyuserpwd);
conn->allocptr.proxyuserpwd = userp;
if(neg_ctx->state == GSS_AUTHRECV) {
if(neg_ctx->havenegdata) {
neg_ctx->havemultiplerequests = TRUE;
}
}
else {
Curl_safefree(conn->allocptr.userpwd);
conn->allocptr.userpwd = userp;
else if(neg_ctx->state == GSS_AUTHSUCC) {
if(!neg_ctx->havenoauthpersist) {
neg_ctx->noauthpersist = !neg_ctx->havemultiplerequests;
}
}
free(base64);
if(neg_ctx->noauthpersist ||
(neg_ctx->state != GSS_AUTHDONE && neg_ctx->state != GSS_AUTHSUCC)) {
return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK;
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
}
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 */

View File

@ -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 */

View File

@ -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))) {

View File

@ -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! */

View File

@ -358,7 +358,9 @@ struct ntlmdata {
struct negotiatedata {
/* When doing Negotiate (SPNEGO) auth, we first need to send a token
and then validate the received one. */
enum { GSS_AUTHNONE, GSS_AUTHRECV, GSS_AUTHSENT } state;
enum {
GSS_AUTHNONE, GSS_AUTHRECV, GSS_AUTHSENT, GSS_AUTHDONE, GSS_AUTHSUCC
} state;
#ifdef HAVE_GSSAPI
OM_uint32 status;
gss_ctx_id_t context;
@ -380,6 +382,10 @@ struct negotiatedata {
size_t output_token_length;
#endif
#endif
bool noauthpersist;
bool havenoauthpersist;
bool havenegdata;
bool havemultiplerequests;
};
#endif
@ -977,6 +983,11 @@ struct connectdata {
#endif
#endif
#ifdef USE_SPNEGO
struct negotiatedata negotiate; /* state data for host Negotiate auth */
struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */
#endif
/* data used for the asynch name resolve callback */
struct Curl_async async;
@ -1274,11 +1285,6 @@ struct UrlState {
struct digestdata digest; /* state data for host Digest auth */
struct digestdata proxydigest; /* state data for proxy Digest auth */
#ifdef USE_SPNEGO
struct negotiatedata negotiate; /* state data for host Negotiate auth */
struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */
#endif
struct auth authhost; /* auth details for host */
struct auth authproxy; /* auth details for proxy */

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2016, 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
@ -273,6 +273,11 @@ void Curl_auth_spnego_cleanup(struct negotiatedata *nego)
/* Reset any variables */
nego->status = 0;
nego->state = GSS_AUTHNONE;
nego->noauthpersist = FALSE;
nego->havenoauthpersist = FALSE;
nego->havenegdata = FALSE;
nego->havemultiplerequests = FALSE;
}
#endif /* HAVE_GSSAPI && USE_SPNEGO */

View File

@ -343,6 +343,11 @@ void Curl_auth_spnego_cleanup(struct negotiatedata *nego)
/* Reset any variables */
nego->status = 0;
nego->token_max = 0;
nego->state = GSS_AUTHNONE;
nego->noauthpersist = FALSE;
nego->havenoauthpersist = FALSE;
nego->havenegdata = FALSE;
nego->havemultiplerequests = FALSE;
}
#endif /* USE_WINDOWS_SSPI && USE_SPNEGO */

View File

@ -8,17 +8,7 @@ HTTP Negotiate auth (stub krb5)
</info>
# Server-side
<reply>
<!-- First request, expect 401 Negotiate -->
<data>
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
WWW-Authenticate: Negotiate
Content-Length: 13
Not yet sir!
</data>
<!-- Second request, expect success in one shot -->
<!-- First request, expect success in one shot -->
<data1>
HTTP/1.1 200 Things are fine in server land
Server: Microsoft-IIS/7.0
@ -29,12 +19,6 @@ Content-Length: 15
Nice auth sir!
</data1>
<datacheck>
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
WWW-Authenticate: Negotiate
Content-Length: 13
HTTP/1.1 200 Things are fine in server land
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
@ -73,10 +57,6 @@ CURL_STUB_GSS_CREDS="KRB5_Alice"
^User-Agent:.*
</strip>
<protocol>
GET /2056 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /2056 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: Negotiate IktSQjVfQWxpY2UiOkhUVFBAMTI3LjAuMC4xOjE6QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==

View File

@ -8,17 +8,7 @@ HTTP Negotiate auth (stub ntlm)
</info>
# Server-side
<reply>
<!-- First request, expect 401 Negotiate -->
<data>
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
WWW-Authenticate: Negotiate
Content-Length: 13
Not yet sir!
</data>
<!-- Second request, expect 401 (ntlm challenge) -->
<!-- First request, expect 401 (ntlm challenge) -->
<data1>
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
@ -28,7 +18,7 @@ Content-Length: 19
Still not yet sir!
</data1>
<!-- Third request, expect success -->
<!-- Second request, expect success -->
<data2>
HTTP/1.1 200 Things are fine in server land
Server: Microsoft-IIS/7.0
@ -39,12 +29,6 @@ Content-Length: 15
Nice auth sir!
</data2>
<datacheck>
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
WWW-Authenticate: Negotiate
Content-Length: 13
HTTP/1.1 401 Authorization Required
Server: Microsoft-IIS/7.0
Content-Type: text/html; charset=iso-8859-1
@ -89,10 +73,6 @@ CURL_STUB_GSS_CREDS="NTLM_Alice"
^User-Agent:.*
</strip>
<protocol>
GET /2057 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /2057 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: Negotiate Ik5UTE1fQWxpY2UiOkhUVFBAMTI3LjAuMC4xOjI6QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==