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