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