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  */
49 krb5_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  */
72 krb5_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  */
99 krb5_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  */
245 krb5_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  */
319 krb5_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 
356 int 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  */
373 krb5_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  */
470 krb5_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