diff options
Diffstat (limited to 'kadmin/server.c')
| -rw-r--r-- | kadmin/server.c | 813 |
1 files changed, 571 insertions, 242 deletions
diff --git a/kadmin/server.c b/kadmin/server.c index ccb6a7a991db..281822a30fc0 100644 --- a/kadmin/server.c +++ b/kadmin/server.c @@ -38,48 +38,231 @@ static kadm5_ret_t check_aliases(kadm5_server_context *, kadm5_principal_ent_rec *, kadm5_principal_ent_rec *); +/* + * All the iter_cb stuff is about online listing of principals via + * kadm5_iter_principals(). Search for "LIST" to see more commentary. + */ +struct iter_cb_data { + krb5_context context; + krb5_auth_context ac; + krb5_storage *rsp; + kadm5_ret_t ret; + size_t n; + size_t i; + int fd; + unsigned int initial:1; + unsigned int stop:1; +}; + +/* + * This function sends the current chunk of principal listing and checks if the + * client requested that the listing stop. + */ +static int +iter_cb_send_now(struct iter_cb_data *d) +{ + struct timeval tv; + krb5_data out; + + krb5_data_zero(&out); + + if (!d->stop) { + fd_set fds; + int nfds; + + /* + * The client can send us one message to interrupt the iteration. + * + * TODO: Maybe we should have the client send a message every N chunks + * so we can clock the listing and have a chance to receive any + * interrupt message from the client? + */ + FD_ZERO(&fds); + FD_SET(d->fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + nfds = select(d->fd + 1, &fds, NULL, NULL, &tv); + if (nfds == -1) { + d->ret = errno; + } else if (nfds > 0) { + /* + * And it did. We'll throw this message away. It should be a NOP + * call, which we'd throw away anyways. If the client's stop + * message arrives after we're done anyways, well, it will be + * processed as a NOP and thrown away. + */ + d->stop = 1; + d->ret = krb5_read_priv_message(d->context, d->ac, &d->fd, &out); + krb5_data_free(&out); + if (d->ret == HEIM_ERR_EOF) + exit(0); + } + } + d->i = 0; + d->ret = krb5_storage_to_data(d->rsp, &out); + if (d->ret == 0) + d->ret = krb5_write_priv_message(d->context, d->ac, &d->fd, &out); + krb5_data_free(&out); + krb5_storage_free(d->rsp); + if ((d->rsp = krb5_storage_emem()) == NULL) + return krb5_enomem(d->context); + return d->ret; +} + +static int +iter_cb(void *cbdata, const char *p) +{ + struct iter_cb_data *d = cbdata; + krb5_error_code ret = 0; + size_t n = d->n; + + /* Convince the compiler that `-(int)d->n' is defined */ + if (n == 0 || n > INT_MAX) + return ERANGE; + if (d->rsp == NULL && (d->rsp = krb5_storage_emem()) == NULL) + return krb5_enomem(d->context); + if (d->i == 0) { + /* Every chunk starts with a result code */ + ret = krb5_store_int32(d->rsp, d->ret); + if (ret) + return ret; + if (d->ret) + return ret; + } + if (d->initial) { + /* + * We'll send up to `d->n' entries per-write. We send a negative + * number to indicate we accepted the client's proposal that we speak + * the online LIST protocol. + * + * Note that if we're here then we've already placed a result code in + * this reply (see above). + */ + d->initial = 0; + ret = krb5_store_int32(d->rsp, -(int)n); /* Princs per-chunk */ + if (ret == 0) + ret = iter_cb_send_now(d); + if (ret) + return ret; + /* + * Now that we've sent the acceptance reply, put a result code as the + * first thing in the next reply, which will have the first chunk of + * the listing. + */ + ret = krb5_store_int32(d->rsp, d->ret); + if (ret) + return ret; + if (d->ret) + return ret; + } + + if (p) { + ret = krb5_store_string(d->rsp, p); + d->i++; + } else { + /* + * We get called with `p == NULL' when the listing is done. This + * forces us to iter_cb_send_now(d) below, but also forces us to have a + * properly formed reply (i.e., that we have a result code as the first + * item), even if the chunk is otherwise empty (`d->i == 0'). + */ + d->i = n; + } + + if (ret == 0 && d->i == n) + ret = iter_cb_send_now(d); /* Chunk finished; send it */ + if (d->stop) + return EINTR; + return ret; +} + static kadm5_ret_t kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, - krb5_data *in, krb5_data *out) + krb5_data *in, krb5_auth_context ac, int fd, + krb5_data *out, int readonly) { - kadm5_ret_t ret; - int32_t cmd, mask, tmp; + kadm5_ret_t ret = 0; + kadm5_ret_t ret_sp = 0; + int32_t cmd, mask, kvno, tmp; kadm5_server_context *contextp = kadm_handlep; char client[128], name[128], name2[128]; const char *op = ""; - krb5_principal princ, princ2; + krb5_principal princ = NULL, princ2 = NULL; kadm5_principal_ent_rec ent, ent_prev; char *password = NULL, *expression; krb5_keyblock *new_keys; krb5_key_salt_tuple *ks_tuple = NULL; - krb5_boolean keepold = FALSE; + int keepold = FALSE; int n_ks_tuple = 0; int n_keys; char **princs; int n_princs; int keys_ok = 0; + krb5_storage *rsp; /* response goes here */ krb5_storage *sp; int len; - krb5_unparse_name_fixed(contextp->context, contextp->caller, - client, sizeof(client)); + memset(&ent, 0, sizeof(ent)); + memset(&ent_prev, 0, sizeof(ent_prev)); + krb5_data_zero(out); + + rsp = krb5_storage_emem(); + if (rsp == NULL) + return krb5_enomem(contextp->context); sp = krb5_storage_from_data(in); - if (sp == NULL) - krb5_errx(contextp->context, 1, "out of memory"); + if (sp == NULL) { + krb5_storage_free(rsp); + return krb5_enomem(contextp->context); + } + + ret = krb5_unparse_name_fixed(contextp->context, contextp->caller, + client, sizeof(client)); + if (ret == 0) + ret = krb5_ret_int32(sp, &cmd); + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + goto fail; + } - krb5_ret_int32(sp, &cmd); switch(cmd){ + case kadm_nop:{ + /* + * In the future we could use this for versioning. + * + * We used to respond to NOPs with KADM5_FAILURE. Now we respond with + * zero. In the future we could send back a protocol version number + * and use NOPs for protocol version negotiation. + * + * In the meantime, this gets called only if a client wants to + * interrupt a long-running LIST operation. + */ + op = "NOP"; + ret = krb5_ret_int32(sp, &tmp); + if (ret == 0 && tmp == 0) { + /* + * Reply not wanted. This would be a LIST interrupt request. + */ + krb5_storage_free(rsp); + krb5_storage_free(sp); + return 0; + } + ret_sp = krb5_store_int32(rsp, ret = 0); + break; + } case kadm_get:{ op = "GET"; ret = krb5_ret_principal(sp, &princ); - if(ret) + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_UNK_PRINC); goto fail; + } ret = krb5_ret_int32(sp, &mask); - if(ret){ - krb5_free_principal(contextp->context, princ); + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - } + } + mask |= KADM5_PRINCIPAL; krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); krb5_warnx(contextp->context, "%s: %s %s", client, op, name); @@ -87,7 +270,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, /* If the caller doesn't have KADM5_PRIV_GET, we're done. */ ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ); if (ret) { - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, ret); goto fail; } @@ -117,62 +300,66 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, * modes request other things too, so in all likelihood this * heuristic will not hurt any kadmin get uses. */ - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, ret); goto fail; } } ret = kadm5_get_principal(kadm_handlep, princ, &ent, mask); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); - if (ret == 0){ - if (keys_ok) - kadm5_store_principal_ent(sp, &ent); - else - kadm5_store_principal_ent_nokeys(sp, &ent); - kadm5_free_principal_ent(kadm_handlep, &ent); + ret_sp = krb5_store_int32(rsp, ret); + if (ret == 0) { + if (ret_sp == 0 && keys_ok) + ret_sp = kadm5_store_principal_ent(rsp, &ent); + else if (ret_sp == 0) + ret_sp = kadm5_store_principal_ent_nokeys(rsp, &ent); } - krb5_free_principal(contextp->context, princ); + kadm5_free_principal_ent(kadm_handlep, &ent); break; } case kadm_delete:{ op = "DELETE"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = krb5_ret_principal(sp, &princ); - if(ret) - goto fail; - krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); - krb5_warnx(contextp->context, "%s: %s %s", client, op, name); - ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ); - if(ret){ - krb5_free_principal(contextp->context, princ); - goto fail; - } + if (ret == 0) + ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); + if (ret == 0) { + ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ); + krb5_warnx(contextp->context, "%s: %s %s (%s)", client, op, name, + ret == 0 ? "granted" : "denied"); + } /* * There's no need to check that the caller has permission to * delete the victim principal's aliases. */ - - ret = kadm5_delete_principal(kadm_handlep, princ); - krb5_free_principal(contextp->context, princ); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + if (ret == 0) + ret = kadm5_delete_principal(kadm_handlep, princ); + ret_sp = krb5_store_int32(rsp, ret); break; } case kadm_create:{ op = "CREATE"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = kadm5_ret_principal_ent(sp, &ent); - if(ret) + if(ret) { + ret_sp = krb5_store_int32(rsp, ret); goto fail; + } ret = krb5_ret_int32(sp, &mask); if(ret){ + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); kadm5_free_principal_ent(kadm_handlep, &ent); goto fail; } ret = krb5_ret_string(sp, &password); if(ret){ + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); kadm5_free_principal_ent(kadm_handlep, &ent); goto fail; } @@ -182,6 +369,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD, ent.principal); if(ret){ + ret_sp = krb5_store_int32(rsp, ret); kadm5_free_principal_ent(kadm_handlep, &ent); goto fail; } @@ -192,6 +380,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, */ ret = check_aliases(contextp, &ent, NULL); if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL); kadm5_free_principal_ent(kadm_handlep, &ent); goto fail; } @@ -199,18 +388,23 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, ret = kadm5_create_principal(kadm_handlep, &ent, mask, password); kadm5_free_principal_ent(kadm_handlep, &ent); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + ret_sp = krb5_store_int32(rsp, ret); break; } case kadm_modify:{ op = "MODIFY"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = kadm5_ret_principal_ent(sp, &ent); - if(ret) + if(ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; + } ret = krb5_ret_int32(sp, &mask); if(ret){ + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); kadm5_free_principal_ent(contextp, &ent); goto fail; } @@ -220,6 +414,7 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY, ent.principal); if(ret){ + ret_sp = krb5_store_int32(rsp, ret); kadm5_free_principal_ent(contextp, &ent); goto fail; } @@ -232,33 +427,64 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, */ ret = kadm5_get_principal(kadm_handlep, ent.principal, &ent_prev, mask); if (ret) { + ret_sp = krb5_store_int32(rsp, ret); kadm5_free_principal_ent(contextp, &ent); goto fail; } ret = check_aliases(contextp, &ent, &ent_prev); kadm5_free_principal_ent(contextp, &ent_prev); if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL); kadm5_free_principal_ent(contextp, &ent); goto fail; } } ret = kadm5_modify_principal(kadm_handlep, &ent, mask); kadm5_free_principal_ent(kadm_handlep, &ent); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + ret_sp = krb5_store_int32(rsp, ret); break; } + case kadm_prune:{ + op = "PRUNE"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } + ret = krb5_ret_principal(sp, &princ); + if (ret == 0) + ret = krb5_ret_int32(sp, &kvno); + if (ret == HEIM_ERR_EOF) { + kvno = 0; + } else if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + goto fail; + } + krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); + krb5_warnx(contextp->context, "%s: %s %s", client, op, name); + ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); + if (ret) { + ret_sp = krb5_store_int32(rsp, ret); + goto fail; + } + + ret = kadm5_prune_principal(kadm_handlep, princ, kvno); + ret_sp = krb5_store_int32(rsp, ret); + break; + } case kadm_rename:{ op = "RENAME"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = krb5_ret_principal(sp, &princ); - if(ret) - goto fail; - ret = krb5_ret_principal(sp, &princ2); - if(ret){ - krb5_free_principal(contextp->context, princ); + if (ret == 0) + ret = krb5_ret_principal(sp, &princ2); + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - } + } + krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); krb5_unparse_name_fixed(contextp->context, princ2, name2, sizeof(name2)); @@ -282,78 +508,61 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, princ); } } - if(ret){ - krb5_free_principal(contextp->context, princ); - krb5_free_principal(contextp->context, princ2); + if (ret) { + ret_sp = krb5_store_int32(rsp, ret); goto fail; - } + } + ret = kadm5_rename_principal(kadm_handlep, princ, princ2); - krb5_free_principal(contextp->context, princ); - krb5_free_principal(contextp->context, princ2); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + ret_sp = krb5_store_int32(rsp, ret); break; } case kadm_chpass:{ + krb5_boolean is_self_cpw, allow_self_cpw; + op = "CHPASS"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = krb5_ret_principal(sp, &princ); - if (ret) - goto fail; - ret = krb5_ret_string(sp, &password); + if (ret == 0) + ret = krb5_ret_string(sp, &password); + if (ret == 0) + ret = krb5_ret_int32(sp, &keepold); + if (ret == HEIM_ERR_EOF) + ret = 0; + if (ret == 0) { + ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); + if (ret == 0) + krb5_warnx(contextp->context, "%s: %s %s", client, op, name); + } if (ret) { - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - } - ret = krb5_ret_int32(sp, &keepold); - if (ret && ret != HEIM_ERR_EOF) { - krb5_free_principal(contextp->context, princ); - goto fail; - } - krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); - krb5_warnx(contextp->context, "%s: %s %s", client, op, name); + } /* - * The change is allowed if at least one of: - * - * a) allowed by sysadmin - * b) it's for the principal him/herself and this was an - * initial ticket, but then, check with the password quality - * function. - * c) the user is on the CPW ACL. + * Change password requests are subject to ACLs unless the principal is + * changing their own password and the initial ticket flag is set, and + * the allow_self_change_password configuration option is TRUE. */ - - if (krb5_config_get_bool_default(contextp->context, NULL, TRUE, - "kadmin", "allow_self_change_password", NULL) - && initial - && krb5_principal_compare (contextp->context, contextp->caller, - princ)) - { - krb5_data pwd_data; - const char *pwd_reason; - - pwd_data.data = password; - pwd_data.length = strlen(password); - - pwd_reason = kadm5_check_password_quality (contextp->context, - princ, &pwd_data); - if (pwd_reason != NULL) - ret = KADM5_PASS_Q_DICT; - else - ret = 0; - } else + is_self_cpw = + krb5_principal_compare(contextp->context, contextp->caller, princ); + allow_self_cpw = + krb5_config_get_bool_default(contextp->context, NULL, TRUE, + "kadmin", "allow_self_change_password", NULL); + if (!(is_self_cpw && initial && allow_self_cpw)) { ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); - - if(ret) { - krb5_free_principal(contextp->context, princ); - goto fail; + if (ret) { + ret_sp = krb5_store_int32(rsp, ret); + goto fail; + } } + ret = kadm5_chpass_principal_3(kadm_handlep, princ, keepold, 0, NULL, password); - krb5_free_principal(contextp->context, princ); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + ret_sp = krb5_store_int32(rsp, ret); break; } case kadm_chpass_with_key:{ @@ -362,31 +571,35 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, int n_key_data; op = "CHPASS_WITH_KEY"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = krb5_ret_principal(sp, &princ); - if(ret) - goto fail; - ret = krb5_ret_int32(sp, &n_key_data); - if (ret) { - krb5_free_principal(contextp->context, princ); - goto fail; + if (ret == 0) + ret = krb5_ret_int32(sp, &n_key_data); + if (ret == 0) { + ret = krb5_ret_int32(sp, &keepold); + if (ret == HEIM_ERR_EOF) + ret = 0; } - ret = krb5_ret_int32(sp, &keepold); - if (ret && ret != HEIM_ERR_EOF) { - krb5_free_principal(contextp->context, princ); + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - } + } + /* n_key_data will be squeezed into an int16_t below. */ if (n_key_data < 0 || n_key_data >= 1 << 16 || (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); ret = ERANGE; - krb5_free_principal(contextp->context, princ); goto fail; } key_data = malloc (n_key_data * sizeof(*key_data)); if (key_data == NULL && n_key_data != 0) { - ret = ENOMEM; - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + ret = krb5_enomem(contextp->context); goto fail; } @@ -397,26 +610,27 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, kadm5_free_key_data (contextp, &dummy, key_data); free (key_data); - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; } } - krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); - krb5_warnx(contextp->context, "%s: %s %s", client, op, name); - /* * The change is only allowed if the user is on the CPW ACL, * this it to force password quality check on the user. */ ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); + ret_sp = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); + if (ret_sp == 0) + krb5_warnx(contextp->context, "%s: %s %s (%s)", client, op, name, + ret ? "denied" : "granted"); if(ret) { int16_t dummy = n_key_data; kadm5_free_key_data (contextp, &dummy, key_data); free (key_data); - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; } ret = kadm5_chpass_principal_with_key_3(kadm_handlep, princ, keepold, @@ -426,17 +640,22 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, kadm5_free_key_data (contextp, &dummy, key_data); } free (key_data); - krb5_free_principal(contextp->context, princ); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); + ret_sp = krb5_store_int32(rsp, ret); break; } case kadm_randkey:{ + size_t i; + op = "RANDKEY"; + if (readonly) { + ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY); + goto fail; + } ret = krb5_ret_principal(sp, &princ); - if(ret) + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; + } krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name)); krb5_warnx(contextp->context, "%s: %s %s", client, op, name); /* @@ -452,10 +671,10 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, else ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ); - if(ret) { - krb5_free_principal(contextp->context, princ); + if (ret) { + ret_sp = krb5_store_int32(rsp, ret); goto fail; - } + } /* * See comments in kadm5_c_randkey_principal() regarding the @@ -463,59 +682,68 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, */ ret = krb5_ret_int32(sp, &keepold); if (ret != 0 && ret != HEIM_ERR_EOF) { - krb5_free_principal(contextp->context, princ); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - } + } ret = krb5_ret_int32(sp, &n_ks_tuple); - if (ret != 0 && ret != HEIM_ERR_EOF) { - krb5_free_principal(contextp->context, princ); + if (ret == HEIM_ERR_EOF) { + const char *enctypes; + size_t n; + + enctypes = krb5_config_get_string(contextp->context, NULL, + "realms", + krb5_principal_get_realm(contextp->context, + princ), + "supported_enctypes", NULL); + if (enctypes == NULL || enctypes[0] == '\0') + enctypes = "aes128-cts-hmac-sha1-96"; + ret = krb5_string_to_keysalts2(contextp->context, enctypes, + &n, &ks_tuple); + n_ks_tuple = n; + } + if (ret != 0) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */ goto fail; - } else if (ret == 0) { - size_t i; - - if (n_ks_tuple < 0) { - ret = EOVERFLOW; - krb5_free_principal(contextp->context, princ); - goto fail; - } + } - if ((ks_tuple = calloc(n_ks_tuple, sizeof (*ks_tuple))) == NULL) { - ret = errno; - krb5_free_principal(contextp->context, princ); - goto fail; - } + if (n_ks_tuple < 0) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */ + ret = EOVERFLOW; + goto fail; + } + free(ks_tuple); + if ((ks_tuple = calloc(n_ks_tuple, sizeof (*ks_tuple))) == NULL) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + ret = errno; + goto fail; + } - for (i = 0; i < n_ks_tuple; i++) { - ret = krb5_ret_int32(sp, &ks_tuple[i].ks_enctype); - if (ret != 0) { - krb5_free_principal(contextp->context, princ); - free(ks_tuple); - goto fail; - } - ret = krb5_ret_int32(sp, &ks_tuple[i].ks_salttype); - if (ret != 0) { - krb5_free_principal(contextp->context, princ); - free(ks_tuple); - goto fail; - } - } - } + for (i = 0; i < n_ks_tuple; i++) { + ret = krb5_ret_int32(sp, &ks_tuple[i].ks_enctype); + if (ret != 0) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + free(ks_tuple); + goto fail; + } + ret = krb5_ret_int32(sp, &ks_tuple[i].ks_salttype); + if (ret != 0) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + free(ks_tuple); + goto fail; + } + } ret = kadm5_randkey_principal_3(kadm_handlep, princ, keepold, n_ks_tuple, ks_tuple, &new_keys, &n_keys); - krb5_free_principal(contextp->context, princ); free(ks_tuple); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); - if(ret == 0){ - int i; - krb5_store_int32(sp, n_keys); - for(i = 0; i < n_keys; i++){ - if (ret == 0) - ret = krb5_store_keyblock(sp, new_keys[i]); + ret_sp = krb5_store_int32(rsp, ret); + if (ret == 0 && ret_sp == 0){ + ret_sp = krb5_store_int32(rsp, n_keys); + for (i = 0; i < n_keys; i++){ + if (ret_sp == 0) + ret_sp = krb5_store_keyblock(rsp, new_keys[i]); krb5_free_keyblock_contents(contextp->context, &new_keys[i]); } free(new_keys); @@ -525,71 +753,142 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial, case kadm_get_privs:{ uint32_t privs; ret = kadm5_get_privs(kadm_handlep, &privs); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); - if(ret == 0) - krb5_store_uint32(sp, privs); + if (ret == 0) + ret_sp = krb5_store_uint32(rsp, privs); break; } case kadm_get_princs:{ op = "LIST"; ret = krb5_ret_int32(sp, &tmp); - if(ret) + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; - if(tmp){ + } + /* See kadm5_c_iter_principals() */ + if (tmp == 0x55555555) { + /* Want online iteration */ ret = krb5_ret_string(sp, &expression); - if(ret) + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + goto fail; + } + if (expression[0] == '\0') { + free(expression); + expression = NULL; + } + } else if (tmp) { + ret = krb5_ret_string(sp, &expression); + if (ret) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); goto fail; + } }else expression = NULL; krb5_warnx(contextp->context, "%s: %s %s", client, op, expression ? expression : "*"); ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL); if(ret){ + ret_sp = krb5_store_int32(rsp, ret); free(expression); goto fail; } - ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs); - free(expression); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, ret); - if(ret == 0){ - int i; - krb5_store_int32(sp, n_princs); - for(i = 0; i < n_princs; i++) - krb5_store_string(sp, princs[i]); - kadm5_free_name_list(kadm_handlep, princs, &n_princs); - } + if (fd > -1 && tmp == 0x55555555) { + struct iter_cb_data iter_cbdata; + int n; + + /* + * The client proposes that we speak the online variation of LIST + * by sending a magic value in the int32 that is meant to be a + * boolean for "an expression follows". The client must send an + * expression in this case because the server might be an old one, + * so even if the caller to kadm5_get/iter_principals() passed NULL + * for the expression, the client must send something ("*"). + * + * The list of principals will be streamed in multiple replies. + * + * The first reply will have just a return code and a negative + * count of maximum number of names per-subsequent reply. See + * `iter_cb()'. + * + * The second reply, third, .., nth replies will have a return code + * followed by 50 names, except the last reply must have fewer than + * 50 names -zero if need be- so the client can deterministically + * notice the end of the stream. + */ + + n = list_chunk_size; + if (n < 0) + n = krb5_config_get_int_default(contextp->context, NULL, -1, + "kadmin", "list_chunk_size", NULL); + if (n < 0) + n = 50; + if (n > 500) + n = 500; + if ((iter_cbdata.rsp = krb5_storage_emem()) == NULL) { + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); + ret = krb5_enomem(contextp->context); + goto fail; + } + iter_cbdata.context = contextp->context; + iter_cbdata.initial = 1; + iter_cbdata.stop = 0; + iter_cbdata.ret = 0; + iter_cbdata.ac = ac; + iter_cbdata.fd = fd; + iter_cbdata.n = n; + iter_cbdata.i = 0; + + /* + * All sending of replies will happen in iter_cb, except for the + * final chunk with the final result code. + */ + iter_cbdata.ret = kadm5_iter_principals(kadm_handlep, expression, + iter_cb, &iter_cbdata); + /* Send terminating chunk */ + iter_cb(&iter_cbdata, NULL); + /* Final result */ + ret = krb5_store_int32(rsp, iter_cbdata.ret); + krb5_storage_free(iter_cbdata.rsp); + } else { + ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs); + ret_sp = krb5_store_int32(rsp, ret); + if (ret == 0 && ret_sp == 0) { + int i; + + ret_sp = krb5_store_int32(rsp, n_princs); + for (i = 0; ret_sp == 0 && i < n_princs; i++) + ret_sp = krb5_store_string(rsp, princs[i]); + kadm5_free_name_list(kadm_handlep, princs, &n_princs); + } + } + free(expression); break; } default: krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd); - krb5_storage_free(sp); - sp = krb5_storage_emem(); - krb5_store_int32(sp, KADM5_FAILURE); + ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); break; } - if (password != NULL) { - len = strlen(password); - memset_s(password, len, 0, len); - free(password); - } - krb5_storage_to_data(sp, out); - krb5_storage_free(sp); - return 0; + fail: if (password != NULL) { len = strlen(password); memset_s(password, len, 0, len); free(password); } - krb5_warn(contextp->context, ret, "%s", op); - krb5_storage_seek(sp, 0, SEEK_SET); - krb5_store_int32(sp, ret); - krb5_storage_to_data(sp, out); + krb5_storage_to_data(rsp, out); + krb5_storage_free(rsp); krb5_storage_free(sp); + krb5_free_principal(contextp->context, princ); + krb5_free_principal(contextp->context, princ2); + if (ret) + krb5_warn(contextp->context, ret, "%s", op); + if (out->length == 0) + krb5_warn(contextp->context, ret, "%s: reply failed", op); + else if (ret_sp) + krb5_warn(contextp->context, ret, "%s: reply incomplete", op); + if (ret_sp) + return ret_sp; return 0; } @@ -613,6 +912,10 @@ iter_aliases(kadm5_principal_ent_rec *from, if (ctx->done > 0) return 0; + if (from == NULL) { + ctx->done = 1; + return 0; + } if (ctx->done == 0) { if (ctx->alias_idx < ctx->aliases.aliases.len) { @@ -712,7 +1015,8 @@ v5_loop (krb5_context contextp, krb5_auth_context ac, krb5_boolean initial, void *kadm_handlep, - krb5_socket_t fd) + krb5_socket_t fd, + int readonly) { krb5_error_code ret; krb5_data in, out; @@ -727,9 +1031,14 @@ v5_loop (krb5_context contextp, if(ret) krb5_err(contextp, 1, ret, "krb5_read_priv_message"); doing_useful_work = 1; - kadmind_dispatch(kadm_handlep, initial, &in, &out); + ret = kadmind_dispatch(kadm_handlep, initial, &in, ac, fd, &out, + readonly); + if (ret) + krb5_err(contextp, 1, ret, "kadmind_dispatch"); krb5_data_free(&in); - ret = krb5_write_priv_message(contextp, ac, &fd, &out); + if (out.length) + ret = krb5_write_priv_message(contextp, ac, &fd, &out); + krb5_data_free(&out); if(ret) krb5_err(contextp, 1, ret, "krb5_write_priv_message"); } @@ -749,7 +1058,8 @@ match_appl_version(const void *data, const char *appl_version) static void handle_v5(krb5_context contextp, krb5_keytab keytab, - krb5_socket_t fd) + krb5_socket_t fd, + int readonly) { krb5_error_code ret; krb5_ticket *ticket; @@ -758,7 +1068,6 @@ handle_v5(krb5_context contextp, void *kadm_handlep; krb5_boolean initial; krb5_auth_context ac = NULL; - unsigned kadm_version = 1; kadm5_config_params realm_params; @@ -766,35 +1075,51 @@ handle_v5(krb5_context contextp, match_appl_version, &kadm_version, NULL, KRB5_RECVAUTH_IGNORE_VERSION, keytab, &ticket); - if (ret) + if (ret) { krb5_err(contextp, 1, ret, "krb5_recvauth"); - - ret = krb5_unparse_name (contextp, ticket->server, &server_name); - if (ret) - krb5_err (contextp, 1, ret, "krb5_unparse_name"); - - if (strncmp (server_name, KADM5_ADMIN_SERVICE, - strlen(KADM5_ADMIN_SERVICE)) != 0) - krb5_errx (contextp, 1, "ticket for strange principal (%s)", - server_name); - - free (server_name); + return; + } + ret = krb5_unparse_name(contextp, ticket->server, &server_name); + if (ret) { + krb5_err(contextp, 1, ret, "krb5_unparse_name"); + krb5_free_ticket(contextp, ticket); + return; + } + if (strncmp(server_name, KADM5_ADMIN_SERVICE, + strlen(KADM5_ADMIN_SERVICE)) != 0) { + krb5_errx(contextp, 1, "ticket for strange principal (%s)", server_name); + krb5_free_ticket(contextp, ticket); + free(server_name); + return; + } + free(server_name); memset(&realm_params, 0, sizeof(realm_params)); if(kadm_version == 1) { krb5_data params; ret = krb5_read_priv_message(contextp, ac, &fd, ¶ms); - if(ret) + if (ret) { krb5_err(contextp, 1, ret, "krb5_read_priv_message"); - _kadm5_unmarshal_params(contextp, ¶ms, &realm_params); + krb5_free_ticket(contextp, ticket); + return; + } + ret = _kadm5_unmarshal_params(contextp, ¶ms, &realm_params); + if (ret) { + krb5_err(contextp, 1, ret, + "Could not read or parse kadm5 parameters"); + krb5_free_ticket(contextp, ticket); + return; + } } initial = ticket->ticket.flags.initial; ret = krb5_unparse_name(contextp, ticket->client, &client); - if (ret) - krb5_err (contextp, 1, ret, "krb5_unparse_name"); - krb5_free_ticket (contextp, ticket); + krb5_free_ticket(contextp, ticket); + if (ret) { + krb5_err(contextp, 1, ret, "krb5_unparse_name"); + return; + } ret = kadm5_s_init_with_password_ctx(contextp, client, NULL, @@ -802,15 +1127,18 @@ handle_v5(krb5_context contextp, &realm_params, 0, 0, &kadm_handlep); - if(ret) - krb5_err (contextp, 1, ret, "kadm5_init_with_password_ctx"); - v5_loop (contextp, ac, initial, kadm_handlep, fd); + if (ret) { + krb5_err(contextp, 1, ret, "kadm5_init_with_password_ctx"); + return; + } + v5_loop(contextp, ac, initial, kadm_handlep, fd, readonly); } krb5_error_code kadmind_loop(krb5_context contextp, krb5_keytab keytab, - krb5_socket_t sock) + krb5_socket_t sock, + int readonly) { u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4]; ssize_t n; @@ -832,14 +1160,15 @@ kadmind_loop(krb5_context contextp, krb5_errx (contextp, 1, "EOF reading sendauth version"); if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) { - handle_v5(contextp, keytab, sock); + handle_v5(contextp, keytab, sock, readonly); return 0; } len += 4; } else len = 4; - handle_mit(contextp, buf, len, sock); + handle_mit(contextp, buf, len, sock, readonly); return 0; } + |
