dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / lib / krb5 / kadm5 / clnt / changepw.c
blob38b1da321e1ca8f27d1043722c61c26e57bb647a
1 /*
2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
7 /*
8 * lib/krb5/os/changepw.c
10 * Copyright 1990,1999 by the Massachusetts Institute of Technology.
11 * All Rights Reserved.
13 * Export of this software from the United States of America may
14 * require a specific license from the United States Government.
15 * It is the responsibility of any person or organization contemplating
16 * export to obtain such a license before exporting.
18 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
19 * distribute this software and its documentation for any purpose and
20 * without fee is hereby granted, provided that the above copyright
21 * notice appear in all copies and that both that copyright notice and
22 * this permission notice appear in supporting documentation, and that
23 * the name of M.I.T. not be used in advertising or publicity pertaining
24 * to distribution of the software without specific, written prior
25 * permission. Furthermore if you modify this software you must label
26 * your software as modified software and not distribute it in such a
27 * fashion that it might be confused with the original M.I.T. software.
28 * M.I.T. makes no representations about the suitability of
29 * this software for any purpose. It is provided "as is" without express
30 * or implied warranty.
34 #define NEED_SOCKETS
35 #include <k5-int.h>
36 #include <kadm5/admin.h>
37 #include <client_internal.h>
38 #include <gssapi/gssapi.h>
39 #include <gssapi_krb5.h>
40 #include <gssapiP_krb5.h>
41 #include <krb5.h>
43 /* #include "adm_err.h" */
44 #include <stdio.h>
45 #include <errno.h>
47 extern krb5_error_code krb5int_mk_chpw_req(krb5_context context,
48 krb5_auth_context auth_context,
49 krb5_data *ap_req, char *passwd,
50 krb5_data *packet);
52 extern krb5_error_code krb5int_rd_chpw_rep(krb5_context context,
53 krb5_auth_context auth_context,
54 krb5_data *packet, int *result_code,
55 krb5_data *result_data);
58 * _kadm5_get_kpasswd_protocol
60 * returns the password change protocol value to the caller.
61 * Since the 'handle' is an opaque value to higher up callers,
62 * this method is needed to provide a way for them to get a peek
63 * at the protocol being used without having to expose the entire
64 * handle structure.
66 krb5_chgpwd_prot
67 _kadm5_get_kpasswd_protocol(void *handle)
69 kadm5_server_handle_t srvrhdl = (kadm5_server_handle_t)handle;
71 return (srvrhdl->params.kpasswd_protocol);
75 * krb5_change_password
77 * Prepare and send a CHANGEPW request to a password server
78 * using UDP datagrams. This is only used for sending to
79 * non-SEAM servers which support the Marc Horowitz defined
80 * protocol (1998) for password changing.
82 * SUNW14resync - added _local as it conflicts with one in krb5.h
84 static krb5_error_code
85 krb5_change_password_local(context, params, creds, newpw, srvr_rsp_code,
86 srvr_msg)
87 krb5_context context;
88 kadm5_config_params *params;
89 krb5_creds *creds;
90 char *newpw;
91 kadm5_ret_t *srvr_rsp_code;
92 krb5_data *srvr_msg;
94 krb5_auth_context auth_context;
95 krb5_data ap_req, chpw_req, chpw_rep;
96 krb5_address local_kaddr, remote_kaddr;
97 krb5_error_code code = 0;
98 int i;
99 socklen_t addrlen, tmp_len;
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;
105 SOCKET s1 = INVALID_SOCKET;
106 SOCKET s2 = INVALID_SOCKET;
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));
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;
120 if (code = krb5_mk_req_extended(context, &auth_context,
121 AP_OPTS_USE_SUBKEY,
122 NULL, creds, &ap_req))
123 goto cleanup;
126 * find the address of the kpasswd_server.
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;
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;
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.
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.
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.
162 if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
164 code = errno;
165 goto cleanup;
168 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
170 code = errno;
171 goto cleanup;
174 for (i = 0; i < naddr_p; i++)
176 fd_set fdset;
177 struct timeval timeout;
179 if (connect(s2, &addr_p[i], sizeof (addr_p[i])) ==
180 SOCKET_ERROR)
182 if ((errno == ECONNREFUSED) ||
183 (errno == EHOSTUNREACH))
184 continue; /* try the next addr */
186 code = errno;
187 goto cleanup;
190 addrlen = sizeof (local_addr);
192 if (getsockname(s2, &local_addr, &addrlen) < 0)
194 if ((errno == ECONNREFUSED) ||
195 (errno == EHOSTUNREACH))
196 continue; /* try the next addr */
198 code = errno;
199 goto cleanup;
203 * some brain-dead OS's don't return useful information from
204 * the getsockname call. Namely, windows and solaris.
206 if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0)
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);
215 else
217 krb5_address **addrs;
219 krb5_os_localaddr(context, &addrs);
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);
228 krb5_free_addresses(context, addrs);
231 addrlen = sizeof (remote_addr);
232 if (getpeername(s2, &remote_addr, &addrlen) < 0)
234 if ((errno == ECONNREFUSED) ||
235 (errno == EHOSTUNREACH))
236 continue; /* try the next addr */
238 code = errno;
239 goto cleanup;
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);
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?
264 if (code = krb5_auth_con_setaddrs(context, auth_context,
265 &local_kaddr, NULL))
267 code = errno;
268 goto cleanup;
271 if (code = krb5int_mk_chpw_req(context, auth_context,
272 &ap_req, newpw, &chpw_req))
274 code = errno;
275 goto cleanup;
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)
282 if ((cc < 0) && ((errno == ECONNREFUSED) ||
283 (errno == EHOSTUNREACH)))
284 continue; /* try the next addr */
286 code = (cc < 0) ? errno : ECONNABORTED;
287 goto cleanup;
290 chpw_rep.length = 1500;
291 chpw_rep.data = (char *)malloc(chpw_rep.length);
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 */
310 tmp_len = sizeof (tmp_addr);
311 if ((cc = recvfrom(s1, chpw_rep.data, chpw_rep.length,
312 0, &tmp_addr, &tmp_len)) < 0)
314 code = errno;
315 goto cleanup;
318 closesocket(s1);
319 s1 = INVALID_SOCKET;
320 closesocket(s2);
321 s2 = INVALID_SOCKET;
323 chpw_rep.length = cc;
325 if (code = krb5_auth_con_setaddrs(context, auth_context,
326 NULL, &remote_kaddr))
327 goto cleanup;
329 if (code = krb5int_rd_chpw_rep(context, auth_context, &chpw_rep,
330 &local_result_code, srvr_msg))
331 goto cleanup;
333 if (srvr_rsp_code)
334 *srvr_rsp_code = local_result_code;
336 code = 0;
337 goto cleanup;
340 code = errno;
342 cleanup:
343 if (auth_context != NULL)
344 krb5_auth_con_free(context, auth_context);
346 if (addr_p != NULL)
347 krb5_xfree(addr_p);
349 if (s1 != INVALID_SOCKET)
350 closesocket(s1);
352 if (s2 != INVALID_SOCKET)
353 closesocket(s2);
355 krb5_xfree(chpw_req.data);
356 krb5_xfree(chpw_rep.data);
357 krb5_xfree(ap_req.data);
359 return (code);
364 * kadm5_chpass_principal_v2
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.
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)
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;
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.
393 memset(&mcreds, 0, sizeof (krb5_creds));
394 if ((code = krb5_cc_resolve(handle->context, handle->cache_name,
395 &ccache)))
396 return (code);
398 /* set the client principal in the credential match structure */
399 mcreds.client = princ;
402 * set the server principal (kadmin/changepw@REALM) in the credential
403 * match struct
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);
412 snprintf(cpw_service, cpwlen, "%s@%s",
413 KADM5_CHANGEPW_SERVICE, handle->params.realm);
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);
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);
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);
435 free(cpw_service);
436 return (result);