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