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 #ifndef HEIMDAL_SMALLER
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     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 
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_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 
131 static krb5_error_code KRB5_CALLCONV
132 akf_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 
167 static krb5_error_code KRB5_CALLCONV
168 akf_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 
182 static krb5_error_code KRB5_CALLCONV
183 akf_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 
198 static krb5_error_code KRB5_CALLCONV
199 akf_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 
237 static krb5_error_code KRB5_CALLCONV
238 akf_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 
299 static krb5_error_code KRB5_CALLCONV
300 akf_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 
310 static krb5_error_code KRB5_CALLCONV
311 akf_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;
448 out:
449     krb5_storage_free(sp);
450     close (fd);
451     return ret;
452 }
453 
454 const 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 /* HEIMDAL_SMALLER */
469