#include "kcm_locl.h"
#include <uuid/uuid.h>
#include <bsm/libbsm.h>
#include <vproc.h>
#include <vproc_priv.h>
static void kcm_release_ccache_locked(krb5_context, kcm_ccache);
HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
TAILQ_HEAD(ccache_head, kcm_ccache_data);
static struct ccache_head ccache_head = TAILQ_HEAD_INITIALIZER(ccache_head);
static uint32_t ccache_nextid = 0;
char *
kcm_ccache_nextid(pid_t pid, uid_t uid)
{
kcm_ccache c;
char *name = NULL;
unsigned n;
HEIMDAL_MUTEX_lock(&ccache_mutex);
while (name == NULL) {
n = ++ccache_nextid;
asprintf(&name, "%ld:%u", (long)uid, n);
TAILQ_FOREACH(c, &ccache_head, members) {
if (strcmp(c->name, name) == 0) {
free(name);
name = NULL;
break;
}
}
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return name;
}
krb5_error_code
kcm_ccache_resolve(krb5_context context,
const char *name,
kcm_ccache *ccache)
{
kcm_ccache p;
krb5_error_code ret;
*ccache = NULL;
ret = KRB5_FCC_NOFILE;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
if (strcmp(p->name, name) == 0) {
ret = 0;
break;
}
}
if (ret == 0) {
kcm_retain_ccache(context, p);
*ccache = p;
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return ret;
}
krb5_error_code
kcm_ccache_resolve_by_uuid(krb5_context context,
kcmuuid_t uuid,
kcm_ccache *ccache)
{
kcm_ccache p;
krb5_error_code ret;
*ccache = NULL;
ret = KRB5_FCC_NOFILE;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
if (memcmp(p->uuid, uuid, sizeof(uuid)) == 0) {
ret = 0;
break;
}
}
if (ret == 0) {
kcm_retain_ccache(context, p);
*ccache = p;
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return ret;
}
krb5_error_code
kcm_ccache_get_uuids(krb5_context context,
kcm_client *client,
kcm_operation opcode,
krb5_storage *sp)
{
kcm_ccache p;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
krb5_error_code ret;
ret = kcm_access(context, client, opcode, p);
if (ret)
continue;
krb5_storage_write(sp, p->uuid, sizeof(p->uuid));
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return 0;
}
krb5_error_code
kcm_debug_ccache(krb5_context context)
{
kcm_ccache p;
TAILQ_FOREACH(p, &ccache_head, members) {
char *cpn = NULL, *spn = NULL;
int ncreds = 0;
struct kcm_creds *k;
KCM_ASSERT_VALID(p);
for (k = p->creds; k != NULL; k = k->next)
ncreds++;
if (p->client != NULL)
krb5_unparse_name(context, p->client, &cpn);
if (p->server != NULL)
krb5_unparse_name(context, p->server, &spn);
kcm_log(7, "cache name %s refcnt %d flags %04x"
"uid %d client %s server %s ncreds %d",
p->name, p->refcnt, p->flags, p->uid,
(cpn == NULL) ? "<none>" : cpn,
(spn == NULL) ? "<none>" : spn,
ncreds);
if (cpn != NULL)
free(cpn);
if (spn != NULL)
free(spn);
}
return 0;
}
static void
kcm_free_ccache_data_internal(krb5_context context,
kcm_ccache cache)
{
KCM_ASSERT_VALID(cache);
if (cache->name != NULL) {
free(cache->name);
cache->name = NULL;
}
if (cache->flags & KCM_FLAGS_USE_KEYTAB) {
krb5_kt_close(context, cache->keytab);
cache->keytab = NULL;
} else if (cache->flags & KCM_FLAGS_USE_PASSWORD) {
memset(cache->password, 0, strlen(cache->password));
free(cache->password);
}
cache->flags = 0;
cache->uid = -1;
cache->session = -1;
kcm_zero_ccache_data_internal(context, cache);
cache->tkt_life = 0;
cache->renew_life = 0;
cache->refcnt = 0;
}
krb5_error_code
kcm_ccache_destroy(krb5_context context, const char *name)
{
kcm_ccache p;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
if (strcmp(p->name, name) == 0) {
HEIMDAL_MUTEX_lock(&p->mutex);
TAILQ_REMOVE(&ccache_head, p, members);
break;
}
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
if (p == NULL)
return KRB5_FCC_NOFILE;
heim_ipc_event_cancel(p->renew_event);
heim_ipc_event_free(p->renew_event);
p->renew_event = NULL;
heim_ipc_event_cancel(p->expire_event);
heim_ipc_event_free(p->expire_event);
p->expire_event = NULL;
kcm_release_ccache_locked(context, p);
return 0;
}
#define KCM_EVENT_QUEUE_INTERVAL 60
void
kcm_update_expire_time(kcm_ccache ccache)
{
time_t renewtime = time(NULL) + 3600 * 2;
time_t expire = ccache->expire;
if (time(NULL) + KCM_EVENT_QUEUE_INTERVAL > expire)
return;
if (renewtime > expire - KCM_EVENT_QUEUE_INTERVAL)
renewtime = expire - KCM_EVENT_QUEUE_INTERVAL;
kcm_log(1, "%s: will try to renew credentals in %d seconds",
ccache->name, (int)(renewtime - time(NULL)));
heim_ipc_event_set_time(ccache->renew_event, renewtime);
ccache->renew_time = renewtime;
#ifdef HAVE_NOTIFY_H
notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
#endif
}
static void
renew_func(heim_event_t event, void *ptr)
{
kcm_ccache cache = ptr;
krb5_error_code ret;
time_t expire;
kcm_log(0, "cache: %s renewing", cache->name);
HEIMDAL_MUTEX_lock(&cache->mutex);
if (cache->flags & KCM_MASK_KEY_PRESENT) {
ret = kcm_ccache_acquire(kcm_context, cache, &expire);
} else {
ret = kcm_ccache_refresh(kcm_context, cache, &expire);
}
switch (ret) {
case KRB5KRB_AP_ERR_BAD_INTEGRITY:
case KRB5KRB_AP_ERR_MODIFIED:
case KRB5KDC_ERR_PREAUTH_FAILED:
kcm_log(0, "cache: %s got bad password, stop renewing",
cache->name);
break;
case 0:
cache->expire = expire;
heim_ipc_event_set_time(cache->expire_event, cache->expire);
break;
default: {
const char *msg = krb5_get_error_message(kcm_context, ret);
kcm_log(0, "failed to renew: %s: %d", msg, ret);
krb5_free_error_message(kcm_context, msg);
}
}
kcm_update_expire_time(cache);
HEIMDAL_MUTEX_unlock(&cache->mutex);
}
static void
expire_func(heim_event_t event, void *ptr)
{
kcm_ccache cache = ptr;
krb5_error_code ret;
kcm_log(0, "cache: %s expired", cache->name);
HEIMDAL_MUTEX_lock(&cache->mutex);
heim_ipc_event_cancel(cache->renew_event);
if (cache->flags & KCM_MASK_KEY_PRESENT){
time_t expire;
ret = kcm_ccache_acquire(kcm_context, cache, &expire);
switch (ret) {
case KRB5KRB_AP_ERR_BAD_INTEGRITY:
case KRB5KRB_AP_ERR_MODIFIED:
case KRB5KDC_ERR_PREAUTH_FAILED:
kcm_log(0, "cache: %s got bad password, stop renewing",
cache->name);
break;
case 0:
kcm_log(0, "cache: %s got new tickets (expire in %d seconds)",
cache->name, (int)(expire - time(NULL)));
cache->expire = expire;
heim_ipc_event_set_time(cache->expire_event, cache->expire);
kcm_update_expire_time(cache);
break;
default:
heim_ipc_event_set_time(cache->expire_event, time(NULL) + 300);
break;
}
}
HEIMDAL_MUTEX_unlock(&cache->mutex);
}
static void
release_cache(void *ctx)
{
kcm_release_ccache(kcm_context, (kcm_ccache)ctx);
}
static krb5_error_code
kcm_ccache_alloc(krb5_context context,
const char *name,
kcm_ccache *cache)
{
kcm_ccache p = NULL;
krb5_error_code ret;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
if (strcmp(p->name, name) == 0) {
ret = KRB5_CC_WRITE;
goto out;
}
}
p = calloc(1, sizeof(*p));
if (p == NULL) {
ret = KRB5_CC_NOMEM;
goto out;
}
HEIMDAL_MUTEX_init(&p->mutex);
CCRandomCopyBytes(kCCRandomDefault, p->uuid, sizeof(p->uuid));
p->name = strdup(name);
if (p->name == NULL) {
ret = KRB5_CC_NOMEM;
goto out;
}
p->refcnt = 3;
p->holdcount = 1;
p->flags = 0;
p->uid = -1;
p->client = NULL;
p->server = NULL;
p->creds = NULL;
p->keytab = NULL;
p->password = NULL;
p->tkt_life = 0;
p->renew_life = 0;
p->renew_event = heim_ipc_event_create_f(renew_func, p);
p->expire_event = heim_ipc_event_create_f(expire_func, p);
heim_ipc_event_set_final_f(p->renew_event, release_cache);
heim_ipc_event_set_final_f(p->expire_event, release_cache);
TAILQ_INSERT_HEAD(&ccache_head, p, members);
*cache = p;
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return 0;
out:
HEIMDAL_MUTEX_unlock(&ccache_mutex);
if (p != NULL) {
HEIMDAL_MUTEX_destroy(&p->mutex);
free(p);
}
*cache = NULL;
return ret;
}
krb5_error_code
kcm_ccache_enqueue_default(krb5_context context,
kcm_ccache cache,
krb5_creds *newcred)
{
if (newcred == NULL) {
heim_ipc_event_set_time(cache->expire_event, 0);
} else if (cache->flags & KCM_MASK_KEY_PRESENT) {
cache->expire = newcred->times.endtime;
kcm_update_expire_time(cache);
} else if (newcred->flags.b.initial) {
cache->expire = newcred->times.endtime;
if (newcred->flags.b.renewable)
kcm_update_expire_time(cache);
heim_ipc_event_set_time(cache->expire_event, newcred->times.endtime);
}
return 0;
}
krb5_error_code
kcm_ccache_remove_creds_internal(krb5_context context,
kcm_ccache ccache)
{
struct kcm_creds *k;
k = ccache->creds;
while (k != NULL) {
struct kcm_creds *old;
krb5_free_cred_contents(context, &k->cred);
old = k;
k = k->next;
free(old);
}
ccache->creds = NULL;
#ifdef HAVE_NOTIFY_H
notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
#endif
return 0;
}
krb5_error_code
kcm_ccache_remove_creds(krb5_context context,
kcm_ccache ccache)
{
krb5_error_code ret;
KCM_ASSERT_VALID(ccache);
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = kcm_ccache_remove_creds_internal(context, ccache);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
return ret;
}
krb5_error_code
kcm_zero_ccache_data_internal(krb5_context context,
kcm_ccache cache)
{
if (cache->client != NULL) {
krb5_free_principal(context, cache->client);
cache->client = NULL;
}
if (cache->server != NULL) {
krb5_free_principal(context, cache->server);
cache->server = NULL;
}
kcm_ccache_remove_creds_internal(context, cache);
return 0;
}
krb5_error_code
kcm_zero_ccache_data(krb5_context context,
kcm_ccache cache)
{
krb5_error_code ret;
KCM_ASSERT_VALID(cache);
HEIMDAL_MUTEX_lock(&cache->mutex);
ret = kcm_zero_ccache_data_internal(context, cache);
HEIMDAL_MUTEX_unlock(&cache->mutex);
return ret;
}
krb5_error_code
kcm_retain_ccache(krb5_context context,
kcm_ccache ccache)
{
KCM_ASSERT_VALID(ccache);
HEIMDAL_MUTEX_lock(&ccache->mutex);
ccache->refcnt++;
HEIMDAL_MUTEX_unlock(&ccache->mutex);
return 0;
}
static void
kcm_release_ccache_locked(krb5_context context, kcm_ccache p)
{
if (p->refcnt == 1) {
kcm_free_ccache_data_internal(context, p);
HEIMDAL_MUTEX_unlock(&p->mutex);
HEIMDAL_MUTEX_destroy(&p->mutex);
free(p);
} else {
p->refcnt--;
HEIMDAL_MUTEX_unlock(&p->mutex);
}
}
krb5_error_code
kcm_release_ccache(krb5_context context, kcm_ccache c)
{
KCM_ASSERT_VALID(c);
HEIMDAL_MUTEX_lock(&c->mutex);
kcm_release_ccache_locked(context, c);
return 0;
}
krb5_error_code
kcm_ccache_new(krb5_context context,
const char *name,
kcm_ccache *ccache)
{
krb5_error_code ret;
ret = kcm_ccache_alloc(context, name, ccache);
if (ret == 0) {
kcm_retain_ccache(context, *ccache);
}
return ret;
}
krb5_error_code
kcm_ccache_store_cred(krb5_context context,
kcm_ccache ccache,
krb5_creds *creds,
int copy)
{
krb5_error_code ret;
KCM_ASSERT_VALID(ccache);
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = kcm_ccache_store_cred_internal(context, ccache, creds, NULL, copy);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
return ret;
}
struct kcm_creds *
kcm_ccache_find_cred_uuid(krb5_context context,
kcm_ccache ccache,
kcmuuid_t uuid)
{
struct kcm_creds *c;
for (c = ccache->creds; c != NULL; c = c->next)
if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0)
return c;
return NULL;
}
krb5_error_code
kcm_ccache_store_cred_internal(krb5_context context,
kcm_ccache ccache,
krb5_creds *creds,
kcmuuid_t uuid,
int copy)
{
struct kcm_creds **c;
krb5_error_code ret;
c = &ccache->creds;
while (*c != NULL) {
if (krb5_compare_creds(context, 0, creds, &(*c)->cred)) {
struct kcm_creds *dup_cred = *c;
*c = dup_cred->next;
krb5_free_cred_contents(context, &dup_cred->cred);
free(dup_cred);
} else {
c = &(*c)->next;
}
}
*c = (struct kcm_creds *)calloc(1, sizeof(**c));
if (*c == NULL)
return KRB5_CC_NOMEM;
if (uuid)
memcpy((*c)->uuid, uuid, sizeof((*c)->uuid));
else
CCRandomCopyBytes(kCCRandomDefault, (*c)->uuid, sizeof((*c)->uuid));
if (copy) {
ret = krb5_copy_creds_contents(context, creds, &(*c)->cred);
if (ret) {
free(*c);
*c = NULL;
}
} else {
(*c)->cred = *creds;
ret = 0;
}
#ifdef HAVE_NOTIFY_H
notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
#endif
return ret;
}
krb5_error_code
kcm_ccache_remove_cred_internal(krb5_context context,
kcm_ccache ccache,
krb5_flags whichfields,
const krb5_creds *mcreds)
{
krb5_error_code ret;
struct kcm_creds **c;
ret = KRB5_CC_NOTFOUND;
for (c = &ccache->creds; *c != NULL; c = &(*c)->next) {
if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) {
struct kcm_creds *cred = *c;
*c = cred->next;
krb5_free_cred_contents(context, &cred->cred);
free(cred);
ret = 0;
if (*c == NULL)
break;
}
}
#ifdef HAVE_NOTIFY_H
notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
#endif
return ret;
}
krb5_error_code
kcm_ccache_remove_cred(krb5_context context,
kcm_ccache ccache,
krb5_flags whichfields,
const krb5_creds *mcreds)
{
krb5_error_code ret;
KCM_ASSERT_VALID(ccache);
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
return ret;
}
krb5_error_code
kcm_ccache_retrieve_cred_internal(krb5_context context,
kcm_ccache ccache,
krb5_flags whichfields,
const krb5_creds *mcreds,
krb5_creds **creds)
{
krb5_boolean match;
struct kcm_creds *c;
krb5_error_code ret;
memset(creds, 0, sizeof(*creds));
ret = KRB5_CC_END;
match = FALSE;
for (c = ccache->creds; c != NULL; c = c->next) {
match = krb5_compare_creds(context, whichfields, mcreds, &c->cred);
if (match)
break;
}
if (match) {
ret = 0;
*creds = &c->cred;
}
return ret;
}
krb5_error_code
kcm_ccache_retrieve_cred(krb5_context context,
kcm_ccache ccache,
krb5_flags whichfields,
const krb5_creds *mcreds,
krb5_creds **credp)
{
krb5_error_code ret;
KCM_ASSERT_VALID(ccache);
HEIMDAL_MUTEX_lock(&ccache->mutex);
ret = kcm_ccache_retrieve_cred_internal(context, ccache,
whichfields, mcreds, credp);
HEIMDAL_MUTEX_unlock(&ccache->mutex);
return ret;
}
char *
kcm_ccache_first_name(kcm_client *client)
{
kcm_ccache p;
char *name = NULL;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH(p, &ccache_head, members) {
if (kcm_is_same_session(client, p->uid, p->session))
break;
}
if (p)
name = strdup(p->name);
HEIMDAL_MUTEX_unlock(&ccache_mutex);
return name;
}
void
kcm_cache_remove_session(pid_t session)
{
kcm_ccache p, tempp;
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH_SAFE(p, &ccache_head, members, tempp) {
if (p->session == session) {
kcm_log(1, "remove credental %s because session %d went away",
p->name, (int)session);
TAILQ_REMOVE(&ccache_head, p, members);
dispatch_async(dispatch_get_main_queue(), ^{
HEIMDAL_MUTEX_lock(&p->mutex);
heim_ipc_event_cancel(p->renew_event);
heim_ipc_event_free(p->renew_event);
p->renew_event = NULL;
heim_ipc_event_cancel(p->expire_event);
heim_ipc_event_free(p->expire_event);
p->expire_event = NULL;
kcm_release_ccache_locked(kcm_context, p);
});
}
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
}
static bool
session_exists(pid_t asid)
{
auditinfo_addr_t aia;
aia.ai_asid = asid;
if (audit_get_sinfo_addr(&aia, sizeof(aia)) == 0)
return true;
return false;
}
#define CHECK(s) do { if ((s)) { goto out; } } while(0)
#define DUMP_F_SERVER 1
#define DUMP_F_PASSWORD 2
#define DUMP_F_KEYTAB 4
static krb5_error_code
parse_krb5_cache(krb5_context context, krb5_storage *sp)
{
krb5_error_code ret;
char *name = NULL;
kcm_ccache c;
uint32_t u32;
int32_t s32;
uint8_t u8;
time_t renew_time;
CHECK(ret = krb5_ret_stringz(sp, &name));
ret = kcm_ccache_new(context, name, &c);
free(name);
CHECK(ret);
CHECK(ret = krb5_ret_uuid(sp, c->uuid));
CHECK(ret = krb5_ret_uint32(sp, &u32));
c->renew_time = renew_time = u32;
CHECK(ret = krb5_ret_uint32(sp, &u32));
c->holdcount = u32;
CHECK(ret = krb5_ret_uint32(sp, &u32));
c->flags = u32;
CHECK(ret = krb5_ret_int32(sp, &s32));
c->uid = s32;
CHECK(ret = krb5_ret_int32(sp, &s32));
c->session = s32;
CHECK(ret = krb5_ret_principal(sp, &c->client));
CHECK(ret = krb5_ret_uint32(sp, &u32));
if (u32 & DUMP_F_SERVER)
CHECK(ret = krb5_ret_principal(sp, &c->server));
if (u32 & DUMP_F_PASSWORD)
CHECK(ret = krb5_ret_stringz(sp, &c->password));
if (u32 & DUMP_F_KEYTAB) {
char *keytab;
CHECK(ret = krb5_ret_stringz(sp, &keytab));
CHECK(ret = krb5_kt_resolve(context, keytab, &c->keytab));
free(keytab);
}
while (1) {
krb5_creds cred;
kcmuuid_t uuid;
CHECK(ret = krb5_ret_uint8(sp, &u8));
if (u8 == 0)
break;
CHECK(ret = krb5_ret_uuid(sp, uuid));
CHECK(ret = krb5_ret_creds(sp, &cred));
ret = kcm_ccache_store_cred_internal(context, c, &cred, uuid, 1);
if (cred.flags.b.initial)
kcm_ccache_enqueue_default(context, c, &cred);
krb5_free_cred_contents(context, &cred);
CHECK(ret);
}
if (renew_time && renew_time > time(NULL) - 60) {
kcm_log(1, "re-setting renew time to: %ds (original renew time)", (int)(renew_time - time(NULL)));
heim_ipc_event_set_time(c->renew_event, renew_time);
}
out:
if (ret) {
TAILQ_REMOVE(&ccache_head, c, members);
}
return ret;
}
static krb5_error_code
unparse_krb5_cache(krb5_context context, krb5_storage *sp, kcm_ccache c, time_t *nextwakeup)
{
struct kcm_creds *cred;
krb5_error_code ret;
uint32_t sflags;
if (c->renew_time && (*nextwakeup == 0 || *nextwakeup > c->renew_time))
*nextwakeup = c->renew_time;
CHECK(ret = krb5_store_stringz(sp, c->name));
CHECK(ret = krb5_store_uuid(sp, c->uuid));
CHECK(ret = krb5_store_uint32(sp, c->renew_time));
CHECK(ret = krb5_store_uint32(sp, c->holdcount));
CHECK(ret = krb5_store_uint32(sp, c->flags));
CHECK(ret = krb5_store_int32(sp, c->uid));
CHECK(ret = krb5_store_int32(sp, c->session));
CHECK(ret = krb5_store_principal(sp, c->client));
sflags = 0;
if (c->server) sflags |= DUMP_F_SERVER;
if (c->password) sflags |= DUMP_F_PASSWORD;
if (c->keytab) sflags |= DUMP_F_KEYTAB;
CHECK(ret = krb5_store_uint32(sp, sflags));
if (c->server)
CHECK(ret = krb5_store_principal(sp, c->server));
if (c->password)
CHECK(ret = krb5_store_stringz(sp, c->password));
if (c->keytab) {
char *str;
CHECK(ret = krb5_kt_get_full_name(context, c->keytab, &str));
CHECK(ret = krb5_store_stringz(sp, str));
krb5_xfree(str);
}
for (cred = c->creds; cred != NULL; cred = cred->next) {
CHECK(ret = krb5_store_uint8(sp, 1));
CHECK(ret = krb5_store_uuid(sp, cred->uuid));
CHECK(ret = krb5_store_creds(sp, &cred->cred));
}
CHECK(ret = krb5_store_uint8(sp, 0));
out:
return ret;
}
static krb5_error_code
parse_default_one(krb5_context context, krb5_storage *sp)
{
struct kcm_default_cache *c;
krb5_error_code ret;
int32_t s32;
char *str;
c = calloc(1, sizeof(*c));
if (c == NULL)
return ENOMEM;
CHECK(ret = krb5_ret_int32(sp, &s32));
c->uid = s32;
CHECK(ret = krb5_ret_int32(sp, &s32));
c->session = s32;
CHECK(ret = krb5_ret_stringz(sp, &str));
c->name = str;
c->next = default_caches;
default_caches = c;
out:
if (ret)
kcm_log(10, "failed to parse default entry");
return ret;
}
static krb5_error_code
unparse_default_all(krb5_context context, krb5_storage *sp)
{
struct kcm_default_cache *c;
krb5_error_code r = 0;
for (c = default_caches; r == 0 && c != NULL; c = c->next) {
r = kcm_unparse_wrap(sp, "default-cache", c->session, ^(krb5_storage *inner) {
krb5_error_code ret;
CHECK(ret = krb5_store_int32(inner, c->uid));
CHECK(ret = krb5_store_int32(inner, c->session));
CHECK(ret = krb5_store_stringz(inner, c->name));
out:
return ret;
});
}
return r;
}
#define KCM_DUMP_VERSION 1
void
kcm_parse_cache_data(krb5_context context, krb5_data *data)
{
krb5_error_code ret;
krb5_storage *sp;
char *str;
uint8_t u8;
sp = krb5_storage_from_readonly_mem(data->data, data->length);
if (sp == NULL)
return;
CHECK(ret = krb5_ret_stringz(sp, &str));
if (strcmp(str, "start-dump") != 0) {
free(str);
ret = EINVAL;
goto out;
}
free(str);
CHECK(ret = krb5_ret_uint8(sp, &u8));
CHECK((u8 == KCM_DUMP_VERSION) ? 0 : 1);
CHECK(ret = krb5_ret_uint32(sp, &ccache_nextid));
while(ret == 0) {
int32_t session;
krb5_data data;
krb5_storage *inner;
CHECK(ret = krb5_ret_stringz(sp, &str));
kcm_log(10, "dump: reading a %s entry", str);
if (strcmp(str, "end-dump") == 0) {
free(str);
break;
}
CHECK(ret = krb5_ret_int32(sp, &session));
CHECK(ret = krb5_ret_data(sp, &data));
inner = krb5_storage_from_data(&data);
heim_assert(inner, "krb5_storage_from_data");
if (!session_exists(session)) {
} else if (strcmp(str, "krb5-cache") == 0) {
ret = parse_krb5_cache(context, inner);
} else if (strcmp(str, "digest-cache") == 0) {
ret = kcm_parse_digest_one(context, inner);
} else if (strcmp(str, "default-cache") == 0) {
ret = parse_default_one(context, inner);
} else {
kcm_log(10, "dump: unknown type: %s", str);
ret = 0;
}
if (ret)
kcm_log(10, "dump: failed to unparse a %s cache with: %d", str, ret);
free(str);
krb5_storage_free(inner);
krb5_data_free(&data);
}
out:
krb5_storage_free(sp);
if (ret)
kcm_log(10, "dump: failed to read credential dump: %d", ret);
return;
}
krb5_error_code
kcm_unparse_wrap(krb5_storage *sp, char *name, int32_t session, int (^wrapped)(krb5_storage *inner))
{
krb5_error_code ret;
krb5_storage *inner = krb5_storage_emem();
krb5_data data;
CHECK(ret = wrapped(inner));
CHECK(ret = krb5_store_stringz(sp, name));
CHECK(ret = krb5_store_int32(sp, session));
CHECK(ret = krb5_storage_to_data(inner, &data));
ret = krb5_store_data(sp, data);
krb5_data_free(&data);
out:
if (ret)
kcm_log(10, "dump: failed to add a %s", name);
krb5_storage_free(inner);
return ret;
}
void
kcm_unparse_cache_data(krb5_context context, krb5_data *data)
{
__block time_t nextwakeup = 0;
krb5_error_code ret;
krb5_storage *sp;
kcm_ccache c;
krb5_data_zero(data);
sp = krb5_storage_emem();
if (sp == NULL)
return;
CHECK(ret = krb5_store_stringz(sp, "start-dump"));
CHECK(ret = krb5_store_uint8(sp, KCM_DUMP_VERSION));
CHECK(ret = krb5_store_uint32(sp, ccache_nextid));
HEIMDAL_MUTEX_lock(&ccache_mutex);
TAILQ_FOREACH_REVERSE(c, &ccache_head, ccache_head, members) {
ret = kcm_unparse_wrap(sp, "krb5-cache", c->session, ^(krb5_storage *inner) {
return unparse_krb5_cache(context, inner, c, &nextwakeup);
});
}
HEIMDAL_MUTEX_unlock(&ccache_mutex);
CHECK(ret);
CHECK(ret = unparse_default_all(context, sp));
CHECK(ret = kcm_unparse_digest_all(context, sp));
CHECK(ret = krb5_store_stringz(sp, "end-dump"));
if (nextwakeup) {
int64_t next = nextwakeup - time(NULL);
if (next > 0) {
vproc_swap_integer(NULL, VPROC_GSK_START_INTERVAL, &next, NULL);
}
kcm_log(1, "next wakup in: %d", (int)next);
}
out:
if (ret == 0) {
ret = krb5_storage_to_data(sp, data);
if (ret)
kcm_log(1, "dump: failed to create credential data: %d", ret);
}
krb5_storage_free(sp);
}
static int have_uuid_master = 0;
static krb5_uuid uuid_master;
static const char *dumpfile = "/var/db/kcm-dump.bin";
static const char *keyfile = "/var/db/kcm-dump.uuid";
static krb5_error_code
kcm_load_key(krb5_context context)
{
krb5_error_code ret;
krb5_data enc;
size_t len;
void *p = NULL;
if (have_uuid_master)
return 0;
ret = rk_undumpdata(keyfile, &p, &len);
if (ret != 0)
goto nokey;
if (len != sizeof(uuid_master)) {
free(p);
goto nokey;
}
memcpy(uuid_master, p, sizeof(uuid_master));
free(p);
ret = kcm_store_io(context, uuid_master, "", 0, &enc, true);
if (ret)
goto nokey;
krb5_data_free(&enc);
have_uuid_master = 1;
return 0;
nokey:
ret = kcm_create_key(uuid_master);
if (ret)
return ret;
rk_dumpdata(keyfile, uuid_master, sizeof(uuid_master));
have_uuid_master = 1;
return ret;
}
void
kcm_write_dump(krb5_context context)
{
uuid_string_t uuidstr;
krb5_data data, enc;
krb5_error_code ret;
ret = kcm_load_key(context);
if (ret) {
unlink(keyfile);
unlink(dumpfile);
return;
}
uuid_unparse(uuid_master, uuidstr);
kcm_log(10, "dump: [masterkey] %s", uuidstr);
kcm_unparse_cache_data(context, &data);
if (data.length == 0)
return;
ret = kcm_store_io(context, uuid_master, data.data, data.length, &enc, true);
krb5_data_free(&data);
if (ret) {
kcm_log(1, "dump: failed to encrypt credential data %d", ret);
return;
}
rk_dumpdata(dumpfile, enc.data, enc.length);
krb5_data_free(&enc);
}
void
kcm_read_dump(krb5_context context)
{
uuid_string_t uuidstr;
krb5_error_code ret;
krb5_data data;
size_t len;
void *p;
ret = kcm_load_key(context);
if (ret)
return;
uuid_unparse(uuid_master, uuidstr);
kcm_log(10, "load: [masterkey] %s", uuidstr);
ret = rk_undumpdata(dumpfile, &p, &len);
if (ret != 0 || len == 0)
return;
ret = kcm_store_io(kcm_context, uuid_master, p, len, &data, false);
if (ret == 0) {
kcm_parse_cache_data(kcm_context, &data);
krb5_data_free(&data);
have_uuid_master = 1;
} else {
unlink(dumpfile);
}
free(p);
}