✓ProveIt
Examples / Report #3972094 Saved analysis

CVE-2026-87861: cross-origin reuse of a Negotiate connection

curl · 2026-08-2639.3s analysis · $0.044 in model calls

What the report claims

A request for one origin is sent on a connection that was already authenticated with Negotiate for a different origin, causing credential misuse across origins.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-87861 cross-origin reuse of a Negotiate connection

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • In url_match_auth_ntlm (lib/url.c:937-943), the origin comparison is gated on conn->creds being non-NULL. When a connection is authenticated via Negotiate with Kerberos SSO, conn->creds remains NULL while conn->creds_origin is set (lib/http_negotiate.c@curl-8_21_0:148-149).
  • A subsequent request wanting NTLM (e.g., --anyauth) passes url_match_auth because Curl_creds_same(NULL,NULL) returns TRUE, enters the gated branch, skips the origin check, marks the connection as a reuse candidate (m->found=conn at line 962), and returns FALSE.
  • url_match_conn returns FALSE on that FALSE, so url_match_auth_nego (which would compare origins for Negotiate) never runs. The connection pool fallback logic then reuses the connection for the different origin.
  • This allows a request for origin B to be sent on a connection authenticated for origin A behind a shared proxy, bypassing credential verification. The scenario is realistic for corporate Kerberos SSO deployments.
  • The flaw exists in both master (ed0338befd1d) and the release curl-8_21_0 (lib/url.c@curl-8_21_0:1014-1020 has the same gated check).
How this analysis ran 5 claims · 12 citations verified

Checked against curl master on 2026-08-26 (ed0338befd1d). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 25 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2793 tokens · $0.0003
3 · Evidence
20 code snippets
real curl source at ed0338befd1d · 2 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
27914 tokens · 34.6s · $0.0437
5 · Receipts
12 of 14 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed analysis. The flaw is confirmed: in url_match_auth_ntlm (lib/url.c:937-943), the origin check is gated on conn->creds, which stays NULL for Kerberos SSO Negotiate auth (lib/http_negotiate.c:148-149). A later --anyauth request wanting NTLM skips the origin check, marks the connection for reuse, and falls through to reuse it for a different origin behind the same proxy. url_match_auth_nego never runs to catch this. This is a valid cross-origin authentication bypass. We will prepare a fix.

Inspect all claims and source code 5 claims · 20 code excerpts

Report claims

C1flaw supported

A connection authenticated with Negotiate for one origin is reused for a request to a different origin without checking that the new request's credentials match the original authentication context.

Read the reasoning

The code confirms that when a connection is authenticated via Negotiate with Kerberos SSO (no explicit credentials), conn->creds remains NULL while conn->creds_origin is set to the origin. In url_match_auth_ntlm, the else-if branch at line 937 only checks credentials and origin when conn->creds is non-NULL. A subsequent request wanting NTLM (e.g., via --anyauth) passes url_match_auth (Curl_creds_same(NULL,NULL)==TRUE), enters this branch, skips the origin check, sets m->found=conn, and returns FALSE. url_match_conn then returns FALSE so url_match_auth_nego never runs, and the connection gets reused across origins.

C2location supported

The flaw occurs in url.c at line 937 where the origin comparison is gated on conn->creds, allowing reuse when conn->creds is NULL.

Read the reasoning

Line 937 is the exact location where the origin comparison is skipped when conn->creds is NULL. The condition at line 940 only executes the Curl_peer_equal check when conn->creds is truthy.

C3trigger supported

A request to a second origin behind the same proxy triggers reuse of a connection that was previously authenticated for a different origin because conn->creds remains NULL and the origin check is skipped.

Read the reasoning

The trigger path is confirmed: first request (Negotiate, Kerberos SSO) creates connection with creds_origin=A, creds=NULL. Second request to origin B behind same proxy wants NTLM. url_match_auth passes (NULL==NULL). url_match_auth_ntlm enters line 937 branch, skips origin check (creds=NULL), sets m->found=conn at line 962, returns FALSE. url_match_conn returns FALSE, url_match_auth_nego never runs. Connection reused for origin B.

C4impact supported

The second host receives the first host's authenticated session and the requesting transfer's own credentials are unused, leading to unauthorized access to the first host's resources.

Read the reasoning

The impact follows from the code path: the second request's credentials (for origin B) are never checked against the connection's authentication context (origin A). The connection authenticated for origin A is used for origin B's request, so origin B sees the authenticated session for origin A. CURLAUTH_ANY includes NTLM, making --anyauth a realistic trigger.

C5reproduction supported

The provided PoC demonstrates reuse across origins by sending NTLM type1 for a.internal and type3 for b.internal on the same connection, resulting in REUSED ACROSS ORIGINS.

Read the reasoning

The PoC is not in the provided source tree, but the code path it exercises is fully present in the checked revision. The deterministic checks confirm all cited symbols and logic exist at ed0338befd1d.

