xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/keytab_keyfile.c (revision d3273b5b76f5afaafe308cead5511dbb8df8c5e9)
1 /*	$NetBSD: keytab_keyfile.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "krb5_locl.h"
37 
38 #ifndef HEIMDAL_SMALLER
39 
40 /* afs keyfile operations --------------------------------------- */
41 
42 /*
43  * Minimum tools to handle the AFS KeyFile.
44  *
45  * Format of the KeyFile is:
46  * <int32_t numkeys> {[<int32_t kvno> <char[8] deskey>] * numkeys}
47  *
48  * It just adds to the end of the keyfile, deleting isn't implemented.
49  * Use your favorite text/hex editor to delete keys.
50  *
51  */
52 
53 #define AFS_SERVERTHISCELL "/usr/afs/etc/ThisCell"
54 #define AFS_SERVERMAGICKRBCONF "/usr/afs/etc/krb.conf"
55 
56 struct akf_data {
57     uint32_t num_entries;
58     char *filename;
59     char *cell;
60     char *realm;
61 };
62 
63 /*
64  * set `d->cell' and `d->realm'
65  */
66 
67 static int
get_cell_and_realm(krb5_context context,struct akf_data * d)68 get_cell_and_realm (krb5_context context, struct akf_data *d)
69 {
70     FILE *f;
71     char buf[BUFSIZ], *cp;
72     int ret;
73 
74     f = fopen (AFS_SERVERTHISCELL, "r");
75     if (f == NULL) {
76 	ret = errno;
77 	krb5_set_error_message (context, ret,
78 				N_("Open ThisCell %s: %s", ""),
79 				AFS_SERVERTHISCELL,
80 				strerror(ret));
81 	return ret;
82     }
83     if (fgets (buf, sizeof(buf), f) == NULL) {
84 	fclose (f);
85 	krb5_set_error_message (context, EINVAL,
86 				N_("No cell in ThisCell file %s", ""),
87 				AFS_SERVERTHISCELL);
88 	return EINVAL;
89     }
90     buf[strcspn(buf, "\n")] = '\0';
91     fclose(f);
92 
93     d->cell = strdup (buf);
94     if (d->cell == NULL)
95 	return krb5_enomem(context);
96 
97     f = fopen (AFS_SERVERMAGICKRBCONF, "r");
98     if (f != NULL) {
99 	if (fgets (buf, sizeof(buf), f) == NULL) {
100 	    free (d->cell);
101 	    d->cell = NULL;
102 	    fclose (f);
103 	    krb5_set_error_message (context, EINVAL,
104 				    N_("No realm in ThisCell file %s", ""),
105 				    AFS_SERVERMAGICKRBCONF);
106 	    return EINVAL;
107 	}
108 	buf[strcspn(buf, "\n")] = '\0';
109 	fclose(f);
110     }
111     /* uppercase */
112     for (cp = buf; *cp != '\0'; cp++)
113 	*cp = toupper((unsigned char)*cp);
114 
115     d->realm = strdup (buf);
116     if (d->realm == NULL) {
117 	free (d->cell);
118 	d->cell = NULL;
119 	return krb5_enomem(context);
120     }
121     return 0;
122 }
123 
124 /*
125  * init and get filename
126  */
127 
128 static krb5_error_code KRB5_CALLCONV
akf_resolve(krb5_context context,const char * name,krb5_keytab id)129 akf_resolve(krb5_context context, const char *name, krb5_keytab id)
130 {
131     int ret;
132     struct akf_data *d = calloc(1, sizeof (struct akf_data));
133 
134     if (d == NULL)
135 	return krb5_enomem(context);
136 
137     d->num_entries = 0;
138     ret = get_cell_and_realm (context, d);
139     if (ret) {
140 	free (d);
141 	return ret;
142     }
143     d->filename = strdup (name);
144     if (d->filename == NULL) {
145 	free (d->cell);
146 	free (d->realm);
147 	free (d);
148 	return krb5_enomem(context);
149     }
150     id->data = d;
151 
152     return 0;
153 }
154 
155 /*
156  * cleanup
157  */
158 
159 static krb5_error_code KRB5_CALLCONV
akf_close(krb5_context context,krb5_keytab id)160 akf_close(krb5_context context, krb5_keytab id)
161 {
162     struct akf_data *d = id->data;
163 
164     free (d->filename);
165     free (d->cell);
166     free (d);
167     return 0;
168 }
169 
170 /*
171  * Return filename
172  */
173 
174 static krb5_error_code KRB5_CALLCONV
akf_get_name(krb5_context context,krb5_keytab id,char * name,size_t name_sz)175 akf_get_name(krb5_context context,
176 	     krb5_keytab id,
177 	     char *name,
178 	     size_t name_sz)
179 {
180     struct akf_data *d = id->data;
181 
182     strlcpy (name, d->filename, name_sz);
183     return 0;
184 }
185 
186 /*
187  * Init
188  */
189 
190 static krb5_error_code KRB5_CALLCONV
akf_start_seq_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * c)191 akf_start_seq_get(krb5_context context,
192 		  krb5_keytab id,
193 		  krb5_kt_cursor *c)
194 {
195     int32_t ret;
196     struct akf_data *d = id->data;
197 
198     c->fd = open (d->filename, O_RDONLY | O_BINARY | O_CLOEXEC, 0600);
199     if (c->fd < 0) {
200 	ret = errno;
201 	krb5_set_error_message(context, ret,
202 			       N_("keytab afs keyfile open %s failed: %s", ""),
203 			       d->filename, strerror(ret));
204 	return ret;
205     }
206 
207     c->data = NULL;
208     c->sp = krb5_storage_from_fd(c->fd);
209     if (c->sp == NULL) {
210 	close(c->fd);
211 	krb5_clear_error_message (context);
212 	return KRB5_KT_NOTFOUND;
213     }
214     krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
215 
216     ret = krb5_ret_uint32(c->sp, &d->num_entries);
217     if(ret || d->num_entries > INT_MAX / 8) {
218 	krb5_storage_free(c->sp);
219 	close(c->fd);
220 	krb5_clear_error_message (context);
221 	if(ret == KRB5_KT_END)
222 	    return KRB5_KT_NOTFOUND;
223 	return ret;
224     }
225 
226     return 0;
227 }
228 
229 static krb5_error_code KRB5_CALLCONV
akf_next_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry,krb5_kt_cursor * cursor)230 akf_next_entry(krb5_context context,
231 	       krb5_keytab id,
232 	       krb5_keytab_entry *entry,
233 	       krb5_kt_cursor *cursor)
234 {
235     struct akf_data *d = id->data;
236     int32_t kvno;
237     off_t pos;
238     int ret;
239 
240     pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
241 
242     if ((pos - 4) / (4 + 8) >= d->num_entries)
243 	return KRB5_KT_END;
244 
245     ret = krb5_make_principal (context, &entry->principal,
246 			       d->realm, "afs", d->cell, NULL);
247     if (ret)
248 	goto out;
249 
250     ret = krb5_ret_int32(cursor->sp, &kvno);
251     if (ret) {
252 	krb5_free_principal (context, entry->principal);
253 	goto out;
254     }
255 
256     entry->vno = kvno;
257 
258     if (cursor->data)
259 	entry->keyblock.keytype         = ETYPE_DES_CBC_MD5;
260     else
261 	entry->keyblock.keytype         = ETYPE_DES_CBC_CRC;
262     entry->keyblock.keyvalue.length = 8;
263     entry->keyblock.keyvalue.data   = malloc (8);
264     if (entry->keyblock.keyvalue.data == NULL) {
265 	krb5_free_principal (context, entry->principal);
266 	ret = krb5_enomem(context);
267 	goto out;
268     }
269 
270     ret = krb5_storage_read(cursor->sp, entry->keyblock.keyvalue.data, 8);
271     if(ret != 8)
272 	ret = (ret < 0) ? errno : KRB5_KT_END;
273     else
274 	ret = 0;
275 
276     entry->timestamp = time(NULL);
277     entry->flags = 0;
278     entry->aliases = NULL;
279 
280  out:
281     if (cursor->data) {
282 	krb5_storage_seek(cursor->sp, pos + 4 + 8, SEEK_SET);
283 	cursor->data = NULL;
284     } else
285 	cursor->data = cursor;
286     return ret;
287 }
288 
289 static krb5_error_code KRB5_CALLCONV
akf_end_seq_get(krb5_context context,krb5_keytab id,krb5_kt_cursor * cursor)290 akf_end_seq_get(krb5_context context,
291 		krb5_keytab id,
292 		krb5_kt_cursor *cursor)
293 {
294     krb5_storage_free(cursor->sp);
295     close(cursor->fd);
296     cursor->data = NULL;
297     return 0;
298 }
299 
300 static krb5_error_code KRB5_CALLCONV
akf_add_entry(krb5_context context,krb5_keytab id,krb5_keytab_entry * entry)301 akf_add_entry(krb5_context context,
302 	      krb5_keytab id,
303 	      krb5_keytab_entry *entry)
304 {
305     struct akf_data *d = id->data;
306     int fd, created = 0;
307     krb5_error_code ret;
308     int32_t len;
309     krb5_storage *sp;
310 
311 
312     if (entry->keyblock.keyvalue.length != 8)
313 	return 0;
314     switch(entry->keyblock.keytype) {
315     case ETYPE_DES_CBC_CRC:
316     case ETYPE_DES_CBC_MD4:
317     case ETYPE_DES_CBC_MD5:
318 	break;
319     default:
320 	return 0;
321     }
322 
323     fd = open (d->filename, O_RDWR | O_BINARY | O_CLOEXEC);
324     if (fd < 0) {
325 	fd = open (d->filename,
326 		   O_RDWR | O_BINARY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
327 	if (fd < 0) {
328 	    ret = errno;
329 	    krb5_set_error_message(context, ret,
330 				   N_("open keyfile(%s): %s", ""),
331 				   d->filename,
332 				   strerror(ret));
333 	    return ret;
334 	}
335 	created = 1;
336     }
337 
338     sp = krb5_storage_from_fd(fd);
339     if(sp == NULL) {
340 	close(fd);
341 	return krb5_enomem(context);
342     }
343     if (created)
344 	len = 0;
345     else {
346 	if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
347 	    ret = errno;
348 	    krb5_storage_free(sp);
349 	    close(fd);
350 	    krb5_set_error_message(context, ret,
351 				   N_("seeking in keyfile: %s", ""),
352 				   strerror(ret));
353 	    return ret;
354 	}
355 
356 	ret = krb5_ret_int32(sp, &len);
357 	if(ret) {
358 	    krb5_storage_free(sp);
359 	    close(fd);
360 	    return ret;
361 	}
362     }
363 
364     /*
365      * Make sure we don't add the entry twice, assumes the DES
366      * encryption types are all the same key.
367      */
368     if (len > 0) {
369 	int32_t kvno;
370 	int i;
371 
372 	for (i = 0; i < len; i++) {
373 	    ret = krb5_ret_int32(sp, &kvno);
374 	    if (ret) {
375 		krb5_set_error_message (context, ret,
376 					N_("Failed getting kvno from keyfile", ""));
377 		goto out;
378 	    }
379 	    if(krb5_storage_seek(sp, 8, SEEK_CUR) < 0) {
380 		ret = errno;
381 		krb5_set_error_message (context, ret,
382 					N_("Failed seeing in keyfile: %s", ""),
383 					strerror(ret));
384 		goto out;
385 	    }
386 	    if (kvno == entry->vno) {
387 		ret = 0;
388 		goto out;
389 	    }
390 	}
391     }
392 
393     len++;
394 
395     if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
396 	ret = errno;
397 	krb5_set_error_message (context, ret,
398 				N_("Failed seeing in keyfile: %s", ""),
399 				strerror(ret));
400 	goto out;
401     }
402 
403     ret = krb5_store_int32(sp, len);
404     if(ret) {
405 	ret = errno;
406 	krb5_set_error_message (context, ret,
407 				N_("keytab keyfile failed new length", ""));
408 	return ret;
409     }
410 
411     if(krb5_storage_seek(sp, (len - 1) * (8 + 4), SEEK_CUR) < 0) {
412 	ret = errno;
413 	krb5_set_error_message (context, ret,
414 				N_("seek to end: %s", ""), strerror(ret));
415 	goto out;
416     }
417 
418     ret = krb5_store_int32(sp, entry->vno);
419     if(ret) {
420 	krb5_set_error_message(context, ret,
421 			       N_("keytab keyfile failed store kvno", ""));
422 	goto out;
423     }
424     ret = krb5_storage_write(sp, entry->keyblock.keyvalue.data,
425 			     entry->keyblock.keyvalue.length);
426     if(ret != entry->keyblock.keyvalue.length) {
427 	if (ret < 0)
428 	    ret = errno;
429 	else
430 	    ret = ENOTTY;
431 	krb5_set_error_message(context, ret,
432 			       N_("keytab keyfile failed to add key", ""));
433 	goto out;
434     }
435     ret = 0;
436 out:
437     krb5_storage_free(sp);
438     close (fd);
439     return ret;
440 }
441 
442 const krb5_kt_ops krb5_akf_ops = {
443     "AFSKEYFILE",
444     akf_resolve,
445     akf_get_name,
446     akf_close,
447     NULL, /* destroy */
448     NULL, /* get */
449     akf_start_seq_get,
450     akf_next_entry,
451     akf_end_seq_get,
452     akf_add_entry,
453     NULL, /* remove */
454     NULL,
455     0
456 };
457 
458 #endif /* HEIMDAL_SMALLER */
459