1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 #include "k5-int.h" 3 #include <kadm5/admin.h> 4 #include <syslog.h> 5 #include <adm_proto.h> /* krb5_klog_syslog */ 6 #include <stdio.h> 7 #include <errno.h> 8 9 #include "kadm5/server_internal.h" /* XXX for kadm5_server_handle_t */ 10 11 #include "misc.h" 12 13 #ifndef GETSOCKNAME_ARG3_TYPE 14 #define GETSOCKNAME_ARG3_TYPE int 15 #endif 16 17 #define RFC3244_VERSION 0xff80 18 19 static krb5_error_code 20 process_chpw_request(krb5_context context, void *server_handle, char *realm, 21 krb5_keytab keytab, const krb5_fulladdr *local_addr, 22 const krb5_fulladdr *remote_addr, krb5_data *req, 23 krb5_data *rep) 24 { 25 krb5_error_code ret; 26 char *ptr; 27 unsigned int plen, vno; 28 krb5_data ap_req, ap_rep = empty_data(); 29 krb5_data cipher = empty_data(), clear = empty_data(); 30 krb5_auth_context auth_context = NULL; 31 krb5_principal changepw = NULL; 32 krb5_principal client, target = NULL; 33 krb5_ticket *ticket = NULL; 34 krb5_replay_data replay; 35 krb5_error krberror; 36 int numresult; 37 char strresult[1024]; 38 char *clientstr = NULL, *targetstr = NULL; 39 const char *errmsg = NULL; 40 size_t clen; 41 char *cdots; 42 struct sockaddr_storage ss; 43 socklen_t salen; 44 char addrbuf[100]; 45 krb5_address *addr = remote_addr->address; 46 47 *rep = empty_data(); 48 49 if (req->length < 4) { 50 /* either this, or the server is printing bad messages, 51 or the caller passed in garbage */ 52 ret = KRB5KRB_AP_ERR_MODIFIED; 53 numresult = KRB5_KPASSWD_MALFORMED; 54 strlcpy(strresult, "Request was truncated", sizeof(strresult)); 55 goto bailout; 56 } 57 58 ptr = req->data; 59 60 /* verify length */ 61 62 plen = (*ptr++ & 0xff); 63 plen = (plen<<8) | (*ptr++ & 0xff); 64 65 if (plen != req->length) { 66 ret = KRB5KRB_AP_ERR_MODIFIED; 67 numresult = KRB5_KPASSWD_MALFORMED; 68 strlcpy(strresult, "Request length was inconsistent", 69 sizeof(strresult)); 70 goto bailout; 71 } 72 73 /* verify version number */ 74 75 vno = (*ptr++ & 0xff) ; 76 vno = (vno<<8) | (*ptr++ & 0xff); 77 78 if (vno != 1 && vno != RFC3244_VERSION) { 79 ret = KRB5KDC_ERR_BAD_PVNO; 80 numresult = KRB5_KPASSWD_BAD_VERSION; 81 snprintf(strresult, sizeof(strresult), 82 "Request contained unknown protocol version number %d", vno); 83 goto bailout; 84 } 85 86 /* read, check ap-req length */ 87 88 ap_req.length = (*ptr++ & 0xff); 89 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff); 90 91 if (ptr + ap_req.length >= req->data + req->length) { 92 ret = KRB5KRB_AP_ERR_MODIFIED; 93 numresult = KRB5_KPASSWD_MALFORMED; 94 strlcpy(strresult, "Request was truncated in AP-REQ", 95 sizeof(strresult)); 96 goto bailout; 97 } 98 99 /* verify ap_req */ 100 101 ap_req.data = ptr; 102 ptr += ap_req.length; 103 104 ret = krb5_auth_con_init(context, &auth_context); 105 if (ret) { 106 numresult = KRB5_KPASSWD_HARDERROR; 107 strlcpy(strresult, "Failed initializing auth context", 108 sizeof(strresult)); 109 goto chpwfail; 110 } 111 112 ret = krb5_auth_con_setflags(context, auth_context, 113 KRB5_AUTH_CONTEXT_DO_SEQUENCE); 114 if (ret) { 115 numresult = KRB5_KPASSWD_HARDERROR; 116 strlcpy(strresult, "Failed initializing auth context", 117 sizeof(strresult)); 118 goto chpwfail; 119 } 120 121 ret = krb5_build_principal(context, &changepw, strlen(realm), realm, 122 "kadmin", "changepw", NULL); 123 if (ret) { 124 numresult = KRB5_KPASSWD_HARDERROR; 125 strlcpy(strresult, "Failed building kadmin/changepw principal", 126 sizeof(strresult)); 127 goto chpwfail; 128 } 129 130 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab, 131 NULL, &ticket); 132 133 if (ret) { 134 numresult = KRB5_KPASSWD_AUTHERROR; 135 strlcpy(strresult, "Failed reading application request", 136 sizeof(strresult)); 137 goto chpwfail; 138 } 139 140 /* construct the ap-rep */ 141 142 ret = krb5_mk_rep(context, auth_context, &ap_rep); 143 if (ret) { 144 numresult = KRB5_KPASSWD_AUTHERROR; 145 strlcpy(strresult, "Failed replying to application request", 146 sizeof(strresult)); 147 goto chpwfail; 148 } 149 150 /* decrypt the ChangePasswdData */ 151 152 cipher.length = (req->data + req->length) - ptr; 153 cipher.data = ptr; 154 155 /* 156 * Don't set a remote address in auth_context before calling krb5_rd_priv, 157 * so that we can work against clients behind a NAT. Reflection attacks 158 * aren't a concern since we use sequence numbers and since our requests 159 * don't look anything like our responses. Also don't set a local address, 160 * since we don't know what interface the request was received on. 161 */ 162 163 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay); 164 if (ret) { 165 numresult = KRB5_KPASSWD_HARDERROR; 166 strlcpy(strresult, "Failed decrypting request", sizeof(strresult)); 167 goto chpwfail; 168 } 169 170 client = ticket->enc_part2->client; 171 172 /* decode ChangePasswdData for setpw requests */ 173 if (vno == RFC3244_VERSION) { 174 krb5_data *clear_data; 175 176 ret = decode_krb5_setpw_req(&clear, &clear_data, &target); 177 if (ret != 0) { 178 numresult = KRB5_KPASSWD_MALFORMED; 179 strlcpy(strresult, "Failed decoding ChangePasswdData", 180 sizeof(strresult)); 181 goto chpwfail; 182 } 183 184 zapfree(clear.data, clear.length); 185 186 clear = *clear_data; 187 free(clear_data); 188 189 if (target != NULL) { 190 ret = krb5_unparse_name(context, target, &targetstr); 191 if (ret != 0) { 192 numresult = KRB5_KPASSWD_HARDERROR; 193 strlcpy(strresult, "Failed unparsing target name for log", 194 sizeof(strresult)); 195 goto chpwfail; 196 } 197 } 198 } 199 200 ret = krb5_unparse_name(context, client, &clientstr); 201 if (ret) { 202 numresult = KRB5_KPASSWD_HARDERROR; 203 strlcpy(strresult, "Failed unparsing client name for log", 204 sizeof(strresult)); 205 goto chpwfail; 206 } 207 208 /* change the password */ 209 210 ptr = k5memdup0(clear.data, clear.length, &ret); 211 ret = schpw_util_wrapper(server_handle, client, target, 212 (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0, 213 ptr, NULL, strresult, sizeof(strresult)); 214 if (ret) 215 errmsg = krb5_get_error_message(context, ret); 216 217 /* zap the password */ 218 zapfree(clear.data, clear.length); 219 zapfree(ptr, clear.length); 220 clear = empty_data(); 221 222 clen = strlen(clientstr); 223 trunc_name(&clen, &cdots); 224 225 switch (addr->addrtype) { 226 case ADDRTYPE_INET: { 227 struct sockaddr_in *sin = ss2sin(&ss); 228 229 sin->sin_family = AF_INET; 230 memcpy(&sin->sin_addr, addr->contents, addr->length); 231 sin->sin_port = htons(remote_addr->port); 232 salen = sizeof(*sin); 233 break; 234 } 235 case ADDRTYPE_INET6: { 236 struct sockaddr_in6 *sin6 = ss2sin6(&ss); 237 238 sin6->sin6_family = AF_INET6; 239 memcpy(&sin6->sin6_addr, addr->contents, addr->length); 240 sin6->sin6_port = htons(remote_addr->port); 241 salen = sizeof(*sin6); 242 break; 243 } 244 default: { 245 struct sockaddr *sa = ss2sa(&ss); 246 247 sa->sa_family = AF_UNSPEC; 248 salen = sizeof(*sa); 249 break; 250 } 251 } 252 253 if (getnameinfo(ss2sa(&ss), salen, 254 addrbuf, sizeof(addrbuf), NULL, 0, 255 NI_NUMERICHOST | NI_NUMERICSERV) != 0) 256 strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf)); 257 258 if (vno == RFC3244_VERSION) { 259 size_t tlen; 260 char *tdots; 261 const char *targetp; 262 263 if (target == NULL) { 264 tlen = clen; 265 tdots = cdots; 266 targetp = targetstr; 267 } else { 268 tlen = strlen(targetstr); 269 trunc_name(&tlen, &tdots); 270 targetp = clientstr; 271 } 272 273 krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for " 274 "%.*s%s: %s"), addrbuf, (int) clen, 275 clientstr, cdots, (int) tlen, targetp, tdots, 276 errmsg ? errmsg : "success"); 277 } else { 278 krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"), 279 addrbuf, (int) clen, clientstr, cdots, 280 errmsg ? errmsg : "success"); 281 } 282 switch (ret) { 283 case KADM5_AUTH_CHANGEPW: 284 numresult = KRB5_KPASSWD_ACCESSDENIED; 285 break; 286 case KADM5_AUTH_INITIAL: 287 numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED; 288 break; 289 case KADM5_PASS_Q_TOOSHORT: 290 case KADM5_PASS_REUSE: 291 case KADM5_PASS_Q_CLASS: 292 case KADM5_PASS_Q_DICT: 293 case KADM5_PASS_Q_GENERIC: 294 case KADM5_PASS_TOOSOON: 295 numresult = KRB5_KPASSWD_SOFTERROR; 296 break; 297 case 0: 298 numresult = KRB5_KPASSWD_SUCCESS; 299 strlcpy(strresult, "", sizeof(strresult)); 300 break; 301 default: 302 numresult = KRB5_KPASSWD_HARDERROR; 303 break; 304 } 305 306 chpwfail: 307 308 ret = alloc_data(&clear, 2 + strlen(strresult)); 309 if (ret) 310 goto bailout; 311 312 ptr = clear.data; 313 314 *ptr++ = (numresult>>8) & 0xff; 315 *ptr++ = numresult & 0xff; 316 317 memcpy(ptr, strresult, strlen(strresult)); 318 319 cipher = empty_data(); 320 321 if (ap_rep.length) { 322 ret = krb5_auth_con_setaddrs(context, auth_context, 323 local_addr->address, NULL); 324 if (ret) { 325 numresult = KRB5_KPASSWD_HARDERROR; 326 strlcpy(strresult, 327 "Failed storing client and server internet addresses", 328 sizeof(strresult)); 329 } else { 330 ret = krb5_mk_priv(context, auth_context, &clear, &cipher, 331 &replay); 332 if (ret) { 333 numresult = KRB5_KPASSWD_HARDERROR; 334 strlcpy(strresult, "Failed encrypting reply", 335 sizeof(strresult)); 336 } 337 } 338 } 339 340 /* if no KRB-PRIV was constructed, then we need a KRB-ERROR. 341 if this fails, just bail. there's nothing else we can do. */ 342 343 if (cipher.length == 0) { 344 /* clear out ap_rep now, so that it won't be inserted in the 345 reply */ 346 347 if (ap_rep.length) { 348 free(ap_rep.data); 349 ap_rep = empty_data(); 350 } 351 352 krberror.ctime = 0; 353 krberror.cusec = 0; 354 krberror.susec = 0; 355 ret = krb5_timeofday(context, &krberror.stime); 356 if (ret) 357 goto bailout; 358 359 /* this is really icky. but it's what all the other callers 360 to mk_error do. */ 361 krberror.error = ret; 362 krberror.error -= ERROR_TABLE_BASE_krb5; 363 if (krberror.error > KRB_ERR_MAX) 364 krberror.error = KRB_ERR_GENERIC; 365 366 krberror.client = NULL; 367 368 ret = krb5_build_principal(context, &krberror.server, 369 strlen(realm), realm, 370 "kadmin", "changepw", NULL); 371 if (ret) 372 goto bailout; 373 krberror.text.length = 0; 374 krberror.e_data = clear; 375 376 ret = krb5_mk_error(context, &krberror, &cipher); 377 378 krb5_free_principal(context, krberror.server); 379 380 if (ret) 381 goto bailout; 382 } 383 384 /* construct the reply */ 385 386 ret = alloc_data(rep, 6 + ap_rep.length + cipher.length); 387 if (ret) 388 goto bailout; 389 ptr = rep->data; 390 391 /* length */ 392 393 *ptr++ = (rep->length>>8) & 0xff; 394 *ptr++ = rep->length & 0xff; 395 396 /* version == 0x0001 big-endian */ 397 398 *ptr++ = 0; 399 *ptr++ = 1; 400 401 /* ap_rep length, big-endian */ 402 403 *ptr++ = (ap_rep.length>>8) & 0xff; 404 *ptr++ = ap_rep.length & 0xff; 405 406 /* ap-rep data */ 407 408 if (ap_rep.length) { 409 memcpy(ptr, ap_rep.data, ap_rep.length); 410 ptr += ap_rep.length; 411 } 412 413 /* krb-priv or krb-error */ 414 415 memcpy(ptr, cipher.data, cipher.length); 416 417 bailout: 418 krb5_auth_con_free(context, auth_context); 419 krb5_free_principal(context, changepw); 420 krb5_free_ticket(context, ticket); 421 free(ap_rep.data); 422 free(clear.data); 423 free(cipher.data); 424 krb5_free_principal(context, target); 425 krb5_free_unparsed_name(context, targetstr); 426 krb5_free_unparsed_name(context, clientstr); 427 krb5_free_error_message(context, errmsg); 428 return ret; 429 } 430 431 /* Dispatch routine for set/change password */ 432 void 433 dispatch(void *handle, const krb5_fulladdr *local_addr, 434 const krb5_fulladdr *remote_addr, krb5_data *request, int is_tcp, 435 verto_ctx *vctx, loop_respond_fn respond, void *arg) 436 { 437 krb5_error_code ret; 438 krb5_keytab kt = NULL; 439 kadm5_server_handle_t server_handle = *(void **)handle; 440 krb5_data *response = NULL; 441 const char *emsg; 442 443 ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt); 444 if (ret != 0) { 445 emsg = krb5_get_error_message(server_handle->context, ret); 446 krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"), 447 emsg); 448 krb5_free_error_message(server_handle->context, emsg); 449 goto egress; 450 } 451 452 response = k5alloc(sizeof(krb5_data), &ret); 453 if (response == NULL) 454 goto egress; 455 456 ret = process_chpw_request(server_handle->context, 457 server_handle, 458 server_handle->params.realm, 459 kt, 460 local_addr, 461 remote_addr, 462 request, 463 response); 464 egress: 465 if (ret) 466 krb5_free_data(server_handle->context, response); 467 krb5_kt_close(server_handle->context, kt); 468 (*respond)(arg, ret, ret == 0 ? response : NULL); 469 } 470