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