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