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