xref: /freebsd/crypto/heimdal/lib/krb5/keytab_file.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
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 
36 RCSID("$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 
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 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;
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_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 
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_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 
382 static krb5_error_code
383 fkt_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 
390 static krb5_error_code
391 fkt_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);
405 loop:
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 
444 static krb5_error_code
445 fkt_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 
453 static krb5_error_code
454 fkt_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 
464 static krb5_error_code
465 fkt_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 
478 static krb5_error_code
479 fkt_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 
618 static krb5_error_code
619 fkt_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 
659 const 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 
672 const 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 
685 const 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