1/* $NetBSD$ */ 2 3/* 4 * Copyright (c) 1997 - 2005 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "kadmin_locl.h" 37#include <krb5/krb5-private.h> 38 39static kadm5_ret_t 40kadmind_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 if (sp == NULL) 62 krb5_errx(context->context, 1, "out of memory"); 63 64 krb5_ret_int32(sp, &cmd); 65 switch(cmd){ 66 case kadm_get:{ 67 op = "GET"; 68 ret = krb5_ret_principal(sp, &princ); 69 if(ret) 70 goto fail; 71 ret = krb5_ret_int32(sp, &mask); 72 if(ret){ 73 krb5_free_principal(context->context, princ); 74 goto fail; 75 } 76 mask |= KADM5_PRINCIPAL; 77 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 78 krb5_warnx(context->context, "%s: %s %s", client, op, name); 79 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_GET, princ); 80 if(ret){ 81 krb5_free_principal(context->context, princ); 82 goto fail; 83 } 84 ret = kadm5_get_principal(kadm_handle, princ, &ent, mask); 85 krb5_storage_free(sp); 86 sp = krb5_storage_emem(); 87 krb5_store_int32(sp, ret); 88 if(ret == 0){ 89 kadm5_store_principal_ent(sp, &ent); 90 kadm5_free_principal_ent(kadm_handle, &ent); 91 } 92 krb5_free_principal(context->context, princ); 93 break; 94 } 95 case kadm_delete:{ 96 op = "DELETE"; 97 ret = krb5_ret_principal(sp, &princ); 98 if(ret) 99 goto fail; 100 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 101 krb5_warnx(context->context, "%s: %s %s", client, op, name); 102 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_DELETE, princ); 103 if(ret){ 104 krb5_free_principal(context->context, princ); 105 goto fail; 106 } 107 ret = kadm5_delete_principal(kadm_handle, princ); 108 krb5_free_principal(context->context, princ); 109 krb5_storage_free(sp); 110 sp = krb5_storage_emem(); 111 krb5_store_int32(sp, ret); 112 break; 113 } 114 case kadm_create:{ 115 op = "CREATE"; 116 ret = kadm5_ret_principal_ent(sp, &ent); 117 if(ret) 118 goto fail; 119 ret = krb5_ret_int32(sp, &mask); 120 if(ret){ 121 kadm5_free_principal_ent(context->context, &ent); 122 goto fail; 123 } 124 ret = krb5_ret_string(sp, &password); 125 if(ret){ 126 kadm5_free_principal_ent(context->context, &ent); 127 goto fail; 128 } 129 krb5_unparse_name_fixed(context->context, ent.principal, 130 name, sizeof(name)); 131 krb5_warnx(context->context, "%s: %s %s", client, op, name); 132 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_ADD, 133 ent.principal); 134 if(ret){ 135 kadm5_free_principal_ent(context->context, &ent); 136 memset(password, 0, strlen(password)); 137 free(password); 138 goto fail; 139 } 140 ret = kadm5_create_principal(kadm_handle, &ent, 141 mask, password); 142 kadm5_free_principal_ent(kadm_handle, &ent); 143 memset(password, 0, strlen(password)); 144 free(password); 145 krb5_storage_free(sp); 146 sp = krb5_storage_emem(); 147 krb5_store_int32(sp, ret); 148 break; 149 } 150 case kadm_modify:{ 151 op = "MODIFY"; 152 ret = kadm5_ret_principal_ent(sp, &ent); 153 if(ret) 154 goto fail; 155 ret = krb5_ret_int32(sp, &mask); 156 if(ret){ 157 kadm5_free_principal_ent(context, &ent); 158 goto fail; 159 } 160 krb5_unparse_name_fixed(context->context, ent.principal, 161 name, sizeof(name)); 162 krb5_warnx(context->context, "%s: %s %s", client, op, name); 163 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_MODIFY, 164 ent.principal); 165 if(ret){ 166 kadm5_free_principal_ent(context, &ent); 167 goto fail; 168 } 169 ret = kadm5_modify_principal(kadm_handle, &ent, mask); 170 kadm5_free_principal_ent(kadm_handle, &ent); 171 krb5_storage_free(sp); 172 sp = krb5_storage_emem(); 173 krb5_store_int32(sp, ret); 174 break; 175 } 176 case kadm_rename:{ 177 op = "RENAME"; 178 ret = krb5_ret_principal(sp, &princ); 179 if(ret) 180 goto fail; 181 ret = krb5_ret_principal(sp, &princ2); 182 if(ret){ 183 krb5_free_principal(context->context, princ); 184 goto fail; 185 } 186 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 187 krb5_unparse_name_fixed(context->context, princ2, name2, sizeof(name2)); 188 krb5_warnx(context->context, "%s: %s %s -> %s", 189 client, op, name, name2); 190 ret = _kadm5_acl_check_permission(context, 191 KADM5_PRIV_ADD, 192 princ2) 193 || _kadm5_acl_check_permission(context, 194 KADM5_PRIV_DELETE, 195 princ); 196 if(ret){ 197 krb5_free_principal(context->context, princ); 198 krb5_free_principal(context->context, princ2); 199 goto fail; 200 } 201 ret = kadm5_rename_principal(kadm_handle, princ, princ2); 202 krb5_free_principal(context->context, princ); 203 krb5_free_principal(context->context, princ2); 204 krb5_storage_free(sp); 205 sp = krb5_storage_emem(); 206 krb5_store_int32(sp, ret); 207 break; 208 } 209 case kadm_chpass:{ 210 op = "CHPASS"; 211 ret = krb5_ret_principal(sp, &princ); 212 if(ret) 213 goto fail; 214 ret = krb5_ret_string(sp, &password); 215 if(ret){ 216 krb5_free_principal(context->context, princ); 217 goto fail; 218 } 219 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 220 krb5_warnx(context->context, "%s: %s %s", client, op, name); 221 222 /* 223 * The change is allowed if at least one of: 224 225 * a) it's for the principal him/herself and this was an 226 * initial ticket, but then, check with the password quality 227 * function. 228 * b) the user is on the CPW ACL. 229 */ 230 231 if (initial 232 && krb5_principal_compare (context->context, context->caller, 233 princ)) 234 { 235 krb5_data pwd_data; 236 const char *pwd_reason; 237 238 pwd_data.data = password; 239 pwd_data.length = strlen(password); 240 241 pwd_reason = kadm5_check_password_quality (context->context, 242 princ, &pwd_data); 243 if (pwd_reason != NULL) 244 ret = KADM5_PASS_Q_DICT; 245 else 246 ret = 0; 247 } else 248 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 249 250 if(ret) { 251 krb5_free_principal(context->context, princ); 252 memset(password, 0, strlen(password)); 253 free(password); 254 goto fail; 255 } 256 ret = kadm5_chpass_principal(kadm_handle, princ, password); 257 krb5_free_principal(context->context, princ); 258 memset(password, 0, strlen(password)); 259 free(password); 260 krb5_storage_free(sp); 261 sp = krb5_storage_emem(); 262 krb5_store_int32(sp, ret); 263 break; 264 } 265 case kadm_chpass_with_key:{ 266 int i; 267 krb5_key_data *key_data; 268 int n_key_data; 269 270 op = "CHPASS_WITH_KEY"; 271 ret = krb5_ret_principal(sp, &princ); 272 if(ret) 273 goto fail; 274 ret = krb5_ret_int32(sp, &n_key_data); 275 if (ret) { 276 krb5_free_principal(context->context, princ); 277 goto fail; 278 } 279 /* n_key_data will be squeezed into an int16_t below. */ 280 if (n_key_data < 0 || n_key_data >= 1 << 16 || 281 n_key_data > UINT_MAX/sizeof(*key_data)) { 282 ret = ERANGE; 283 krb5_free_principal(context->context, princ); 284 goto fail; 285 } 286 287 key_data = malloc (n_key_data * sizeof(*key_data)); 288 if (key_data == NULL && n_key_data != 0) { 289 ret = ENOMEM; 290 krb5_free_principal(context->context, princ); 291 goto fail; 292 } 293 294 for (i = 0; i < n_key_data; ++i) { 295 ret = kadm5_ret_key_data (sp, &key_data[i]); 296 if (ret) { 297 int16_t dummy = i; 298 299 kadm5_free_key_data (context, &dummy, key_data); 300 free (key_data); 301 krb5_free_principal(context->context, princ); 302 goto fail; 303 } 304 } 305 306 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 307 krb5_warnx(context->context, "%s: %s %s", client, op, name); 308 309 /* 310 * The change is only allowed if the user is on the CPW ACL, 311 * this it to force password quality check on the user. 312 */ 313 314 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 315 if(ret) { 316 int16_t dummy = n_key_data; 317 318 kadm5_free_key_data (context, &dummy, key_data); 319 free (key_data); 320 krb5_free_principal(context->context, princ); 321 goto fail; 322 } 323 ret = kadm5_chpass_principal_with_key(kadm_handle, princ, 324 n_key_data, key_data); 325 { 326 int16_t dummy = n_key_data; 327 kadm5_free_key_data (context, &dummy, key_data); 328 } 329 free (key_data); 330 krb5_free_principal(context->context, princ); 331 krb5_storage_free(sp); 332 sp = krb5_storage_emem(); 333 krb5_store_int32(sp, ret); 334 break; 335 } 336 case kadm_randkey:{ 337 op = "RANDKEY"; 338 ret = krb5_ret_principal(sp, &princ); 339 if(ret) 340 goto fail; 341 krb5_unparse_name_fixed(context->context, princ, name, sizeof(name)); 342 krb5_warnx(context->context, "%s: %s %s", client, op, name); 343 /* 344 * The change is allowed if at least one of: 345 * a) it's for the principal him/herself and this was an initial ticket 346 * b) the user is on the CPW ACL. 347 */ 348 349 if (initial 350 && krb5_principal_compare (context->context, context->caller, 351 princ)) 352 ret = 0; 353 else 354 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ); 355 356 if(ret) { 357 krb5_free_principal(context->context, princ); 358 goto fail; 359 } 360 ret = kadm5_randkey_principal(kadm_handle, princ, 361 &new_keys, &n_keys); 362 krb5_free_principal(context->context, princ); 363 krb5_storage_free(sp); 364 sp = krb5_storage_emem(); 365 krb5_store_int32(sp, ret); 366 if(ret == 0){ 367 int i; 368 krb5_store_int32(sp, n_keys); 369 for(i = 0; i < n_keys; i++){ 370 krb5_store_keyblock(sp, new_keys[i]); 371 krb5_free_keyblock_contents(context->context, &new_keys[i]); 372 } 373 free(new_keys); 374 } 375 break; 376 } 377 case kadm_get_privs:{ 378 uint32_t privs; 379 ret = kadm5_get_privs(kadm_handle, &privs); 380 krb5_storage_free(sp); 381 sp = krb5_storage_emem(); 382 krb5_store_int32(sp, ret); 383 if(ret == 0) 384 krb5_store_uint32(sp, privs); 385 break; 386 } 387 case kadm_get_princs:{ 388 op = "LIST"; 389 ret = krb5_ret_int32(sp, &tmp); 390 if(ret) 391 goto fail; 392 if(tmp){ 393 ret = krb5_ret_string(sp, &expression); 394 if(ret) 395 goto fail; 396 }else 397 expression = NULL; 398 krb5_warnx(context->context, "%s: %s %s", client, op, 399 expression ? expression : "*"); 400 ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST, NULL); 401 if(ret){ 402 free(expression); 403 goto fail; 404 } 405 ret = kadm5_get_principals(kadm_handle, expression, &princs, &n_princs); 406 free(expression); 407 krb5_storage_free(sp); 408 sp = krb5_storage_emem(); 409 krb5_store_int32(sp, ret); 410 if(ret == 0){ 411 int i; 412 krb5_store_int32(sp, n_princs); 413 for(i = 0; i < n_princs; i++) 414 krb5_store_string(sp, princs[i]); 415 kadm5_free_name_list(kadm_handle, princs, &n_princs); 416 } 417 break; 418 } 419 default: 420 krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd); 421 krb5_storage_free(sp); 422 sp = krb5_storage_emem(); 423 krb5_store_int32(sp, KADM5_FAILURE); 424 break; 425 } 426 krb5_storage_to_data(sp, out); 427 krb5_storage_free(sp); 428 return 0; 429fail: 430 krb5_warn(context->context, ret, "%s", op); 431 krb5_storage_seek(sp, 0, SEEK_SET); 432 krb5_store_int32(sp, ret); 433 krb5_storage_to_data(sp, out); 434 krb5_storage_free(sp); 435 return 0; 436} 437 438static void 439v5_loop (krb5_context context, 440 krb5_auth_context ac, 441 krb5_boolean initial, 442 void *kadm_handle, 443 krb5_socket_t fd) 444{ 445 krb5_error_code ret; 446 krb5_data in, out; 447 448 for (;;) { 449 doing_useful_work = 0; 450 if(term_flag) 451 exit(0); 452 ret = krb5_read_priv_message(context, ac, &fd, &in); 453 if(ret == HEIM_ERR_EOF) 454 exit(0); 455 if(ret) 456 krb5_err(context, 1, ret, "krb5_read_priv_message"); 457 doing_useful_work = 1; 458 kadmind_dispatch(kadm_handle, initial, &in, &out); 459 krb5_data_free(&in); 460 ret = krb5_write_priv_message(context, ac, &fd, &out); 461 if(ret) 462 krb5_err(context, 1, ret, "krb5_write_priv_message"); 463 } 464} 465 466static krb5_boolean 467match_appl_version(const void *data, const char *appl_version) 468{ 469 unsigned minor; 470 if(sscanf(appl_version, "KADM0.%u", &minor) != 1) 471 return 0; 472 *(unsigned*)data = minor; 473 return 1; 474} 475 476static void 477handle_v5(krb5_context context, 478 krb5_keytab keytab, 479 krb5_socket_t fd) 480{ 481 krb5_error_code ret; 482 krb5_ticket *ticket; 483 char *server_name; 484 char *client; 485 void *kadm_handle; 486 krb5_boolean initial; 487 krb5_auth_context ac = NULL; 488 489 unsigned kadm_version; 490 kadm5_config_params realm_params; 491 492 ret = krb5_recvauth_match_version(context, &ac, &fd, 493 match_appl_version, &kadm_version, 494 NULL, KRB5_RECVAUTH_IGNORE_VERSION, 495 keytab, &ticket); 496 if(ret == KRB5_KT_NOTFOUND) 497 krb5_errx(context, 1, "krb5_recvauth: key not found"); 498 if(ret) 499 krb5_err(context, 1, ret, "krb5_recvauth"); 500 501 ret = krb5_unparse_name (context, ticket->server, &server_name); 502 if (ret) 503 krb5_err (context, 1, ret, "krb5_unparse_name"); 504 505 if (strncmp (server_name, KADM5_ADMIN_SERVICE, 506 strlen(KADM5_ADMIN_SERVICE)) != 0) 507 krb5_errx (context, 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(context, ac, &fd, ¶ms); 517 if(ret) 518 krb5_err(context, 1, ret, "krb5_read_priv_message"); 519 _kadm5_unmarshal_params(context, ¶ms, &realm_params); 520 } 521 522 initial = ticket->ticket.flags.initial; 523 ret = krb5_unparse_name(context, ticket->client, &client); 524 if (ret) 525 krb5_err (context, 1, ret, "krb5_unparse_name"); 526 krb5_free_ticket (context, ticket); 527 ret = kadm5_s_init_with_password_ctx(context, 528 client, 529 NULL, 530 KADM5_ADMIN_SERVICE, 531 &realm_params, 532 0, 0, 533 &kadm_handle); 534 if(ret) 535 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx"); 536 v5_loop (context, ac, initial, kadm_handle, fd); 537} 538 539krb5_error_code 540kadmind_loop(krb5_context context, 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(context, &sock, buf, 4); 549 if(n == 0) 550 exit(0); 551 if(n < 0) 552 krb5_err(context, 1, errno, "read"); 553 _krb5_get_int(buf, &len, 4); 554 555 if (len == sizeof(KRB5_SENDAUTH_VERSION)) { 556 557 n = krb5_net_read(context, &sock, buf + 4, len); 558 if (n < 0) 559 krb5_err (context, 1, errno, "reading sendauth version"); 560 if (n == 0) 561 krb5_errx (context, 1, "EOF reading sendauth version"); 562 563 if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) { 564 handle_v5(context, keytab, sock); 565 return 0; 566 } 567 len += 4; 568 } else 569 len = 4; 570 571 handle_mit(context, buf, len, sock); 572 573 return 0; 574} 575