xref: /illumos-gate/usr/src/lib/krb5/kadm5/clnt/changepw.c (revision 505d05c73a6e56769f263d4803b22eddd168ee24)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * lib/krb5/os/changepw.c
10  *
11  * Copyright 1990,1999 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  Furthermore if you modify this software you must label
27  * your software as modified software and not distribute it in such a
28  * fashion that it might be confused with the original M.I.T. software.
29  * M.I.T. makes no representations about the suitability of
30  * this software for any purpose.  It is provided "as is" without express
31  * or implied warranty.
32  *
33  */
34 
35 #define	NEED_SOCKETS
36 #include <krb5.h>
37 #include <k5-int.h>
38 #include <kadm5/admin.h>
39 #include <client_internal.h>
40 #include <gssapi/gssapi.h>
41 #include <gssapi_krb5.h>
42 #include <gssapiP_krb5.h>
43 
44 /* #include "adm_err.h" */
45 #include <stdio.h>
46 #include <errno.h>
47 
48 extern krb5_error_code krb5_mk_chpw_req(krb5_context  context,
49 					krb5_auth_context auth_context,
50 					krb5_data *ap_req, char *passwd,
51 					krb5_data *packet);
52 
53 extern krb5_error_code krb5_rd_chpw_rep(krb5_context context,
54 					krb5_auth_context auth_context,
55 					krb5_data *packet, int *result_code,
56 					krb5_data *result_data);
57 
58 /*
59  * _kadm5_get_kpasswd_protocol
60  *
61  * returns the password change protocol value to the caller.
62  * Since the 'handle' is an opaque value to higher up callers,
63  * this method is needed to provide a way for them to get a peek
64  * at the protocol being used without having to expose the entire
65  * handle structure.
66  */
67 krb5_chgpwd_prot
68 _kadm5_get_kpasswd_protocol(void *handle)
69 {
70 	kadm5_server_handle_t srvrhdl = (kadm5_server_handle_t)handle;
71 
72 	return (srvrhdl->params.kpasswd_protocol);
73 }
74 
75 /*
76  * krb5_change_password
77  *
78  * Prepare and send a CHANGEPW request to a password server
79  * using UDP datagrams.  This is only used for sending to
80  * non-SEAM servers which support the Marc Horowitz defined
81  * protocol (1998) for password changing.
82  *
83  * SUNW14resync - added _local as it conflicts with one in krb5.h
84  */
85 static krb5_error_code
86 krb5_change_password_local(context, params, creds, newpw, srvr_rsp_code,
87 		    srvr_msg)
88 krb5_context context;
89 kadm5_config_params *params;
90 krb5_creds *creds;
91 char *newpw;
92 kadm5_ret_t *srvr_rsp_code;
93 krb5_data *srvr_msg;
94 {
95 	krb5_auth_context auth_context;
96 	krb5_data ap_req, chpw_req, chpw_rep;
97 	krb5_address local_kaddr, remote_kaddr;
98 	krb5_error_code code = 0;
99 	int i, addrlen;
100 	struct sockaddr *addr_p, local_addr, remote_addr, tmp_addr;
101 	struct sockaddr_in *sin_p;
102 	struct hostent *hp;
103 	int naddr_p;
104 	int cc, local_result_code, tmp_len;
105 	SOCKET s1 = INVALID_SOCKET;
106 	SOCKET s2 = INVALID_SOCKET;
107 
108 
109 	/* Initialize values so that cleanup call can safely check for NULL */
110 	auth_context = NULL;
111 	addr_p = NULL;
112 	memset(&chpw_req, 0, sizeof (krb5_data));
113 	memset(&chpw_rep, 0, sizeof (krb5_data));
114 	memset(&ap_req, 0, sizeof (krb5_data));
115 
116 	/* initialize auth_context so that we know we have to free it */
117 	if ((code = krb5_auth_con_init(context, &auth_context)))
118 		goto cleanup;
119 
120 	if (code = krb5_mk_req_extended(context, &auth_context,
121 					AP_OPTS_USE_SUBKEY,
122 					NULL, creds, &ap_req))
123 		goto cleanup;
124 
125 	/*
126 	 * find the address of the kpasswd_server.
127 	 */
128 	addr_p = (struct sockaddr *)malloc(sizeof (struct sockaddr));
129 	if (!addr_p)
130 		goto cleanup;
131 	memset(addr_p, 0, sizeof (struct sockaddr));
132 	if ((hp = gethostbyname(params->kpasswd_server)) == NULL) {
133 		code = KRB5_REALM_CANT_RESOLVE;
134 		goto cleanup;
135 	}
136 	sin_p = (struct sockaddr_in *)addr_p;
137 	memset((char *)sin_p, 0, sizeof (struct sockaddr));
138 	sin_p->sin_family = hp->h_addrtype;
139 	sin_p->sin_port = htons(params->kpasswd_port);
140 	memcpy((char *)&sin_p->sin_addr, (char *)hp->h_addr, hp->h_length);
141 	naddr_p = 1;
142 
143 
144 	/*
145 	 * this is really obscure.  s1 is used for all communications.  it
146 	 * is left unconnected in case the server is multihomed and routes
147 	 * are asymmetric.  s2 is connected to resolve routes and get
148 	 * addresses.  this is the *only* way to get proper addresses for
149 	 * multihomed hosts if routing is asymmetric.
150 	 *
151 	 * A related problem in the server, but not the client, is that
152 	 * many os's have no way to disconnect a connected udp socket, so
153 	 * the s2 socket needs to be closed and recreated for each
154 	 * request.  The s1 socket must not be closed, or else queued
155 	 * requests will be lost.
156 	 *
157 	 * A "naive" client implementation (one socket, no connect,
158 	 * hostname resolution to get the local ip addr) will work and
159 	 * interoperate if the client is single-homed.
160 	 */
161 
162 	if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
163 	{
164 		code = errno;
165 		goto cleanup;
166 	}
167 
168 	if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
169 	{
170 		code = errno;
171 		goto cleanup;
172 	}
173 
174 	for (i = 0; i < naddr_p; i++)
175 	{
176 		fd_set fdset;
177 		struct timeval timeout;
178 
179 		if (connect(s2, &addr_p[i], sizeof (addr_p[i])) ==
180 		    SOCKET_ERROR)
181 		{
182 			if ((errno == ECONNREFUSED) ||
183 			    (errno == EHOSTUNREACH))
184 				continue; /* try the next addr */
185 
186 			code = errno;
187 			goto cleanup;
188 		}
189 
190 		addrlen = sizeof (local_addr);
191 
192 		if (getsockname(s2, &local_addr, &addrlen) < 0)
193 		{
194 			if ((errno == ECONNREFUSED) ||
195 			    (errno == EHOSTUNREACH))
196 				continue; /* try the next addr */
197 
198 			code = errno;
199 			goto cleanup;
200 		}
201 
202 		/*
203 		 * some brain-dead OS's don't return useful information from
204 		 * the getsockname call.  Namely, windows and solaris.
205 		 */
206 		if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0)
207 		{
208 			local_kaddr.addrtype = ADDRTYPE_INET;
209 			local_kaddr.length = sizeof (((struct sockaddr_in *)
210 						    &local_addr)->sin_addr);
211 			local_kaddr.contents = (krb5_octet *)
212 				&(((struct sockaddr_in *)
213 				&local_addr)->sin_addr);
214 		}
215 		else
216 		{
217 			krb5_address **addrs;
218 
219 			krb5_os_localaddr(context, &addrs);
220 
221 			local_kaddr.magic = addrs[0]->magic;
222 			local_kaddr.addrtype = addrs[0]->addrtype;
223 			local_kaddr.length = addrs[0]->length;
224 			local_kaddr.contents = malloc(addrs[0]->length);
225 			memcpy(local_kaddr.contents, addrs[0]->contents,
226 			    addrs[0]->length);
227 
228 			krb5_free_addresses(context, addrs);
229 		}
230 
231 		addrlen = sizeof (remote_addr);
232 		if (getpeername(s2, &remote_addr, &addrlen) < 0)
233 		{
234 			if ((errno == ECONNREFUSED) ||
235 			    (errno == EHOSTUNREACH))
236 				continue; /* try the next addr */
237 
238 			code = errno;
239 			goto cleanup;
240 		}
241 
242 		remote_kaddr.addrtype = ADDRTYPE_INET;
243 		remote_kaddr.length = sizeof (((struct sockaddr_in *)
244 					    &remote_addr)->sin_addr);
245 		remote_kaddr.contents = (krb5_octet *)
246 			&(((struct sockaddr_in *)&remote_addr)->sin_addr);
247 
248 		/*
249 		 * mk_priv requires that the local address be set.
250 		 * getsockname is used for this.  rd_priv requires that the
251 		 * remote address be set.  recvfrom is used for this.  If
252 		 * rd_priv is given a local address, and the message has the
253 		 * recipient addr in it, this will be checked.  However, there
254 		 * is simply no way to know ahead of time what address the
255 		 * message will be delivered *to*.  Therefore, it is important
256 		 * that either no recipient address is in the messages when
257 		 * mk_priv is called, or that no local address is passed to
258 		 * rd_priv.  Both is a better idea, and I have done that.  In
259 		 * summary, when mk_priv is called, *only* a local address is
260 		 * specified.  when rd_priv is called, *only* a remote address
261 		 * is specified.  Are we having fun yet?
262 		 */
263 
264 		if (code = krb5_auth_con_setaddrs(context, auth_context,
265 						&local_kaddr, NULL))
266 		{
267 			code = errno;
268 			goto cleanup;
269 		}
270 
271 		if (code = krb5_mk_chpw_req(context, auth_context,
272 					    &ap_req, newpw, &chpw_req))
273 		{
274 			code = errno;
275 			goto cleanup;
276 		}
277 
278 		if ((cc = sendto(s1, chpw_req.data, chpw_req.length, 0,
279 		    (struct sockaddr *)&addr_p[i],
280 		    sizeof (addr_p[i]))) != chpw_req.length)
281 		{
282 			if ((cc < 0) && ((errno == ECONNREFUSED) ||
283 					(errno == EHOSTUNREACH)))
284 				continue; /* try the next addr */
285 
286 			code = (cc < 0) ? errno : ECONNABORTED;
287 			goto cleanup;
288 		}
289 
290 		chpw_rep.length = 1500;
291 		chpw_rep.data = (char *)malloc(chpw_rep.length);
292 
293 		/* XXX need a timeout/retry loop here */
294 		FD_ZERO(&fdset);
295 		FD_SET(s1, &fdset);
296 		timeout.tv_sec = 120;
297 		timeout.tv_usec = 0;
298 		switch (select(s1 + 1, &fdset, 0, 0, &timeout)) {
299 		case -1:
300 			code = errno;
301 			goto cleanup;
302 		case 0:
303 			code = ETIMEDOUT;
304 			goto cleanup;
305 		default:
306 			/* fall through */
307 			;
308 		}
309 
310 		tmp_len = sizeof (tmp_addr);
311 		if ((cc = recvfrom(s1, chpw_rep.data, chpw_rep.length,
312 				0, &tmp_addr, &tmp_len)) < 0)
313 		{
314 			code = errno;
315 			goto cleanup;
316 		}
317 
318 		closesocket(s1);
319 		s1 = INVALID_SOCKET;
320 		closesocket(s2);
321 		s2 = INVALID_SOCKET;
322 
323 		chpw_rep.length = cc;
324 
325 		if (code = krb5_auth_con_setaddrs(context, auth_context,
326 						NULL, &remote_kaddr))
327 			goto cleanup;
328 
329 		if (code = krb5_rd_chpw_rep(context, auth_context, &chpw_rep,
330 					&local_result_code, srvr_msg))
331 			goto cleanup;
332 
333 		if (srvr_rsp_code)
334 			*srvr_rsp_code = local_result_code;
335 
336 		code = 0;
337 		goto cleanup;
338 	}
339 
340 	code = errno;
341 
342 cleanup:
343 	if (auth_context != NULL)
344 		krb5_auth_con_free(context, auth_context);
345 
346 	if (addr_p != NULL)
347 		krb5_xfree(addr_p);
348 
349 	if (s1 != INVALID_SOCKET)
350 		closesocket(s1);
351 
352 	if (s2 != INVALID_SOCKET)
353 		closesocket(s2);
354 
355 	krb5_xfree(chpw_req.data);
356 	krb5_xfree(chpw_rep.data);
357 	krb5_xfree(ap_req.data);
358 
359 	return (code);
360 }
361 
362 
363 /*
364  * kadm5_chpass_principal_v2
365  *
366  * New function used to prepare to make the change password request to a
367  * non-SEAM admin server.  The protocol used in this case is not based on
368  * RPCSEC_GSS, it simply makes the request to port 464 (udp and tcp).
369  * This is the same way that MIT KRB5 1.2.1 changes passwords.
370  */
371 kadm5_ret_t
372 kadm5_chpass_principal_v2(void *server_handle,
373 			krb5_principal princ,
374 			char *newpw,
375 			kadm5_ret_t *srvr_rsp_code,
376 			krb5_data *srvr_msg)
377 {
378 	kadm5_ret_t code;
379 	kadm5_server_handle_t handle  = (kadm5_server_handle_t)server_handle;
380 	krb5_error_code result;
381 	krb5_creds mcreds;
382 	krb5_creds ncreds;
383 	krb5_ccache ccache;
384 	int cpwlen;
385 	char *cpw_service = NULL;
386 
387 	/*
388 	 * The credentials have already been stored in the cache in the
389 	 * initialization step earlier, but we dont have direct access to it
390 	 * at this level. Derive the cache and fetch the credentials to use for
391 	 * sending the request.
392 	 */
393 	memset(&mcreds, 0, sizeof (krb5_creds));
394 	if ((code = krb5_cc_resolve(handle->context, handle->cache_name,
395 				    &ccache)))
396 		return (code);
397 
398 	/* set the client principal in the credential match structure */
399 	mcreds.client = princ;
400 
401 	/*
402 	 * set the server principal (kadmin/changepw@REALM) in the credential
403 	 * match struct
404 	 */
405 	cpwlen = strlen(KADM5_CHANGEPW_SERVICE) +
406 		strlen(handle->params.realm) + 2;
407 	cpw_service = malloc(cpwlen);
408 	if (cpw_service == NULL) {
409 		return (ENOMEM);
410 	}
411 
412 	snprintf(cpw_service, cpwlen, "%s@%s",
413 		KADM5_CHANGEPW_SERVICE,	handle->params.realm);
414 
415 	/* generate the server principal from the name string we generated */
416 	if ((code = krb5_parse_name(handle->context, cpw_service,
417 		&mcreds.server))) {
418 		free(cpw_service);
419 		return (code);
420 	}
421 
422 	/* Find the credentials in the cache */
423 	if ((code = krb5_cc_retrieve_cred(handle->context, ccache, 0, &mcreds,
424 					&ncreds))) {
425 		free(cpw_service);
426 		return (code);
427 	}
428 
429 	/* Now we have all we need to make the change request. */
430 	result = krb5_change_password_local(handle->context, &handle->params,
431 				    &ncreds, newpw,
432 				    srvr_rsp_code,
433 				    srvr_msg);
434 
435 	free(cpw_service);
436 	return (result);
437 }
438