1/*
2 * Copyright (c) 1997 - 2005 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
36RCSID("$Id: keytab_file.c 17457 2006-05-05 12:36:57Z 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
47struct fkt_data {
48    char *filename;
49    int flags;
50};
51
52static krb5_error_code
53krb5_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
74static krb5_error_code
75krb5_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
96static krb5_error_code
97krb5_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
114static krb5_error_code
115krb5_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
132static krb5_error_code
133krb5_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
145static krb5_error_code
146krb5_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
159static krb5_error_code
160krb5_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_string (context, "malloc: out of memory");
172	return ENOMEM;
173    }
174
175    ret = krb5_ret_int16(sp, &len);
176    if(ret) {
177	krb5_set_error_string(context,
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	krb5_set_error_string(context,
185			      "Keytab principal contains invalid length");
186	ret = KRB5_KT_END;
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	krb5_set_error_string (context, "malloc: out of memory");
195	ret = ENOMEM;
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;
215out:
216    krb5_free_principal(context, p);
217    return ret;
218}
219
220static krb5_error_code
221krb5_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
249static krb5_error_code
250fkt_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_string (context, "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_string (context, "malloc: out of memory");
263	return ENOMEM;
264    }
265    d->flags = 0;
266    id->data = d;
267    return 0;
268}
269
270static krb5_error_code
271fkt_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
283static krb5_error_code
284fkt_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
292static krb5_error_code
293fkt_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
304static void
305storage_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
323static krb5_error_code
324fkt_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_string(context, "%s: %s", d->filename,
338			      strerror(ret));
339	return ret;
340    }
341    ret = _krb5_xlock(context, c->fd, exclusive, d->filename);
342    if (ret) {
343	close(c->fd);
344	return ret;
345    }
346    c->sp = krb5_storage_from_fd(c->fd);
347    if (c->sp == NULL) {
348	_krb5_xunlock(context, c->fd);
349	close(c->fd);
350	krb5_set_error_string (context, "malloc: out of memory");
351	return ENOMEM;
352    }
353    krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
354    ret = krb5_ret_int8(c->sp, &pvno);
355    if(ret) {
356	krb5_storage_free(c->sp);
357	_krb5_xunlock(context, c->fd);
358	close(c->fd);
359	krb5_clear_error_string(context);
360	return ret;
361    }
362    if(pvno != 5) {
363	krb5_storage_free(c->sp);
364	_krb5_xunlock(context, c->fd);
365	close(c->fd);
366	krb5_clear_error_string (context);
367	return KRB5_KEYTAB_BADVNO;
368    }
369    ret = krb5_ret_int8(c->sp, &tag);
370    if (ret) {
371	krb5_storage_free(c->sp);
372	_krb5_xunlock(context, c->fd);
373	close(c->fd);
374	krb5_clear_error_string(context);
375	return ret;
376    }
377    id->version = tag;
378    storage_set_flags(context, c->sp, id->version);
379    return 0;
380}
381
382static krb5_error_code
383fkt_start_seq_get(krb5_context context,
384		  krb5_keytab id,
385		  krb5_kt_cursor *c)
386{
387    return fkt_start_seq_get_int(context, id, O_RDONLY | O_BINARY, 0, c);
388}
389
390static krb5_error_code
391fkt_next_entry_int(krb5_context context,
392		   krb5_keytab id,
393		   krb5_keytab_entry *entry,
394		   krb5_kt_cursor *cursor,
395		   off_t *start,
396		   off_t *end)
397{
398    int32_t len;
399    int ret;
400    int8_t tmp8;
401    int32_t tmp32;
402    off_t pos, curpos;
403
404    pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
405loop:
406    ret = krb5_ret_int32(cursor->sp, &len);
407    if (ret)
408	return ret;
409    if(len < 0) {
410	pos = krb5_storage_seek(cursor->sp, -len, SEEK_CUR);
411	goto loop;
412    }
413    ret = krb5_kt_ret_principal (context, cursor->sp, &entry->principal);
414    if (ret)
415	goto out;
416    ret = krb5_ret_int32(cursor->sp, &tmp32);
417    entry->timestamp = tmp32;
418    if (ret)
419	goto out;
420    ret = krb5_ret_int8(cursor->sp, &tmp8);
421    if (ret)
422	goto out;
423    entry->vno = tmp8;
424    ret = krb5_kt_ret_keyblock (context, cursor->sp, &entry->keyblock);
425    if (ret)
426	goto out;
427    /* there might be a 32 bit kvno here
428     * if it's zero, assume that the 8bit one was right,
429     * otherwise trust the new value */
430    curpos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
431    if(len + 4 + pos - curpos >= 4) {
432	ret = krb5_ret_int32(cursor->sp, &tmp32);
433	if (ret == 0 && tmp32 != 0) {
434	    entry->vno = tmp32;
435	}
436    }
437    if(start) *start = pos;
438    if(end) *end = pos + 4 + len;
439 out:
440    krb5_storage_seek(cursor->sp, pos + 4 + len, SEEK_SET);
441    return ret;
442}
443
444static krb5_error_code
445fkt_next_entry(krb5_context context,
446	       krb5_keytab id,
447	       krb5_keytab_entry *entry,
448	       krb5_kt_cursor *cursor)
449{
450    return fkt_next_entry_int(context, id, entry, cursor, NULL, NULL);
451}
452
453static krb5_error_code
454fkt_end_seq_get(krb5_context context,
455		krb5_keytab id,
456		krb5_kt_cursor *cursor)
457{
458    krb5_storage_free(cursor->sp);
459    _krb5_xunlock(context, cursor->fd);
460    close(cursor->fd);
461    return 0;
462}
463
464static krb5_error_code
465fkt_setup_keytab(krb5_context context,
466		 krb5_keytab id,
467		 krb5_storage *sp)
468{
469    krb5_error_code ret;
470    ret = krb5_store_int8(sp, 5);
471    if(ret)
472	return ret;
473    if(id->version == 0)
474	id->version = KRB5_KT_VNO;
475    return krb5_store_int8 (sp, id->version);
476}
477
478static krb5_error_code
479fkt_add_entry(krb5_context context,
480	      krb5_keytab id,
481	      krb5_keytab_entry *entry)
482{
483    int ret;
484    int fd;
485    krb5_storage *sp;
486    struct fkt_data *d = id->data;
487    krb5_data keytab;
488    int32_t len;
489
490    fd = open (d->filename, O_RDWR | O_BINARY);
491    if (fd < 0) {
492	fd = open (d->filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
493	if (fd < 0) {
494	    ret = errno;
495	    krb5_set_error_string(context, "open(%s): %s", d->filename,
496				  strerror(ret));
497	    return ret;
498	}
499	ret = _krb5_xlock(context, fd, 1, d->filename);
500	if (ret) {
501	    close(fd);
502	    return ret;
503	}
504	sp = krb5_storage_from_fd(fd);
505	krb5_storage_set_eof_code(sp, KRB5_KT_END);
506	ret = fkt_setup_keytab(context, id, sp);
507	if(ret) {
508	    goto out;
509	}
510	storage_set_flags(context, sp, id->version);
511    } else {
512	int8_t pvno, tag;
513	ret = _krb5_xlock(context, fd, 1, d->filename);
514	if (ret) {
515	    close(fd);
516	    return ret;
517	}
518	sp = krb5_storage_from_fd(fd);
519	krb5_storage_set_eof_code(sp, KRB5_KT_END);
520	ret = krb5_ret_int8(sp, &pvno);
521	if(ret) {
522	    /* we probably have a zero byte file, so try to set it up
523               properly */
524	    ret = fkt_setup_keytab(context, id, sp);
525	    if(ret) {
526		krb5_set_error_string(context, "%s: keytab is corrupted: %s",
527				      d->filename, strerror(ret));
528		goto out;
529	    }
530	    storage_set_flags(context, sp, id->version);
531	} else {
532	    if(pvno != 5) {
533		ret = KRB5_KEYTAB_BADVNO;
534		krb5_set_error_string(context, "%s: %s",
535				      d->filename, strerror(ret));
536		goto out;
537	    }
538	    ret = krb5_ret_int8 (sp, &tag);
539	    if (ret) {
540		krb5_set_error_string(context, "%s: reading tag: %s",
541				      d->filename, strerror(ret));
542		goto out;
543	    }
544	    id->version = tag;
545	    storage_set_flags(context, sp, id->version);
546	}
547    }
548
549    {
550	krb5_storage *emem;
551	emem = krb5_storage_emem();
552	if(emem == NULL) {
553	    ret = ENOMEM;
554	    krb5_set_error_string (context, "malloc: out of memory");
555	    goto out;
556	}
557	ret = krb5_kt_store_principal(context, emem, entry->principal);
558	if(ret) {
559	    krb5_storage_free(emem);
560	    goto out;
561	}
562	ret = krb5_store_int32 (emem, entry->timestamp);
563	if(ret) {
564	    krb5_storage_free(emem);
565	    goto out;
566	}
567	ret = krb5_store_int8 (emem, entry->vno % 256);
568	if(ret) {
569	    krb5_storage_free(emem);
570	    goto out;
571	}
572	ret = krb5_kt_store_keyblock (context, emem, &entry->keyblock);
573	if(ret) {
574	    krb5_storage_free(emem);
575	    goto out;
576	}
577	if ((d->flags & KRB5_KT_FL_JAVA) == 0) {
578	    ret = krb5_store_int32 (emem, entry->vno);
579	    if (ret) {
580		krb5_storage_free(emem);
581		goto out;
582	    }
583	}
584
585	ret = krb5_storage_to_data(emem, &keytab);
586	krb5_storage_free(emem);
587	if(ret)
588	    goto out;
589    }
590
591    while(1) {
592	ret = krb5_ret_int32(sp, &len);
593	if(ret == KRB5_KT_END) {
594	    len = keytab.length;
595	    break;
596	}
597	if(len < 0) {
598	    len = -len;
599	    if(len >= keytab.length) {
600		krb5_storage_seek(sp, -4, SEEK_CUR);
601		break;
602	    }
603	}
604	krb5_storage_seek(sp, len, SEEK_CUR);
605    }
606    ret = krb5_store_int32(sp, len);
607    if(krb5_storage_write(sp, keytab.data, keytab.length) < 0)
608	ret = errno;
609    memset(keytab.data, 0, keytab.length);
610    krb5_data_free(&keytab);
611  out:
612    krb5_storage_free(sp);
613    _krb5_xunlock(context, fd);
614    close(fd);
615    return ret;
616}
617
618static krb5_error_code
619fkt_remove_entry(krb5_context context,
620		 krb5_keytab id,
621		 krb5_keytab_entry *entry)
622{
623    krb5_keytab_entry e;
624    krb5_kt_cursor cursor;
625    off_t pos_start, pos_end;
626    int found = 0;
627    krb5_error_code ret;
628
629    ret = fkt_start_seq_get_int(context, id, O_RDWR | O_BINARY, 1, &cursor);
630    if(ret != 0)
631	goto out; /* return other error here? */
632    while(fkt_next_entry_int(context, id, &e, &cursor,
633			     &pos_start, &pos_end) == 0) {
634	if(krb5_kt_compare(context, &e, entry->principal,
635			   entry->vno, entry->keyblock.keytype)) {
636	    int32_t len;
637	    unsigned char buf[128];
638	    found = 1;
639	    krb5_storage_seek(cursor.sp, pos_start, SEEK_SET);
640	    len = pos_end - pos_start - 4;
641	    krb5_store_int32(cursor.sp, -len);
642	    memset(buf, 0, sizeof(buf));
643	    while(len > 0) {
644		krb5_storage_write(cursor.sp, buf, min(len, sizeof(buf)));
645		len -= min(len, sizeof(buf));
646	    }
647	}
648	krb5_kt_free_entry(context, &e);
649    }
650    krb5_kt_end_seq_get(context, id, &cursor);
651  out:
652    if (!found) {
653	krb5_clear_error_string (context);
654	return KRB5_KT_NOTFOUND;
655    }
656    return 0;
657}
658
659const krb5_kt_ops krb5_fkt_ops = {
660    "FILE",
661    fkt_resolve,
662    fkt_get_name,
663    fkt_close,
664    NULL, /* get */
665    fkt_start_seq_get,
666    fkt_next_entry,
667    fkt_end_seq_get,
668    fkt_add_entry,
669    fkt_remove_entry
670};
671
672const krb5_kt_ops krb5_wrfkt_ops = {
673    "WRFILE",
674    fkt_resolve,
675    fkt_get_name,
676    fkt_close,
677    NULL, /* get */
678    fkt_start_seq_get,
679    fkt_next_entry,
680    fkt_end_seq_get,
681    fkt_add_entry,
682    fkt_remove_entry
683};
684
685const krb5_kt_ops krb5_javakt_ops = {
686    "JAVA14",
687    fkt_resolve_java14,
688    fkt_get_name,
689    fkt_close,
690    NULL, /* get */
691    fkt_start_seq_get,
692    fkt_next_entry,
693    fkt_end_seq_get,
694    fkt_add_entry,
695    fkt_remove_entry
696};
697