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