155682Smarkm/*
2233294Sstas * Copyright (c) 1997 - 2005 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Redistribution and use in source and binary forms, with or without
7233294Sstas * modification, are permitted provided that the following conditions
8233294Sstas * are met:
955682Smarkm *
10233294Sstas * 1. Redistributions of source code must retain the above copyright
11233294Sstas *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
14233294Sstas *    notice, this list of conditions and the following disclaimer in the
15233294Sstas *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
17233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
18233294Sstas *    may be used to endorse or promote products derived from this software
19233294Sstas *    without specific prior written permission.
2055682Smarkm *
21233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31233294Sstas * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
34233294Sstas#include "krb5_locl.h"
3555682Smarkm
36233294Sstas#undef __attribute__
37233294Sstas#define __attribute__(X)
3855682Smarkm
39233294Sstas
40142403Snectarstatic void
41142403Snectarstr2data (krb5_data *d,
42142403Snectar	  const char *fmt,
43142403Snectar	  ...) __attribute__ ((format (printf, 2, 3)));
44142403Snectar
45142403Snectarstatic void
46142403Snectarstr2data (krb5_data *d,
47142403Snectar	  const char *fmt,
48142403Snectar	  ...)
49142403Snectar{
50142403Snectar    va_list args;
51178825Sdfr    char *str;
52142403Snectar
53142403Snectar    va_start(args, fmt);
54178825Sdfr    d->length = vasprintf (&str, fmt, args);
55142403Snectar    va_end(args);
56178825Sdfr    d->data = str;
57142403Snectar}
58142403Snectar
59142403Snectar/*
60142403Snectar * Change password protocol defined by
61142403Snectar * draft-ietf-cat-kerb-chg-password-02.txt
62233294Sstas *
63142403Snectar * Share the response part of the protocol with MS set password
64142403Snectar * (RFC3244)
65142403Snectar */
66142403Snectar
6755682Smarkmstatic krb5_error_code
68142403Snectarchgpw_send_request (krb5_context context,
69142403Snectar		    krb5_auth_context *auth_context,
70142403Snectar		    krb5_creds *creds,
71142403Snectar		    krb5_principal targprinc,
72142403Snectar		    int is_stream,
73233294Sstas		    rk_socket_t sock,
74178825Sdfr		    const char *passwd,
75142403Snectar		    const char *host)
7655682Smarkm{
7755682Smarkm    krb5_error_code ret;
7855682Smarkm    krb5_data ap_req_data;
7955682Smarkm    krb5_data krb_priv_data;
8055682Smarkm    krb5_data passwd_data;
8155682Smarkm    size_t len;
8255682Smarkm    u_char header[6];
8355682Smarkm    struct iovec iov[3];
8455682Smarkm    struct msghdr msghdr;
8555682Smarkm
86142403Snectar    if (is_stream)
87142403Snectar	return KRB5_KPASSWD_MALFORMED;
88142403Snectar
89142403Snectar    if (targprinc &&
90142403Snectar	krb5_principal_compare(context, creds->client, targprinc) != TRUE)
91142403Snectar	return KRB5_KPASSWD_MALFORMED;
92142403Snectar
9355682Smarkm    krb5_data_zero (&ap_req_data);
9455682Smarkm
9555682Smarkm    ret = krb5_mk_req_extended (context,
9655682Smarkm				auth_context,
97103423Snectar				AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
9855682Smarkm				NULL, /* in_data */
9955682Smarkm				creds,
10055682Smarkm				&ap_req_data);
10155682Smarkm    if (ret)
10255682Smarkm	return ret;
10355682Smarkm
104178825Sdfr    passwd_data.data   = rk_UNCONST(passwd);
10555682Smarkm    passwd_data.length = strlen(passwd);
10655682Smarkm
10755682Smarkm    krb5_data_zero (&krb_priv_data);
10855682Smarkm
10955682Smarkm    ret = krb5_mk_priv (context,
11055682Smarkm			*auth_context,
11155682Smarkm			&passwd_data,
11255682Smarkm			&krb_priv_data,
11355682Smarkm			NULL);
11455682Smarkm    if (ret)
11555682Smarkm	goto out2;
11655682Smarkm
11755682Smarkm    len = 6 + ap_req_data.length + krb_priv_data.length;
118233294Sstas    header[0] = (len >> 8) & 0xFF;
119233294Sstas    header[1] = (len >> 0) & 0xFF;
120233294Sstas    header[2] = 0;
121233294Sstas    header[3] = 1;
122233294Sstas    header[4] = (ap_req_data.length >> 8) & 0xFF;
123233294Sstas    header[5] = (ap_req_data.length >> 0) & 0xFF;
12455682Smarkm
12555682Smarkm    memset(&msghdr, 0, sizeof(msghdr));
12690926Snectar    msghdr.msg_name       = NULL;
12790926Snectar    msghdr.msg_namelen    = 0;
12855682Smarkm    msghdr.msg_iov        = iov;
12955682Smarkm    msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
13055682Smarkm#if 0
13155682Smarkm    msghdr.msg_control    = NULL;
13255682Smarkm    msghdr.msg_controllen = 0;
13355682Smarkm#endif
13455682Smarkm
13555682Smarkm    iov[0].iov_base    = (void*)header;
13655682Smarkm    iov[0].iov_len     = 6;
13755682Smarkm    iov[1].iov_base    = ap_req_data.data;
13855682Smarkm    iov[1].iov_len     = ap_req_data.length;
13955682Smarkm    iov[2].iov_base    = krb_priv_data.data;
14055682Smarkm    iov[2].iov_len     = krb_priv_data.length;
14155682Smarkm
142233294Sstas    if (rk_IS_SOCKET_ERROR( sendmsg (sock, &msghdr, 0) )) {
143233294Sstas	ret = rk_SOCK_ERRNO;
144233294Sstas	krb5_set_error_message(context, ret, "sendmsg %s: %s",
145233294Sstas			       host, strerror(ret));
14678527Sassar    }
14755682Smarkm
14855682Smarkm    krb5_data_free (&krb_priv_data);
14955682Smarkmout2:
15055682Smarkm    krb5_data_free (&ap_req_data);
15155682Smarkm    return ret;
15255682Smarkm}
15355682Smarkm
154142403Snectar/*
155142403Snectar * Set password protocol as defined by RFC3244 --
156142403Snectar * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
157142403Snectar */
15872445Sassar
159142403Snectarstatic krb5_error_code
160142403Snectarsetpw_send_request (krb5_context context,
161142403Snectar		    krb5_auth_context *auth_context,
162142403Snectar		    krb5_creds *creds,
163142403Snectar		    krb5_principal targprinc,
164142403Snectar		    int is_stream,
165233294Sstas		    rk_socket_t sock,
166178825Sdfr		    const char *passwd,
167142403Snectar		    const char *host)
16855682Smarkm{
169142403Snectar    krb5_error_code ret;
170142403Snectar    krb5_data ap_req_data;
171142403Snectar    krb5_data krb_priv_data;
172142403Snectar    krb5_data pwd_data;
173142403Snectar    ChangePasswdDataMS chpw;
174233294Sstas    size_t len = 0;
175142403Snectar    u_char header[4 + 6];
176142403Snectar    u_char *p;
177142403Snectar    struct iovec iov[3];
178142403Snectar    struct msghdr msghdr;
17955682Smarkm
180142403Snectar    krb5_data_zero (&ap_req_data);
181142403Snectar
182142403Snectar    ret = krb5_mk_req_extended (context,
183142403Snectar				auth_context,
184142403Snectar				AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
185142403Snectar				NULL, /* in_data */
186142403Snectar				creds,
187142403Snectar				&ap_req_data);
188142403Snectar    if (ret)
189142403Snectar	return ret;
190142403Snectar
191142403Snectar    chpw.newpasswd.length = strlen(passwd);
192178825Sdfr    chpw.newpasswd.data = rk_UNCONST(passwd);
193142403Snectar    if (targprinc) {
194142403Snectar	chpw.targname = &targprinc->name;
195142403Snectar	chpw.targrealm = &targprinc->realm;
196142403Snectar    } else {
197142403Snectar	chpw.targname = NULL;
198142403Snectar	chpw.targrealm = NULL;
199142403Snectar    }
200233294Sstas
201142403Snectar    ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,
202142403Snectar		       &chpw, &len, ret);
203142403Snectar    if (ret) {
204142403Snectar	krb5_data_free (&ap_req_data);
205142403Snectar	return ret;
206142403Snectar    }
207142403Snectar
208142403Snectar    if(pwd_data.length != len)
209142403Snectar	krb5_abortx(context, "internal error in ASN.1 encoder");
210142403Snectar
211142403Snectar    ret = krb5_mk_priv (context,
212142403Snectar			*auth_context,
213142403Snectar			&pwd_data,
214142403Snectar			&krb_priv_data,
215142403Snectar			NULL);
216142403Snectar    if (ret)
217142403Snectar	goto out2;
218142403Snectar
219142403Snectar    len = 6 + ap_req_data.length + krb_priv_data.length;
220142403Snectar    p = header;
221142403Snectar    if (is_stream) {
222142403Snectar	_krb5_put_int(p, len, 4);
223142403Snectar	p += 4;
224142403Snectar    }
225142403Snectar    *p++ = (len >> 8) & 0xFF;
226142403Snectar    *p++ = (len >> 0) & 0xFF;
227142403Snectar    *p++ = 0xff;
228142403Snectar    *p++ = 0x80;
229142403Snectar    *p++ = (ap_req_data.length >> 8) & 0xFF;
230233294Sstas    *p   = (ap_req_data.length >> 0) & 0xFF;
231142403Snectar
232142403Snectar    memset(&msghdr, 0, sizeof(msghdr));
233142403Snectar    msghdr.msg_name       = NULL;
234142403Snectar    msghdr.msg_namelen    = 0;
235142403Snectar    msghdr.msg_iov        = iov;
236142403Snectar    msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
237142403Snectar#if 0
238142403Snectar    msghdr.msg_control    = NULL;
239142403Snectar    msghdr.msg_controllen = 0;
240142403Snectar#endif
241142403Snectar
242142403Snectar    iov[0].iov_base    = (void*)header;
243142403Snectar    if (is_stream)
244142403Snectar	iov[0].iov_len     = 10;
245142403Snectar    else
246142403Snectar	iov[0].iov_len     = 6;
247142403Snectar    iov[1].iov_base    = ap_req_data.data;
248142403Snectar    iov[1].iov_len     = ap_req_data.length;
249142403Snectar    iov[2].iov_base    = krb_priv_data.data;
250142403Snectar    iov[2].iov_len     = krb_priv_data.length;
251142403Snectar
252233294Sstas    if (rk_IS_SOCKET_ERROR( sendmsg (sock, &msghdr, 0) )) {
253233294Sstas	ret = rk_SOCK_ERRNO;
254233294Sstas	krb5_set_error_message(context, ret, "sendmsg %s: %s",
255233294Sstas			       host, strerror(ret));
256142403Snectar    }
257142403Snectar
258142403Snectar    krb5_data_free (&krb_priv_data);
259142403Snectarout2:
260142403Snectar    krb5_data_free (&ap_req_data);
261142403Snectar    krb5_data_free (&pwd_data);
262142403Snectar    return ret;
26355682Smarkm}
26455682Smarkm
26555682Smarkmstatic krb5_error_code
26655682Smarkmprocess_reply (krb5_context context,
26755682Smarkm	       krb5_auth_context auth_context,
268142403Snectar	       int is_stream,
269233294Sstas	       rk_socket_t sock,
27055682Smarkm	       int *result_code,
27155682Smarkm	       krb5_data *result_code_string,
27278527Sassar	       krb5_data *result_string,
27378527Sassar	       const char *host)
27455682Smarkm{
27555682Smarkm    krb5_error_code ret;
276142403Snectar    u_char reply[1024 * 3];
277233294Sstas    size_t len;
278178825Sdfr    uint16_t pkt_len, pkt_ver;
279142403Snectar    krb5_data ap_rep_data;
28078527Sassar    int save_errno;
28155682Smarkm
282142403Snectar    len = 0;
283142403Snectar    if (is_stream) {
284142403Snectar	while (len < sizeof(reply)) {
285142403Snectar	    unsigned long size;
286142403Snectar
287233294Sstas	    ret = recvfrom (sock, reply + len, sizeof(reply) - len,
288142403Snectar			    0, NULL, NULL);
289233294Sstas	    if (rk_IS_SOCKET_ERROR(ret)) {
290233294Sstas		save_errno = rk_SOCK_ERRNO;
291233294Sstas		krb5_set_error_message(context, save_errno,
292233294Sstas				       "recvfrom %s: %s",
293233294Sstas				       host, strerror(save_errno));
294142403Snectar		return save_errno;
295142403Snectar	    } else if (ret == 0) {
296233294Sstas		krb5_set_error_message(context, 1,"recvfrom timeout %s", host);
297142403Snectar		return 1;
298142403Snectar	    }
299142403Snectar	    len += ret;
300142403Snectar	    if (len < 4)
301142403Snectar		continue;
302142403Snectar	    _krb5_get_int(reply, &size, 4);
303142403Snectar	    if (size + 4 < len)
304142403Snectar		continue;
305233294Sstas	    memmove(reply, reply + 4, size);
306142403Snectar	    len = size;
307142403Snectar	    break;
308142403Snectar	}
309142403Snectar	if (len == sizeof(reply)) {
310233294Sstas	    krb5_set_error_message(context, ENOMEM,
311233294Sstas				   N_("Message too large from %s", "host"),
312233294Sstas				   host);
313142403Snectar	    return ENOMEM;
314142403Snectar	}
315142403Snectar    } else {
316142403Snectar	ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);
317233294Sstas	if (rk_IS_SOCKET_ERROR(ret)) {
318233294Sstas	    save_errno = rk_SOCK_ERRNO;
319233294Sstas	    krb5_set_error_message(context, save_errno,
320233294Sstas				   "recvfrom %s: %s",
321233294Sstas				   host, strerror(save_errno));
322142403Snectar	    return save_errno;
323142403Snectar	}
324142403Snectar	len = ret;
32578527Sassar    }
32655682Smarkm
327142403Snectar    if (len < 6) {
328142403Snectar	str2data (result_string, "server %s sent to too short message "
329233294Sstas		  "(%zu bytes)", host, len);
330142403Snectar	*result_code = KRB5_KPASSWD_MALFORMED;
331142403Snectar	return 0;
332142403Snectar    }
333142403Snectar
33455682Smarkm    pkt_len = (reply[0] << 8) | (reply[1]);
33555682Smarkm    pkt_ver = (reply[2] << 8) | (reply[3]);
33655682Smarkm
337142403Snectar    if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {
338142403Snectar	KRB_ERROR error;
339142403Snectar	size_t size;
340142403Snectar	u_char *p;
341142403Snectar
342142403Snectar	memset(&error, 0, sizeof(error));
343142403Snectar
344142403Snectar	ret = decode_KRB_ERROR(reply, len, &error, &size);
345142403Snectar	if (ret)
346142403Snectar	    return ret;
347142403Snectar
348142403Snectar	if (error.e_data->length < 2) {
349142403Snectar	    str2data(result_string, "server %s sent too short "
350142403Snectar		     "e_data to print anything usable", host);
351142403Snectar	    free_KRB_ERROR(&error);
352142403Snectar	    *result_code = KRB5_KPASSWD_MALFORMED;
353142403Snectar	    return 0;
354142403Snectar	}
355142403Snectar
356142403Snectar	p = error.e_data->data;
357142403Snectar	*result_code = (p[0] << 8) | p[1];
358142403Snectar	if (error.e_data->length == 2)
359142403Snectar	    str2data(result_string, "server only sent error code");
360233294Sstas	else
361142403Snectar	    krb5_data_copy (result_string,
362142403Snectar			    p + 2,
363142403Snectar			    error.e_data->length - 2);
364142403Snectar	free_KRB_ERROR(&error);
365142403Snectar	return 0;
366142403Snectar    }
367142403Snectar
36855682Smarkm    if (pkt_len != len) {
36955682Smarkm	str2data (result_string, "client: wrong len in reply");
37055682Smarkm	*result_code = KRB5_KPASSWD_MALFORMED;
37155682Smarkm	return 0;
37255682Smarkm    }
373142403Snectar    if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {
37455682Smarkm	str2data (result_string,
37555682Smarkm		  "client: wrong version number (%d)", pkt_ver);
37655682Smarkm	*result_code = KRB5_KPASSWD_MALFORMED;
37755682Smarkm	return 0;
37855682Smarkm    }
37955682Smarkm
38055682Smarkm    ap_rep_data.data = reply + 6;
38155682Smarkm    ap_rep_data.length  = (reply[4] << 8) | (reply[5]);
382233294Sstas
383142403Snectar    if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
384142403Snectar	str2data (result_string, "client: wrong AP len in reply");
385142403Snectar	*result_code = KRB5_KPASSWD_MALFORMED;
386142403Snectar	return 0;
387142403Snectar    }
388142403Snectar
38955682Smarkm    if (ap_rep_data.length) {
39055682Smarkm	krb5_ap_rep_enc_part *ap_rep;
391142403Snectar	krb5_data priv_data;
39255682Smarkm	u_char *p;
39355682Smarkm
394142403Snectar	priv_data.data   = (u_char*)ap_rep_data.data + ap_rep_data.length;
395142403Snectar	priv_data.length = len - ap_rep_data.length - 6;
396142403Snectar
39755682Smarkm	ret = krb5_rd_rep (context,
39855682Smarkm			   auth_context,
39955682Smarkm			   &ap_rep_data,
40055682Smarkm			   &ap_rep);
40155682Smarkm	if (ret)
40255682Smarkm	    return ret;
40355682Smarkm
40455682Smarkm	krb5_free_ap_rep_enc_part (context, ap_rep);
40555682Smarkm
40655682Smarkm	ret = krb5_rd_priv (context,
40755682Smarkm			    auth_context,
40855682Smarkm			    &priv_data,
40955682Smarkm			    result_code_string,
41055682Smarkm			    NULL);
41155682Smarkm	if (ret) {
41255682Smarkm	    krb5_data_free (result_code_string);
41355682Smarkm	    return ret;
41455682Smarkm	}
41555682Smarkm
41655682Smarkm	if (result_code_string->length < 2) {
41755682Smarkm	    *result_code = KRB5_KPASSWD_MALFORMED;
41855682Smarkm	    str2data (result_string,
41955682Smarkm		      "client: bad length in result");
42055682Smarkm	    return 0;
42155682Smarkm	}
422142403Snectar
423142403Snectar        p = result_code_string->data;
424233294Sstas
425142403Snectar        *result_code = (p[0] << 8) | p[1];
426142403Snectar        krb5_data_copy (result_string,
427142403Snectar                        (unsigned char*)result_code_string->data + 2,
428142403Snectar                        result_code_string->length - 2);
429142403Snectar        return 0;
43055682Smarkm    } else {
43155682Smarkm	KRB_ERROR error;
43255682Smarkm	size_t size;
43355682Smarkm	u_char *p;
434233294Sstas
43555682Smarkm	ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
43655682Smarkm	if (ret) {
43755682Smarkm	    return ret;
43855682Smarkm	}
43955682Smarkm	if (error.e_data->length < 2) {
44055682Smarkm	    krb5_warnx (context, "too short e_data to print anything usable");
44178527Sassar	    return 1;		/* XXX */
44255682Smarkm	}
44355682Smarkm
44455682Smarkm	p = error.e_data->data;
44555682Smarkm	*result_code = (p[0] << 8) | p[1];
44655682Smarkm	krb5_data_copy (result_string,
44755682Smarkm			p + 2,
44855682Smarkm			error.e_data->length - 2);
44955682Smarkm	return 0;
45055682Smarkm    }
45155682Smarkm}
45255682Smarkm
453142403Snectar
45478527Sassar/*
45578527Sassar * change the password using the credentials in `creds' (for the
45678527Sassar * principal indicated in them) to `newpw', storing the result of
45778527Sassar * the operation in `result_*' and an error code or 0.
45878527Sassar */
45978527Sassar
460142403Snectartypedef krb5_error_code (*kpwd_send_request) (krb5_context,
461142403Snectar					      krb5_auth_context *,
462142403Snectar					      krb5_creds *,
463142403Snectar					      krb5_principal,
464142403Snectar					      int,
465233294Sstas					      rk_socket_t,
466178825Sdfr					      const char *,
467142403Snectar					      const char *);
468142403Snectartypedef krb5_error_code (*kpwd_process_reply) (krb5_context,
469142403Snectar					       krb5_auth_context,
470142403Snectar					       int,
471233294Sstas					       rk_socket_t,
472142403Snectar					       int *,
473142403Snectar					       krb5_data *,
474142403Snectar					       krb5_data *,
475142403Snectar					       const char *);
476142403Snectar
477178825Sdfrstatic struct kpwd_proc {
478142403Snectar    const char *name;
479142403Snectar    int flags;
480142403Snectar#define SUPPORT_TCP	1
481142403Snectar#define SUPPORT_UDP	2
482142403Snectar    kpwd_send_request send_req;
483142403Snectar    kpwd_process_reply process_rep;
484142403Snectar} procs[] = {
485142403Snectar    {
486233294Sstas	"MS set password",
487142403Snectar	SUPPORT_TCP|SUPPORT_UDP,
488233294Sstas	setpw_send_request,
489142403Snectar	process_reply
490142403Snectar    },
491142403Snectar    {
492142403Snectar	"change password",
493142403Snectar	SUPPORT_UDP,
494142403Snectar	chgpw_send_request,
495142403Snectar	process_reply
496142403Snectar    },
497233294Sstas    { NULL, 0, NULL, NULL }
498142403Snectar};
499142403Snectar
500142403Snectar/*
501142403Snectar *
502142403Snectar */
503142403Snectar
504142403Snectarstatic krb5_error_code
505142403Snectarchange_password_loop (krb5_context	context,
50655682Smarkm		      krb5_creds	*creds,
507142403Snectar		      krb5_principal	targprinc,
508178825Sdfr		      const char	*newpw,
50955682Smarkm		      int		*result_code,
51055682Smarkm		      krb5_data		*result_code_string,
511142403Snectar		      krb5_data		*result_string,
512142403Snectar		      struct kpwd_proc	*proc)
51355682Smarkm{
51455682Smarkm    krb5_error_code ret;
51555682Smarkm    krb5_auth_context auth_context = NULL;
51690926Snectar    krb5_krbhst_handle handle = NULL;
51790926Snectar    krb5_krbhst_info *hi;
518233294Sstas    rk_socket_t sock;
519233294Sstas    unsigned int i;
52072445Sassar    int done = 0;
521178825Sdfr    krb5_realm realm;
52255682Smarkm
523178825Sdfr    if (targprinc)
524178825Sdfr	realm = targprinc->realm;
525178825Sdfr    else
526178825Sdfr	realm = creds->client->realm;
527178825Sdfr
52855682Smarkm    ret = krb5_auth_con_init (context, &auth_context);
52955682Smarkm    if (ret)
53055682Smarkm	return ret;
53155682Smarkm
53290926Snectar    krb5_auth_con_setflags (context, auth_context,
53390926Snectar			    KRB5_AUTH_CONTEXT_DO_SEQUENCE);
53490926Snectar
53590926Snectar    ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle);
53655682Smarkm    if (ret)
53755682Smarkm	goto out;
53855682Smarkm
539102644Snectar    while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
54090926Snectar	struct addrinfo *ai, *a;
541142403Snectar	int is_stream;
54255682Smarkm
543142403Snectar	switch (hi->proto) {
544142403Snectar	case KRB5_KRBHST_UDP:
545142403Snectar	    if ((proc->flags & SUPPORT_UDP) == 0)
546142403Snectar		continue;
547142403Snectar	    is_stream = 0;
548142403Snectar	    break;
549142403Snectar	case KRB5_KRBHST_TCP:
550142403Snectar	    if ((proc->flags & SUPPORT_TCP) == 0)
551142403Snectar		continue;
552142403Snectar	    is_stream = 1;
553142403Snectar	    break;
554142403Snectar	default:
555142403Snectar	    continue;
556142403Snectar	}
557142403Snectar
55890926Snectar	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
55990926Snectar	if (ret)
56055682Smarkm	    continue;
56155682Smarkm
56290926Snectar	for (a = ai; !done && a != NULL; a = a->ai_next) {
56390926Snectar	    int replied = 0;
56455682Smarkm
565233294Sstas	    sock = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
566233294Sstas	    if (rk_IS_BAD_SOCKET(sock))
56790926Snectar		continue;
568233294Sstas	    rk_cloexec(sock);
56990926Snectar
57090926Snectar	    ret = connect(sock, a->ai_addr, a->ai_addrlen);
571233294Sstas	    if (rk_IS_SOCKET_ERROR(ret)) {
572233294Sstas		rk_closesocket (sock);
57390926Snectar		goto out;
57472445Sassar	    }
57590926Snectar
57690926Snectar	    ret = krb5_auth_con_genaddrs (context, auth_context, sock,
57790926Snectar					  KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
57890926Snectar	    if (ret) {
579233294Sstas		rk_closesocket (sock);
58055682Smarkm		goto out;
58172445Sassar	    }
58255682Smarkm
58390926Snectar	    for (i = 0; !done && i < 5; ++i) {
58490926Snectar		fd_set fdset;
58590926Snectar		struct timeval tv;
58655682Smarkm
58790926Snectar		if (!replied) {
58890926Snectar		    replied = 0;
589233294Sstas
590142403Snectar		    ret = (*proc->send_req) (context,
591142403Snectar					     &auth_context,
592142403Snectar					     creds,
593142403Snectar					     targprinc,
594142403Snectar					     is_stream,
595142403Snectar					     sock,
596142403Snectar					     newpw,
597142403Snectar					     hi->hostname);
59890926Snectar		    if (ret) {
599233294Sstas			rk_closesocket(sock);
60090926Snectar			goto out;
60190926Snectar		    }
60290926Snectar		}
603233294Sstas
604233294Sstas#ifndef NO_LIMIT_FD_SETSIZE
60590926Snectar		if (sock >= FD_SETSIZE) {
60690926Snectar		    ret = ERANGE;
607233294Sstas		    krb5_set_error_message(context, ret,
608233294Sstas					   "fd %d too large", sock);
609233294Sstas		    rk_closesocket (sock);
61090926Snectar		    goto out;
61190926Snectar		}
612233294Sstas#endif
61390926Snectar
61490926Snectar		FD_ZERO(&fdset);
61590926Snectar		FD_SET(sock, &fdset);
61690926Snectar		tv.tv_usec = 0;
61790926Snectar		tv.tv_sec  = 1 + (1 << i);
61890926Snectar
61990926Snectar		ret = select (sock + 1, &fdset, NULL, NULL, &tv);
620233294Sstas		if (rk_IS_SOCKET_ERROR(ret) && rk_SOCK_ERRNO != EINTR) {
621233294Sstas		    rk_closesocket(sock);
62290926Snectar		    goto out;
62390926Snectar		}
62490926Snectar		if (ret == 1) {
625142403Snectar		    ret = (*proc->process_rep) (context,
626142403Snectar						auth_context,
627142403Snectar						is_stream,
628142403Snectar						sock,
629142403Snectar						result_code,
630142403Snectar						result_code_string,
631142403Snectar						result_string,
632142403Snectar						hi->hostname);
63390926Snectar		    if (ret == 0)
63490926Snectar			done = 1;
63590926Snectar		    else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
63690926Snectar			replied = 1;
63790926Snectar		} else {
63890926Snectar		    ret = KRB5_KDC_UNREACH;
63990926Snectar		}
64072445Sassar	    }
641233294Sstas	    rk_closesocket (sock);
64255682Smarkm	}
64355682Smarkm    }
64455682Smarkm
64590926Snectar out:
64690926Snectar    krb5_krbhst_free (context, handle);
64755682Smarkm    krb5_auth_con_free (context, auth_context);
648233294Sstas
649233294Sstas    if (ret == KRB5_KDC_UNREACH) {
650233294Sstas	krb5_set_error_message(context,
651233294Sstas			       ret,
652233294Sstas			       N_("Unable to reach any changepw server "
653233294Sstas				 " in realm %s", "realm"), realm);
654233294Sstas	*result_code = KRB5_KPASSWD_HARDERROR;
65578527Sassar    }
656233294Sstas    return ret;
65755682Smarkm}
65890926Snectar
659233294Sstas#ifndef HEIMDAL_SMALLER
660142403Snectar
661233294Sstasstatic struct kpwd_proc *
662233294Sstasfind_chpw_proto(const char *name)
663233294Sstas{
664233294Sstas    struct kpwd_proc *p;
665233294Sstas    for (p = procs; p->name != NULL; p++) {
666233294Sstas	if (strcmp(p->name, name) == 0)
667233294Sstas	    return p;
668233294Sstas    }
669233294Sstas    return NULL;
670233294Sstas}
671233294Sstas
672233294Sstas/**
673233294Sstas * Deprecated: krb5_change_password() is deprecated, use krb5_set_password().
674233294Sstas *
675233294Sstas * @param context a Keberos context
676233294Sstas * @param creds
677233294Sstas * @param newpw
678233294Sstas * @param result_code
679233294Sstas * @param result_code_string
680233294Sstas * @param result_string
681233294Sstas *
682233294Sstas * @return On sucess password is changed.
683233294Sstas
684233294Sstas * @ingroup @krb5_deprecated
685142403Snectar */
686142403Snectar
687233294SstasKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
688142403Snectarkrb5_change_password (krb5_context	context,
689142403Snectar		      krb5_creds	*creds,
690178825Sdfr		      const char	*newpw,
691142403Snectar		      int		*result_code,
692142403Snectar		      krb5_data		*result_code_string,
693142403Snectar		      krb5_data		*result_string)
694233294Sstas    KRB5_DEPRECATED_FUNCTION("Use X instead")
695142403Snectar{
696142403Snectar    struct kpwd_proc *p = find_chpw_proto("change password");
697142403Snectar
698142403Snectar    *result_code = KRB5_KPASSWD_MALFORMED;
699142403Snectar    result_code_string->data = result_string->data = NULL;
700142403Snectar    result_code_string->length = result_string->length = 0;
701142403Snectar
702142403Snectar    if (p == NULL)
703142403Snectar	return KRB5_KPASSWD_MALFORMED;
704142403Snectar
705233294Sstas    return change_password_loop(context, creds, NULL, newpw,
706233294Sstas				result_code, result_code_string,
707142403Snectar				result_string, p);
708142403Snectar}
709233294Sstas#endif /* HEIMDAL_SMALLER */
710142403Snectar
711233294Sstas/**
712233294Sstas * Change password using creds.
713142403Snectar *
714233294Sstas * @param context a Keberos context
715233294Sstas * @param creds The initial kadmin/passwd for the principal or an admin principal
716233294Sstas * @param newpw The new password to set
717233294Sstas * @param targprinc if unset, the default principal is used.
718233294Sstas * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed.
719233294Sstas * @param result_code_string binary message from the server, contains
720233294Sstas * at least the result_code.
721233294Sstas * @param result_string A message from the kpasswd service or the
722233294Sstas * library in human printable form. The string is NUL terminated.
723233294Sstas *
724233294Sstas * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed.
725233294Sstas
726233294Sstas * @ingroup @krb5
727142403Snectar */
728142403Snectar
729233294SstasKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
730142403Snectarkrb5_set_password(krb5_context context,
731142403Snectar		  krb5_creds *creds,
732178825Sdfr		  const char *newpw,
733142403Snectar		  krb5_principal targprinc,
734142403Snectar		  int *result_code,
735142403Snectar		  krb5_data *result_code_string,
736142403Snectar		  krb5_data *result_string)
737142403Snectar{
738142403Snectar    krb5_principal principal = NULL;
739142403Snectar    krb5_error_code ret = 0;
740142403Snectar    int i;
741142403Snectar
742142403Snectar    *result_code = KRB5_KPASSWD_MALFORMED;
743233294Sstas    krb5_data_zero(result_code_string);
744233294Sstas    krb5_data_zero(result_string);
745142403Snectar
746142403Snectar    if (targprinc == NULL) {
747142403Snectar	ret = krb5_get_default_principal(context, &principal);
748142403Snectar	if (ret)
749142403Snectar	    return ret;
750142403Snectar    } else
751142403Snectar	principal = targprinc;
752142403Snectar
753142403Snectar    for (i = 0; procs[i].name != NULL; i++) {
754142403Snectar	*result_code = 0;
755233294Sstas	ret = change_password_loop(context, creds, principal, newpw,
756233294Sstas				   result_code, result_code_string,
757233294Sstas				   result_string,
758142403Snectar				   &procs[i]);
759142403Snectar	if (ret == 0 && *result_code == 0)
760142403Snectar	    break;
761142403Snectar    }
762142403Snectar
763142403Snectar    if (targprinc == NULL)
764142403Snectar	krb5_free_principal(context, principal);
765142403Snectar    return ret;
766142403Snectar}
767142403Snectar
768142403Snectar/*
769142403Snectar *
770142403Snectar */
771142403Snectar
772233294SstasKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
773142403Snectarkrb5_set_password_using_ccache(krb5_context context,
774142403Snectar			       krb5_ccache ccache,
775178825Sdfr			       const char *newpw,
776142403Snectar			       krb5_principal targprinc,
777142403Snectar			       int *result_code,
778142403Snectar			       krb5_data *result_code_string,
779142403Snectar			       krb5_data *result_string)
780142403Snectar{
781142403Snectar    krb5_creds creds, *credsp;
782142403Snectar    krb5_error_code ret;
783142403Snectar    krb5_principal principal = NULL;
784142403Snectar
785142403Snectar    *result_code = KRB5_KPASSWD_MALFORMED;
786142403Snectar    result_code_string->data = result_string->data = NULL;
787142403Snectar    result_code_string->length = result_string->length = 0;
788142403Snectar
789142403Snectar    memset(&creds, 0, sizeof(creds));
790142403Snectar
791142403Snectar    if (targprinc == NULL) {
792142403Snectar	ret = krb5_cc_get_principal(context, ccache, &principal);
793142403Snectar	if (ret)
794142403Snectar	    return ret;
795142403Snectar    } else
796142403Snectar	principal = targprinc;
797142403Snectar
798233294Sstas    ret = krb5_make_principal(context, &creds.server,
799142403Snectar			      krb5_principal_get_realm(context, principal),
800142403Snectar			      "kadmin", "changepw", NULL);
801142403Snectar    if (ret)
802142403Snectar	goto out;
803142403Snectar
804142403Snectar    ret = krb5_cc_get_principal(context, ccache, &creds.client);
805142403Snectar    if (ret) {
806142403Snectar        krb5_free_principal(context, creds.server);
807142403Snectar	goto out;
808142403Snectar    }
809142403Snectar
810142403Snectar    ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
811142403Snectar    krb5_free_principal(context, creds.server);
812142403Snectar    krb5_free_principal(context, creds.client);
813142403Snectar    if (ret)
814142403Snectar	goto out;
815142403Snectar
816142403Snectar    ret = krb5_set_password(context,
817142403Snectar			    credsp,
818142403Snectar			    newpw,
819142403Snectar			    principal,
820142403Snectar			    result_code,
821142403Snectar			    result_code_string,
822142403Snectar			    result_string);
823142403Snectar
824233294Sstas    krb5_free_creds(context, credsp);
825142403Snectar
826142403Snectar    return ret;
827142403Snectar out:
828142403Snectar    if (targprinc == NULL)
829142403Snectar	krb5_free_principal(context, principal);
830142403Snectar    return ret;
831142403Snectar}
832142403Snectar
833142403Snectar/*
834142403Snectar *
835142403Snectar */
836142403Snectar
837233294SstasKRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
83890926Snectarkrb5_passwd_result_to_string (krb5_context context,
83990926Snectar			      int result)
84090926Snectar{
84190926Snectar    static const char *strings[] = {
84290926Snectar	"Success",
84390926Snectar	"Malformed",
84490926Snectar	"Hard error",
84590926Snectar	"Auth error",
846142403Snectar	"Soft error" ,
847142403Snectar	"Access denied",
848142403Snectar	"Bad version",
849142403Snectar	"Initial flag needed"
85090926Snectar    };
85190926Snectar
852142403Snectar    if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)
85390926Snectar	return "unknown result code";
85490926Snectar    else
85590926Snectar	return strings[result];
85690926Snectar}
857