digest: flush state on origin or credential change

Verified by test 1686

Closes #21944
This commit is contained in:
Daniel Stenberg 2026-06-10 10:27:50 +02:00
parent 3f1055303e
commit 5c6b488035
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 203 additions and 4 deletions

View file

@ -94,6 +94,22 @@ CURLcode Curl_output_digest(struct Curl_easy *data,
#endif
}
else {
bool flush = FALSE;
DEBUGASSERT(data->conn->origin);
if(data->state.digest.origin &&
!Curl_peer_same_destination(data->conn->origin,
data->state.digest.origin))
flush = TRUE;
else if(data->state.digest.creds &&
!Curl_creds_same(data->state.creds, data->state.digest.creds))
flush = TRUE;
if(flush)
/* flush host Digest state */
Curl_auth_digest_cleanup(&data->state.digest);
Curl_peer_link(&data->state.digest.origin, data->conn->origin);
Curl_creds_link(&data->state.digest.creds, data->state.creds);
digest = &data->state.digest;
allocuserpwd = &data->req.hd_auth;
creds = data->state.creds;

View file

@ -148,13 +148,12 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */
#ifndef CURL_DISABLE_DIGEST_AUTH
/* Struct used for Digest challenge-response authentication */
struct digestdata {
struct Curl_creds *creds;
struct Curl_peer *origin;
#ifdef USE_WINDOWS_SSPI
BYTE *input_token;
size_t input_token_len;
CtxtHandle *http_context;
/* linked credentials used to make the identity for http_context.
may be NULL. */
struct Curl_creds *creds;
#else
char *nonce;
char *cnonce;

View file

@ -1039,6 +1039,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
*/
void Curl_auth_digest_cleanup(struct digestdata *digest)
{
Curl_peer_unlink(&digest->origin);
Curl_creds_unlink(&digest->creds);
curlx_safefree(digest->nonce);
curlx_safefree(digest->cnonce);
curlx_safefree(digest->realm);

View file

@ -630,6 +630,7 @@ void Curl_auth_digest_cleanup(struct digestdata *digest)
/* Free the copy of user/passwd used to make the identity for http_context */
Curl_creds_unlink(&digest->creds);
Curl_peer_unlink(&digest->origin);
}
#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */

View file

@ -221,7 +221,7 @@ test1653 test1654 test1655 test1656 test1657 test1658 test1659 test1660 \
test1661 test1662 test1663 test1664 test1665 test1666 test1667 test1668 \
test1669 test1670 test1671 test1672 test1673 test1674 test1675 test1676 \
test1677 test1680 test1681 test1682 test1683 test1684 \
test1685 \
test1685 test1686 \
\
test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \
test1708 test1709 test1710 test1711 test1712 test1713 test1714 test1715 \

84
tests/data/test1686 Normal file
View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
HTTP
Digest
</keywords>
</info>
<reply>
<data crlf="headers" nocheck="yes">
HTTP/1.1 401 Authorization Required
Server: Apache/1.3.27 (Darwin) PHP/4.1.2
WWW-Authenticate: Digest realm="my-backyard", nonce="314156295"
Content-Length: 26
This is not the real page
</data>
# This is supposed to be returned when the server gets a
# Authorization: Digest line passed-in from the client
<data1000 crlf="headers">
HTTP/1.1 200 OK
Server: Apache/1.3.27 (Darwin) PHP/4.1.2
Content-Type: text/html; charset=iso-8859-1
Content-Length: 23
This IS the real page!
</data1000>
</reply>
<client>
<features>
!SSPI
crypto
digest
</features>
<server>
http
</server>
<name>
HTTP Digest to different origins and switching credentials
</name>
<tool>
lib%TESTNUMBER
</tool>
<command>
%HOSTIP %HTTPPORT
</command>
</client>
<verify>
<protocol crlf="headers">
GET /api HTTP/1.1
Host: first.test:%HTTPPORT
Accept: */*
GET /api HTTP/1.1
Host: first.test:%HTTPPORT
Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/api", response="4ecc00e567c37a9d537727890c2e5b32"
Accept: */*
GET /hook HTTP/1.1
Host: second.test:%HTTPPORT
Accept: */*
GET /hook HTTP/1.1
Host: second.test:%HTTPPORT
Authorization: Digest username="alice", realm="my-backyard", nonce="314156295", uri="/hook", response="d3a7738fb6a23f5543fb8dacc0f0f253"
Accept: */*
GET /hook HTTP/1.1
Host: second.test:%HTTPPORT
Accept: */*
GET /hook HTTP/1.1
Host: second.test:%HTTPPORT
Authorization: Digest username="bob", realm="my-backyard", nonce="314156295", uri="/hook", response="777e68eddb77294d9cbd6134973cbbab"
Accept: */*
</protocol>
</verify>
</testcase>

View file

@ -101,6 +101,7 @@ TESTS_C = \
lib1598.c lib1599.c \
lib1647.c lib1648.c lib1649.c \
lib1662.c \
lib1686.c \
lib1900.c lib1901.c lib1902.c lib1903.c lib1905.c lib1906.c lib1907.c \
lib1908.c lib1910.c lib1911.c lib1912.c lib1913.c \
lib1915.c lib1916.c lib1918.c lib1919.c lib1920.c lib1921.c \

96
tests/libtest/lib1686.c Normal file
View file

@ -0,0 +1,96 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 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
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "first.h"
static size_t devnull_1686(char *p, size_t s, size_t n, void *u)
{
(void)p;
(void)u;
return s * n;
}
#define FIRSTHOST "first.test"
#define SECONDHOST "second.test"
static CURLcode test_lib1686(const char *hostip)
{
CURL *curl = NULL;
CURLcode result = CURLE_OK;
const char *httpport = libtest_arg2;
char firsturl[100];
char secondurl[100];
char firstres[100];
char secondres[100];
struct curl_slist *host = NULL;
struct curl_slist *host2 = NULL;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
curl_mfprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
/* create strings for CURLOPT_RESOLVE */
curl_msnprintf(firstres, sizeof(firstres), "%s:%s:%s",
FIRSTHOST, httpport, hostip);
curl_msnprintf(secondres, sizeof(secondres), "%s:%s:%s",
SECONDHOST, httpport, hostip);
/* create URLs */
curl_msnprintf(firsturl, sizeof(firsturl), "http://%s:%s/api",
FIRSTHOST, httpport);
curl_msnprintf(secondurl, sizeof(secondurl), "http://%s:%s/hook",
SECONDHOST, httpport);
host = curl_slist_append(NULL, firstres);
if(!host)
goto test_cleanup;
host2 = curl_slist_append(host, secondres);
if(!host2)
goto test_cleanup;
host = host2;
curl = curl_easy_init();
if(curl) {
easy_setopt(curl, CURLOPT_RESOLVE, host);
easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
easy_setopt(curl, CURLOPT_USERPWD, "alice:bond");
easy_setopt(curl, CURLOPT_WRITEFUNCTION, devnull_1686);
easy_setopt(curl, CURLOPT_URL, firsturl);
result = curl_easy_perform(curl);
easy_setopt(curl, CURLOPT_URL, secondurl);
result = curl_easy_perform(curl);
easy_setopt(curl, CURLOPT_USERPWD, "bob:secret");
easy_setopt(curl, CURLOPT_URL, secondurl);
result = curl_easy_perform(curl);
}
test_cleanup:
curl_easy_cleanup(curl);
curl_global_cleanup();
curl_slist_free_all(host);
return result;
}