#include "kadmin_locl.h"
#include <kadm5/private.h>
#include <gssapi.h>
#include <gssapi_krb5.h>
#include <gssapi_spnego.h>
#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
};
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;
if (memcmp(p, "\x04\x01", 2) != 0)
return 1;
len -= 2;
p += 2;
l = (p[0] << 8) | p[1];
len -= 2;
p += 2;
if (l < 2 || len < l)
return 1;
if (p[0] != 6 || p[1] != l - 2)
return 1;
p += 2;
l -= 2;
len -= 2;
if (l != oid->length || memcmp(p, oid->elements, oid->length) != 0)
return 1;
len -= l;
p += l;
if (len < 4)
return 1;
l = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
len -= 4;
p += 4;
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 : "<noprinc>");
CHECK(krb5_store_uint32(out, VERSION2));
CHECK(krb5_store_uint32(out, ret));
free(password);
free(princ);
kadm5_free_principal_ent(context, &ent);
}
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 : "<noprinc>");
CHECK(krb5_store_uint32(out, VERSION2));
CHECK(krb5_store_uint32(out, ret));
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 : "<noprinc>");
CHECK(krb5_store_uint32(out, VERSION2));
CHECK(krb5_store_uint32(out, ret));
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));
CHECK(krb5_store_uint32(out, ret));
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 : "<unknown>", (int)ent.kvno);
CHECK(krb5_store_uint32(out, VERSION2));
CHECK(krb5_store_uint32(out, ret));
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 : "<unknown>");
CHECK(krb5_store_uint32(out, VERSION2));
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 : "<unknown>");
CHECK(krb5_store_uint32(out, VERSION2));
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));
CHECK(krb5_store_uint32(out, 0));
CHECK(krb5_store_uint32(out, 0));
}
static void
proc_get_policy(kadm5_server_context *lcontext,
krb5_storage *in,
krb5_storage *out)
{
CHECK(krb5_store_uint32(out, VERSION2));
CHECK(krb5_store_uint32(out, KADM5_AUTH_GET));
}
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);
}
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);
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);
INSIST(seq > gctx->seq_num);
gctx->seq_num = seq;
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;
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;
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));
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);
}
}
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);
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;
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);
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));
INSIST(seq == gctx->seq_num + 1);
gctx->seq_num = seq + 1;
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));
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();
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));
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));
CHECK(krb5_store_uint32(reply, 0));
if (!gctx.done) {
krb5_data data;
CHECK(krb5_store_uint32(reply, 0));
CHECK(krb5_store_uint32(reply, 0));
CHECK(krb5_store_uint32(reply, 0));
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, ((uint32_t)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;
}