1 /* 2 * Copyright (c) 1997 - 2005 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 "kadmin_locl.h" 35 #include <krb5-private.h> 36 37 static kadm5_ret_t 38 kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, 39 krb5_data *in, krb5_data *out) 40 { 41 kadm5_ret_t ret = 0; 42 kadm5_ret_t ret_sp = 0; 43 int32_t cmd, mask, tmp; 44 kadm5_server_context *contextp = kadm_handlep; 45 char client[128], name[128], name2[128]; 46 const char *op = ""; 47 krb5_principal princ, princ2; 48 kadm5_principal_ent_rec ent; 49 char *password, *expression; 50 krb5_keyblock *new_keys; 51 int n_keys; 52 char **princs; 53 int n_princs; 54 krb5_storage *rsp = NULL; /* response goes here */ 55 krb5_storage *sp = NULL; 56 57 memset(&ent, 0, sizeof(ent)); 58 krb5_data_zero(out); 59 ret = krb5_unparse_name_fixed(contextp->context, contextp->caller, 60 client, sizeof(client)); 61 62 sp = krb5_storage_from_data(in); 63 if (sp == NULL) 64 krb5_errx(contextp->context, 1, "out of memory"); 65 66 ret = krb5_ret_int32(sp, &cmd); 67 if (ret) { 68 krb5_storage_free(sp); 69 goto fail; 70 } 71 switch(cmd){ 72 case kadm_get:{ 73 op = "GET"; 74 ret = krb5_ret_principal(sp, &princ); 75 if(ret) 76 goto fail; 77 ret = krb5_ret_int32(sp, &mask); 78 if(ret){ 79 krb5_free_principal(contextp->context, princ); 80 goto fail; 81 } 82 mask |= KADM5_PRINCIPAL; 83 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 84 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 85 if (ret == 0) 86 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ); 87 if(ret){ 88 krb5_free_principal(contextp->context, princ); 89 goto fail; 90 } 91 ret = kadm5_get_principal(kadm_handlep, princ, &ent, mask); 92 krb5_storage_free(sp); 93 sp = krb5_storage_emem(); 94 krb5_store_int32(sp, ret); 95 if(ret == 0){ 96 kadm5_store_principal_ent(sp, &ent); 97 kadm5_free_principal_ent(kadm_handlep, &ent); 98 } 99 krb5_free_principal(contextp->context, princ); 100 break; 101 } 102 case kadm_delete:{ 103 op = "DELETE"; 104 ret = krb5_ret_principal(sp, &princ); 105 if(ret) 106 goto fail; 107 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 108 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 109 if (ret == 0) 110 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ); 111 if(ret){ 112 krb5_free_principal(contextp->context, princ); 113 goto fail; 114 } 115 ret = kadm5_delete_principal(kadm_handlep, princ); 116 krb5_free_principal(contextp->context, princ); 117 krb5_storage_free(sp); 118 sp = krb5_storage_emem(); 119 krb5_store_int32(sp, ret); 120 break; 121 } 122 case kadm_create:{ 123 op = "CREATE"; 124 ret = kadm5_ret_principal_ent(sp, &ent); 125 if(ret) 126 goto fail; 127 ret = krb5_ret_int32(sp, &mask); 128 if(ret){ 129 kadm5_free_principal_ent(contextp->context, &ent); 130 goto fail; 131 } 132 ret = krb5_ret_string(sp, &password); 133 if(ret){ 134 kadm5_free_principal_ent(contextp->context, &ent); 135 goto fail; 136 } 137 ret = krb5_unparse_name_fixed(contextp->context, ent.principal, 138 name, sizeof(name)); 139 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 140 if (ret == 0) 141 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD, 142 ent.principal); 143 if(ret){ 144 kadm5_free_principal_ent(contextp->context, &ent); 145 memset(password, 0, strlen(password)); 146 free(password); 147 goto fail; 148 } 149 ret = kadm5_create_principal(kadm_handlep, &ent, 150 mask, password); 151 kadm5_free_principal_ent(kadm_handlep, &ent); 152 memset(password, 0, strlen(password)); 153 free(password); 154 krb5_storage_free(sp); 155 sp = krb5_storage_emem(); 156 krb5_store_int32(sp, ret); 157 break; 158 } 159 case kadm_modify:{ 160 op = "MODIFY"; 161 ret = kadm5_ret_principal_ent(sp, &ent); 162 if(ret) 163 goto fail; 164 ret = krb5_ret_int32(sp, &mask); 165 if(ret){ 166 kadm5_free_principal_ent(contextp, &ent); 167 goto fail; 168 } 169 ret = krb5_unparse_name_fixed(contextp->context, ent.principal, 170 name, sizeof(name)); 171 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 172 if (ret == 0) 173 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY, 174 ent.principal); 175 if(ret){ 176 kadm5_free_principal_ent(contextp, &ent); 177 goto fail; 178 } 179 ret = kadm5_modify_principal(kadm_handlep, &ent, mask); 180 kadm5_free_principal_ent(kadm_handlep, &ent); 181 krb5_storage_free(sp); 182 sp = krb5_storage_emem(); 183 krb5_store_int32(sp, ret); 184 break; 185 } 186 case kadm_rename:{ 187 op = "RENAME"; 188 ret = krb5_ret_principal(sp, &princ); 189 if(ret) 190 goto fail; 191 ret = krb5_ret_principal(sp, &princ2); 192 if(ret){ 193 krb5_free_principal(contextp->context, princ); 194 goto fail; 195 } 196 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 197 if (ret == 0) 198 ret = krb5_unparse_name_fixed(contextp->context, princ2, name2, sizeof(name2)); 199 krb5_warnx(contextp->context, "%s: %s %s -> %s", 200 client, op, name, name2); 201 if (ret == 0) 202 ret = _kadm5_acl_check_permission(contextp, 203 KADM5_PRIV_ADD, 204 princ2) 205 || _kadm5_acl_check_permission(contextp, 206 KADM5_PRIV_DELETE, 207 princ); 208 if(ret){ 209 krb5_free_principal(contextp->context, princ); 210 krb5_free_principal(contextp->context, princ2); 211 goto fail; 212 } 213 ret = kadm5_rename_principal(kadm_handlep, princ, princ2); 214 krb5_free_principal(contextp->context, princ); 215 krb5_free_principal(contextp->context, princ2); 216 krb5_storage_free(sp); 217 sp = krb5_storage_emem(); 218 krb5_store_int32(sp, ret); 219 break; 220 } 221 case kadm_chpass:{ 222 op = "CHPASS"; 223 ret = krb5_ret_principal(sp, &princ); 224 if(ret) 225 goto fail; 226 ret = krb5_ret_string(sp, &password); 227 if(ret){ 228 krb5_free_principal(contextp->context, princ); 229 goto fail; 230 } 231 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 232 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 233 234 /* 235 * The change is allowed if at least one of: 236 * 237 * a) allowed by sysadmin 238 * b) it's for the principal him/herself and this was an 239 * initial ticket, but then, check with the password quality 240 * function. 241 * c) the user is on the CPW ACL. 242 */ 243 244 if (ret == 0) { 245 if (krb5_config_get_bool_default(contextp->context, NULL, TRUE, 246 "kadmin", "allow_self_change_password", NULL) 247 && initial 248 && krb5_principal_compare (contextp->context, contextp->caller, 249 princ)) 250 { 251 krb5_data pwd_data; 252 const char *pwd_reason; 253 254 pwd_data.data = password; 255 pwd_data.length = strlen(password); 256 257 pwd_reason = kadm5_check_password_quality (contextp->context, 258 princ, &pwd_data); 259 if (pwd_reason != NULL) 260 ret = KADM5_PASS_Q_DICT; 261 else 262 ret = 0; 263 } else 264 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); 265 } 266 267 if(ret) { 268 krb5_free_principal(contextp->context, princ); 269 memset(password, 0, strlen(password)); 270 free(password); 271 goto fail; 272 } 273 ret = kadm5_chpass_principal(kadm_handlep, princ, password); 274 krb5_free_principal(contextp->context, princ); 275 memset(password, 0, strlen(password)); 276 free(password); 277 krb5_storage_free(sp); 278 sp = krb5_storage_emem(); 279 krb5_store_int32(sp, ret); 280 break; 281 } 282 case kadm_chpass_with_key:{ 283 int i; 284 krb5_key_data *key_data; 285 int n_key_data; 286 287 op = "CHPASS_WITH_KEY"; 288 ret = krb5_ret_principal(sp, &princ); 289 if(ret) 290 goto fail; 291 ret = krb5_ret_int32(sp, &n_key_data); 292 if (ret) { 293 krb5_free_principal(contextp->context, princ); 294 goto fail; 295 } 296 /* n_key_data will be squeezed into an int16_t below. */ 297 if (n_key_data < 0 || n_key_data >= 1 << 16 || 298 (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) { 299 ret = ERANGE; 300 krb5_free_principal(contextp->context, princ); 301 goto fail; 302 } 303 304 key_data = malloc (n_key_data * sizeof(*key_data)); 305 if (key_data == NULL && n_key_data != 0) { 306 ret = ENOMEM; 307 krb5_free_principal(contextp->context, princ); 308 goto fail; 309 } 310 311 for (i = 0; i < n_key_data; ++i) { 312 ret = kadm5_ret_key_data (sp, &key_data[i]); 313 if (ret) { 314 int16_t dummy = i; 315 316 kadm5_free_key_data (contextp, &dummy, key_data); 317 free (key_data); 318 krb5_free_principal(contextp->context, princ); 319 goto fail; 320 } 321 } 322 323 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 324 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 325 326 /* 327 * The change is only allowed if the user is on the CPW ACL, 328 * this it to force password quality check on the user. 329 */ 330 331 if (ret == 0) 332 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); 333 if(ret) { 334 int16_t dummy = n_key_data; 335 336 kadm5_free_key_data (contextp, &dummy, key_data); 337 free (key_data); 338 krb5_free_principal(contextp->context, princ); 339 goto fail; 340 } 341 ret = kadm5_chpass_principal_with_key(kadm_handlep, princ, 342 n_key_data, key_data); 343 { 344 int16_t dummy = n_key_data; 345 kadm5_free_key_data (contextp, &dummy, key_data); 346 } 347 free (key_data); 348 krb5_free_principal(contextp->context, princ); 349 krb5_storage_free(sp); 350 sp = krb5_storage_emem(); 351 krb5_store_int32(sp, ret); 352 break; 353 } 354 case kadm_randkey:{ 355 op = "RANDKEY"; 356 ret = krb5_ret_principal(sp, &princ); 357 if(ret) 358 goto fail; 359 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); 360 krb5_warnx(contextp->context, "%s: %s %s", client, op, name); 361 /* 362 * The change is allowed if at least one of: 363 * a) it's for the principal him/herself and this was an initial ticket 364 * b) the user is on the CPW ACL. 365 */ 366 367 if (ret == 0) { 368 if (initial 369 && krb5_principal_compare (contextp->context, contextp->caller, 370 princ)) 371 ret = 0; 372 else 373 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); 374 } 375 if(ret) { 376 krb5_free_principal(contextp->context, princ); 377 goto fail; 378 } 379 ret = kadm5_randkey_principal(kadm_handlep, princ, 380 &new_keys, &n_keys); 381 krb5_free_principal(contextp->context, princ); 382 krb5_storage_free(sp); 383 sp = krb5_storage_emem(); 384 krb5_store_int32(sp, ret); 385 if(ret == 0){ 386 int i; 387 krb5_store_int32(sp, n_keys); 388 for(i = 0; i < n_keys; i++){ 389 krb5_store_keyblock(sp, new_keys[i]); 390 krb5_free_keyblock_contents(contextp->context, &new_keys[i]); 391 } 392 free(new_keys); 393 } 394 break; 395 } 396 case kadm_get_privs:{ 397 uint32_t privs; 398 ret = kadm5_get_privs(kadm_handlep, &privs); 399 krb5_storage_free(sp); 400 sp = krb5_storage_emem(); 401 krb5_store_int32(sp, ret); 402 if(ret == 0) 403 krb5_store_uint32(sp, privs); 404 break; 405 } 406 case kadm_get_princs:{ 407 op = "LIST"; 408 ret = krb5_ret_int32(sp, &tmp); 409 if(ret) 410 goto fail; 411 if(tmp){ 412 ret = krb5_ret_string(sp, &expression); 413 if(ret) 414 goto fail; 415 }else 416 expression = NULL; 417 krb5_warnx(contextp->context, "%s: %s %s", client, op, 418 expression ? expression : "*"); 419 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL); 420 if(ret){ 421 free(expression); 422 goto fail; 423 } 424 ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs); 425 free(expression); 426 krb5_storage_free(sp); 427 sp = krb5_storage_emem(); 428 krb5_store_int32(sp, ret); 429 if(ret == 0){ 430 int i; 431 if ((ret = krb5_store_int32(sp, n_princs))) 432 goto fail; 433 for(i = 0; i < n_princs; i++) 434 if ((ret = krb5_store_string(sp, princs[i]))) 435 goto fail; 436 kadm5_free_name_list(kadm_handlep, princs, &n_princs); 437 } 438 break; 439 } 440 default: 441 krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd); 442 krb5_storage_free(sp); 443 sp = krb5_storage_emem(); 444 krb5_store_int32(sp, KADM5_FAILURE); 445 break; 446 } 447 krb5_storage_to_data(sp, out); 448 krb5_storage_free(sp); 449 return 0; 450 fail: 451 krb5_warn(contextp->context, ret, "%s", op); 452 krb5_storage_seek(sp, 0, SEEK_SET); 453 krb5_store_int32(sp, ret); 454 krb5_storage_to_data(sp, out); 455 krb5_storage_free(sp); 456 return ret; 457 } 458 459 static void 460 v5_loop (krb5_context contextp, 461 krb5_auth_context ac, 462 krb5_boolean initial, 463 void *kadm_handlep, 464 krb5_socket_t fd) 465 { 466 krb5_error_code ret; 467 krb5_data in, out; 468 469 for (;;) { 470 doing_useful_work = 0; 471 if(term_flag) 472 exit(0); 473 ret = krb5_read_priv_message(contextp, ac, &fd, &in); 474 if(ret == HEIM_ERR_EOF) 475 exit(0); 476 if (in.length == 0) 477 ret = HEIM_ERR_OPNOTSUPP; 478 if(ret) 479 krb5_err(contextp, 1, ret, "krb5_read_priv_message"); 480 doing_useful_work = 1; 481 kadmind_dispatch(kadm_handlep, initial, &in, &out); 482 krb5_data_free(&in); 483 ret = krb5_write_priv_message(contextp, ac, &fd, &out); 484 if(ret) 485 krb5_err(contextp, 1, ret, "krb5_write_priv_message"); 486 } 487 } 488 489 static krb5_boolean 490 match_appl_version(const void *data, const char *appl_version) 491 { 492 unsigned minor; 493 if(sscanf(appl_version, "KADM0.%u", &minor) != 1) 494 return 0; 495 /*XXX*/ 496 *(unsigned*)(intptr_t)data = minor; 497 return 1; 498 } 499 500 static void 501 handle_v5(krb5_context contextp, 502 krb5_keytab keytab, 503 krb5_socket_t fd) 504 { 505 krb5_error_code ret; 506 krb5_ticket *ticket; 507 char *server_name; 508 char *client; 509 void *kadm_handlep; 510 krb5_boolean initial; 511 krb5_auth_context ac = NULL; 512 513 unsigned kadm_version; 514 kadm5_config_params realm_params; 515 516 ret = krb5_recvauth_match_version(contextp, &ac, &fd, 517 match_appl_version, &kadm_version, 518 NULL, KRB5_RECVAUTH_IGNORE_VERSION, 519 keytab, &ticket); 520 if (ret) 521 krb5_err(contextp, 1, ret, "krb5_recvauth"); 522 523 ret = krb5_unparse_name (contextp, ticket->server, &server_name); 524 if (ret) 525 krb5_err (contextp, 1, ret, "krb5_unparse_name"); 526 527 if (strncmp (server_name, KADM5_ADMIN_SERVICE, 528 strlen(KADM5_ADMIN_SERVICE)) != 0) 529 krb5_errx (contextp, 1, "ticket for strange principal (%s)", 530 server_name); 531 532 free (server_name); 533 534 memset(&realm_params, 0, sizeof(realm_params)); 535 536 if(kadm_version == 1) { 537 krb5_data params; 538 ret = krb5_read_priv_message(contextp, ac, &fd, ¶ms); 539 if(ret) 540 krb5_err(contextp, 1, ret, "krb5_read_priv_message"); 541 ret = _kadm5_unmarshal_params(contextp, ¶ms, &realm_params); 542 if(ret) 543 krb5_err(contextp, 1, ret, "Could not read or parse kadm5 parameters"); 544 } 545 546 initial = ticket->ticket.flags.initial; 547 ret = krb5_unparse_name(contextp, ticket->client, &client); 548 if (ret) 549 krb5_err (contextp, 1, ret, "krb5_unparse_name"); 550 krb5_free_ticket (contextp, ticket); 551 ret = kadm5_s_init_with_password_ctx(contextp, 552 client, 553 NULL, 554 KADM5_ADMIN_SERVICE, 555 &realm_params, 556 0, 0, 557 &kadm_handlep); 558 if(ret) 559 krb5_err (contextp, 1, ret, "kadm5_init_with_password_ctx"); 560 v5_loop (contextp, ac, initial, kadm_handlep, fd); 561 } 562 563 krb5_error_code 564 kadmind_loop(krb5_context contextp, 565 krb5_keytab keytab, 566 krb5_socket_t sock) 567 { 568 u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4]; 569 ssize_t n; 570 unsigned long len; 571 572 n = krb5_net_read(contextp, &sock, buf, 4); 573 if(n == 0) 574 exit(0); 575 if(n < 0) 576 krb5_err(contextp, 1, errno, "read"); 577 _krb5_get_int(buf, &len, 4); 578 579 if (len == sizeof(KRB5_SENDAUTH_VERSION)) { 580 581 n = krb5_net_read(contextp, &sock, buf + 4, len); 582 if (n < 0) 583 krb5_err (contextp, 1, errno, "reading sendauth version"); 584 if (n == 0) 585 krb5_errx (contextp, 1, "EOF reading sendauth version"); 586 587 if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) { 588 handle_v5(contextp, keytab, sock); 589 return 0; 590 } 591 len += 4; 592 } else 593 len = 4; 594 595 handle_mit(contextp, buf, len, sock); 596 597 return 0; 598 } 599