1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * kadmin/ktutil/ktutil_funcs.c
8  *
9  *(C) Copyright 1995, 1996 by the Massachusetts Institute of Technology.
10  * All Rights Reserved.
11  *
12  * Export of this software from the United States of America may
13  *   require a specific license from the United States Government.
14  *   It is the responsibility of any person or organization contemplating
15  *   export to obtain such a license before exporting.
16  *
17  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
18  * distribute this software and its documentation for any purpose and
19  * without fee is hereby granted, provided that the above copyright
20  * notice appear in all copies and that both that copyright notice and
21  * this permission notice appear in supporting documentation, and that
22  * the name of M.I.T. not be used in advertising or publicity pertaining
23  * to distribution of the software without specific, written prior
24  * permission.  Furthermore if you modify this software you must label
25  * your software as modified software and not distribute it in such a
26  * fashion that it might be confused with the original M.I.T. software.
27  * M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is" without express
29  * or implied warranty.
30  *
31  * Utility functions for ktutil.
32  */
33 
34 #include "k5-int.h"
35 #include "ktutil.h"
36 #ifdef KRB5_KRB4_COMPAT
37 #include "kerberosIV/krb.h"
38 #include <stdio.h>
39 #endif
40 #include <string.h>
41 #include <ctype.h>
42 #include <libintl.h>
43 
44 /*
45  * Free a kt_list
46  */
ktutil_free_kt_list(context,list)47 krb5_error_code ktutil_free_kt_list(context, list)
48     krb5_context context;
49     krb5_kt_list list;
50 {
51     krb5_kt_list lp, prev;
52     krb5_error_code retval = 0;
53 
54     for (lp = list; lp;) {
55 	retval = krb5_kt_free_entry(context, lp->entry);
56 	free((char *)lp->entry);
57 	if (retval)
58 	    break;
59 	prev = lp;
60 	lp = lp->next;
61 	free((char *)prev);
62     }
63     return retval;
64 }
65 
66 /*
67  * Delete a numbered entry in a kt_list.  Takes a pointer to a kt_list
68  * in case head gets deleted.
69  */
ktutil_delete(context,list,idx)70 krb5_error_code ktutil_delete(context, list, idx)
71     krb5_context context;
72     krb5_kt_list *list;
73     int idx;
74 {
75     krb5_kt_list lp, prev;
76     int i;
77 
78     for (lp = *list, i = 1; lp; prev = lp, lp = lp->next, i++) {
79 	if (i == idx) {
80 	    if (i == 1)
81 		*list = lp->next;
82 	    else
83 		prev->next = lp->next;
84 	    lp->next = NULL;
85 	    return ktutil_free_kt_list(context, lp);
86 	}
87     }
88     return EINVAL;
89 }
90 
91 /*
92  * Create a new keytab entry and add it to the keytab list.
93  * Based on the value of use_pass, either prompt the user for a
94  * password or key.  If the keytab list is NULL, allocate a new
95  * one first.
96  */
ktutil_add(context,list,princ_str,kvno,enctype_str,use_pass)97 krb5_error_code ktutil_add(context, list, princ_str, kvno,
98 			   enctype_str, use_pass)
99     krb5_context context;
100     krb5_kt_list *list;
101     char *princ_str;
102     krb5_kvno kvno;
103     char *enctype_str;
104     int use_pass;
105 {
106     krb5_keytab_entry *entry;
107     krb5_kt_list lp = NULL, prev = NULL;
108     krb5_principal princ;
109     krb5_enctype enctype;
110     krb5_timestamp now;
111     krb5_error_code retval;
112     krb5_data password, salt;
113     krb5_keyblock key;
114     char buf[BUFSIZ];
115     char promptstr[1024];
116 
117     char *cp;
118     int i, tmp;
119     unsigned int pwsize = BUFSIZ;
120 
121     retval = krb5_parse_name(context, princ_str, &princ);
122     if (retval)
123         return retval;
124     /* now unparse in order to get the default realm appended
125        to princ_str, if no realm was specified */
126     retval = krb5_unparse_name(context, princ, &princ_str);
127     if (retval)
128         return retval;
129     retval = krb5_string_to_enctype(enctype_str, &enctype);
130     if (retval)
131         return KRB5_BAD_ENCTYPE;
132     retval = krb5_timeofday(context, &now);
133     if (retval)
134         return retval;
135 
136     if (*list) {
137         /* point lp at the tail of the list */
138         for (lp = *list; lp->next; lp = lp->next);
139     }
140     entry = (krb5_keytab_entry *) malloc(sizeof(krb5_keytab_entry));
141     if (!entry) {
142         return ENOMEM;
143     }
144     memset((char *) entry, 0, sizeof(*entry));
145 
146     if (!lp) {		/* if list is empty, start one */
147         lp = (krb5_kt_list) malloc(sizeof(*lp));
148 	if (!lp) {
149 	    return ENOMEM;
150 	}
151     } else {
152         lp->next = (krb5_kt_list) malloc(sizeof(*lp));
153 	if (!lp->next) {
154 	    return ENOMEM;
155 	}
156 	prev = lp;
157 	lp = lp->next;
158     }
159     lp->next = NULL;
160     lp->entry = entry;
161 
162     if (use_pass) {
163         password.length = pwsize;
164 	password.data = (char *) malloc(pwsize);
165 	if (!password.data) {
166 	    retval = ENOMEM;
167 	    goto cleanup;
168 	}
169 
170 	(void) snprintf(promptstr, sizeof(promptstr),
171 		gettext("Password for %.1000s"), princ_str);
172         retval = krb5_read_password(context, promptstr, NULL, password.data,
173 				    &password.length);
174 	if (retval)
175 	    goto cleanup;
176 	retval = krb5_principal2salt(context, princ, &salt);
177 	if (retval)
178 	    goto cleanup;
179 	retval = krb5_c_string_to_key(context, enctype, &password,
180 				      &salt, &key);
181 	if (retval)
182 	    goto cleanup;
183 	memset(password.data, 0, password.length);
184 	password.length = 0;
185 	memcpy(&lp->entry->key, &key, sizeof(krb5_keyblock));
186     } else {
187         printf(gettext("Key for %s (hex): "), princ_str);
188 	fgets(buf, BUFSIZ, stdin);
189 	/*
190 	 * We need to get rid of the trailing '\n' from fgets.
191 	 * If we have an even number of hex digits (as we should),
192 	 * write a '\0' over the '\n'.  If for some reason we have
193 	 * an odd number of hex digits, force an even number of hex
194 	 * digits by writing a '0' into the last position (the string
195 	 * will still be null-terminated).
196 	 */
197 	buf[strlen(buf) - 1] = strlen(buf) % 2 ? '\0' : '0';
198 	if (strlen(buf) == 0) {
199 	    fprintf(stderr, "addent: %s", gettext("Error reading key.\n"));
200 	    retval = 0;
201 	    goto cleanup;
202 	}
203 
204         lp->entry->key.enctype = enctype;
205 	lp->entry->key.contents = (krb5_octet *) malloc((strlen(buf) + 1) / 2);
206 	if (!lp->entry->key.contents) {
207 	    retval = ENOMEM;
208 	    goto cleanup;
209 	}
210 
211 	i = 0;
212 	for (cp = buf; *cp; cp += 2) {
213 	    if (!isxdigit((int) cp[0]) || !isxdigit((int) cp[1])) {
214 	        fprintf(stderr, "addent: %s",
215 			gettext("Illegal character in key.\n"));
216 		retval = 0;
217 		goto cleanup;
218 	    }
219 	    sscanf(cp, "%02x", &tmp);
220 	    lp->entry->key.contents[i++] = (krb5_octet) tmp;
221 	}
222 	lp->entry->key.length = i;
223     }
224     lp->entry->principal = princ;
225     lp->entry->vno = kvno;
226     lp->entry->timestamp = now;
227 
228     if (!*list)
229 	*list = lp;
230 
231     return 0;
232 
233  cleanup:
234     if (prev)
235         prev->next = NULL;
236     ktutil_free_kt_list(context, lp);
237     return retval;
238 }
239 
240 /*
241  * Read in a keytab and append it to list.  If list starts as NULL,
242  * allocate a new one if necessary.
243  */
ktutil_read_keytab(context,name,list)244 krb5_error_code ktutil_read_keytab(context, name, list)
245     krb5_context context;
246     char *name;
247     krb5_kt_list *list;
248 {
249     krb5_kt_list lp = NULL, tail = NULL, back = NULL;
250     krb5_keytab kt;
251     krb5_keytab_entry *entry;
252     krb5_kt_cursor cursor;
253     krb5_error_code retval = 0;
254 
255     if (*list) {
256 	/* point lp at the tail of the list */
257 	for (lp = *list; lp->next; lp = lp->next);
258 	back = lp;
259     }
260     retval = krb5_kt_resolve(context, name, &kt);
261     if (retval)
262 	return retval;
263     retval = krb5_kt_start_seq_get(context, kt, &cursor);
264     if (retval)
265 	goto close_kt;
266     for (;;) {
267 	entry = (krb5_keytab_entry *)malloc(sizeof (krb5_keytab_entry));
268 	if (!entry) {
269 	    retval = ENOMEM;
270 	    break;
271 	}
272 	memset((char *)entry, 0, sizeof (*entry));
273 	retval = krb5_kt_next_entry(context, kt, entry, &cursor);
274 	if (retval)
275 	    break;
276 
277 	if (!lp) {		/* if list is empty, start one */
278 	    lp = (krb5_kt_list)malloc(sizeof (*lp));
279 	    if (!lp) {
280 		retval = ENOMEM;
281 		break;
282 	    }
283 	} else {
284 	    lp->next = (krb5_kt_list)malloc(sizeof (*lp));
285 	    if (!lp->next) {
286 		retval = ENOMEM;
287 		break;
288 	    }
289 	    lp = lp->next;
290 	}
291 	if (!tail)
292 	    tail = lp;
293 	lp->next = NULL;
294 	lp->entry = entry;
295     }
296     if (entry)
297 	free((char *)entry);
298     if (retval) {
299 	if (retval == KRB5_KT_END)
300 	    retval = 0;
301 	else {
302 	    ktutil_free_kt_list(context, tail);
303 	    tail = NULL;
304 	    if (back)
305 		back->next = NULL;
306 	}
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  */
ktutil_write_keytab(context,list,name)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 
getstr(fp,s,n)356 static int getstr(fp, s, n)
357     FILE *fp;
358     register char *s;
359     int n;
360 {
361     register int 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  */
ktutil_read_srvtab(context,name,list)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  */
ktutil_write_srvtab(context,list,name)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 {
516 	    /* This heuristic should be roughly the same as in the
517 	       keytab-reading code in libkrb5.  */
518 	    int offset = 0;
519 	    if (lp1->entry->vno > 240 || lp->entry->vno > 240) {
520 		offset = 128;
521 	    }
522 #define M(X) (((X) + offset) % 256)
523 	    if (M(lp1->entry->vno) < M(lp->entry->vno))
524 		/* Check if lp->entry is newer kvno; if so, update */
525 		lp1->entry = lp->entry;
526 	}
527     }
528     umask(0077); /*Changing umask for all of ktutil is OK
529 		  * We don't ever write out anything that should use
530 		  * default umask.*/
531     fp = fopen(name, "w");
532     if (!fp) {
533 	retval = EIO;
534 	goto free_pruned;
535     }
536     for (lp = pruned; lp; lp = lp->next) {
537 	unsigned char  kvno;
538 	kvno = (unsigned char) lp->entry->vno;
539 	retval = krb5_524_conv_principal(context,
540 					 lp->entry->principal,
541 					 sname, sinst, srealm);
542 	if (retval)
543 	    break;
544 	fwrite(sname, strlen(sname) + 1, 1, fp);
545 	fwrite(sinst, strlen(sinst) + 1, 1, fp);
546 	fwrite(srealm, strlen(srealm) + 1, 1, fp);
547 	fwrite((char *)&kvno, 1, 1, fp);
548 	fwrite((char *)lp->entry->key.contents,
549 	       sizeof (des_cblock), 1, fp);
550     }
551     fclose(fp);
552  free_pruned:
553     /*
554      * Loop over and free the pruned list; don't use free_kt_list
555      * because that kills the entries.
556      */
557     for (lp = pruned; lp;) {
558 	prev = lp;
559 	lp = lp->next;
560 	free((char *)prev);
561     }
562     return retval;
563 }
564 #endif /* KRB5_KRB4_COMPAT */
565