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