creds: hold credentials

Authorizdation credentials are kept in `struct Curl_creds`. This contains:

* `user`: the username, maybe the empty string
* `passwd`: the password, maybe the empty string
* `sasl_authzid`: the SASL authz value, maybe the empty string
* `oauth_bearer`: the OAUTH bearer token, maybe the empty string
* `source`: where the credentials from from
* `refcount`: a reference counter to link/unkink creds

A `creds` with all values empty is equivalent to NULL, e.g. no `creds`
instance. With reference counting, `creds` can be linked/unlinked
in several places.

See docs/internals/CREDENTIALS.md for use.

Closes #21548
This commit is contained in:
Stefan Eissing 2026-05-11 14:25:52 +02:00 committed by Daniel Stenberg
parent a32a2b0b77
commit 8f71d0fde5
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
47 changed files with 1138 additions and 962 deletions

View file

@ -250,14 +250,14 @@ char *Curl_copy_header_value(const char *header)
*
* Returns CURLcode.
*/
static CURLcode http_output_basic(struct Curl_easy *data, bool proxy)
static CURLcode http_output_basic(struct Curl_easy *data,
struct connectdata *conn, bool proxy)
{
size_t size = 0;
char *authorization = NULL;
char **p_hd;
const char *user;
const char *pwd;
CURLcode result;
struct Curl_creds *creds = NULL;
char *out;
/* credentials are unique per transfer for HTTP, do not use the ones for the
@ -265,19 +265,23 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy)
if(proxy) {
#ifndef CURL_DISABLE_PROXY
p_hd = &data->req.hd_proxy_auth;
user = data->state.aptr.proxyuser;
pwd = data->state.aptr.proxypasswd;
creds = conn->http_proxy.creds;
#else
(void)conn;
return CURLE_NOT_BUILT_IN;
#endif
}
else {
p_hd = &data->req.hd_auth;
user = data->state.aptr.user;
pwd = data->state.aptr.passwd;
creds = data->state.creds;
}
out = curl_maprintf("%s:%s", user ? user : "", pwd ? pwd : "");
if(!creds) {
DEBUGASSERT(0);
return CURLE_FAILED_INIT;
}
out = curl_maprintf("%s:%s", creds->user, creds->passwd);
if(!out)
return CURLE_OUT_OF_MEMORY;
@ -320,10 +324,11 @@ static CURLcode http_output_bearer(struct Curl_easy *data)
char **userp;
CURLcode result = CURLE_OK;
DEBUGASSERT(Curl_creds_has_oauth_bearer(data->state.creds));
userp = &data->req.hd_auth;
curlx_free(*userp);
*userp = curl_maprintf("Authorization: Bearer %s\r\n",
data->set.str[STRING_BEARER]);
Curl_creds_oauth_bearer(data->state.creds));
if(!*userp) {
result = CURLE_OUT_OF_MEMORY;
@ -527,10 +532,10 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode)
* Either we are not authenticating, or we are supposed to be authenticating
* something else. This is an error.
*/
if((httpcode == 401) && !data->state.aptr.user)
if((httpcode == 401) && !data->state.creds)
return TRUE;
#ifndef CURL_DISABLE_PROXY
if((httpcode == 407) && !data->conn->bits.proxy_user_passwd)
if((httpcode == 407) && !data->conn->http_proxy.creds)
return TRUE;
#endif
@ -551,7 +556,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data)
CURLcode result = CURLE_OK;
unsigned long authmask = ~0UL;
if(!data->set.str[STRING_BEARER])
if(!Curl_creds_has_oauth_bearer(data->state.creds))
authmask &= (unsigned long)~CURLAUTH_BEARER;
if(100 <= data->req.httpcode && data->req.httpcode <= 199)
@ -561,7 +566,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data)
if(data->state.authproblem)
return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK;
if((data->state.aptr.user || data->set.str[STRING_BEARER]) &&
if(data->state.creds &&
((data->req.httpcode == 401) ||
(data->req.authneg && data->req.httpcode < 300))) {
pickhost = pickoneauth(&data->state.authhost, authmask);
@ -578,7 +583,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data)
}
}
#ifndef CURL_DISABLE_PROXY
if(conn->bits.proxy_user_passwd &&
if(conn->http_proxy.creds &&
((data->req.httpcode == 407) ||
(data->req.authneg && data->req.httpcode < 300))) {
pickproxy = pickoneauth(&data->state.authproxy,
@ -694,14 +699,14 @@ static CURLcode output_auth_headers(struct Curl_easy *data,
/* Basic */
if(
#ifndef CURL_DISABLE_PROXY
(proxy && conn->bits.proxy_user_passwd &&
(proxy && conn->http_proxy.creds &&
!Curl_checkProxyheaders(data, conn,
STRCONST("Proxy-authorization"))) ||
#endif
(!proxy && data->state.aptr.user &&
(!proxy && data->state.creds &&
!Curl_checkheaders(data, STRCONST("Authorization")))) {
auth = "Basic";
result = http_output_basic(data, proxy);
result = http_output_basic(data, conn, proxy);
if(result)
return result;
}
@ -714,8 +719,7 @@ static CURLcode output_auth_headers(struct Curl_easy *data,
#ifndef CURL_DISABLE_BEARER_AUTH
if(authstatus->picked == CURLAUTH_BEARER) {
/* Bearer */
if(!proxy && data->set.str[STRING_BEARER] &&
Curl_auth_allowed_to_host(data) &&
if(!proxy && Curl_creds_has_oauth_bearer(data->state.creds) &&
!Curl_checkheaders(data, STRCONST("Authorization"))) {
auth = "Bearer";
result = http_output_bearer(data);
@ -737,15 +741,15 @@ static CURLcode output_auth_headers(struct Curl_easy *data,
data->info.httpauthpicked = authstatus->picked;
infof(data, "%s auth using %s with user '%s'",
proxy ? "Proxy" : "Server", auth,
proxy ? (data->state.aptr.proxyuser ?
data->state.aptr.proxyuser : "") :
(data->state.aptr.user ?
data->state.aptr.user : ""));
proxy ? (conn->http_proxy.creds ?
conn->http_proxy.creds->user : "") :
(data->state.creds ?
data->state.creds->user : ""));
#else
(void)proxy;
infof(data, "Server auth using %s with user '%s'",
auth, data->state.aptr.user ?
data->state.aptr.user : "");
auth, data->state.creds ?
data->state.creds->user : "");
#endif
authstatus->multipass = !authstatus->done;
}
@ -780,14 +784,13 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data,
if(
#ifndef CURL_DISABLE_PROXY
(!conn->bits.httpproxy || !conn->bits.proxy_user_passwd) &&
(!conn->bits.httpproxy || !conn->http_proxy.creds) &&
#endif
!data->state.aptr.user &&
#ifdef USE_SPNEGO
!(authhost->want & CURLAUTH_NEGOTIATE) &&
!(authproxy->want & CURLAUTH_NEGOTIATE) &&
#endif
!data->set.str[STRING_BEARER]) {
!data->state.creds) {
/* no authentication with no user or password */
authhost->done = TRUE;
authproxy->done = TRUE;
@ -832,13 +835,9 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data,
with it */
authproxy->done = TRUE;
/* To prevent the user+password to get sent to other than the original host
due to a location-follow */
if(Curl_auth_allowed_to_host(data)
#ifndef CURL_DISABLE_NETRC
|| conn->bits.netrc
#endif
)
/* Either we have credentials for the origin we talk to or
performing authentication is allowed here */
if(data->state.creds || Curl_auth_allowed_to_host(data))
result = output_auth_headers(data, conn, authhost, request,
path_and_query, FALSE);
else
@ -1227,8 +1226,6 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl,
return CURLE_OUT_OF_MEMORY;
}
else {
bool same_origin;
CURLcode result;
CURLU *u = curl_url();
if(!u)
return CURLE_OUT_OF_MEMORY;
@ -1242,29 +1239,16 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl,
return Curl_uc_to_curlcode(uc);
}
same_origin = Curl_url_same_origin(u, data->state.uh);
curl_url_cleanup(u);
#ifndef CURL_DISABLE_DIGEST_AUTH
if(!same_origin)
Curl_auth_digest_cleanup(&data->state.digest);
{
bool same_origin = Curl_url_same_origin(u, data->state.uh);
curl_url_cleanup(u);
if(!same_origin)
Curl_auth_digest_cleanup(&data->state.digest);
}
#else
curl_url_cleanup(u);
#endif
if((!same_origin && !data->set.allow_auth_to_other_hosts) ||
!data->set.str[STRING_USERNAME]) {
result = Curl_reset_userpwd(data);
if(result) {
curlx_free(follow_url);
return result;
}
curlx_safefree(data->state.aptr.user);
curlx_safefree(data->state.aptr.passwd);
}
result = Curl_reset_proxypwd(data);
if(result) {
curlx_free(follow_url);
return result;
}
}
DEBUGASSERT(follow_url);
@ -2005,9 +1989,6 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data)
struct dynamically_allocated_data *aptr = &data->state.aptr;
const char *ptr;
if(!data->state.this_is_a_follow)
Curl_peer_link(&data->state.first_origin, conn->origin);
curlx_safefree(aptr->host);
#ifndef CURL_DISABLE_COOKIES
curlx_safefree(data->req.cookiehost);
@ -2015,7 +1996,7 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data)
ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr && (!data->state.this_is_a_follow ||
Curl_peer_equal(data->state.first_origin, conn->origin))) {
Curl_peer_equal(data->state.initial_origin, conn->origin))) {
#ifndef CURL_DISABLE_COOKIES
/* If we have a given custom Host: header, we extract the hostname in
order to possibly use it for cookie reasons later on. We only allow the
@ -2138,6 +2119,19 @@ static CURLcode http_target(struct Curl_easy *data,
return CURLE_OUT_OF_MEMORY;
}
}
else if(data->state.creds && (data->state.creds->source != CREDS_URL)) {
/* credentials not from the URL need to be set */
uc = curl_url_set(h, CURLUPART_USER,
data->state.creds->user, CURLU_URLENCODE);
if(!uc)
uc = curl_url_set(h, CURLUPART_PASSWORD,
data->state.creds->passwd, CURLU_URLENCODE);
if(uc) {
curl_url_cleanup(h);
return Curl_uc_to_curlcode(uc);
}
}
/* Extract the URL to use in the request. */
uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT);
if(uc) {