#include "kcm_locl.h"
#include <heimntlm.h>
#include <heimscram.h>
static void
kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name);
int
kcm_is_same_session(kcm_client *client, uid_t uid, pid_t session)
{
if (use_uid_matching && client->uid != 0 && client->uid == uid) {
kcm_log(1, "allowed (uid matching)");
return 1;
} else if (client->session == session) {
kcm_log(1, "allowed (session matching)");
return 1;
}
kcm_log(1, "denied");
return 0;
}
static krb5_error_code
kcm_op_noop(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
KCM_LOG_REQUEST(context, client, opcode);
return 0;
}
static krb5_error_code
kcm_op_get_name(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
char *name = NULL;
kcm_ccache ccache;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
return ret;
}
ret = krb5_store_stringz(response, ccache->name);
if (ret) {
kcm_release_ccache(context, ccache);
free(name);
return ret;
}
free(name);
kcm_release_ccache(context, ccache);
return 0;
}
static krb5_error_code
kcm_op_gen_new(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
char *name;
KCM_LOG_REQUEST(context, client, opcode);
name = kcm_ccache_nextid(client->pid, client->uid);
if (name == NULL) {
return KRB5_CC_NOMEM;
}
ret = krb5_store_stringz(response, name);
free(name);
return ret;
}
static krb5_error_code
kcm_op_initialize(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
kcm_ccache ccache;
krb5_principal principal;
krb5_error_code ret;
char *name;
#if 0
kcm_event event;
#endif
KCM_LOG_REQUEST(context, client, opcode);
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
ret = krb5_ret_principal(request, &principal);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_new_client(context, client, name, &ccache);
if (ret) {
free(name);
krb5_free_principal(context, principal);
return ret;
}
ccache->client = principal;
free(name);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_destroy(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_destroy_client(context, client, name);
if (ret == 0)
kcm_drop_default_cache(context, client, name);
free(name);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_store(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_creds creds;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_creds(request, &creds);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
krb5_free_cred_contents(context, &creds);
return ret;
}
ret = kcm_ccache_store_cred(context, ccache, &creds, 0);
if (ret) {
free(name);
krb5_free_cred_contents(context, &creds);
kcm_release_ccache(context, ccache);
return ret;
}
if (creds.client && krb5_principal_is_root_krbtgt(context, creds.server))
kcm_ccache_enqueue_default(context, ccache, &creds);
free(name);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
return 0;
}
static krb5_error_code
kcm_op_retrieve(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint32_t flags;
krb5_creds mcreds;
krb5_error_code ret;
kcm_ccache ccache;
char *name = NULL;
krb5_creds *credp;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint32(request, &flags);
if (ret) {
goto out;
}
if (flags & KRB5_TC_MATCH_REFERRAL)
flags |= KRB5_TC_DONT_MATCH_REALM;
ret = krb5_ret_creds_tag(request, &mcreds);
if (ret) {
goto out;
}
if (disallow_getting_krbtgt &&
mcreds.server->name.name_string.len == 2 &&
strcmp(mcreds.server->name.name_string.val[0], KRB5_TGS_NAME) == 0)
{
krb5_free_cred_contents(context, &mcreds);
ret = KRB5_FCC_PERM;
goto out;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
krb5_free_cred_contents(context, &mcreds);
goto out;
}
ret = kcm_ccache_retrieve_cred(context, ccache, flags,
&mcreds, &credp);
if (ret == 0)
ret = krb5_store_creds(response, credp);
kcm_release_ccache(context, ccache);
krb5_free_cred_contents(context, &mcreds);
out:
free(name);
return ret;
}
static krb5_error_code
kcm_op_get_principal(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
return ret;
}
if (ccache->client == NULL)
ret = KRB5_CC_NOTFOUND;
else
ret = krb5_store_principal(response, ccache->client);
free(name);
kcm_release_ccache(context, ccache);
return ret;
}
static krb5_error_code
kcm_op_get_cred_uuid_list(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_creds *creds;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
free(name);
if (ret)
return ret;
for (creds = ccache->creds ; creds ; creds = creds->next) {
ssize_t sret;
sret = krb5_storage_write(response, &creds->uuid, sizeof(creds->uuid));
if (sret != sizeof(creds->uuid)) {
ret = ENOMEM;
break;
}
}
kcm_release_ccache(context, ccache);
return ret;
}
static krb5_error_code
kcm_op_get_cred_by_uuid(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
struct kcm_creds *c;
kcmuuid_t uuid;
ssize_t sret;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
free(name);
if (ret)
return ret;
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
kcm_release_ccache(context, ccache);
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
c = kcm_ccache_find_cred_uuid(context, ccache, uuid);
if (c == NULL) {
kcm_release_ccache(context, ccache);
return KRB5_CC_END;
}
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = krb5_store_creds(response, &c->cred);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
return ret;
}
static krb5_error_code
kcm_op_remove_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint32_t whichfields;
krb5_creds mcreds;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint32(request, &whichfields);
if (ret) {
free(name);
return ret;
}
ret = krb5_ret_creds_tag(request, &mcreds);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
krb5_free_cred_contents(context, &mcreds);
return ret;
}
ret = kcm_ccache_remove_cred(context, ccache, whichfields, &mcreds);
free(name);
krb5_free_cred_contents(context, &mcreds);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_set_flags(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint32_t flags;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint32(request, &flags);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
return ret;
}
free(name);
kcm_release_ccache(context, ccache);
return 0;
}
static krb5_error_code
kcm_op_chown(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint32_t uid;
uint32_t gid;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint32(request, &uid);
if (ret) {
free(name);
return ret;
}
ret = krb5_ret_uint32(request, &gid);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
return ret;
}
free(name);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_chmod(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint16_t mode;
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint16(request, &mode);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
free(name);
return ret;
}
ret = kcm_chmod(context, client, ccache, mode);
free(name);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_get_initial_ticket(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
char *name, *password;
krb5_error_code ret;
kcm_ccache ccache;
int8_t not_tgt = 0;
krb5_principal cprincipal = NULL;
krb5_principal server = NULL;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_principal(request, &cprincipal);
if (ret) {
free(name);
return ret;
}
ret = krb5_ret_int8(request, ¬_tgt);
if (ret) {
free(name);
return ret;
}
if (not_tgt) {
ret = krb5_ret_principal(request, &server);
} else {
ret = krb5_make_principal(context,&server, cprincipal->realm,
KRB5_TGS_NAME, cprincipal->realm,
NULL);
}
if (ret) {
krb5_free_principal(context, cprincipal);
free(name);
return ret;
}
ret = krb5_ret_stringz(request, &password);
if (ret) {
free(name);
krb5_free_principal(context, cprincipal);
if (server != NULL)
krb5_free_principal(context, server);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret == 0) {
HEIMDAL_MUTEX_lock(&ccache->mutex);
if (ccache->client)
krb5_free_principal(context, ccache->client);
if (ccache->server)
krb5_free_principal(context, ccache->server);
if (ccache->password) {
memset(ccache->password, 0, strlen(ccache->password));
free(ccache->password);
}
ccache->client = cprincipal;
ccache->server = server;
ccache->password = password;
ccache->flags |= KCM_FLAGS_USE_PASSWORD;
ccache->renew_life = 3600 * 24 * 7;
kcm_ccache_update_acquire_status(kcm_context, ccache, KCM_STATUS_ACQUIRE_START, 0);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
kcm_data_changed = 1;
} else {
krb5_free_principal(context, cprincipal);
if (server)
krb5_free_principal(context, server);
memset(password, 0, strlen(password));
free(password);
}
free(name);
return ret;
}
static krb5_error_code
kcm_op_get_ticket(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
krb5_principal server = NULL;
krb5_ccache_data ccdata;
krb5_creds in, *out;
krb5_kdc_flags flags;
memset(&in, 0, sizeof(in));
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_uint32(request, &flags.i);
if (ret) {
free(name);
return ret;
}
ret = krb5_ret_int32(request, &in.session.keytype);
if (ret) {
free(name);
return ret;
}
ret = krb5_ret_principal(request, &server);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode,
name, &ccache);
if (ret) {
krb5_free_principal(context, server);
free(name);
return ret;
}
HEIMDAL_MUTEX_lock(&ccache->mutex);
kcm_internal_ccache(context, ccache, &ccdata);
in.client = ccache->client;
in.server = server;
in.times.endtime = 0;
ret = krb5_get_credentials_with_flags(context, 0, flags,
&ccdata, &in, &out);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
krb5_free_principal(context, server);
if (ret == 0)
krb5_free_cred_contents(context, out);
kcm_release_ccache(context, ccache);
free(name);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_move_cache(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache oldid, newid;
char *oldname, *newname;
ret = krb5_ret_stringz(request, &oldname);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, oldname);
ret = krb5_ret_stringz(request, &newname);
if (ret) {
free(oldname);
return ret;
}
if (strcmp(newname, oldname) == 0) {
free(oldname);
free(newname);
return 0;
}
ret = kcm_ccache_resolve_client(context, client, opcode, oldname, &oldid);
if (ret) {
free(oldname);
free(newname);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode, newname, &newid);
if (ret == KRB5_FCC_NOFILE)
ret = kcm_ccache_new_client(context, client, newname, &newid);
free(newname);
if (ret) {
free(oldname);
kcm_release_ccache(context, oldid);
return ret;
}
HEIMDAL_MUTEX_lock(&oldid->mutex);
HEIMDAL_MUTEX_lock(&newid->mutex);
{
struct kcm_ccache_data tmp;
#define MOVE(n,o,f) { tmp.f = n->f ; n->f = o->f; o->f = tmp.f; }
MOVE(newid, oldid, flags);
MOVE(newid, oldid, client);
MOVE(newid, oldid, server);
MOVE(newid, oldid, creds);
MOVE(newid, oldid, tkt_life);
MOVE(newid, oldid, renew_life);
MOVE(newid, oldid, password);
MOVE(newid, oldid, keytab);
MOVE(newid, oldid, kdc_offset);
MOVE(newid, oldid, expire);
#undef MOVE
}
kcm_update_renew_time(newid);
if (newid->expire && (newid->flags & KCM_MASK_KEY_PRESENT) == 0 && time(NULL) < newid->expire)
kcm_update_expire_time(newid, newid->expire);
HEIMDAL_MUTEX_unlock(&oldid->mutex);
HEIMDAL_MUTEX_unlock(&newid->mutex);
kcm_release_ccache(context, oldid);
kcm_release_ccache(context, newid);
ret = kcm_ccache_destroy_client(context, client, oldname);
if (ret == 0)
kcm_drop_default_cache(context, client, oldname);
free(oldname);
kcm_data_changed = 1;
return ret;
}
static krb5_error_code
kcm_op_get_cache_uuid_list(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
KCM_LOG_REQUEST(context, client, opcode);
return kcm_ccache_get_uuids(context, client, opcode, response);
}
static krb5_error_code
kcm_op_get_cache_by_uuid(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcmuuid_t uuid;
ssize_t sret;
kcm_ccache cache;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
ret = kcm_ccache_resolve_by_uuid(context, uuid, &cache);
if (ret)
return ret;
ret = kcm_access(context, client, opcode, cache);
if (ret)
ret = KRB5_FCC_NOFILE;
if (ret == 0)
ret = krb5_store_stringz(response, cache->name);
kcm_release_ccache(context, cache);
return ret;
}
struct kcm_default_cache *default_caches;
static krb5_error_code
kcm_op_get_default_cache(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_default_cache *c;
krb5_error_code ret;
const char *name = NULL;
char *n = NULL;
KCM_LOG_REQUEST(context, client, opcode);
for (c = default_caches; c != NULL; c = c->next) {
if (kcm_is_same_session(client, c->uid, c->session)) {
name = c->name;
break;
}
}
if (name == NULL)
name = n = kcm_ccache_first_name(client);
if (name == NULL) {
asprintf(&n, "%d", (int)client->uid);
name = n;
}
if (name == NULL)
return ENOMEM;
ret = krb5_store_stringz(response, name);
if (n)
free(n);
return ret;
}
static void
kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name)
{
struct kcm_default_cache **c;
for (c = &default_caches; *c != NULL; c = &(*c)->next) {
if (!kcm_is_same_session(client, (*c)->uid, (*c)->session))
continue;
if (strcmp((*c)->name, name) == 0) {
struct kcm_default_cache *h = *c;
*c = (*c)->next;
free(h->name);
free(h);
break;
}
}
}
static krb5_error_code
kcm_op_set_default_cache(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_default_cache *c;
krb5_error_code ret;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
for (c = default_caches; c != NULL; c = c->next) {
if (kcm_is_same_session(client, c->uid, c->session))
break;
}
if (c == NULL) {
c = calloc(1, sizeof(*c));
if (c == NULL) {
free(name);
return ENOMEM;
}
c->session = client->session;
c->uid = client->uid;
c->name = name;
c->next = default_caches;
default_caches = c;
} else {
free(c->name);
c->name = name;
}
return 0;
}
static krb5_error_code
kcm_op_get_kdc_offset(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
free(name);
if (ret)
return ret;
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = krb5_store_int32(response, ccache->kdc_offset);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
return ret;
}
static krb5_error_code
kcm_op_set_kdc_offset(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
int32_t offset;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = krb5_ret_int32(request, &offset);
if (ret) {
free(name);
return ret;
}
ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
free(name);
if (ret)
return ret;
HEIMDAL_MUTEX_lock(&ccache->mutex);
ccache->kdc_offset = offset;
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
return ret;
}
static krb5_error_code
kcm_op_retain_kcred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
if (ret) {
free(name);
return ret;
}
HEIMDAL_MUTEX_lock(&ccache->mutex);
ccache->holdcount++;
kcm_log(1, "retain_kcred: holdcount for %s is %ld", name, ccache->holdcount);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
free(name);
kcm_data_changed = 1;
return 0;
}
static krb5_error_code
kcm_op_release_kcred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
int destroy = 0;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
if (ret) {
free(name);
return ret;
}
HEIMDAL_MUTEX_lock(&ccache->mutex);
ccache->holdcount--;
if (ccache->holdcount < 1)
destroy = 1;
kcm_log(1, "release_kcred: holdcount for %s is %ld", name, ccache->holdcount);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
if (destroy) {
kcm_log(1, "holdcount for %s is zero, removing", name);
ret = kcm_ccache_destroy_client(context, client, name);
if (ret == 0)
kcm_drop_default_cache(context, client, name);
}
free(name);
kcm_data_changed = 1;
return 0;
}
static krb5_error_code
kcm_op_get_uuid(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
krb5_error_code ret;
kcm_ccache ccache;
char *name;
krb5_uuid uuid;
ret = krb5_ret_stringz(request, &name);
if (ret)
return ret;
KCM_LOG_REQUEST_NAME(context, client, opcode, name);
ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
free(name);
if (ret) {
return ret;
}
HEIMDAL_MUTEX_lock(&ccache->mutex);
memcpy(uuid, ccache->uuid, sizeof(uuid));
HEIMDAL_MUTEX_unlock(&ccache->mutex);
kcm_release_ccache(context, ccache);
(void)krb5_storage_write(response, uuid, sizeof(uuid));
return 0;
}
enum kcm_cred_type { KCM_NTLM_CRED, KCM_SCRAM_CRED };
struct kcm_ntlm_cred {
enum kcm_cred_type type;
kcmuuid_t uuid;
char *user;
char *domain;
#define nthash u.ntlm
union {
krb5_data ntlm;
char *password;
} u;
uid_t uid;
pid_t session;
long refcount;
heim_dict_t labels;
struct kcm_ntlm_cred *next;
};
static struct kcm_ntlm_cred *ntlm_head;
static HEIMDAL_MUTEX cred_mutex = HEIMDAL_MUTEX_INITIALIZER;
#define CHECK(s) do { if ((s)) { goto out; } } while(0)
static krb5_error_code
kcm_unparse_digest_one(krb5_storage *inner, struct kcm_ntlm_cred *c)
{
__block krb5_error_code ret;
if (c->type == KCM_NTLM_CRED)
CHECK(ret = krb5_store_stringz(inner, "ntlm-cache"));
else if (c->type == KCM_SCRAM_CRED)
CHECK(ret = krb5_store_stringz(inner, "scram-cache"));
else
heim_assert(false, "unknown cred type");
CHECK(ret = krb5_store_uuid(inner, c->uuid));
CHECK(ret = krb5_store_stringz(inner, c->user));
if (c->domain) {
CHECK(ret = krb5_store_uint8(inner, 1));
CHECK(ret = krb5_store_stringz(inner, c->domain));
} else {
CHECK(ret = krb5_store_uint8(inner, 0));
}
if (c->type == KCM_NTLM_CRED)
CHECK(ret = krb5_store_data(inner, c->u.ntlm));
else if (c->type == KCM_SCRAM_CRED)
CHECK(ret = krb5_store_stringz(inner, c->u.password));
CHECK(ret = krb5_store_int32(inner, c->uid));
CHECK(ret = krb5_store_int32(inner, c->session));
CHECK(ret = krb5_store_uint32(inner, (uint32_t)c->refcount));
heim_dict_iterate(c->labels, ^(heim_object_t key, heim_object_t value) {
heim_data_t d = value;
krb5_data data;
data.data = (void *)heim_data_get_bytes(d);
data.length = heim_data_get_length(d);
if (ret) return;
ret = krb5_store_uint8(inner, 1);
if (ret) return;
char *k = heim_string_copy_utf8(key);
ret = krb5_store_stringz(inner, k);
free(k);
if (ret) return;
ret = krb5_store_data(inner, data);
if (ret) return;
});
CHECK(ret);
CHECK(ret = krb5_store_uint8(inner, 0));
out:
return ret;
}
krb5_error_code
kcm_unparse_digest_all(krb5_context context, krb5_storage *sp)
{
struct kcm_ntlm_cred *c;
krb5_error_code r = 0;
HEIMDAL_MUTEX_lock(&cred_mutex);
for (c = ntlm_head; r == 0 && c != NULL; c = c->next) {
r = kcm_unparse_wrap(sp, "digest-cache", c->session, ^(krb5_storage *inner) {
return kcm_unparse_digest_one(inner, c);
});
}
if (r)
kcm_log(10, "failed to write digest-cred: %d", r);
HEIMDAL_MUTEX_unlock(&cred_mutex);
return r;
}
krb5_error_code
kcm_parse_digest_one(krb5_context context, krb5_storage *sp)
{
struct kcm_ntlm_cred *c;
krb5_error_code ret;
char *type = NULL;
uint32_t u32;
int32_t s32;
uint8_t u8;
c = calloc(1, sizeof(*c));
CHECK(ret = krb5_ret_stringz(sp, &type));
if (strcmp(type, "ntlm-cache") == 0) {
c->type = KCM_NTLM_CRED;
} else if (strcmp(type, "scram-cache") == 0) {
c->type = KCM_SCRAM_CRED;
} else {
free(type);
return EINVAL;
}
CHECK(ret = krb5_ret_uuid(sp, c->uuid));
CHECK(ret = krb5_ret_stringz(sp, &c->user));
CHECK(ret = krb5_ret_uint8(sp, &u8));
if (u8) {
CHECK(ret = krb5_ret_stringz(sp, &c->domain));
}
if (c->type == KCM_NTLM_CRED)
CHECK(ret = krb5_ret_data(sp, &c->u.ntlm));
else if (c->type == KCM_SCRAM_CRED)
CHECK(ret = krb5_ret_stringz(sp, &c->u.password));
CHECK(ret = krb5_ret_int32(sp, &s32));
c->uid = s32;
CHECK(ret = krb5_ret_uint32(sp, &u32));
c->session = u32;
CHECK(ret = krb5_ret_uint32(sp, &u32));
c->refcount = u32;
c->labels = heim_dict_create(0);
while (1) {
krb5_data data;
char *str;
CHECK(ret = krb5_ret_uint8(sp, &u8));
if (u8 == 0)
break;
CHECK(ret = krb5_ret_stringz(sp, &str));
heim_string_t s = heim_string_create(str);
free(str);
CHECK(ret = krb5_ret_data(sp, &data));
heim_data_t d = heim_data_create(data.data, data.length);
krb5_data_free(&data);
heim_dict_set_value(c->labels, s, d);
heim_release(s);
heim_release(d);
}
c->next = ntlm_head;
ntlm_head = c;
out:
free(type);
if (ret) {
kcm_log(10, "failed to read %s: %d", type, ret);
}
return ret;
}
static void
free_cred(struct kcm_ntlm_cred *cred)
{
free(cred->user);
free(cred->domain);
if (cred->type == KCM_NTLM_CRED) {
krb5_data_free(&cred->nthash);
} else if (cred->type == KCM_SCRAM_CRED) {
free(cred->u.password);
} else {
abort();
}
heim_release(cred->labels);
free(cred);
}
static struct kcm_ntlm_cred *
find_ntlm_cred(enum kcm_cred_type type, const char *user, const char *domain, kcm_client *client)
{
struct kcm_ntlm_cred *c;
for (c = ntlm_head; c != NULL; c = c->next)
if (c->type == type && (user[0] == '\0' || strcasecmp(user, c->user) == 0) &&
(domain == NULL || domain[0] == '\0' || strcasecmp(domain, c->domain) == 0) &&
kcm_is_same_session(client, c->uid, c->session))
return c;
return NULL;
}
static struct kcm_ntlm_cred *
create_cred(enum kcm_cred_type type)
{
struct kcm_ntlm_cred *cred;
cred = calloc(1, sizeof(*cred));
if (cred == NULL)
return NULL;
cred->type = type;
cred->labels = heim_dict_create(0);
cred->refcount = 1;
CCRandomCopyBytes(kCCRandomDefault, cred->uuid, sizeof(cred->uuid));
return cred;
}
static krb5_error_code
kcm_op_add_ntlm_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *cred, *c;
krb5_error_code ret;
cred = create_cred(KCM_NTLM_CRED);
if (cred == NULL)
return ENOMEM;
ret = krb5_ret_stringz(request, &cred->user);
if (ret)
goto error;
ret = krb5_ret_stringz(request, &cred->domain);
if (ret)
goto error;
ret = krb5_ret_data(request, &cred->nthash);
if (ret)
goto error;
HEIMDAL_MUTEX_lock(&cred_mutex);
c = find_ntlm_cred(KCM_NTLM_CRED, cred->user, cred->domain, client);
if (c) {
krb5_data hash = c->nthash;
c->nthash = cred->nthash;
cred->nthash = hash;
free_cred(cred);
cred = c;
} else {
cred->next = ntlm_head;
ntlm_head = cred;
}
cred->uid = client->uid;
cred->session = client->session;
HEIMDAL_MUTEX_unlock(&cred_mutex);
(void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid));
kcm_data_changed = 1;
return 0;
error:
free_cred(cred);
return ret;
}
static krb5_error_code
kcm_op_have_ntlm_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
char *user = NULL, *domain = NULL;
krb5_error_code ret;
ret = krb5_ret_stringz(request, &user);
if (ret)
goto error;
ret = krb5_ret_stringz(request, &domain);
if (ret)
goto error;
HEIMDAL_MUTEX_lock(&cred_mutex);
c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client);
if (c == NULL)
ret = ENOENT;
kcm_log(10, "ntlm checking for ntlm cred for %s@%s, -> %s",
user, domain, (c == NULL ? "no" : "yes"));
if (c)
(void)krb5_storage_write(response, &c->uuid, sizeof(c->uuid));
HEIMDAL_MUTEX_unlock(&cred_mutex);
error:
free(user);
if (domain)
free(domain);
return ret;
}
static krb5_error_code
kcm_op_del_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred **cp, *c;
kcmuuid_t uuid;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
HEIMDAL_MUTEX_lock(&cred_mutex);
for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
if ((*cp)->type == KCM_NTLM_CRED &&
memcmp((*cp)->uuid, uuid, sizeof(uuid)) == 0 &&
kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
{
c = *cp;
*cp = c->next;
free_cred(c);
kcm_data_changed = 1;
break;
}
}
HEIMDAL_MUTEX_unlock(&cred_mutex);
return 0;
}
static struct ntlm_challenge {
struct ntlm_challenge *next;
uint8_t challenge[8];
time_t ts;
} *ntlm_challenges = NULL;
static void
ntlm_delete_chain(struct ntlm_challenge *c)
{
while (c) {
struct ntlm_challenge *next = c->next;
free(c);
c = next;
}
}
static int
ntlm_expiredp(struct ntlm_challenge *c, time_t now)
{
return c->ts + heim_ntlm_time_skew < now;
}
static int
check_ntlm_challage(uint8_t chal[8])
{
struct ntlm_challenge **q = &ntlm_challenges;
time_t t = time(NULL);
while (*q) {
if (ntlm_expiredp(*q, t)) {
struct ntlm_challenge *c = *q;
*q = NULL;
ntlm_delete_chain(c);
return 0;
}
if (memcmp((*q)->challenge, chal, sizeof((*q)->challenge)) == 0)
return EAUTH;
q = &(*q)->next;
}
return 0;
}
static krb5_error_code
kcm_op_add_ntlm_challenge(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct ntlm_challenge *c;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
c = malloc(sizeof(*c));
if (c == NULL)
return ENOMEM;
sret = krb5_storage_read(request, c->challenge, sizeof(c->challenge));
if (sret != sizeof(c->challenge)) {
free(c);
return KRB5_CC_IO;
}
c->ts = time(NULL);
c->next = ntlm_challenges;
ntlm_challenges = c;
kcm_data_changed = 1;
return 0;
}
static krb5_error_code
kcm_op_check_ntlm_challenge(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
uint8_t chal[8];
ssize_t sret;
int res;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, chal, sizeof(chal));
if (sret != sizeof(chal))
return KRB5_CC_IO;
res = check_ntlm_challage(chal);
if (res)
kcm_log(10, "ntlm reflection attack detected");
return krb5_store_uint8(response, !!res);
}
krb5_error_code
kcm_parse_ntlm_challenge_one(krb5_context context, krb5_storage *sp)
{
struct ntlm_challenge *c, **q;
krb5_error_code ret;
int32_t ts;
ssize_t sret;
c = malloc(sizeof(*c));
if (c == NULL)
return ENOMEM;
sret = krb5_storage_read(sp, c->challenge, sizeof(c->challenge));
if (sret != sizeof(c->challenge)) {
free(c);
return KRB5_CC_IO;
}
ret = krb5_ret_int32(sp, &ts);
if (ret) {
free(c);
return ret;
}
c->ts = ts;
c->next = NULL;
if (ntlm_expiredp(c, time(NULL))) {
free(c);
} else {
for (q = &ntlm_challenges; *q != NULL; q = &(*q)->next)
;
*q = c;
}
return 0;
}
krb5_error_code
kcm_unparse_challenge_all(krb5_context context, krb5_storage *sp)
{
struct ntlm_challenge *c;
krb5_error_code r = 0;
time_t now = time(NULL);
for (c = ntlm_challenges; r == 0 && c != NULL; c = c->next) {
if (ntlm_expiredp(c, now))
break;
r = kcm_unparse_wrap(sp, "ntlm-chal", 0, ^(krb5_storage *inner) {
ssize_t sret;
sret = krb5_storage_write(inner, c->challenge,
sizeof(c->challenge));
if (sret != sizeof(c->challenge))
return EINVAL;
return krb5_store_int32(inner, (int32_t)c->ts);
});
}
if (r)
kcm_log(10, "failed to write ntlm-chal: %d", r);
return r;
}
static int
ntlm_domain_is_hostname(const char *name)
{
return (name[0] == '\\');
}
static krb5_error_code
kcm_op_do_ntlm(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
#ifdef ENABLE_NTLM
struct kcm_ntlm_cred *c;
struct ntlm_type2 type2;
struct ntlm_type3 type3;
char *user = NULL, *domain = NULL, *targetname = NULL;
struct ntlm_buf ndata, sessionkey, tidata;
krb5_data type2data, cb, type1data, tempdata;
krb5_error_code ret;
uint32_t type1flags, flags = 0;
const char *type = "unknown";
char flagname[256];
size_t mic_offset = 0;
KCM_LOG_REQUEST(context, client, opcode);
krb5_data_zero(&cb);
krb5_data_zero(&type1data);
krb5_data_zero(&type2data);
memset(&tidata, 0, sizeof(tidata));
memset(&type2, 0, sizeof(type2));
memset(&type3, 0, sizeof(type3));
sessionkey.data = NULL;
sessionkey.length = 0;
HEIMDAL_MUTEX_lock(&cred_mutex);
ret = krb5_ret_stringz(request, &user);
if (ret)
goto error;
ret = krb5_ret_stringz(request, &domain);
if (ret)
goto error;
kcm_log(10, "NTLM AUTH with cred %s\\%s", domain, user);
c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client);
if (c == NULL) {
ret = EINVAL;
goto error;
}
ret = krb5_ret_data(request, &type2data);
if (ret)
goto error;
ret = krb5_ret_data(request, &cb);
if (ret)
goto error;
ret = krb5_ret_data(request, &type1data);
if (ret)
goto error;
ret = krb5_ret_stringz(request, &targetname);
if (ret)
goto error;
ret = krb5_ret_uint32(request, &type1flags);
if (ret)
goto error;
ndata.data = type2data.data;
ndata.length = type2data.length;
ret = heim_ntlm_decode_type2(&ndata, &type2);
if (ret)
goto error;
if (!disable_ntlm_reflection_detection) {
kcm_log(10, "checking for ntlm mirror attack");
ret = check_ntlm_challage(type2.challenge);
if (ret) {
kcm_log(0, "ntlm mirror attack detected");
goto error;
}
}
if (ntlm_domain_is_hostname(c->domain) || strcasecmp(domain, type2.targetname) == 0) {
free(domain);
domain = type2.targetname;
if (domain == NULL) {
ret = ENOMEM;
goto error;
}
} else {
free(domain);
domain = c->domain;
}
type3.username = c->user;
type3.flags = type2.flags;
type3.flags &= type1flags;
type3.targetname = domain;
type3.ws = rk_UNCONST("workstation");
if (gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_FORCE_V1, NULL)) {
type = "v1";
if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
unsigned char nonce[8];
if (CCRandomCopyBytes(kCCRandomDefault, nonce, sizeof(nonce))) {
ret = EINVAL;
goto error;
}
ret = heim_ntlm_calculate_ntlm2_sess(nonce,
type2.challenge,
c->nthash.data,
&type3.lm,
&type3.ntlm);
} else {
ret = heim_ntlm_calculate_ntlm1(c->nthash.data,
c->nthash.length,
type2.challenge,
&type3.ntlm);
}
if (ret)
goto error;
if (type3.flags & NTLM_NEG_KEYEX) {
ret = heim_ntlm_build_ntlm1_master(c->nthash.data,
c->nthash.length,
&sessionkey,
&type3.sessionkey);
} else {
ret = heim_ntlm_v1_base_session(c->nthash.data,
c->nthash.length,
&sessionkey);
}
if (ret)
goto error;
} else {
unsigned char ntlmv2[16];
struct ntlm_targetinfo ti;
static uint8_t zeros[16] = { 0 };
type = "v2";
ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
if (ret)
goto error;
if (ti.avflags & NTLM_TI_AV_FLAG_GUEST)
flags |= KCM_NTLM_FLAG_AV_GUEST;
if (ti.channel_bindings.data)
free(ti.channel_bindings.data);
if (ti.targetname)
free(ti.targetname);
ti.avflags |= NTLM_TI_AV_FLAG_MIC;
ti.targetname = targetname;
if (cb.length == 0) {
ti.channel_bindings.data = zeros;
ti.channel_bindings.length = sizeof(zeros);
} else {
kcm_log(10, "using channelbindings of size %lu", (unsigned long)cb.length);
ti.channel_bindings.data = cb.data;
ti.channel_bindings.length = cb.length;
}
ret = heim_ntlm_encode_targetinfo(&ti, TRUE, &tidata);
ti.targetname = NULL;
ti.channel_bindings.data = NULL;
ti.channel_bindings.length = 0;
heim_ntlm_free_targetinfo(&ti);
if (ret)
goto error;
if (type3.flags & NTLM_NEG_NTLM2_SESSION)
type3.flags &= ~NTLM_NEG_LM_KEY;
if ((type3.flags & NTLM_NEG_LM_KEY) &&
gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_SUPPORT_LM2, NULL)) {
ret = heim_ntlm_calculate_lm2(c->nthash.data,
c->nthash.length,
type3.username,
domain,
type2.challenge,
ntlmv2,
&type3.lm);
} else {
type3.lm.data = malloc(24);
if (type3.lm.data == NULL) {
ret = ENOMEM;
} else {
type3.lm.length = 24;
memset(type3.lm.data, 0, type3.lm.length);
}
}
if (ret)
goto error;
ret = heim_ntlm_calculate_ntlm2(c->nthash.data,
c->nthash.length,
type3.username,
domain,
type2.challenge,
&tidata,
ntlmv2,
&type3.ntlm);
if (ret)
goto error;
if (type3.flags & NTLM_NEG_KEYEX) {
ret = heim_ntlm_build_ntlm2_master(ntlmv2, sizeof(ntlmv2),
&type3.ntlm,
&sessionkey,
&type3.sessionkey);
} else {
ret = heim_ntlm_v2_base_session(ntlmv2, sizeof(ntlmv2), &type3.ntlm, &sessionkey);
}
memset(ntlmv2, 0, sizeof(ntlmv2));
if (ret)
goto error;
}
ret = heim_ntlm_encode_type3(&type3, &ndata, &mic_offset);
if (ret)
goto error;
if (ndata.length < CC_MD5_DIGEST_LENGTH) {
ret = EINVAL;
goto error;
}
if (mic_offset && mic_offset < ndata.length - CC_MD5_DIGEST_LENGTH) {
CCHmacContext mic;
uint8_t *p = (uint8_t *)ndata.data + mic_offset;
CCHmacInit(&mic, kCCHmacAlgMD5, sessionkey.data, sessionkey.length);
CCHmacUpdate(&mic, type1data.data, type1data.length);
CCHmacUpdate(&mic, type2data.data, type2data.length);
CCHmacUpdate(&mic, ndata.data, ndata.length);
CCHmacFinal(&mic, p);
}
tempdata.data = ndata.data;
tempdata.length = ndata.length;
ret = krb5_store_data(response, tempdata);
heim_ntlm_free_buf(&ndata);
if (ret) goto error;
ret = krb5_store_int32(response, flags);
if (ret) goto error;
tempdata.data = sessionkey.data;
tempdata.length = sessionkey.length;
ret = krb5_store_data(response, tempdata);
if (ret) goto error;
ret = krb5_store_string(response, c->user);
if (ret) goto error;
ret = krb5_store_string(response, domain);
if (ret) goto error;
ret = krb5_store_uint32(response, type3.flags);
if (ret) goto error;
heim_ntlm_unparse_flags(type3.flags, flagname, sizeof(flagname));
kcm_log(0, "ntlm %s request processed for %s\\%s flags: %s",
type, domain, c->user, flagname);
error:
HEIMDAL_MUTEX_unlock(&cred_mutex);
krb5_data_free(&cb);
krb5_data_free(&type1data);
krb5_data_free(&type2data);
if (type3.lm.data)
free(type3.lm.data);
if (type3.ntlm.data)
free(type3.ntlm.data);
if (type3.sessionkey.data)
free(type3.sessionkey.data);
if (targetname)
free(targetname);
heim_ntlm_free_type2(&type2);
heim_ntlm_free_buf(&sessionkey);
heim_ntlm_free_buf(&tidata);
free(user);
return ret;
#else
return EINVAL;
#endif
}
static krb5_error_code
kcm_op_get_ntlm_user_list(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
krb5_error_code ret;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
HEIMDAL_MUTEX_lock(&cred_mutex);
for (c = ntlm_head; c != NULL; c = c->next) {
if (c->type != KCM_NTLM_CRED || !kcm_is_same_session(client, c->uid, c->session))
continue;
ret = krb5_store_uint32(response, 1);
if (ret)
goto out;
ret = krb5_store_stringz(response, c->user);
if (ret)
goto out;
ret = krb5_store_stringz(response, c->domain);
if (ret)
goto out;
sret = krb5_storage_write(response, c->uuid, sizeof(c->uuid));
if (sret != sizeof(c->uuid)) {
ret = ENOMEM;
goto out;
}
}
ret = krb5_store_uint32(response, 0);
out:
HEIMDAL_MUTEX_unlock(&cred_mutex);
return ret;
}
static krb5_error_code
kcm_op_add_scram_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *cred, *c;
krb5_error_code ret;
KCM_LOG_REQUEST(context, client, opcode);
cred = create_cred(KCM_SCRAM_CRED);
if (cred == NULL)
return ENOMEM;
ret = krb5_ret_stringz(request, &cred->user);
if (ret)
goto error;
ret = krb5_ret_stringz(request, &cred->u.password);
if (ret)
goto error;
HEIMDAL_MUTEX_lock(&cred_mutex);
c = find_ntlm_cred(KCM_SCRAM_CRED, cred->user, NULL, client);
if (c) {
char *pw = c->u.password;
c->u.password = cred->u.password;
cred->u.password = pw;
free_cred(cred);
cred = c;
} else {
cred->next = ntlm_head;
ntlm_head = cred;
}
cred->uid = client->uid;
cred->session = client->session;
(void)krb5_storage_write(response, cred->uuid, sizeof(cred->uuid));
HEIMDAL_MUTEX_unlock(&cred_mutex);
kcm_data_changed = 1;
return 0;
error:
free_cred(cred);
return ret;
}
static krb5_error_code
kcm_op_have_scram_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
char *user = NULL;
krb5_error_code ret;
KCM_LOG_REQUEST(context, client, opcode);
ret = krb5_ret_stringz(request, &user);
if (ret)
return ret;
HEIMDAL_MUTEX_lock(&cred_mutex);
c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client);
if (c == NULL)
ret = ENOENT;
if (c)
(void)krb5_storage_write(response, c->uuid, sizeof(c->uuid));
HEIMDAL_MUTEX_unlock(&cred_mutex);
free(user);
return ret;
}
static krb5_error_code
kcm_op_del_scram_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred **cp, *c;
char *user = NULL;
krb5_error_code ret;
KCM_LOG_REQUEST(context, client, opcode);
ret = krb5_ret_stringz(request, &user);
if (ret)
return ret;
HEIMDAL_MUTEX_lock(&cred_mutex);
for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
if ((*cp)->type == KCM_SCRAM_CRED && strcasecmp(user, (*cp)->user) == 0 &&
kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
{
c = *cp;
*cp = c->next;
free_cred(c);
kcm_data_changed = 1;
break;
}
}
HEIMDAL_MUTEX_unlock(&cred_mutex);
free(user);
return ret;
}
static krb5_error_code
kcm_op_do_scram(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
#ifdef ENABLE_SCRAM
heim_scram_data proof, server, client_key, stored, server_key, session_key;
heim_scram_method method = HEIM_SCRAM_DIGEST_SHA1;
krb5_data salt, c1, s1, c2noproof;
struct kcm_ntlm_cred *c;
krb5_error_code ret;
uint32_t iterations;
unsigned char *p, *q;
char *user = NULL;
size_t n;
KCM_LOG_REQUEST(context, client, opcode);
memset(&proof, 0, sizeof(proof));
memset(&server, 0, sizeof(server));
memset(&client_key, 0, sizeof(client_key));
memset(&stored, 0, sizeof(stored));
memset(&server_key, 0, sizeof(server_key));
memset(&session_key, 0, sizeof(session_key));
krb5_data_zero(&salt);
krb5_data_zero(&c1);
krb5_data_zero(&s1);
krb5_data_zero(&c2noproof);
HEIMDAL_MUTEX_lock(&cred_mutex);
ret = krb5_ret_stringz(request, &user);
if (ret)
goto out;
c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client);
if (c == NULL) {
ret = ENOENT;
goto out;
}
ret = krb5_ret_uint32(request, &iterations);
if (ret)
goto out;
ret = krb5_ret_data(request, &salt);
if (ret)
goto out;
ret = krb5_ret_data(request, &c1);
if (ret)
goto out;
ret = krb5_ret_data(request, &s1);
if (ret)
goto out;
ret = krb5_ret_data(request, &c2noproof);
if (ret)
goto out;
ret = heim_scram_stored_key(method, c->u.password, iterations, &salt,
&client_key, &stored, &server_key);
if (ret)
goto out;
ret = heim_scram_generate(method, &stored, &server_key,
&c1, &s1, &c2noproof, &proof, &server);
if (ret)
goto out;
ret = heim_scram_session_key(method, &stored, &client_key,
&c1, &s1, &c2noproof,
&session_key);
if (ret)
goto out;
p = proof.data;
q = client_key.data;
for (n = 0 ; n < client_key.length; n++)
p[n] = p[n] ^ q[n];
ret = krb5_store_data(response, proof);
if (ret)
goto out;
ret = krb5_store_data(response, server);
if (ret)
goto out;
ret = krb5_store_data(response, session_key);
if (ret)
goto out;
out:
HEIMDAL_MUTEX_unlock(&cred_mutex);
if (user)
free(user);
krb5_data_free(&salt);
krb5_data_free(&c1);
krb5_data_free(&s1);
krb5_data_free(&c2noproof);
heim_scram_data_free(&proof);
heim_scram_data_free(&server);
heim_scram_data_free(&client_key);
heim_scram_data_free(&stored);
heim_scram_data_free(&server_key);
heim_scram_data_free(&session_key);
return ret;
#else
return EINVAL;
#endif
}
static krb5_error_code
kcm_op_get_scram_user_list(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
krb5_error_code ret;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
for (c = ntlm_head; c != NULL; c = c->next) {
if (c->type != KCM_SCRAM_CRED || !kcm_is_same_session(client, c->uid, c->session))
continue;
ret = krb5_store_uint32(response, 1);
if (ret)
return ret;
ret = krb5_store_stringz(response, c->user);
if (ret)
return ret;
sret = krb5_storage_write(response, c->uuid, sizeof(c->uuid));
if (sret != sizeof(c->uuid)) {
ret = ENOMEM;
return ret;
}
}
return krb5_store_uint32(response, 0);
}
static krb5_error_code
kcm_op_retain_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
kcmuuid_t uuid;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
for (c = ntlm_head; c != NULL; c = c->next) {
if (!kcm_is_same_session(client, c->uid, c->session))
continue;
if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) {
c->refcount++;
kcm_data_changed = 1;
return 0;
}
}
return 0;
}
static krb5_error_code
kcm_op_release_cred(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred **cp;
kcmuuid_t uuid;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
struct kcm_ntlm_cred *c = *cp;
if (!kcm_is_same_session(client, c->uid, c->session))
continue;
if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) {
c->refcount--;
if (c->refcount < 1) {
*cp = c->next;
free_cred(c);
}
kcm_data_changed = 1;
return 0;
}
}
return 0;
}
static krb5_error_code
kcm_op_cred_label_get(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
krb5_error_code ret;
heim_string_t s;
char *label;
kcmuuid_t uuid;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
ret = krb5_ret_stringz(request, &label);
if (ret)
return ret;
s = heim_string_create(label);
free(label);
for (c = ntlm_head; c != NULL; c = c->next) {
if (!kcm_is_same_session(client, c->uid, c->session))
continue;
if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) {
heim_data_t d;
d = heim_dict_copy_value(c->labels, s);
if (d) {
krb5_data data;
data.length = heim_data_get_length(d);
data.data = (void *)heim_data_get_bytes(d);
krb5_store_data(response, data);
heim_release(d);
break;
}
}
}
heim_release(s);
if (c == NULL)
return ENOENT;
return 0;
}
static krb5_error_code
kcm_op_cred_label_set(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *request,
krb5_storage *response)
{
struct kcm_ntlm_cred *c;
kcmuuid_t uuid;
krb5_data data;
char *label = NULL;
ssize_t sret;
KCM_LOG_REQUEST(context, client, opcode);
sret = krb5_storage_read(request, &uuid, sizeof(uuid));
if (sret != sizeof(uuid)) {
krb5_clear_error_message(context);
return KRB5_CC_IO;
}
krb5_ret_stringz(request, &label);
krb5_ret_data(request, &data);
HEIMDAL_MUTEX_lock(&cred_mutex);
for (c = ntlm_head; c != NULL; c = c->next) {
if (!kcm_is_same_session(client, c->uid, c->session))
continue;
if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) {
heim_string_t s;
s = heim_string_create(label);
if (data.length) {
heim_data_t d;
d = heim_data_create(data.data, data.length);
heim_dict_set_value(c->labels, s, d);
heim_release(d);
} else {
heim_dict_delete_key(c->labels, s);
}
kcm_data_changed = 1;
heim_release(s);
break;
}
}
HEIMDAL_MUTEX_unlock(&cred_mutex);
krb5_data_free(&data);
free(label);
if (c == NULL)
return ENOENT;
return 0;
}
static struct kcm_op kcm_ops[] = {
{ "NOOP", kcm_op_noop },
{ "GET_NAME", kcm_op_get_name },
{ "RESOLVE", kcm_op_noop },
{ "GEN_NEW", kcm_op_gen_new },
{ "INITIALIZE", kcm_op_initialize },
{ "DESTROY", kcm_op_destroy },
{ "STORE", kcm_op_store },
{ "RETRIEVE", kcm_op_retrieve },
{ "GET_PRINCIPAL", kcm_op_get_principal },
{ "GET_CRED_UUID_LIST", kcm_op_get_cred_uuid_list },
{ "GET_CRED_BY_UUID", kcm_op_get_cred_by_uuid },
{ "REMOVE_CRED", kcm_op_remove_cred },
{ "SET_FLAGS", kcm_op_set_flags },
{ "CHOWN", kcm_op_chown },
{ "CHMOD", kcm_op_chmod },
{ "GET_INITIAL_TICKET", kcm_op_get_initial_ticket },
{ "GET_TICKET", kcm_op_get_ticket },
{ "MOVE_CACHE", kcm_op_move_cache },
{ "GET_CACHE_UUID_LIST", kcm_op_get_cache_uuid_list },
{ "GET_CACHE_BY_UUID", kcm_op_get_cache_by_uuid },
{ "GET_DEFAULT_CACHE", kcm_op_get_default_cache },
{ "SET_DEFAULT_CACHE", kcm_op_set_default_cache },
{ "GET_KDC_OFFSET", kcm_op_get_kdc_offset },
{ "SET_KDC_OFFSET", kcm_op_set_kdc_offset },
{ "RETAIN_KCRED", kcm_op_retain_kcred },
{ "RELEASE_KCRED", kcm_op_release_kcred },
{ "GET_UUID", kcm_op_get_uuid },
{ "ADD_NTLM_CRED", kcm_op_add_ntlm_cred },
{ "HAVE_NTLM_CRED", kcm_op_have_ntlm_cred },
{ "SET_NTLM_CHALLEGE", kcm_op_add_ntlm_challenge },
{ "DO_NTLM_AUTH", kcm_op_do_ntlm },
{ "SET_NTLM_USER_LIST", kcm_op_get_ntlm_user_list },
{ "ADD_SCRAM_CRED", kcm_op_add_scram_cred },
{ "HAVE_SCRAM_CRED", kcm_op_have_scram_cred },
{ "DEL_SCRAM_CRED", kcm_op_del_scram_cred },
{ "DO_SCRAM_AUTH", kcm_op_do_scram },
{ "GET_SCRAM_USER_LIST", kcm_op_get_scram_user_list },
{ "DEL_CRED", kcm_op_del_cred },
{ "RETAIN_CRED", kcm_op_retain_cred },
{ "RELEASE_CRED", kcm_op_release_cred },
{ "CRED_LABEL_GET", kcm_op_cred_label_get },
{ "CRED_LABEL_SET", kcm_op_cred_label_set },
{ "CHECK_NTLM_CHALLAGE", kcm_op_check_ntlm_challenge },
};
const char *
kcm_op2string(kcm_operation opcode)
{
if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0]))
return "Unknown operation";
return kcm_ops[opcode].name;
}
krb5_error_code
kcm_dispatch(krb5_context context,
kcm_client *client,
krb5_data *req_data,
krb5_data *resp_data)
{
krb5_error_code ret;
kcm_method method;
krb5_storage *req_sp = NULL;
krb5_storage *resp_sp = NULL;
uint16_t opcode;
resp_sp = krb5_storage_emem();
if (resp_sp == NULL) {
return ENOMEM;
}
if (client->pid == -1) {
kcm_log(0, "Client had invalid process number");
ret = KRB5_FCC_INTERNAL;
goto out;
}
req_sp = krb5_storage_from_data(req_data);
if (req_sp == NULL) {
kcm_log(0, "Process %d: failed to initialize storage from data",
client->pid);
ret = KRB5_CC_IO;
goto out;
}
ret = krb5_ret_uint16(req_sp, &opcode);
if (ret) {
kcm_log(0, "Process %d: didn't send a message", client->pid);
goto out;
}
if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
kcm_log(0, "Process %d: invalid operation code %d",
client->pid, opcode);
ret = KRB5_FCC_INTERNAL;
goto out;
}
method = kcm_ops[opcode].method;
if (method == NULL) {
kcm_log(0, "Process %d: operation code %s not implemented",
client->pid, kcm_op2string(opcode));
ret = KRB5_FCC_INTERNAL;
goto out;
}
krb5_storage_seek(resp_sp, 4, SEEK_SET);
ret = (*method)(context, client, opcode, req_sp, resp_sp);
out:
if (req_sp != NULL) {
krb5_storage_free(req_sp);
}
krb5_storage_seek(resp_sp, 0, SEEK_SET);
krb5_store_int32(resp_sp, ret);
ret = krb5_storage_to_data(resp_sp, resp_data);
krb5_storage_free(resp_sp);
return ret;
}