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