changepw.c revision 178826
1/*
2 * Copyright (c) 1997 - 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 the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <krb5_locl.h>
35
36RCSID("$Id: changepw.c 21505 2007-07-12 12:28:38Z lha $");
37
38static void
39str2data (krb5_data *d,
40	  const char *fmt,
41	  ...) __attribute__ ((format (printf, 2, 3)));
42
43static void
44str2data (krb5_data *d,
45	  const char *fmt,
46	  ...)
47{
48    va_list args;
49    char *str;
50
51    va_start(args, fmt);
52    d->length = vasprintf (&str, fmt, args);
53    va_end(args);
54    d->data = str;
55}
56
57/*
58 * Change password protocol defined by
59 * draft-ietf-cat-kerb-chg-password-02.txt
60 *
61 * Share the response part of the protocol with MS set password
62 * (RFC3244)
63 */
64
65static krb5_error_code
66chgpw_send_request (krb5_context context,
67		    krb5_auth_context *auth_context,
68		    krb5_creds *creds,
69		    krb5_principal targprinc,
70		    int is_stream,
71		    int sock,
72		    const char *passwd,
73		    const char *host)
74{
75    krb5_error_code ret;
76    krb5_data ap_req_data;
77    krb5_data krb_priv_data;
78    krb5_data passwd_data;
79    size_t len;
80    u_char header[6];
81    u_char *p;
82    struct iovec iov[3];
83    struct msghdr msghdr;
84
85    if (is_stream)
86	return KRB5_KPASSWD_MALFORMED;
87
88    if (targprinc &&
89	krb5_principal_compare(context, creds->client, targprinc) != TRUE)
90	return KRB5_KPASSWD_MALFORMED;
91
92    krb5_data_zero (&ap_req_data);
93
94    ret = krb5_mk_req_extended (context,
95				auth_context,
96				AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
97				NULL, /* in_data */
98				creds,
99				&ap_req_data);
100    if (ret)
101	return ret;
102
103    passwd_data.data   = rk_UNCONST(passwd);
104    passwd_data.length = strlen(passwd);
105
106    krb5_data_zero (&krb_priv_data);
107
108    ret = krb5_mk_priv (context,
109			*auth_context,
110			&passwd_data,
111			&krb_priv_data,
112			NULL);
113    if (ret)
114	goto out2;
115
116    len = 6 + ap_req_data.length + krb_priv_data.length;
117    p = header;
118    *p++ = (len >> 8) & 0xFF;
119    *p++ = (len >> 0) & 0xFF;
120    *p++ = 0;
121    *p++ = 1;
122    *p++ = (ap_req_data.length >> 8) & 0xFF;
123    *p++ = (ap_req_data.length >> 0) & 0xFF;
124
125    memset(&msghdr, 0, sizeof(msghdr));
126    msghdr.msg_name       = NULL;
127    msghdr.msg_namelen    = 0;
128    msghdr.msg_iov        = iov;
129    msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
130#if 0
131    msghdr.msg_control    = NULL;
132    msghdr.msg_controllen = 0;
133#endif
134
135    iov[0].iov_base    = (void*)header;
136    iov[0].iov_len     = 6;
137    iov[1].iov_base    = ap_req_data.data;
138    iov[1].iov_len     = ap_req_data.length;
139    iov[2].iov_base    = krb_priv_data.data;
140    iov[2].iov_len     = krb_priv_data.length;
141
142    if (sendmsg (sock, &msghdr, 0) < 0) {
143	ret = errno;
144	krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
145    }
146
147    krb5_data_free (&krb_priv_data);
148out2:
149    krb5_data_free (&ap_req_data);
150    return ret;
151}
152
153/*
154 * Set password protocol as defined by RFC3244 --
155 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
156 */
157
158static krb5_error_code
159setpw_send_request (krb5_context context,
160		    krb5_auth_context *auth_context,
161		    krb5_creds *creds,
162		    krb5_principal targprinc,
163		    int is_stream,
164		    int sock,
165		    const char *passwd,
166		    const char *host)
167{
168    krb5_error_code ret;
169    krb5_data ap_req_data;
170    krb5_data krb_priv_data;
171    krb5_data pwd_data;
172    ChangePasswdDataMS chpw;
173    size_t len;
174    u_char header[4 + 6];
175    u_char *p;
176    struct iovec iov[3];
177    struct msghdr msghdr;
178
179    krb5_data_zero (&ap_req_data);
180
181    ret = krb5_mk_req_extended (context,
182				auth_context,
183				AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
184				NULL, /* in_data */
185				creds,
186				&ap_req_data);
187    if (ret)
188	return ret;
189
190    chpw.newpasswd.length = strlen(passwd);
191    chpw.newpasswd.data = rk_UNCONST(passwd);
192    if (targprinc) {
193	chpw.targname = &targprinc->name;
194	chpw.targrealm = &targprinc->realm;
195    } else {
196	chpw.targname = NULL;
197	chpw.targrealm = NULL;
198    }
199
200    ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,
201		       &chpw, &len, ret);
202    if (ret) {
203	krb5_data_free (&ap_req_data);
204	return ret;
205    }
206
207    if(pwd_data.length != len)
208	krb5_abortx(context, "internal error in ASN.1 encoder");
209
210    ret = krb5_mk_priv (context,
211			*auth_context,
212			&pwd_data,
213			&krb_priv_data,
214			NULL);
215    if (ret)
216	goto out2;
217
218    len = 6 + ap_req_data.length + krb_priv_data.length;
219    p = header;
220    if (is_stream) {
221	_krb5_put_int(p, len, 4);
222	p += 4;
223    }
224    *p++ = (len >> 8) & 0xFF;
225    *p++ = (len >> 0) & 0xFF;
226    *p++ = 0xff;
227    *p++ = 0x80;
228    *p++ = (ap_req_data.length >> 8) & 0xFF;
229    *p++ = (ap_req_data.length >> 0) & 0xFF;
230
231    memset(&msghdr, 0, sizeof(msghdr));
232    msghdr.msg_name       = NULL;
233    msghdr.msg_namelen    = 0;
234    msghdr.msg_iov        = iov;
235    msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
236#if 0
237    msghdr.msg_control    = NULL;
238    msghdr.msg_controllen = 0;
239#endif
240
241    iov[0].iov_base    = (void*)header;
242    if (is_stream)
243	iov[0].iov_len     = 10;
244    else
245	iov[0].iov_len     = 6;
246    iov[1].iov_base    = ap_req_data.data;
247    iov[1].iov_len     = ap_req_data.length;
248    iov[2].iov_base    = krb_priv_data.data;
249    iov[2].iov_len     = krb_priv_data.length;
250
251    if (sendmsg (sock, &msghdr, 0) < 0) {
252	ret = errno;
253	krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
254    }
255
256    krb5_data_free (&krb_priv_data);
257out2:
258    krb5_data_free (&ap_req_data);
259    krb5_data_free (&pwd_data);
260    return ret;
261}
262
263static krb5_error_code
264process_reply (krb5_context context,
265	       krb5_auth_context auth_context,
266	       int is_stream,
267	       int sock,
268	       int *result_code,
269	       krb5_data *result_code_string,
270	       krb5_data *result_string,
271	       const char *host)
272{
273    krb5_error_code ret;
274    u_char reply[1024 * 3];
275    ssize_t len;
276    uint16_t pkt_len, pkt_ver;
277    krb5_data ap_rep_data;
278    int save_errno;
279
280    len = 0;
281    if (is_stream) {
282	while (len < sizeof(reply)) {
283	    unsigned long size;
284
285	    ret = recvfrom (sock, reply + len, sizeof(reply) - len,
286			    0, NULL, NULL);
287	    if (ret < 0) {
288		save_errno = errno;
289		krb5_set_error_string(context, "recvfrom %s: %s",
290				      host, strerror(save_errno));
291		return save_errno;
292	    } else if (ret == 0) {
293		krb5_set_error_string(context, "recvfrom timeout %s", host);
294		return 1;
295	    }
296	    len += ret;
297	    if (len < 4)
298		continue;
299	    _krb5_get_int(reply, &size, 4);
300	    if (size + 4 < len)
301		continue;
302	    memmove(reply, reply + 4, size);
303	    len = size;
304	    break;
305	}
306	if (len == sizeof(reply)) {
307	    krb5_set_error_string(context, "message too large from %s",
308				  host);
309	    return ENOMEM;
310	}
311    } else {
312	ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);
313	if (ret < 0) {
314	    save_errno = errno;
315	    krb5_set_error_string(context, "recvfrom %s: %s",
316				  host, strerror(save_errno));
317	    return save_errno;
318	}
319	len = ret;
320    }
321
322    if (len < 6) {
323	str2data (result_string, "server %s sent to too short message "
324		  "(%ld bytes)", host, (long)len);
325	*result_code = KRB5_KPASSWD_MALFORMED;
326	return 0;
327    }
328
329    pkt_len = (reply[0] << 8) | (reply[1]);
330    pkt_ver = (reply[2] << 8) | (reply[3]);
331
332    if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {
333	KRB_ERROR error;
334	size_t size;
335	u_char *p;
336
337	memset(&error, 0, sizeof(error));
338
339	ret = decode_KRB_ERROR(reply, len, &error, &size);
340	if (ret)
341	    return ret;
342
343	if (error.e_data->length < 2) {
344	    str2data(result_string, "server %s sent too short "
345		     "e_data to print anything usable", host);
346	    free_KRB_ERROR(&error);
347	    *result_code = KRB5_KPASSWD_MALFORMED;
348	    return 0;
349	}
350
351	p = error.e_data->data;
352	*result_code = (p[0] << 8) | p[1];
353	if (error.e_data->length == 2)
354	    str2data(result_string, "server only sent error code");
355	else
356	    krb5_data_copy (result_string,
357			    p + 2,
358			    error.e_data->length - 2);
359	free_KRB_ERROR(&error);
360	return 0;
361    }
362
363    if (pkt_len != len) {
364	str2data (result_string, "client: wrong len in reply");
365	*result_code = KRB5_KPASSWD_MALFORMED;
366	return 0;
367    }
368    if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {
369	str2data (result_string,
370		  "client: wrong version number (%d)", pkt_ver);
371	*result_code = KRB5_KPASSWD_MALFORMED;
372	return 0;
373    }
374
375    ap_rep_data.data = reply + 6;
376    ap_rep_data.length  = (reply[4] << 8) | (reply[5]);
377
378    if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
379	str2data (result_string, "client: wrong AP len in reply");
380	*result_code = KRB5_KPASSWD_MALFORMED;
381	return 0;
382    }
383
384    if (ap_rep_data.length) {
385	krb5_ap_rep_enc_part *ap_rep;
386	krb5_data priv_data;
387	u_char *p;
388
389	priv_data.data   = (u_char*)ap_rep_data.data + ap_rep_data.length;
390	priv_data.length = len - ap_rep_data.length - 6;
391
392	ret = krb5_rd_rep (context,
393			   auth_context,
394			   &ap_rep_data,
395			   &ap_rep);
396	if (ret)
397	    return ret;
398
399	krb5_free_ap_rep_enc_part (context, ap_rep);
400
401	ret = krb5_rd_priv (context,
402			    auth_context,
403			    &priv_data,
404			    result_code_string,
405			    NULL);
406	if (ret) {
407	    krb5_data_free (result_code_string);
408	    return ret;
409	}
410
411	if (result_code_string->length < 2) {
412	    *result_code = KRB5_KPASSWD_MALFORMED;
413	    str2data (result_string,
414		      "client: bad length in result");
415	    return 0;
416	}
417
418        p = result_code_string->data;
419
420        *result_code = (p[0] << 8) | p[1];
421        krb5_data_copy (result_string,
422                        (unsigned char*)result_code_string->data + 2,
423                        result_code_string->length - 2);
424        return 0;
425    } else {
426	KRB_ERROR error;
427	size_t size;
428	u_char *p;
429
430	ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
431	if (ret) {
432	    return ret;
433	}
434	if (error.e_data->length < 2) {
435	    krb5_warnx (context, "too short e_data to print anything usable");
436	    return 1;		/* XXX */
437	}
438
439	p = error.e_data->data;
440	*result_code = (p[0] << 8) | p[1];
441	krb5_data_copy (result_string,
442			p + 2,
443			error.e_data->length - 2);
444	return 0;
445    }
446}
447
448
449/*
450 * change the password using the credentials in `creds' (for the
451 * principal indicated in them) to `newpw', storing the result of
452 * the operation in `result_*' and an error code or 0.
453 */
454
455typedef krb5_error_code (*kpwd_send_request) (krb5_context,
456					      krb5_auth_context *,
457					      krb5_creds *,
458					      krb5_principal,
459					      int,
460					      int,
461					      const char *,
462					      const char *);
463typedef krb5_error_code (*kpwd_process_reply) (krb5_context,
464					       krb5_auth_context,
465					       int,
466					       int,
467					       int *,
468					       krb5_data *,
469					       krb5_data *,
470					       const char *);
471
472static struct kpwd_proc {
473    const char *name;
474    int flags;
475#define SUPPORT_TCP	1
476#define SUPPORT_UDP	2
477    kpwd_send_request send_req;
478    kpwd_process_reply process_rep;
479} procs[] = {
480    {
481	"MS set password",
482	SUPPORT_TCP|SUPPORT_UDP,
483	setpw_send_request,
484	process_reply
485    },
486    {
487	"change password",
488	SUPPORT_UDP,
489	chgpw_send_request,
490	process_reply
491    },
492    { NULL }
493};
494
495static struct kpwd_proc *
496find_chpw_proto(const char *name)
497{
498    struct kpwd_proc *p;
499    for (p = procs; p->name != NULL; p++) {
500	if (strcmp(p->name, name) == 0)
501	    return p;
502    }
503    return NULL;
504}
505
506/*
507 *
508 */
509
510static krb5_error_code
511change_password_loop (krb5_context	context,
512		      krb5_creds	*creds,
513		      krb5_principal	targprinc,
514		      const char	*newpw,
515		      int		*result_code,
516		      krb5_data		*result_code_string,
517		      krb5_data		*result_string,
518		      struct kpwd_proc	*proc)
519{
520    krb5_error_code ret;
521    krb5_auth_context auth_context = NULL;
522    krb5_krbhst_handle handle = NULL;
523    krb5_krbhst_info *hi;
524    int sock;
525    int i;
526    int done = 0;
527    krb5_realm realm;
528
529    if (targprinc)
530	realm = targprinc->realm;
531    else
532	realm = creds->client->realm;
533
534    ret = krb5_auth_con_init (context, &auth_context);
535    if (ret)
536	return ret;
537
538    krb5_auth_con_setflags (context, auth_context,
539			    KRB5_AUTH_CONTEXT_DO_SEQUENCE);
540
541    ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle);
542    if (ret)
543	goto out;
544
545    while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
546	struct addrinfo *ai, *a;
547	int is_stream;
548
549	switch (hi->proto) {
550	case KRB5_KRBHST_UDP:
551	    if ((proc->flags & SUPPORT_UDP) == 0)
552		continue;
553	    is_stream = 0;
554	    break;
555	case KRB5_KRBHST_TCP:
556	    if ((proc->flags & SUPPORT_TCP) == 0)
557		continue;
558	    is_stream = 1;
559	    break;
560	default:
561	    continue;
562	}
563
564	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
565	if (ret)
566	    continue;
567
568	for (a = ai; !done && a != NULL; a = a->ai_next) {
569	    int replied = 0;
570
571	    sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
572	    if (sock < 0)
573		continue;
574
575	    ret = connect(sock, a->ai_addr, a->ai_addrlen);
576	    if (ret < 0) {
577		close (sock);
578		goto out;
579	    }
580
581	    ret = krb5_auth_con_genaddrs (context, auth_context, sock,
582					  KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
583	    if (ret) {
584		close (sock);
585		goto out;
586	    }
587
588	    for (i = 0; !done && i < 5; ++i) {
589		fd_set fdset;
590		struct timeval tv;
591
592		if (!replied) {
593		    replied = 0;
594
595		    ret = (*proc->send_req) (context,
596					     &auth_context,
597					     creds,
598					     targprinc,
599					     is_stream,
600					     sock,
601					     newpw,
602					     hi->hostname);
603		    if (ret) {
604			close(sock);
605			goto out;
606		    }
607		}
608
609		if (sock >= FD_SETSIZE) {
610		    krb5_set_error_string(context, "fd %d too large", sock);
611		    ret = ERANGE;
612		    close (sock);
613		    goto out;
614		}
615
616		FD_ZERO(&fdset);
617		FD_SET(sock, &fdset);
618		tv.tv_usec = 0;
619		tv.tv_sec  = 1 + (1 << i);
620
621		ret = select (sock + 1, &fdset, NULL, NULL, &tv);
622		if (ret < 0 && errno != EINTR) {
623		    close(sock);
624		    goto out;
625		}
626		if (ret == 1) {
627		    ret = (*proc->process_rep) (context,
628						auth_context,
629						is_stream,
630						sock,
631						result_code,
632						result_code_string,
633						result_string,
634						hi->hostname);
635		    if (ret == 0)
636			done = 1;
637		    else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
638			replied = 1;
639		} else {
640		    ret = KRB5_KDC_UNREACH;
641		}
642	    }
643	    close (sock);
644	}
645    }
646
647 out:
648    krb5_krbhst_free (context, handle);
649    krb5_auth_con_free (context, auth_context);
650    if (done)
651	return 0;
652    else {
653	if (ret == KRB5_KDC_UNREACH) {
654	    krb5_set_error_string(context,
655				  "unable to reach any changepw server "
656				  " in realm %s", realm);
657	    *result_code = KRB5_KPASSWD_HARDERROR;
658	}
659	return ret;
660    }
661}
662
663
664/*
665 * change the password using the credentials in `creds' (for the
666 * principal indicated in them) to `newpw', storing the result of
667 * the operation in `result_*' and an error code or 0.
668 */
669
670krb5_error_code KRB5_LIB_FUNCTION
671krb5_change_password (krb5_context	context,
672		      krb5_creds	*creds,
673		      const char	*newpw,
674		      int		*result_code,
675		      krb5_data		*result_code_string,
676		      krb5_data		*result_string)
677{
678    struct kpwd_proc *p = find_chpw_proto("change password");
679
680    *result_code = KRB5_KPASSWD_MALFORMED;
681    result_code_string->data = result_string->data = NULL;
682    result_code_string->length = result_string->length = 0;
683
684    if (p == NULL)
685	return KRB5_KPASSWD_MALFORMED;
686
687    return change_password_loop(context, creds, NULL, newpw,
688				result_code, result_code_string,
689				result_string, p);
690}
691
692/*
693 *
694 */
695
696krb5_error_code KRB5_LIB_FUNCTION
697krb5_set_password(krb5_context context,
698		  krb5_creds *creds,
699		  const char *newpw,
700		  krb5_principal targprinc,
701		  int *result_code,
702		  krb5_data *result_code_string,
703		  krb5_data *result_string)
704{
705    krb5_principal principal = NULL;
706    krb5_error_code ret = 0;
707    int i;
708
709    *result_code = KRB5_KPASSWD_MALFORMED;
710    result_code_string->data = result_string->data = NULL;
711    result_code_string->length = result_string->length = 0;
712
713    if (targprinc == NULL) {
714	ret = krb5_get_default_principal(context, &principal);
715	if (ret)
716	    return ret;
717    } else
718	principal = targprinc;
719
720    for (i = 0; procs[i].name != NULL; i++) {
721	*result_code = 0;
722	ret = change_password_loop(context, creds, principal, newpw,
723				   result_code, result_code_string,
724				   result_string,
725				   &procs[i]);
726	if (ret == 0 && *result_code == 0)
727	    break;
728    }
729
730    if (targprinc == NULL)
731	krb5_free_principal(context, principal);
732    return ret;
733}
734
735/*
736 *
737 */
738
739krb5_error_code KRB5_LIB_FUNCTION
740krb5_set_password_using_ccache(krb5_context context,
741			       krb5_ccache ccache,
742			       const char *newpw,
743			       krb5_principal targprinc,
744			       int *result_code,
745			       krb5_data *result_code_string,
746			       krb5_data *result_string)
747{
748    krb5_creds creds, *credsp;
749    krb5_error_code ret;
750    krb5_principal principal = NULL;
751
752    *result_code = KRB5_KPASSWD_MALFORMED;
753    result_code_string->data = result_string->data = NULL;
754    result_code_string->length = result_string->length = 0;
755
756    memset(&creds, 0, sizeof(creds));
757
758    if (targprinc == NULL) {
759	ret = krb5_cc_get_principal(context, ccache, &principal);
760	if (ret)
761	    return ret;
762    } else
763	principal = targprinc;
764
765    ret = krb5_make_principal(context, &creds.server,
766			      krb5_principal_get_realm(context, principal),
767			      "kadmin", "changepw", NULL);
768    if (ret)
769	goto out;
770
771    ret = krb5_cc_get_principal(context, ccache, &creds.client);
772    if (ret) {
773        krb5_free_principal(context, creds.server);
774	goto out;
775    }
776
777    ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
778    krb5_free_principal(context, creds.server);
779    krb5_free_principal(context, creds.client);
780    if (ret)
781	goto out;
782
783    ret = krb5_set_password(context,
784			    credsp,
785			    newpw,
786			    principal,
787			    result_code,
788			    result_code_string,
789			    result_string);
790
791    krb5_free_creds(context, credsp);
792
793    return ret;
794 out:
795    if (targprinc == NULL)
796	krb5_free_principal(context, principal);
797    return ret;
798}
799
800/*
801 *
802 */
803
804const char* KRB5_LIB_FUNCTION
805krb5_passwd_result_to_string (krb5_context context,
806			      int result)
807{
808    static const char *strings[] = {
809	"Success",
810	"Malformed",
811	"Hard error",
812	"Auth error",
813	"Soft error" ,
814	"Access denied",
815	"Bad version",
816	"Initial flag needed"
817    };
818
819    if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)
820	return "unknown result code";
821    else
822	return strings[result];
823}
824