changepw.c revision 78527
1/*
2 * Copyright (c) 1997 - 2001 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.32 2001/05/14 22:49:55 assar Exp $");
37
38static krb5_error_code
39get_kdc_address (krb5_context context,
40		 krb5_realm realm,
41		 struct addrinfo **ai,
42		 char **ret_host)
43{
44    krb5_error_code ret;
45    char **hostlist;
46    int port = 0;
47    int error;
48    char *host;
49    int save_errno;
50
51    ret = krb5_get_krb_changepw_hst (context,
52				     &realm,
53				     &hostlist);
54    if (ret)
55	return ret;
56
57    host = strdup(*hostlist);
58    krb5_free_krbhst(context, hostlist);
59    if (host == NULL) {
60	krb5_set_error_string (context, "malloc: out of memory");
61	return ENOMEM;
62    }
63
64    port = ntohs(krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT));
65    error = roken_getaddrinfo_hostspec2(host, SOCK_DGRAM, port, ai);
66
67    if(error) {
68	save_errno = errno;
69	krb5_set_error_string(context, "resolving %s: %s",
70			      host, gai_strerror(error));
71	return krb5_eai_to_heim_errno(error, save_errno);
72    }
73    *ret_host = host;
74    return 0;
75}
76
77static krb5_error_code
78send_request (krb5_context context,
79	      krb5_auth_context *auth_context,
80	      krb5_creds *creds,
81	      int sock,
82	      struct sockaddr *sa,
83	      int sa_size,
84	      char *passwd,
85	      const char *host)
86{
87    krb5_error_code ret;
88    krb5_data ap_req_data;
89    krb5_data krb_priv_data;
90    krb5_data passwd_data;
91    size_t len;
92    u_char header[6];
93    u_char *p;
94    struct iovec iov[3];
95    struct msghdr msghdr;
96
97    krb5_data_zero (&ap_req_data);
98
99    ret = krb5_mk_req_extended (context,
100				auth_context,
101				AP_OPTS_MUTUAL_REQUIRED,
102				NULL, /* in_data */
103				creds,
104				&ap_req_data);
105    if (ret)
106	return ret;
107
108    passwd_data.data   = passwd;
109    passwd_data.length = strlen(passwd);
110
111    krb5_data_zero (&krb_priv_data);
112
113    ret = krb5_mk_priv (context,
114			*auth_context,
115			&passwd_data,
116			&krb_priv_data,
117			NULL);
118    if (ret)
119	goto out2;
120
121    len = 6 + ap_req_data.length + krb_priv_data.length;
122    p = header;
123    *p++ = (len >> 8) & 0xFF;
124    *p++ = (len >> 0) & 0xFF;
125    *p++ = 0;
126    *p++ = 1;
127    *p++ = (ap_req_data.length >> 8) & 0xFF;
128    *p++ = (ap_req_data.length >> 0) & 0xFF;
129
130    memset(&msghdr, 0, sizeof(msghdr));
131    msghdr.msg_name       = (void *)sa;
132    msghdr.msg_namelen    = sa_size;
133    msghdr.msg_iov        = iov;
134    msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
135#if 0
136    msghdr.msg_control    = NULL;
137    msghdr.msg_controllen = 0;
138#endif
139
140    iov[0].iov_base    = (void*)header;
141    iov[0].iov_len     = 6;
142    iov[1].iov_base    = ap_req_data.data;
143    iov[1].iov_len     = ap_req_data.length;
144    iov[2].iov_base    = krb_priv_data.data;
145    iov[2].iov_len     = krb_priv_data.length;
146
147    if (sendmsg (sock, &msghdr, 0) < 0) {
148	ret = errno;
149	krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
150    }
151
152    krb5_data_free (&krb_priv_data);
153out2:
154    krb5_data_free (&ap_req_data);
155    return ret;
156}
157
158static void
159str2data (krb5_data *d,
160	  const char *fmt,
161	  ...) __attribute__ ((format (printf, 2, 3)));
162
163static void
164str2data (krb5_data *d,
165	  const char *fmt,
166	  ...)
167{
168    va_list args;
169
170    va_start(args, fmt);
171    d->length = vasprintf ((char **)&d->data, fmt, args);
172    va_end(args);
173}
174
175static krb5_error_code
176process_reply (krb5_context context,
177	       krb5_auth_context auth_context,
178	       int sock,
179	       int *result_code,
180	       krb5_data *result_code_string,
181	       krb5_data *result_string,
182	       const char *host)
183{
184    krb5_error_code ret;
185    u_char reply[BUFSIZ];
186    size_t len;
187    u_int16_t pkt_len, pkt_ver;
188    krb5_data ap_rep_data;
189    int save_errno;
190
191    ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);
192    if (ret < 0) {
193	save_errno = errno;
194	krb5_set_error_string(context, "recvfrom %s: %s",
195			      host, strerror(save_errno));
196	return save_errno;
197    }
198
199    len = ret;
200    pkt_len = (reply[0] << 8) | (reply[1]);
201    pkt_ver = (reply[2] << 8) | (reply[3]);
202
203    if (pkt_len != len) {
204	str2data (result_string, "client: wrong len in reply");
205	*result_code = KRB5_KPASSWD_MALFORMED;
206	return 0;
207    }
208    if (pkt_ver != 0x0001) {
209	str2data (result_string,
210		  "client: wrong version number (%d)", pkt_ver);
211	*result_code = KRB5_KPASSWD_MALFORMED;
212	return 0;
213    }
214
215    ap_rep_data.data = reply + 6;
216    ap_rep_data.length  = (reply[4] << 8) | (reply[5]);
217
218    if (ap_rep_data.length) {
219	krb5_ap_rep_enc_part *ap_rep;
220	krb5_data priv_data;
221	u_char *p;
222
223	ret = krb5_rd_rep (context,
224			   auth_context,
225			   &ap_rep_data,
226			   &ap_rep);
227	if (ret)
228	    return ret;
229
230	krb5_free_ap_rep_enc_part (context, ap_rep);
231
232	priv_data.data   = (u_char*)ap_rep_data.data + ap_rep_data.length;
233	priv_data.length = len - ap_rep_data.length - 6;
234
235	ret = krb5_rd_priv (context,
236			    auth_context,
237			    &priv_data,
238			    result_code_string,
239			    NULL);
240	if (ret) {
241	    krb5_data_free (result_code_string);
242	    return ret;
243	}
244
245	if (result_code_string->length < 2) {
246	    *result_code = KRB5_KPASSWD_MALFORMED;
247	    str2data (result_string,
248		      "client: bad length in result");
249	    return 0;
250	}
251	p = result_code_string->data;
252
253	*result_code = (p[0] << 8) | p[1];
254	krb5_data_copy (result_string,
255			(unsigned char*)result_code_string->data + 2,
256			result_code_string->length - 2);
257	return 0;
258    } else {
259	KRB_ERROR error;
260	size_t size;
261	u_char *p;
262
263	ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
264	if (ret) {
265	    return ret;
266	}
267	if (error.e_data->length < 2) {
268	    krb5_warnx (context, "too short e_data to print anything usable");
269	    return 1;		/* XXX */
270	}
271
272	p = error.e_data->data;
273	*result_code = (p[0] << 8) | p[1];
274	krb5_data_copy (result_string,
275			p + 2,
276			error.e_data->length - 2);
277	return 0;
278    }
279}
280
281/*
282 * change the password using the credentials in `creds' (for the
283 * principal indicated in them) to `newpw', storing the result of
284 * the operation in `result_*' and an error code or 0.
285 */
286
287krb5_error_code
288krb5_change_password (krb5_context	context,
289		      krb5_creds	*creds,
290		      char		*newpw,
291		      int		*result_code,
292		      krb5_data		*result_code_string,
293		      krb5_data		*result_string)
294{
295    krb5_error_code ret;
296    krb5_auth_context auth_context = NULL;
297    int sock;
298    int i;
299    struct addrinfo *ai, *a;
300    int done = 0;
301    char *host = NULL;
302
303    ret = krb5_auth_con_init (context, &auth_context);
304    if (ret)
305	return ret;
306
307    ret = get_kdc_address (context, creds->client->realm, &ai, &host);
308    if (ret)
309	goto out;
310
311    for (a = ai; !done && a != NULL; a = a->ai_next) {
312	int replied = 0;
313
314	sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
315	if (sock < 0)
316	    continue;
317
318	for (i = 0; !done && i < 5; ++i) {
319	    fd_set fdset;
320	    struct timeval tv;
321
322	    if (!replied) {
323		replied = 0;
324		ret = send_request (context,
325				    &auth_context,
326				    creds,
327				    sock,
328				    a->ai_addr,
329				    a->ai_addrlen,
330				    newpw,
331				    host);
332		if (ret) {
333		    close(sock);
334		    goto out;
335		}
336	    }
337
338	    if (sock >= FD_SETSIZE) {
339		krb5_set_error_string(context, "fd %d too large", sock);
340		ret = ERANGE;
341		close (sock);
342		goto out;
343	    }
344
345	    FD_ZERO(&fdset);
346	    FD_SET(sock, &fdset);
347	    tv.tv_usec = 0;
348	    tv.tv_sec  = 1 + (1 << i);
349
350	    ret = select (sock + 1, &fdset, NULL, NULL, &tv);
351	    if (ret < 0 && errno != EINTR) {
352		close(sock);
353		goto out;
354	    }
355	    if (ret == 1) {
356		ret = process_reply (context,
357				     auth_context,
358				     sock,
359				     result_code,
360				     result_code_string,
361				     result_string,
362				     host);
363		if (ret == 0)
364		    done = 1;
365		else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
366		    replied = 1;
367	    } else {
368		ret = KRB5_KDC_UNREACH;
369	    }
370	}
371	close (sock);
372    }
373    freeaddrinfo (ai);
374
375out:
376    krb5_auth_con_free (context, auth_context);
377    free (host);
378    if (done)
379	return 0;
380    else {
381	if (ret == KRB5_KDC_UNREACH)
382	    krb5_set_error_string(context,
383				  "failed to reach kpasswd server %s "
384				  "in realm %s",
385				  host, creds->client->realm);
386
387	return ret;
388    }
389}
390