HEIMDAL: move code from source4/heimdal* to third_party/heimdal*
[samba.git] / third_party / heimdal / lib / gssapi / mech / gss_accept_sec_context.c
1 /*-
2  * Copyright (c) 2005 Doug Rabson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  *      $FreeBSD: src/lib/libgssapi/gss_accept_sec_context.c,v 1.1 2005/12/29 14:40:20 dfr Exp $
27  */
28
29 #include "mech_locl.h"
30
31 /*
32  * accumulate_token() tries to assemble a complete GSS token which may
33  * be fed to it in pieces.  Microsoft does this when tokens are too large
34  * in CIFS, e.g.  It may occur in other places as well.  It is specified in:
35  *
36  *      [MS-SPNG]: Simple and Protected GSS-API Negotiation
37  *                 Mechanism (SPNEGO) Extension
38  *
39  *      https://winprotocoldoc.blob.core.windows.net/
40  *      productionwindowsarchives/MS-SPNG/%5bMS-SPNG%5d.pdf
41  *
42  * Sections 3.1.5.4 to 3.1.5.9.
43  *
44  * We only accumulate if we see the appropriate application tag in the
45  * first byte of 0x60 because in the absence of this, we cannot interpret
46  * the following bytes as a DER length.
47  *
48  * We only allocate an accumulating buffer if we detect that the token
49  * is split between multiple packets as this is the uncommon case and
50  * we want to optimise for the common case.  If we aren't accumulating,
51  * we simply return success.
52  *
53  * Our return value is GSS_S_CONTINUE_NEEDED if we need more input.
54  * We return GSS_S_COMPLETE if we are either finished accumulating or
55  * if we decide that we do not understand this token.  We only return
56  * an error if we think that we should understand the token and still
57  * fail to understand it.
58  */
59
60 static OM_uint32
61 accumulate_token(struct _gss_context *ctx, gss_buffer_t input_token)
62 {
63         unsigned char *p = input_token->value;
64         size_t len = input_token->length;
65         gss_buffer_t gci;
66         size_t l;
67
68         /*
69          * Token must start with [APPLICATION 0] SEQUENCE.
70          * But if it doesn't assume it is DCE-STYLE Kerberos!
71          */
72         if (!ctx->gc_target_len) {
73                 free(ctx->gc_free_this);
74                 ctx->gc_free_this = NULL;
75                 _mg_buffer_zero(&ctx->gc_input);
76
77                 /*
78                  * Let's prepare gc_input for the case where
79                  * we aren't accumulating.
80                  */
81
82                 ctx->gc_input.length = len;
83                 ctx->gc_input.value  = p;
84
85                 if (len == 0)
86                         return GSS_S_COMPLETE;
87
88                 /* Not our DER w/ a length */
89                 if (*p != 0x60)
90                         return GSS_S_COMPLETE;
91
92                 if (der_get_length(p+1, len-1, &ctx->gc_target_len, &l) != 0)
93                         return GSS_S_DEFECTIVE_TOKEN;
94
95                 _gss_mg_log(10, "gss-asc: DER length: %zu",
96                     ctx->gc_target_len);
97
98                 ctx->gc_oid_offset  = l + 1;
99                 ctx->gc_target_len += ctx->gc_oid_offset;
100
101                 _gss_mg_log(10, "gss-asc: total length: %zu",
102                     ctx->gc_target_len);
103
104                 if (ctx->gc_target_len == ASN1_INDEFINITE ||
105                     ctx->gc_target_len < len)
106                         return GSS_S_DEFECTIVE_TOKEN;
107
108                 /* We've got it all, short-circuit the accumulating */
109                 if (ctx->gc_target_len == len)
110                         goto done;
111
112                 _gss_mg_log(10, "gss-asc: accumulating partial token");
113
114                 ctx->gc_input.length = 0;
115                 ctx->gc_input.value  = calloc(ctx->gc_target_len, 1);
116                 if (!ctx->gc_input.value)
117                         return GSS_S_FAILURE;
118                 ctx->gc_free_this = ctx->gc_input.value;
119         }
120
121         if (len == 0)
122                 return GSS_S_DEFECTIVE_TOKEN;
123
124         gci = &ctx->gc_input;
125
126         if (ctx->gc_target_len > gci->length) {
127                 if (gci->length + len > ctx->gc_target_len) {
128                         _gss_mg_log(10, "gss-asc: accumulation exceeded "
129                             "target length: bailing");
130                         return GSS_S_DEFECTIVE_TOKEN;
131                 }
132                 memcpy((char *)gci->value + gci->length, p, len);
133                 gci->length += len;
134         }
135
136         if (gci->length != ctx->gc_target_len) {
137                 _gss_mg_log(10, "gss-asc: collected %zu/%zu bytes",
138                     gci->length, ctx->gc_target_len);
139                 return GSS_S_CONTINUE_NEEDED;
140         }
141
142 done:
143         _gss_mg_log(10, "gss-asc: received complete %zu byte token",
144             ctx->gc_target_len);
145         ctx->gc_target_len = 0;
146
147         return GSS_S_COMPLETE;
148 }
149
150 static void
151 log_oid(const char *str, gss_OID mech)
152 {
153         OM_uint32        maj, min;
154         gss_buffer_desc  buf;
155
156         maj = gss_oid_to_str(&min, mech, &buf);
157         if (maj == GSS_S_COMPLETE) {
158                 _gss_mg_log(10, "%s: %.*s", str, (int)buf.length,
159                     (char *)buf.value);
160                 gss_release_buffer(&min, &buf);
161         }
162 }
163
164 static OM_uint32
165 choose_mech(struct _gss_context *ctx)
166 {
167         gss_OID_desc     mech;
168         gss_OID          mech_oid;
169         unsigned char   *p = ctx->gc_input.value;
170         size_t           len = ctx->gc_input.length;
171
172         if (len == 0) {
173                 /*
174                  * There is the a wierd mode of SPNEGO (in CIFS and
175                  * SASL GSS-SPENGO where the first token is zero
176                  * length and the acceptor returns a mech_list, lets
177                  * hope that is what is happening now.
178                  *
179                  * http://msdn.microsoft.com/en-us/library/cc213114.aspx
180                  * "NegTokenInit2 Variation for Server-Initiation"
181                  */
182                 mech_oid = &__gss_spnego_mechanism_oid_desc;
183                 goto gss_get_mechanism;
184         }
185
186         p   += ctx->gc_oid_offset;
187         len -= ctx->gc_oid_offset;
188
189         /*
190          * Decode the OID for the mechanism. Simplify life by
191          * assuming that the OID length is less than 128 bytes.
192          */
193         if (len < 2 || *p != 0x06)
194                 goto bail;
195         if ((p[1] & 0x80) || p[1] > (len - 2))
196                 goto bail;
197         mech.length = p[1];
198         p += 2;
199         len -= 2;
200         mech.elements = p;
201
202         mech_oid = _gss_mg_support_mechanism(&mech);
203         if (mech_oid == GSS_C_NO_OID)
204                 return GSS_S_COMPLETE;
205
206 gss_get_mechanism:
207         /*
208          * If mech_oid == GSS_C_NO_OID then the mech is non-standard
209          * and we have to try all mechs (that we have a cred element
210          * for, if we have a cred).
211          */
212         if (mech_oid != GSS_C_NO_OID) {
213                 log_oid("mech oid", mech_oid);
214                 ctx->gc_mech = __gss_get_mechanism(mech_oid);
215                 if (!ctx->gc_mech) {
216                         _gss_mg_log(10, "mechanism client used is unknown");
217                         return (GSS_S_BAD_MECH);
218                 }
219                 _gss_mg_log(10, "using mech \"%s\"", ctx->gc_mech->gm_name);
220                 return GSS_S_COMPLETE;
221         }
222
223 bail:
224         _gss_mg_log(10, "no mech oid found");
225         return GSS_S_COMPLETE;
226 }
227
228 GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
229 gss_accept_sec_context(OM_uint32 *minor_status,
230     gss_ctx_id_t *context_handle,
231     gss_const_cred_id_t acceptor_cred_handle,
232     const gss_buffer_t input_token,
233     const gss_channel_bindings_t input_chan_bindings,
234     gss_name_t *src_name,
235     gss_OID *mech_type,
236     gss_buffer_t output_token,
237     OM_uint32 *ret_flags,
238     OM_uint32 *time_rec,
239     gss_cred_id_t *delegated_cred_handle)
240 {
241         OM_uint32 major_status, mech_ret_flags, junk;
242         gssapi_mech_interface m = NULL;
243         struct _gss_context *ctx = (struct _gss_context *) *context_handle;
244         struct _gss_cred *cred = (struct _gss_cred *) acceptor_cred_handle;
245         struct _gss_mechanism_cred *mc;
246         gss_buffer_desc defective_token_error;
247         gss_const_cred_id_t acceptor_mc;
248         gss_cred_id_t delegated_mc = GSS_C_NO_CREDENTIAL;
249         gss_name_t src_mn = GSS_C_NO_NAME;
250         gss_OID mech_ret_type = GSS_C_NO_OID;
251         int initial;
252
253         defective_token_error.length = 0;
254         defective_token_error.value = NULL;
255
256         *minor_status = 0;
257         if (src_name)
258             *src_name = GSS_C_NO_NAME;
259         if (mech_type)
260             *mech_type = GSS_C_NO_OID;
261         if (ret_flags)
262             *ret_flags = 0;
263         if (time_rec)
264             *time_rec = 0;
265         if (delegated_cred_handle)
266             *delegated_cred_handle = GSS_C_NO_CREDENTIAL;
267         _mg_buffer_zero(output_token);
268
269         if (!*context_handle) {
270                 ctx = calloc(sizeof(*ctx), 1);
271                 if (!ctx) {
272                         *minor_status = ENOMEM;
273                         return (GSS_S_DEFECTIVE_TOKEN);
274                 }
275                 *context_handle = (gss_ctx_id_t)ctx;
276                 ctx->gc_initial = 1;
277         }
278
279         major_status = accumulate_token(ctx, input_token);
280         if (major_status != GSS_S_COMPLETE)
281                 return major_status;
282
283         /*
284          * If we get here, then we have a complete token.  Please note
285          * that we may have a major_status of GSS_S_DEFECTIVE_TOKEN.  This
286          * 
287          */
288
289         initial = ctx->gc_initial;
290         ctx->gc_initial = 0;
291
292         if (major_status == GSS_S_COMPLETE && initial) {
293                 major_status = choose_mech(ctx);
294                 if (major_status != GSS_S_COMPLETE)
295                         return major_status;
296         }
297         m = ctx->gc_mech;
298
299         if (initial && !m && acceptor_cred_handle == GSS_C_NO_CREDENTIAL) {
300                 /*
301                  * No header, not a standard mechanism.  Try all the mechanisms
302                  * (because default credential).
303                  */
304                 struct _gss_mech_switch *ms;
305
306                 _gss_load_mech();
307                 acceptor_mc = GSS_C_NO_CREDENTIAL;
308                 HEIM_TAILQ_FOREACH(ms, &_gss_mechs, gm_link) {
309                         m = &ms->gm_mech;
310                         mech_ret_flags = 0;
311                         major_status = m->gm_accept_sec_context(minor_status,
312                             &ctx->gc_ctx,
313                             acceptor_mc,
314                             &ctx->gc_input,
315                             input_chan_bindings,
316                             &src_mn,
317                             &mech_ret_type,
318                             output_token,
319                             &mech_ret_flags,
320                             time_rec,
321                             &delegated_mc);
322                         if (major_status == GSS_S_DEFECTIVE_TOKEN) {
323                                 /*
324                                  * Try to retain and output one error token for
325                                  * GSS_S_DEFECTIVE_TOKEN.  The first one.
326                                  */
327                                 if (output_token->length &&
328                                     defective_token_error.length == 0) {
329                                     defective_token_error = *output_token;
330                                     output_token->length = 0;
331                                     output_token->value = NULL;
332                                 }
333                                 gss_release_buffer(&junk, output_token);
334                                 continue;
335                         }
336                         gss_release_buffer(&junk, &defective_token_error);
337                         ctx->gc_mech = m;
338                         goto got_one;
339                 }
340                 m = NULL;
341                 acceptor_mc = GSS_C_NO_CREDENTIAL;
342         } else if (initial && !m) {
343                 /*
344                  * No header, not a standard mechanism.  Try all the mechanisms
345                  * that we have a credential element for if we have a
346                  * non-default credential.
347                  */
348                 HEIM_TAILQ_FOREACH(mc, &cred->gc_mc, gmc_link) {
349                         m = mc->gmc_mech;
350                         acceptor_mc = (m->gm_flags & GM_USE_MG_CRED) ?
351                             acceptor_cred_handle : mc->gmc_cred;
352                         mech_ret_flags = 0;
353                         major_status = m->gm_accept_sec_context(minor_status,
354                             &ctx->gc_ctx,
355                             acceptor_mc,
356                             &ctx->gc_input,
357                             input_chan_bindings,
358                             &src_mn,
359                             &mech_ret_type,
360                             output_token,
361                             &mech_ret_flags,
362                             time_rec,
363                             &delegated_mc);
364                         if (major_status == GSS_S_DEFECTIVE_TOKEN) {
365                                 if (output_token->length &&
366                                     defective_token_error.length == 0) {
367                                     defective_token_error = *output_token;
368                                     output_token->length = 0;
369                                     output_token->value = NULL;
370                                 }
371                                 gss_release_buffer(&junk, output_token);
372                                 continue;
373                         }
374                         gss_release_buffer(&junk, &defective_token_error);
375                         ctx->gc_mech = m;
376                         goto got_one;
377                 }
378                 m = NULL;
379                 acceptor_mc = GSS_C_NO_CREDENTIAL;
380         }
381
382         if (m == NULL) {
383                 gss_delete_sec_context(&junk, context_handle, NULL);
384                 _gss_mg_log(10, "No mechanism accepted the non-standard initial security context token");
385                 *output_token = defective_token_error;
386                 return GSS_S_BAD_MECH;
387         }
388
389         if (m->gm_flags & GM_USE_MG_CRED) {
390                 acceptor_mc = acceptor_cred_handle;
391         } else if (cred) {
392                 HEIM_TAILQ_FOREACH(mc, &cred->gc_mc, gmc_link)
393                         if (mc->gmc_mech == m)
394                                 break;
395                 if (!mc) {
396                         gss_delete_sec_context(&junk, context_handle, NULL);
397                         _gss_mg_log(10, "gss-asc: client sent mech %s "
398                                     "but no credential was matching",
399                                     m->gm_name);
400                         HEIM_TAILQ_FOREACH(mc, &cred->gc_mc, gmc_link)
401                                 _gss_mg_log(10, "gss-asc: available creds were %s", mc->gmc_mech->gm_name);
402                         return (GSS_S_BAD_MECH);
403                 }
404                 acceptor_mc = mc->gmc_cred;
405         } else {
406                 acceptor_mc = GSS_C_NO_CREDENTIAL;
407         }
408
409         mech_ret_flags = 0;
410         major_status = m->gm_accept_sec_context(minor_status,
411             &ctx->gc_ctx,
412             acceptor_mc,
413             &ctx->gc_input,
414             input_chan_bindings,
415             &src_mn,
416             &mech_ret_type,
417             output_token,
418             &mech_ret_flags,
419             time_rec,
420             &delegated_mc);
421
422 got_one:
423         if (major_status != GSS_S_COMPLETE &&
424             major_status != GSS_S_CONTINUE_NEEDED)
425         {
426                 _gss_mg_error(m, *minor_status);
427                 gss_delete_sec_context(&junk, context_handle, NULL);
428                 return (major_status);
429         }
430
431         if (mech_type)
432                 *mech_type = mech_ret_type;
433
434         if (src_name && src_mn) {
435                 if (ctx->gc_mech->gm_flags & GM_USE_MG_NAME) {
436                         /* Negotiation mechanisms use mechglue names as names */
437                         *src_name = src_mn;
438                         src_mn = GSS_C_NO_NAME;
439                 } else {
440                         /*
441                          * Make a new name and mark it as an MN.
442                          *
443                          * Note that _gss_create_name() consumes `src_mn' but doesn't
444                          * take a pointer, so it can't set it to GSS_C_NO_NAME.
445                          */
446                         struct _gss_name *name = _gss_create_name(src_mn, m);
447
448                         if (!name) {
449                                 m->gm_release_name(minor_status, &src_mn);
450                                 gss_delete_sec_context(&junk, context_handle, NULL);
451                                 return (GSS_S_FAILURE);
452                         }
453                         *src_name = (gss_name_t) name;
454                         src_mn = GSS_C_NO_NAME;
455                 }
456         } else if (src_mn) {
457                 if (ctx->gc_mech->gm_flags & GM_USE_MG_NAME) {
458                         _gss_mg_release_name((struct _gss_name *)src_mn);
459                         src_mn = GSS_C_NO_NAME;
460                 } else {
461                         m->gm_release_name(minor_status, &src_mn);
462                 }
463         }
464
465         if (mech_ret_flags & GSS_C_DELEG_FLAG) {
466                 if (!delegated_cred_handle) {
467                         if (m->gm_flags  & GM_USE_MG_CRED)
468                                 gss_release_cred(minor_status, &delegated_mc);
469                         else
470                                 m->gm_release_cred(minor_status, &delegated_mc);
471                         mech_ret_flags &=
472                             ~(GSS_C_DELEG_FLAG|GSS_C_DELEG_POLICY_FLAG);
473                 } else if ((m->gm_flags & GM_USE_MG_CRED) != 0) {
474                         /* 
475                          * If credential is uses mechglue cred, assume it
476                          * returns one too.
477                          */
478                         *delegated_cred_handle = delegated_mc;
479                 } else if (gss_oid_equal(mech_ret_type, &m->gm_mech_oid) == 0) {
480                         /*
481                          * If the returned mech_type is not the same
482                          * as the mech, assume its pseudo mech type
483                          * and the returned type is already a
484                          * mech-glue object
485                          */
486                         *delegated_cred_handle = delegated_mc;
487
488                 } else if (delegated_mc) {
489                         struct _gss_cred *dcred;
490                         struct _gss_mechanism_cred *dmc;
491
492                         dcred = _gss_mg_alloc_cred();
493                         if (!dcred) {
494                                 *minor_status = ENOMEM;
495                                 gss_delete_sec_context(&junk, context_handle, NULL);
496                                 return (GSS_S_FAILURE);
497                         }
498                         dmc = malloc(sizeof(struct _gss_mechanism_cred));
499                         if (!dmc) {
500                                 free(dcred);
501                                 *minor_status = ENOMEM;
502                                 gss_delete_sec_context(&junk, context_handle, NULL);
503                                 return (GSS_S_FAILURE);
504                         }
505                         dmc->gmc_mech = m;
506                         dmc->gmc_mech_oid = &m->gm_mech_oid;
507                         dmc->gmc_cred = delegated_mc;
508                         HEIM_TAILQ_INSERT_TAIL(&dcred->gc_mc, dmc, gmc_link);
509
510                         *delegated_cred_handle = (gss_cred_id_t) dcred;
511                 }
512         }
513
514         _gss_mg_log(10, "gss-asc: return %d/%d", (int)major_status, (int)*minor_status);
515
516         if (ret_flags)
517             *ret_flags = mech_ret_flags;
518         return (major_status);
519 }