xref: /freebsd/crypto/heimdal/lib/hdb/print.c (revision 5000d023a446b81f6d45ed59aa379607ec814f01)
1 /*
2  * Copyright (c) 1999-2005 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of KTH nor the names of its contributors may be
18  *    used to endorse or promote products derived from this software without
19  *    specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32 
33 #include "hdb_locl.h"
34 #include <hex.h>
35 #include <ctype.h>
36 
37 /*
38    This is the present contents of a dump line. This might change at
39    any time. Fields are separated by white space.
40 
41   principal
42   keyblock
43   	kvno
44 	keys...
45 		mkvno
46 		enctype
47 		keyvalue
48 		salt (- means use normal salt)
49   creation date and principal
50   modification date and principal
51   principal valid from date (not used)
52   principal valid end date (not used)
53   principal key expires (not used)
54   max ticket life
55   max renewable life
56   flags
57   generation number
58   */
59 
60 /*
61  * These utility functions return the number of bytes written or -1, and
62  * they set an error in the context.
63  */
64 static ssize_t
append_string(krb5_context context,krb5_storage * sp,const char * fmt,...)65 append_string(krb5_context context, krb5_storage *sp, const char *fmt, ...)
66 {
67     ssize_t sz;
68     char *s;
69     int rc;
70     va_list ap;
71     va_start(ap, fmt);
72     rc = vasprintf(&s, fmt, ap);
73     va_end(ap);
74     if(rc < 0) {
75 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
76 	return -1;
77     }
78     sz = krb5_storage_write(sp, s, strlen(s));
79     free(s);
80     return sz;
81 }
82 
83 static krb5_error_code
append_hex(krb5_context context,krb5_storage * sp,int always_encode,int lower,krb5_data * data)84 append_hex(krb5_context context, krb5_storage *sp,
85            int always_encode, int lower, krb5_data *data)
86 {
87     ssize_t sz;
88     int printable = 1;
89     size_t i;
90     char *p;
91 
92     p = data->data;
93     if (!always_encode) {
94         for (i = 0; i < data->length; i++) {
95             if (!isalnum((unsigned char)p[i]) && p[i] != '.'){
96                 printable = 0;
97                 break;
98             }
99         }
100     }
101     if (printable && !always_encode)
102 	return append_string(context, sp, "\"%.*s\"",
103 			     data->length, data->data);
104     sz = hex_encode(data->data, data->length, &p);
105     if (sz == -1) return sz;
106     if (lower)
107         strlwr(p);
108     sz = append_string(context, sp, "%s", p);
109     free(p);
110     return sz;
111 }
112 
113 static char *
time2str(time_t t)114 time2str(time_t t)
115 {
116     static char buf[128];
117     strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", gmtime(&t));
118     return buf;
119 }
120 
121 static ssize_t
append_event(krb5_context context,krb5_storage * sp,Event * ev)122 append_event(krb5_context context, krb5_storage *sp, Event *ev)
123 {
124     krb5_error_code ret;
125     ssize_t sz;
126     char *pr = NULL;
127     if(ev == NULL)
128 	return append_string(context, sp, "- ");
129     if (ev->principal != NULL) {
130        ret = krb5_unparse_name(context, ev->principal, &pr);
131        if (ret) return -1; /* krb5_unparse_name() sets error info */
132     }
133     sz = append_string(context, sp, "%s:%s ", time2str(ev->time),
134                        pr ? pr : "UNKNOWN");
135     free(pr);
136     return sz;
137 }
138 
139 #define KRB5_KDB_SALTTYPE_NORMAL        0
140 #define KRB5_KDB_SALTTYPE_V4            1
141 #define KRB5_KDB_SALTTYPE_NOREALM       2
142 #define KRB5_KDB_SALTTYPE_ONLYREALM     3
143 #define KRB5_KDB_SALTTYPE_SPECIAL       4
144 #define KRB5_KDB_SALTTYPE_AFS3          5
145 
146 static ssize_t
append_mit_key(krb5_context context,krb5_storage * sp,krb5_const_principal princ,unsigned int kvno,Key * key)147 append_mit_key(krb5_context context, krb5_storage *sp,
148                krb5_const_principal princ,
149                unsigned int kvno, Key *key)
150 {
151     krb5_error_code ret;
152     ssize_t sz;
153     size_t key_versions = key->salt ? 2 : 1;
154     size_t decrypted_key_length;
155     char buf[2];
156     krb5_data keylenbytes;
157     unsigned int salttype;
158 
159     sz = append_string(context, sp, "\t%u\t%u\t%d\t%d\t", key_versions, kvno,
160                         key->key.keytype, key->key.keyvalue.length + 2);
161     if (sz == -1) return sz;
162     ret = krb5_enctype_keysize(context, key->key.keytype, &decrypted_key_length);
163     if (ret) return -1; /* XXX we lose the error code */
164     buf[0] = decrypted_key_length & 0xff;
165     buf[1] = (decrypted_key_length & 0xff00) >> 8;
166     keylenbytes.data = buf;
167     keylenbytes.length = sizeof (buf);
168     sz = append_hex(context, sp, 1, 1, &keylenbytes);
169     if (sz == -1) return sz;
170     sz = append_hex(context, sp, 1, 1, &key->key.keyvalue);
171     if (!key->salt)
172         return sz;
173 
174     /* Map salt to MIT KDB style */
175     if (key->salt->type == KRB5_PADATA_PW_SALT) {
176         krb5_salt k5salt;
177 
178         /*
179          * Compute normal salt and then see whether it matches the stored one
180          */
181         ret = krb5_get_pw_salt(context, princ, &k5salt);
182         if (ret) return -1;
183         if (k5salt.saltvalue.length == key->salt->salt.length &&
184             memcmp(k5salt.saltvalue.data, key->salt->salt.data,
185                    k5salt.saltvalue.length) == 0)
186             salttype = KRB5_KDB_SALTTYPE_NORMAL; /* matches */
187         else if (key->salt->salt.length == strlen(princ->realm) &&
188                  memcmp(key->salt->salt.data, princ->realm,
189                         key->salt->salt.length) == 0)
190             salttype = KRB5_KDB_SALTTYPE_ONLYREALM; /* matches realm */
191         else if (key->salt->salt.length == k5salt.saltvalue.length - strlen(princ->realm) &&
192                  memcmp((char *)k5salt.saltvalue.data + strlen(princ->realm),
193                         key->salt->salt.data, key->salt->salt.length) == 0)
194             salttype = KRB5_KDB_SALTTYPE_NOREALM; /* matches w/o realm */
195         else
196             salttype = KRB5_KDB_SALTTYPE_NORMAL;  /* hope for best */
197 
198     } else if (key->salt->type == KRB5_PADATA_AFS3_SALT) {
199         salttype = KRB5_KDB_SALTTYPE_AFS3;
200     }
201     sz = append_string(context, sp, "\t%u\t%u\t", salttype,
202                        key->salt->salt.length);
203     if (sz == -1) return sz;
204     return append_hex(context, sp, 1, 1, &key->salt->salt);
205 }
206 
207 static krb5_error_code
entry2string_int(krb5_context context,krb5_storage * sp,hdb_entry * ent)208 entry2string_int (krb5_context context, krb5_storage *sp, hdb_entry *ent)
209 {
210     char *p;
211     int i;
212     krb5_error_code ret;
213 
214     /* --- principal */
215     ret = krb5_unparse_name(context, ent->principal, &p);
216     if(ret)
217 	return ret;
218     append_string(context, sp, "%s ", p);
219     free(p);
220     /* --- kvno */
221     append_string(context, sp, "%d", ent->kvno);
222     /* --- keys */
223     for(i = 0; i < ent->keys.len; i++){
224 	/* --- mkvno, keytype */
225 	if(ent->keys.val[i].mkvno)
226 	    append_string(context, sp, ":%d:%d:",
227 			  *ent->keys.val[i].mkvno,
228 			  ent->keys.val[i].key.keytype);
229 	else
230 	    append_string(context, sp, "::%d:",
231 			  ent->keys.val[i].key.keytype);
232 	/* --- keydata */
233 	append_hex(context, sp, 0, 0, &ent->keys.val[i].key.keyvalue);
234 	append_string(context, sp, ":");
235 	/* --- salt */
236 	if(ent->keys.val[i].salt){
237 	    append_string(context, sp, "%u/", ent->keys.val[i].salt->type);
238 	    append_hex(context, sp, 0, 0, &ent->keys.val[i].salt->salt);
239 	}else
240 	    append_string(context, sp, "-");
241     }
242     append_string(context, sp, " ");
243     /* --- created by */
244     append_event(context, sp, &ent->created_by);
245     /* --- modified by */
246     append_event(context, sp, ent->modified_by);
247 
248     /* --- valid start */
249     if(ent->valid_start)
250 	append_string(context, sp, "%s ", time2str(*ent->valid_start));
251     else
252 	append_string(context, sp, "- ");
253 
254     /* --- valid end */
255     if(ent->valid_end)
256 	append_string(context, sp, "%s ", time2str(*ent->valid_end));
257     else
258 	append_string(context, sp, "- ");
259 
260     /* --- password ends */
261     if(ent->pw_end)
262 	append_string(context, sp, "%s ", time2str(*ent->pw_end));
263     else
264 	append_string(context, sp, "- ");
265 
266     /* --- max life */
267     if(ent->max_life)
268 	append_string(context, sp, "%d ", *ent->max_life);
269     else
270 	append_string(context, sp, "- ");
271 
272     /* --- max renewable life */
273     if(ent->max_renew)
274 	append_string(context, sp, "%d ", *ent->max_renew);
275     else
276 	append_string(context, sp, "- ");
277 
278     /* --- flags */
279     append_string(context, sp, "%d ", HDBFlags2int(ent->flags));
280 
281     /* --- generation number */
282     if(ent->generation) {
283 	append_string(context, sp, "%s:%d:%d ", time2str(ent->generation->time),
284 		      ent->generation->usec,
285 		      ent->generation->gen);
286     } else
287 	append_string(context, sp, "- ");
288 
289     /* --- extensions */
290     if(ent->extensions && ent->extensions->len > 0) {
291 	for(i = 0; i < ent->extensions->len; i++) {
292 	    void *d;
293 	    size_t size, sz = 0;
294 
295 	    ASN1_MALLOC_ENCODE(HDB_extension, d, size,
296 			       &ent->extensions->val[i], &sz, ret);
297 	    if (ret) {
298 		krb5_clear_error_message(context);
299 		return ret;
300 	    }
301 	    if(size != sz)
302 		krb5_abortx(context, "internal asn.1 encoder error");
303 
304 	    if (hex_encode(d, size, &p) < 0) {
305 		free(d);
306 		krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
307 		return ENOMEM;
308 	    }
309 
310 	    free(d);
311 	    append_string(context, sp, "%s%s", p,
312 			  ent->extensions->len - 1 != i ? ":" : "");
313 	    free(p);
314 	}
315     } else
316 	append_string(context, sp, "-");
317 
318     return 0;
319 }
320 
321 #define KRB5_KDB_DISALLOW_POSTDATED     0x00000001
322 #define KRB5_KDB_DISALLOW_FORWARDABLE   0x00000002
323 #define KRB5_KDB_DISALLOW_TGT_BASED     0x00000004
324 #define KRB5_KDB_DISALLOW_RENEWABLE     0x00000008
325 #define KRB5_KDB_DISALLOW_PROXIABLE     0x00000010
326 #define KRB5_KDB_DISALLOW_DUP_SKEY      0x00000020
327 #define KRB5_KDB_DISALLOW_ALL_TIX       0x00000040
328 #define KRB5_KDB_REQUIRES_PRE_AUTH      0x00000080
329 #define KRB5_KDB_REQUIRES_HW_AUTH       0x00000100
330 #define KRB5_KDB_REQUIRES_PWCHANGE      0x00000200
331 #define KRB5_KDB_DISALLOW_SVR           0x00001000
332 #define KRB5_KDB_PWCHANGE_SERVICE       0x00002000
333 #define KRB5_KDB_SUPPORT_DESMD5         0x00004000
334 #define KRB5_KDB_NEW_PRINC              0x00008000
335 
336 static int
flags_to_attr(HDBFlags flags)337 flags_to_attr(HDBFlags flags)
338 {
339     int a = 0;
340 
341     if (!flags.postdate)
342         a |= KRB5_KDB_DISALLOW_POSTDATED;
343     if (!flags.forwardable)
344         a |= KRB5_KDB_DISALLOW_FORWARDABLE;
345     if (flags.initial)
346         a |= KRB5_KDB_DISALLOW_TGT_BASED;
347     if (!flags.renewable)
348         a |= KRB5_KDB_DISALLOW_RENEWABLE;
349     if (!flags.proxiable)
350         a |= KRB5_KDB_DISALLOW_PROXIABLE;
351     if (flags.invalid)
352         a |= KRB5_KDB_DISALLOW_ALL_TIX;
353     if (flags.require_preauth)
354         a |= KRB5_KDB_REQUIRES_PRE_AUTH;
355     if (flags.require_hwauth)
356         a |= KRB5_KDB_REQUIRES_HW_AUTH;
357     if (!flags.server)
358         a |= KRB5_KDB_DISALLOW_SVR;
359     if (flags.change_pw)
360         a |= KRB5_KDB_PWCHANGE_SERVICE;
361     return a;
362 }
363 
364 krb5_error_code
entry2mit_string_int(krb5_context context,krb5_storage * sp,hdb_entry * ent)365 entry2mit_string_int(krb5_context context, krb5_storage *sp, hdb_entry *ent)
366 {
367     krb5_error_code ret;
368     ssize_t sz;
369     size_t i, k;
370     size_t num_tl_data = 0;
371     size_t num_key_data = 0;
372     char *p;
373     HDB_Ext_KeySet *hist_keys = NULL;
374     HDB_extension *extp;
375     time_t last_pw_chg = 0;
376     time_t exp = 0;
377     time_t pwexp = 0;
378     unsigned int max_life = 0;
379     unsigned int max_renew = 0;
380 
381     /* Always create a modified_by entry. */
382     num_tl_data++;
383 
384     ret = hdb_entry_get_pw_change_time(ent, &last_pw_chg);
385     if (ret) return ret;
386     if (last_pw_chg)
387         num_tl_data++;
388 
389     extp = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
390     if (extp)
391         hist_keys = &extp->data.u.hist_keys;
392 
393     for (i = 0; i < ent->keys.len;i++) {
394 	if (!mit_strong_etype(ent->keys.val[i].key.keytype))
395             continue;
396         num_key_data++;
397     }
398     if (hist_keys) {
399         for (i = 0; i < hist_keys->len; i++) {
400             /*
401              * MIT uses the highest kvno as the current kvno instead of
402              * tracking kvno separately, so we can't dump keysets with kvno
403              * higher than the entry's kvno.
404              */
405             if (hist_keys->val[i].kvno >= ent->kvno)
406                 continue;
407             for (k = 0; k < hist_keys->val[i].keys.len; k++) {
408                 if (ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD4 ||
409                     ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD5)
410                     continue;
411                 num_key_data++;
412             }
413         }
414     }
415 
416     ret = krb5_unparse_name(context, ent->principal, &p);
417     if (ret) return ret;
418     sz = append_string(context, sp, "princ\t38\t%u\t%u\t%u\t0\t%s\t%d",
419                        strlen(p), num_tl_data, num_key_data, p,
420                        flags_to_attr(ent->flags));
421     if (sz == -1) {
422 	free(p);
423 	return ENOMEM;
424     }
425 
426     if (ent->max_life)
427         max_life = *ent->max_life;
428     if (ent->max_renew)
429         max_renew = *ent->max_renew;
430     if (ent->valid_end)
431         exp = *ent->valid_end;
432     if (ent->pw_end)
433         pwexp = *ent->pw_end;
434 
435     sz = append_string(context, sp, "\t%u\t%u\t%u\t%u\t0\t0\t0",
436                        max_life, max_renew, exp, pwexp);
437     if (sz == -1) {
438 	free(p);
439 	return ENOMEM;
440     }
441 
442     /* Dump TL data we know: last pw chg and modified_by */
443 #define mit_KRB5_TL_LAST_PWD_CHANGE     1
444 #define mit_KRB5_TL_MOD_PRINC           2
445     if (last_pw_chg) {
446         krb5_data d;
447         time_t val;
448         unsigned char *ptr;
449 
450         ptr = (unsigned char *)&last_pw_chg;
451         val = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
452         d.data = &val;
453         d.length = sizeof (last_pw_chg);
454         sz = append_string(context, sp, "\t%u\t%u\t",
455                            mit_KRB5_TL_LAST_PWD_CHANGE, d.length);
456 	if (sz == -1) {
457 	    free(p);
458 	    return ENOMEM;
459 	}
460         sz = append_hex(context, sp, 1, 1, &d);
461 	if (sz == -1) {
462 	    free(p);
463 	    return ENOMEM;
464 	}
465     }
466     if (ent->modified_by) {
467         krb5_data d;
468         unsigned int val;
469         size_t plen;
470         unsigned char *ptr;
471         char *modby_p;
472 
473 	free(p);
474         ptr = (unsigned char *)&ent->modified_by->time;
475         val = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
476         d.data = &val;
477         d.length = sizeof (ent->modified_by->time);
478         ret = krb5_unparse_name(context, ent->modified_by->principal, &modby_p);
479         if (ret) return ret;
480         plen = strlen(modby_p);
481         sz = append_string(context, sp, "\t%u\t%u\t",
482                            mit_KRB5_TL_MOD_PRINC,
483                            d.length + plen + 1 /* NULL counted */);
484         if (sz == -1) {
485             free(modby_p);
486             return ENOMEM;
487         }
488         sz = append_hex(context, sp, 1, 1, &d);
489         if (sz == -1) {
490             free(modby_p);
491             return ENOMEM;
492         }
493         d.data = modby_p;
494         d.length = plen + 1;
495         sz = append_hex(context, sp, 1, 1, &d);
496         free(modby_p);
497         if (sz == -1) return ENOMEM;
498     } else {
499         krb5_data d;
500         unsigned int val;
501         size_t plen;
502         unsigned char *ptr;
503 
504 	/* Fake the entry to make MIT happy. */
505         ptr = (unsigned char *)&last_pw_chg;
506         val = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
507         d.data = &val;
508         d.length = sizeof (last_pw_chg);
509         plen = strlen(p);
510         sz = append_string(context, sp, "\t%u\t%u\t",
511                            mit_KRB5_TL_MOD_PRINC,
512                            d.length + plen + 1 /* NULL counted */);
513 	if (sz == -1) {
514 	    free(p);
515 	    return ENOMEM;
516 	}
517         sz = append_hex(context, sp, 1, 1, &d);
518 	if (sz == -1) {
519 	    free(p);
520 	    return ENOMEM;
521 	}
522         d.data = p;
523         d.length = plen + 1;
524         sz = append_hex(context, sp, 1, 1, &d);
525 	free(p);
526         if (sz == -1) return ENOMEM;
527     }
528     /*
529      * Dump keys (remembering to not include any with kvno higher than
530      * the entry's because MIT doesn't track entry kvno separately from
531      * the entry's keys -- max kvno is it)
532      */
533     for (i = 0; i < ent->keys.len; i++) {
534 	if (!mit_strong_etype(ent->keys.val[i].key.keytype))
535             continue;
536         sz = append_mit_key(context, sp, ent->principal, ent->kvno,
537                             &ent->keys.val[i]);
538         if (sz == -1) return ENOMEM;
539     }
540     for (i = 0; hist_keys && i < ent->kvno; i++) {
541         size_t m;
542 
543         /* dump historical keys */
544         for (k = 0; k < hist_keys->len; k++) {
545             if (hist_keys->val[k].kvno != ent->kvno - i)
546                 continue;
547             for (m = 0; m < hist_keys->val[k].keys.len; m++) {
548                 if (ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD4 ||
549                     ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD5)
550                     continue;
551                 sz = append_mit_key(context, sp, ent->principal,
552                                     hist_keys->val[k].kvno,
553                                     &hist_keys->val[k].keys.val[m]);
554                 if (sz == -1) return ENOMEM;
555             }
556         }
557     }
558     sz = append_string(context, sp, "\t-1;"); /* "extra data" */
559     if (sz == -1) return ENOMEM;
560     return 0;
561 }
562 
563 krb5_error_code
hdb_entry2string(krb5_context context,hdb_entry * ent,char ** str)564 hdb_entry2string(krb5_context context, hdb_entry *ent, char **str)
565 {
566     krb5_error_code ret;
567     krb5_data data;
568     krb5_storage *sp;
569 
570     sp = krb5_storage_emem();
571     if (sp == NULL) {
572 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
573 	return ENOMEM;
574     }
575 
576     ret = entry2string_int(context, sp, ent);
577     if (ret) {
578 	krb5_storage_free(sp);
579 	return ret;
580     }
581 
582     krb5_storage_write(sp, "\0", 1);
583     krb5_storage_to_data(sp, &data);
584     krb5_storage_free(sp);
585     *str = data.data;
586     return 0;
587 }
588 
589 /* print a hdb_entry to (FILE*)data; suitable for hdb_foreach */
590 
591 krb5_error_code
hdb_print_entry(krb5_context context,HDB * db,hdb_entry_ex * entry,void * data)592 hdb_print_entry(krb5_context context, HDB *db, hdb_entry_ex *entry,
593                 void *data)
594 {
595     struct hdb_print_entry_arg *parg = data;
596     krb5_error_code ret;
597     krb5_storage *sp;
598 
599     fflush(parg->out);
600     sp = krb5_storage_from_fd(fileno(parg->out));
601     if (sp == NULL) {
602 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
603 	return ENOMEM;
604     }
605 
606     switch (parg->fmt) {
607     case HDB_DUMP_HEIMDAL:
608         ret = entry2string_int(context, sp, &entry->entry);
609         break;
610     case HDB_DUMP_MIT:
611         ret = entry2mit_string_int(context, sp, &entry->entry);
612         break;
613     default:
614         heim_abort("Only two dump formats supported: Heimdal and MIT");
615     }
616     if (ret) {
617 	krb5_storage_free(sp);
618 	return ret;
619     }
620 
621     krb5_storage_write(sp, "\n", 1);
622     krb5_storage_free(sp);
623     return 0;
624 }
625