/* * Copyright (c) 2008 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 the Institute 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 THE INSTITUTE AND 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 THE INSTITUTE OR 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 #include #include #include #define CHECK(x) \ do { \ int __r; \ if ((__r = (x))) { \ krb5_errx(dcontext, 1, "Failed (%d) on %s:%d", \ __r, __FILE__, __LINE__); \ } \ } while(0) #define EXPECT(x,expected) \ do { \ if ((x) != (expected)) { \ krb5_errx(dcontext, 1, \ "Got %d, was not the expected %d at %s:%d", \ x, expected, __FILE__, __LINE__); \ } \ } while(0) #define EXPECT_EGT(x,expected) \ do { \ if ((x) < (expected)) { \ krb5_errx(dcontext, 1, \ "Got %d that is < %d at %s:%d", \ x, expected, __FILE__, __LINE__); \ } \ } while(0) static krb5_context dcontext; #define INSIST(x) CHECK(!(x)) #define VERSION2 0x12345702 #define LAST_FRAGMENT 0x80000000 #define RPC_VERSION 2 #define KADM_SERVER 2112 #define VVERSION 2 #define FLAVOR_GSS 6 #define FLAVOR_GSS_VERSION 1 #define SEQ_WINDOW_SIZE 1 #define FLAVOR_GSS_OLD 300001 #define FLAVOR_GSS_OLD_MIN_VERSION 3 enum { RPG_DATA = 0, RPG_INIT = 1, RPG_CONTINUE_INIT = 2, RPG_DESTROY = 3 }; enum { rpg_privacy = 3 }; /* struct chrand_ret { krb5_ui_4 api_version; kadm5_ret_t ret; int n_keys; krb5_keyblock *keys; }; */ static int parse_name(const unsigned char *p, size_t len, const gss_OID oid, char **name) { size_t l; if (len < 4) return 1; /* TOK_ID */ if (memcmp(p, "\x04\x01", 2) != 0) return 1; len -= 2; p += 2; /* MECH_LEN */ l = (p[0] << 8) | p[1]; len -= 2; p += 2; if (l < 2 || len < l) return 1; /* oid wrapping */ if (p[0] != 6 || p[1] != l - 2) return 1; p += 2; l -= 2; len -= 2; /* MECH */ if (l != oid->length || memcmp(p, oid->elements, oid->length) != 0) return 1; len -= l; p += l; /* MECHNAME_LEN */ if (len < 4) return 1; l = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; len -= 4; p += 4; /* MECH NAME */ if (len != l) return 1; *name = malloc(l + 1); INSIST(*name != NULL); memcpy(*name, p, l); (*name)[l] = '\0'; return 0; } static void gss_error(krb5_context lcontext, gss_OID mech, OM_uint32 type, OM_uint32 error) { OM_uint32 new_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string; OM_uint32 ret; do { ret = gss_display_status (&new_stat, error, type, mech, &msg_ctx, &status_string); krb5_warnx(lcontext, "%.*s", (int)status_string.length, (char *)status_string.value); gss_release_buffer (&new_stat, &status_string); } while (!GSS_ERROR(ret) && msg_ctx != 0); } static void gss_print_errors (krb5_context lcontext, OM_uint32 maj_stat, OM_uint32 min_stat) { gss_error(lcontext, GSS_C_NO_OID, GSS_C_GSS_CODE, maj_stat); gss_error(lcontext, GSS_C_NO_OID, GSS_C_MECH_CODE, min_stat); } static int read_data(krb5_storage *sp, krb5_storage *msg, size_t len) { char buf[1024]; while (len) { size_t tlen = len; ssize_t slen; if (tlen > sizeof(buf)) tlen = sizeof(buf); slen = krb5_storage_read(sp, buf, tlen); INSIST(slen == tlen); slen = krb5_storage_write(msg, buf, tlen); INSIST(slen == tlen); len -= tlen; } return 0; } static int collect_fragments(krb5_storage *sp, krb5_storage *msg) { krb5_error_code ret; uint32_t len; int last_fragment; size_t total_len = 0; do { ret = krb5_ret_uint32(sp, &len); if (ret) return ret; last_fragment = (len & LAST_FRAGMENT); len &= ~LAST_FRAGMENT; CHECK(read_data(sp, msg, len)); total_len += len; } while(!last_fragment || total_len == 0); return 0; } /* * */ static void proc_create_principal(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { uint32_t version, mask; kadm5_principal_ent_rec ent; krb5_error_code ret; char *password, *princ = NULL; memset(&ent, 0, sizeof(ent)); CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_ent(lcontext->context, in, &ent)); INSIST(ent.principal != NULL); CHECK(krb5_unparse_name(lcontext->context, ent.principal, &princ)); CHECK(krb5_ret_uint32(in, &mask)); CHECK(_kadm5_xdr_ret_string_xdr(in, &password)); INSIST(ent.principal); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_ADD, ent.principal); if (ret) goto fail; ret = kadm5_create_principal(lcontext, &ent, mask, password); fail: krb5_warn(lcontext->context, ret, "create principal: %s", princ ? princ : ""); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); /* code */ free(password); free(princ); kadm5_free_principal_ent(context, &ent); } /* get array of salt tuples */ static krb5_key_salt_tuple * parse_ks_tuple(krb5_context lcontext, krb5_storage *in, uint32_t *n_ks_tuple) { krb5_key_salt_tuple *tuples; uint32_t n; CHECK(krb5_ret_uint32(in, n_ks_tuple)); INSIST(*n_ks_tuple < 1000); if (*n_ks_tuple == 0) return NULL; tuples = calloc(*n_ks_tuple, sizeof(tuples[0])); INSIST(tuples != NULL); for (n = 0; n < *n_ks_tuple; n++) { int32_t enctype, salttype; CHECK(krb5_ret_int32(in, &enctype)); CHECK(krb5_ret_int32(in, &salttype)); tuples[n].ks_enctype = (krb5_enctype)enctype; tuples[n].ks_salttype = (krb5_enctype)salttype; } return tuples; } static void proc_create_principal3(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { uint32_t version, mask, n_ks_tuple; kadm5_principal_ent_rec ent; krb5_error_code ret; char *password, *princ = NULL; krb5_key_salt_tuple *ks_tuple; memset(&ent, 0, sizeof(ent)); CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_ent(lcontext->context, in, &ent)); INSIST(ent.principal != NULL); CHECK(krb5_unparse_name(lcontext->context, ent.principal, &princ)); CHECK(krb5_ret_uint32(in, &mask)); ks_tuple = parse_ks_tuple(lcontext->context, in, &n_ks_tuple); CHECK(_kadm5_xdr_ret_string_xdr(in, &password)); INSIST(ent.principal); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_ADD, ent.principal); if (ret) goto fail; ret = kadm5_create_principal_2(context, &ent, mask, n_ks_tuple, ks_tuple, password); fail: krb5_warn(lcontext->context, ret, "create principal: %s", princ ? princ : ""); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); /* code */ free(password); kadm5_free_principal_ent(lcontext, &ent); free(princ); if (ks_tuple) free(ks_tuple); kadm5_free_principal_ent(context, &ent); } static void proc_delete_principal(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { uint32_t version; krb5_principal principal; krb5_error_code ret; char *princ = NULL; CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_xdr(lcontext->context, in, &principal)); CHECK(krb5_unparse_name(lcontext->context, principal, &princ)); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_DELETE, principal); if (ret) goto fail; ret = kadm5_delete_principal(lcontext, principal); fail: krb5_warn(lcontext->context, ret, "delete principal: %s", princ ? princ : ""); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); /* code */ free(princ); krb5_free_principal(lcontext->context, principal); } static void proc_modify_principal(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { uint32_t version, mask; kadm5_principal_ent_rec ent; krb5_error_code ret; CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_ent(lcontext->context, in, &ent)); INSIST(ent.principal != NULL); CHECK(krb5_ret_uint32(in, &mask)); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_MODIFY, ent.principal); if (ret) goto fail; ret = kadm5_modify_principal(lcontext, &ent, mask); fail: krb5_warn(lcontext->context, ret, "modify principal"); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); /* code */ kadm5_free_principal_ent(lcontext, &ent); } static void proc_get_principal(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { uint32_t version, mask; krb5_principal principal; kadm5_principal_ent_rec ent; krb5_error_code ret; char *princ = NULL; memset(&ent, 0, sizeof(ent)); CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_xdr(lcontext->context, in, &principal)); CHECK(krb5_unparse_name(lcontext->context, principal, &princ)); CHECK(krb5_ret_uint32(in, &mask)); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_GET, principal); if(ret) goto fail; mask |= KADM5_KVNO | KADM5_PRINCIPAL; ret = kadm5_get_principal(lcontext, principal, &ent, mask); fail: krb5_warn(lcontext->context, ret, "get principal: %s kvno %d", princ ? princ : "", (int)ent.kvno); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); /* code */ if (ret == 0) { CHECK(_kadm5_xdr_store_principal_ent(lcontext->context, out, &ent)); } krb5_free_principal(lcontext->context, principal); kadm5_free_principal_ent(lcontext, &ent); } static void proc_chrand_principal_v2(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { krb5_error_code ret; krb5_principal principal; uint32_t version; krb5_keyblock *new_keys; int n_keys; char *princ = NULL; CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_xdr(lcontext->context, in, &principal)); CHECK(krb5_unparse_name(lcontext->context, principal, &princ)); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_CPW, principal); if(ret) goto fail; ret = kadm5_randkey_principal(lcontext, principal, &new_keys, &n_keys); fail: krb5_warn(lcontext->context, ret, "rand key principal v2: %s", princ ? princ : ""); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); if (ret == 0) { size_t i; CHECK(krb5_store_int32(out, n_keys)); for(i = 0; i < n_keys; i++){ CHECK(krb5_store_uint32(out, new_keys[i].keytype)); CHECK(_kadm5_xdr_store_data_xdr(out, new_keys[i].keyvalue)); krb5_free_keyblock_contents(lcontext->context, &new_keys[i]); } free(new_keys); } krb5_free_principal(lcontext->context, principal); if (princ) free(princ); } static void proc_chrand_principal_v3(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { krb5_error_code ret; krb5_principal principal; uint32_t version, keepold, n_ks_tuple; krb5_keyblock *new_keys; krb5_key_salt_tuple *ks_tuple; char *princ = NULL; int n_keys; CHECK(krb5_ret_uint32(in, &version)); EXPECT_EGT(version, VERSION2); CHECK(_kadm5_xdr_ret_principal_xdr(lcontext->context, in, &principal)); CHECK(krb5_unparse_name(lcontext->context, principal, &princ)); CHECK(krb5_ret_uint32(in, &keepold)); ks_tuple = parse_ks_tuple(lcontext->context, in, &n_ks_tuple); ret = _kadm5_acl_check_permission(lcontext, KADM5_PRIV_CPW, principal); if(ret) goto fail; ret = kadm5_randkey_principal_3(context, principal, keepold, n_ks_tuple, ks_tuple, &new_keys, &n_keys); fail: krb5_warn(lcontext->context, ret, "rand key principal v3: %s", princ ? princ : ""); CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, ret)); if (ret == 0) { size_t i; CHECK(krb5_store_int32(out, n_keys)); for(i = 0; i < n_keys; i++){ CHECK(krb5_store_uint32(out, new_keys[i].keytype)); CHECK(_kadm5_xdr_store_data_xdr(out, new_keys[i].keyvalue)); krb5_free_keyblock_contents(lcontext->context, &new_keys[i]); } free(new_keys); } if (ks_tuple) free(ks_tuple); krb5_free_principal(lcontext->context, principal); if (princ) free(princ); } static void proc_init(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, 0)); /* code */ CHECK(krb5_store_uint32(out, 0)); /* code */ } static void proc_get_policy(kadm5_server_context *lcontext, krb5_storage *in, krb5_storage *out) { CHECK(krb5_store_uint32(out, VERSION2)); /* api version */ CHECK(krb5_store_uint32(out, KADM5_AUTH_GET)); /* code */ } struct proc { char *name; void (*func)(kadm5_server_context *, krb5_storage *, krb5_storage *); } procs[] = { { "NULL", NULL }, { "create principal", proc_create_principal }, { "delete principal", proc_delete_principal }, { "modify principal", proc_modify_principal }, { "rename principal", NULL }, { "get principal", proc_get_principal }, { "chpass principal", NULL }, { "chrand principal v2", proc_chrand_principal_v2 }, { "create policy", NULL }, { "delete policy", NULL }, { "modify policy", NULL }, { "get policy", proc_get_policy }, { "get privs", NULL }, { "init", proc_init }, { "get principals", NULL }, { "get polices", NULL }, { "setkey principal", NULL }, { "setkey principal v4", NULL }, { "create principal v3", proc_create_principal3 }, { "chpass principal v3", NULL }, { "chrand principal v3", proc_chrand_principal_v3 }, { "setkey principal v3", NULL } }; static krb5_error_code copyheader(krb5_storage *sp, krb5_data *data) { off_t off; ssize_t sret; off = krb5_storage_seek(sp, 0, SEEK_CUR); CHECK(krb5_data_alloc(data, off)); INSIST(off == data->length); krb5_storage_seek(sp, 0, SEEK_SET); sret = krb5_storage_read(sp, data->data, data->length); INSIST(sret == off); INSIST(off == krb5_storage_seek(sp, 0, SEEK_CUR)); return 0; } struct gctx { krb5_data handle; gss_ctx_id_t ctx; uint32_t seq_num; uint32_t protocol; int done; int inprogress; void *server_handle; void (*verify_header)(struct gctx *, struct _kadm5_xdr_call_header *, krb5_data *); void (*handle_protocol)(struct gctx *, struct _kadm5_xdr_call_header *, krb5_storage *, krb5_storage *); void (*reply)(struct gctx *, krb5_storage *, krb5_storage *); }; static void setup_context(struct gctx *gctx, gss_name_t src_name) { kadm5_config_params realm_params; gss_buffer_desc buf; OM_uint32 maj_stat, min_stat, junk; krb5_error_code ret; char *client; INSIST(gctx->done); memset(&realm_params, 0, sizeof(realm_params)); maj_stat = gss_export_name(&min_stat, src_name, &buf); EXPECT(maj_stat, GSS_S_COMPLETE); EXPECT(min_stat, 0); CHECK(parse_name(buf.value, buf.length, GSS_KRB5_MECHANISM, &client)); gss_release_buffer(&junk, &buf); krb5_warnx(context, "%s connected", client); ret = kadm5_s_init_with_password_ctx(context, client, NULL, KADM5_ADMIN_SERVICE, &realm_params, 0, 0, &gctx->server_handle); EXPECT(ret, 0); } /* * GSS flavor */ static void xpcgss_verify_header(struct gctx *gctx, struct _kadm5_xdr_call_header *chdr, krb5_data *header) { OM_uint32 maj_stat, min_stat; gss_buffer_desc gin, gout; EXPECT(chdr->verf.flavor, gctx->protocol); /* from first byte to last of credential */ gin.value = header->data; gin.length = header->length; gout.value = chdr->verf.data.data; gout.length = chdr->verf.data.length; maj_stat = gss_verify_mic(&min_stat, gctx->ctx, &gin, &gout, NULL); EXPECT(maj_stat, GSS_S_COMPLETE); } static void rpcgss_handle_protocol(struct gctx *gctx, struct _kadm5_xdr_call_header *chdr, krb5_storage *msg, krb5_storage *dreply) { OM_uint32 maj_stat, min_stat, junk; gss_buffer_desc gin, gout; struct _kadm5_xdr_gcred gcred; memset(&gcred, 0, sizeof(gcred)); CHECK(_kadm5_xdr_ret_gcred(&chdr->cred.data, &gcred)); EXPECT(gcred.version, FLAVOR_GSS_VERSION); switch(gcred.proc) { case RPG_DATA: { krb5_data data; int conf_state; uint32_t seq; krb5_storage *sp; EXPECT(gcred.service, rpg_privacy); INSIST(gctx->done); INSIST(krb5_data_cmp(&gcred.handle, &gctx->handle) == 0); CHECK(_kadm5_xdr_ret_data_xdr(msg, &data)); gin.value = data.data; gin.length = data.length; maj_stat = gss_unwrap(&min_stat, gctx->ctx, &gin, &gout, &conf_state, NULL); krb5_data_free(&data); INSIST(maj_stat == GSS_S_COMPLETE); INSIST(conf_state != 0); sp = krb5_storage_from_mem(gout.value, gout.length); INSIST(sp != NULL); CHECK(krb5_ret_uint32(sp, &seq)); EXPECT(seq, gcred.seq_num); /* * Check sequence number */ INSIST(seq > gctx->seq_num); gctx->seq_num = seq; /* * If context is setup, priv data have the seq_num stored * first in the block, so add it here before users data is * added. */ CHECK(krb5_store_uint32(dreply, gctx->seq_num)); if (chdr->proc >= sizeof(procs)/sizeof(procs[0])) { krb5_warnx(context, "proc number out of array"); } else if (procs[chdr->proc].func == NULL) { krb5_warnx(context, "proc '%s' never implemented", procs[chdr->proc].name); } else { krb5_warnx(context, "proc %s", procs[chdr->proc].name); INSIST(gctx->server_handle != NULL); (*procs[chdr->proc].func)(gctx->server_handle, sp, dreply); } krb5_storage_free(sp); gss_release_buffer(&min_stat, &gout); break; } case RPG_INIT: INSIST(gctx->inprogress == 0); INSIST(gctx->ctx == NULL); gctx->inprogress = 1; /* FALL THOUGH */ case RPG_CONTINUE_INIT: { gss_name_t src_name = GSS_C_NO_NAME; krb5_data in; INSIST(gctx->inprogress); CHECK(_kadm5_xdr_ret_data_xdr(msg, &in)); gin.value = in.data; gin.length = in.length; gout.value = NULL; gout.length = 0; maj_stat = gss_accept_sec_context(&min_stat, &gctx->ctx, GSS_C_NO_CREDENTIAL, &gin, GSS_C_NO_CHANNEL_BINDINGS, &src_name, NULL, &gout, NULL, NULL, NULL); if (GSS_ERROR(maj_stat)) { gss_print_errors(context, maj_stat, min_stat); krb5_errx(context, 1, "gss error, exit"); } if ((maj_stat & GSS_S_CONTINUE_NEEDED) == 0) { gctx->done = 1; gctx->verify_header = xpcgss_verify_header; setup_context(gctx, src_name); } INSIST(gctx->ctx != GSS_C_NO_CONTEXT); CHECK(_kadm5_xdr_store_gss_init_res(dreply, gctx->handle, maj_stat, min_stat, SEQ_WINDOW_SIZE, &gout)); if (gout.value) gss_release_buffer(&junk, &gout); if (src_name) gss_release_name(&junk, &src_name); break; } case RPG_DESTROY: krb5_errx(context, 1, "client destroyed gss context"); default: krb5_errx(context, 1, "client sent unknown gsscode %d", (int)gcred.proc); } krb5_data_free(&gcred.handle); } static void rpcgss_reply(struct gctx *gctx, krb5_storage *dreply, krb5_storage *reply) { uint32_t seqnum = htonl(gctx->seq_num); gss_buffer_desc gin, gout; OM_uint32 maj_stat, min_stat, junk; krb5_data data; /* * The first checksum is really the checksum of the * seq_window in the rpc_gss_init_res packet, lets agree * with that. */ if (gctx->seq_num == 0) seqnum = htonl(SEQ_WINDOW_SIZE); gin.value = &seqnum; gin.length = sizeof(seqnum); maj_stat = gss_get_mic(&min_stat, gctx->ctx, 0, &gin, &gout); INSIST(maj_stat == GSS_S_COMPLETE); data.data = gout.value; data.length = gout.length; CHECK(krb5_store_uint32(reply, FLAVOR_GSS)); CHECK(_kadm5_xdr_store_data_xdr(reply, data)); gss_release_buffer(&junk, &gout); CHECK(krb5_store_uint32(reply, 0)); /* SUCCESS */ CHECK(krb5_storage_to_data(dreply, &data)); if (gctx->inprogress) { ssize_t sret; gctx->inprogress = 0; sret = krb5_storage_write(reply, data.data, data.length); INSIST(sret == data.length); krb5_data_free(&data); } else { int conf_state; gin.value = data.data; gin.length = data.length; maj_stat = gss_wrap(&min_stat, gctx->ctx, 1, 0, &gin, &conf_state, &gout); INSIST(maj_stat == GSS_S_COMPLETE); INSIST(conf_state != 0); krb5_data_free(&data); data.data = gout.value; data.length = gout.length; _kadm5_xdr_store_data_xdr(reply, data); gss_release_buffer(&min_stat, &gout); } } /* * GSSAPI flavor */ enum { RPGA_INIT = 1, RPGA_CONTINUE_INIT = 2, RPGA_MSG = 3, RPGA_DESTORY = 4 }; static void xpcgssapi_verify_header(struct gctx *gctx, struct _kadm5_xdr_call_header *chdr, krb5_data *header) { #if 0 OM_uint32 maj_stat, min_stat; gss_buffer_desc gin, gout; EXPECT(chdr->verf.flavor, gctx->protocol); /* from first byte to last of credential */ gin.value = header->data; gin.length = header->length; gout.value = chdr->verf.data.data; gout.length = chdr->verf.data.length; maj_stat = gss_verify_mic(&min_stat, gctx->ctx, &gin, &gout, NULL); EXPECT(maj_stat, GSS_S_COMPLETE); #endif } static void rpcgssapi_handle_protocol(struct gctx *gctx, struct _kadm5_xdr_call_header *chdr, krb5_storage *msg, krb5_storage *dreply) { struct _kadm5_xdr_gacred gacred; OM_uint32 maj_stat, min_stat, junk; gss_buffer_desc gin, gout, gseq; CHECK(_kadm5_xdr_ret_gacred(&chdr->cred.data, &gacred)); gseq.length = 0; gseq.value = NULL; if (gctx->done == 0 && chdr->proc == RPGA_INIT) { INSIST(gacred.handle.length == 0); INSIST(gctx->handle.length == 0); CHECK(krb5_data_alloc(&gctx->handle, 16)); CCRandomCopyBytes(kCCRandomDefault, gctx->handle.data, gctx->handle.length); } else { INSIST(gacred.handle.length != 0); INSIST(krb5_data_cmp(&gacred.handle, &gctx->handle) == 0); } if (gctx->done == 0) { uint32_t version; krb5_data token, out; CHECK(krb5_ret_uint32(msg, &version)); CHECK(_kadm5_xdr_ret_data_xdr(msg, &token)); switch (chdr->proc) { case RPGA_INIT: INSIST(gctx->inprogress == 0); INSIST(gctx->ctx == NULL); INSIST(version == 3 || version == 4); gctx->inprogress = 1; /* FALL THOUGH */ case RPGA_CONTINUE_INIT: { gss_name_t src_name = GSS_C_NO_NAME; INSIST(gctx->inprogress); gin.value = token.data; gin.length = token.length; gout.value = NULL; gout.length = 0; maj_stat = gss_accept_sec_context(&min_stat, &gctx->ctx, GSS_C_NO_CREDENTIAL, &gin, GSS_C_NO_CHANNEL_BINDINGS, &src_name, NULL, &gout, NULL, NULL, NULL); if (GSS_ERROR(maj_stat)) { gss_print_errors(context, maj_stat, min_stat); krb5_errx(context, 1, "gss error, exit"); } if ((maj_stat & GSS_S_CONTINUE_NEEDED) == 0) { uint32_t netseqnum; gctx->done = 1; gctx->verify_header = xpcgssapi_verify_header; setup_context(gctx, src_name); CCRandomCopyBytes(kCCRandomDefault, &gctx->seq_num, sizeof(gctx->seq_num)); netseqnum = htonl(gctx->seq_num); gin.value = &netseqnum; gin.length = sizeof(netseqnum); CHECK(gss_wrap(&junk, gctx->ctx, 0, GSS_C_QOP_DEFAULT, &gin, NULL, &gseq)); } INSIST(gctx->ctx != GSS_C_NO_CONTEXT); /* reply argument */ CHECK(krb5_store_uint32(dreply, version)); CHECK(_kadm5_xdr_store_data_xdr(dreply, gctx->handle)); CHECK(krb5_store_uint32(dreply, maj_stat)); CHECK(krb5_store_uint32(dreply, min_stat)); out.data = gout.value; out.length = gout.length; CHECK(_kadm5_xdr_store_data_xdr(dreply, out)); out.data = gseq.value; out.length = gseq.length; CHECK(_kadm5_xdr_store_data_xdr(dreply, out)); if (gout.value) gss_release_buffer(&junk, &gout); if (gseq.value) gss_release_buffer(&junk, &gseq); if (src_name) gss_release_name(&junk, &src_name); break; } default: krb5_errx(context, 1, "unsupported init message %d", (int)chdr->proc); } krb5_data_free(&token); } else if (gacred.auth_msg) { if (chdr->proc == RPGA_MSG) krb5_warnx(context, "auth message MSG not supported"); else if (chdr->proc == RPGA_DESTORY) krb5_warnx(context, "auth message DESTROY not supported"); else krb5_errx(context, 1, "auth message not supported: %d", (int)chdr->proc); } else { krb5_storage *sp; krb5_data data; int conf_state = 0; uint32_t seq; INSIST(gctx->done); CHECK(_kadm5_xdr_ret_data_xdr(msg, &data)); gin.value = data.data; gin.length = data.length; maj_stat = gss_unwrap(&min_stat, gctx->ctx, &gin, &gout, &conf_state, NULL); krb5_data_free(&data); INSIST(maj_stat == GSS_S_COMPLETE); INSIST(conf_state != 0); sp = krb5_storage_from_mem(gout.value, gout.length); INSIST(sp != NULL); CHECK(krb5_ret_uint32(sp, &seq)); /* * Check sequence number */ INSIST(seq == gctx->seq_num + 1); gctx->seq_num = seq + 1; /* * If context is setup, priv data have the seq_num stored * first in the block, so add it here before users data is * added. */ CHECK(krb5_store_uint32(dreply, gctx->seq_num)); if (chdr->proc >= sizeof(procs)/sizeof(procs[0])) { krb5_warnx(context, "proc number out of array"); } else if (procs[chdr->proc].func == NULL) { krb5_warnx(context, "proc '%s' never implemented", procs[chdr->proc].name); } else { krb5_warnx(context, "proc %s", procs[chdr->proc].name); INSIST(gctx->server_handle != NULL); (*procs[chdr->proc].func)(gctx->server_handle, sp, dreply); } krb5_storage_free(sp); gss_release_buffer(&min_stat, &gout); } krb5_data_free(&gacred.handle); } static void rpcgssapi_reply(struct gctx *gctx, krb5_storage *dreply, krb5_storage *reply) { uint32_t seqnum = htonl(gctx->seq_num); gss_buffer_desc gin, gout; OM_uint32 maj_stat, min_stat, junk; krb5_data data; gin.value = &seqnum; gin.length = sizeof(seqnum); maj_stat = gss_wrap(&min_stat, gctx->ctx, 0, GSS_C_QOP_DEFAULT, &gin, NULL, &gout); INSIST(maj_stat == GSS_S_COMPLETE); data.data = gout.value; data.length = gout.length; CHECK(krb5_store_uint32(reply, FLAVOR_GSS_OLD)); CHECK(_kadm5_xdr_store_data_xdr(reply, data)); gss_release_buffer(&junk, &gout); CHECK(krb5_store_uint32(reply, 0)); /* SUCCESS */ CHECK(krb5_storage_to_data(dreply, &data)); if (gctx->inprogress) { ssize_t sret; gctx->inprogress = 0; sret = krb5_storage_write(reply, data.data, data.length); INSIST(sret == data.length); krb5_data_free(&data); } else { int conf_state; gin.value = data.data; gin.length = data.length; maj_stat = gss_wrap(&min_stat, gctx->ctx, 1, 0, &gin, &conf_state, &gout); INSIST(maj_stat == GSS_S_COMPLETE); INSIST(conf_state != 0); krb5_data_free(&data); data.data = gout.value; data.length = gout.length; _kadm5_xdr_store_data_xdr(reply, data); gss_release_buffer(&min_stat, &gout); } } /* * */ static int process_stream(krb5_context lcontext, unsigned char *buf, size_t ilen, krb5_storage *sp) { krb5_error_code ret; krb5_storage *msg, *reply, *dreply; struct gctx gctx; memset(&gctx, 0, sizeof(gctx)); msg = krb5_storage_emem(); reply = krb5_storage_emem(); dreply = krb5_storage_emem(); /* * First packet comes partly from the caller */ INSIST(ilen >= 4); while (1) { struct _kadm5_xdr_call_header chdr; uint32_t mtype; krb5_data headercopy; krb5_storage_truncate(dreply, 0); krb5_storage_truncate(reply, 0); krb5_storage_truncate(msg, 0); krb5_data_zero(&headercopy); memset(&chdr, 0, sizeof(chdr)); /* * This is very icky to handle the the auto-detection between * the Heimdal protocol and the MIT ONC-RPC based protocol. */ if (ilen) { int last_fragment; unsigned long len; ssize_t slen; unsigned char tmp[4]; if (ilen < 4) { memcpy(tmp, buf, ilen); slen = krb5_storage_read(sp, tmp + ilen, sizeof(tmp) - ilen); INSIST(slen == sizeof(tmp) - ilen); ilen = sizeof(tmp); buf = tmp; } INSIST(ilen >= 4); _krb5_get_int(buf, &len, 4); last_fragment = (len & LAST_FRAGMENT) != 0; len &= ~LAST_FRAGMENT; ilen -= 4; buf += 4; if (ilen) { if (len < ilen) { slen = krb5_storage_write(msg, buf, len); INSIST(slen == len); ilen -= len; len = 0; } else { slen = krb5_storage_write(msg, buf, ilen); INSIST(slen == ilen); len -= ilen; } } CHECK(read_data(sp, msg, len)); if (!last_fragment) { ret = collect_fragments(sp, msg); if (ret == HEIM_ERR_EOF) krb5_errx(lcontext, 0, "client disconnected"); INSIST(ret == 0); } } else { ret = collect_fragments(sp, msg); if (ret == HEIM_ERR_EOF) krb5_errx(lcontext, 0, "client disconnected"); INSIST(ret == 0); } krb5_storage_seek(msg, 0, SEEK_SET); CHECK(krb5_ret_uint32(msg, &chdr.xid)); CHECK(krb5_ret_uint32(msg, &mtype)); CHECK(krb5_ret_uint32(msg, &chdr.rpcvers)); CHECK(krb5_ret_uint32(msg, &chdr.prog)); CHECK(krb5_ret_uint32(msg, &chdr.vers)); CHECK(krb5_ret_uint32(msg, &chdr.proc)); CHECK(_kadm5_xdr_ret_auth_opaque(msg, &chdr.cred)); CHECK(copyheader(msg, &headercopy)); CHECK(_kadm5_xdr_ret_auth_opaque(msg, &chdr.verf)); EXPECT(chdr.rpcvers, RPC_VERSION); EXPECT(chdr.prog, KADM_SERVER); EXPECT(chdr.vers, VVERSION); if (gctx.protocol == 0) { gctx.protocol = chdr.cred.flavor; INSIST(gctx.handle_protocol == NULL); switch(gctx.protocol) { case FLAVOR_GSS: gctx.handle_protocol = rpcgss_handle_protocol; gctx.reply = rpcgss_reply; break; case FLAVOR_GSS_OLD: gctx.handle_protocol = rpcgssapi_handle_protocol; gctx.reply = rpcgssapi_reply; break; default: krb5_errx(lcontext, 0, "unsupported protocol version: %d", (int)gctx.protocol); } } else { EXPECT(chdr.cred.flavor, gctx.protocol); } if (gctx.verify_header) gctx.verify_header(&gctx, &chdr, &headercopy); gctx.handle_protocol(&gctx, &chdr, msg, dreply); krb5_data_free(&chdr.cred.data); krb5_data_free(&chdr.verf.data); krb5_data_free(&headercopy); CHECK(krb5_store_uint32(reply, chdr.xid)); CHECK(krb5_store_uint32(reply, 1)); /* REPLY */ CHECK(krb5_store_uint32(reply, 0)); /* MSG_ACCEPTED */ if (!gctx.done) { krb5_data data; CHECK(krb5_store_uint32(reply, 0)); /* flavor_none */ CHECK(krb5_store_uint32(reply, 0)); /* length */ CHECK(krb5_store_uint32(reply, 0)); /* SUCCESS */ CHECK(krb5_storage_to_data(dreply, &data)); INSIST(krb5_storage_write(reply, data.data, data.length) == data.length); krb5_data_free(&data); } else { INSIST(gctx.reply != NULL); gctx.reply(&gctx, dreply, reply); } { krb5_data data; ssize_t sret; CHECK(krb5_storage_to_data(reply, &data)); CHECK(krb5_store_uint32(sp, data.length | LAST_FRAGMENT)); sret = krb5_storage_write(sp, data.data, data.length); INSIST(sret == data.length); krb5_data_free(&data); } } } int handle_mit(krb5_context lcontext, void *buf, size_t len, krb5_socket_t sock) { krb5_storage *sp; dcontext = context; sp = krb5_storage_from_fd(sock); INSIST(sp != NULL); process_stream(lcontext, buf, len, sp); return 0; }