Imported Upstream version 1.10.2
[platform/upstream/krb5.git] / src / lib / krb5 / krb / pac.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/pac.c */
3 /*
4  * Copyright 2008 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26
27 #include "k5-int.h"
28 #include "authdata.h"
29
30 /* draft-brezak-win2k-krb-authz-00 */
31
32 /*
33  * Add a buffer to the provided PAC and update header.
34  */
35 krb5_error_code
36 k5_pac_add_buffer(krb5_context context,
37                   krb5_pac pac,
38                   krb5_ui_4 type,
39                   const krb5_data *data,
40                   krb5_boolean zerofill,
41                   krb5_data *out_data)
42 {
43     PACTYPE *header;
44     size_t header_len, i, pad = 0;
45     char *pac_data;
46
47     assert((data->data == NULL) == zerofill);
48
49     /* Check there isn't already a buffer of this type */
50     if (k5_pac_locate_buffer(context, pac, type, NULL) == 0) {
51         return EEXIST;
52     }
53
54     header = (PACTYPE *)realloc(pac->pac,
55                                 sizeof(PACTYPE) +
56                                 (pac->pac->cBuffers * sizeof(PAC_INFO_BUFFER)));
57     if (header == NULL) {
58         return ENOMEM;
59     }
60     pac->pac = header;
61
62     header_len = PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH);
63
64     if (data->length % PAC_ALIGNMENT)
65         pad = PAC_ALIGNMENT - (data->length % PAC_ALIGNMENT);
66
67     pac_data = realloc(pac->data.data,
68                        pac->data.length + PAC_INFO_BUFFER_LENGTH + data->length + pad);
69     if (pac_data == NULL) {
70         return ENOMEM;
71     }
72     pac->data.data = pac_data;
73
74     /* Update offsets of existing buffers */
75     for (i = 0; i < pac->pac->cBuffers; i++)
76         pac->pac->Buffers[i].Offset += PAC_INFO_BUFFER_LENGTH;
77
78     /* Make room for new PAC_INFO_BUFFER */
79     memmove(pac->data.data + header_len + PAC_INFO_BUFFER_LENGTH,
80             pac->data.data + header_len,
81             pac->data.length - header_len);
82     memset(pac->data.data + header_len, 0, PAC_INFO_BUFFER_LENGTH);
83
84     /* Initialise new PAC_INFO_BUFFER */
85     pac->pac->Buffers[i].ulType = type;
86     pac->pac->Buffers[i].cbBufferSize = data->length;
87     pac->pac->Buffers[i].Offset = pac->data.length + PAC_INFO_BUFFER_LENGTH;
88     assert((pac->pac->Buffers[i].Offset % PAC_ALIGNMENT) == 0);
89
90     /* Copy in new PAC data and zero padding bytes */
91     if (zerofill)
92         memset(pac->data.data + pac->pac->Buffers[i].Offset, 0, data->length);
93     else
94         memcpy(pac->data.data + pac->pac->Buffers[i].Offset, data->data, data->length);
95
96     memset(pac->data.data + pac->pac->Buffers[i].Offset + data->length, 0, pad);
97
98     pac->pac->cBuffers++;
99     pac->data.length += PAC_INFO_BUFFER_LENGTH + data->length + pad;
100
101     if (out_data != NULL) {
102         out_data->data = pac->data.data + pac->pac->Buffers[i].Offset;
103         out_data->length = data->length;
104     }
105
106     pac->verified = FALSE;
107
108     return 0;
109 }
110
111 krb5_error_code KRB5_CALLCONV
112 krb5_pac_add_buffer(krb5_context context,
113                     krb5_pac pac,
114                     krb5_ui_4 type,
115                     const krb5_data *data)
116 {
117     return k5_pac_add_buffer(context, pac, type, data, FALSE, NULL);
118 }
119
120 /*
121  * Free a PAC
122  */
123 void KRB5_CALLCONV
124 krb5_pac_free(krb5_context context,
125               krb5_pac pac)
126 {
127     if (pac != NULL) {
128         if (pac->data.data != NULL) {
129             memset(pac->data.data, 0, pac->data.length);
130             free(pac->data.data);
131         }
132         if (pac->pac != NULL)
133             free(pac->pac);
134         memset(pac, 0, sizeof(*pac));
135         free(pac);
136     }
137 }
138
139 krb5_error_code
140 k5_pac_locate_buffer(krb5_context context,
141                      const krb5_pac pac,
142                      krb5_ui_4 type,
143                      krb5_data *data)
144 {
145     PAC_INFO_BUFFER *buffer = NULL;
146     size_t i;
147
148     if (pac == NULL)
149         return EINVAL;
150
151     for (i = 0; i < pac->pac->cBuffers; i++) {
152         if (pac->pac->Buffers[i].ulType == type) {
153             if (buffer == NULL)
154                 buffer = &pac->pac->Buffers[i];
155             else
156                 return EINVAL;
157         }
158     }
159
160     if (buffer == NULL)
161         return ENOENT;
162
163     assert(buffer->Offset + buffer->cbBufferSize <= pac->data.length);
164
165     if (data != NULL) {
166         data->length = buffer->cbBufferSize;
167         data->data = pac->data.data + buffer->Offset;
168     }
169
170     return 0;
171 }
172
173 /*
174  * Find a buffer and copy data into output
175  */
176 krb5_error_code KRB5_CALLCONV
177 krb5_pac_get_buffer(krb5_context context,
178                     krb5_pac pac,
179                     krb5_ui_4 type,
180                     krb5_data *data)
181 {
182     krb5_data d;
183     krb5_error_code ret;
184
185     ret = k5_pac_locate_buffer(context, pac, type, &d);
186     if (ret != 0)
187         return ret;
188
189     data->data = malloc(d.length);
190     if (data->data == NULL)
191         return ENOMEM;
192
193     data->length = d.length;
194     memcpy(data->data, d.data, d.length);
195
196     return 0;
197 }
198
199 /*
200  * Return an array of the types of data in the PAC
201  */
202 krb5_error_code KRB5_CALLCONV
203 krb5_pac_get_types(krb5_context context,
204                    krb5_pac pac,
205                    size_t *len,
206                    krb5_ui_4 **types)
207 {
208     size_t i;
209
210     *types = (krb5_ui_4 *)malloc(pac->pac->cBuffers * sizeof(krb5_ui_4));
211     if (*types == NULL)
212         return ENOMEM;
213
214     *len = pac->pac->cBuffers;
215
216     for (i = 0; i < pac->pac->cBuffers; i++)
217         (*types)[i] = pac->pac->Buffers[i].ulType;
218
219     return 0;
220 }
221
222 /*
223  * Initialize PAC
224  */
225 krb5_error_code KRB5_CALLCONV
226 krb5_pac_init(krb5_context context,
227               krb5_pac *ppac)
228 {
229     krb5_pac pac;
230
231     pac = (krb5_pac)malloc(sizeof(*pac));
232     if (pac == NULL)
233         return ENOMEM;
234
235     pac->pac = (PACTYPE *)malloc(sizeof(PACTYPE));
236     if (pac->pac == NULL) {
237         free(pac);
238         return ENOMEM;
239     }
240
241     pac->pac->cBuffers = 0;
242     pac->pac->Version = 0;
243
244     pac->data.length = PACTYPE_LENGTH;
245     pac->data.data = calloc(1, pac->data.length);
246     if (pac->data.data == NULL) {
247         krb5_pac_free(context, pac);
248         return ENOMEM;
249     }
250
251     pac->verified = FALSE;
252
253     *ppac = pac;
254
255     return 0;
256 }
257
258 static krb5_error_code
259 k5_pac_copy(krb5_context context,
260             krb5_pac src,
261             krb5_pac *dst)
262 {
263     size_t header_len;
264     krb5_ui_4 cbuffers;
265     krb5_error_code code;
266     krb5_pac pac;
267
268     cbuffers = src->pac->cBuffers;
269     if (cbuffers != 0)
270         cbuffers--;
271
272     header_len = sizeof(PACTYPE) + cbuffers * sizeof(PAC_INFO_BUFFER);
273
274     pac = (krb5_pac)malloc(sizeof(*pac));
275     if (pac == NULL)
276         return ENOMEM;
277
278     pac->pac = (PACTYPE *)malloc(header_len);
279     if (pac->pac == NULL) {
280         free(pac);
281         return ENOMEM;
282     }
283
284     memcpy(pac->pac, src->pac, header_len);
285
286     code = krb5int_copy_data_contents(context, &src->data, &pac->data);
287     if (code != 0) {
288         free(pac->pac);
289         free(pac);
290         return ENOMEM;
291     }
292
293     pac->verified = src->verified;
294     *dst = pac;
295
296     return 0;
297 }
298
299 /*
300  * Parse the supplied data into the PAC allocated by this function
301  */
302 krb5_error_code KRB5_CALLCONV
303 krb5_pac_parse(krb5_context context,
304                const void *ptr,
305                size_t len,
306                krb5_pac *ppac)
307 {
308     krb5_error_code ret;
309     size_t i;
310     const unsigned char *p = (const unsigned char *)ptr;
311     krb5_pac pac;
312     size_t header_len;
313     krb5_ui_4 cbuffers, version;
314
315     *ppac = NULL;
316
317     if (len < PACTYPE_LENGTH)
318         return ERANGE;
319
320     cbuffers = load_32_le(p);
321     p += 4;
322     version = load_32_le(p);
323     p += 4;
324
325     if (version != 0)
326         return EINVAL;
327
328     header_len = PACTYPE_LENGTH + (cbuffers * PAC_INFO_BUFFER_LENGTH);
329     if (len < header_len)
330         return ERANGE;
331
332     ret = krb5_pac_init(context, &pac);
333     if (ret != 0)
334         return ret;
335
336     pac->pac = (PACTYPE *)realloc(pac->pac,
337                                   sizeof(PACTYPE) + ((cbuffers - 1) * sizeof(PAC_INFO_BUFFER)));
338     if (pac->pac == NULL) {
339         krb5_pac_free(context, pac);
340         return ENOMEM;
341     }
342
343     pac->pac->cBuffers = cbuffers;
344     pac->pac->Version = version;
345
346     for (i = 0; i < pac->pac->cBuffers; i++) {
347         PAC_INFO_BUFFER *buffer = &pac->pac->Buffers[i];
348
349         buffer->ulType = load_32_le(p);
350         p += 4;
351         buffer->cbBufferSize = load_32_le(p);
352         p += 4;
353         buffer->Offset = load_64_le(p);
354         p += 8;
355
356         if (buffer->Offset % PAC_ALIGNMENT) {
357             krb5_pac_free(context, pac);
358             return EINVAL;
359         }
360         if (buffer->Offset < header_len ||
361             buffer->Offset + buffer->cbBufferSize > len) {
362             krb5_pac_free(context, pac);
363             return ERANGE;
364         }
365     }
366
367     pac->data.data = realloc(pac->data.data, len);
368     if (pac->data.data == NULL) {
369         krb5_pac_free(context, pac);
370         return ENOMEM;
371     }
372     memcpy(pac->data.data, ptr, len);
373
374     pac->data.length = len;
375
376     *ppac = pac;
377
378     return 0;
379 }
380
381 static krb5_error_code
382 k5_time_to_seconds_since_1970(krb5_int64 ntTime,
383                               krb5_timestamp *elapsedSeconds)
384 {
385     krb5_ui_8 abstime;
386
387     ntTime /= 10000000;
388
389     abstime = ntTime > 0 ? ntTime - NT_TIME_EPOCH : -ntTime;
390
391     if (abstime > KRB5_INT32_MAX)
392         return ERANGE;
393
394     *elapsedSeconds = abstime;
395
396     return 0;
397 }
398
399 krb5_error_code
400 k5_seconds_since_1970_to_time(krb5_timestamp elapsedSeconds,
401                               krb5_ui_8 *ntTime)
402 {
403     *ntTime = elapsedSeconds;
404
405     if (elapsedSeconds > 0)
406         *ntTime += NT_TIME_EPOCH;
407
408     *ntTime *= 10000000;
409
410     return 0;
411 }
412
413 krb5_error_code
414 k5_pac_validate_client(krb5_context context,
415                        const krb5_pac pac,
416                        krb5_timestamp authtime,
417                        krb5_const_principal principal)
418 {
419     krb5_error_code ret;
420     krb5_data client_info;
421     char *pac_princname;
422     unsigned char *p;
423     krb5_timestamp pac_authtime;
424     krb5_ui_2 pac_princname_length;
425     krb5_int64 pac_nt_authtime;
426     krb5_principal pac_principal;
427
428     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
429                                &client_info);
430     if (ret != 0)
431         return ret;
432
433     if (client_info.length < PAC_CLIENT_INFO_LENGTH)
434         return ERANGE;
435
436     p = (unsigned char *)client_info.data;
437     pac_nt_authtime = load_64_le(p);
438     p += 8;
439     pac_princname_length = load_16_le(p);
440     p += 2;
441
442     ret = k5_time_to_seconds_since_1970(pac_nt_authtime, &pac_authtime);
443     if (ret != 0)
444         return ret;
445
446     if (client_info.length < PAC_CLIENT_INFO_LENGTH + pac_princname_length ||
447         pac_princname_length % 2)
448         return ERANGE;
449
450     ret = krb5int_ucs2lecs_to_utf8s(p, (size_t)pac_princname_length / 2,
451                                     &pac_princname, NULL);
452     if (ret != 0)
453         return ret;
454
455     ret = krb5_parse_name_flags(context, pac_princname,
456                                 KRB5_PRINCIPAL_PARSE_NO_REALM, &pac_principal);
457     if (ret != 0) {
458         free(pac_princname);
459         return ret;
460     }
461
462     free(pac_princname);
463
464     if (pac_authtime != authtime ||
465         !krb5_principal_compare_flags(context,
466                                       pac_principal,
467                                       principal,
468                                       KRB5_PRINCIPAL_COMPARE_IGNORE_REALM))
469         ret = KRB5KRB_AP_WRONG_PRINC;
470
471     krb5_free_principal(context, pac_principal);
472
473     return ret;
474 }
475
476 static krb5_error_code
477 k5_pac_zero_signature(krb5_context context,
478                       const krb5_pac pac,
479                       krb5_ui_4 type,
480                       krb5_data *data)
481 {
482     PAC_INFO_BUFFER *buffer = NULL;
483     size_t i;
484
485     assert(type == KRB5_PAC_SERVER_CHECKSUM ||
486            type == KRB5_PAC_PRIVSVR_CHECKSUM);
487     assert(data->length >= pac->data.length);
488
489     for (i = 0; i < pac->pac->cBuffers; i++) {
490         if (pac->pac->Buffers[i].ulType == type) {
491             buffer = &pac->pac->Buffers[i];
492             break;
493         }
494     }
495
496     if (buffer == NULL)
497         return ENOENT;
498
499     if (buffer->Offset + buffer->cbBufferSize > pac->data.length)
500         return ERANGE;
501
502     if (buffer->cbBufferSize < PAC_SIGNATURE_DATA_LENGTH)
503         return KRB5_BAD_MSIZE;
504
505     /* Zero out the data portion of the checksum only */
506     memset(data->data + buffer->Offset + PAC_SIGNATURE_DATA_LENGTH,
507            0,
508            buffer->cbBufferSize - PAC_SIGNATURE_DATA_LENGTH);
509
510     return 0;
511 }
512
513 static krb5_error_code
514 k5_pac_verify_server_checksum(krb5_context context,
515                               const krb5_pac pac,
516                               const krb5_keyblock *server)
517 {
518     krb5_error_code ret;
519     krb5_data pac_data; /* PAC with zeroed checksums */
520     krb5_checksum checksum;
521     krb5_data checksum_data;
522     krb5_boolean valid;
523     krb5_octet *p;
524
525     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_SERVER_CHECKSUM,
526                                &checksum_data);
527     if (ret != 0)
528         return ret;
529
530     if (checksum_data.length < PAC_SIGNATURE_DATA_LENGTH)
531         return KRB5_BAD_MSIZE;
532
533     p = (krb5_octet *)checksum_data.data;
534     checksum.checksum_type = load_32_le(p);
535     checksum.length = checksum_data.length - PAC_SIGNATURE_DATA_LENGTH;
536     checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;
537     if (!krb5_c_is_keyed_cksum(checksum.checksum_type))
538         return KRB5KRB_AP_ERR_INAPP_CKSUM;
539
540     pac_data.length = pac->data.length;
541     pac_data.data = malloc(pac->data.length);
542     if (pac_data.data == NULL)
543         return ENOMEM;
544
545     memcpy(pac_data.data, pac->data.data, pac->data.length);
546
547     /* Zero out both checksum buffers */
548     ret = k5_pac_zero_signature(context, pac, KRB5_PAC_SERVER_CHECKSUM,
549                                 &pac_data);
550     if (ret != 0) {
551         free(pac_data.data);
552         return ret;
553     }
554
555     ret = k5_pac_zero_signature(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM,
556                                 &pac_data);
557     if (ret != 0) {
558         free(pac_data.data);
559         return ret;
560     }
561
562     ret = krb5_c_verify_checksum(context, server,
563                                  KRB5_KEYUSAGE_APP_DATA_CKSUM,
564                                  &pac_data, &checksum, &valid);
565
566     free(pac_data.data);
567
568     if (ret != 0) {
569         return ret;
570     }
571
572     if (valid == FALSE)
573         ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
574
575     return ret;
576 }
577
578 static krb5_error_code
579 k5_pac_verify_kdc_checksum(krb5_context context,
580                            const krb5_pac pac,
581                            const krb5_keyblock *privsvr)
582 {
583     krb5_error_code ret;
584     krb5_data server_checksum, privsvr_checksum;
585     krb5_checksum checksum;
586     krb5_boolean valid;
587     krb5_octet *p;
588
589     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM,
590                                &privsvr_checksum);
591     if (ret != 0)
592         return ret;
593
594     if (privsvr_checksum.length < PAC_SIGNATURE_DATA_LENGTH)
595         return KRB5_BAD_MSIZE;
596
597     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_SERVER_CHECKSUM,
598                                &server_checksum);
599     if (ret != 0)
600         return ret;
601
602     if (server_checksum.length < PAC_SIGNATURE_DATA_LENGTH)
603         return KRB5_BAD_MSIZE;
604
605     p = (krb5_octet *)privsvr_checksum.data;
606     checksum.checksum_type = load_32_le(p);
607     checksum.length = privsvr_checksum.length - PAC_SIGNATURE_DATA_LENGTH;
608     checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;
609     if (!krb5_c_is_keyed_cksum(checksum.checksum_type))
610         return KRB5KRB_AP_ERR_INAPP_CKSUM;
611
612     server_checksum.data += PAC_SIGNATURE_DATA_LENGTH;
613     server_checksum.length -= PAC_SIGNATURE_DATA_LENGTH;
614
615     ret = krb5_c_verify_checksum(context, privsvr,
616                                  KRB5_KEYUSAGE_APP_DATA_CKSUM,
617                                  &server_checksum, &checksum, &valid);
618     if (ret != 0)
619         return ret;
620
621     if (valid == FALSE)
622         ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
623
624     return ret;
625 }
626
627 krb5_error_code KRB5_CALLCONV
628 krb5_pac_verify(krb5_context context,
629                 const krb5_pac pac,
630                 krb5_timestamp authtime,
631                 krb5_const_principal principal,
632                 const krb5_keyblock *server,
633                 const krb5_keyblock *privsvr)
634 {
635     krb5_error_code ret;
636
637     if (server == NULL)
638         return EINVAL;
639
640     ret = k5_pac_verify_server_checksum(context, pac, server);
641     if (ret != 0)
642         return ret;
643
644     if (privsvr != NULL) {
645         ret = k5_pac_verify_kdc_checksum(context, pac, privsvr);
646         if (ret != 0)
647             return ret;
648     }
649
650     if (principal != NULL) {
651         ret = k5_pac_validate_client(context, pac, authtime, principal);
652         if (ret != 0)
653             return ret;
654     }
655
656     pac->verified = TRUE;
657
658     return 0;
659 }
660
661 /*
662  * PAC auth data attribute backend
663  */
664 struct mspac_context {
665     krb5_pac pac;
666 };
667
668 static krb5_error_code
669 mspac_init(krb5_context kcontext, void **plugin_context)
670 {
671     *plugin_context = NULL;
672     return 0;
673 }
674
675 static void
676 mspac_flags(krb5_context kcontext,
677             void *plugin_context,
678             krb5_authdatatype ad_type,
679             krb5_flags *flags)
680 {
681     *flags = AD_USAGE_TGS_REQ;
682 }
683
684 static void
685 mspac_fini(krb5_context kcontext, void *plugin_context)
686 {
687     return;
688 }
689
690 static krb5_error_code
691 mspac_request_init(krb5_context kcontext,
692                    krb5_authdata_context context,
693                    void *plugin_context,
694                    void **request_context)
695 {
696     struct mspac_context *pacctx;
697
698     pacctx = (struct mspac_context *)malloc(sizeof(*pacctx));
699     if (pacctx == NULL)
700         return ENOMEM;
701
702     pacctx->pac = NULL;
703
704     *request_context = pacctx;
705
706     return 0;
707 }
708
709 static krb5_error_code
710 mspac_import_authdata(krb5_context kcontext,
711                       krb5_authdata_context context,
712                       void *plugin_context,
713                       void *request_context,
714                       krb5_authdata **authdata,
715                       krb5_boolean kdc_issued,
716                       krb5_const_principal kdc_issuer)
717 {
718     krb5_error_code code;
719     struct mspac_context *pacctx = (struct mspac_context *)request_context;
720
721     if (kdc_issued)
722         return EINVAL;
723
724     if (pacctx->pac != NULL) {
725         krb5_pac_free(kcontext, pacctx->pac);
726         pacctx->pac = NULL;
727     }
728
729     assert(authdata[0] != NULL);
730     assert((authdata[0]->ad_type & AD_TYPE_FIELD_TYPE_MASK) ==
731            KRB5_AUTHDATA_WIN2K_PAC);
732
733     code = krb5_pac_parse(kcontext, authdata[0]->contents,
734                           authdata[0]->length, &pacctx->pac);
735
736     return code;
737 }
738
739 static krb5_error_code
740 mspac_export_authdata(krb5_context kcontext,
741                       krb5_authdata_context context,
742                       void *plugin_context,
743                       void *request_context,
744                       krb5_flags usage,
745                       krb5_authdata ***out_authdata)
746 {
747     struct mspac_context *pacctx = (struct mspac_context *)request_context;
748     krb5_error_code code;
749     krb5_authdata **authdata;
750     krb5_data data;
751
752     if (pacctx->pac == NULL)
753         return 0;
754
755     authdata = calloc(2, sizeof(krb5_authdata *));
756     if (authdata == NULL)
757         return ENOMEM;
758
759     authdata[0] = calloc(1, sizeof(krb5_authdata));
760     if (authdata[0] == NULL) {
761         free(authdata);
762         return ENOMEM;
763     }
764     authdata[1] = NULL;
765
766     code = krb5int_copy_data_contents(kcontext, &pacctx->pac->data, &data);
767     if (code != 0) {
768         krb5_free_authdata(kcontext, authdata);
769         return code;
770     }
771
772     authdata[0]->magic = KV5M_AUTHDATA;
773     authdata[0]->ad_type = KRB5_AUTHDATA_WIN2K_PAC;
774     authdata[0]->length = data.length;
775     authdata[0]->contents = (krb5_octet *)data.data;
776
777     authdata[1] = NULL;
778
779     *out_authdata = authdata;
780
781     return 0;
782 }
783
784 static krb5_error_code
785 mspac_verify(krb5_context kcontext,
786              krb5_authdata_context context,
787              void *plugin_context,
788              void *request_context,
789              const krb5_auth_context *auth_context,
790              const krb5_keyblock *key,
791              const krb5_ap_req *req)
792 {
793     krb5_error_code code;
794     struct mspac_context *pacctx = (struct mspac_context *)request_context;
795
796     if (pacctx->pac == NULL)
797         return EINVAL;
798
799     code = krb5_pac_verify(kcontext, pacctx->pac,
800                            req->ticket->enc_part2->times.authtime,
801                            req->ticket->enc_part2->client, key, NULL);
802     if (code != 0)
803         TRACE_MSPAC_VERIFY_FAIL(kcontext, code);
804
805     /*
806      * If the above verification failed, don't fail the whole authentication,
807      * just don't mark the PAC as verified.  A checksum mismatch can occur if
808      * the PAC was copied from a cross-realm TGT by an ignorant KDC, and Apple
809      * Mac OS X Server Open Directory (as of 10.6) generates PACs with no
810      * server checksum at all.
811      */
812     return 0;
813 }
814
815 static void
816 mspac_request_fini(krb5_context kcontext,
817                    krb5_authdata_context context,
818                    void *plugin_context,
819                    void *request_context)
820 {
821     struct mspac_context *pacctx = (struct mspac_context *)request_context;
822
823     if (pacctx != NULL) {
824         if (pacctx->pac != NULL)
825             krb5_pac_free(kcontext, pacctx->pac);
826
827         free(pacctx);
828     }
829 }
830
831 #define STRLENOF(x) (sizeof((x)) - 1)
832
833 static struct {
834     krb5_ui_4 type;
835     krb5_data attribute;
836 } mspac_attribute_types[] = {
837     { (krb5_ui_4)-1,            { KV5M_DATA, STRLENOF("urn:mspac:"),
838                                   "urn:mspac:" } },
839     { KRB5_PAC_LOGON_INFO,       { KV5M_DATA,
840                                    STRLENOF("urn:mspac:logon-info"),
841                                    "urn:mspac:logon-info" } },
842     { KRB5_PAC_CREDENTIALS_INFO, { KV5M_DATA,
843                                    STRLENOF("urn:mspac:credentials-info"),
844                                    "urn:mspac:credentials-info" } },
845     { KRB5_PAC_SERVER_CHECKSUM,  { KV5M_DATA,
846                                    STRLENOF("urn:mspac:server-checksum"),
847                                    "urn:mspac:server-checksum" } },
848     { KRB5_PAC_PRIVSVR_CHECKSUM, { KV5M_DATA,
849                                    STRLENOF("urn:mspac:privsvr-checksum"),
850                                    "urn:mspac:privsvr-checksum" } },
851     { KRB5_PAC_CLIENT_INFO,      { KV5M_DATA,
852                                    STRLENOF("urn:mspac:client-info"),
853                                    "urn:mspac:client-info" } },
854     { KRB5_PAC_DELEGATION_INFO,  { KV5M_DATA,
855                                    STRLENOF("urn:mspac:delegation-info"),
856                                    "urn:mspac:delegation-info" } },
857     { KRB5_PAC_UPN_DNS_INFO,     { KV5M_DATA,
858                                    STRLENOF("urn:mspac:upn-dns-info"),
859                                    "urn:mspac:upn-dns-info" } },
860 };
861
862 #define MSPAC_ATTRIBUTE_COUNT   (sizeof(mspac_attribute_types)/sizeof(mspac_attribute_types[0]))
863
864 static krb5_error_code
865 mspac_type2attr(krb5_ui_4 type, krb5_data *attr)
866 {
867     unsigned int i;
868
869     for (i = 0; i < MSPAC_ATTRIBUTE_COUNT; i++) {
870         if (mspac_attribute_types[i].type == type) {
871             *attr = mspac_attribute_types[i].attribute;
872             return 0;
873         }
874     }
875
876     return ENOENT;
877 }
878
879 static krb5_error_code
880 mspac_attr2type(const krb5_data *attr, krb5_ui_4 *type)
881 {
882     unsigned int i;
883
884     for (i = 0; i < MSPAC_ATTRIBUTE_COUNT; i++) {
885         if (attr->length == mspac_attribute_types[i].attribute.length &&
886             strncasecmp(attr->data, mspac_attribute_types[i].attribute.data, attr->length) == 0) {
887             *type = mspac_attribute_types[i].type;
888             return 0;
889         }
890     }
891
892     if (attr->length > STRLENOF("urn:mspac:") &&
893         strncasecmp(attr->data, "urn:mspac:", STRLENOF("urn:mspac:")) == 0)
894     {
895         char *p = &attr->data[STRLENOF("urn:mspac:")];
896         char *endptr;
897
898         *type = strtoul(p, &endptr, 10);
899         if (*type != 0 && *endptr == '\0')
900             return 0;
901     }
902
903     return ENOENT;
904 }
905
906 static krb5_error_code
907 mspac_get_attribute_types(krb5_context kcontext,
908                           krb5_authdata_context context,
909                           void *plugin_context,
910                           void *request_context,
911                           krb5_data **out_attrs)
912 {
913     struct mspac_context *pacctx = (struct mspac_context *)request_context;
914     unsigned int i, j;
915     krb5_data *attrs;
916     krb5_error_code code;
917
918     if (pacctx->pac == NULL)
919         return ENOENT;
920
921     attrs = calloc(1 + pacctx->pac->pac->cBuffers + 1, sizeof(krb5_data));
922     if (attrs == NULL)
923         return ENOMEM;
924
925     j = 0;
926
927     /* The entire PAC */
928     code = krb5int_copy_data_contents(kcontext,
929                                       &mspac_attribute_types[0].attribute,
930                                       &attrs[j++]);
931     if (code != 0) {
932         free(attrs);
933         return code;
934     }
935
936     /* PAC buffers */
937     for (i = 0; i < pacctx->pac->pac->cBuffers; i++) {
938         krb5_data attr;
939
940         code = mspac_type2attr(pacctx->pac->pac->Buffers[i].ulType, &attr);
941         if (code == 0) {
942             code = krb5int_copy_data_contents(kcontext, &attr, &attrs[j++]);
943             if (code != 0) {
944                 krb5int_free_data_list(kcontext, attrs);
945                 return code;
946             }
947         } else {
948             int length;
949
950             length = asprintf(&attrs[j].data, "urn:mspac:%d",
951                               pacctx->pac->pac->Buffers[i].ulType);
952             if (length < 0) {
953                 krb5int_free_data_list(kcontext, attrs);
954                 return ENOMEM;
955             }
956             attrs[j++].length = length;
957         }
958     }
959     attrs[j].data = NULL;
960     attrs[j].length = 0;
961
962     *out_attrs = attrs;
963
964     return 0;
965 }
966
967 static krb5_error_code
968 mspac_get_attribute(krb5_context kcontext,
969                     krb5_authdata_context context,
970                     void *plugin_context,
971                     void *request_context,
972                     const krb5_data *attribute,
973                     krb5_boolean *authenticated,
974                     krb5_boolean *complete,
975                     krb5_data *value,
976                     krb5_data *display_value,
977                     int *more)
978 {
979     struct mspac_context *pacctx = (struct mspac_context *)request_context;
980     krb5_error_code code;
981     krb5_ui_4 type;
982
983     if (display_value != NULL) {
984         display_value->data = NULL;
985         display_value->length = 0;
986     }
987
988     if (*more != -1 || pacctx->pac == NULL)
989         return ENOENT;
990
991     /* If it didn't verify, pretend it didn't exist. */
992     if (!pacctx->pac->verified) {
993         TRACE_MSPAC_DISCARD_UNVERF(kcontext);
994         return ENOENT;
995     }
996
997     code = mspac_attr2type(attribute, &type);
998     if (code != 0)
999         return code;
1000
1001     /* -1 is a magic type that refers to the entire PAC */
1002     if (type == (krb5_ui_4)-1) {
1003         if (value != NULL)
1004             code = krb5int_copy_data_contents(kcontext,
1005                                               &pacctx->pac->data,
1006                                               value);
1007         else
1008             code = 0;
1009     } else {
1010         if (value != NULL)
1011             code = krb5_pac_get_buffer(kcontext, pacctx->pac, type, value);
1012         else
1013             code = k5_pac_locate_buffer(kcontext, pacctx->pac, type, NULL);
1014     }
1015     if (code == 0) {
1016         *authenticated = pacctx->pac->verified;
1017         *complete = TRUE;
1018     }
1019
1020     *more = 0;
1021
1022     return code;
1023 }
1024
1025 static krb5_error_code
1026 mspac_set_attribute(krb5_context kcontext,
1027                     krb5_authdata_context context,
1028                     void *plugin_context,
1029                     void *request_context,
1030                     krb5_boolean complete,
1031                     const krb5_data *attribute,
1032                     const krb5_data *value)
1033 {
1034     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1035     krb5_error_code code;
1036     krb5_ui_4 type;
1037
1038     if (pacctx->pac == NULL)
1039         return ENOENT;
1040
1041     code = mspac_attr2type(attribute, &type);
1042     if (code != 0)
1043         return code;
1044
1045     /* -1 is a magic type that refers to the entire PAC */
1046     if (type == (krb5_ui_4)-1) {
1047         krb5_pac newpac;
1048
1049         code = krb5_pac_parse(kcontext, value->data, value->length, &newpac);
1050         if (code != 0)
1051             return code;
1052
1053         krb5_pac_free(kcontext, pacctx->pac);
1054         pacctx->pac = newpac;
1055     } else {
1056         code = krb5_pac_add_buffer(kcontext, pacctx->pac, type, value);
1057     }
1058
1059     return code;
1060 }
1061
1062 static krb5_error_code
1063 mspac_export_internal(krb5_context kcontext,
1064                       krb5_authdata_context context,
1065                       void *plugin_context,
1066                       void *request_context,
1067                       krb5_boolean restrict_authenticated,
1068                       void **ptr)
1069 {
1070     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1071     krb5_error_code code;
1072     krb5_pac pac;
1073
1074     *ptr = NULL;
1075
1076     if (pacctx->pac == NULL)
1077         return ENOENT;
1078
1079     if (restrict_authenticated && (pacctx->pac->verified) == FALSE)
1080         return ENOENT;
1081
1082     code = krb5_pac_parse(kcontext, pacctx->pac->data.data,
1083                           pacctx->pac->data.length, &pac);
1084     if (code == 0) {
1085         pac->verified = pacctx->pac->verified;
1086         *ptr = pac;
1087     }
1088
1089     return code;
1090 }
1091
1092 static void
1093 mspac_free_internal(krb5_context kcontext,
1094                     krb5_authdata_context context,
1095                     void *plugin_context,
1096                     void *request_context,
1097                     void *ptr)
1098 {
1099     if (ptr != NULL)
1100         krb5_pac_free(kcontext, (krb5_pac)ptr);
1101
1102     return;
1103 }
1104
1105 static krb5_error_code
1106 mspac_size(krb5_context kcontext,
1107            krb5_authdata_context context,
1108            void *plugin_context,
1109            void *request_context,
1110            size_t *sizep)
1111 {
1112     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1113
1114     *sizep += sizeof(krb5_int32);
1115
1116     if (pacctx->pac != NULL)
1117         *sizep += pacctx->pac->data.length;
1118
1119     *sizep += sizeof(krb5_int32);
1120
1121     return 0;
1122 }
1123
1124 static krb5_error_code
1125 mspac_externalize(krb5_context kcontext,
1126                   krb5_authdata_context context,
1127                   void *plugin_context,
1128                   void *request_context,
1129                   krb5_octet **buffer,
1130                   size_t *lenremain)
1131 {
1132     krb5_error_code code = 0;
1133     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1134     size_t required = 0;
1135     krb5_octet *bp;
1136     size_t remain;
1137
1138     bp = *buffer;
1139     remain = *lenremain;
1140
1141     if (pacctx->pac != NULL) {
1142         mspac_size(kcontext, context, plugin_context,
1143                    request_context, &required);
1144
1145         if (required <= remain) {
1146             krb5_ser_pack_int32((krb5_int32)pacctx->pac->data.length,
1147                                 &bp, &remain);
1148             krb5_ser_pack_bytes((krb5_octet *)pacctx->pac->data.data,
1149                                 (size_t)pacctx->pac->data.length,
1150                                 &bp, &remain);
1151             krb5_ser_pack_int32((krb5_int32)pacctx->pac->verified,
1152                                 &bp, &remain);
1153         } else {
1154             code = ENOMEM;
1155         }
1156     } else {
1157         krb5_ser_pack_int32(0, &bp, &remain); /* length */
1158         krb5_ser_pack_int32(0, &bp, &remain); /* verified */
1159     }
1160
1161     *buffer = bp;
1162     *lenremain = remain;
1163
1164     return code;
1165 }
1166
1167 static krb5_error_code
1168 mspac_internalize(krb5_context kcontext,
1169                   krb5_authdata_context context,
1170                   void *plugin_context,
1171                   void *request_context,
1172                   krb5_octet **buffer,
1173                   size_t *lenremain)
1174 {
1175     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1176     krb5_error_code code;
1177     krb5_int32 ibuf;
1178     krb5_octet *bp;
1179     size_t remain;
1180     krb5_pac pac = NULL;
1181
1182     bp = *buffer;
1183     remain = *lenremain;
1184
1185     /* length */
1186     code = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
1187     if (code != 0)
1188         return code;
1189
1190     if (ibuf != 0) {
1191         code = krb5_pac_parse(kcontext, bp, ibuf, &pac);
1192         if (code != 0)
1193             return code;
1194
1195         bp += ibuf;
1196         remain -= ibuf;
1197     }
1198
1199     /* verified */
1200     code = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
1201     if (code != 0) {
1202         krb5_pac_free(kcontext, pac);
1203         return code;
1204     }
1205
1206     if (pac != NULL) {
1207         pac->verified = (ibuf != 0);
1208     }
1209
1210     if (pacctx->pac != NULL) {
1211         krb5_pac_free(kcontext, pacctx->pac);
1212     }
1213
1214     pacctx->pac = pac;
1215
1216     *buffer = bp;
1217     *lenremain = remain;
1218
1219     return 0;
1220 }
1221
1222 static krb5_error_code
1223 mspac_copy(krb5_context kcontext,
1224            krb5_authdata_context context,
1225            void *plugin_context,
1226            void *request_context,
1227            void *dst_plugin_context,
1228            void *dst_request_context)
1229 {
1230     struct mspac_context *srcctx = (struct mspac_context *)request_context;
1231     struct mspac_context *dstctx = (struct mspac_context *)dst_request_context;
1232     krb5_error_code code = 0;
1233
1234     assert(dstctx != NULL);
1235     assert(dstctx->pac == NULL);
1236
1237     if (srcctx->pac != NULL)
1238         code = k5_pac_copy(kcontext, srcctx->pac, &dstctx->pac);
1239
1240     return code;
1241 }
1242
1243 static krb5_authdatatype mspac_ad_types[] = { KRB5_AUTHDATA_WIN2K_PAC, 0 };
1244
1245 krb5plugin_authdata_client_ftable_v0 krb5int_mspac_authdata_client_ftable = {
1246     "mspac",
1247     mspac_ad_types,
1248     mspac_init,
1249     mspac_fini,
1250     mspac_flags,
1251     mspac_request_init,
1252     mspac_request_fini,
1253     mspac_get_attribute_types,
1254     mspac_get_attribute,
1255     mspac_set_attribute,
1256     NULL, /* delete_attribute_proc */
1257     mspac_export_authdata,
1258     mspac_import_authdata,
1259     mspac_export_internal,
1260     mspac_free_internal,
1261     mspac_verify,
1262     mspac_size,
1263     mspac_externalize,
1264     mspac_internalize,
1265     mspac_copy
1266 };