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 RCSID("$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 
54 struct 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 
65 static int
66 get_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 
124 static krb5_error_code
125 akf_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 
158 static krb5_error_code
159 akf_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 
173 static krb5_error_code
174 akf_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 
189 static krb5_error_code
190 akf_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 
219 static krb5_error_code
220 akf_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 
271 static krb5_error_code
272 akf_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 
281 static krb5_error_code
282 akf_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;
403 out:
404     krb5_storage_free(sp);
405     close (fd);
406     return ret;
407 }
408 
409 const 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