1 #pragma ident	"%Z%%M%	%I%	%E% SMI"
2 
3 /*
4  * lib/krb5/os/changepw.c
5  *
6  * Copyright 1990,1999,2001 by the Massachusetts Institute of Technology.
7  * All Rights Reserved.
8  *
9  * Export of this software from the United States of America may
10  *   require a specific license from the United States Government.
11  *   It is the responsibility of any person or organization contemplating
12  *   export to obtain such a license before exporting.
13  *
14  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
15  * distribute this software and its documentation for any purpose and
16  * without fee is hereby granted, provided that the above copyright
17  * notice appear in all copies and that both that copyright notice and
18  * this permission notice appear in supporting documentation, and that
19  * the name of M.I.T. not be used in advertising or publicity pertaining
20  * to distribution of the software without specific, written prior
21  * permission.  Furthermore if you modify this software you must label
22  * your software as modified software and not distribute it in such a
23  * fashion that it might be confused with the original M.I.T. software.
24  * M.I.T. makes no representations about the suitability of
25  * this software for any purpose.  It is provided "as is" without express
26  * or implied warranty.
27  *
28  */
29 /*
30  * krb5_set_password - Implements set password per RFC 3244
31  * Added by Paul W. Nelson, Thursby Software Systems, Inc.
32  */
33 
34 #define NEED_SOCKETS
35 #include "fake-addrinfo.h"
36 #include "k5-int.h"
37 #include "os-proto.h"
38 
39 #include <stdio.h>
40 #include <errno.h>
41 
42 #ifndef GETSOCKNAME_ARG3_TYPE
43 #define GETSOCKNAME_ARG3_TYPE int
44 #endif
45 
46 /*
47  * Wrapper function for the two backends
48  */
49 
50 static krb5_error_code
51 krb5_locate_kpasswd(krb5_context context, const krb5_data *realm,
52 		    struct addrlist *addrlist)
53 {
54     krb5_error_code code;
55 
56     code = krb5int_locate_server (context, realm, addrlist, 0,
57 				  "kpasswd_server", "_kpasswd", 0,
58 				  htons(DEFAULT_KPASSWD_PORT), 0, 0);
59     if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) {
60 	code = krb5int_locate_server (context, realm, addrlist, 0,
61 				      "admin_server", "_kerberos-adm", 1,
62 				      DEFAULT_KPASSWD_PORT, 0, 0);
63 	if (!code) {
64 	    /* Success with admin_server but now we need to change the
65 	       port number to use DEFAULT_KPASSWD_PORT.  */
66 	    int i;
67 	    for ( i=0;i<addrlist->naddrs;i++ ) {
68 		struct addrinfo *a = addrlist->addrs[i];
69 		if (a->ai_family == AF_INET)
70 		    sa2sin (a->ai_addr)->sin_port = htons(DEFAULT_KPASSWD_PORT);
71 	    }
72 	}
73     }
74     return (code);
75 }
76 
77 
78 /*
79 ** The logic for setting and changing a password is mostly the same
80 ** krb5_change_set_password handles both cases
81 **	if set_password_for is NULL, then a password change is performed,
82 **  otherwise, the password is set for the principal indicated in set_password_for
83 */
84 krb5_error_code KRB5_CALLCONV
85 krb5_change_set_password(
86 	krb5_context context, krb5_creds *creds, char *newpw, krb5_principal set_password_for,
87 	int *result_code, krb5_data *result_code_string, krb5_data *result_string)
88 {
89     krb5_auth_context auth_context;
90     krb5_data ap_req, chpw_req, chpw_rep;
91     krb5_address local_kaddr, remote_kaddr;
92     char *code_string;
93     krb5_error_code code = 0;
94     int i;
95     GETSOCKNAME_ARG3_TYPE addrlen;
96     struct sockaddr_storage local_addr, remote_addr, tmp_addr;
97     int cc, local_result_code;
98     /* platforms seem to be consistant and use the same types */
99     GETSOCKNAME_ARG3_TYPE tmp_len;
100     SOCKET s1 = INVALID_SOCKET, s2 = INVALID_SOCKET;
101     int tried_one = 0;
102     struct addrlist al = ADDRLIST_INIT;
103 
104 
105     /* Initialize values so that cleanup call can safely check for NULL */
106     auth_context = NULL;
107     memset(&chpw_req, 0, sizeof(krb5_data));
108     memset(&chpw_rep, 0, sizeof(krb5_data));
109     memset(&ap_req, 0, sizeof(krb5_data));
110 
111     /* initialize auth_context so that we know we have to free it */
112     if ((code = krb5_auth_con_init(context, &auth_context)))
113 	  goto cleanup;
114 
115     if ((code = krb5_mk_req_extended(context, &auth_context,
116 				     AP_OPTS_USE_SUBKEY,
117 				     NULL, creds, &ap_req)))
118 	  goto cleanup;
119 
120     if ((code = krb5_locate_kpasswd(context,
121                                     krb5_princ_realm(context, creds->server),
122 				    &al)))
123         goto cleanup;
124 
125     /* this is really obscure.  s1 is used for all communications.  it
126        is left unconnected in case the server is multihomed and routes
127        are asymmetric.  s2 is connected to resolve routes and get
128        addresses.  this is the *only* way to get proper addresses for
129        multihomed hosts if routing is asymmetric.
130 
131        A related problem in the server, but not the client, is that
132        many os's have no way to disconnect a connected udp socket, so
133        the s2 socket needs to be closed and recreated for each
134        request.  The s1 socket must not be closed, or else queued
135        requests will be lost.
136 
137        A "naive" client implementation (one socket, no connect,
138        hostname resolution to get the local ip addr) will work and
139        interoperate if the client is single-homed. */
140 
141     if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
142 	code = SOCKET_ERRNO;
143 	goto cleanup;
144     }
145 
146     if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
147 	code = SOCKET_ERRNO;
148 	goto cleanup;
149     }
150 
151     /*
152      * This really should try fallback addresses in cases of timeouts.
153      * For now, where the MIT KDC implementation only supports one
154      * kpasswd server machine anyways, we'll only try the first IPv4
155      * address we can connect() to.  This isn't right for multi-homed
156      * servers; oh well.
157      */
158     for (i=0; i<al.naddrs; i++) {
159 	fd_set fdset;
160 	struct timeval timeout;
161 
162 	/* XXX Now the locate_ functions can return IPv6 addresses.  */
163 	if (al.addrs[i]->ai_family != AF_INET)
164 	    continue;
165 
166 	tried_one = 1;
167 	if (connect(s2, al.addrs[i]->ai_addr, al.addrs[i]->ai_addrlen) == SOCKET_ERROR) {
168 	    if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH)
169 		continue; /* try the next addr */
170 
171 	    code = SOCKET_ERRNO;
172 	    goto cleanup;
173 	}
174 
175         addrlen = sizeof(local_addr);
176 
177 	if (getsockname(s2, ss2sa(&local_addr), &addrlen) < 0) {
178 	    if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH)
179 		continue; /* try the next addr */
180 
181 	    code = SOCKET_ERRNO;
182 	    goto cleanup;
183 	}
184 
185 	/* some brain-dead OS's don't return useful information from
186 	 * the getsockname call.  Namely, windows and solaris.  */
187 
188 	if (ss2sin(&local_addr)->sin_addr.s_addr != 0) {
189 	    local_kaddr.addrtype = ADDRTYPE_INET;
190 	    local_kaddr.length = sizeof(ss2sin(&local_addr)->sin_addr);
191 	    local_kaddr.contents = (krb5_octet *) &ss2sin(&local_addr)->sin_addr;
192 	} else {
193 	    krb5_address **addrs;
194 
195 	    krb5_os_localaddr(context, &addrs);
196 
197 	    local_kaddr.magic = addrs[0]->magic;
198 	    local_kaddr.addrtype = addrs[0]->addrtype;
199 	    local_kaddr.length = addrs[0]->length;
200 	    local_kaddr.contents = malloc(addrs[0]->length);
201 	    memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length);
202 
203 	    krb5_free_addresses(context, addrs);
204 	}
205 
206 	addrlen = sizeof(remote_addr);
207 	if (getpeername(s2, ss2sa(&remote_addr), &addrlen) < 0) {
208 	    if (SOCKET_ERRNO == ECONNREFUSED || SOCKET_ERRNO == EHOSTUNREACH)
209 		continue; /* try the next addr */
210 
211 	    code = SOCKET_ERRNO;
212 	    goto cleanup;
213 	}
214 
215 	remote_kaddr.addrtype = ADDRTYPE_INET;
216 	remote_kaddr.length = sizeof(ss2sin(&remote_addr)->sin_addr);
217 	remote_kaddr.contents = (krb5_octet *) &ss2sin(&remote_addr)->sin_addr;
218 
219 	/* mk_priv requires that the local address be set.
220 	   getsockname is used for this.  rd_priv requires that the
221 	   remote address be set.  recvfrom is used for this.  If
222 	   rd_priv is given a local address, and the message has the
223 	   recipient addr in it, this will be checked.  However, there
224 	   is simply no way to know ahead of time what address the
225 	   message will be delivered *to*.  Therefore, it is important
226 	   that either no recipient address is in the messages when
227 	   mk_priv is called, or that no local address is passed to
228 	   rd_priv.  Both is a better idea, and I have done that.  In
229 	   summary, when mk_priv is called, *only* a local address is
230 	   specified.  when rd_priv is called, *only* a remote address
231 	   is specified.  Are we having fun yet?  */
232 
233 	if ((code = krb5_auth_con_setaddrs(context, auth_context,
234 					   &local_kaddr, NULL))) {
235 	  goto cleanup;
236 	}
237 
238 	if( set_password_for )
239 		code = krb5int_mk_setpw_req(context, auth_context, &ap_req, set_password_for, newpw, &chpw_req);
240 	else
241 		code = krb5int_mk_chpw_req(context, auth_context, &ap_req, newpw, &chpw_req);
242 	if (code)
243 	{
244 	    goto cleanup;
245 	}
246 
247 	if ((cc = sendto(s1, chpw_req.data,
248 			 (GETSOCKNAME_ARG3_TYPE) chpw_req.length, 0,
249 			 al.addrs[i]->ai_addr, al.addrs[i]->ai_addrlen)) != chpw_req.length)
250 	{
251 	    if ((cc < 0) && ((SOCKET_ERRNO == ECONNREFUSED) ||
252 			     (SOCKET_ERRNO == EHOSTUNREACH)))
253 		continue; /* try the next addr */
254 
255 	    code = (cc < 0) ? SOCKET_ERRNO : ECONNABORTED;
256 	    goto cleanup;
257 	}
258 
259 	chpw_rep.length = 1500;
260 	chpw_rep.data = (char *) malloc(chpw_rep.length);
261 
262 	/* XXX need a timeout/retry loop here */
263 	FD_ZERO (&fdset);
264 	FD_SET (s1, &fdset);
265 	timeout.tv_sec = 120;
266 	timeout.tv_usec = 0;
267 	switch (select (s1 + 1, &fdset, 0, 0, &timeout)) {
268 	case -1:
269 	    code = SOCKET_ERRNO;
270 	    goto cleanup;
271 	case 0:
272 	    code = ETIMEDOUT;
273 	    goto cleanup;
274 	default:
275 	    /* fall through */
276 	    ;
277 	}
278 
279 	/* "recv" would be good enough here... except that Windows/NT
280 	   commits the atrocity of returning -1 to indicate failure,
281 	   but leaving errno set to 0.
282 
283 	   "recvfrom(...,NULL,NULL)" would seem to be a good enough
284 	   alternative, and it works on NT, but it doesn't work on
285 	   SunOS 4.1.4 or Irix 5.3.  Thus we must actually accept the
286 	   value and discard it. */
287 	tmp_len = sizeof(tmp_addr);
288 	if ((cc = recvfrom(s1, chpw_rep.data,
289 			   (GETSOCKNAME_ARG3_TYPE) chpw_rep.length,
290 			   0, ss2sa(&tmp_addr), &tmp_len)) < 0)
291 	{
292 	    code = SOCKET_ERRNO;
293 	    goto cleanup;
294 	}
295 
296 	closesocket(s1);
297 	s1 = INVALID_SOCKET;
298 	closesocket(s2);
299 	s2 = INVALID_SOCKET;
300 
301 	chpw_rep.length = cc;
302 
303 	if ((code = krb5_auth_con_setaddrs(context, auth_context,
304 					   NULL, &remote_kaddr)))
305 	    goto cleanup;
306 
307 	if( set_password_for )
308 		code = krb5int_rd_setpw_rep(context, auth_context, &chpw_rep, &local_result_code, result_string);
309 	else
310 		code = krb5int_rd_chpw_rep(context, auth_context, &chpw_rep, &local_result_code, result_string);
311 	if (code)
312 		goto cleanup;
313 
314 	if (result_code)
315 	    *result_code = local_result_code;
316 
317 	if (result_code_string) {
318 		if( set_password_for )
319 	    	code = krb5int_setpw_result_code_string(context, local_result_code, (const char **)&code_string);
320 		else
321 	    	code = krb5_chpw_result_code_string(context, local_result_code, &code_string);
322 		if(code)
323 			goto cleanup;
324 
325 	    result_code_string->length = strlen(code_string);
326 	    result_code_string->data = malloc(result_code_string->length);
327 	    if (result_code_string->data == NULL) {
328 		code = ENOMEM;
329 		goto cleanup;
330 	    }
331 	    strncpy(result_code_string->data, code_string, result_code_string->length);
332 	}
333 
334 	code = 0;
335 	goto cleanup;
336     }
337 
338     if (tried_one)
339 	/* Got some non-fatal errors, but didn't get any successes.  */
340 	code = SOCKET_ERRNO;
341     else
342 	/* Had some addresses, but didn't try any because they weren't
343 	   AF_INET addresses and we don't support AF_INET6 addresses
344 	   here yet.  */
345 	code = EHOSTUNREACH;
346 
347 cleanup:
348     if (auth_context != NULL)
349 	krb5_auth_con_free(context, auth_context);
350 
351     krb5int_free_addrlist (&al);
352 
353     if (s1 != INVALID_SOCKET)
354 	closesocket(s1);
355 
356     if (s2 != INVALID_SOCKET)
357 	closesocket(s2);
358 
359     krb5_free_data_contents(context, &chpw_req);
360     krb5_free_data_contents(context, &chpw_rep);
361     krb5_free_data_contents(context, &ap_req);
362 
363     return(code);
364 }
365 
366 krb5_error_code KRB5_CALLCONV
367 krb5_change_password(krb5_context context, krb5_creds *creds, char *newpw, int *result_code, krb5_data *result_code_string, krb5_data *result_string)
368 {
369 	return krb5_change_set_password(
370 		context, creds, newpw, NULL, result_code, result_code_string, result_string );
371 }
372 
373 /*
374  * krb5_set_password - Implements set password per RFC 3244
375  *
376  */
377 
378 krb5_error_code KRB5_CALLCONV
379 krb5_set_password(
380 	krb5_context context,
381 	krb5_creds *creds,
382 	char *newpw,
383 	krb5_principal change_password_for,
384 	int *result_code, krb5_data *result_code_string, krb5_data *result_string
385 	)
386 {
387 	return krb5_change_set_password(
388 		context, creds, newpw, change_password_for, result_code, result_code_string, result_string );
389 }
390 
391 krb5_error_code KRB5_CALLCONV
392 krb5_set_password_using_ccache(
393 	krb5_context context,
394 	krb5_ccache ccache,
395 	char *newpw,
396 	krb5_principal change_password_for,
397 	int *result_code, krb5_data *result_code_string, krb5_data *result_string
398 	)
399 {
400 	krb5_creds		creds;
401 	krb5_creds		*credsp;
402 	krb5_error_code	code;
403 
404 /*
405 ** get the proper creds for use with krb5_set_password -
406 */
407 	memset( &creds, 0, sizeof(creds) );
408 /*
409 ** first get the principal for the password service -
410 */
411 	code = krb5_cc_get_principal( context, ccache, &creds.client );
412 	if( !code )
413 	{
414 		code = krb5_build_principal( context, &creds.server,
415 				krb5_princ_realm(context, change_password_for)->length,
416 				krb5_princ_realm(context, change_password_for)->data,
417 				"kadmin", "changepw", NULL );
418 		if(!code)
419 		{
420 			code = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
421 			if( ! code )
422 			{
423 				code = krb5_set_password(context, credsp, newpw, change_password_for,
424 					result_code, result_code_string,
425 					result_string);
426 				krb5_free_creds(context, credsp);
427 			}
428 		}
429 		krb5_free_cred_contents(context, &creds);
430 	}
431 	return code;
432 }
433