1 /* 2 * Copyright (c) 1997 - 2003 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,v 1.38 2003/01/29 12:33:05 lha Exp $"); 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, *exp; 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 goto fail; 196 } 197 ret = kadm5_rename_principal(kadm_handle, princ, princ2); 198 krb5_free_principal(context->context, princ); 199 krb5_free_principal(context->context, princ2); 200 krb5_storage_free(sp); 201 sp = krb5_storage_emem(); 202 krb5_store_int32(sp, ret); 203 break; 204 } 205 case kadm_chpass:{ 206 op = "CHPASS"; 207 ret = krb5_ret_principal(sp, &princ); 208 if(ret) 209 goto fail; 210 ret = krb5_ret_string(sp, &password); 211 if(ret){ 212 krb5_free_principal(context->context, princ); 213 goto fail; 214 } 215 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 216 krb5_warnx(context->context, "%s: %s %s", client, op, name); 217 218 /* 219 * The change is allowed if at least one of: 220 221 * a) it's for the principal him/herself and this was an 222 * initial ticket, but then, check with the password quality 223 * function. 224 * b) the user is on the CPW ACL. 225 */ 226 227 if (initial 228 && krb5_principal_compare (context->context, context->caller, 229 princ)) 230 { 231 krb5_data pwd_data; 232 const char *pwd_reason; 233 234 pwd_data.data = password; 235 pwd_data.length = strlen(password); 236 237 pwd_reason = kadm5_check_password_quality (context->context, 238 princ, &pwd_data); 239 if (pwd_reason != NULL) 240 ret = KADM5_PASS_Q_DICT; 241 else 242 ret = 0; 243 } else 244 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 245 246 if(ret) { 247 krb5_free_principal(context->context, princ); 248 memset(password, 0, strlen(password)); 249 free(password); 250 goto fail; 251 } 252 ret = kadm5_chpass_principal(kadm_handle, princ, password); 253 krb5_free_principal(context->context, princ); 254 memset(password, 0, strlen(password)); 255 free(password); 256 krb5_storage_free(sp); 257 sp = krb5_storage_emem(); 258 krb5_store_int32(sp, ret); 259 break; 260 } 261 case kadm_chpass_with_key:{ 262 int i; 263 krb5_key_data *key_data; 264 int n_key_data; 265 266 op = "CHPASS_WITH_KEY"; 267 ret = krb5_ret_principal(sp, &princ); 268 if(ret) 269 goto fail; 270 ret = krb5_ret_int32(sp, &n_key_data); 271 if (ret) { 272 krb5_free_principal(context->context, princ); 273 goto fail; 274 } 275 /* n_key_data will be squeezed into an int16_t below. */ 276 if (n_key_data < 0 || n_key_data >= 1 << 16 || 277 n_key_data > UINT_MAX/sizeof(*key_data)) { 278 ret = ERANGE; 279 krb5_free_principal(context->context, princ); 280 goto fail; 281 } 282 283 key_data = malloc (n_key_data * sizeof(*key_data)); 284 if (key_data == NULL) { 285 ret = ENOMEM; 286 krb5_free_principal(context->context, princ); 287 goto fail; 288 } 289 290 for (i = 0; i < n_key_data; ++i) { 291 ret = kadm5_ret_key_data (sp, &key_data[i]); 292 if (ret) { 293 int16_t dummy = i; 294 295 kadm5_free_key_data (context, &dummy, key_data); 296 free (key_data); 297 krb5_free_principal(context->context, princ); 298 goto fail; 299 } 300 } 301 302 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 303 krb5_warnx(context->context, "%s: %s %s", client, op, name); 304 305 /* 306 * The change is only allowed if the user is on the CPW ACL, 307 * this it to force password quality check on the user. 308 */ 309 310 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 311 if(ret) { 312 int16_t dummy = n_key_data; 313 314 kadm5_free_key_data (context, &dummy, key_data); 315 free (key_data); 316 krb5_free_principal(context->context, princ); 317 goto fail; 318 } 319 ret = kadm5_chpass_principal_with_key(kadm_handle, princ, 320 n_key_data, key_data); 321 { 322 int16_t dummy = n_key_data; 323 kadm5_free_key_data (context, &dummy, key_data); 324 } 325 free (key_data); 326 krb5_free_principal(context->context, princ); 327 krb5_storage_free(sp); 328 sp = krb5_storage_emem(); 329 krb5_store_int32(sp, ret); 330 break; 331 } 332 case kadm_randkey:{ 333 op = "RANDKEY"; 334 ret = krb5_ret_principal(sp, &princ); 335 if(ret) 336 goto fail; 337 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 338 krb5_warnx(context->context, "%s: %s %s", client, op, name); 339 /* 340 * The change is allowed if at least one of: 341 * a) it's for the principal him/herself and this was an initial ticket 342 * b) the user is on the CPW ACL. 343 */ 344 345 if (initial 346 && krb5_principal_compare (context->context, context->caller, 347 princ)) 348 ret = 0; 349 else 350 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 351 352 if(ret) { 353 krb5_free_principal(context->context, princ); 354 goto fail; 355 } 356 ret = kadm5_randkey_principal(kadm_handle, princ, 357 &new_keys, &n_keys); 358 krb5_free_principal(context->context, princ); 359 krb5_storage_free(sp); 360 sp = krb5_storage_emem(); 361 krb5_store_int32(sp, ret); 362 if(ret == 0){ 363 int i; 364 krb5_store_int32(sp, n_keys); 365 for(i = 0; i < n_keys; i++){ 366 krb5_store_keyblock(sp, new_keys[i]); 367 krb5_free_keyblock_contents(context->context, &new_keys[i]); 368 } 369 } 370 break; 371 } 372 case kadm_get_privs:{ 373 ret = kadm5_get_privs(kadm_handle, &mask); 374 krb5_storage_free(sp); 375 sp = krb5_storage_emem(); 376 krb5_store_int32(sp, ret); 377 if(ret == 0) 378 krb5_store_int32(sp, mask); 379 break; 380 } 381 case kadm_get_princs:{ 382 op = "LIST"; 383 ret = krb5_ret_int32(sp, &tmp); 384 if(ret) 385 goto fail; 386 if(tmp){ 387 ret = krb5_ret_string(sp, &exp); 388 if(ret) 389 goto fail; 390 }else 391 exp = NULL; 392 krb5_warnx(context->context, "%s: %s %s", client, op, exp ? exp : "*"); 393 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST, NULL); 394 if(ret){ 395 free(exp); 396 goto fail; 397 } 398 ret = kadm5_get_principals(kadm_handle, exp, &princs, &n_princs); 399 free(exp); 400 krb5_storage_free(sp); 401 sp = krb5_storage_emem(); 402 krb5_store_int32(sp, ret); 403 if(ret == 0){ 404 int i; 405 krb5_store_int32(sp, n_princs); 406 for(i = 0; i < n_princs; i++) 407 krb5_store_string(sp, princs[i]); 408 kadm5_free_name_list(kadm_handle, princs, &n_princs); 409 } 410 break; 411 } 412 default: 413 krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd); 414 krb5_storage_free(sp); 415 sp = krb5_storage_emem(); 416 krb5_store_int32(sp, KADM5_FAILURE); 417 break; 418 } 419 krb5_storage_to_data(sp, out); 420 krb5_storage_free(sp); 421 return 0; 422 fail: 423 krb5_warn(context->context, ret, "%s", op); 424 krb5_storage_seek(sp, 0, SEEK_SET); 425 krb5_store_int32(sp, ret); 426 krb5_storage_to_data(sp, out); 427 krb5_storage_free(sp); 428 return 0; 429 } 430 431 static void 432 v5_loop (krb5_context context, 433 krb5_auth_context ac, 434 krb5_boolean initial, 435 void *kadm_handle, 436 int fd) 437 { 438 krb5_error_code ret; 439 krb5_data in, out; 440 441 for (;;) { 442 doing_useful_work = 0; 443 if(term_flag) 444 exit(0); 445 ret = krb5_read_priv_message(context, ac, &fd, &in); 446 if(ret == HEIM_ERR_EOF) 447 exit(0); 448 if(ret) 449 krb5_err(context, 1, ret, "krb5_read_priv_message"); 450 doing_useful_work = 1; 451 kadmind_dispatch(kadm_handle, initial, &in, &out); 452 krb5_data_free(&in); 453 ret = krb5_write_priv_message(context, ac, &fd, &out); 454 if(ret) 455 krb5_err(context, 1, ret, "krb5_write_priv_message"); 456 } 457 } 458 459 static krb5_boolean 460 match_appl_version(const void *data, const char *appl_version) 461 { 462 unsigned minor; 463 if(sscanf(appl_version, "KADM0.%u", &minor) != 1) 464 return 0; 465 *(unsigned*)data = minor; 466 return 1; 467 } 468 469 static void 470 handle_v5(krb5_context context, 471 krb5_auth_context ac, 472 krb5_keytab keytab, 473 int len, 474 int fd) 475 { 476 krb5_error_code ret; 477 u_char version[sizeof(KRB5_SENDAUTH_VERSION)]; 478 krb5_ticket *ticket; 479 char *server_name; 480 char *client; 481 void *kadm_handle; 482 ssize_t n; 483 krb5_boolean initial; 484 485 unsigned kadm_version; 486 kadm5_config_params realm_params; 487 488 if (len != sizeof(KRB5_SENDAUTH_VERSION)) 489 krb5_errx(context, 1, "bad sendauth len %d", len); 490 n = krb5_net_read(context, &fd, version, len); 491 if (n < 0) 492 krb5_err (context, 1, errno, "reading sendauth version"); 493 if (n == 0) 494 krb5_errx (context, 1, "EOF reading sendauth version"); 495 if(memcmp(version, KRB5_SENDAUTH_VERSION, len) != 0) 496 krb5_errx(context, 1, "bad sendauth version %.8s", version); 497 498 ret = krb5_recvauth_match_version(context, &ac, &fd, 499 match_appl_version, &kadm_version, 500 NULL, KRB5_RECVAUTH_IGNORE_VERSION, 501 keytab, &ticket); 502 if(ret == KRB5_KT_NOTFOUND) 503 krb5_errx(context, 1, "krb5_recvauth: key not found"); 504 if(ret) 505 krb5_err(context, 1, ret, "krb5_recvauth"); 506 507 ret = krb5_unparse_name (context, ticket->server, &server_name); 508 if (ret) 509 krb5_err (context, 1, ret, "krb5_unparse_name"); 510 511 if (strncmp (server_name, KADM5_ADMIN_SERVICE, 512 strlen(KADM5_ADMIN_SERVICE)) != 0) 513 krb5_errx (context, 1, "ticket for strange principal (%s)", 514 server_name); 515 516 free (server_name); 517 518 memset(&realm_params, 0, sizeof(realm_params)); 519 520 if(kadm_version == 1) { 521 krb5_data params; 522 ret = krb5_read_priv_message(context, ac, &fd, ¶ms); 523 if(ret) 524 krb5_err(context, 1, ret, "krb5_read_priv_message"); 525 _kadm5_unmarshal_params(context, ¶ms, &realm_params); 526 } 527 528 initial = ticket->ticket.flags.initial; 529 ret = krb5_unparse_name(context, ticket->client, &client); 530 if (ret) 531 krb5_err (context, 1, ret, "krb5_unparse_name"); 532 krb5_free_ticket (context, ticket); 533 ret = kadm5_init_with_password_ctx(context, 534 client, 535 NULL, 536 KADM5_ADMIN_SERVICE, 537 &realm_params, 538 0, 0, 539 &kadm_handle); 540 if(ret) 541 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 542 v5_loop (context, ac, initial, kadm_handle, fd); 543 } 544 545 extern int do_kerberos4; 546 547 krb5_error_code 548 kadmind_loop(krb5_context context, 549 krb5_auth_context ac, 550 krb5_keytab keytab, 551 int fd) 552 { 553 unsigned char tmp[4]; 554 ssize_t n; 555 unsigned long len; 556 557 n = krb5_net_read(context, &fd, tmp, 4); 558 if(n == 0) 559 exit(0); 560 if(n < 0) 561 krb5_err(context, 1, errno, "read"); 562 _krb5_get_int(tmp, &len, 4); 563 if(len > 0xffff && (len & 0xffff) == ('K' << 8) + 'A') { 564 len >>= 16; 565 #ifdef KRB4 566 if(do_kerberos4) 567 handle_v4(context, keytab, len, fd); 568 else 569 krb5_errx(context, 1, "version 4 kadmin is disabled"); 570 #else 571 krb5_errx(context, 1, "packet appears to be version 4"); 572 #endif 573 } else { 574 handle_v5(context, ac, keytab, len, fd); 575 } 576 return 0; 577 } 578