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