xref: /freebsd/crypto/heimdal/lib/krb5/changepw.c (revision 5e9cd1ae3e10592ed70e7575551cba1bbab04d84)
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 
36 RCSID("$Id: changepw.c,v 1.30 2000/12/10 23:10:10 assar Exp $");
37 
38 static krb5_error_code
39 get_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 
63 static krb5_error_code
64 send_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);
136 out2:
137     krb5_data_free (&ap_req_data);
138     return ret;
139 }
140 
141 static void
142 str2data (krb5_data *d,
143 	  const char *fmt,
144 	  ...) __attribute__ ((format (printf, 2, 3)));
145 
146 static void
147 str2data (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 
158 static krb5_error_code
159 process_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 
258 krb5_error_code
259 krb5_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 
342 out:
343     krb5_auth_con_free (context, auth_context);
344     if (done)
345 	return 0;
346     else
347 	return ret;
348 }
349