ktutil_funcs.c revision 1795:b618f05f612c
1/*
2 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6#pragma ident	"%Z%%M%	%I%	%E% SMI"
7
8/*
9 * kadmin/ktutil/ktutil_funcs.c
10 *
11 *(C) Copyright 1995, 1996 by the Massachusetts Institute of Technology.
12 * All Rights Reserved.
13 *
14 * Export of this software from the United States of America may
15 *   require a specific license from the United States Government.
16 *   It is the responsibility of any person or organization contemplating
17 *   export to obtain such a license before exporting.
18 *
19 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20 * distribute this software and its documentation for any purpose and
21 * without fee is hereby granted, provided that the above copyright
22 * notice appear in all copies and that both that copyright notice and
23 * this permission notice appear in supporting documentation, and that
24 * the name of M.I.T. not be used in advertising or publicity pertaining
25 * to distribution of the software without specific, written prior
26 * permission.  Furthermore if you modify this software you must label
27 * your software as modified software and not distribute it in such a
28 * fashion that it might be confused with the original M.I.T. software.
29 * M.I.T. makes no representations about the suitability of
30 * this software for any purpose.  It is provided "as is" without express
31 * or implied warranty.
32 *
33 * Utility functions for ktutil.
34 */
35
36#include "k5-int.h"
37#include "ktutil.h"
38#ifdef KRB5_KRB4_COMPAT
39#include "kerberosIV/krb.h"
40#include <stdio.h>
41#endif
42#include <string.h>
43#include <ctype.h>
44#include <libintl.h>
45
46/*
47 * Free a kt_list
48 */
49krb5_error_code ktutil_free_kt_list(context, list)
50    krb5_context context;
51    krb5_kt_list list;
52{
53    krb5_kt_list lp, prev;
54    krb5_error_code retval = 0;
55
56    for (lp = list; lp;) {
57	retval = krb5_kt_free_entry(context, lp->entry);
58	free((char *)lp->entry);
59	if (retval)
60	    break;
61	prev = lp;
62	lp = lp->next;
63	free((char *)prev);
64    }
65    return retval;
66}
67
68/*
69 * Delete a numbered entry in a kt_list.  Takes a pointer to a kt_list
70 * in case head gets deleted.
71 */
72krb5_error_code ktutil_delete(context, list, index)
73    krb5_context context;
74    krb5_kt_list *list;
75    int index;
76{
77    krb5_kt_list lp, prev;
78    int i;
79
80    for (lp = *list, i = 1; lp; prev = lp, lp = lp->next, i++) {
81	if (i == index) {
82	    if (i == 1)
83		*list = lp->next;
84	    else
85		prev->next = lp->next;
86	    lp->next = NULL;
87	    return ktutil_free_kt_list(context, lp);
88	}
89    }
90    return EINVAL;
91}
92
93/*
94 * Create a new keytab entry and add it to the keytab list.
95 * Based on the value of use_pass, either prompt the user for a
96 * password or key.  If the keytab list is NULL, allocate a new
97 * one first.
98 */
99krb5_error_code ktutil_add(context, list, princ_str, kvno,
100			   enctype_str, use_pass)
101    krb5_context context;
102    krb5_kt_list *list;
103    char *princ_str;
104    krb5_kvno kvno;
105    char *enctype_str;
106    int use_pass;
107{
108    krb5_keytab_entry *entry;
109    krb5_kt_list lp = NULL, prev = NULL;
110    krb5_principal princ;
111    krb5_enctype enctype;
112    krb5_timestamp now;
113    krb5_error_code retval;
114    krb5_data password, salt;
115    krb5_keyblock key;
116    char buf[BUFSIZ];
117    char promptstr[1024];
118
119    char *cp;
120    int i, tmp, pwsize = BUFSIZ;
121
122    retval = krb5_parse_name(context, princ_str, &princ);
123    if (retval)
124        return retval;
125    /* now unparse in order to get the default realm appended
126       to princ_str, if no realm was specified */
127    retval = krb5_unparse_name(context, princ, &princ_str);
128    if (retval)
129        return retval;
130    retval = krb5_string_to_enctype(enctype_str, &enctype);
131    if (retval)
132        return KRB5_BAD_ENCTYPE;
133    retval = krb5_timeofday(context, &now);
134    if (retval)
135        return retval;
136
137    if (*list) {
138        /* point lp at the tail of the list */
139        for (lp = *list; lp->next; lp = lp->next);
140    }
141    entry = (krb5_keytab_entry *) malloc(sizeof(krb5_keytab_entry));
142    if (!entry) {
143        return ENOMEM;
144    }
145    memset((char *) entry, 0, sizeof(*entry));
146
147    if (!lp) {		/* if list is empty, start one */
148        lp = (krb5_kt_list) malloc(sizeof(*lp));
149	if (!lp) {
150	    return ENOMEM;
151	}
152    } else {
153        lp->next = (krb5_kt_list) malloc(sizeof(*lp));
154	if (!lp->next) {
155	    return ENOMEM;
156	}
157	prev = lp;
158	lp = lp->next;
159    }
160    lp->next = NULL;
161    lp->entry = entry;
162
163    if (use_pass) {
164        password.length = pwsize;
165	password.data = (char *) malloc(pwsize);
166	if (!password.data) {
167	    retval = ENOMEM;
168	    goto cleanup;
169	}
170
171	(void) snprintf(promptstr, sizeof(promptstr),
172		gettext("Password for %.1000s"), princ_str);
173        retval = krb5_read_password(context, promptstr, NULL, password.data,
174				    &password.length);
175	if (retval)
176	    goto cleanup;
177	retval = krb5_principal2salt(context, princ, &salt);
178	if (retval)
179	    goto cleanup;
180	retval = krb5_c_string_to_key(context, enctype, &password,
181				      &salt, &key);
182	if (retval)
183	    goto cleanup;
184	memset(password.data, 0, password.length);
185	password.length = 0;
186	memcpy(&lp->entry->key, &key, sizeof(krb5_keyblock));
187    } else {
188        printf(gettext("Key for %s (hex): "), princ_str);
189	fgets(buf, BUFSIZ, stdin);
190	/*
191	 * We need to get rid of the trailing '\n' from fgets.
192	 * If we have an even number of hex digits (as we should),
193	 * write a '\0' over the '\n'.  If for some reason we have
194	 * an odd number of hex digits, force an even number of hex
195	 * digits by writing a '0' into the last position (the string
196	 * will still be null-terminated).
197	 */
198	buf[strlen(buf) - 1] = strlen(buf) % 2 ? '\0' : '0';
199	if (strlen(buf) == 0) {
200	    fprintf(stderr, "addent: %s", gettext("Error reading key.\n"));
201	    retval = 0;
202	    goto cleanup;
203	}
204
205        lp->entry->key.enctype = enctype;
206	lp->entry->key.contents = (krb5_octet *) malloc((strlen(buf) + 1) / 2);
207	if (!lp->entry->key.contents) {
208	    retval = ENOMEM;
209	    goto cleanup;
210	}
211
212	i = 0;
213	for (cp = buf; *cp; cp += 2) {
214	    if (!isxdigit(cp[0]) || !isxdigit(cp[1])) {
215	        fprintf(stderr, "addent: %s",
216			gettext("Illegal character in key.\n"));
217		retval = 0;
218		goto cleanup;
219	    }
220	    sscanf(cp, "%02x", &tmp);
221	    lp->entry->key.contents[i++] = (krb5_octet) tmp;
222	}
223	lp->entry->key.length = i;
224    }
225    lp->entry->principal = princ;
226    lp->entry->vno = kvno;
227    lp->entry->timestamp = now;
228
229    if (!*list)
230	*list = lp;
231
232    return 0;
233
234 cleanup:
235    if (prev)
236        prev->next = NULL;
237    ktutil_free_kt_list(context, lp);
238    return retval;
239}
240
241/*
242 * Read in a keytab and append it to list.  If list starts as NULL,
243 * allocate a new one if necessary.
244 */
245krb5_error_code ktutil_read_keytab(context, name, list)
246    krb5_context context;
247    char *name;
248    krb5_kt_list *list;
249{
250    krb5_kt_list lp = NULL, tail = NULL, back = NULL;
251    krb5_keytab kt;
252    krb5_keytab_entry *entry;
253    krb5_kt_cursor cursor;
254    krb5_error_code retval = 0;
255
256    if (*list) {
257	/* point lp at the tail of the list */
258	for (lp = *list; lp->next; lp = lp->next);
259	back = lp;
260    }
261    retval = krb5_kt_resolve(context, name, &kt);
262    if (retval)
263	return retval;
264    retval = krb5_kt_start_seq_get(context, kt, &cursor);
265    if (retval)
266	goto close_kt;
267    for (;;) {
268	entry = (krb5_keytab_entry *)malloc(sizeof (krb5_keytab_entry));
269	if (!entry) {
270	    retval = ENOMEM;
271	    break;
272	}
273	memset((char *)entry, 0, sizeof (*entry));
274	retval = krb5_kt_next_entry(context, kt, entry, &cursor);
275	if (retval)
276	    break;
277
278	if (!lp) {		/* if list is empty, start one */
279	    lp = (krb5_kt_list)malloc(sizeof (*lp));
280	    if (!lp) {
281		retval = ENOMEM;
282		break;
283	    }
284	} else {
285	    lp->next = (krb5_kt_list)malloc(sizeof (*lp));
286	    if (!lp->next) {
287		retval = ENOMEM;
288		break;
289	    }
290	    lp = lp->next;
291	}
292	if (!tail)
293	    tail = lp;
294	lp->next = NULL;
295	lp->entry = entry;
296    }
297    if (entry)
298	free((char *)entry);
299    if (retval)
300	if (retval == KRB5_KT_END)
301	    retval = 0;
302	else {
303	    ktutil_free_kt_list(context, tail);
304	    tail = NULL;
305	    if (back)
306		back->next = NULL;
307	}
308    if (!*list)
309	*list = tail;
310    krb5_kt_end_seq_get(context, kt, &cursor);
311 close_kt:
312    krb5_kt_close(context, kt);
313    return retval;
314}
315
316/*
317 * Takes a kt_list and writes it to the named keytab.
318 */
319krb5_error_code ktutil_write_keytab(context, list, name)
320    krb5_context context;
321    krb5_kt_list list;
322    char *name;
323{
324    krb5_kt_list lp;
325    krb5_keytab kt;
326    char ktname[MAXPATHLEN+sizeof("WRFILE:")+1];
327    krb5_error_code retval = 0;
328
329    strcpy(ktname, "WRFILE:");
330    if (strlen (name) >= MAXPATHLEN)
331	return ENAMETOOLONG;
332    strncat (ktname, name, MAXPATHLEN);
333    retval = krb5_kt_resolve(context, ktname, &kt);
334    if (retval)
335	return retval;
336    for (lp = list; lp; lp = lp->next) {
337	retval = krb5_kt_add_entry(context, kt, lp->entry);
338	if (retval)
339	    break;
340    }
341    krb5_kt_close(context, kt);
342    return retval;
343}
344
345#ifdef KRB5_KRB4_COMPAT
346/*
347 * getstr() takes a file pointer, a string and a count.  It reads from
348 * the file until either it has read "count" characters, or until it
349 * reads a null byte.  When finished, what has been read exists in the
350 * given string "s".  If "count" characters were actually read, the
351 * last is changed to a null, so the returned string is always null-
352 * terminated.  getstr() returns the number of characters read,
353 * including the null terminator.
354 */
355
356int getstr(fp, s, n)
357    FILE *fp;
358    register char *s;
359    int n;
360{
361    register count = n;
362    while (fread(s, 1, 1, fp) > 0 && --count)
363        if (*s++ == '\0')
364            return (n - count);
365    *s = '\0';
366    return (n - count);
367}
368
369/*
370 * Read in a named krb4 srvtab and append to list.  Allocate new list
371 * if needed.
372 */
373krb5_error_code ktutil_read_srvtab(context, name, list)
374    krb5_context context;
375    char *name;
376    krb5_kt_list *list;
377{
378    krb5_kt_list lp = NULL, tail = NULL, back = NULL;
379    krb5_keytab_entry *entry;
380    krb5_error_code retval = 0;
381    char sname[SNAME_SZ];	/* name of service */
382    char sinst[INST_SZ];	/* instance of service */
383    char srealm[REALM_SZ];	/* realm of service */
384    unsigned char kvno;		/* key version number */
385    des_cblock key;
386    FILE *fp;
387
388    if (*list) {
389	/* point lp at the tail of the list */
390	for (lp = *list; lp->next; lp = lp->next);
391	back = lp;
392    }
393    fp = fopen(name, "r");
394    if (!fp)
395	return EIO;
396    for (;;) {
397	entry = (krb5_keytab_entry *)malloc(sizeof (krb5_keytab_entry));
398	if (!entry) {
399	    retval = ENOMEM;
400	    break;
401	}
402	memset((char *)entry, 0, sizeof (*entry));
403	memset(sname, 0, sizeof (sname));
404	memset(sinst, 0, sizeof (sinst));
405	memset(srealm, 0, sizeof (srealm));
406	if (!(getstr(fp, sname, SNAME_SZ) > 0 &&
407	      getstr(fp, sinst, INST_SZ) > 0 &&
408	      getstr(fp, srealm, REALM_SZ) > 0 &&
409	      fread(&kvno, 1, 1, fp) > 0 &&
410	      fread((char *)key, sizeof (key), 1, fp) > 0))
411	    break;
412	entry->magic = KV5M_KEYTAB_ENTRY;
413	entry->timestamp = 0;	/* XXX */
414	entry->vno = kvno;
415	retval = krb5_425_conv_principal(context,
416					 sname, sinst, srealm,
417					 &entry->principal);
418	if (retval)
419	    break;
420	entry->key.magic = KV5M_KEYBLOCK;
421	entry->key.enctype = ENCTYPE_DES_CBC_CRC;
422	entry->key.length = sizeof (key);
423	entry->key.contents = (krb5_octet *)malloc(sizeof (key));
424	if (!entry->key.contents) {
425	    retval = ENOMEM;
426	    break;
427	}
428	memcpy((char *)entry->key.contents, (char *)key, sizeof (key));
429	if (!lp) {		/* if list is empty, start one */
430	    lp = (krb5_kt_list)malloc(sizeof (*lp));
431	    if (!lp) {
432		retval = ENOMEM;
433		break;
434	    }
435	} else {
436	    lp->next = (krb5_kt_list)malloc(sizeof (*lp));
437	    if (!lp->next) {
438		retval = ENOMEM;
439		break;
440	    }
441	    lp = lp->next;
442	}
443	lp->next = NULL;
444	lp->entry = entry;
445	if (!tail)
446	    tail = lp;
447    }
448    if (entry) {
449	if (entry->magic == KV5M_KEYTAB_ENTRY)
450	    krb5_kt_free_entry(context, entry);
451	free((char *)entry);
452    }
453    if (retval) {
454	ktutil_free_kt_list(context, tail);
455	tail = NULL;
456	if (back)
457	    back->next = NULL;
458    }
459    if (!*list)
460	*list = tail;
461    fclose(fp);
462    return retval;
463}
464
465/*
466 * Writes a kt_list out to a krb4 srvtab file.  Note that it first
467 * prunes the kt_list so that it won't contain any keys that are not
468 * the most recent, and ignores keys that are not ENCTYPE_DES.
469 */
470krb5_error_code ktutil_write_srvtab(context, list, name)
471    krb5_context context;
472    krb5_kt_list list;
473    char *name;
474{
475    krb5_kt_list lp, lp1, prev, pruned = NULL;
476    krb5_error_code retval = 0;
477    FILE *fp;
478    char sname[SNAME_SZ];
479    char sinst[INST_SZ];
480    char srealm[REALM_SZ];
481
482    /* First do heinous stuff to prune the list. */
483    for (lp = list; lp; lp = lp->next) {
484	if ((lp->entry->key.enctype != ENCTYPE_DES_CBC_CRC) &&
485	    (lp->entry->key.enctype != ENCTYPE_DES_CBC_MD5) &&
486	    (lp->entry->key.enctype != ENCTYPE_DES_CBC_MD4) &&
487	    (lp->entry->key.enctype != ENCTYPE_DES_CBC_RAW))
488	    continue;
489
490	for (lp1 = pruned; lp1; prev = lp1, lp1 = lp1->next) {
491	    /* Hunt for the current principal in the pruned list */
492	    if (krb5_principal_compare(context,
493				       lp->entry->principal,
494				       lp1->entry->principal))
495		    break;
496	}
497	if (!lp1) {		/* need to add entry to tail of pruned list */
498	    if (!pruned) {
499		pruned = (krb5_kt_list) malloc(sizeof (*pruned));
500		if (!pruned)
501		    return ENOMEM;
502		memset((char *) pruned, 0, sizeof(*pruned));
503		lp1 = pruned;
504	    } else {
505		prev->next
506		    = (krb5_kt_list) malloc(sizeof (*pruned));
507		if (!prev->next) {
508		    retval = ENOMEM;
509		    goto free_pruned;
510		}
511		memset((char *) prev->next, 0, sizeof(*pruned));
512		lp1 = prev->next;
513	    }
514	    lp1->entry = lp->entry;
515	} else if (lp1->entry->vno < lp->entry->vno)
516	    /* Check if lp->entry is newer kvno; if so, update */
517	    lp1->entry = lp->entry;
518    }
519    fp = fopen(name, "w");
520    if (!fp) {
521	retval = EIO;
522	goto free_pruned;
523    }
524    for (lp = pruned; lp; lp = lp->next) {
525	unsigned char  kvno;
526	kvno = (unsigned char) lp->entry->vno;
527	retval = krb5_524_conv_principal(context,
528					 lp->entry->principal,
529					 sname, sinst, srealm);
530	if (retval)
531	    break;
532	fwrite(sname, strlen(sname) + 1, 1, fp);
533	fwrite(sinst, strlen(sinst) + 1, 1, fp);
534	fwrite(srealm, strlen(srealm) + 1, 1, fp);
535	fwrite((char *)&kvno, 1, 1, fp);
536	fwrite((char *)lp->entry->key.contents,
537	       sizeof (des_cblock), 1, fp);
538    }
539    fclose(fp);
540 free_pruned:
541    /*
542     * Loop over and free the pruned list; don't use free_kt_list
543     * because that kills the entries.
544     */
545    for (lp = pruned; lp;) {
546	prev = lp;
547	lp = lp->next;
548	free((char *)prev);
549    }
550    return retval;
551}
552#endif /* KRB5_KRB4_COMPAT */
553