CVE-2026-87861: cross-origin reuse of a Negotiate connection
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
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.
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
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.
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.
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.
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.
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.
Read the original report (excerpt)
Source code
| 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 |
| 923 | static 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 |
| 987 | static 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 | } |
| 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)) |
| 799 | CURLcode 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 | |
| 892 | out: |
| 893 | curlx_free(tmp_str); |
| 894 | return result; |
| 895 | } |
| 52 | CURLcode 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 | } |
Show 50 lines (not cited by the judge)
| 1069 | static 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 | } |
Show 18 lines (not cited by the judge)
| 843 | static 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 | } |
Show 66 lines (not cited by the judge)
| 1046 | static 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 | } |
Show 62 lines (not cited by the judge)
| 1123 | static 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 | } |
Show 2 lines (not cited by the judge)
| 847 | #define CURLAUTH_ANY ((~CURLAUTH_DIGEST_IE) & \ |
| 848 | ((unsigned long)0xffffffff)) |
Show 1 lines (not cited by the judge)
| 839 | #define CURLAUTH_DIGEST_IE (((unsigned long)1) << 4) |
Show 1 lines (not cited by the judge)
| 846 | #define CURLAUTH_HTTPSIG (((unsigned long)1) << 8) |
Show 11 lines (not cited by the judge)
| 130 | void 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 | } |
Show 10 lines (not cited by the judge)
| 158 | bool 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 | } |
Show 94 lines (not cited by the judge)
| 771 | CURLcode 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 | |
| 861 | out: |
| 862 | curlx_free(tmp_str); |
| 863 | return result; |
| 864 | } |
Show 102 lines (not cited by the judge)
| 52 | CURLcode 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 | } |
Show 1 lines (not cited by the judge)
| 1052 | #define FALSE false |
Show 1 lines (not cited by the judge)
| 1049 | #define TRUE true |
Show 58 lines (not cited by the judge)
| 1006 | static 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 | } |
Show 23 lines (not cited by the judge)
| 1113 | static 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 | } |
Show 23 lines (not cited by the judge)
| 1186 | static 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 | } |