/src/gss-ntlmssp/src/gss_sec_ctx.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright 2013 Simo Sorce <simo@samba.org>, see COPYING for license */ |
2 | | |
3 | | #include <endian.h> |
4 | | #include <errno.h> |
5 | | #include <stdlib.h> |
6 | | #include <string.h> |
7 | | #include <time.h> |
8 | | |
9 | | #include "gssapi_ntlmssp.h" |
10 | | #include "gss_ntlmssp.h" |
11 | | |
12 | | uint32_t gssntlm_init_sec_context(uint32_t *minor_status, |
13 | | gss_cred_id_t claimant_cred_handle, |
14 | | gss_ctx_id_t *context_handle, |
15 | | gss_name_t target_name, |
16 | | gss_OID mech_type, |
17 | | uint32_t req_flags, |
18 | | uint32_t time_req, |
19 | | gss_channel_bindings_t input_chan_bindings, |
20 | | gss_buffer_t input_token, |
21 | | gss_OID *actual_mech_type, |
22 | | gss_buffer_t output_token, |
23 | | uint32_t *ret_flags, |
24 | | uint32_t *time_rec) |
25 | 0 | { |
26 | 0 | struct gssntlm_ctx *ctx; |
27 | 0 | struct gssntlm_name *server = NULL; |
28 | 0 | struct gssntlm_cred *cred = NULL; |
29 | 0 | char *nb_computer_name = NULL; |
30 | 0 | char *nb_domain_name = NULL; |
31 | 0 | struct gssntlm_name *client_name = NULL; |
32 | 0 | uint32_t in_flags; |
33 | 0 | uint32_t msg_type; |
34 | 0 | char *trgt_name = NULL; |
35 | 0 | struct ntlm_buffer challenge = { 0 }; |
36 | 0 | struct ntlm_buffer target_info = { 0 }; |
37 | 0 | int lm_compat_lvl; |
38 | 0 | uint32_t tmpmin; |
39 | 0 | uint32_t retmin = 0; |
40 | 0 | uint32_t retmaj = 0; |
41 | |
|
42 | 0 | ctx = (struct gssntlm_ctx *)(*context_handle); |
43 | | |
44 | | /* reset return values */ |
45 | 0 | if (actual_mech_type) *actual_mech_type = NULL; |
46 | 0 | if (ret_flags) *ret_flags = 0; |
47 | 0 | if (time_rec) *time_rec = 0; |
48 | |
|
49 | 0 | if (output_token == GSS_C_NO_BUFFER) { |
50 | 0 | return GSSERRS(0, GSS_S_CALL_INACCESSIBLE_WRITE); |
51 | 0 | } |
52 | | |
53 | 0 | if (target_name) { |
54 | 0 | server = (struct gssntlm_name *)target_name; |
55 | 0 | if (server->type != GSSNTLM_NAME_SERVER) { |
56 | 0 | return GSSERRS(ERR_NOSRVNAME, GSS_S_BAD_NAMETYPE); |
57 | 0 | } |
58 | 0 | if (!server->data.server.name || |
59 | 0 | !server->data.server.name[0]) { |
60 | 0 | return GSSERRS(ERR_NONAME, GSS_S_BAD_NAME); |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | 0 | if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) { |
65 | 0 | if (req_flags & GSS_C_ANON_FLAG) { |
66 | 0 | set_GSSERRS(ERR_NOARG, GSS_S_UNAVAILABLE); |
67 | 0 | goto done; |
68 | 0 | } else { |
69 | 0 | retmaj = gssntlm_acquire_cred(&retmin, |
70 | 0 | NULL, time_req, |
71 | 0 | NULL, GSS_C_INITIATE, |
72 | 0 | (gss_cred_id_t *)&cred, |
73 | 0 | NULL, time_rec); |
74 | 0 | if (retmaj) goto done; |
75 | 0 | } |
76 | 0 | } else { |
77 | 0 | cred = (struct gssntlm_cred *)claimant_cred_handle; |
78 | 0 | if (cred->type != GSSNTLM_CRED_USER && |
79 | 0 | cred->type != GSSNTLM_CRED_EXTERNAL) { |
80 | 0 | set_GSSERRS(ERR_NOARG, GSS_S_CRED_UNAVAIL); |
81 | 0 | goto done; |
82 | 0 | } |
83 | 0 | if (cred->type == GSSNTLM_CRED_EXTERNAL && |
84 | 0 | cred->cred.external.creds_in_cache == 0) { |
85 | 0 | set_GSSERRS(ERR_NOARG, GSS_S_CRED_UNAVAIL); |
86 | 0 | goto done; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | 0 | if (ctx == NULL) { |
91 | | |
92 | | /* first call */ |
93 | 0 | ctx = calloc(1, sizeof(struct gssntlm_ctx)); |
94 | 0 | if (!ctx) { |
95 | 0 | set_GSSERR(ENOMEM); |
96 | 0 | goto done; |
97 | 0 | } |
98 | | |
99 | 0 | ctx->external_context = external_get_context(); |
100 | |
|
101 | 0 | retmin = gssntlm_copy_name(&cred->cred.user.user, |
102 | 0 | &ctx->source_name); |
103 | 0 | if (retmin) { |
104 | 0 | set_GSSERR(retmin); |
105 | 0 | goto done; |
106 | 0 | } |
107 | | |
108 | 0 | if (server) { |
109 | 0 | retmin = gssntlm_copy_name(server, &ctx->target_name); |
110 | 0 | if (retmin) { |
111 | 0 | set_GSSERR(retmin); |
112 | 0 | goto done; |
113 | 0 | } |
114 | 0 | } |
115 | | |
116 | 0 | ctx->gss_flags = req_flags; |
117 | |
|
118 | 0 | ctx->neg_flags = NTLMSSP_DEFAULT_CLIENT_FLAGS; |
119 | | /* override neg_flags default if requested */ |
120 | 0 | if (cred->neg_flags) { |
121 | 0 | ctx->neg_flags = cred->neg_flags; |
122 | 0 | } |
123 | | |
124 | | /* |
125 | | * we ignore unsupported flags for now |
126 | | * |
127 | | * GSS_C_DELEG_FLAG |
128 | | * GSS_C_MUTUAL_FLAG |
129 | | * GSS_C_PROT_READY_FLAG |
130 | | * GSS_C_TRANS_FLAG |
131 | | * GSS_C_DELEG_POLICY_FLAG |
132 | | * GSS_C_DCE_STYLE |
133 | | * GSS_C_EXTENDED_ERROR_FLAG |
134 | | */ |
135 | 0 | if ((req_flags & GSS_C_INTEG_FLAG) || |
136 | 0 | (req_flags & GSS_C_REPLAY_FLAG) || |
137 | 0 | (req_flags & GSS_C_SEQUENCE_FLAG)) { |
138 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_SIGN | |
139 | 0 | NTLMSSP_NEGOTIATE_KEY_EXCH; |
140 | 0 | } |
141 | 0 | if (req_flags & GSS_C_CONF_FLAG) { |
142 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_SEAL | |
143 | 0 | NTLMSSP_NEGOTIATE_KEY_EXCH | |
144 | 0 | NTLMSSP_NEGOTIATE_LM_KEY | |
145 | 0 | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY; |
146 | 0 | } |
147 | 0 | if (req_flags & GSS_C_ANON_FLAG) { |
148 | 0 | ctx->neg_flags |= NTLMSSP_ANONYMOUS; |
149 | 0 | } |
150 | 0 | if (req_flags & GSS_C_IDENTIFY_FLAG) { |
151 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_IDENTIFY; |
152 | 0 | } |
153 | 0 | if (req_flags & GSS_C_DATAGRAM_FLAG) { |
154 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_DATAGRAM | |
155 | 0 | NTLMSSP_NEGOTIATE_KEY_EXCH; |
156 | 0 | } |
157 | | |
158 | | /* acquire our own name */ |
159 | 0 | if (!client_name) { |
160 | 0 | gss_buffer_desc tmpbuf; |
161 | 0 | tmpbuf.value = discard_const(""); |
162 | 0 | tmpbuf.length = 0; |
163 | 0 | retmaj = gssntlm_import_name_by_mech(&retmin, |
164 | 0 | &gssntlm_oid, |
165 | 0 | &tmpbuf, |
166 | 0 | GSS_C_NT_HOSTBASED_SERVICE, |
167 | 0 | (gss_name_t *)&client_name); |
168 | 0 | if (retmaj) goto done; |
169 | 0 | } |
170 | | |
171 | 0 | retmin = netbios_get_names(ctx->external_context, |
172 | 0 | client_name->data.server.name, |
173 | 0 | &nb_computer_name, &nb_domain_name); |
174 | 0 | if (retmin) { |
175 | 0 | set_GSSERR(retmin); |
176 | 0 | goto done; |
177 | 0 | } |
178 | | |
179 | 0 | ctx->workstation = strdup(nb_computer_name); |
180 | 0 | if (!ctx->workstation) { |
181 | 0 | set_GSSERR(ENOMEM); |
182 | 0 | goto done; |
183 | 0 | } |
184 | | |
185 | 0 | gssntlm_set_role(ctx, GSSNTLM_CLIENT, nb_domain_name); |
186 | |
|
187 | 0 | lm_compat_lvl = gssntlm_get_lm_compatibility_level(); |
188 | 0 | if (!gssntlm_required_security(lm_compat_lvl, ctx)) { |
189 | 0 | set_GSSERR(ERR_BADLMLVL); |
190 | 0 | goto done; |
191 | 0 | } |
192 | 0 | if (!gssntlm_sec_lm_ok(ctx)) { |
193 | 0 | ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; |
194 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY; |
195 | 0 | } |
196 | 0 | if (!gssntlm_ext_sec_ok(ctx)) { |
197 | 0 | ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY; |
198 | 0 | } |
199 | |
|
200 | 0 | retmin = ntlm_init_ctx(&ctx->ntlm); |
201 | 0 | if (retmin) { |
202 | 0 | set_GSSERR(retmin); |
203 | 0 | goto done; |
204 | 0 | } |
205 | | |
206 | | /* only in connectionless mode we may receive an input buffer |
207 | | * on the the first call, if DATAGRAM is not selected and |
208 | | * we have a buffer here, somethings wrong */ |
209 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM) { |
210 | |
|
211 | 0 | if ((input_token == GSS_C_NO_BUFFER) || |
212 | 0 | (input_token->length == 0)) { |
213 | | /* in connectionless mode we return an empty buffer here: |
214 | | * see MS-NLMP 1.3.1.3 and 1.7 */ |
215 | 0 | output_token->value = NULL; |
216 | 0 | output_token->length = 0; |
217 | | |
218 | | /* and return the ball */ |
219 | 0 | ctx->stage = NTLMSSP_STAGE_NEGOTIATE; |
220 | 0 | set_GSSERRS(0, GSS_S_CONTINUE_NEEDED); |
221 | 0 | goto done; |
222 | 0 | } |
223 | 0 | } else { |
224 | |
|
225 | 0 | if (input_token && input_token->length != 0) { |
226 | 0 | set_GSSERRS(ERR_BADARG, GSS_S_DEFECTIVE_TOKEN); |
227 | 0 | goto done; |
228 | 0 | } |
229 | | |
230 | 0 | retmin = ntlm_encode_neg_msg(ctx->ntlm, ctx->neg_flags, |
231 | 0 | NULL, NULL, &ctx->nego_msg); |
232 | 0 | if (retmin) { |
233 | 0 | set_GSSERR(retmin); |
234 | 0 | goto done; |
235 | 0 | } |
236 | | |
237 | 0 | output_token->value = malloc(ctx->nego_msg.length); |
238 | 0 | if (!output_token->value) { |
239 | 0 | set_GSSERR(ENOMEM); |
240 | 0 | goto done; |
241 | 0 | } |
242 | 0 | memcpy(output_token->value, ctx->nego_msg.data, ctx->nego_msg.length); |
243 | 0 | output_token->length = ctx->nego_msg.length; |
244 | |
|
245 | 0 | ctx->stage = NTLMSSP_STAGE_NEGOTIATE; |
246 | 0 | set_GSSERRS(0, GSS_S_CONTINUE_NEEDED); |
247 | 0 | goto done; |
248 | 0 | } |
249 | | |
250 | | /* If we get here we are in connectionless mode and where called |
251 | | * with a chalenge message in the input buffer */ |
252 | 0 | ctx->stage = NTLMSSP_STAGE_NEGOTIATE; |
253 | 0 | } |
254 | | |
255 | 0 | if (ctx == NULL) { |
256 | | /* this should not happen */ |
257 | 0 | set_GSSERR(ERR_IMPOSSIBLE); |
258 | 0 | goto done; |
259 | |
|
260 | 0 | } else { |
261 | |
|
262 | 0 | if (!gssntlm_role_is_client(ctx)) { |
263 | 0 | set_GSSERRS(ERR_WRONGCTX, GSS_S_NO_CONTEXT); |
264 | 0 | goto done; |
265 | 0 | } |
266 | | |
267 | 0 | ctx->chal_msg.data = malloc(input_token->length); |
268 | 0 | if (!ctx->chal_msg.data) { |
269 | 0 | set_GSSERR(ENOMEM); |
270 | 0 | goto done; |
271 | 0 | } |
272 | 0 | memcpy(ctx->chal_msg.data, input_token->value, input_token->length); |
273 | 0 | ctx->chal_msg.length = input_token->length; |
274 | |
|
275 | 0 | retmin = ntlm_decode_msg_type(ctx->ntlm, &ctx->chal_msg, &msg_type); |
276 | 0 | if (retmin) { |
277 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
278 | 0 | goto done; |
279 | 0 | } |
280 | | |
281 | 0 | if (msg_type != CHALLENGE_MESSAGE || |
282 | 0 | ctx->stage != NTLMSSP_STAGE_NEGOTIATE) { |
283 | 0 | set_GSSERRS(ERR_WRONGMSG, GSS_S_NO_CONTEXT); |
284 | 0 | goto done; |
285 | 0 | } |
286 | | |
287 | | /* store challenge in ctx */ |
288 | 0 | challenge.data = ctx->server_chal; |
289 | 0 | challenge.length = 8; |
290 | 0 | retmin = ntlm_decode_chal_msg(ctx->ntlm, &ctx->chal_msg, &in_flags, |
291 | 0 | &trgt_name, &challenge, &target_info); |
292 | 0 | if (retmin) { |
293 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
294 | 0 | goto done; |
295 | 0 | } |
296 | | |
297 | | /* mask unacceptable flags */ |
298 | 0 | if (!gssntlm_sec_lm_ok(ctx)) { |
299 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; |
300 | 0 | } |
301 | 0 | if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_56)) { |
302 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_56; |
303 | 0 | } |
304 | 0 | if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_128)) { |
305 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_128; |
306 | 0 | } |
307 | 0 | if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { |
308 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH; |
309 | 0 | } |
310 | 0 | if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_OEM)) { |
311 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_OEM; |
312 | 0 | } |
313 | 0 | if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_UNICODE)) { |
314 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; |
315 | 0 | } |
316 | | |
317 | | /* check required flags */ |
318 | 0 | if ((ctx->neg_flags & NTLMSSP_NEGOTIATE_128) && |
319 | 0 | (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_56)) && |
320 | 0 | (!(in_flags & NTLMSSP_NEGOTIATE_128))) { |
321 | 0 | set_GSSERR(ERR_REQNEGFLAG); |
322 | 0 | goto done; |
323 | 0 | } |
324 | 0 | if ((ctx->neg_flags & NTLMSSP_NEGOTIATE_SEAL) && |
325 | 0 | (!(in_flags & NTLMSSP_NEGOTIATE_SEAL))) { |
326 | 0 | set_GSSERR(ERR_REQNEGFLAG); |
327 | 0 | goto done; |
328 | 0 | } |
329 | 0 | if ((ctx->neg_flags & NTLMSSP_NEGOTIATE_SIGN) && |
330 | 0 | (!(in_flags & NTLMSSP_NEGOTIATE_SIGN))) { |
331 | 0 | set_GSSERR(ERR_REQNEGFLAG); |
332 | 0 | goto done; |
333 | 0 | } |
334 | | |
335 | 0 | if (!(in_flags & (NTLMSSP_NEGOTIATE_OEM | |
336 | 0 | NTLMSSP_NEGOTIATE_UNICODE))) { |
337 | | /* no common understanding */ |
338 | 0 | set_GSSERR(ERR_FAILNEGFLAGS); |
339 | 0 | goto done; |
340 | 0 | } |
341 | | |
342 | 0 | if (ctx->gss_flags & GSS_C_DATAGRAM_FLAG) { |
343 | 0 | if (!(in_flags & NTLMSSP_NEGOTIATE_DATAGRAM)) { |
344 | | /* no common understanding */ |
345 | 0 | set_GSSERR(ERR_FAILNEGFLAGS); |
346 | 0 | goto done; |
347 | 0 | } |
348 | 0 | if (!(in_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { |
349 | | /* no common understanding */ |
350 | 0 | set_GSSERR(ERR_FAILNEGFLAGS); |
351 | 0 | goto done; |
352 | 0 | } |
353 | 0 | if ((in_flags & NTLMSSP_NEGOTIATE_OEM) && |
354 | 0 | (in_flags & NTLMSSP_NEGOTIATE_UNICODE)) { |
355 | | /* prefer Unicode */ |
356 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_OEM; |
357 | 0 | } |
358 | 0 | } else { |
359 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_DATAGRAM; |
360 | |
|
361 | 0 | if ((in_flags & NTLMSSP_NEGOTIATE_OEM) && |
362 | 0 | (in_flags & NTLMSSP_NEGOTIATE_UNICODE)) { |
363 | | /* server sent both?? This is broken, proceed only if there |
364 | | * are no strings set in the challenge packet and downgrade |
365 | | * to OEM charset hoping the server will cope */ |
366 | 0 | if (in_flags & (NTLMSSP_NEGOTIATE_TARGET_INFO | |
367 | 0 | NTLMSSP_TARGET_TYPE_SERVER | |
368 | 0 | NTLMSSP_TARGET_TYPE_DOMAIN)) { |
369 | 0 | set_GSSERR(ERR_BADNEGFLAGS); |
370 | 0 | goto done; |
371 | 0 | } else { |
372 | 0 | in_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | | /* Now that everything has been checked clear non |
378 | | * negotiated flags */ |
379 | 0 | ctx->neg_flags &= in_flags; |
380 | |
|
381 | 0 | retmaj = gssntlm_cli_auth(&retmin, ctx, cred, &target_info, |
382 | 0 | in_flags, input_chan_bindings); |
383 | 0 | if (retmaj) goto done; |
384 | | |
385 | 0 | if (in_flags & (NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL)) { |
386 | 0 | retmin = ntlm_signseal_keys(in_flags, true, |
387 | 0 | &ctx->exported_session_key, |
388 | 0 | &ctx->crypto_state); |
389 | 0 | if (retmin) { |
390 | 0 | set_GSSERR(retmin); |
391 | 0 | goto done; |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { |
396 | 0 | ctx->gss_flags |= GSS_C_INTEG_FLAG; |
397 | 0 | } |
398 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { |
399 | 0 | ctx->gss_flags |= GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; |
400 | 0 | } |
401 | |
|
402 | 0 | ctx->stage = NTLMSSP_STAGE_DONE; |
403 | |
|
404 | 0 | output_token->value = malloc(ctx->auth_msg.length); |
405 | 0 | if (!output_token->value) { |
406 | 0 | set_GSSERR(ENOMEM); |
407 | 0 | goto done; |
408 | 0 | } |
409 | 0 | memcpy(output_token->value, ctx->auth_msg.data, ctx->auth_msg.length); |
410 | 0 | output_token->length = ctx->auth_msg.length; |
411 | | |
412 | | /* For now use the same as the challenge/response lifetime (36h) */ |
413 | 0 | ctx->expiration_time = time(NULL) + MAX_CHALRESP_LIFETIME; |
414 | 0 | ctx->int_flags |= NTLMSSP_CTX_FLAG_ESTABLISHED; |
415 | |
|
416 | 0 | set_GSSERRS(0, GSS_S_COMPLETE); |
417 | 0 | } |
418 | | |
419 | 0 | done: |
420 | 0 | if ((retmaj != GSS_S_COMPLETE) && |
421 | 0 | (retmaj != GSS_S_CONTINUE_NEEDED)) { |
422 | 0 | gssntlm_delete_sec_context(&tmpmin, (gss_ctx_id_t *)&ctx, NULL); |
423 | 0 | } else { |
424 | 0 | if (actual_mech_type) *actual_mech_type = discard_const(&gssntlm_oid); |
425 | 0 | if (ret_flags) *ret_flags = ctx->gss_flags; |
426 | 0 | if (time_rec) *time_rec = GSS_C_INDEFINITE; |
427 | 0 | } |
428 | 0 | *context_handle = (gss_ctx_id_t)ctx; |
429 | 0 | if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) { |
430 | | /* do not leak it, if not passed in */ |
431 | 0 | gssntlm_release_cred(&tmpmin, (gss_cred_id_t *)&cred); |
432 | 0 | } |
433 | 0 | gssntlm_release_name(&tmpmin, (gss_name_t *)&client_name); |
434 | 0 | safefree(nb_computer_name); |
435 | 0 | safefree(nb_domain_name); |
436 | 0 | safefree(trgt_name); |
437 | 0 | ntlm_free_buffer_data(&target_info); |
438 | |
|
439 | 0 | return GSSERR(); |
440 | 0 | } |
441 | | |
442 | | uint32_t gssntlm_delete_sec_context(uint32_t *minor_status, |
443 | | gss_ctx_id_t *context_handle, |
444 | | gss_buffer_t output_token) |
445 | 2.29k | { |
446 | 2.29k | struct gssntlm_ctx *ctx; |
447 | 2.29k | uint32_t retmin; |
448 | 2.29k | uint32_t retmaj; |
449 | 2.29k | int ret; |
450 | | |
451 | 2.29k | if (!context_handle) { |
452 | 0 | set_GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
453 | 0 | goto done; |
454 | 0 | } |
455 | 2.29k | if (*context_handle == NULL) { |
456 | 1.14k | set_GSSERRS(ERR_NOARG, GSS_S_NO_CONTEXT); |
457 | 1.14k | goto done; |
458 | 1.14k | } |
459 | | |
460 | 1.14k | ctx = (struct gssntlm_ctx *)*context_handle; |
461 | | |
462 | 1.14k | safefree(ctx->workstation); |
463 | | |
464 | 1.14k | ret = ntlm_free_ctx(&ctx->ntlm); |
465 | | |
466 | 1.14k | safefree(ctx->nego_msg.data); |
467 | 1.14k | safefree(ctx->chal_msg.data); |
468 | 1.14k | safefree(ctx->auth_msg.data); |
469 | 1.14k | ctx->nego_msg.length = 0; |
470 | 1.14k | ctx->chal_msg.length = 0; |
471 | 1.14k | ctx->auth_msg.length = 0; |
472 | | |
473 | 1.14k | gssntlm_int_release_name(&ctx->source_name); |
474 | 1.14k | gssntlm_int_release_name(&ctx->target_name); |
475 | | |
476 | 1.14k | ntlm_release_rc4_state(&ctx->crypto_state); |
477 | | |
478 | 1.14k | external_free_context(ctx->external_context); |
479 | | |
480 | 1.14k | safezero((uint8_t *)ctx, sizeof(struct gssntlm_ctx)); |
481 | 1.14k | safefree(*context_handle); |
482 | | |
483 | 1.14k | set_GSSERRS(ret, ret ? GSS_S_FAILURE : GSS_S_COMPLETE); |
484 | 2.29k | done: |
485 | 2.29k | return GSSERR(); |
486 | 1.14k | } |
487 | | |
488 | | uint32_t gssntlm_context_time(uint32_t *minor_status, |
489 | | gss_ctx_id_t context_handle, |
490 | | uint32_t *time_rec) |
491 | 0 | { |
492 | 0 | struct gssntlm_ctx *ctx; |
493 | 0 | time_t now; |
494 | 0 | uint32_t retmin; |
495 | 0 | uint32_t retmaj; |
496 | |
|
497 | 0 | if (context_handle == GSS_C_NO_CONTEXT) { |
498 | 0 | set_GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
499 | 0 | goto done; |
500 | 0 | } |
501 | | |
502 | 0 | ctx = (struct gssntlm_ctx *)context_handle; |
503 | 0 | retmaj = gssntlm_context_is_valid(ctx, &now); |
504 | 0 | if (retmaj) { |
505 | 0 | set_GSSERRS(ERR_BADCTX, retmaj); |
506 | 0 | goto done; |
507 | 0 | } |
508 | | |
509 | 0 | *time_rec = ctx->expiration_time - now; |
510 | 0 | set_GSSERRS(0, GSS_S_COMPLETE); |
511 | 0 | done: |
512 | 0 | return GSSERR(); |
513 | 0 | } |
514 | | |
515 | | uint32_t gssntlm_accept_sec_context(uint32_t *minor_status, |
516 | | gss_ctx_id_t *context_handle, |
517 | | gss_cred_id_t acceptor_cred_handle, |
518 | | gss_buffer_t input_token, |
519 | | gss_channel_bindings_t input_chan_bindings, |
520 | | gss_name_t *src_name, |
521 | | gss_OID *mech_type, |
522 | | gss_buffer_t output_token, |
523 | | uint32_t *ret_flags, |
524 | | uint32_t *time_rec, |
525 | | gss_cred_id_t *delegated_cred_handle) |
526 | 2.08k | { |
527 | 2.08k | struct gssntlm_ctx *ctx; |
528 | 2.08k | struct gssntlm_cred *cred = NULL; |
529 | 2.08k | int lm_compat_lvl = -1; |
530 | 2.08k | struct ntlm_buffer challenge = { 0 }; |
531 | 2.08k | struct gssntlm_name *server_name = NULL; |
532 | 2.08k | char *nb_computer_name = NULL; |
533 | 2.08k | char *nb_domain_name = NULL; |
534 | 2.08k | char *chal_target_name; |
535 | 2.08k | uint64_t timestamp; |
536 | 2.08k | struct ntlm_buffer target_info = { 0 }; |
537 | 2.08k | struct ntlm_buffer nt_chal_resp = { 0 }; |
538 | 2.08k | struct ntlm_buffer lm_chal_resp = { 0 }; |
539 | 2.08k | struct ntlm_buffer enc_sess_key = { 0 }; |
540 | 2.08k | struct ntlm_key encrypted_random_session_key = { .length = 16 }; |
541 | 2.08k | struct ntlm_key key_exchange_key = { .length = 16 }; |
542 | 2.08k | uint8_t micbuf[16]; |
543 | 2.08k | struct ntlm_buffer mic = { micbuf, 16 }; |
544 | 2.08k | char *dom_name = NULL; |
545 | 2.08k | char *usr_name = NULL; |
546 | 2.08k | char *wks_name = NULL; |
547 | 2.08k | struct gssntlm_name *gss_usrname = NULL; |
548 | 2.08k | struct gssntlm_cred *usr_cred = NULL; |
549 | 2.08k | uint32_t retmin; |
550 | 2.08k | uint32_t retmaj; |
551 | 2.08k | uint32_t tmpmin; |
552 | 2.08k | uint32_t in_flags; |
553 | 2.08k | uint32_t msg_type; |
554 | 2.08k | uint32_t av_flags = 0; |
555 | 2.08k | struct ntlm_buffer unhashed_cb = { 0 }; |
556 | 2.08k | struct ntlm_buffer av_cb = { 0 }; |
557 | | |
558 | 2.08k | if (context_handle == NULL) { |
559 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
560 | 0 | } |
561 | 2.08k | if (output_token == GSS_C_NO_BUFFER) { |
562 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_WRITE); |
563 | 0 | } |
564 | | |
565 | 2.08k | if (src_name) *src_name = GSS_C_NO_NAME; |
566 | 2.08k | if (mech_type) *mech_type = GSS_C_NO_OID; |
567 | 2.08k | if (ret_flags) *ret_flags = 0; |
568 | 2.08k | if (time_rec) *time_rec = 0; |
569 | 2.08k | if (delegated_cred_handle) *delegated_cred_handle = GSS_C_NO_CREDENTIAL; |
570 | | |
571 | 2.08k | if (acceptor_cred_handle) { |
572 | 0 | cred = (struct gssntlm_cred *)acceptor_cred_handle; |
573 | 0 | if (cred->type != GSSNTLM_CRED_SERVER) { |
574 | 0 | set_GSSERRS(ERR_NOSRVCRED, GSS_S_DEFECTIVE_CREDENTIAL); |
575 | 0 | goto done; |
576 | 0 | } |
577 | 0 | if (cred->cred.server.name.type != GSSNTLM_NAME_SERVER) { |
578 | 0 | set_GSSERRS(ERR_NOSRVNAME, GSS_S_DEFECTIVE_CREDENTIAL); |
579 | 0 | goto done; |
580 | 0 | } |
581 | 0 | retmaj = gssntlm_duplicate_name(&retmin, |
582 | 0 | (const gss_name_t)&cred->cred.server.name, |
583 | 0 | (gss_name_t *)&server_name); |
584 | 0 | if (retmaj) goto done; |
585 | 0 | } |
586 | | |
587 | 2.08k | if (*context_handle == GSS_C_NO_CONTEXT) { |
588 | | |
589 | | /* first call */ |
590 | 1.14k | ctx = calloc(1, sizeof(struct gssntlm_ctx)); |
591 | 1.14k | if (!ctx) { |
592 | 0 | set_GSSERR(ENOMEM); |
593 | 0 | goto done; |
594 | 0 | } |
595 | | |
596 | 1.14k | ctx->external_context = external_get_context(); |
597 | | |
598 | | /* acquire our own name */ |
599 | 1.14k | if (!server_name) { |
600 | 1.14k | gss_buffer_desc tmpbuf; |
601 | 1.14k | tmpbuf.value = discard_const(""); |
602 | 1.14k | tmpbuf.length = 0; |
603 | 1.14k | retmaj = gssntlm_import_name_by_mech(&retmin, |
604 | 1.14k | &gssntlm_oid, |
605 | 1.14k | &tmpbuf, |
606 | 1.14k | GSS_C_NT_HOSTBASED_SERVICE, |
607 | 1.14k | (gss_name_t *)&server_name); |
608 | 1.14k | if (retmaj) goto done; |
609 | 1.14k | } |
610 | | |
611 | 1.14k | retmin = gssntlm_copy_name(server_name, &ctx->target_name); |
612 | 1.14k | if (retmin) { |
613 | 0 | set_GSSERR(retmin); |
614 | 0 | goto done; |
615 | 0 | } |
616 | | |
617 | 1.14k | retmin = netbios_get_names(ctx->external_context, |
618 | 1.14k | server_name->data.server.name, |
619 | 1.14k | &nb_computer_name, &nb_domain_name); |
620 | 1.14k | if (retmin) { |
621 | 0 | set_GSSERR(retmin); |
622 | 0 | goto done; |
623 | 0 | } |
624 | | |
625 | 1.14k | ctx->workstation = strdup(nb_computer_name); |
626 | 1.14k | if (!ctx->workstation) { |
627 | 0 | set_GSSERR(ENOMEM); |
628 | 0 | goto done; |
629 | 0 | } |
630 | | |
631 | 1.14k | gssntlm_set_role(ctx, GSSNTLM_SERVER, nb_domain_name); |
632 | | |
633 | 1.14k | lm_compat_lvl = gssntlm_get_lm_compatibility_level(); |
634 | 1.14k | if (!gssntlm_required_security(lm_compat_lvl, ctx)) { |
635 | 0 | set_GSSERR(ERR_BADLMLVL); |
636 | 0 | goto done; |
637 | 0 | } |
638 | | |
639 | 1.14k | ctx->neg_flags = NTLMSSP_DEFAULT_SERVER_FLAGS; |
640 | | /* Fixme: How do we allow anonymous negotition ? */ |
641 | | |
642 | | /* override neg_flags default if requested */ |
643 | 1.14k | if (cred && cred->neg_flags) { |
644 | 0 | ctx->neg_flags = cred->neg_flags; |
645 | 0 | } |
646 | | |
647 | 1.14k | if (gssntlm_sec_lm_ok(ctx)) { |
648 | 0 | ctx->neg_flags |= NTLMSSP_REQUEST_NON_NT_SESSION_KEY; |
649 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; |
650 | 0 | } |
651 | 1.14k | if (gssntlm_ext_sec_ok(ctx)) { |
652 | 1.14k | ctx->neg_flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY; |
653 | 1.14k | } |
654 | | |
655 | 1.14k | retmin = ntlm_init_ctx(&ctx->ntlm); |
656 | 1.14k | if (retmin) { |
657 | 0 | set_GSSERR(retmin); |
658 | 0 | goto done; |
659 | 0 | } |
660 | | |
661 | 1.14k | if (input_token && input_token->length != 0) { |
662 | 1.14k | ctx->nego_msg.data = malloc(input_token->length); |
663 | 1.14k | if (!ctx->nego_msg.data) { |
664 | 0 | set_GSSERR(ENOMEM); |
665 | 0 | goto done; |
666 | 0 | } |
667 | 1.14k | memcpy(ctx->nego_msg.data, input_token->value, input_token->length); |
668 | 1.14k | ctx->nego_msg.length = input_token->length; |
669 | | |
670 | 1.14k | retmin = ntlm_decode_msg_type(ctx->ntlm, &ctx->nego_msg, &msg_type); |
671 | 1.14k | if (retmin || (msg_type != NEGOTIATE_MESSAGE)) { |
672 | 185 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
673 | 185 | goto done; |
674 | 185 | } |
675 | | |
676 | 961 | retmin = ntlm_decode_neg_msg(ctx->ntlm, &ctx->nego_msg, &in_flags, |
677 | 961 | NULL, NULL); |
678 | 961 | if (retmin) { |
679 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
680 | 0 | goto done; |
681 | 0 | } |
682 | | |
683 | | /* leave only the crossing between requested and allowed flags */ |
684 | 961 | ctx->neg_flags &= in_flags; |
685 | | |
686 | | /* Try to force the use of NTLMSSP_NEGOTIATE_VERSION even if the |
687 | | * client did not advertize it in their negotiate message, but |
688 | | * should be capable of providing it. |
689 | | * This is what Windows Server 2022 also does, and addresses |
690 | | * issues with older clients that incorrectly deal with MIC |
691 | | * calculations in absence of this flag. */ |
692 | 961 | if ((ctx->neg_flags & (NTLMSSP_NEGOTIATE_ALWAYS_SIGN | |
693 | 961 | NTLMSSP_NEGOTIATE_SEAL | |
694 | 961 | NTLMSSP_NEGOTIATE_SIGN))) { |
695 | 798 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_VERSION; |
696 | 798 | } |
697 | 961 | } else { |
698 | | /* If there is no negotiate message set datagram mode */ |
699 | 0 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_DATAGRAM | \ |
700 | 0 | NTLMSSP_NEGOTIATE_KEY_EXCH; |
701 | 0 | } |
702 | | |
703 | | /* TODO: Check some minimum required flags ? */ |
704 | | /* TODO: Check MS-NLMP ServerRequire128bitEncryption */ |
705 | | |
706 | 961 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { |
707 | | /* Choose unicode in preferemce if both are set */ |
708 | 314 | ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; |
709 | 647 | } else if (!(ctx->neg_flags & NTLMSSP_NEGOTIATE_OEM)) { |
710 | | /* no agreement */ |
711 | 4 | set_GSSERR(ERR_FAILNEGFLAGS); |
712 | 4 | goto done; |
713 | 4 | } |
714 | | |
715 | 957 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) { |
716 | 362 | ctx->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; |
717 | 362 | } |
718 | | |
719 | 957 | if (ctx->neg_flags & NTLMSSP_REQUEST_TARGET) { |
720 | 593 | ctx->neg_flags |= NTLMSSP_NEGOTIATE_TARGET_INFO; |
721 | 593 | } |
722 | | |
723 | 957 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { |
724 | 401 | ctx->gss_flags |= GSS_C_INTEG_FLAG; |
725 | 401 | } |
726 | 957 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { |
727 | 515 | ctx->gss_flags |= GSS_C_CONF_FLAG; |
728 | 515 | } |
729 | 957 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM) { |
730 | 0 | ctx->gss_flags |= GSS_C_DATAGRAM_FLAG; |
731 | 0 | } |
732 | | |
733 | | /* Random server challenge */ |
734 | 957 | challenge.data = ctx->server_chal; |
735 | 957 | challenge.length = 8; |
736 | 957 | retmin = RAND_BUFFER(&challenge); |
737 | 957 | if (retmin) { |
738 | 0 | set_GSSERR(retmin); |
739 | 0 | goto done; |
740 | 0 | } |
741 | | |
742 | | /* TODO: allow client applications to set a context option to |
743 | | * provide an av_flags default value so that flags like |
744 | | * MSVAVFLAGS_UNVERIFIED_SPN can be set. By default SSPI does |
745 | | * not set this flag, and setting it causes servers with restrictive |
746 | | * policy to fail authentication. Given no MS client set this flag |
747 | | * by default, neither should we until there is a clear need. We |
748 | | * trust our calling applications to do the right thing here. |
749 | | * av_flags = MSVAVFLAGS_UNVERIFIED_SPN; |
750 | | */ |
751 | | |
752 | 957 | timestamp = ntlm_timestamp_now(); |
753 | | |
754 | 957 | retmin = ntlm_encode_target_info(ctx->ntlm, |
755 | 957 | nb_computer_name, |
756 | 957 | nb_domain_name, |
757 | 957 | server_name->data.server.name, |
758 | 957 | NULL, NULL, |
759 | 957 | av_flags ? &av_flags : NULL, /* don't include empty MsvAvFlags */ |
760 | 957 | ×tamp, |
761 | 957 | NULL, |
762 | 957 | server_name->data.server.spn, |
763 | 957 | NULL, |
764 | 957 | &target_info); |
765 | 957 | if (retmin) { |
766 | 0 | set_GSSERR(retmin); |
767 | 0 | goto done; |
768 | 0 | } |
769 | | |
770 | 957 | if (gssntlm_role_is_domain_member(ctx)) { |
771 | 0 | chal_target_name = nb_domain_name; |
772 | 0 | ctx->neg_flags |= NTLMSSP_TARGET_TYPE_DOMAIN; |
773 | 957 | } else { |
774 | 957 | chal_target_name = nb_computer_name; |
775 | 957 | ctx->neg_flags |= NTLMSSP_TARGET_TYPE_SERVER; |
776 | 957 | } |
777 | | |
778 | 957 | retmin = ntlm_encode_chal_msg(ctx->ntlm, ctx->neg_flags, |
779 | 957 | chal_target_name, &challenge, |
780 | 957 | &target_info, &ctx->chal_msg); |
781 | 957 | if (retmin) { |
782 | 0 | set_GSSERR(retmin); |
783 | 0 | goto done; |
784 | 0 | } |
785 | | |
786 | 957 | ctx->stage = NTLMSSP_STAGE_CHALLENGE; |
787 | | |
788 | 957 | output_token->value = malloc(ctx->chal_msg.length); |
789 | 957 | if (!output_token->value) { |
790 | 0 | set_GSSERR(ENOMEM); |
791 | 0 | goto done; |
792 | 0 | } |
793 | 957 | memcpy(output_token->value, ctx->chal_msg.data, ctx->chal_msg.length); |
794 | 957 | output_token->length = ctx->chal_msg.length; |
795 | | |
796 | 957 | retmaj = GSS_S_CONTINUE_NEEDED; |
797 | | |
798 | 957 | } else { |
799 | 935 | ctx = (struct gssntlm_ctx *)(*context_handle); |
800 | | |
801 | 935 | if (!gssntlm_role_is_server(ctx)) { |
802 | 0 | set_GSSERRS(ERR_WRONGCTX, GSS_S_NO_CONTEXT); |
803 | 0 | goto done; |
804 | 0 | } |
805 | | |
806 | 935 | if ((input_token == GSS_C_NO_BUFFER) || |
807 | 935 | (input_token->length == 0)) { |
808 | 0 | set_GSSERRS(ERR_NOTOKEN, GSS_S_DEFECTIVE_TOKEN); |
809 | 0 | goto done; |
810 | 0 | } |
811 | | |
812 | 935 | ctx->auth_msg.data = malloc(input_token->length); |
813 | 935 | if (!ctx->auth_msg.data) { |
814 | 0 | set_GSSERR(ENOMEM); |
815 | 0 | goto done; |
816 | 0 | } |
817 | 935 | memcpy(ctx->auth_msg.data, input_token->value, input_token->length); |
818 | 935 | ctx->auth_msg.length = input_token->length; |
819 | | |
820 | 935 | retmin = ntlm_decode_msg_type(ctx->ntlm, &ctx->auth_msg, &msg_type); |
821 | 935 | if (retmin) { |
822 | 9 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
823 | 9 | goto done; |
824 | 9 | } |
825 | | |
826 | 926 | if (msg_type != AUTHENTICATE_MESSAGE || |
827 | 926 | ctx->stage != NTLMSSP_STAGE_CHALLENGE) { |
828 | 5 | set_GSSERRS(ERR_WRONGMSG, GSS_S_NO_CONTEXT); |
829 | 5 | goto done; |
830 | 5 | } |
831 | | |
832 | 921 | retmin = ntlm_decode_auth_msg(ctx->ntlm, &ctx->auth_msg, |
833 | 921 | ctx->neg_flags, |
834 | 921 | &lm_chal_resp, &nt_chal_resp, |
835 | 921 | &dom_name, &usr_name, &wks_name, |
836 | 921 | &enc_sess_key, &target_info, &mic); |
837 | 921 | if (retmin) { |
838 | 548 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
839 | 548 | goto done; |
840 | 548 | } |
841 | | |
842 | 373 | if (target_info.length > 0) { |
843 | 209 | retmin = ntlm_decode_target_info(ctx->ntlm, &target_info, |
844 | 209 | NULL, NULL, NULL, NULL, |
845 | 209 | NULL, NULL, &av_flags, |
846 | 209 | NULL, NULL, &av_cb); |
847 | 209 | if (retmin) { |
848 | 194 | set_GSSERR(retmin); |
849 | 194 | goto done; |
850 | 194 | } |
851 | 209 | } |
852 | | |
853 | 179 | if ((ctx->neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM) && |
854 | 179 | !(ctx->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { |
855 | 0 | set_GSSERRS(ERR_BADNEGFLAGS, GSS_S_DEFECTIVE_TOKEN); |
856 | 0 | goto done; |
857 | 0 | } |
858 | | |
859 | 179 | if ((usr_name == NULL) || (usr_name[0] == '\0')) { |
860 | 74 | if ((nt_chal_resp.length == 0) && |
861 | 74 | (((lm_chal_resp.length == 1) && |
862 | 60 | (lm_chal_resp.data[0] == '\0')) || |
863 | 60 | (lm_chal_resp.length == 0))) { |
864 | | /* Anonymous auth */ |
865 | | /* FIXME: not supported for now */ |
866 | 14 | set_GSSERR(ERR_NOTSUPPORTED); |
867 | 14 | goto done; |
868 | 60 | } else { |
869 | 60 | set_GSSERR(ERR_NOUSRFOUND); |
870 | 60 | goto done; |
871 | 60 | } |
872 | 105 | } else { |
873 | | |
874 | 105 | char useratdom[1024]; |
875 | 105 | size_t ulen, dlen, uadlen; |
876 | 105 | gss_buffer_desc usrname; |
877 | 105 | gss_const_key_value_set_t cred_store = GSS_C_NO_CRED_STORE; |
878 | 105 | gss_key_value_set_desc cs; |
879 | 105 | gss_key_value_element_desc cs_el; |
880 | | |
881 | 105 | if (!dom_name) { |
882 | 12 | dom_name = strdup(""); |
883 | 12 | if (!dom_name) { |
884 | 0 | set_GSSERR(ENOMEM); |
885 | 0 | goto done; |
886 | 0 | } |
887 | 12 | } |
888 | | |
889 | | /* Use domain\username format as that allows to pass in |
890 | | * enterprise names without the need to escape them */ |
891 | 105 | ulen = strlen(usr_name); |
892 | 105 | dlen = strlen(dom_name); |
893 | 105 | if (ulen + dlen + 2 > 1024) { |
894 | 17 | set_GSSERR(ERR_NAMETOOLONG); |
895 | 17 | goto done; |
896 | 17 | } |
897 | 88 | uadlen = dlen; |
898 | 88 | if (dlen) { |
899 | 74 | memcpy(useratdom, dom_name, dlen); |
900 | 74 | } |
901 | | |
902 | | /* always add the domain separator, this way if the username |
903 | | * is an enteprise name (user@email.domain form) it will be |
904 | | * correctly recognized by gssntlm_import_name() as such */ |
905 | 88 | useratdom[uadlen] = '\\'; |
906 | 88 | uadlen++; |
907 | | |
908 | | /* finally add usernmae part */ |
909 | 88 | memcpy(&useratdom[uadlen], usr_name, ulen); |
910 | 88 | uadlen += ulen; |
911 | 88 | useratdom[uadlen] = '\0'; |
912 | | |
913 | 88 | usrname.value = useratdom; |
914 | 88 | usrname.length = uadlen; |
915 | 88 | retmaj = gssntlm_import_name(&retmin, &usrname, |
916 | 88 | GSS_C_NT_USER_NAME, |
917 | 88 | (gss_name_t *)&gss_usrname); |
918 | 88 | if (retmaj) goto done; |
919 | | |
920 | 80 | if (cred && cred->cred.server.keyfile) { |
921 | 0 | cs_el.key = GSS_NTLMSSP_CS_KEYFILE; |
922 | 0 | cs_el.value = cred->cred.server.keyfile; |
923 | 0 | cs.count = 1; |
924 | 0 | cs.elements = &cs_el; |
925 | 0 | cred_store = &cs; |
926 | 0 | } |
927 | | |
928 | 80 | retmaj = gssntlm_acquire_cred_from(&retmin, ctx->external_context, |
929 | 80 | (gss_name_t)gss_usrname, |
930 | 80 | GSS_C_INDEFINITE, |
931 | 80 | GSS_C_NO_OID_SET, |
932 | 80 | GSS_C_INITIATE, |
933 | 80 | cred_store, |
934 | 80 | (gss_cred_id_t *)&usr_cred, |
935 | 80 | NULL, NULL); |
936 | 80 | if (retmaj) goto done; |
937 | | /* We can't handle winbind credentials yet */ |
938 | 0 | if (usr_cred->type != GSSNTLM_CRED_USER && |
939 | 0 | usr_cred->type != GSSNTLM_CRED_EXTERNAL) { |
940 | 0 | set_GSSERRS(ERR_NOUSRCRED, GSS_S_DEFECTIVE_CREDENTIAL); |
941 | 0 | goto done; |
942 | 0 | } |
943 | | |
944 | 0 | retmin = gssntlm_copy_name(gss_usrname, &ctx->source_name); |
945 | 0 | if (retmin) { |
946 | 0 | set_GSSERR(retmin); |
947 | 0 | goto done; |
948 | 0 | } |
949 | | |
950 | 0 | retmaj = gssntlm_srv_auth(&retmin, ctx, usr_cred, |
951 | 0 | &nt_chal_resp, &lm_chal_resp, |
952 | 0 | &key_exchange_key); |
953 | 0 | if (retmaj) goto done; |
954 | 0 | } |
955 | | |
956 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { |
957 | 0 | memcpy(encrypted_random_session_key.data, enc_sess_key.data, 16); |
958 | 0 | ctx->exported_session_key.length = 16; |
959 | |
|
960 | 0 | retmin = ntlm_encrypted_session_key(&key_exchange_key, |
961 | 0 | &encrypted_random_session_key, |
962 | 0 | &ctx->exported_session_key); |
963 | 0 | if (retmin) { |
964 | 0 | set_GSSERR(retmin); |
965 | 0 | goto done; |
966 | 0 | } |
967 | 0 | } else { |
968 | 0 | ctx->exported_session_key = key_exchange_key; |
969 | 0 | } |
970 | | |
971 | | /* check if MIC was sent */ |
972 | 0 | if (av_flags & MSVAVFLAGS_MIC_PRESENT) { |
973 | 0 | retmin = ntlm_verify_mic(&ctx->exported_session_key, |
974 | 0 | &ctx->nego_msg, &ctx->chal_msg, |
975 | 0 | &ctx->auth_msg, &mic); |
976 | 0 | if (retmin) { |
977 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
978 | 0 | goto done; |
979 | 0 | } |
980 | 0 | } |
981 | | |
982 | 0 | if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) { |
983 | 0 | uint8_t zero_cb[16] = { 0 }; |
984 | 0 | if (input_chan_bindings->initiator_addrtype != 0 || |
985 | 0 | input_chan_bindings->initiator_address.length != 0 || |
986 | 0 | input_chan_bindings->acceptor_addrtype != 0 || |
987 | 0 | input_chan_bindings->acceptor_address.length != 0 || |
988 | 0 | input_chan_bindings->application_data.length == 0) { |
989 | 0 | set_GSSERRS(ERR_BADARG, GSS_S_BAD_BINDINGS); |
990 | 0 | goto done; |
991 | 0 | } |
992 | 0 | unhashed_cb.length = input_chan_bindings->application_data.length; |
993 | 0 | unhashed_cb.data = input_chan_bindings->application_data.value; |
994 | |
|
995 | 0 | if (av_cb.length && (av_cb.length != 16)) { |
996 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
997 | 0 | goto done; |
998 | 0 | } |
999 | 0 | if (av_cb.length && |
1000 | 0 | (memcmp(av_cb.data, zero_cb, 16) != 0)) { |
1001 | 0 | retmin = ntlm_verify_channel_bindings(&unhashed_cb, &av_cb); |
1002 | 0 | if (retmin) { |
1003 | 0 | set_GSSERRS(retmin, GSS_S_DEFECTIVE_TOKEN); |
1004 | 0 | goto done; |
1005 | | /* This flag has been introduced only recently in MIT krb5 */ |
1006 | | #ifdef GSS_C_CHANNEL_BOUND_FLAG |
1007 | | } else { |
1008 | | ctx->gss_flags |= GSS_C_CHANNEL_BOUND_FLAG; |
1009 | | #endif /* GSS_C_CHANNEL_BOUND_FLAG */ |
1010 | 0 | } |
1011 | 0 | } |
1012 | 0 | } |
1013 | | |
1014 | 0 | if (ctx->neg_flags & (NTLMSSP_NEGOTIATE_SIGN | |
1015 | 0 | NTLMSSP_NEGOTIATE_SEAL)) { |
1016 | 0 | retmin = ntlm_signseal_keys(ctx->neg_flags, false, |
1017 | 0 | &ctx->exported_session_key, |
1018 | 0 | &ctx->crypto_state); |
1019 | 0 | if (retmin) { |
1020 | 0 | set_GSSERR(retmin); |
1021 | 0 | goto done; |
1022 | 0 | } |
1023 | 0 | } |
1024 | | |
1025 | 0 | if (src_name) { |
1026 | 0 | retmaj = gssntlm_duplicate_name(&retmin, |
1027 | 0 | (gss_name_t)&ctx->source_name, |
1028 | 0 | src_name); |
1029 | 0 | if (retmaj) goto done; |
1030 | 0 | } |
1031 | | |
1032 | 0 | ctx->stage = NTLMSSP_STAGE_DONE; |
1033 | 0 | ctx->expiration_time = time(NULL) + MAX_CHALRESP_LIFETIME; |
1034 | 0 | ctx->int_flags |= NTLMSSP_CTX_FLAG_ESTABLISHED; |
1035 | 0 | set_GSSERRS(0, GSS_S_COMPLETE); |
1036 | 0 | } |
1037 | | |
1038 | 2.08k | done: |
1039 | | |
1040 | 2.08k | if ((retmaj != GSS_S_COMPLETE) && |
1041 | 2.08k | (retmaj != GSS_S_CONTINUE_NEEDED)) { |
1042 | 1.12k | gssntlm_delete_sec_context(&tmpmin, (gss_ctx_id_t *)&ctx, NULL); |
1043 | 1.12k | } else { |
1044 | 957 | if (mech_type) *mech_type = discard_const(&gssntlm_oid); |
1045 | 957 | if (ret_flags) *ret_flags = ctx->gss_flags; |
1046 | 957 | if (time_rec) *time_rec = GSS_C_INDEFINITE; |
1047 | 957 | } |
1048 | 2.08k | *context_handle = (gss_ctx_id_t)ctx; |
1049 | 2.08k | gssntlm_release_name(&tmpmin, (gss_name_t *)&server_name); |
1050 | 2.08k | gssntlm_release_name(&tmpmin, (gss_name_t *)&gss_usrname); |
1051 | 2.08k | gssntlm_release_cred(&tmpmin, (gss_cred_id_t *)&usr_cred); |
1052 | 2.08k | safefree(nb_computer_name); |
1053 | 2.08k | safefree(nb_domain_name); |
1054 | 2.08k | safefree(usr_name); |
1055 | 2.08k | safefree(dom_name); |
1056 | 2.08k | safefree(wks_name); |
1057 | 2.08k | ntlm_free_buffer_data(&nt_chal_resp); |
1058 | 2.08k | ntlm_free_buffer_data(&lm_chal_resp); |
1059 | 2.08k | ntlm_free_buffer_data(&enc_sess_key); |
1060 | 2.08k | ntlm_free_buffer_data(&target_info); |
1061 | | |
1062 | 2.08k | return GSSERR(); |
1063 | 2.08k | } |
1064 | | |
1065 | | uint32_t gssntlm_inquire_context(uint32_t *minor_status, |
1066 | | gss_ctx_id_t context_handle, |
1067 | | gss_name_t *src_name, |
1068 | | gss_name_t *targ_name, |
1069 | | uint32_t *lifetime_rec, |
1070 | | gss_OID *mech_type, |
1071 | | uint32_t *ctx_flags, |
1072 | | int *locally_initiated, |
1073 | | int *open) |
1074 | 0 | { |
1075 | 0 | struct gssntlm_ctx *ctx; |
1076 | 0 | uint32_t retmaj; |
1077 | 0 | uint32_t retmin; |
1078 | 0 | time_t now; |
1079 | |
|
1080 | 0 | ctx = (struct gssntlm_ctx *)context_handle; |
1081 | 0 | if (!ctx) { |
1082 | 0 | return GSSERRS(ERR_NOARG, GSS_S_NO_CONTEXT); |
1083 | 0 | } |
1084 | | |
1085 | 0 | if (src_name) { |
1086 | 0 | retmaj = gssntlm_duplicate_name(&retmin, |
1087 | 0 | (gss_name_t)&ctx->source_name, |
1088 | 0 | src_name); |
1089 | 0 | if (retmaj) goto done; |
1090 | 0 | } |
1091 | | |
1092 | 0 | if (targ_name) { |
1093 | 0 | retmaj = gssntlm_duplicate_name(&retmin, |
1094 | 0 | (gss_name_t)&ctx->target_name, |
1095 | 0 | targ_name); |
1096 | 0 | if (retmaj) goto done; |
1097 | 0 | } |
1098 | | |
1099 | 0 | if (mech_type) { |
1100 | 0 | *mech_type = discard_const(&gssntlm_oid); |
1101 | 0 | } |
1102 | |
|
1103 | 0 | if (ctx_flags) { |
1104 | 0 | *ctx_flags = ctx->gss_flags; |
1105 | 0 | } |
1106 | |
|
1107 | 0 | if (locally_initiated) { |
1108 | 0 | if (gssntlm_role_is_client(ctx)) { |
1109 | 0 | *locally_initiated = 1; |
1110 | 0 | } else { |
1111 | 0 | *locally_initiated = 0; |
1112 | 0 | } |
1113 | 0 | } |
1114 | |
|
1115 | 0 | if (ctx->int_flags & NTLMSSP_CTX_FLAG_ESTABLISHED) { |
1116 | 0 | if (lifetime_rec) { |
1117 | 0 | now = time(NULL); |
1118 | 0 | if (ctx->expiration_time > now) { |
1119 | 0 | *lifetime_rec = 0; |
1120 | 0 | } else { |
1121 | 0 | *lifetime_rec = ctx->expiration_time - now; |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | if (open) { |
1125 | 0 | *open = 1; |
1126 | 0 | } |
1127 | 0 | } else { |
1128 | 0 | if (lifetime_rec) { |
1129 | 0 | *lifetime_rec = 0; |
1130 | 0 | } |
1131 | 0 | if (open) { |
1132 | 0 | *open = 0; |
1133 | 0 | } |
1134 | 0 | } |
1135 | |
|
1136 | 0 | set_GSSERRS(0, GSS_S_COMPLETE); |
1137 | |
|
1138 | 0 | done: |
1139 | 0 | return GSSERR(); |
1140 | 0 | } |
1141 | | |
1142 | | gss_OID_desc set_seq_num_oid = { |
1143 | | GSS_NTLMSSP_SET_SEQ_NUM_OID_LENGTH, |
1144 | | discard_const(GSS_NTLMSSP_SET_SEQ_NUM_OID_STRING) |
1145 | | }; |
1146 | | |
1147 | | uint32_t gssntlm_set_seq_num(uint32_t *minor_status, |
1148 | | struct gssntlm_ctx *ctx, |
1149 | | const gss_buffer_t value) |
1150 | 0 | { |
1151 | 0 | uint32_t retmin; |
1152 | 0 | uint32_t retmaj; |
1153 | |
|
1154 | 0 | if (ctx->gss_flags & GSS_C_DATAGRAM_FLAG) { |
1155 | 0 | if (value->length != 4) { |
1156 | 0 | return GSSERRS(ERR_BADARG, GSS_S_FAILURE); |
1157 | 0 | } |
1158 | 0 | memcpy(&ctx->crypto_state.recv.seq_num, |
1159 | 0 | value->value, value->length); |
1160 | 0 | ctx->crypto_state.send.seq_num = ctx->crypto_state.recv.seq_num; |
1161 | 0 | } else { |
1162 | 0 | return GSSERRS(ERR_WRONGCTX, GSS_S_FAILURE); |
1163 | 0 | } |
1164 | | |
1165 | 0 | return GSSERRS(0, GSS_S_COMPLETE); |
1166 | 0 | } |
1167 | | |
1168 | | gss_OID_desc reset_crypto_oid = { |
1169 | | GSS_NTLMSSP_RESET_CRYPTO_OID_LENGTH, |
1170 | | discard_const(GSS_NTLMSSP_RESET_CRYPTO_OID_STRING) |
1171 | | }; |
1172 | | |
1173 | | uint32_t gssntlm_reset_crypto(uint32_t *minor_status, |
1174 | | struct gssntlm_ctx *ctx, |
1175 | | const gss_buffer_t value) |
1176 | 0 | { |
1177 | 0 | uint32_t retmin; |
1178 | 0 | uint32_t retmaj; |
1179 | |
|
1180 | 0 | if (value->length != 4) { |
1181 | 0 | return GSSERRS(ERR_BADARG, GSS_S_FAILURE); |
1182 | 0 | } |
1183 | | |
1184 | | /* reset crypto state */ |
1185 | 0 | if (ctx->neg_flags & (NTLMSSP_NEGOTIATE_SIGN | |
1186 | 0 | NTLMSSP_NEGOTIATE_SEAL)) { |
1187 | 0 | uint32_t val; |
1188 | |
|
1189 | 0 | memcpy(&val, value->value, value->length); |
1190 | | |
1191 | | /* A val of 1 means we want to reset the verifier handle, |
1192 | | * which is the receive handle for NTLM, otherwise we reset |
1193 | | * the send handle. */ |
1194 | 0 | retmin = ntlm_reset_rc4_state(ctx->neg_flags, (val == 1), |
1195 | 0 | &ctx->exported_session_key, |
1196 | 0 | &ctx->crypto_state); |
1197 | 0 | if (retmin) { |
1198 | 0 | return GSSERRS(retmin, GSS_S_FAILURE); |
1199 | 0 | } |
1200 | 0 | } |
1201 | | |
1202 | 0 | return GSSERRS(0, GSS_S_COMPLETE); |
1203 | 0 | } |
1204 | | |
1205 | | uint32_t gssntlm_set_sec_context_option(uint32_t *minor_status, |
1206 | | gss_ctx_id_t *context_handle, |
1207 | | const gss_OID desired_object, |
1208 | | const gss_buffer_t value) |
1209 | 0 | { |
1210 | 0 | struct gssntlm_ctx *ctx; |
1211 | 0 | uint32_t retmin; |
1212 | 0 | uint32_t retmaj; |
1213 | |
|
1214 | 0 | if (context_handle == NULL || *context_handle == NULL) { |
1215 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
1216 | 0 | } |
1217 | 0 | if (desired_object == GSS_C_NO_OID) { |
1218 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
1219 | 0 | } |
1220 | | |
1221 | 0 | ctx = (struct gssntlm_ctx *)*context_handle; |
1222 | | |
1223 | | /* set seq num */ |
1224 | 0 | if (gss_oid_equal(desired_object, &set_seq_num_oid)) { |
1225 | 0 | return gssntlm_set_seq_num(minor_status, ctx, value); |
1226 | 0 | } else if (gss_oid_equal(desired_object, &reset_crypto_oid)) { |
1227 | 0 | return gssntlm_reset_crypto(minor_status, ctx, value); |
1228 | 0 | } |
1229 | | |
1230 | 0 | return GSSERRS(ERR_BADARG, GSS_S_UNAVAILABLE); |
1231 | 0 | } |
1232 | | |
1233 | | gss_OID_desc spnego_req_mic_oid = { |
1234 | | GSS_SPNEGO_REQUIRE_MIC_OID_LENGTH, |
1235 | | discard_const(GSS_SPNEGO_REQUIRE_MIC_OID_STRING) |
1236 | | }; |
1237 | | |
1238 | | uint32_t gssntlm_spnego_req_mic(uint32_t *minor_status, |
1239 | | struct gssntlm_ctx *ctx, |
1240 | | gss_buffer_set_t *data_set) |
1241 | 0 | { |
1242 | 0 | gss_buffer_desc mic_buf; |
1243 | 0 | uint32_t retmin; |
1244 | 0 | uint32_t retmaj; |
1245 | 0 | uint32_t tmpmin; |
1246 | 0 | uint8_t mic_set; |
1247 | | |
1248 | | /* the simple fact the spnego layer is asking means it can handle |
1249 | | * forcing mechlistMIC if we add a MIC to the Authenticate packet. |
1250 | | * We expect this to be called before the authenticate token is |
1251 | | * generated to set this flag ... */ |
1252 | 0 | ctx->int_flags |= NTLMSSP_CTX_FLAG_SPNEGO_CAN_MIC; |
1253 | | |
1254 | | /* ... and then again after, in which case if we actually did add |
1255 | | * a MIC we can tell spnego to add a mechlistMIC */ |
1256 | 0 | if (ctx->int_flags & NTLMSSP_CTX_FLAG_AUTH_WITH_MIC) { |
1257 | 0 | mic_set = 1; |
1258 | 0 | } else { |
1259 | 0 | mic_set = 0; |
1260 | 0 | } |
1261 | |
|
1262 | 0 | mic_buf.value = &mic_set; |
1263 | 0 | mic_buf.length = sizeof(mic_set); |
1264 | |
|
1265 | 0 | retmaj = gss_add_buffer_set_member(&retmin, &mic_buf, data_set); |
1266 | 0 | if (retmaj != GSS_S_COMPLETE) { |
1267 | 0 | (void)gss_release_buffer_set(&tmpmin, data_set); |
1268 | 0 | } |
1269 | |
|
1270 | 0 | return GSSERRS(retmin, retmaj); |
1271 | 0 | } |
1272 | | |
1273 | | static const gss_OID_desc sasl_ssf_oid = { |
1274 | | 11, discard_const("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0f") |
1275 | | }; |
1276 | | |
1277 | | static uint32_t gssntlm_sasl_ssf(uint32_t *minor_status, |
1278 | | struct gssntlm_ctx *ctx, |
1279 | | gss_buffer_set_t *data_set) |
1280 | 0 | { |
1281 | 0 | uint32_t retmin; |
1282 | 0 | uint32_t retmaj; |
1283 | 0 | uint32_t tmpmin; |
1284 | 0 | gss_buffer_desc ssf_buf; |
1285 | 0 | uint32_t ssf = 0; |
1286 | | |
1287 | | /* Handwaving a bit here but this is what SSF is all about */ |
1288 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { |
1289 | 0 | if (ctx->neg_flags & NTLMSSP_NEGOTIATE_128) { |
1290 | | /* Technically we use RC4 with a 128 bit key, but we |
1291 | | * consider the RC4 strenght degraded so we assign |
1292 | | * it a value of 64, this is consistent with what |
1293 | | * the krb5 mechanism does for the Rc4-HMAC enctype */ |
1294 | 0 | ssf = 64; |
1295 | 0 | } else if (ctx->neg_flags & NTLMSSP_NEGOTIATE_56) { |
1296 | 0 | ssf = 56; |
1297 | 0 | } else { |
1298 | 0 | ssf = 40; |
1299 | 0 | } |
1300 | 0 | } else if (ctx->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { |
1301 | 0 | ssf = 1; |
1302 | 0 | } |
1303 | |
|
1304 | 0 | ssf = htobe32(ssf); |
1305 | 0 | ssf_buf.value = &ssf; |
1306 | 0 | ssf_buf.length = 4; |
1307 | |
|
1308 | 0 | retmaj = gss_add_buffer_set_member(&retmin, &ssf_buf, data_set); |
1309 | 0 | if (retmaj != GSS_S_COMPLETE) { |
1310 | 0 | (void)gss_release_buffer_set(&tmpmin, data_set); |
1311 | 0 | } |
1312 | |
|
1313 | 0 | return GSSERRS(retmin, retmaj); |
1314 | 0 | } |
1315 | | |
1316 | | static uint32_t gssntlm_sspi_session_key(uint32_t *minor_status, |
1317 | | struct gssntlm_ctx *ctx, |
1318 | | gss_buffer_set_t *data_set) |
1319 | 0 | { |
1320 | 0 | uint32_t retmin; |
1321 | 0 | uint32_t retmaj; |
1322 | 0 | uint32_t tmpmin; |
1323 | 0 | gss_buffer_desc session_key_buf; |
1324 | |
|
1325 | 0 | if (ctx->exported_session_key.length == 0) { |
1326 | 0 | return GSSERRS(ERR_NOTAVAIL, GSS_S_UNAVAILABLE); |
1327 | 0 | } |
1328 | | |
1329 | 0 | session_key_buf.length = ctx->exported_session_key.length; |
1330 | 0 | session_key_buf.value = ctx->exported_session_key.data; |
1331 | |
|
1332 | 0 | retmaj = gss_add_buffer_set_member(&retmin, &session_key_buf, data_set); |
1333 | 0 | if (retmaj != GSS_S_COMPLETE) { |
1334 | 0 | (void)gss_release_buffer_set(&tmpmin, data_set); |
1335 | 0 | } |
1336 | 0 | return GSSERRS(retmin, retmaj); |
1337 | 0 | } |
1338 | | |
1339 | | uint32_t gssntlm_inquire_sec_context_by_oid(uint32_t *minor_status, |
1340 | | const gss_ctx_id_t context_handle, |
1341 | | const gss_OID desired_object, |
1342 | | gss_buffer_set_t *data_set) |
1343 | 0 | { |
1344 | 0 | struct gssntlm_ctx *ctx; |
1345 | 0 | uint32_t retmin; |
1346 | 0 | uint32_t retmaj; |
1347 | |
|
1348 | 0 | if (context_handle == NULL) { |
1349 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
1350 | 0 | } |
1351 | 0 | if (desired_object == GSS_C_NO_OID) { |
1352 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_READ); |
1353 | 0 | } |
1354 | 0 | if (!data_set) { |
1355 | 0 | return GSSERRS(ERR_NOARG, GSS_S_CALL_INACCESSIBLE_WRITE); |
1356 | 0 | } |
1357 | | |
1358 | 0 | ctx = (struct gssntlm_ctx *)context_handle; |
1359 | 0 | *data_set = GSS_C_NO_BUFFER_SET; |
1360 | |
|
1361 | 0 | if (gss_oid_equal(desired_object, &spnego_req_mic_oid)) { |
1362 | 0 | return gssntlm_spnego_req_mic(minor_status, ctx, data_set); |
1363 | 0 | } else if (gss_oid_equal(desired_object, &sasl_ssf_oid)){ |
1364 | 0 | return gssntlm_sasl_ssf(minor_status, ctx, data_set); |
1365 | 0 | } else if (gss_oid_equal(desired_object, GSS_C_INQ_SSPI_SESSION_KEY)) { |
1366 | 0 | return gssntlm_sspi_session_key(minor_status, ctx, data_set); |
1367 | 0 | } |
1368 | | |
1369 | 0 | return GSSERRS(ERR_NOTSUPPORTED, GSS_S_UNAVAILABLE); |
1370 | 0 | } |