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