diff --git a/kadmin/version4.c b/kadmin/version4.c new file mode 100644 index 000000000..ee52dae15 --- /dev/null +++ b/kadmin/version4.c @@ -0,0 +1,785 @@ +/* + * Copyright (c) 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of KTH nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#include "kadmin_locl.h" +#include + +#define Principal krb4_Principal +#define kadm_get krb4_kadm_get +#undef ALLOC +#include +#include +#include +#include + +RCSID("$Id$"); + +#define KADM_NO_OPCODE -1 +#define KADM_NO_ENCRYPT -2 + +static void +make_error_packet(int code, krb5_data *reply) +{ + krb5_data_alloc(reply, KADM_VERSIZE + 4); + memcpy(reply->data, KADM_ULOSE, KADM_VERSIZE); + _krb5_put_int((char*)reply->data + KADM_VERSIZE, code, 4); +} + + +static int +ret_fields(krb5_storage *sp, char *fields) +{ + return sp->fetch(sp, fields, FLDSZ); +} + +static int +store_fields(krb5_storage *sp, char *fields) +{ + return sp->store(sp, fields, FLDSZ); +} + +static void +ret_vals(krb5_storage *sp, Kadm_vals *vals) +{ + int field; + char *tmp_string; + + memset(vals, 0, sizeof(*vals)); + + ret_fields(sp, vals->fields); + + for(field = 31; field >= 0; field--) { + if(IS_FIELD(field, vals->fields)) { + switch(field) { + case KADM_NAME: + krb5_ret_stringz(sp, &tmp_string); + strcpy_truncate(vals->name, tmp_string, sizeof(vals->name)); + free(tmp_string); + break; + case KADM_INST: + krb5_ret_stringz(sp, &tmp_string); + strcpy_truncate(vals->instance, tmp_string, + sizeof(vals->instance)); + free(tmp_string); + break; + case KADM_EXPDATE: + krb5_ret_int32(sp, &vals->exp_date); + break; + case KADM_ATTR: + krb5_ret_int16(sp, &vals->attributes); + break; + case KADM_MAXLIFE: + krb5_ret_int8(sp, &vals->max_life); + break; + case KADM_DESKEY: + krb5_ret_int32(sp, &vals->key_high); + krb5_ret_int32(sp, &vals->key_low); + break; +#ifdef EXTENDED_KADM + case KADM_MODDATE: + krb5_ret_int32(sp, &vals->mod_date); + break; + case KADM_MODNAME: + krb5_ret_stringz(sp, &tmp_string); + strcpy_truncate(vals->mod_name, tmp_string, + sizeof(vals->mod_name)); + free(tmp_string); + break; + case KADM_MODINST: + krb5_ret_stringz(sp, &tmp_string); + strcpy_truncate(vals->mod_instance, tmp_string, + sizeof(vals->mod_instance)); + free(tmp_string); + break; + case KADM_KVNO: + krb5_ret_int8(sp, &vals->key_version); + break; +#endif + default: + break; + } + } + } +} + +static void +store_vals(krb5_storage *sp, Kadm_vals *vals) +{ + int field; + + store_fields(sp, vals->fields); + + for(field = 31; field >= 0; field--) { + if(IS_FIELD(field, vals->fields)) { + switch(field) { + case KADM_NAME: + krb5_store_stringz(sp, vals->name); + break; + case KADM_INST: + krb5_store_stringz(sp, vals->instance); + break; + case KADM_EXPDATE: + krb5_store_int32(sp, vals->exp_date); + break; + case KADM_ATTR: + krb5_store_int16(sp, vals->attributes); + break; + case KADM_MAXLIFE: + krb5_store_int8(sp, vals->max_life); + break; + case KADM_DESKEY: + krb5_store_int32(sp, vals->key_high); + krb5_store_int32(sp, vals->key_low); + break; +#ifdef EXTENDED_KADM + case KADM_MODDATE: + krb5_store_int32(sp, vals->mod_date); + break; + case KADM_MODNAME: + krb5_store_stringz(sp, vals->mod_name); + break; + case KADM_MODINST: + krb5_store_stringz(sp, vals->mod_instance); + break; + case KADM_KVNO: + krb5_store_int8(sp, vals->key_version); + break; +#endif + default: + break; + } + } + } +} + +static int +flags_4_to_5(char *flags) +{ + int i; + int32_t mask = 0; + for(i = 31; i >= 0; i--) { + if(IS_FIELD(i, flags)) + switch(i) { + case KADM_NAME: + case KADM_INST: + mask |= KADM5_PRINCIPAL; + case KADM_EXPDATE: + mask |= KADM5_PW_EXPIRATION; + case KADM_MAXLIFE: + mask |= KADM5_MAX_LIFE; +#ifdef EXTENDED_KADM + case KADM_KVNO: + mask |= KADM5_KEY_DATA; + case KADM_MODDATE: + mask |= KADM5_MOD_TIME; + case KADM_MODNAME: + case KADM_MODINST: + mask |= KADM5_MOD_NAME; +#endif + } + } + return mask; +} + +static void +ent_to_values(krb5_context context, + kadm5_principal_ent_t ent, + int32_t mask, + Kadm_vals *vals) +{ + krb5_error_code ret; + char realm[REALM_SZ]; + memset(vals, 0, sizeof(*vals)); + if(mask & KADM5_PRINCIPAL) { + ret = krb5_524_conv_principal(context, ent->principal, + vals->name, vals->instance, realm); + SET_FIELD(KADM_NAME, vals->fields); + SET_FIELD(KADM_INST, vals->fields); + } + if(mask & KADM5_PW_EXPIRATION) { + time_t exp = 0; + if(ent->princ_expire_time != 0) + exp = ent->princ_expire_time; + if(ent->pw_expiration != 0 && (exp == 0 || exp > ent->pw_expiration)) + exp = ent->pw_expiration; + if(exp) { + vals->exp_date = exp; + SET_FIELD(KADM_EXPDATE, vals->fields); + } + } + if(mask & KADM5_MAX_LIFE) { + if(ent->max_life == 0) + vals->max_life = 255; + else + vals->max_life = krb_time_to_life(0, ent->max_life); + SET_FIELD(KADM_MAXLIFE, vals->fields); + } + if(mask & KADM5_KEY_DATA) { + if(ent->n_key_data > 0) { +#ifdef EXTENDED_KADM + vals->key_version = ent->key_data[0].key_data_kvno; + SET_FIELD(KADM_KVNO, vals->fields); +#endif + } + /* XXX the key itself? */ + } +#ifdef EXTENDED_KADM + if(mask & KADM5_MOD_TIME) { + vals->mod_date = ent->mod_date; + SET_FIELD(KADM_MODDATE, vals->fields); + } + if(mask & KADM5_MOD_NAME) { + krb5_524_conv_principal(context, ent->mod_name, + vals->mod_name, vals->mod_instance, realm); + SET_FIELD(KADM_MODNAME, vals->fields); + SET_FIELD(KADM_MODINST, vals->fields); + } +#endif +} + +static krb5_error_code +values_to_ent(krb5_context context, + Kadm_vals *vals, + kadm5_principal_ent_t ent, + int32_t *mask) +{ + krb5_error_code ret; + *mask = 0; + memset(ent, 0, sizeof(*ent)); + + if(IS_FIELD(KADM_NAME, vals->fields)) { + char *inst = NULL; + if(IS_FIELD(KADM_INST, vals->fields)) + inst = vals->instance; + ret = krb5_425_conv_principal(context, + vals->name, + inst, + NULL, + &ent->principal); + if(ret) + return ret; + *mask |= KADM5_PRINCIPAL; + } + if(IS_FIELD(KADM_EXPDATE, vals->fields)) { + ent->pw_expiration = vals->exp_date; + *mask |= KADM5_PW_EXPIRATION; + } + if(IS_FIELD(KADM_MAXLIFE, vals->fields)) { + ent->max_life = krb_life_to_time(0, vals->max_life); + *mask |= KADM5_MAX_LIFE; + } + + if(IS_FIELD(KADM_DESKEY, vals->fields)) { + int i; + ent->key_data = calloc(3, sizeof(*ent->key_data)); + if(ent->key_data == NULL) + return ENOMEM; + for(i = 0; i < 3; i++) { + ent->key_data[i].key_data_ver = 2; +#ifdef EXTENDED_KADM + if(IS_FIELD(KADM_KVNO, vals->fields)) + ent->key_data[i].key_data_kvno = vals->key_version; +#endif + ent->key_data[i].key_data_type[0] = ETYPE_DES_CBC_MD5; + ent->key_data[i].key_data_length[0] = 8; + if((ent->key_data[i].key_data_contents[0] = malloc(8)) == NULL) + return ENOMEM; + memcpy(ent->key_data[i].key_data_contents[0], + &vals->key_high, + 4); + memcpy((char*)ent->key_data[i].key_data_contents[0] + 4, + &vals->key_low, + 4); + ent->key_data[i].key_data_type[1] = KRB5_PW_SALT; + ent->key_data[i].key_data_length[1] = 0; + ent->key_data[i].key_data_contents[1] = NULL; + } + ent->key_data[1].key_data_type[0] = ETYPE_DES_CBC_MD4; + ent->key_data[2].key_data_type[0] = ETYPE_DES_CBC_CRC; + ent->n_key_data = 3; + *mask |= KADM5_KEY_DATA; + } + +#ifdef EXTENDED_KADM + if(IS_FIELD(KADM_MODDATE, vals->fields)) { + ent->mod_date = vals->mod_date; + *mask |= KADM5_MOD_TIME; + } + if(IS_FIELD(KADM_MODNAME, vals->fields)) { + char *inst = NULL; + if(IS_FIELD(KADM_MODINST, vals->fields)) + inst = vals->mod_instance; + ret = krb5_425_conv_principal(context, + vals->mod_name, + inst, + NULL, + &ent->mod_name); + if(ret) + return ret; + *mask |= KADM5_MOD_NAME; + } +#endif + return 0; +} + +static int +error_code(int ret) +{ + switch(ret) { + case KADM5_UNK_PRINC: + return KADM_NOENTRY; + case KADM5_DUP: + return KADM_INUSE; + case KADM5_AUTH_GET: + case KADM5_AUTH_ADD: + case KADM5_AUTH_MODIFY: + case KADM5_AUTH_DELETE: + case KADM5_AUTH_INSUFFICIENT: + return KADM_UNAUTH; + case 0: + return 0; + default: + return KADM_RCSID; /* XXX */ + } +} + +static int +kadm_ser_cpw(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_storage *message, + krb5_storage *reply) +{ + char key[8]; + char *password; + krb5_error_code ret; + + ret = message->fetch(message, key, 8); + ret = krb5_ret_stringz(message, &password); + + if(password) + ret = kadm5_chpass_principal(kadm_handle, principal, password); + else { + krb5_key_data key_data[3]; + int i; + for(i = 0; i < 3; i++) { + key_data[i].key_data_ver = 2; + key_data[i].key_data_kvno = 0; + /* key */ + key_data[i].key_data_type[0] = ETYPE_DES_CBC_CRC; + key_data[i].key_data_length[0] = 8; + key_data[i].key_data_contents[0] = malloc(8); + memcpy(key_data[i].key_data_contents[0], &key, 8); + /* salt */ + key_data[i].key_data_type[1] = KRB5_PW_SALT; + key_data[i].key_data_length[1] = 0; + key_data[i].key_data_contents[1] = NULL; + } + key_data[0].key_data_type[0] = ETYPE_DES_CBC_MD5; + key_data[1].key_data_type[0] = ETYPE_DES_CBC_MD4; + ret = kadm5_s_chpass_principal_with_key(kadm_handle, + principal, 3, key_data); + } + + if(ret != 0) { + krb5_store_stringz(reply, (char*)krb5_get_err_text(context, ret)); + return -1; + } + return 0; +} + +static int +kadm_ser_add(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_storage *message, + krb5_storage *reply) +{ + int32_t mask; + kadm5_principal_ent_rec ent, out; + Kadm_vals values; + krb5_error_code ret; + + ret_vals(message, &values); + + ret = values_to_ent(context, &values, &ent, &mask); + if(ret) { + kadm5_free_principal_ent(kadm_handle, &ent); + } + + ret = kadm5_s_create_principal_with_key(kadm_handle, &ent, mask); + if(ret) { + kadm5_free_principal_ent(kadm_handle, &ent); + return error_code(ret); + } + + mask = KADM5_PRINCIPAL | KADM5_PW_EXPIRATION | KADM5_MAX_LIFE | + KADM5_KEY_DATA | KADM5_MOD_TIME | KADM5_MOD_NAME; + + kadm5_get_principal(kadm_handle, ent.principal, &out, mask); + ent_to_values(context, &out, mask, &values); + kadm5_free_principal_ent(kadm_handle, &ent); + kadm5_free_principal_ent(kadm_handle, &out); + store_vals(reply, &values); + return 0; +} + +static int +kadm_ser_get(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_storage *message, + krb5_storage *reply) +{ + Kadm_vals values; + kadm5_principal_ent_rec ent, out; + int32_t mask; + char flags[FLDSZ]; + krb5_error_code ret; + + ret_vals(message, &values); + /* XXX BRAIN DAMAGE! these flags are not stored in the same order + as in the header */ + krb5_ret_int8(message, &flags[3]); + krb5_ret_int8(message, &flags[2]); + krb5_ret_int8(message, &flags[1]); + krb5_ret_int8(message, &flags[0]); + ret = values_to_ent(context, &values, &ent, &mask); + if(ret) { + kadm5_free_principal_ent(kadm_handle, &ent); + return error_code(ret); + } + mask = flags_4_to_5(flags); + + ret = kadm5_get_principal(kadm_handle, ent.principal, &out, mask); + + kadm5_free_principal_ent(kadm_handle, &ent); + + ent_to_values(context, &out, mask, &values); + + kadm5_free_principal_ent(kadm_handle, &out); + + store_vals(reply, &values); + return 0; +} + +static int +kadm_ser_mod(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_storage *message, + krb5_storage *reply) +{ + Kadm_vals values1, values2; + kadm5_principal_ent_rec ent, out; + int32_t mask; + krb5_error_code ret; + + ret_vals(message, &values1); + /* why are the old values sent? is the mask the same in the old and + the new entry? */ + ret_vals(message, &values2); + + ret = values_to_ent(context, &values2, &ent, &mask); + if(ret) { + kadm5_free_principal_ent(kadm_handle, &ent); + return error_code(ret); + } + + ret = kadm5_s_modify_principal_with_key(kadm_handle, &ent, mask); + if(ret) { + krb5_warn(context, ret, "kadm5_s_modify_principal"); + return error_code(ret); + } + + ret = kadm5_get_principal(kadm_handle, ent.principal, &out, mask); + if(ret) { + krb5_warn(context, ret, "kadm5_s_modify_principal"); + return error_code(ret); + } + + ent_to_values(context, &out, mask, &values1); + + kadm5_free_principal_ent(kadm_handle, &ent); + kadm5_free_principal_ent(kadm_handle, &out); + + store_vals(reply, &values1); + return 0; +} + +static int +kadm_ser_del(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_storage *message, + krb5_storage *reply) +{ + Kadm_vals values; + kadm5_principal_ent_rec ent; + int32_t mask; + krb5_error_code ret; + + ret_vals(message, &values); + + ret = values_to_ent(context, &values, &ent, &mask); + if(ret) { + kadm5_free_principal_ent(kadm_handle, &ent); + return error_code(ret); + } + + ret = kadm5_delete_principal(kadm_handle, ent.principal); + + kadm5_free_principal_ent(kadm_handle, &ent); + + return error_code(ret); +} + +static void +dispatch(krb5_context context, + void *kadm_handle, + krb5_principal principal, + krb5_data msg, + krb5_data *reply) +{ + int retval; + int8_t command; + + krb5_storage *sp_in, *sp_out; + + sp_in = krb5_storage_from_data(&msg); + krb5_ret_int8(sp_in, &command); + + sp_out = krb5_storage_emem(); + sp_out->store(sp_out, KADM_VERSTR, KADM_VERSIZE); + krb5_store_int32(sp_out, 0); + + switch(command) { + case CHANGE_PW: + retval = kadm_ser_cpw(context, kadm_handle, principal, + sp_in, sp_out); + break; + case ADD_ENT: + retval = kadm_ser_add(context, kadm_handle, principal, + sp_in, sp_out); + break; + case GET_ENT: + retval = kadm_ser_get(context, kadm_handle, principal, + sp_in, sp_out); + break; + case MOD_ENT: + retval = kadm_ser_mod(context, kadm_handle, principal, + sp_in, sp_out); + break; + case DEL_ENT: + retval = kadm_ser_del(context, kadm_handle, principal, + sp_in, sp_out); + break; + default: + retval = KADM_NO_OPCODE; + break; + } + krb5_storage_free(sp_in); + if(retval) { + krb5_storage_free(sp_out); + make_error_packet(retval, reply); + return; + } + krb5_storage_to_data(sp_out, reply); + krb5_storage_free(sp_out); +} + +static void +decode_packet(krb5_context context, + struct sockaddr_in *admin_addr, + struct sockaddr_in *client_addr, + krb5_data message, + krb5_data *reply) +{ + int ret; + KTEXT_ST authent; + AUTH_DAT ad; + MSG_DAT msg_dat; + off_t off = 0; + unsigned long rlen; + char sname[] = "changepw", sinst[] = "kerberos"; + unsigned long checksum; + des_key_schedule schedule; + char *msg = message.data; + + void *kadm_handle; + + if(message.length < KADM_VERSIZE || strncmp(msg, KADM_VERSTR, KADM_VERSIZE) != 0) + ; + off = KADM_VERSIZE; + off += _krb5_get_int(msg + off, &rlen, 4); + memset(&authent, 0, sizeof(authent)); + authent.length = message.length - rlen - KADM_VERSIZE - 4; + memcpy(authent.dat, (char*)msg + off, authent.length); + off += authent.length; + + { + krb5_principal principal; + krb5_keyblock *key; + krb5_make_principal(context, &principal, NULL, + "changepw", "kerberos", NULL); + ret = krb5_kt_read_service_key(context, + NULL, + principal, + 0, + ETYPE_DES_CBC_CRC, + &key); + krb5_free_principal(context, principal); + if(ret) { + if(ret == KRB5_KT_NOTFOUND) + make_error_packet(KADM_NO_AUTH, reply); + else + /* XXX */ + make_error_packet(KADM_NO_AUTH, reply); + krb5_warn(context, ret, "krb5_kt_read_service_key"); + return; + } + + if(key->keyvalue.length != 8) + krb5_abortx(context, "key has wrong length (%lu)", + (unsigned long)key->keyvalue.length); + krb_set_key(key->keyvalue.data, 0); + krb5_free_keyblock(context, key); + } + + ret = krb_rd_req(&authent, sname, sinst, + client_addr->sin_addr.s_addr, &ad, NULL); + + if(ret) { + make_error_packet(krb_err_base + ret, reply); + krb5_warnx(context, "krb_rd_req: %d", ret); + return; + } + { + krb5_principal client; + char *client_str; + krb5_425_conv_principal(context, ad.pname, ad.pinst, ad.prealm, + &client); + krb5_unparse_name(context, client, &client_str); + krb5_free_principal(context, client); + + ret = kadm5_init_with_password_ctx(context, + client_str, + NULL, + KADM5_ADMIN_SERVICE, + NULL, 0, 0, + &kadm_handle); + + free(client_str); + } + + checksum = des_quad_cksum((des_cblock*)(msg + off), NULL, rlen, + 0, &ad.session); + if(checksum != ad.checksum) + ; + des_set_key(&ad.session, schedule); + ret = krb_rd_priv(msg + off, rlen, schedule, &ad.session, + client_addr, admin_addr, &msg_dat); + + { + krb5_data d, r; + krb5_principal principal; + d.data = msg_dat.app_data; + d.length = msg_dat.app_length; + + krb5_425_conv_principal(context, + ad.pname, ad.pinst, ad.prealm, + &principal); + + dispatch(context, kadm_handle, principal, d, &r); + krb5_data_alloc(reply, r.length + 26); + reply->length = krb_mk_priv(r.data, reply->data, r.length, + schedule, &ad.session, + admin_addr, client_addr); + if((ssize_t)reply->length < 0) { + make_error_packet(KADM_NO_ENCRYPT, reply); + return; + } + } +} + + +void +handle_v4(krb5_context context, + int len, + int fd); + +void +handle_v4(krb5_context context, + int len, + int fd) +{ + int first = 1; + struct sockaddr_in admin_addr, client_addr; + int addr_len; + krb5_error_code ret; + krb5_data message, reply; + + addr_len = sizeof(client_addr); + getsockname(fd, (struct sockaddr*)&admin_addr, &addr_len); + addr_len = sizeof(client_addr); + getpeername(fd, (struct sockaddr*)&client_addr, &addr_len); + + while(1) { + if(first) { + /* first time around, we have already read len, and two + bytes of the version string */ + krb5_data_alloc(&message, len); + memcpy(message.data, "KA", 2); + ret = krb5_net_read(context, &fd, (char*)message.data + 2, len - 2); + first = 0; + } else { + char buf[2]; + unsigned long tmp; + ret = krb5_net_read(context, &fd, buf, sizeof(2)); + if(ret == 0) + exit(0); + _krb5_get_int(buf, &tmp, 2); + krb5_data_alloc(&message, tmp); + krb5_net_read(context, &fd, message.data, message.length); + } + decode_packet(context, &admin_addr, &client_addr, + message, &reply); + krb5_data_free(&message); + { + char buf[2]; + _krb5_put_int(buf, reply.length, sizeof(buf)); + krb5_net_write(context, &fd, buf, sizeof(buf)); + krb5_net_write(context, &fd, reply.data, reply.length); + krb5_data_free(&reply); + } + } +}