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  */
50 krb5_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  */
73 krb5_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  */
100 krb5_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  */
247 krb5_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  */
322 krb5_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 
359 static 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  */
376 krb5_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  */
473 krb5_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