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