xref: /freebsd/crypto/heimdal/lib/krb5/changepw.c (revision 6fd05b64b5b65dd4ba9b86482a0634a5f0b96c29)
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 
36 RCSID("$Id: changepw.c,v 1.38 2002/09/29 11:48:34 joda Exp $");
37 
38 static krb5_error_code
39 send_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 | AP_OPTS_USE_SUBKEY,
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);
112 out2:
113     krb5_data_free (&ap_req_data);
114     return ret;
115 }
116 
117 static void
118 str2data (krb5_data *d,
119 	  const char *fmt,
120 	  ...) __attribute__ ((format (printf, 2, 3)));
121 
122 static void
123 str2data (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 
134 static krb5_error_code
135 process_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, priv_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     priv_data.data   = (u_char*)ap_rep_data.data + ap_rep_data.length;
177     priv_data.length = len - ap_rep_data.length - 6;
178     if ((u_char *)priv_data.data + priv_data.length > reply + len)
179 	return KRB5_KPASSWD_MALFORMED;
180 
181     if (ap_rep_data.length) {
182 	krb5_ap_rep_enc_part *ap_rep;
183 	u_char *p;
184 
185 	ret = krb5_rd_rep (context,
186 			   auth_context,
187 			   &ap_rep_data,
188 			   &ap_rep);
189 	if (ret)
190 	    return ret;
191 
192 	krb5_free_ap_rep_enc_part (context, ap_rep);
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 
246 krb5_error_code
247 krb5_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 
370 const char *
371 krb5_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