r8302: import mini HEIMDAL into the tree
[samba.git] / source4 / heimdal / lib / krb5 / keytab_file.c
1 /*
2  * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden). 
4  * All rights reserved. 
5  *
6  * Redistribution and use in source and binary forms, with or without 
7  * modification, are permitted provided that the following conditions 
8  * are met: 
9  *
10  * 1. Redistributions of source code must retain the above copyright 
11  *    notice, this list of conditions and the following disclaimer. 
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright 
14  *    notice, this list of conditions and the following disclaimer in the 
15  *    documentation and/or other materials provided with the distribution. 
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors 
18  *    may be used to endorse or promote products derived from this software 
19  *    without specific prior written permission. 
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
31  * SUCH DAMAGE. 
32  */
33
34 #include "krb5_locl.h"
35
36 RCSID("$Id: keytab_file.c,v 1.18 2005/05/31 21:50:43 lha Exp $");
37
38 #define KRB5_KT_VNO_1 1
39 #define KRB5_KT_VNO_2 2
40 #define KRB5_KT_VNO   KRB5_KT_VNO_2
41
42 #define KRB5_KT_FL_JAVA 1
43
44
45 /* file operations -------------------------------------------- */
46
47 struct fkt_data {
48     char *filename;
49     int flags;
50 };
51
52 static krb5_error_code
53 krb5_kt_ret_data(krb5_context context,
54                  krb5_storage *sp,
55                  krb5_data *data)
56 {
57     int ret;
58     int16_t size;
59     ret = krb5_ret_int16(sp, &size);
60     if(ret)
61         return ret;
62     data->length = size;
63     data->data = malloc(size);
64     if (data->data == NULL) {
65         krb5_set_error_string (context, "malloc: out of memory");
66         return ENOMEM;
67     }
68     ret = krb5_storage_read(sp, data->data, size);
69     if(ret != size)
70         return (ret < 0)? errno : KRB5_KT_END;
71     return 0;
72 }
73
74 static krb5_error_code
75 krb5_kt_ret_string(krb5_context context,
76                    krb5_storage *sp,
77                    heim_general_string *data)
78 {
79     int ret;
80     int16_t size;
81     ret = krb5_ret_int16(sp, &size);
82     if(ret)
83         return ret;
84     *data = malloc(size + 1);
85     if (*data == NULL) {
86         krb5_set_error_string (context, "malloc: out of memory");
87         return ENOMEM;
88     }
89     ret = krb5_storage_read(sp, *data, size);
90     (*data)[size] = '\0';
91     if(ret != size)
92         return (ret < 0)? errno : KRB5_KT_END;
93     return 0;
94 }
95
96 static krb5_error_code
97 krb5_kt_store_data(krb5_context context,
98                    krb5_storage *sp,
99                    krb5_data data)
100 {
101     int ret;
102     ret = krb5_store_int16(sp, data.length);
103     if(ret < 0)
104         return ret;
105     ret = krb5_storage_write(sp, data.data, data.length);
106     if(ret != data.length){
107         if(ret < 0)
108             return errno;
109         return KRB5_KT_END;
110     }
111     return 0;
112 }
113
114 static krb5_error_code
115 krb5_kt_store_string(krb5_storage *sp,
116                      heim_general_string data)
117 {
118     int ret;
119     size_t len = strlen(data);
120     ret = krb5_store_int16(sp, len);
121     if(ret < 0)
122         return ret;
123     ret = krb5_storage_write(sp, data, len);
124     if(ret != len){
125         if(ret < 0)
126             return errno;
127         return KRB5_KT_END;
128     }
129     return 0;
130 }
131
132 static krb5_error_code
133 krb5_kt_ret_keyblock(krb5_context context, krb5_storage *sp, krb5_keyblock *p)
134 {
135     int ret;
136     int16_t tmp;
137
138     ret = krb5_ret_int16(sp, &tmp); /* keytype + etype */
139     if(ret) return ret;
140     p->keytype = tmp;
141     ret = krb5_kt_ret_data(context, sp, &p->keyvalue);
142     return ret;
143 }
144
145 static krb5_error_code
146 krb5_kt_store_keyblock(krb5_context context,
147                        krb5_storage *sp, 
148                        krb5_keyblock *p)
149 {
150     int ret;
151
152     ret = krb5_store_int16(sp, p->keytype); /* keytype + etype */
153     if(ret) return ret;
154     ret = krb5_kt_store_data(context, sp, p->keyvalue);
155     return ret;
156 }
157
158
159 static krb5_error_code
160 krb5_kt_ret_principal(krb5_context context,
161                       krb5_storage *sp,
162                       krb5_principal *princ)
163 {
164     int i;
165     int ret;
166     krb5_principal p;
167     int16_t tmp;
168     
169     ALLOC(p, 1);
170     if(p == NULL) {
171         krb5_set_error_string (context, "malloc: out of memory");
172         return ENOMEM;
173     }
174
175     ret = krb5_ret_int16(sp, &tmp);
176     if(ret)
177         return ret;
178     if(krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS))
179         tmp--;
180     p->name.name_string.len = tmp;
181     ret = krb5_kt_ret_string(context, sp, &p->realm);
182     if(ret)
183         return ret;
184     p->name.name_string.val = calloc(p->name.name_string.len, 
185                                      sizeof(*p->name.name_string.val));
186     if(p->name.name_string.val == NULL) {
187         krb5_set_error_string (context, "malloc: out of memory");
188         return ENOMEM;
189     }
190     for(i = 0; i < p->name.name_string.len; i++){
191         ret = krb5_kt_ret_string(context, sp, p->name.name_string.val + i);
192         if(ret)
193             return ret;
194     }
195     if (krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE))
196         p->name.name_type = KRB5_NT_UNKNOWN;
197     else {
198         int32_t tmp32;
199         ret = krb5_ret_int32(sp, &tmp32);
200         p->name.name_type = tmp32;
201         if (ret)
202             return ret;
203     }
204     *princ = p;
205     return 0;
206 }
207
208 static krb5_error_code
209 krb5_kt_store_principal(krb5_context context,
210                         krb5_storage *sp,
211                         krb5_principal p)
212 {
213     int i;
214     int ret;
215     
216     if(krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS))
217         ret = krb5_store_int16(sp, p->name.name_string.len + 1);
218     else
219         ret = krb5_store_int16(sp, p->name.name_string.len);
220     if(ret) return ret;
221     ret = krb5_kt_store_string(sp, p->realm);
222     if(ret) return ret;
223     for(i = 0; i < p->name.name_string.len; i++){
224         ret = krb5_kt_store_string(sp, p->name.name_string.val[i]);
225         if(ret)
226             return ret;
227     }
228     if(!krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE)) {
229         ret = krb5_store_int32(sp, p->name.name_type);
230         if(ret)
231             return ret;
232     }
233
234     return 0;
235 }
236
237 static krb5_error_code
238 fkt_resolve(krb5_context context, const char *name, krb5_keytab id)
239 {
240     struct fkt_data *d;
241
242     d = malloc(sizeof(*d));
243     if(d == NULL) {
244         krb5_set_error_string (context, "malloc: out of memory");
245         return ENOMEM;
246     }
247     d->filename = strdup(name);
248     if(d->filename == NULL) {
249         free(d);
250         krb5_set_error_string (context, "malloc: out of memory");
251         return ENOMEM;
252     }
253     d->flags = 0;
254     id->data = d;
255     return 0;
256 }
257
258 static krb5_error_code
259 fkt_resolve_java14(krb5_context context, const char *name, krb5_keytab id)
260 {
261     krb5_error_code ret;
262
263     ret = fkt_resolve(context, name, id);
264     if (ret == 0) {
265         struct fkt_data *d = id->data;
266         d->flags |= KRB5_KT_FL_JAVA;
267     }
268     return ret;
269 }
270
271 static krb5_error_code
272 fkt_close(krb5_context context, krb5_keytab id)
273 {
274     struct fkt_data *d = id->data;
275     free(d->filename);
276     free(d);
277     return 0;
278 }
279
280 static krb5_error_code 
281 fkt_get_name(krb5_context context, 
282              krb5_keytab id, 
283              char *name, 
284              size_t namesize)
285 {
286     /* This function is XXX */
287     struct fkt_data *d = id->data;
288     strlcpy(name, d->filename, namesize);
289     return 0;
290 }
291
292 static void
293 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
294 {
295     int flags = 0;
296     switch(vno) {
297     case KRB5_KT_VNO_1:
298         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
299         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
300         flags |= KRB5_STORAGE_HOST_BYTEORDER;
301         break;
302     case KRB5_KT_VNO_2:
303         break;
304     default:
305         krb5_warnx(context, 
306                    "storage_set_flags called with bad vno (%d)", vno);
307     }
308     krb5_storage_set_flags(sp, flags);
309 }
310
311 static krb5_error_code
312 fkt_start_seq_get_int(krb5_context context, 
313                       krb5_keytab id, 
314                       int flags,
315                       int exclusive,
316                       krb5_kt_cursor *c)
317 {
318     int8_t pvno, tag;
319     krb5_error_code ret;
320     struct fkt_data *d = id->data;
321     
322     c->fd = open (d->filename, flags);
323     if (c->fd < 0) {
324         ret = errno;
325         krb5_set_error_string(context, "%s: %s", d->filename,
326                               strerror(ret));
327         return ret;
328     }
329     ret = _krb5_xlock(context, c->fd, exclusive, d->filename);
330     if (ret) {
331         close(c->fd);
332         return ret;
333     }
334     c->sp = krb5_storage_from_fd(c->fd);
335     krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
336     ret = krb5_ret_int8(c->sp, &pvno);
337     if(ret) {
338         krb5_storage_free(c->sp);
339         _krb5_xunlock(context, c->fd);
340         close(c->fd);
341         krb5_clear_error_string(context);
342         return ret;
343     }
344     if(pvno != 5) {
345         krb5_storage_free(c->sp);
346         _krb5_xunlock(context, c->fd);
347         close(c->fd);
348         krb5_clear_error_string (context);
349         return KRB5_KEYTAB_BADVNO;
350     }
351     ret = krb5_ret_int8(c->sp, &tag);
352     if (ret) {
353         krb5_storage_free(c->sp);
354         _krb5_xunlock(context, c->fd);
355         close(c->fd);
356         krb5_clear_error_string(context);
357         return ret;
358     }
359     id->version = tag;
360     storage_set_flags(context, c->sp, id->version);
361     return 0;
362 }
363
364 static krb5_error_code
365 fkt_start_seq_get(krb5_context context, 
366                   krb5_keytab id, 
367                   krb5_kt_cursor *c)
368 {
369     return fkt_start_seq_get_int(context, id, O_RDONLY | O_BINARY, 0, c);
370 }
371
372 static krb5_error_code
373 fkt_next_entry_int(krb5_context context, 
374                    krb5_keytab id, 
375                    krb5_keytab_entry *entry, 
376                    krb5_kt_cursor *cursor,
377                    off_t *start,
378                    off_t *end)
379 {
380     int32_t len;
381     int ret;
382     int8_t tmp8;
383     int32_t tmp32;
384     off_t pos, curpos;
385
386     pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
387 loop:
388     ret = krb5_ret_int32(cursor->sp, &len);
389     if (ret)
390         return ret;
391     if(len < 0) {
392         pos = krb5_storage_seek(cursor->sp, -len, SEEK_CUR);
393         goto loop;
394     }
395     ret = krb5_kt_ret_principal (context, cursor->sp, &entry->principal);
396     if (ret)
397         goto out;
398     ret = krb5_ret_int32(cursor->sp, &tmp32);
399     entry->timestamp = tmp32;
400     if (ret)
401         goto out;
402     ret = krb5_ret_int8(cursor->sp, &tmp8);
403     if (ret)
404         goto out;
405     entry->vno = tmp8;
406     ret = krb5_kt_ret_keyblock (context, cursor->sp, &entry->keyblock);
407     if (ret)
408         goto out;
409     /* there might be a 32 bit kvno here
410      * if it's zero, assume that the 8bit one was right,
411      * otherwise trust the new value */
412     curpos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
413     if(len + 4 + pos - curpos == 4) {
414         ret = krb5_ret_int32(cursor->sp, &tmp32);
415         if (ret == 0 && tmp32 != 0) {
416             entry->vno = tmp32;
417         }
418     }
419     if(start) *start = pos;
420     if(end) *end = *start + 4 + len;
421  out:
422     krb5_storage_seek(cursor->sp, pos + 4 + len, SEEK_SET);
423     return ret;
424 }
425
426 static krb5_error_code
427 fkt_next_entry(krb5_context context, 
428                krb5_keytab id, 
429                krb5_keytab_entry *entry, 
430                krb5_kt_cursor *cursor)
431 {
432     return fkt_next_entry_int(context, id, entry, cursor, NULL, NULL);
433 }
434
435 static krb5_error_code
436 fkt_end_seq_get(krb5_context context, 
437                 krb5_keytab id,
438                 krb5_kt_cursor *cursor)
439 {
440     krb5_storage_free(cursor->sp);
441     _krb5_xunlock(context, cursor->fd);
442     close(cursor->fd);
443     return 0;
444 }
445
446 static krb5_error_code
447 fkt_setup_keytab(krb5_context context,
448                  krb5_keytab id,
449                  krb5_storage *sp)
450 {
451     krb5_error_code ret;
452     ret = krb5_store_int8(sp, 5);
453     if(ret)
454         return ret;
455     if(id->version == 0)
456         id->version = KRB5_KT_VNO;
457     return krb5_store_int8 (sp, id->version);
458 }
459                  
460 static krb5_error_code
461 fkt_add_entry(krb5_context context,
462               krb5_keytab id,
463               krb5_keytab_entry *entry)
464 {
465     int ret;
466     int fd;
467     krb5_storage *sp;
468     struct fkt_data *d = id->data;
469     krb5_data keytab;
470     int32_t len;
471     
472     fd = open (d->filename, O_RDWR | O_BINARY);
473     if (fd < 0) {
474         fd = open (d->filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
475         if (fd < 0) {
476             ret = errno;
477             krb5_set_error_string(context, "open(%s): %s", d->filename,
478                                   strerror(ret));
479             return ret;
480         }
481         ret = _krb5_xlock(context, fd, 1, d->filename);
482         if (ret) {
483             close(fd);
484             return ret;
485         }
486         sp = krb5_storage_from_fd(fd);
487         krb5_storage_set_eof_code(sp, KRB5_KT_END);
488         ret = fkt_setup_keytab(context, id, sp);
489         if(ret) {
490             goto out;
491         }
492         storage_set_flags(context, sp, id->version);
493     } else {
494         int8_t pvno, tag;
495         ret = _krb5_xlock(context, fd, 1, d->filename);
496         if (ret) {
497             close(fd);
498             return ret;
499         }
500         sp = krb5_storage_from_fd(fd);
501         krb5_storage_set_eof_code(sp, KRB5_KT_END);
502         ret = krb5_ret_int8(sp, &pvno);
503         if(ret) {
504             /* we probably have a zero byte file, so try to set it up
505                properly */
506             ret = fkt_setup_keytab(context, id, sp);
507             if(ret) {
508                 krb5_set_error_string(context, "%s: keytab is corrupted: %s", 
509                                       d->filename, strerror(ret));
510                 goto out;
511             }
512             storage_set_flags(context, sp, id->version);
513         } else {
514             if(pvno != 5) {
515                 ret = KRB5_KEYTAB_BADVNO;
516                 krb5_set_error_string(context, "%s: %s", 
517                                       d->filename, strerror(ret));
518                 goto out;
519             }
520             ret = krb5_ret_int8 (sp, &tag);
521             if (ret) {
522                 krb5_set_error_string(context, "%s: reading tag: %s", 
523                                       d->filename, strerror(ret));
524                 goto out;
525             }
526             id->version = tag;
527             storage_set_flags(context, sp, id->version);
528         }
529     }
530
531     {
532         krb5_storage *emem;
533         emem = krb5_storage_emem();
534         if(emem == NULL) {
535             ret = ENOMEM;
536             krb5_set_error_string (context, "malloc: out of memory");
537             goto out;
538         }
539         ret = krb5_kt_store_principal(context, emem, entry->principal);
540         if(ret) {
541             krb5_storage_free(emem);
542             goto out;
543         }
544         ret = krb5_store_int32 (emem, entry->timestamp);
545         if(ret) {
546             krb5_storage_free(emem);
547             goto out;
548         }
549         ret = krb5_store_int8 (emem, entry->vno % 256);
550         if(ret) {
551             krb5_storage_free(emem);
552             goto out;
553         }
554         ret = krb5_kt_store_keyblock (context, emem, &entry->keyblock);
555         if(ret) {
556             krb5_storage_free(emem);
557             goto out;
558         }
559         if ((d->flags & KRB5_KT_FL_JAVA) == 0) {
560             ret = krb5_store_int32 (emem, entry->vno);
561             if (ret) {
562                 krb5_storage_free(emem);
563                 goto out;
564             }
565         }
566
567         ret = krb5_storage_to_data(emem, &keytab);
568         krb5_storage_free(emem);
569         if(ret)
570             goto out;
571     }
572     
573     while(1) {
574         ret = krb5_ret_int32(sp, &len);
575         if(ret == KRB5_KT_END) {
576             len = keytab.length;
577             break;
578         }
579         if(len < 0) {
580             len = -len;
581             if(len >= keytab.length) {
582                 krb5_storage_seek(sp, -4, SEEK_CUR);
583                 break;
584             }
585         }
586         krb5_storage_seek(sp, len, SEEK_CUR);
587     }
588     ret = krb5_store_int32(sp, len);
589     if(krb5_storage_write(sp, keytab.data, keytab.length) < 0)
590         ret = errno;
591     memset(keytab.data, 0, keytab.length);
592     krb5_data_free(&keytab);
593   out:
594     krb5_storage_free(sp);
595     _krb5_xunlock(context, fd);
596     close(fd);
597     return ret;
598 }
599
600 static krb5_error_code
601 fkt_remove_entry(krb5_context context,
602                  krb5_keytab id,
603                  krb5_keytab_entry *entry)
604 {
605     krb5_keytab_entry e;
606     krb5_kt_cursor cursor;
607     off_t pos_start, pos_end;
608     int found = 0;
609     krb5_error_code ret;
610     
611     ret = fkt_start_seq_get_int(context, id, O_RDWR | O_BINARY, 1, &cursor);
612     if(ret != 0) 
613         goto out; /* return other error here? */
614     while(fkt_next_entry_int(context, id, &e, &cursor, 
615                              &pos_start, &pos_end) == 0) {
616         if(krb5_kt_compare(context, &e, entry->principal, 
617                            entry->vno, entry->keyblock.keytype)) {
618             int32_t len;
619             unsigned char buf[128];
620             found = 1;
621             krb5_storage_seek(cursor.sp, pos_start, SEEK_SET);
622             len = pos_end - pos_start - 4;
623             krb5_store_int32(cursor.sp, -len);
624             memset(buf, 0, sizeof(buf));
625             while(len > 0) {
626                 krb5_storage_write(cursor.sp, buf, min(len, sizeof(buf)));
627                 len -= min(len, sizeof(buf));
628             }
629         }
630         krb5_kt_free_entry(context, &e);
631     }
632     krb5_kt_end_seq_get(context, id, &cursor);
633   out:
634     if (!found) {
635         krb5_clear_error_string (context);
636         return KRB5_KT_NOTFOUND;
637     }
638     return 0;
639 }
640
641 const krb5_kt_ops krb5_fkt_ops = {
642     "FILE",
643     fkt_resolve,
644     fkt_get_name,
645     fkt_close,
646     NULL, /* get */
647     fkt_start_seq_get,
648     fkt_next_entry,
649     fkt_end_seq_get,
650     fkt_add_entry,
651     fkt_remove_entry
652 };
653
654 const krb5_kt_ops krb5_wrfkt_ops = {
655     "WRFILE",
656     fkt_resolve,
657     fkt_get_name,
658     fkt_close,
659     NULL, /* get */
660     fkt_start_seq_get,
661     fkt_next_entry,
662     fkt_end_seq_get,
663     fkt_add_entry,
664     fkt_remove_entry
665 };
666
667 const krb5_kt_ops krb5_javakt_ops = {
668     "JAVA14",
669     fkt_resolve_java14,
670     fkt_get_name,
671     fkt_close,
672     NULL, /* get */
673     fkt_start_seq_get,
674     fkt_next_entry,
675     fkt_end_seq_get,
676     fkt_add_entry,
677     fkt_remove_entry
678 };