1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 * RFC4178 Simple and Protected GSS-API Negotiation Mechanism
24 *
25 ***************************************************************************/
26
27 #include "curl_setup.h"
28
29 #if defined(HAVE_GSSAPI) && defined(USE_SPNEGO)
30
31 #include <curl/curl.h>
32
33 #include "vauth/vauth.h"
34 #include "urldata.h"
35 #include "curl_base64.h"
36 #include "curl_gssapi.h"
37 #include "warnless.h"
38 #include "curl_multibyte.h"
39 #include "sendf.h"
40
41 /* The last #include files should be: */
42 #include "curl_memory.h"
43 #include "memdebug.h"
44
45 /*
46 * Curl_auth_is_spnego_supported()
47 *
48 * This is used to evaluate if SPNEGO (Negotiate) is supported.
49 *
50 * Parameters: None
51 *
52 * Returns TRUE if Negotiate supported by the GSS-API library.
53 */
Curl_auth_is_spnego_supported(void)54 bool Curl_auth_is_spnego_supported(void)
55 {
56 return TRUE;
57 }
58
59 /*
60 * Curl_auth_decode_spnego_message()
61 *
62 * This is used to decode an already encoded SPNEGO (Negotiate) challenge
63 * message.
64 *
65 * Parameters:
66 *
67 * data [in] - The session handle.
68 * userp [in] - The username in the format User or Domain\User.
69 * passwdp [in] - The user's password.
70 * service [in] - The service type such as http, smtp, pop or imap.
71 * host [in] - The hostname.
72 * chlg64 [in] - The optional base64 encoded challenge message.
73 * nego [in/out] - The Negotiate data struct being used and modified.
74 *
75 * Returns CURLE_OK on success.
76 */
Curl_auth_decode_spnego_message(struct Curl_easy * data,const char * user,const char * password,const char * service,const char * host,const char * chlg64,struct negotiatedata * nego)77 CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
78 const char *user,
79 const char *password,
80 const char *service,
81 const char *host,
82 const char *chlg64,
83 struct negotiatedata *nego)
84 {
85 CURLcode result = CURLE_OK;
86 size_t chlglen = 0;
87 unsigned char *chlg = NULL;
88 OM_uint32 major_status;
89 OM_uint32 minor_status;
90 OM_uint32 unused_status;
91 gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER;
92 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
93 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
94 gss_channel_bindings_t chan_bindings = GSS_C_NO_CHANNEL_BINDINGS;
95 struct gss_channel_bindings_struct chan;
96
97 (void) user;
98 (void) password;
99
100 if(nego->context && nego->status == GSS_S_COMPLETE) {
101 /* We finished successfully our part of authentication, but server
102 * rejected it (since we are again here). Exit with an error since we
103 * cannot invent anything better */
104 Curl_auth_cleanup_spnego(nego);
105 return CURLE_LOGIN_DENIED;
106 }
107
108 if(!nego->spn) {
109 /* Generate our SPN */
110 char *spn = Curl_auth_build_spn(service, NULL, host);
111 if(!spn)
112 return CURLE_OUT_OF_MEMORY;
113
114 /* Populate the SPN structure */
115 spn_token.value = spn;
116 spn_token.length = strlen(spn);
117
118 /* Import the SPN */
119 major_status = gss_import_name(&minor_status, &spn_token,
120 GSS_C_NT_HOSTBASED_SERVICE,
121 &nego->spn);
122 if(GSS_ERROR(major_status)) {
123 Curl_gss_log_error(data, "gss_import_name() failed: ",
124 major_status, minor_status);
125
126 free(spn);
127
128 return CURLE_AUTH_ERROR;
129 }
130
131 free(spn);
132 }
133
134 if(chlg64 && *chlg64) {
135 /* Decode the base-64 encoded challenge message */
136 if(*chlg64 != '=') {
137 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
138 if(result)
139 return result;
140 }
141
142 /* Ensure we have a valid challenge message */
143 if(!chlg) {
144 infof(data, "SPNEGO handshake failure (empty challenge message)");
145 return CURLE_BAD_CONTENT_ENCODING;
146 }
147
148 /* Setup the challenge "input" security buffer */
149 input_token.value = chlg;
150 input_token.length = chlglen;
151 }
152
153 /* Set channel binding data if available */
154 if(nego->channel_binding_data.leng > 0) {
155 memset(&chan, 0, sizeof(struct gss_channel_bindings_struct));
156 chan.application_data.length = nego->channel_binding_data.leng;
157 chan.application_data.value = nego->channel_binding_data.bufr;
158 chan_bindings = &chan;
159 }
160
161 /* Generate our challenge-response message */
162 major_status = Curl_gss_init_sec_context(data,
163 &minor_status,
164 &nego->context,
165 nego->spn,
166 &Curl_spnego_mech_oid,
167 chan_bindings,
168 &input_token,
169 &output_token,
170 TRUE,
171 NULL);
172
173 /* Free the decoded challenge as it is not required anymore */
174 Curl_safefree(input_token.value);
175
176 nego->status = major_status;
177 if(GSS_ERROR(major_status)) {
178 if(output_token.value)
179 gss_release_buffer(&unused_status, &output_token);
180
181 Curl_gss_log_error(data, "gss_init_sec_context() failed: ",
182 major_status, minor_status);
183
184 return CURLE_AUTH_ERROR;
185 }
186
187 if(!output_token.value || !output_token.length) {
188 if(output_token.value)
189 gss_release_buffer(&unused_status, &output_token);
190
191 return CURLE_AUTH_ERROR;
192 }
193
194 /* Free previous token */
195 if(nego->output_token.length && nego->output_token.value)
196 gss_release_buffer(&unused_status, &nego->output_token);
197
198 nego->output_token = output_token;
199
200 return CURLE_OK;
201 }
202
203 /*
204 * Curl_auth_create_spnego_message()
205 *
206 * This is used to generate an already encoded SPNEGO (Negotiate) response
207 * message ready for sending to the recipient.
208 *
209 * Parameters:
210 *
211 * data [in] - The session handle.
212 * nego [in/out] - The Negotiate data struct being used and modified.
213 * outptr [in/out] - The address where a pointer to newly allocated memory
214 * holding the result will be stored upon completion.
215 * outlen [out] - The length of the output message.
216 *
217 * Returns CURLE_OK on success.
218 */
Curl_auth_create_spnego_message(struct negotiatedata * nego,char ** outptr,size_t * outlen)219 CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego,
220 char **outptr, size_t *outlen)
221 {
222 CURLcode result;
223 OM_uint32 minor_status;
224
225 /* Base64 encode the already generated response */
226 result = Curl_base64_encode(nego->output_token.value,
227 nego->output_token.length,
228 outptr, outlen);
229
230 if(result) {
231 gss_release_buffer(&minor_status, &nego->output_token);
232 nego->output_token.value = NULL;
233 nego->output_token.length = 0;
234
235 return result;
236 }
237
238 if(!*outptr || !*outlen) {
239 gss_release_buffer(&minor_status, &nego->output_token);
240 nego->output_token.value = NULL;
241 nego->output_token.length = 0;
242
243 return CURLE_REMOTE_ACCESS_DENIED;
244 }
245
246 return CURLE_OK;
247 }
248
249 /*
250 * Curl_auth_cleanup_spnego()
251 *
252 * This is used to clean up the SPNEGO (Negotiate) specific data.
253 *
254 * Parameters:
255 *
256 * nego [in/out] - The Negotiate data struct being cleaned up.
257 *
258 */
Curl_auth_cleanup_spnego(struct negotiatedata * nego)259 void Curl_auth_cleanup_spnego(struct negotiatedata *nego)
260 {
261 OM_uint32 minor_status;
262
263 /* Free our security context */
264 if(nego->context != GSS_C_NO_CONTEXT) {
265 gss_delete_sec_context(&minor_status, &nego->context, GSS_C_NO_BUFFER);
266 nego->context = GSS_C_NO_CONTEXT;
267 }
268
269 /* Free the output token */
270 if(nego->output_token.value) {
271 gss_release_buffer(&minor_status, &nego->output_token);
272 nego->output_token.value = NULL;
273 nego->output_token.length = 0;
274
275 }
276
277 /* Free the SPN */
278 if(nego->spn != GSS_C_NO_NAME) {
279 gss_release_name(&minor_status, &nego->spn);
280 nego->spn = GSS_C_NO_NAME;
281 }
282
283 /* Reset any variables */
284 nego->status = 0;
285 nego->noauthpersist = FALSE;
286 nego->havenoauthpersist = FALSE;
287 nego->havenegdata = FALSE;
288 nego->havemultiplerequests = FALSE;
289 }
290
291 #endif /* HAVE_GSSAPI && USE_SPNEGO */
292