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