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