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
36RCSID("$Id: keytab_keyfile.c 20695 2007-05-30 14:09:09Z lha $");
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    int 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_string (context, "open %s: %s", AFS_SERVERTHISCELL,
76			       strerror(ret));
77	return ret;
78    }
79    if (fgets (buf, sizeof(buf), f) == NULL) {
80	fclose (f);
81	krb5_set_error_string (context, "no cell in %s", AFS_SERVERTHISCELL);
82	return EINVAL;
83    }
84    buf[strcspn(buf, "\n")] = '\0';
85    fclose(f);
86
87    d->cell = strdup (buf);
88    if (d->cell == NULL) {
89	krb5_set_error_string (context, "malloc: out of memory");
90	return ENOMEM;
91    }
92
93    f = fopen (AFS_SERVERMAGICKRBCONF, "r");
94    if (f != NULL) {
95	if (fgets (buf, sizeof(buf), f) == NULL) {
96	    free (d->cell);
97	    d->cell = NULL;
98	    fclose (f);
99	    krb5_set_error_string (context, "no realm in %s",
100				   AFS_SERVERMAGICKRBCONF);
101	    return EINVAL;
102	}
103	buf[strcspn(buf, "\n")] = '\0';
104	fclose(f);
105    }
106    /* uppercase */
107    for (cp = buf; *cp != '\0'; cp++)
108	*cp = toupper((unsigned char)*cp);
109
110    d->realm = strdup (buf);
111    if (d->realm == NULL) {
112	free (d->cell);
113	d->cell = NULL;
114	krb5_set_error_string (context, "malloc: out of memory");
115	return ENOMEM;
116    }
117    return 0;
118}
119
120/*
121 * init and get filename
122 */
123
124static krb5_error_code
125akf_resolve(krb5_context context, const char *name, krb5_keytab id)
126{
127    int ret;
128    struct akf_data *d = malloc(sizeof (struct akf_data));
129
130    if (d == NULL) {
131	krb5_set_error_string (context, "malloc: out of memory");
132	return ENOMEM;
133    }
134
135    d->num_entries = 0;
136    ret = get_cell_and_realm (context, d);
137    if (ret) {
138	free (d);
139	return ret;
140    }
141    d->filename = strdup (name);
142    if (d->filename == NULL) {
143	free (d->cell);
144	free (d->realm);
145	free (d);
146	krb5_set_error_string (context, "malloc: out of memory");
147	return ENOMEM;
148    }
149    id->data = d;
150
151    return 0;
152}
153
154/*
155 * cleanup
156 */
157
158static krb5_error_code
159akf_close(krb5_context context, krb5_keytab id)
160{
161    struct akf_data *d = id->data;
162
163    free (d->filename);
164    free (d->cell);
165    free (d);
166    return 0;
167}
168
169/*
170 * Return filename
171 */
172
173static krb5_error_code
174akf_get_name(krb5_context context,
175	     krb5_keytab id,
176	     char *name,
177	     size_t name_sz)
178{
179    struct akf_data *d = id->data;
180
181    strlcpy (name, d->filename, name_sz);
182    return 0;
183}
184
185/*
186 * Init
187 */
188
189static krb5_error_code
190akf_start_seq_get(krb5_context context,
191		  krb5_keytab id,
192		  krb5_kt_cursor *c)
193{
194    int32_t ret;
195    struct akf_data *d = id->data;
196
197    c->fd = open (d->filename, O_RDONLY|O_BINARY, 0600);
198    if (c->fd < 0) {
199	ret = errno;
200	krb5_set_error_string(context, "open(%s): %s", d->filename,
201			      strerror(ret));
202	return ret;
203    }
204
205    c->sp = krb5_storage_from_fd(c->fd);
206    ret = krb5_ret_int32(c->sp, &d->num_entries);
207    if(ret) {
208	krb5_storage_free(c->sp);
209	close(c->fd);
210	krb5_clear_error_string (context);
211	if(ret == KRB5_KT_END)
212	    return KRB5_KT_NOTFOUND;
213	return ret;
214    }
215
216    return 0;
217}
218
219static krb5_error_code
220akf_next_entry(krb5_context context,
221	       krb5_keytab id,
222	       krb5_keytab_entry *entry,
223	       krb5_kt_cursor *cursor)
224{
225    struct akf_data *d = id->data;
226    int32_t kvno;
227    off_t pos;
228    int ret;
229
230    pos = krb5_storage_seek(cursor->sp, 0, SEEK_CUR);
231
232    if ((pos - 4) / (4 + 8) >= d->num_entries)
233	return KRB5_KT_END;
234
235    ret = krb5_make_principal (context, &entry->principal,
236			       d->realm, "afs", d->cell, NULL);
237    if (ret)
238	goto out;
239
240    ret = krb5_ret_int32(cursor->sp, &kvno);
241    if (ret) {
242	krb5_free_principal (context, entry->principal);
243	goto out;
244    }
245
246    entry->vno = kvno;
247
248    entry->keyblock.keytype         = ETYPE_DES_CBC_MD5;
249    entry->keyblock.keyvalue.length = 8;
250    entry->keyblock.keyvalue.data   = malloc (8);
251    if (entry->keyblock.keyvalue.data == NULL) {
252	krb5_free_principal (context, entry->principal);
253	krb5_set_error_string (context, "malloc: out of memory");
254	ret = ENOMEM;
255	goto out;
256    }
257
258    ret = krb5_storage_read(cursor->sp, entry->keyblock.keyvalue.data, 8);
259    if(ret != 8)
260	ret = (ret < 0) ? errno : KRB5_KT_END;
261    else
262	ret = 0;
263
264    entry->timestamp = time(NULL);
265
266 out:
267    krb5_storage_seek(cursor->sp, pos + 4 + 8, SEEK_SET);
268    return ret;
269}
270
271static krb5_error_code
272akf_end_seq_get(krb5_context context,
273		krb5_keytab id,
274		krb5_kt_cursor *cursor)
275{
276    krb5_storage_free(cursor->sp);
277    close(cursor->fd);
278    return 0;
279}
280
281static krb5_error_code
282akf_add_entry(krb5_context context,
283	      krb5_keytab id,
284	      krb5_keytab_entry *entry)
285{
286    struct akf_data *d = id->data;
287    int fd, created = 0;
288    krb5_error_code ret;
289    int32_t len;
290    krb5_storage *sp;
291
292
293    if (entry->keyblock.keyvalue.length != 8)
294	return 0;
295    switch(entry->keyblock.keytype) {
296    case ETYPE_DES_CBC_CRC:
297    case ETYPE_DES_CBC_MD4:
298    case ETYPE_DES_CBC_MD5:
299	break;
300    default:
301	return 0;
302    }
303
304    fd = open (d->filename, O_RDWR | O_BINARY);
305    if (fd < 0) {
306	fd = open (d->filename,
307		   O_RDWR | O_BINARY | O_CREAT | O_EXCL, 0600);
308	if (fd < 0) {
309	    ret = errno;
310	    krb5_set_error_string(context, "open(%s): %s", d->filename,
311				  strerror(ret));
312	    return ret;
313	}
314	created = 1;
315    }
316
317    sp = krb5_storage_from_fd(fd);
318    if(sp == NULL) {
319	close(fd);
320	krb5_set_error_string (context, "malloc: out of memory");
321	return ENOMEM;
322    }
323    if (created)
324	len = 0;
325    else {
326	if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
327	    ret = errno;
328	    krb5_storage_free(sp);
329	    close(fd);
330	    krb5_set_error_string (context, "seek: %s", strerror(ret));
331	    return ret;
332	}
333
334	ret = krb5_ret_int32(sp, &len);
335	if(ret) {
336	    krb5_storage_free(sp);
337	    close(fd);
338	    return ret;
339	}
340    }
341
342    /*
343     * Make sure we don't add the entry twice, assumes the DES
344     * encryption types are all the same key.
345     */
346    if (len > 0) {
347	int32_t kvno;
348	int i;
349
350	for (i = 0; i < len; i++) {
351	    ret = krb5_ret_int32(sp, &kvno);
352	    if (ret) {
353		krb5_set_error_string (context, "Failed to get kvno ");
354		goto out;
355	    }
356	    if(krb5_storage_seek(sp, 8, SEEK_CUR) < 0) {
357		krb5_set_error_string (context, "seek: %s", strerror(ret));
358		goto out;
359	    }
360	    if (kvno == entry->vno) {
361		ret = 0;
362		goto out;
363	    }
364	}
365    }
366
367    len++;
368
369    if(krb5_storage_seek(sp, 0, SEEK_SET) < 0) {
370	ret = errno;
371	krb5_set_error_string (context, "seek: %s", strerror(ret));
372	goto out;
373    }
374
375    ret = krb5_store_int32(sp, len);
376    if(ret) {
377	krb5_set_error_string(context, "keytab keyfile failed new length");
378	return ret;
379    }
380
381    if(krb5_storage_seek(sp, (len - 1) * (8 + 4), SEEK_CUR) < 0) {
382	ret = errno;
383	krb5_set_error_string (context, "seek to end: %s", strerror(ret));
384	goto out;
385    }
386
387    ret = krb5_store_int32(sp, entry->vno);
388    if(ret) {
389	krb5_set_error_string(context, "keytab keyfile failed store kvno");
390	goto out;
391    }
392    ret = krb5_storage_write(sp, entry->keyblock.keyvalue.data,
393			     entry->keyblock.keyvalue.length);
394    if(ret != entry->keyblock.keyvalue.length) {
395	if (ret < 0)
396	    ret = errno;
397	else
398	    ret = ENOTTY;
399	krb5_set_error_string(context, "keytab keyfile failed to add key");
400	goto out;
401    }
402    ret = 0;
403out:
404    krb5_storage_free(sp);
405    close (fd);
406    return ret;
407}
408
409const krb5_kt_ops krb5_akf_ops = {
410    "AFSKEYFILE",
411    akf_resolve,
412    akf_get_name,
413    akf_close,
414    NULL, /* get */
415    akf_start_seq_get,
416    akf_next_entry,
417    akf_end_seq_get,
418    akf_add_entry,
419    NULL /* remove */
420};
421