/*
* 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 <gssapi/gssapi.h>
//#include <gssapi_krb5.h>
//#include <gssapi_spnego.h>
static gss_OID_desc krb5_mechanism =
{9, (void *)(uintptr_t) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
#define GSS_KRB5_MECHANISM (&krb5_mechanism)
#define CHECK(x) \
do { \
int __r; \
if ((__r = (x))) { \
krb5_errx(dcontext, 1, "Failed (%d) on %s:%d", \
__r, __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
struct opaque_auth {
uint32_t flavor;
krb5_data data;
};
struct call_header {
uint32_t xid;
uint32_t rpcvers;
uint32_t prog;
uint32_t vers;
uint32_t proc;
struct opaque_auth cred;
struct opaque_auth verf;
};
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;
};
*/
struct gcred {
uint32_t version;
uint32_t proc;
uint32_t seq_num;
uint32_t service;
krb5_data handle;
};
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 contextp,
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(contextp, "%.*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 contextp,
OM_uint32 maj_stat, OM_uint32 min_stat)
{
gss_error(contextp, GSS_C_NO_OID, GSS_C_GSS_CODE, maj_stat);
gss_error(contextp, 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((size_t)slen == tlen);
slen = krb5_storage_write(msg, buf, tlen);
INSIST((size_t)slen == tlen);
len -= tlen;
}
return 0;
}
static int
collect_framents(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 krb5_error_code
store_data_xdr(krb5_storage *sp, krb5_data data)
{
krb5_error_code ret;
size_t res;
ret = krb5_store_data(sp, data);
if (ret)
return ret;
res = 4 - (data.length % 4);
if (res != 4) {
static const char zero[4] = { 0, 0, 0, 0 };
ret = krb5_storage_write(sp, zero, res);
if((size_t)ret != res)
return (ret < 0)? errno : krb5_storage_get_eof_code(sp);
}
return 0;
}
static krb5_error_code
ret_data_xdr(krb5_storage *sp, krb5_data *data)
{
krb5_error_code ret;
ret = krb5_ret_data(sp, data);
if (ret)
return ret;
if ((data->length % 4) != 0) {
char buf[4];
size_t res;
res = 4 - (data->length % 4);
if (res != 4) {
ret = krb5_storage_read(sp, buf, res);
if((size_t)ret != res)
return (ret < 0)? errno : krb5_storage_get_eof_code(sp);
}
}
return 0;
}
static krb5_error_code
ret_auth_opaque(krb5_storage *msg, struct opaque_auth *ao)
{
krb5_error_code ret;
ret = krb5_ret_uint32(msg, &ao->flavor);
if (ret) return ret;
ret = ret_data_xdr(msg, &ao->data);
return ret;
}
static int
ret_gcred(krb5_data *data, struct gcred *gcred)
{
krb5_storage *sp;
memset(gcred, 0, sizeof(*gcred));
sp = krb5_storage_from_data(data);
INSIST(sp != NULL);
CHECK(krb5_ret_uint32(sp, &gcred->version));
CHECK(krb5_ret_uint32(sp, &gcred->proc));
CHECK(krb5_ret_uint32(sp, &gcred->seq_num));
CHECK(krb5_ret_uint32(sp, &gcred->service));
CHECK(ret_data_xdr(sp, &gcred->handle));
krb5_storage_free(sp);
return 0;
}
static krb5_error_code
store_gss_init_res(krb5_storage *sp, krb5_data handle,
OM_uint32 maj_stat, OM_uint32 min_stat,
uint32_t seq_window, gss_buffer_t gout)
{
krb5_error_code ret;
krb5_data out;
out.data = gout->value;
out.length = gout->length;
ret = store_data_xdr(sp, handle);
if (ret) return ret;
ret = krb5_store_uint32(sp, maj_stat);
if (ret) return ret;
ret = krb5_store_uint32(sp, min_stat);
if (ret) return ret;
ret = store_data_xdr(sp, out);
return ret;
}
static int
store_string_xdr(krb5_storage *sp, const char *str)
{
krb5_data c;
if (str) {
c.data = rk_UNCONST(str);
c.length = strlen(str) + 1;
} else
krb5_data_zero(&c);
return store_data_xdr(sp, c);
}
static int
ret_string_xdr(krb5_storage *sp, char **str)
{
krb5_data c;
*str = NULL;
CHECK(ret_data_xdr(sp, &c));
if (c.length) {
*str = malloc(c.length + 1);
INSIST(*str != NULL);
memcpy(*str, c.data, c.length);
(*str)[c.length] = '\0';
}
krb5_data_free(&c);
return 0;
}
static int
store_principal_xdr(krb5_context contextp,
krb5_storage *sp,
krb5_principal p)
{
char *str;
CHECK(krb5_unparse_name(contextp, p, &str));
CHECK(store_string_xdr(sp, str));
free(str);
return 0;
}
static int
ret_principal_xdr(krb5_context contextp,
krb5_storage *sp,
krb5_principal *p)
{
char *str;
*p = NULL;
CHECK(ret_string_xdr(sp, &str));
if (str) {
CHECK(krb5_parse_name(contextp, str, p));
free(str);
}
return 0;
}
static int
store_principal_ent(krb5_context contextp,
krb5_storage *sp,
kadm5_principal_ent_rec *ent)
{
int i;
CHECK(store_principal_xdr(contextp, sp, ent->principal));
CHECK(krb5_store_uint32(sp, ent->princ_expire_time));
CHECK(krb5_store_uint32(sp, ent->pw_expiration));
CHECK(krb5_store_uint32(sp, ent->last_pwd_change));
CHECK(krb5_store_uint32(sp, ent->max_life));
CHECK(krb5_store_int32(sp, ent->mod_name == NULL));
if (ent->mod_name)
CHECK(store_principal_xdr(contextp, sp, ent->mod_name));
CHECK(krb5_store_uint32(sp, ent->mod_date));
CHECK(krb5_store_uint32(sp, ent->attributes));
CHECK(krb5_store_uint32(sp, ent->kvno));
CHECK(krb5_store_uint32(sp, ent->mkvno));
CHECK(store_string_xdr(sp, ent->policy));
CHECK(krb5_store_int32(sp, ent->aux_attributes));
CHECK(krb5_store_int32(sp, ent->max_renewable_life));
CHECK(krb5_store_int32(sp, ent->last_success));
CHECK(krb5_store_int32(sp, ent->last_failed));
CHECK(krb5_store_int32(sp, ent->fail_auth_count));
CHECK(krb5_store_int32(sp, ent->n_key_data));
CHECK(krb5_store_int32(sp, ent->n_tl_data));
CHECK(krb5_store_int32(sp, ent->n_tl_data == 0));
if (ent->n_tl_data) {
krb5_tl_data *tp;
for (tp = ent->tl_data; tp; tp = tp->tl_data_next) {
krb5_data c;
c.length = tp->tl_data_length;
c.data = tp->tl_data_contents;
CHECK(krb5_store_int32(sp, 0)); /* last item */
CHECK(krb5_store_int32(sp, tp->tl_data_type));
CHECK(store_data_xdr(sp, c));
}
CHECK(krb5_store_int32(sp, 1)); /* last item */
}
CHECK(krb5_store_int32(sp, ent->n_key_data));
for (i = 0; i < ent->n_key_data; i++) {
CHECK(krb5_store_uint32(sp, 2));
CHECK(krb5_store_uint32(sp, ent->kvno));
CHECK(krb5_store_uint32(sp, ent->key_data[i].key_data_type[0]));
CHECK(krb5_store_uint32(sp, ent->key_data[i].key_data_type[1]));
}
return 0;
}
static int
ret_principal_ent(krb5_context contextp,
krb5_storage *sp,
kadm5_principal_ent_rec *ent)
{
uint32_t flag, num;
size_t i;
memset(ent, 0, sizeof(*ent));
CHECK(ret_principal_xdr(contextp, sp, &ent->principal));
CHECK(krb5_ret_uint32(sp, &flag));
ent->princ_expire_time = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->pw_expiration = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->last_pwd_change = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->max_life = flag;
CHECK(krb5_ret_uint32(sp, &flag));
if (flag == 0)
ret_principal_xdr(contextp, sp, &ent->mod_name);
CHECK(krb5_ret_uint32(sp, &flag));
ent->mod_date = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->attributes = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->kvno = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->mkvno = flag;
CHECK(ret_string_xdr(sp, &ent->policy));
CHECK(krb5_ret_uint32(sp, &flag));
ent->aux_attributes = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->max_renewable_life = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->last_success = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->last_failed = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->fail_auth_count = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->n_key_data = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->n_tl_data = flag;
CHECK(krb5_ret_uint32(sp, &flag));
if (flag == 0) {
krb5_tl_data **tp = &ent->tl_data;
size_t count = 0;
while(1) {
krb5_data c;
CHECK(krb5_ret_uint32(sp, &flag)); /* last item */
if (flag)
break;
*tp = calloc(1, sizeof(**tp));
INSIST(*tp != NULL);
CHECK(krb5_ret_uint32(sp, &flag));
(*tp)->tl_data_type = flag;
CHECK(ret_data_xdr(sp, &c));
(*tp)->tl_data_length = c.length;
(*tp)->tl_data_contents = c.data;
tp = &(*tp)->tl_data_next;
count++;
}
INSIST((size_t)ent->n_tl_data == count);
} else {
INSIST(ent->n_tl_data == 0);
}
CHECK(krb5_ret_uint32(sp, &num));
INSIST(num == (uint32_t)ent->n_key_data);
ent->key_data = calloc(num, sizeof(ent->key_data[0]));
INSIST(ent->key_data != NULL);
for (i = 0; i < num; i++) {
CHECK(krb5_ret_uint32(sp, &flag)); /* data version */
INSIST(flag > 1);
CHECK(krb5_ret_uint32(sp, &flag));
ent->kvno = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->key_data[i].key_data_type[0] = flag;
CHECK(krb5_ret_uint32(sp, &flag));
ent->key_data[i].key_data_type[1] = flag;
}
return 0;
}
/*
*
*/
static void
proc_create_principal(kadm5_server_context *contextp,
krb5_storage *in,
krb5_storage *out)
{
uint32_t version, mask;
kadm5_principal_ent_rec ent;
krb5_error_code ret;
char *password;
memset(&ent, 0, sizeof(ent));
CHECK(krb5_ret_uint32(in, &version));
INSIST(version == VERSION2);
CHECK(ret_principal_ent(contextp->context, in, &ent));
CHECK(krb5_ret_uint32(in, &mask));
CHECK(ret_string_xdr(in, &password));
INSIST(ent.principal);
ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD, ent.principal);
if (ret)
goto fail;
ret = kadm5_create_principal(contextp, &ent, mask, password);
fail:
krb5_warn(contextp->context, ret, "create principal");
CHECK(krb5_store_uint32(out, VERSION2)); /* api version */
CHECK(krb5_store_uint32(out, ret)); /* code */
free(password);
kadm5_free_principal_ent(contextp, &ent);
}
static void
proc_delete_principal(kadm5_server_context *contextp,
krb5_storage *in,
krb5_storage *out)
{
uint32_t version;
krb5_principal princ;
krb5_error_code ret;
CHECK(krb5_ret_uint32(in, &version));
INSIST(version == VERSION2);
CHECK(ret_principal_xdr(contextp->context, in, &princ));
ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ);
if (ret)
goto fail;
ret = kadm5_delete_principal(contextp, princ);
fail:
krb5_warn(contextp->context, ret, "delete principal");
CHECK(krb5_store_uint32(out, VERSION2)); /* api version */
CHECK(krb5_store_uint32(out, ret)); /* code */
krb5_free_principal(contextp->context, princ);
}
static void
proc_get_principal(kadm5_server_context *contextp,
krb5_storage *in,
krb5_storage *out)
{
uint32_t version, mask;
krb5_principal princ;
kadm5_principal_ent_rec ent;
krb5_error_code ret;
memset(&ent, 0, sizeof(ent));
CHECK(krb5_ret_uint32(in, &version));
INSIST(version == VERSION2);
CHECK(ret_principal_xdr(contextp->context, in, &princ));
CHECK(krb5_ret_uint32(in, &mask));
ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ);
if(ret)
goto fail;
ret = kadm5_get_principal(contextp, princ, &ent, mask);
fail:
krb5_warn(contextp->context, ret, "get principal principal");
CHECK(krb5_store_uint32(out, VERSION2)); /* api version */
CHECK(krb5_store_uint32(out, ret)); /* code */
if (ret == 0) {
CHECK(store_principal_ent(contextp->context, out, &ent));
}
krb5_free_principal(contextp->context, princ);
kadm5_free_principal_ent(contextp, &ent);
}
static void
proc_chrand_principal_v2(kadm5_server_context *contextp,
krb5_storage *in,
krb5_storage *out)
{
krb5_error_code ret;
krb5_principal princ;
uint32_t version;
krb5_keyblock *new_keys;
int n_keys;
CHECK(krb5_ret_uint32(in, &version));
INSIST(version == VERSION2);
CHECK(ret_principal_xdr(contextp->context, in, &princ));
ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
if(ret)
goto fail;
ret = kadm5_randkey_principal(contextp, princ,
&new_keys, &n_keys);
fail:
krb5_warn(contextp->context, ret, "rand key principal");
CHECK(krb5_store_uint32(out, VERSION2)); /* api version */
CHECK(krb5_store_uint32(out, ret));
if (ret == 0) {
int 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(store_data_xdr(out, new_keys[i].keyvalue));
krb5_free_keyblock_contents(contextp->context, &new_keys[i]);
}
free(new_keys);
}
krb5_free_principal(contextp->context, princ);
}
static void
proc_init(kadm5_server_context *contextp,
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 */
}
struct krb5_proc {
const 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", NULL },
{ "rename principal", NULL },
{ "get principal", proc_get_principal },
{ "chpass principal", NULL },
{ "chrand principal", proc_chrand_principal_v2 },
{ "create policy", NULL },
{ "delete policy", NULL },
{ "modify policy", NULL },
{ "get policy", NULL },
{ "get privs", NULL },
{ "init", proc_init },
{ "get principals", NULL },
{ "get polices", NULL },
{ "setkey principal", NULL },
{ "setkey principal v4", NULL },
{ "create principal v3", NULL },
{ "chpass principal v3", NULL },
{ "chrand principal v3", NULL },
{ "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((size_t)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;
int done;
int inprogress;
};
static int
process_stream(krb5_context contextp,
unsigned char *buf, size_t ilen,
krb5_storage *sp)
{
krb5_error_code ret;
krb5_storage *msg, *reply, *dreply;
OM_uint32 maj_stat, min_stat;
gss_buffer_desc gin, gout;
struct gctx gctx;
void *server_handle = NULL;
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 call_header chdr;
struct gcred gcred;
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));
memset(&gcred, 0, sizeof(gcred));
/*
* 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((size_t)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((size_t)slen == len);
ilen -= len;
len = 0;
} else {
slen = krb5_storage_write(msg, buf, ilen);
INSIST((size_t)slen == ilen);
len -= ilen;
}
}
CHECK(read_data(sp, msg, len));
if (!last_fragment) {
ret = collect_framents(sp, msg);
if (ret == HEIM_ERR_EOF)
krb5_errx(contextp, 0, "client disconnected");
INSIST(ret == 0);
}
} else {
ret = collect_framents(sp, msg);
if (ret == HEIM_ERR_EOF)
krb5_errx(contextp, 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(ret_auth_opaque(msg, &chdr.cred));
CHECK(copyheader(msg, &headercopy));
CHECK(ret_auth_opaque(msg, &chdr.verf));
INSIST(chdr.rpcvers == RPC_VERSION);
INSIST(chdr.prog == KADM_SERVER);
INSIST(chdr.vers == VVERSION);
INSIST(chdr.cred.flavor == FLAVOR_GSS);
CHECK(ret_gcred(&chdr.cred.data, &gcred));
INSIST(gcred.version == FLAVOR_GSS_VERSION);
if (gctx.done) {
INSIST(chdr.verf.flavor == FLAVOR_GSS);
/* from first byte to last of credential */
gin.value = headercopy.data;
gin.length = headercopy.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);
INSIST(maj_stat == GSS_S_COMPLETE);
}
switch(gcred.proc) {
case RPG_DATA: {
krb5_data data;
int conf_state;
uint32_t seq;
krb5_storage *sp1;
INSIST(gcred.service == rpg_privacy);
INSIST(gctx.done);
INSIST(krb5_data_cmp(&gcred.handle, &gctx.handle) == 0);
CHECK(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);
sp1 = krb5_storage_from_mem(gout.value, gout.length);
INSIST(sp1 != NULL);
CHECK(krb5_ret_uint32(sp1, &seq));
INSIST (seq == gcred.seq_num);
/*
* Check sequence number
*/
INSIST(seq > gctx.seq_num);
gctx.seq_num = seq;
/*
* If contextp 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(contextp, "proc number out of array");
} else if (procs[chdr.proc].func == NULL) {
krb5_warnx(contextp, "proc '%s' never implemented",
procs[chdr.proc].name);
} else {
krb5_warnx(contextp, "proc %s", procs[chdr.proc].name);
INSIST(server_handle != NULL);
(*procs[chdr.proc].func)(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(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(contextp, maj_stat, min_stat);
krb5_errx(contextp, 1, "gss error, exit");
}
if ((maj_stat & GSS_S_CONTINUE_NEEDED) == 0) {
kadm5_config_params realm_params;
gss_buffer_desc bufp;
char *client;
gctx.done = 1;
memset(&realm_params, 0, sizeof(realm_params));
maj_stat = gss_export_name(&min_stat, src_name, &bufp);
INSIST(maj_stat == GSS_S_COMPLETE);
CHECK(parse_name(bufp.value, bufp.length,
GSS_KRB5_MECHANISM, &client));
gss_release_buffer(&min_stat, &bufp);
krb5_warnx(contextp, "%s connected", client);
ret = kadm5_s_init_with_password_ctx(contextp,
client,
NULL,
KADM5_ADMIN_SERVICE,
&realm_params,
0, 0,
&server_handle);
INSIST(ret == 0);
}
INSIST(gctx.ctx != GSS_C_NO_CONTEXT);
CHECK(krb5_store_uint32(dreply, 0));
CHECK(store_gss_init_res(dreply, gctx.handle,
maj_stat, min_stat, 1, &gout));
if (gout.value)
gss_release_buffer(&min_stat, &gout);
if (src_name)
gss_release_name(&min_stat, &src_name);
break;
}
case RPG_DESTROY:
krb5_errx(contextp, 1, "client destroyed gss contextp");
default:
krb5_errx(contextp, 1, "client sent unknown gsscode %d",
(int)gcred.proc);
}
krb5_data_free(&gcred.handle);
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((size_t)krb5_storage_write(reply, data.data, data.length) == data.length);
krb5_data_free(&data);
} else {
uint32_t seqnum = htonl(gctx.seq_num);
krb5_data data;
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(store_data_xdr(reply, data));
gss_release_buffer(&min_stat, &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((size_t)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;
store_data_xdr(reply, data);
gss_release_buffer(&min_stat, &gout);
}
}
{
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((size_t)sret == data.length);
krb5_data_free(&data);
}
}
}
int
handle_mit(krb5_context contextp, void *buf, size_t len, krb5_socket_t sock)
{
krb5_storage *sp;
dcontext = contextp;
sp = krb5_storage_from_fd(sock);
INSIST(sp != NULL);
process_stream(contextp, buf, len, sp);
return 0;
}