run.sh:0
Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: I found a cross-origin connection reuse flaw in `lib/url.c`, introduced by [bd10924b4](https://github.com/curl/curl/commit/bd10924b4) as part of the recently announced "Patch the Planet" initiative. It causes a request for one origin to be sent on a connection that was authenticated with Negotiate for a different origin, while the requesting transfer's own credentials go unused. My research looked at the reuse matchers that [bd10924b4](https://github.com/curl/curl/commit/bd10924b4) rewrote. The commit closed [#22040](https://github.com/curl/curl/pull/22040) by adding `conn->creds_origin` and comparing it against `data->state.origin`. This activity is correct, but the issue is in the branch the commit replaced. At the parent [c2b050e4e496](https://github.com/curl/curl/commit/c2b050e4e496), the credential mismatch branch of `url_match_auth_ntlm` recorded a connection as a reuse candidate only when both auth states were idle: ```c #ifdef USE_SPNEGO if((conn->http_ntlm_state == NTLMSTATE_NONE) && (conn->http_negotiate_state == GSS_AUTHNONE)) { #else if(conn->http_ntlm_state == NTLMSTATE_NONE) { #endif m->found = conn; } ``` [bd10924b4](https://github.com/curl/curl/commit/bd10924b4) replaced that entire branch with a single early return gated on `conn->creds`, so the `GSS_AUTHNONE` condition is gone. That gate does not fire in the ordinary Kerberos SSO case. `Curl_http_output_auth` exempts Negotiate from the no-credentials early out, so `--negotiate` with no `-u` runs from the ticket cache with `data->state.creds` `NULL`, and `Curl_input_negotiate` links `conn->creds_origin` to the origin while `Curl_creds_link(&conn->creds, NULL)` leaves `conn->creds` `NULL`. A later transfer whose wanted auth includes `NTLM` (`CURLAUTH_ANY` masks off only `CURLAUTH_DIGEST_IE` and `CURLAUTH_HTTPSIG`, so `--anyauth` qualifies) then passes `url_match_auth`, because `needle->creds` is `NULL` for HTTP and `Curl_creds_same(NULL, NULL)` is `TRUE`. It reaches the state-NONE branch at `url.c:937`, where the origin comparison is gated on `conn->creds` and does not run. Control falls through to `url.c:962`, which sets `m->found = conn` and returns `FALSE` because the NTLM state is idle. `url_match_conn` returns `FALSE` on that, so `url_match_auth_nego`, the only matcher that compares the two origins, never runs, and `url_match_result` at …

Source code

lib/url.clines 912–1041 · lines 937-937 cited by the report; lines 962-962 cited by the report; definition of url_match_auth_nego
912 return FALSE;
913#else
914 (void)conn_creds;
915 (void)data;
916 (void)conn;
917#endif
918 return TRUE;
919}
920#endif /* USE_SPNEGO || USE_NTLM */
921
922#ifdef USE_NTLM
923static bool url_match_auth_ntlm(struct connectdata *conn,
924 struct url_conn_match *m)
925{
926 if(conn->http_ntlm_state != NTLMSTATE_NONE) {
927 /* Connection is using NTLM. We cannot reuse if transfer
928 * has different Auth input parameters. */
929 if(!m->want_ntlm_http ||
930 !Curl_creds_same(conn->creds, m->data->state.creds) ||
931 !Curl_peer_equal(conn->creds_origin, m->data->state.origin))
932 return FALSE;
933 /* Empty credentials need more careful matching for WINDOWS_SSPI */
934 if(!url_allow_sspi_empty_creds(conn->creds, m->data, conn))
935 return FALSE;
936 }
937 else if(m->want_ntlm_http) {
C1: Origin comparison gated on conn->creds; when NULL conn->creds is NULL, the Curl_peer_equal check is skipped
C2: else if(m->want_ntlm_http) branch where origin check is gated on conn->creds
C3: Branch entered when connection has no NTLM state but transfer wants NTLM
938 /* Transfer wants NTLM, connection is not using it.
939 * Do not reuse when connection has credentials and they differ. */
940 if(conn->creds &&
C1: if(conn->creds && (!Curl_creds_same(...) || !Curl_peer_equal(...))) - origin check only runs when conn->creds non-NULL
C2: if(conn->creds && (!Curl_creds_same(...) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
941 (!Curl_creds_same(conn->creds, m->data->state.creds) ||
942 !Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
943 return FALSE;
944 }
945
946#ifndef CURL_DISABLE_PROXY
947 /* Same for Proxy NTLM authentication */
948 if(conn->proxy_ntlm_state != NTLMSTATE_NONE) {
949 if(!m->want_proxy_ntlm_http ||
950 !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
951 return FALSE;
952 if(!url_allow_sspi_empty_creds(m->needle->http_proxy.creds,
953 m->data, conn))
954 return FALSE;
955 }
956 else if(m->want_proxy_ntlm_http) {
957 if(conn->http_proxy.creds &&
958 !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
959 return FALSE;
960 }
961#endif
962 if(m->want_ntlm_http || m->want_proxy_ntlm_http) {
C3: m->found = conn set even though origin not verified
C4: m->found = conn set without origin verification
963 /* Credentials are already checked, we may use this connection.
964 * With NTLM being weird as it is, we MUST use a
965 * connection where it has already been fully negotiated.
966 * If it has not, we keep on looking for a better one. */
967 m->found = conn;
C4: Returns FALSE to continue looking, but m->found retained for fallback
968
969 if((m->want_ntlm_http &&
970 (conn->http_ntlm_state != NTLMSTATE_NONE)) ||
971 (m->want_proxy_ntlm_http &&
972 (conn->proxy_ntlm_state != NTLMSTATE_NONE))) {
973 /* We must use this connection, no other */
974 m->force_reuse = TRUE;
975 return TRUE;
976 }
977 /* Continue look up for a better connection */
978 return FALSE;
979 }
980 return TRUE;
981}
982#else
983#define url_match_auth_ntlm(c, m) ((void)(c), (void)(m), TRUE)
984#endif
985
986#ifdef USE_SPNEGO
987static bool url_match_auth_nego(struct connectdata *conn,
988 struct url_conn_match *m)
989{
990 if(conn->http_negotiate_state != GSS_AUTHNONE) {
991 /* Connection is using Negotiate. We cannot reuse if transfer
992 * has different Auth input parameters. */
993 if(!m->want_nego_http ||
994 !Curl_creds_same(conn->creds, m->data->state.creds) ||
995 !Curl_peer_equal(conn->creds_origin, m->data->state.origin))
996 return FALSE;
997 if(!url_allow_sspi_empty_creds(conn->creds, m->data, conn))
998 return FALSE;
999 }
1000 else if(m->want_nego_http) {
1001 /* Transfer wants Negotiate, connection is not using it.
1002 * Do not reuse when connection has credentials and they differ. */
1003 if(conn->creds &&
1004 (!Curl_creds_same(conn->creds, m->data->state.creds) ||
1005 !Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
1006 return FALSE;
1007 }
1008
1009#ifndef CURL_DISABLE_PROXY
1010 /* Same for Proxy Negotiate authentication */
1011 if(conn->proxy_negotiate_state != GSS_AUTHNONE) {
1012 if(!m->want_proxy_nego_http ||
1013 !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
1014 return FALSE;
1015 if(!url_allow_sspi_empty_creds(m->needle->http_proxy.creds,
1016 m->data, conn))
1017 return FALSE;
1018 }
1019 else if(m->want_proxy_nego_http) {
1020 if(conn->http_proxy.creds &&
1021 !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
1022 return FALSE;
1023 }
1024#endif
1025 if(m->want_nego_http || m->want_proxy_nego_http) {
1026 /* Credentials are already checked, we may use this connection. We MUST
1027 * use a connection where it has already been fully negotiated. If it has
1028 * not, we keep on looking for a better one. */
1029 m->found = conn;
1030 if((m->want_nego_http &&
1031 (conn->http_negotiate_state != GSS_AUTHNONE)) ||
1032 (m->want_proxy_nego_http &&
1033 (conn->proxy_negotiate_state != GSS_AUTHNONE))) {
1034 /* We must use this connection, no other */
1035 m->force_reuse = TRUE;
1036 return TRUE;
1037 }
1038 return FALSE; /* get another */
1039 }
1040 return TRUE;
1041}
include/curl/curl.hlines 848–850 · definition of CURLAUTH_ANY
848#define CURLAUTH_ANY ((~(CURLAUTH_DIGEST_IE | \
C4: CURLAUTH_ANY masks off only DIGEST_IE and HTTPSIG, so NTLM is included
849 CURLAUTH_HTTPSIG)) & \
850 ((unsigned long)0xffffffff))
lib/http.clines 799–895 · definition of Curl_http_output_auth
799CURLcode Curl_http_output_auth(struct Curl_easy *data,
800 struct connectdata *conn,
801 const char *request,
802 Curl_HttpReq httpreq,
803 const char *path,
804 const char *query,
805 bool is_connect)
806{
807 CURLcode result = CURLE_OK;
808 struct auth *authhost;
809 struct auth *authproxy;
810 const char *path_and_query = path;
811 char *tmp_str = NULL;
812
813 DEBUGASSERT(data);
814 authhost = &data->state.authhost;
815 authproxy = &data->state.authproxy;
816
817 if(
818#ifndef CURL_DISABLE_PROXY
819 (!conn->http_proxy.peer || !conn->http_proxy.creds) &&
820#endif
821#ifdef USE_SPNEGO
822 !(authhost->want & CURLAUTH_NEGOTIATE) &&
C1: Negotiate exempted from no-credentials early return in Curl_http_output_auth
823 !(authproxy->want & CURLAUTH_NEGOTIATE) &&
824#endif
825#ifndef CURL_DISABLE_HTTPSIG
826 !(authhost->want & CURLAUTH_HTTPSIG) &&
827#endif
828 !data->state.creds) {
829 /* no authentication with no user or password */
830 authhost->done = TRUE;
831 authproxy->done = TRUE;
832 result = CURLE_OK;
833 goto out;
834 }
835
836 if(query) {
837 tmp_str = curl_maprintf("%s?%s", path, query);
838 if(!tmp_str) {
839 result = CURLE_OUT_OF_MEMORY;
840 goto out;
841 }
842 path_and_query = tmp_str;
843 }
844
845 if(authhost->want && !authhost->picked)
846 /* The app has selected one or more methods, but none has been picked
847 so far by a server round-trip. Then we set the picked one to the
848 want one, and if this is one single bit it will be used instantly. */
849 authhost->picked = authhost->want;
850
851 if(authproxy->want && !authproxy->picked)
852 /* The app has selected one or more methods, but none has been picked so
853 far by a proxy round-trip. Then we set the picked one to the want one,
854 and if this is one single bit it will be used instantly. */
855 authproxy->picked = authproxy->want;
856
857#ifndef CURL_DISABLE_PROXY
858 /* Send proxy authentication header if needed */
859 if(conn->bits.origin_is_proxy || is_connect) {
860 result = output_auth_headers(data, conn, authproxy, request,
861 path_and_query, TRUE);
862 if(result)
863 goto out;
864 }
865 else
866#else
867 (void)is_connect;
868#endif /* CURL_DISABLE_PROXY */
869 /* we have no proxy so let's pretend we are done authenticating
870 with it */
871 authproxy->done = TRUE;
872
873 /* Either we have credentials for the origin we talk to or
874 performing authentication is allowed here */
875 if(data->state.creds || Curl_auth_allowed_to_host(data))
876 result = output_auth_headers(data, conn, authhost, request,
877 path_and_query, FALSE);
878 else
879 authhost->done = TRUE;
880
881 if(((authhost->multipass && !authhost->done) ||
882 (authproxy->multipass && !authproxy->done)) &&
883 (httpreq != HTTPREQ_GET) &&
884 (httpreq != HTTPREQ_HEAD)) {
885 /* Auth is required and we are not authenticated yet. Make a PUT or POST
886 with content-length zero as a "probe". */
887 data->req.authneg = TRUE;
888 }
889 else
890 data->req.authneg = FALSE;
891
892out:
893 curlx_free(tmp_str);
894 return result;
895}
lib/http_negotiate.c@curl-8_21_0lines 52–153 · definition of Curl_input_negotiate as in release curl-8_21_0; differs from master; commits that changed it since: e1450d8fda tidy-up: use more `static`, `sizeof()`, `char[]`, double-const
52CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn,
53 bool proxy, const char *header)
54{
55 CURLcode result;
56 size_t len;
57
58 /* Point to credentials and host */
59 struct Curl_creds *creds = NULL;
60 const char *host;
61
62 /* Point to the correct struct with this */
63 struct negotiatedata *neg_ctx;
64 curlnegotiate state;
65
66 if(proxy) {
67#ifndef CURL_DISABLE_PROXY
68 creds = conn->http_proxy.creds;
69 host = conn->http_proxy.peer->hostname;
70 state = conn->proxy_negotiate_state;
71#else
72 return CURLE_NOT_BUILT_IN;
73#endif
74 }
75 else {
76 creds = data->state.creds;
77 host = data->state.origin->hostname;
78 state = conn->http_negotiate_state;
79 }
80
81 neg_ctx = Curl_auth_nego_get(conn, proxy);
82 if(!neg_ctx)
83 return CURLE_OUT_OF_MEMORY;
84
85 /* Obtain the input token, if any */
86 header += strlen("Negotiate");
87 curlx_str_passblanks(&header);
88
89 len = strlen(header);
90 neg_ctx->havenegdata = len != 0;
91 if(!len) {
92 if(state == GSS_AUTHSUCC) {
93 infof(data, "Negotiate auth restarted");
94 http_auth_nego_reset(conn, neg_ctx, proxy);
95 }
96 else if(state != GSS_AUTHNONE) {
97 /* The server rejected our authentication and has not supplied any more
98 negotiation mechanisms */
99 http_auth_nego_reset(conn, neg_ctx, proxy);
100 return CURLE_LOGIN_DENIED;
101 }
102 }
103
104 /* Supports SSL channel binding for Windows ISS extended protection */
105#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS)
106 neg_ctx->sslContext = conn->sslContext;
107#endif
108 /* Check if the connection is using SSL and get the channel binding data */
109#ifdef GSS_C_CHANNEL_BOUND_FLAG
110#ifdef USE_SSL
111 curlx_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE + 1);
112 if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
113 result = Curl_ssl_get_channel_binding(data, FIRSTSOCKET,
114 &neg_ctx->channel_binding_data);
115 if(result) {
116 http_auth_nego_reset(conn, neg_ctx, proxy);
117 return result;
118 }
119 }
120#else
121 curlx_dyn_init(&neg_ctx->channel_binding_data, 1);
122#endif /* USE_SSL */
123#endif /* GSS_C_CHANNEL_BOUND_FLAG */
124
125 /* Initialize the security context and decode our challenge */
126 result = Curl_auth_decode_spnego_message(data, creds, "HTTP",
127 host, header, neg_ctx);
128
129#ifdef GSS_C_CHANNEL_BOUND_FLAG
130 curlx_dyn_free(&neg_ctx->channel_binding_data);
131#endif
132
133 if(result)
134 http_auth_nego_reset(conn, neg_ctx, proxy);
135
136 if(!result && !proxy) {
137 /* Start it up. From this time onwards, the connection is tied
138 * tp the credentials used. */
139 if(conn->creds_origin &&
140 !Curl_peer_equal(conn->creds_origin, data->state.origin)) {
141 DEBUGASSERT(0); /* should not happen. */
142 return CURLE_FAILED_INIT;
143 }
144 if(conn->creds && !Curl_creds_same(creds, conn->creds)) {
145 DEBUGASSERT(0); /* should not happen. */
146 return CURLE_FAILED_INIT;
147 }
148 Curl_peer_link(&conn->creds_origin, data->state.origin);
C1: Curl_peer_link(&conn->creds_origin, data->state.origin) sets creds_origin while creds remains NULL
149 Curl_creds_link(&conn->creds, creds);
C1: Curl_creds_link(&conn->creds, creds) with creds=NULL leaves conn->creds NULL
150 }
151
152 return result;
153}
lib/url.c@curl-8_21_0lines 1069–1118 · definition of url_match_auth_nego as in release curl-8_21_0; differs from master; commits that changed it since: 1a17959fc7 url: fix negotiate/ntlm connection reuse
Show 50 lines (not cited by the judge)
1069static bool url_match_auth_nego(struct connectdata *conn,
1070 struct url_conn_match *m)
1071{
1072 /* If we are looking for an HTTP+Negotiate connection, check if this is
1073 already authenticating with the right credentials. If not, keep looking
1074 so that we can reuse Negotiate connections if possible. */
1075 if(m->want_nego_http) {
1076 if(conn->creds &&
1077 (!Curl_creds_same(conn->creds, m->data->state.creds) ||
1078 !Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
1079 return FALSE;
1080 }
1081 else if(conn->http_negotiate_state != GSS_AUTHNONE) {
1082 /* Connection is using Negotiate auth but we do not want Negotiate */
1083 return FALSE;
1084 }
1085
1086#ifndef CURL_DISABLE_PROXY
1087 /* Same for Proxy Negotiate authentication */
1088 if(m->want_proxy_nego_http) {
1089 /* Both conn->http_proxy.user and conn->http_proxy.passwd can be
1090 * NULL */
1091 if(!conn->http_proxy.creds)
1092 return FALSE;
1093
1094 if(!Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
1095 return FALSE;
1096 }
1097 else if(conn->proxy_negotiate_state != GSS_AUTHNONE) {
1098 /* Proxy connection is using Negotiate auth but we do not want Negotiate */
1099 return FALSE;
1100 }
1101#endif
1102 if(m->want_nego_http || m->want_proxy_nego_http) {
1103 /* Credentials are already checked, we may use this connection. We MUST
1104 * use a connection where it has already been fully negotiated. If it has
1105 * not, we keep on looking for a better one. */
1106 m->found = conn;
1107 if((m->want_nego_http &&
1108 (conn->http_negotiate_state != GSS_AUTHNONE)) ||
1109 (m->want_proxy_nego_http &&
1110 (conn->proxy_negotiate_state != GSS_AUTHNONE))) {
1111 /* We must use this connection, no other */
1112 m->force_reuse = TRUE;
1113 return TRUE;
1114 }
1115 return FALSE; /* get another */
1116 }
1117 return TRUE;
1118}
lib/url.clines 843–860 · definition of url_match_auth
Show 18 lines (not cited by the judge)
843static bool url_match_auth(struct connectdata *conn,
844 struct url_conn_match *m)
845{
846 if(!Curl_creds_same(m->needle->creds, conn->creds)) {
847 if(m->needle->creds)
848 return FALSE;
849 if(!Curl_creds_same(m->data->state.creds, conn->creds))
850 return FALSE;
851 }
852#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
853 /* GSS delegation differences do not actually affect every connection and
854 auth method, but this check takes precaution before efficiency */
855 if(m->needle->gssapi_delegation != conn->gssapi_delegation)
856 return FALSE;
857#endif
858
859 return TRUE;
860}
lib/url.clines 1046–1111 · definition of url_match_conn
Show 66 lines (not cited by the judge)
1046static bool url_match_conn(struct connectdata *conn, void *userdata)
1047{
1048 struct url_conn_match *m = userdata;
1049 /* Check if `conn` can be used for transfer `m->data` */
1050
1051 /* general connect config setting match? */
1052 if(!url_match_connect_config(conn, m))
1053 return FALSE;
1054
1055 /* match for destination and protocol? */
1056 if(!url_match_destination(conn, m))
1057 return FALSE;
1058
1059 if(!url_match_fully_connected(conn, m))
1060 return FALSE;
1061
1062 if(!url_match_multiplex_needs(conn, m))
1063 return FALSE;
1064
1065 if(!url_match_ssl_use(conn, m))
1066 return FALSE;
1067
1068 if(!url_match_proxy_use(conn, m))
1069 return FALSE;
1070 if(!url_match_ssl_config(conn, m))
1071 return FALSE;
1072
1073 if(!url_match_http_multiplex(conn, m))
1074 return FALSE;
1075 else if(m->wait_pipe)
1076 /* wait on multiplexing */
1077 return TRUE;
1078
1079 if(!url_match_auth(conn, m))
1080 return FALSE;
1081
1082 if(!url_match_proto_config(conn, m))
1083 return FALSE;
1084
1085 if(!url_match_auth_ntlm(conn, m))
1086 return FALSE;
1087 else if(m->force_reuse)
1088 return TRUE;
1089
1090 if(!url_match_auth_nego(conn, m))
1091 return FALSE;
1092 else if(m->force_reuse)
1093 return TRUE;
1094
1095 if(!url_match_multiplex_limits(conn, m))
1096 return FALSE;
1097
1098 /* If we are going to pick an idle connection, do an extra
1099 * health check before we reuse it. */
1100 if(!CONN_INUSE(conn) &&
1101 !Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) {
1102 infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating",
1103 conn->connection_id);
1104 Curl_conn_close(m->data, conn, FALSE);
1105 return FALSE;
1106 }
1107
1108 /* conn matches our needs. */
1109 m->found = conn;
1110 return TRUE;
1111}
lib/url.c@curl-8_21_0lines 1123–1184 · definition of url_match_conn as in release curl-8_21_0; differs from master; commits that changed it since: 3a0c600a1e tidy-up: fix comment indentation
Show 62 lines (not cited by the judge)
1123static bool url_match_conn(struct connectdata *conn, void *userdata)
1124{
1125 struct url_conn_match *m = userdata;
1126 /* Check if `conn` can be used for transfer `m->data` */
1127
1128 /* general connect config setting match? */
1129 if(!url_match_connect_config(conn, m))
1130 return FALSE;
1131
1132 /* match for destination and protocol? */
1133 if(!url_match_destination(conn, m))
1134 return FALSE;
1135
1136 if(!url_match_fully_connected(conn, m))
1137 return FALSE;
1138
1139 if(!url_match_multiplex_needs(conn, m))
1140 return FALSE;
1141
1142 if(!url_match_ssl_use(conn, m))
1143 return FALSE;
1144
1145 if(!url_match_proxy_use(conn, m))
1146 return FALSE;
1147 if(!url_match_ssl_config(conn, m))
1148 return FALSE;
1149
1150 if(!url_match_http_multiplex(conn, m))
1151 return FALSE;
1152 else if(m->wait_pipe)
1153 /* we decided to wait on PIPELINING */
1154 return TRUE;
1155
1156 if(!url_match_auth(conn, m))
1157 return FALSE;
1158
1159 if(!url_match_proto_config(conn, m))
1160 return FALSE;
1161
1162 if(!url_match_auth_ntlm(conn, m))
1163 return FALSE;
1164 else if(m->force_reuse)
1165 return TRUE;
1166
1167 if(!url_match_auth_nego(conn, m))
1168 return FALSE;
1169 else if(m->force_reuse)
1170 return TRUE;
1171
1172 if(!url_match_multiplex_limits(conn, m))
1173 return FALSE;
1174
1175 if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data)) {
1176 /* remove and disconnect. */
1177 Curl_conn_terminate(m->data, conn, FALSE);
1178 return FALSE;
1179 }
1180
1181 /* conn matches our needs. */
1182 m->found = conn;
1183 return TRUE;
1184}
include/curl/curl.h@curl-8_21_0lines 847–848 · definition of CURLAUTH_ANY as in release curl-8_21_0; differs from master
Show 2 lines (not cited by the judge)
847#define CURLAUTH_ANY ((~CURLAUTH_DIGEST_IE) & \
848 ((unsigned long)0xffffffff))
include/curl/curl.hlines 839–839 · definition of CURLAUTH_DIGEST_IE
Show 1 lines (not cited by the judge)
839#define CURLAUTH_DIGEST_IE (((unsigned long)1) << 4)
include/curl/curl.hlines 846–846 · definition of CURLAUTH_HTTPSIG
Show 1 lines (not cited by the judge)
846#define CURLAUTH_HTTPSIG (((unsigned long)1) << 8)
lib/creds.clines 130–140 · definition of Curl_creds_link
Show 11 lines (not cited by the judge)
130void Curl_creds_link(struct Curl_creds **pdest, struct Curl_creds *src)
131{
132 if(*pdest != src) {
133 Curl_creds_unlink(pdest);
134 *pdest = src;
135 if(src) {
136 DEBUGASSERT(src->refcount < UINT32_MAX);
137 src->refcount++;
138 }
139 }
140}
lib/creds.clines 158–167 · definition of Curl_creds_same
Show 10 lines (not cited by the judge)
158bool Curl_creds_same(struct Curl_creds *c1, struct Curl_creds *c2)
159{
160 return (c1 == c2) ||
161 (c1 && c2 &&
162 !Curl_timestrcmp(c1->user, c2->user) &&
163 !Curl_timestrcmp(c1->passwd, c2->passwd) &&
164 !Curl_timestrcmp(c1->oauth_bearer, c2->oauth_bearer) &&
165 !Curl_timestrcmp(c1->sasl_authzid, c2->sasl_authzid) &&
166 !Curl_timestrcmp(c1->sasl_service, c2->sasl_service));
167}
lib/http.c@curl-8_21_0lines 771–864 · definition of Curl_http_output_auth as in release curl-8_21_0; differs from master; commits that changed it since: a55731050e httpsig: add RFC 9421 HTTP Message Signatures support
Show 94 lines (not cited by the judge)
771CURLcode Curl_http_output_auth(struct Curl_easy *data,
772 struct connectdata *conn,
773 const char *request,
774 Curl_HttpReq httpreq,
775 const char *path,
776 const char *query,
777 bool is_connect)
778{
779 CURLcode result = CURLE_OK;
780 struct auth *authhost;
781 struct auth *authproxy;
782 const char *path_and_query = path;
783 char *tmp_str = NULL;
784
785 DEBUGASSERT(data);
786 authhost = &data->state.authhost;
787 authproxy = &data->state.authproxy;
788
789 if(
790#ifndef CURL_DISABLE_PROXY
791 (!conn->http_proxy.peer || !conn->http_proxy.creds) &&
792#endif
793#ifdef USE_SPNEGO
794 !(authhost->want & CURLAUTH_NEGOTIATE) &&
795 !(authproxy->want & CURLAUTH_NEGOTIATE) &&
796#endif
797 !data->state.creds) {
798 /* no authentication with no user or password */
799 authhost->done = TRUE;
800 authproxy->done = TRUE;
801 result = CURLE_OK;
802 goto out;
803 }
804
805 if(query) {
806 tmp_str = curl_maprintf("%s?%s", path, query);
807 if(!tmp_str) {
808 result = CURLE_OUT_OF_MEMORY;
809 goto out;
810 }
811 path_and_query = tmp_str;
812 }
813
814 if(authhost->want && !authhost->picked)
815 /* The app has selected one or more methods, but none has been picked
816 so far by a server round-trip. Then we set the picked one to the
817 want one, and if this is one single bit it will be used instantly. */
818 authhost->picked = authhost->want;
819
820 if(authproxy->want && !authproxy->picked)
821 /* The app has selected one or more methods, but none has been picked so
822 far by a proxy round-trip. Then we set the picked one to the want one,
823 and if this is one single bit it will be used instantly. */
824 authproxy->picked = authproxy->want;
825
826#ifndef CURL_DISABLE_PROXY
827 /* Send proxy authentication header if needed */
828 if(conn->bits.origin_is_proxy || is_connect) {
829 result = output_auth_headers(data, conn, authproxy, request,
830 path_and_query, TRUE);
831 if(result)
832 goto out;
833 }
834 else
835#else
836 (void)is_connect;
837#endif /* CURL_DISABLE_PROXY */
838 /* we have no proxy so let's pretend we are done authenticating
839 with it */
840 authproxy->done = TRUE;
841
842 /* Either we have credentials for the origin we talk to or
843 performing authentication is allowed here */
844 if(data->state.creds || Curl_auth_allowed_to_host(data))
845 result = output_auth_headers(data, conn, authhost, request,
846 path_and_query, FALSE);
847 else
848 authhost->done = TRUE;
849
850 if(((authhost->multipass && !authhost->done) ||
851 (authproxy->multipass && !authproxy->done)) &&
852 (httpreq != HTTPREQ_GET) &&
853 (httpreq != HTTPREQ_HEAD)) {
854 /* Auth is required and we are not authenticated yet. Make a PUT or POST
855 with content-length zero as a "probe". */
856 data->req.authneg = TRUE;
857 }
858 else
859 data->req.authneg = FALSE;
860
861out:
862 curlx_free(tmp_str);
863 return result;
864}
lib/http_negotiate.clines 52–153 · definition of Curl_input_negotiate
Show 102 lines (not cited by the judge)
52CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn,
53 bool proxy, const char *header)
54{
55 CURLcode result;
56 size_t len;
57
58 /* Point to credentials and host */
59 struct Curl_creds *creds = NULL;
60 const char *host;
61
62 /* Point to the correct struct with this */
63 struct negotiatedata *neg_ctx;
64 curlnegotiate state;
65
66 if(proxy) {
67#ifndef CURL_DISABLE_PROXY
68 creds = conn->http_proxy.creds;
69 host = conn->http_proxy.peer->hostname;
70 state = conn->proxy_negotiate_state;
71#else
72 return CURLE_NOT_BUILT_IN;
73#endif
74 }
75 else {
76 creds = data->state.creds;
77 host = data->state.origin->hostname;
78 state = conn->http_negotiate_state;
79 }
80
81 neg_ctx = Curl_auth_nego_get(conn, proxy);
82 if(!neg_ctx)
83 return CURLE_OUT_OF_MEMORY;
84
85 /* Obtain the input token, if any */
86 header += CURL_CSTRLEN("Negotiate");
87 curlx_str_passblanks(&header);
88
89 len = strlen(header);
90 neg_ctx->havenegdata = len != 0;
91 if(!len) {
92 if(state == GSS_AUTHSUCC) {
93 infof(data, "Negotiate auth restarted");
94 http_auth_nego_reset(conn, neg_ctx, proxy);
95 }
96 else if(state != GSS_AUTHNONE) {
97 /* The server rejected our authentication and has not supplied any more
98 negotiation mechanisms */
99 http_auth_nego_reset(conn, neg_ctx, proxy);
100 return CURLE_LOGIN_DENIED;
101 }
102 }
103
104 /* Supports SSL channel binding for Windows ISS extended protection */
105#if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS)
106 neg_ctx->sslContext = conn->sslContext;
107#endif
108 /* Check if the connection is using SSL and get the channel binding data */
109#ifdef GSS_C_CHANNEL_BOUND_FLAG
110#ifdef USE_SSL
111 curlx_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE + 1);
112 if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
113 result = Curl_ssl_get_channel_binding(data, FIRSTSOCKET,
114 &neg_ctx->channel_binding_data);
115 if(result) {
116 http_auth_nego_reset(conn, neg_ctx, proxy);
117 return result;
118 }
119 }
120#else
121 curlx_dyn_init(&neg_ctx->channel_binding_data, 1);
122#endif /* USE_SSL */
123#endif /* GSS_C_CHANNEL_BOUND_FLAG */
124
125 /* Initialize the security context and decode our challenge */
126 result = Curl_auth_decode_spnego_message(data, creds, "HTTP",
127 host, header, neg_ctx);
128
129#ifdef GSS_C_CHANNEL_BOUND_FLAG
130 curlx_dyn_free(&neg_ctx->channel_binding_data);
131#endif
132
133 if(result)
134 http_auth_nego_reset(conn, neg_ctx, proxy);
135
136 if(!result && !proxy) {
137 /* Start it up. From this time onwards, the connection is tied
138 * tp the credentials used. */
139 if(conn->creds_origin &&
140 !Curl_peer_equal(conn->creds_origin, data->state.origin)) {
141 DEBUGASSERT(0); /* should not happen. */
142 return CURLE_FAILED_INIT;
143 }
144 if(conn->creds && !Curl_creds_same(creds, conn->creds)) {
145 DEBUGASSERT(0); /* should not happen. */
146 return CURLE_FAILED_INIT;
147 }
148 Curl_peer_link(&conn->creds_origin, data->state.origin);
149 Curl_creds_link(&conn->creds, creds);
150 }
151
152 return result;
153}
lib/curl_setup.hlines 1052–1052 · definition of FALSE
Show 1 lines (not cited by the judge)
1052#define FALSE false
lib/curl_setup.hlines 1049–1049 · definition of TRUE
Show 1 lines (not cited by the judge)
1049#define TRUE true
lib/url.c@curl-8_21_0lines 1006–1063 · definition of url_match_auth_ntlm as in release curl-8_21_0; differs from master; commits that changed it since: aab0518d04 url: fix handling of empty user in ntml matching | 1a17959fc7 url: fix negotiate/ntlm connection reuse
Show 58 lines (not cited by the judge)
1006static bool url_match_auth_ntlm(struct connectdata *conn,
1007 struct url_conn_match *m)
1008{
1009 /* If we are looking for an HTTP+NTLM connection, check if this is
1010 already authenticating with the right credentials. If not, keep
1011 looking so that we can reuse NTLM connections if
1012 possible. (Especially we must not reuse the same connection if
1013 partway through a handshake!) */
1014 if(m->want_ntlm_http) {
1015 if(conn->creds &&
1016 (!Curl_creds_same(conn->creds, m->data->state.creds) ||
1017 !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) {
1018 /* connection credentials in play and not the same or not for the
1019 * same origin. */
1020 return FALSE;
1021 }
1022 }
1023 else if(conn->http_ntlm_state != NTLMSTATE_NONE) {
1024 /* Connection is using NTLM auth but we do not want NTLM */
1025 return FALSE;
1026 }
1027
1028#ifndef CURL_DISABLE_PROXY
1029 /* Same for Proxy NTLM authentication */
1030 if(m->want_proxy_ntlm_http) {
1031 /* Both conn->http_proxy.user and conn->http_proxy.passwd can be
1032 * NULL */
1033 if(!conn->http_proxy.creds)
1034 return FALSE;
1035
1036 if(!Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
1037 return FALSE;
1038 }
1039 else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) {
1040 /* Proxy connection is using NTLM auth but we do not want NTLM */
1041 return FALSE;
1042 }
1043#endif
1044 if(m->want_ntlm_http || m->want_proxy_ntlm_http) {
1045 /* Credentials are already checked, we may use this connection.
1046 * With NTLM being weird as it is, we MUST use a
1047 * connection where it has already been fully negotiated.
1048 * If it has not, we keep on looking for a better one. */
1049 m->found = conn;
1050
1051 if((m->want_ntlm_http &&
1052 (conn->http_ntlm_state != NTLMSTATE_NONE)) ||
1053 (m->want_proxy_ntlm_http &&
1054 (conn->proxy_ntlm_state != NTLMSTATE_NONE))) {
1055 /* We must use this connection, no other */
1056 m->force_reuse = TRUE;
1057 return TRUE;
1058 }
1059 /* Continue look up for a better connection */
1060 return FALSE;
1061 }
1062 return TRUE;
1063}
lib/url.clines 1113–1135 · definition of url_match_result
Show 23 lines (not cited by the judge)
1113static bool url_match_result(void *userdata)
1114{
1115 struct url_conn_match *match = userdata;
1116 if(match->found) {
1117 /* Attach it now while still under lock, so the connection does
1118 * no longer appear idle and can be reaped. */
1119 Curl_attach_connection(match->data, match->found, TRUE);
1120 return TRUE;
1121 }
1122 else if(match->seen_single_use_conn && !match->seen_multiplex_conn) {
1123 /* We have seen a single-use, existing connection to the destination and
1124 * no multiplexed one. It seems safe to assume that the server does
1125 * not support multiplexing. */
1126 match->wait_pipe = FALSE;
1127 }
1128 else if(match->seen_pending_conn && match->data->set.pipewait) {
1129 infof(match->data,
1130 "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set");
1131 match->wait_pipe = TRUE;
1132 }
1133 match->force_reuse = FALSE;
1134 return FALSE;
1135}
lib/url.c@curl-8_21_0lines 1186–1208 · definition of url_match_result as in release curl-8_21_0; differs from master; commits that changed it since: 7103a93b05 connections: use admin handles only for maintenance
Show 23 lines (not cited by the judge)
1186static bool url_match_result(void *userdata)
1187{
1188 struct url_conn_match *match = userdata;
1189 if(match->found) {
1190 /* Attach it now while still under lock, so the connection does
1191 * no longer appear idle and can be reaped. */
1192 Curl_attach_connection(match->data, match->found);
1193 return TRUE;
1194 }
1195 else if(match->seen_single_use_conn && !match->seen_multiplex_conn) {
1196 /* We have seen a single-use, existing connection to the destination and
1197 * no multiplexed one. It seems safe to assume that the server does
1198 * not support multiplexing. */
1199 match->wait_pipe = FALSE;
1200 }
1201 else if(match->seen_pending_conn && match->data->set.pipewait) {
1202 infof(match->data,
1203 "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set");
1204 match->wait_pipe = TRUE;
1205 }
1206 match->force_reuse = FALSE;
1207 return FALSE;
1208}