xref: /freebsd/crypto/heimdal/lib/krb5/changepw.c (revision 17d6c636720d00f77e5d098daf4c278f89d84f7b)
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 
36 RCSID("$Id: changepw.c,v 1.32 2001/05/14 22:49:55 assar Exp $");
37 
38 static krb5_error_code
39 get_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 
77 static krb5_error_code
78 send_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);
153 out2:
154     krb5_data_free (&ap_req_data);
155     return ret;
156 }
157 
158 static void
159 str2data (krb5_data *d,
160 	  const char *fmt,
161 	  ...) __attribute__ ((format (printf, 2, 3)));
162 
163 static void
164 str2data (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 
175 static krb5_error_code
176 process_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 
287 krb5_error_code
288 krb5_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 
375 out:
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