e830ab34129f4aec93284ea7e1f30c6c9d85eea1
[metze/samba/wip.git] / source / heimdal / lib / krb5 / keytab_file.c
1 /*
2  * Copyright (c) 1997 - 2008 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 23469 2008-07-27 12:17:12Z lha $");
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_message(context, ENOMEM, "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_message(context, ENOMEM, "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 len;
168     
169     ALLOC(p, 1);
170     if(p == NULL) {
171         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
172         return ENOMEM;
173     }
174
175     ret = krb5_ret_int16(sp, &len);
176     if(ret) {
177         krb5_set_error_message(context, ret,
178                                "Failed decoding length of keytab principal");
179         goto out;
180     }
181     if(krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS))
182         len--;
183     if (len < 0) {
184         ret = KRB5_KT_END;
185         krb5_set_error_message(context, ret,
186                                "Keytab principal contains invalid length");
187         goto out;
188     }
189     ret = krb5_kt_ret_string(context, sp, &p->realm);
190     if(ret)
191         goto out;
192     p->name.name_string.val = calloc(len, sizeof(*p->name.name_string.val));
193     if(p->name.name_string.val == NULL) {
194         ret = ENOMEM;
195         krb5_set_error_message(context, ret, "malloc: out of memory");
196         goto out;
197     }
198     p->name.name_string.len = len;
199     for(i = 0; i < p->name.name_string.len; i++){
200         ret = krb5_kt_ret_string(context, sp, p->name.name_string.val + i);
201         if(ret)
202             goto out;
203     }
204     if (krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE))
205         p->name.name_type = KRB5_NT_UNKNOWN;
206     else {
207         int32_t tmp32;
208         ret = krb5_ret_int32(sp, &tmp32);
209         p->name.name_type = tmp32;
210         if (ret)
211             goto out;
212     }
213     *princ = p;
214     return 0;
215 out:
216     krb5_free_principal(context, p);
217     return ret;
218 }
219
220 static krb5_error_code
221 krb5_kt_store_principal(krb5_context context,
222                         krb5_storage *sp,
223                         krb5_principal p)
224 {
225     int i;
226     int ret;
227     
228     if(krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS))
229         ret = krb5_store_int16(sp, p->name.name_string.len + 1);
230     else
231         ret = krb5_store_int16(sp, p->name.name_string.len);
232     if(ret) return ret;
233     ret = krb5_kt_store_string(sp, p->realm);
234     if(ret) return ret;
235     for(i = 0; i < p->name.name_string.len; i++){
236         ret = krb5_kt_store_string(sp, p->name.name_string.val[i]);
237         if(ret)
238             return ret;
239     }
240     if(!krb5_storage_is_flags(sp, KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE)) {
241         ret = krb5_store_int32(sp, p->name.name_type);
242         if(ret)
243             return ret;
244     }
245
246     return 0;
247 }
248
249 static krb5_error_code
250 fkt_resolve(krb5_context context, const char *name, krb5_keytab id)
251 {
252     struct fkt_data *d;
253
254     d = malloc(sizeof(*d));
255     if(d == NULL) {
256         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
257         return ENOMEM;
258     }
259     d->filename = strdup(name);
260     if(d->filename == NULL) {
261         free(d);
262         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
263         return ENOMEM;
264     }
265     d->flags = 0;
266     id->data = d;
267     return 0;
268 }
269
270 static krb5_error_code
271 fkt_resolve_java14(krb5_context context, const char *name, krb5_keytab id)
272 {
273     krb5_error_code ret;
274
275     ret = fkt_resolve(context, name, id);
276     if (ret == 0) {
277         struct fkt_data *d = id->data;
278         d->flags |= KRB5_KT_FL_JAVA;
279     }
280     return ret;
281 }
282
283 static krb5_error_code
284 fkt_close(krb5_context context, krb5_keytab id)
285 {
286     struct fkt_data *d = id->data;
287     free(d->filename);
288     free(d);
289     return 0;
290 }
291
292 static krb5_error_code 
293 fkt_get_name(krb5_context context, 
294              krb5_keytab id, 
295              char *name, 
296              size_t namesize)
297 {
298     /* This function is XXX */
299     struct fkt_data *d = id->data;
300     strlcpy(name, d->filename, namesize);
301     return 0;
302 }
303
304 static void
305 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
306 {
307     int flags = 0;
308     switch(vno) {
309     case KRB5_KT_VNO_1:
310         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
311         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
312         flags |= KRB5_STORAGE_HOST_BYTEORDER;
313         break;
314     case KRB5_KT_VNO_2:
315         break;
316     default:
317         krb5_warnx(context, 
318                    "storage_set_flags called with bad vno (%d)", vno);
319     }
320     krb5_storage_set_flags(sp, flags);
321 }
322
323 static krb5_error_code
324 fkt_start_seq_get_int(krb5_context context, 
325                       krb5_keytab id, 
326                       int flags,
327                       int exclusive,
328                       krb5_kt_cursor *c)
329 {
330     int8_t pvno, tag;
331     krb5_error_code ret;
332     struct fkt_data *d = id->data;
333     
334     c->fd = open (d->filename, flags);
335     if (c->fd < 0) {
336         ret = errno;
337         krb5_set_error_message(context, ret, "keytab %s open failed: %s",
338                                d->filename, strerror(ret));
339         return ret;
340     }
341     rk_cloexec(c->fd);
342     ret = _krb5_xlock(context, c->fd, exclusive, d->filename);
343     if (ret) {
344         close(c->fd);
345         return ret;
346     }
347     c->sp = krb5_storage_from_fd(c->fd);
348     if (c->sp == NULL) {
349         _krb5_xunlock(context, c->fd);
350         close(c->fd);
351         krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
352         return ENOMEM;
353     }
354     krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
355     ret = krb5_ret_int8(c->sp, &pvno);
356     if(ret) {
357         krb5_storage_free(c->sp);
358         _krb5_xunlock(context, c->fd);
359         close(c->fd);
360         krb5_clear_error_string(context);
361         return ret;
362     }
363     if(pvno != 5) {
364         krb5_storage_free(c->sp);
365         _krb5_xunlock(context, c->fd);
366         close(c->fd);
367         krb5_clear_error_string (context);
368         return KRB5_KEYTAB_BADVNO;
369     }
370     ret = krb5_ret_int8(c->sp, &tag);
371     if (ret) {
372         krb5_storage_free(c->sp);
373         _krb5_xunlock(context, c->fd);
374         close(c->fd);
375         krb5_clear_error_string(context);
376         return ret;
377     }
378     id->version = tag;
379     storage_set_flags(context, c->sp, id->version);
380     return 0;
381 }
382
383 static krb5_error_code
384 fkt_start_seq_get(krb5_context context, 
385                   krb5_keytab id, 
386                   krb5_kt_cursor *c)
387 {
388     return fkt_start_seq_get_int(context, id, O_RDONLY | O_BINARY, 0, c);
389 }
390
391 static krb5_error_code
392 fkt_next_entry_int(krb5_context context, 
393                    krb5_keytab id, 
394                    krb5_keytab_entry *entry, 
395                    krb5_kt_cursor *cursor,
396                    off_t *start,
397                    off_t *end)
398 {
399     int32_t len;
400     int ret;
401     int8_t tmp8;
402     int32_t tmp32;
403     off_t pos, curpos;
404
405     pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
406 loop:
407     ret = krb5_ret_int32(cursor->sp, &len);
408     if (ret)
409         return ret;
410     if(len < 0) {
411         pos = krb5_storage_seek(cursor->sp, -len, SEEK_CUR);
412         goto loop;
413     }
414     ret = krb5_kt_ret_principal (context, cursor->sp, &entry->principal);
415     if (ret)
416         goto out;
417     ret = krb5_ret_int32(cursor->sp, &tmp32);
418     entry->timestamp = tmp32;
419     if (ret)
420         goto out;
421     ret = krb5_ret_int8(cursor->sp, &tmp8);
422     if (ret)
423         goto out;
424     entry->vno = tmp8;
425     ret = krb5_kt_ret_keyblock (context, cursor->sp, &entry->keyblock);
426     if (ret)
427         goto out;
428     /* there might be a 32 bit kvno here
429      * if it's zero, assume that the 8bit one was right,
430      * otherwise trust the new value */
431     curpos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
432     if(len + 4 + pos - curpos >= 4) {
433         ret = krb5_ret_int32(cursor->sp, &tmp32);
434         if (ret == 0 && tmp32 != 0) {
435             entry->vno = tmp32;
436         }
437     }
438     if(start) *start = pos;
439     if(end) *end = pos + 4 + len;
440  out:
441     krb5_storage_seek(cursor->sp, pos + 4 + len, SEEK_SET);
442     return ret;
443 }
444
445 static krb5_error_code
446 fkt_next_entry(krb5_context context, 
447                krb5_keytab id, 
448                krb5_keytab_entry *entry, 
449                krb5_kt_cursor *cursor)
450 {
451     return fkt_next_entry_int(context, id, entry, cursor, NULL, NULL);
452 }
453
454 static krb5_error_code
455 fkt_end_seq_get(krb5_context context, 
456                 krb5_keytab id,
457                 krb5_kt_cursor *cursor)
458 {
459     krb5_storage_free(cursor->sp);
460     _krb5_xunlock(context, cursor->fd);
461     close(cursor->fd);
462     return 0;
463 }
464
465 static krb5_error_code
466 fkt_setup_keytab(krb5_context context,
467                  krb5_keytab id,
468                  krb5_storage *sp)
469 {
470     krb5_error_code ret;
471     ret = krb5_store_int8(sp, 5);
472     if(ret)
473         return ret;
474     if(id->version == 0)
475         id->version = KRB5_KT_VNO;
476     return krb5_store_int8 (sp, id->version);
477 }
478                  
479 static krb5_error_code
480 fkt_add_entry(krb5_context context,
481               krb5_keytab id,
482               krb5_keytab_entry *entry)
483 {
484     int ret;
485     int fd;
486     krb5_storage *sp;
487     struct fkt_data *d = id->data;
488     krb5_data keytab;
489     int32_t len;
490     
491     fd = open (d->filename, O_RDWR | O_BINARY);
492     if (fd < 0) {
493         fd = open (d->filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
494         if (fd < 0) {
495             ret = errno;
496             krb5_set_error_message(context, ret, "open(%s): %s", d->filename,
497                                    strerror(ret));
498             return ret;
499         }
500         rk_cloexec(fd);
501
502         ret = _krb5_xlock(context, fd, 1, d->filename);
503         if (ret) {
504             close(fd);
505             return ret;
506         }
507         sp = krb5_storage_from_fd(fd);
508         krb5_storage_set_eof_code(sp, KRB5_KT_END);
509         ret = fkt_setup_keytab(context, id, sp);
510         if(ret) {
511             goto out;
512         }
513         storage_set_flags(context, sp, id->version);
514     } else {
515         int8_t pvno, tag;
516
517         rk_cloexec(fd);
518
519         ret = _krb5_xlock(context, fd, 1, d->filename);
520         if (ret) {
521             close(fd);
522             return ret;
523         }
524         sp = krb5_storage_from_fd(fd);
525         krb5_storage_set_eof_code(sp, KRB5_KT_END);
526         ret = krb5_ret_int8(sp, &pvno);
527         if(ret) {
528             /* we probably have a zero byte file, so try to set it up
529                properly */
530             ret = fkt_setup_keytab(context, id, sp);
531             if(ret) {
532                 krb5_set_error_message(context, ret, "%s: keytab is corrupted: %s", 
533                                        d->filename, strerror(ret));
534                 goto out;
535             }
536             storage_set_flags(context, sp, id->version);
537         } else {
538             if(pvno != 5) {
539                 ret = KRB5_KEYTAB_BADVNO;
540                 krb5_set_error_message(context, ret, "%s: %s", 
541                                        d->filename, strerror(ret));
542                 goto out;
543             }
544             ret = krb5_ret_int8 (sp, &tag);
545             if (ret) {
546                 krb5_set_error_message(context, ret, "%s: reading tag: %s", 
547                                        d->filename, strerror(ret));
548                 goto out;
549             }
550             id->version = tag;
551             storage_set_flags(context, sp, id->version);
552         }
553     }
554
555     {
556         krb5_storage *emem;
557         emem = krb5_storage_emem();
558         if(emem == NULL) {
559             ret = ENOMEM;
560             krb5_set_error_message(context, ret, "malloc: out of memory");
561             goto out;
562         }
563         ret = krb5_kt_store_principal(context, emem, entry->principal);
564         if(ret) {
565             krb5_storage_free(emem);
566             goto out;
567         }
568         ret = krb5_store_int32 (emem, entry->timestamp);
569         if(ret) {
570             krb5_storage_free(emem);
571             goto out;
572         }
573         ret = krb5_store_int8 (emem, entry->vno % 256);
574         if(ret) {
575             krb5_storage_free(emem);
576             goto out;
577         }
578         ret = krb5_kt_store_keyblock (context, emem, &entry->keyblock);
579         if(ret) {
580             krb5_storage_free(emem);
581             goto out;
582         }
583         if ((d->flags & KRB5_KT_FL_JAVA) == 0) {
584             ret = krb5_store_int32 (emem, entry->vno);
585             if (ret) {
586                 krb5_storage_free(emem);
587                 goto out;
588             }
589         }
590
591         ret = krb5_storage_to_data(emem, &keytab);
592         krb5_storage_free(emem);
593         if(ret)
594             goto out;
595     }
596     
597     while(1) {
598         ret = krb5_ret_int32(sp, &len);
599         if(ret == KRB5_KT_END) {
600             len = keytab.length;
601             break;
602         }
603         if(len < 0) {
604             len = -len;
605             if(len >= keytab.length) {
606                 krb5_storage_seek(sp, -4, SEEK_CUR);
607                 break;
608             }
609         }
610         krb5_storage_seek(sp, len, SEEK_CUR);
611     }
612     ret = krb5_store_int32(sp, len);
613     if(krb5_storage_write(sp, keytab.data, keytab.length) < 0)
614         ret = errno;
615     memset(keytab.data, 0, keytab.length);
616     krb5_data_free(&keytab);
617   out:
618     krb5_storage_free(sp);
619     _krb5_xunlock(context, fd);
620     close(fd);
621     return ret;
622 }
623
624 static krb5_error_code
625 fkt_remove_entry(krb5_context context,
626                  krb5_keytab id,
627                  krb5_keytab_entry *entry)
628 {
629     krb5_keytab_entry e;
630     krb5_kt_cursor cursor;
631     off_t pos_start, pos_end;
632     int found = 0;
633     krb5_error_code ret;
634     
635     ret = fkt_start_seq_get_int(context, id, O_RDWR | O_BINARY, 1, &cursor);
636     if(ret != 0) 
637         goto out; /* return other error here? */
638     while(fkt_next_entry_int(context, id, &e, &cursor, 
639                              &pos_start, &pos_end) == 0) {
640         if(krb5_kt_compare(context, &e, entry->principal, 
641                            entry->vno, entry->keyblock.keytype)) {
642             int32_t len;
643             unsigned char buf[128];
644             found = 1;
645             krb5_storage_seek(cursor.sp, pos_start, SEEK_SET);
646             len = pos_end - pos_start - 4;
647             krb5_store_int32(cursor.sp, -len);
648             memset(buf, 0, sizeof(buf));
649             while(len > 0) {
650                 krb5_storage_write(cursor.sp, buf, min(len, sizeof(buf)));
651                 len -= min(len, sizeof(buf));
652             }
653         }
654         krb5_kt_free_entry(context, &e);
655     }
656     krb5_kt_end_seq_get(context, id, &cursor);
657   out:
658     if (!found) {
659         krb5_clear_error_string (context);
660         return KRB5_KT_NOTFOUND;
661     }
662     return 0;
663 }
664
665 const krb5_kt_ops krb5_fkt_ops = {
666     "FILE",
667     fkt_resolve,
668     fkt_get_name,
669     fkt_close,
670     NULL, /* get */
671     fkt_start_seq_get,
672     fkt_next_entry,
673     fkt_end_seq_get,
674     fkt_add_entry,
675     fkt_remove_entry
676 };
677
678 const krb5_kt_ops krb5_wrfkt_ops = {
679     "WRFILE",
680     fkt_resolve,
681     fkt_get_name,
682     fkt_close,
683     NULL, /* get */
684     fkt_start_seq_get,
685     fkt_next_entry,
686     fkt_end_seq_get,
687     fkt_add_entry,
688     fkt_remove_entry
689 };
690
691 const krb5_kt_ops krb5_javakt_ops = {
692     "JAVA14",
693     fkt_resolve_java14,
694     fkt_get_name,
695     fkt_close,
696     NULL, /* get */
697     fkt_start_seq_get,
698     fkt_next_entry,
699     fkt_end_seq_get,
700     fkt_add_entry,
701     fkt_remove_entry
702 };