1 /* 2 * Copyright (c) 1997 - 2001 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include <krb5_locl.h> 35 36 RCSID("$Id: changepw.c,v 1.32 2001/05/14 22:49:55 assar Exp $"); 37 38 static krb5_error_code 39 get_kdc_address (krb5_context context, 40 krb5_realm realm, 41 struct addrinfo **ai, 42 char **ret_host) 43 { 44 krb5_error_code ret; 45 char **hostlist; 46 int port = 0; 47 int error; 48 char *host; 49 int save_errno; 50 51 ret = krb5_get_krb_changepw_hst (context, 52 &realm, 53 &hostlist); 54 if (ret) 55 return ret; 56 57 host = strdup(*hostlist); 58 krb5_free_krbhst(context, hostlist); 59 if (host == NULL) { 60 krb5_set_error_string (context, "malloc: out of memory"); 61 return ENOMEM; 62 } 63 64 port = ntohs(krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT)); 65 error = roken_getaddrinfo_hostspec2(host, SOCK_DGRAM, port, ai); 66 67 if(error) { 68 save_errno = errno; 69 krb5_set_error_string(context, "resolving %s: %s", 70 host, gai_strerror(error)); 71 return krb5_eai_to_heim_errno(error, save_errno); 72 } 73 *ret_host = host; 74 return 0; 75 } 76 77 static krb5_error_code 78 send_request (krb5_context context, 79 krb5_auth_context *auth_context, 80 krb5_creds *creds, 81 int sock, 82 struct sockaddr *sa, 83 int sa_size, 84 char *passwd, 85 const char *host) 86 { 87 krb5_error_code ret; 88 krb5_data ap_req_data; 89 krb5_data krb_priv_data; 90 krb5_data passwd_data; 91 size_t len; 92 u_char header[6]; 93 u_char *p; 94 struct iovec iov[3]; 95 struct msghdr msghdr; 96 97 krb5_data_zero (&ap_req_data); 98 99 ret = krb5_mk_req_extended (context, 100 auth_context, 101 AP_OPTS_MUTUAL_REQUIRED, 102 NULL, /* in_data */ 103 creds, 104 &ap_req_data); 105 if (ret) 106 return ret; 107 108 passwd_data.data = passwd; 109 passwd_data.length = strlen(passwd); 110 111 krb5_data_zero (&krb_priv_data); 112 113 ret = krb5_mk_priv (context, 114 *auth_context, 115 &passwd_data, 116 &krb_priv_data, 117 NULL); 118 if (ret) 119 goto out2; 120 121 len = 6 + ap_req_data.length + krb_priv_data.length; 122 p = header; 123 *p++ = (len >> 8) & 0xFF; 124 *p++ = (len >> 0) & 0xFF; 125 *p++ = 0; 126 *p++ = 1; 127 *p++ = (ap_req_data.length >> 8) & 0xFF; 128 *p++ = (ap_req_data.length >> 0) & 0xFF; 129 130 memset(&msghdr, 0, sizeof(msghdr)); 131 msghdr.msg_name = (void *)sa; 132 msghdr.msg_namelen = sa_size; 133 msghdr.msg_iov = iov; 134 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); 135 #if 0 136 msghdr.msg_control = NULL; 137 msghdr.msg_controllen = 0; 138 #endif 139 140 iov[0].iov_base = (void*)header; 141 iov[0].iov_len = 6; 142 iov[1].iov_base = ap_req_data.data; 143 iov[1].iov_len = ap_req_data.length; 144 iov[2].iov_base = krb_priv_data.data; 145 iov[2].iov_len = krb_priv_data.length; 146 147 if (sendmsg (sock, &msghdr, 0) < 0) { 148 ret = errno; 149 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret)); 150 } 151 152 krb5_data_free (&krb_priv_data); 153 out2: 154 krb5_data_free (&ap_req_data); 155 return ret; 156 } 157 158 static void 159 str2data (krb5_data *d, 160 const char *fmt, 161 ...) __attribute__ ((format (printf, 2, 3))); 162 163 static void 164 str2data (krb5_data *d, 165 const char *fmt, 166 ...) 167 { 168 va_list args; 169 170 va_start(args, fmt); 171 d->length = vasprintf ((char **)&d->data, fmt, args); 172 va_end(args); 173 } 174 175 static krb5_error_code 176 process_reply (krb5_context context, 177 krb5_auth_context auth_context, 178 int sock, 179 int *result_code, 180 krb5_data *result_code_string, 181 krb5_data *result_string, 182 const char *host) 183 { 184 krb5_error_code ret; 185 u_char reply[BUFSIZ]; 186 size_t len; 187 u_int16_t pkt_len, pkt_ver; 188 krb5_data ap_rep_data; 189 int save_errno; 190 191 ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL); 192 if (ret < 0) { 193 save_errno = errno; 194 krb5_set_error_string(context, "recvfrom %s: %s", 195 host, strerror(save_errno)); 196 return save_errno; 197 } 198 199 len = ret; 200 pkt_len = (reply[0] << 8) | (reply[1]); 201 pkt_ver = (reply[2] << 8) | (reply[3]); 202 203 if (pkt_len != len) { 204 str2data (result_string, "client: wrong len in reply"); 205 *result_code = KRB5_KPASSWD_MALFORMED; 206 return 0; 207 } 208 if (pkt_ver != 0x0001) { 209 str2data (result_string, 210 "client: wrong version number (%d)", pkt_ver); 211 *result_code = KRB5_KPASSWD_MALFORMED; 212 return 0; 213 } 214 215 ap_rep_data.data = reply + 6; 216 ap_rep_data.length = (reply[4] << 8) | (reply[5]); 217 218 if (ap_rep_data.length) { 219 krb5_ap_rep_enc_part *ap_rep; 220 krb5_data priv_data; 221 u_char *p; 222 223 ret = krb5_rd_rep (context, 224 auth_context, 225 &ap_rep_data, 226 &ap_rep); 227 if (ret) 228 return ret; 229 230 krb5_free_ap_rep_enc_part (context, ap_rep); 231 232 priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length; 233 priv_data.length = len - ap_rep_data.length - 6; 234 235 ret = krb5_rd_priv (context, 236 auth_context, 237 &priv_data, 238 result_code_string, 239 NULL); 240 if (ret) { 241 krb5_data_free (result_code_string); 242 return ret; 243 } 244 245 if (result_code_string->length < 2) { 246 *result_code = KRB5_KPASSWD_MALFORMED; 247 str2data (result_string, 248 "client: bad length in result"); 249 return 0; 250 } 251 p = result_code_string->data; 252 253 *result_code = (p[0] << 8) | p[1]; 254 krb5_data_copy (result_string, 255 (unsigned char*)result_code_string->data + 2, 256 result_code_string->length - 2); 257 return 0; 258 } else { 259 KRB_ERROR error; 260 size_t size; 261 u_char *p; 262 263 ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size); 264 if (ret) { 265 return ret; 266 } 267 if (error.e_data->length < 2) { 268 krb5_warnx (context, "too short e_data to print anything usable"); 269 return 1; /* XXX */ 270 } 271 272 p = error.e_data->data; 273 *result_code = (p[0] << 8) | p[1]; 274 krb5_data_copy (result_string, 275 p + 2, 276 error.e_data->length - 2); 277 return 0; 278 } 279 } 280 281 /* 282 * change the password using the credentials in `creds' (for the 283 * principal indicated in them) to `newpw', storing the result of 284 * the operation in `result_*' and an error code or 0. 285 */ 286 287 krb5_error_code 288 krb5_change_password (krb5_context context, 289 krb5_creds *creds, 290 char *newpw, 291 int *result_code, 292 krb5_data *result_code_string, 293 krb5_data *result_string) 294 { 295 krb5_error_code ret; 296 krb5_auth_context auth_context = NULL; 297 int sock; 298 int i; 299 struct addrinfo *ai, *a; 300 int done = 0; 301 char *host = NULL; 302 303 ret = krb5_auth_con_init (context, &auth_context); 304 if (ret) 305 return ret; 306 307 ret = get_kdc_address (context, creds->client->realm, &ai, &host); 308 if (ret) 309 goto out; 310 311 for (a = ai; !done && a != NULL; a = a->ai_next) { 312 int replied = 0; 313 314 sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol); 315 if (sock < 0) 316 continue; 317 318 for (i = 0; !done && i < 5; ++i) { 319 fd_set fdset; 320 struct timeval tv; 321 322 if (!replied) { 323 replied = 0; 324 ret = send_request (context, 325 &auth_context, 326 creds, 327 sock, 328 a->ai_addr, 329 a->ai_addrlen, 330 newpw, 331 host); 332 if (ret) { 333 close(sock); 334 goto out; 335 } 336 } 337 338 if (sock >= FD_SETSIZE) { 339 krb5_set_error_string(context, "fd %d too large", sock); 340 ret = ERANGE; 341 close (sock); 342 goto out; 343 } 344 345 FD_ZERO(&fdset); 346 FD_SET(sock, &fdset); 347 tv.tv_usec = 0; 348 tv.tv_sec = 1 + (1 << i); 349 350 ret = select (sock + 1, &fdset, NULL, NULL, &tv); 351 if (ret < 0 && errno != EINTR) { 352 close(sock); 353 goto out; 354 } 355 if (ret == 1) { 356 ret = process_reply (context, 357 auth_context, 358 sock, 359 result_code, 360 result_code_string, 361 result_string, 362 host); 363 if (ret == 0) 364 done = 1; 365 else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL) 366 replied = 1; 367 } else { 368 ret = KRB5_KDC_UNREACH; 369 } 370 } 371 close (sock); 372 } 373 freeaddrinfo (ai); 374 375 out: 376 krb5_auth_con_free (context, auth_context); 377 free (host); 378 if (done) 379 return 0; 380 else { 381 if (ret == KRB5_KDC_UNREACH) 382 krb5_set_error_string(context, 383 "failed to reach kpasswd server %s " 384 "in realm %s", 385 host, creds->client->realm); 386 387 return ret; 388 } 389 } 390