1/*
2 * Copyright (c) 1997 - 2007 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#ifdef HEIM_KT_AKF
37
38/* afs keyfile operations --------------------------------------- */
39
40/*
41 * Minimum tools to handle the AFS KeyFile.
42 *
43 * Format of the KeyFile is:
44 * <int32_t numkeys> {[<int32_t kvno> <char[8] deskey>] * numkeys}
45 *
46 * It just adds to the end of the keyfile, deleting isn't implemented.
47 * Use your favorite text/hex editor to delete keys.
48 *
49 */
50
51#define AFS_SERVERTHISCELL "/usr/afs/etc/ThisCell"
52#define AFS_SERVERMAGICKRBCONF "/usr/afs/etc/krb.conf"
53
54struct akf_data {
55    uint32_t num_entries;
56    char *filename;
57    char *cell;
58    char *realm;
59};
60
61/*
62 * set `d->cell' and `d->realm'
63 */
64
65static int
66get_cell_and_realm (krb5_context context, struct akf_data *d)
67{
68    FILE *f;
69    char buf[BUFSIZ], *cp;
70    int ret;
71
72    f = fopen (AFS_SERVERTHISCELL, "r");
73    if (f == NULL) {
74	ret = errno;
75	krb5_set_error_message (context, ret,
76				N_("Open ThisCell %s: %s", ""),
77				AFS_SERVERTHISCELL,
78				strerror(ret));
79	return ret;
80    }
81    if (fgets (buf, sizeof(buf), f) == NULL) {
82	fclose (f);
83	krb5_set_error_message (context, EINVAL,
84				N_("No cell in ThisCell file %s", ""),
85				AFS_SERVERTHISCELL);
86	return EINVAL;
87    }
88    buf[strcspn(buf, "\n")] = '\0';
89    fclose(f);
90
91    d->cell = strdup (buf);
92    if (d->cell == NULL) {
93	krb5_set_error_message(context, ENOMEM,
94			       N_("malloc: out of memory", ""));
95	return ENOMEM;
96    }
97
98    f = fopen (AFS_SERVERMAGICKRBCONF, "r");
99    if (f != NULL) {
100	if (fgets (buf, sizeof(buf), f) == NULL) {
101	    free (d->cell);
102	    d->cell = NULL;
103	    fclose (f);
104	    krb5_set_error_message (context, EINVAL,
105				    N_("No realm in ThisCell file %s", ""),
106				    AFS_SERVERMAGICKRBCONF);
107	    return EINVAL;
108	}
109	buf[strcspn(buf, "\n")] = '\0';
110	fclose(f);
111    }
112    /* uppercase */
113    for (cp = buf; *cp != '\0'; cp++)
114	*cp = toupper((unsigned char)*cp);
115
116    d->realm = strdup (buf);
117    if (d->realm == NULL) {
118	free (d->cell);
119	d->cell = NULL;
120	krb5_set_error_message(context, ENOMEM,
121			       N_("malloc: out of memory", ""));
122	return ENOMEM;
123    }
124    return 0;
125}
126
127/*
128 * init and get filename
129 */
130
131static krb5_error_code KRB5_CALLCONV
132akf_resolve(krb5_context context, const char *name, krb5_keytab id)
133{
134    int ret;
135    struct akf_data *d = malloc(sizeof (struct akf_data));
136
137    if (d == NULL) {
138	krb5_set_error_message(context, ENOMEM,
139			       N_("malloc: out of memory", ""));
140	return ENOMEM;
141    }
142
143    d->num_entries = 0;
144    ret = get_cell_and_realm (context, d);
145    if (ret) {
146	free (d);
147	return ret;
148    }
149    d->filename = strdup (name);
150    if (d->filename == NULL) {
151	free (d->cell);
152	free (d->realm);
153	free (d);
154	krb5_set_error_message(context, ENOMEM,
155			       N_("malloc: out of memory", ""));
156	return ENOMEM;
157    }
158    id->data = d;
159
160    return 0;
161}
162
163/*
164 * cleanup
165 */
166
167static krb5_error_code KRB5_CALLCONV
168akf_close(krb5_context context, krb5_keytab id)
169{
170    struct akf_data *d = id->data;
171
172    free (d->filename);
173    free (d->cell);
174    free (d);
175    return 0;
176}
177
178/*
179 * Return filename
180 */
181
182static krb5_error_code KRB5_CALLCONV
183akf_get_name(krb5_context context,
184	     krb5_keytab id,
185	     char *name,
186	     size_t name_sz)
187{
188    struct akf_data *d = id->data;
189
190    strlcpy (name, d->filename, name_sz);
191    return 0;
192}
193
194/*
195 * Init
196 */
197
198static krb5_error_code KRB5_CALLCONV
199akf_start_seq_get(krb5_context context,
200		  krb5_keytab id,
201		  krb5_kt_cursor *c)
202{
203    int32_t ret;
204    struct akf_data *d = id->data;
205
206    c->fd = open (d->filename, O_RDONLY | O_BINARY | O_CLOEXEC, 0600);
207    if (c->fd < 0) {
208	ret = errno;
209	krb5_set_error_message(context, ret,
210			       N_("keytab afs keyfile open %s failed: %s", ""),
211			       d->filename, strerror(ret));
212	return ret;
213    }
214
215    c->data = NULL;
216    c->sp = krb5_storage_from_fd(c->fd);
217    if (c->sp == NULL) {
218	close(c->fd);
219	krb5_clear_error_message (context);
220	return KRB5_KT_NOTFOUND;
221    }
222    krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
223
224    ret = krb5_ret_uint32(c->sp, &d->num_entries);
225    if(ret || d->num_entries > INT_MAX / 8) {
226	krb5_storage_free(c->sp);
227	close(c->fd);
228	krb5_clear_error_message (context);
229	if(ret == KRB5_KT_END)
230	    return KRB5_KT_NOTFOUND;
231	return ret;
232    }
233
234    return 0;
235}
236
237static krb5_error_code KRB5_CALLCONV
238akf_next_entry(krb5_context context,
239	       krb5_keytab id,
240	       krb5_keytab_entry *entry,
241	       krb5_kt_cursor *cursor)
242{
243    struct akf_data *d = id->data;
244    int32_t kvno;
245    off_t pos;
246    int ret;
247
248    pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
249
250    if ((pos - 4) / (4 + 8) >= d->num_entries)
251	return KRB5_KT_END;
252
253    ret = krb5_make_principal (context, &entry->principal,
254			       d->realm, "afs", d->cell, NULL);
255    if (ret)
256	goto out;
257
258    ret = krb5_ret_int32(cursor->sp, &kvno);
259    if (ret) {
260	krb5_free_principal (context, entry->principal);
261	goto out;
262    }
263
264    entry->vno = kvno;
265
266    if (cursor->data)
267	entry->keyblock.keytype         = ETYPE_DES_CBC_MD5;
268    else
269	entry->keyblock.keytype         = ETYPE_DES_CBC_CRC;
270    entry->keyblock.keyvalue.length = 8;
271    entry->keyblock.keyvalue.data   = malloc (8);
272    if (entry->keyblock.keyvalue.data == NULL) {
273	krb5_free_principal (context, entry->principal);
274	krb5_set_error_message(context, ENOMEM,
275			       N_("malloc: out of memory", ""));
276	ret = ENOMEM;
277	goto out;
278    }
279
280    ret = krb5_storage_read(cursor->sp, entry->keyblock.keyvalue.data, 8);
281    if(ret != 8)
282	ret = (ret < 0) ? errno : KRB5_KT_END;
283    else
284	ret = 0;
285
286    entry->timestamp = time(NULL);
287    entry->flags = 0;
288    entry->aliases = NULL;
289
290 out:
291    if (cursor->data) {
292	krb5_storage_seek(cursor->sp, pos + 4 + 8, SEEK_SET);
293	cursor->data = NULL;
294    } else
295	cursor->data = cursor;
296    return ret;
297}
298
299static krb5_error_code KRB5_CALLCONV
300akf_end_seq_get(krb5_context context,
301		krb5_keytab id,
302		krb5_kt_cursor *cursor)
303{
304    krb5_storage_free(cursor->sp);
305    close(cursor->fd);
306    cursor->data = NULL;
307    return 0;
308}
309
310static krb5_error_code KRB5_CALLCONV
311akf_add_entry(krb5_context context,
312	      krb5_keytab id,
313	      krb5_keytab_entry *entry)
314{
315    struct akf_data *d = id->data;
316    int fd, created = 0;
317    krb5_error_code ret;
318    int32_t len;
319    krb5_storage *sp;
320
321
322    if (entry->keyblock.keyvalue.length != 8)
323	return 0;
324    switch(entry->keyblock.keytype) {
325    case ETYPE_DES_CBC_CRC:
326    case ETYPE_DES_CBC_MD4:
327    case ETYPE_DES_CBC_MD5:
328	break;
329    default:
330	return 0;
331    }
332
333    fd = open (d->filename, O_RDWR | O_BINARY | O_CLOEXEC);
334    if (fd < 0) {
335	fd = open (d->filename,
336		   O_RDWR | O_BINARY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
337	if (fd < 0) {
338	    ret = errno;
339	    krb5_set_error_message(context, ret,
340				   N_("open keyfile(%s): %s", ""),
341				   d->filename,
342				   strerror(ret));
343	    return ret;
344	}
345	created = 1;
346    }
347
348    sp = krb5_storage_from_fd(fd);
349    if(sp == NULL) {
350	close(fd);
351	krb5_set_error_message(context, ENOMEM,
352			       N_("malloc: out of memory", ""));
353	return ENOMEM;
354    }
355    if (created)
356	len = 0;
357    else {
358	if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
359	    ret = errno;
360	    krb5_storage_free(sp);
361	    close(fd);
362	    krb5_set_error_message(context, ret,
363				   N_("seeking in keyfile: %s", ""),
364				   strerror(ret));
365	    return ret;
366	}
367
368	ret = krb5_ret_int32(sp, &len);
369	if(ret) {
370	    krb5_storage_free(sp);
371	    close(fd);
372	    return ret;
373	}
374    }
375
376    /*
377     * Make sure we don't add the entry twice, assumes the DES
378     * encryption types are all the same key.
379     */
380    if (len > 0) {
381	int32_t kvno;
382	int i;
383
384	for (i = 0; i < len; i++) {
385	    ret = krb5_ret_int32(sp, &kvno);
386	    if (ret) {
387		krb5_set_error_message (context, ret,
388					N_("Failed getting kvno from keyfile", ""));
389		goto out;
390	    }
391	    if(krb5_storage_seek(sp, 8, SEEK_CUR) < 0) {
392		ret = errno;
393		krb5_set_error_message (context, ret,
394					N_("Failed seeing in keyfile: %s", ""),
395					strerror(ret));
396		goto out;
397	    }
398	    if (kvno == entry->vno) {
399		ret = 0;
400		goto out;
401	    }
402	}
403    }
404
405    len++;
406
407    if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
408	ret = errno;
409	krb5_set_error_message (context, ret,
410				N_("Failed seeing in keyfile: %s", ""),
411				strerror(ret));
412	goto out;
413    }
414
415    ret = krb5_store_int32(sp, len);
416    if(ret) {
417	ret = errno;
418	krb5_set_error_message (context, ret,
419				N_("keytab keyfile failed new length", ""));
420	return ret;
421    }
422
423    if(krb5_storage_seek(sp, (len - 1) * (8 + 4), SEEK_CUR) < 0) {
424	ret = errno;
425	krb5_set_error_message (context, ret,
426				N_("seek to end: %s", ""), strerror(ret));
427	goto out;
428    }
429
430    ret = krb5_store_int32(sp, entry->vno);
431    if(ret) {
432	krb5_set_error_message(context, ret,
433			       N_("keytab keyfile failed store kvno", ""));
434	goto out;
435    }
436    ret = krb5_storage_write(sp, entry->keyblock.keyvalue.data,
437			     entry->keyblock.keyvalue.length);
438    if(ret != entry->keyblock.keyvalue.length) {
439	if (ret < 0)
440	    ret = errno;
441	else
442	    ret = ENOTTY;
443	krb5_set_error_message(context, ret,
444			       N_("keytab keyfile failed to add key", ""));
445	goto out;
446    }
447    ret = 0;
448out:
449    krb5_storage_free(sp);
450    close (fd);
451    return ret;
452}
453
454const krb5_kt_ops krb5_akf_ops = {
455    "AFSKEYFILE",
456    akf_resolve,
457    akf_get_name,
458    akf_close,
459    NULL, /* destroy */
460    NULL, /* get */
461    akf_start_seq_get,
462    akf_next_entry,
463    akf_end_seq_get,
464    akf_add_entry,
465    NULL /* remove */
466};
467
468#endif /* HEIM_KT_AKF */
469