#include "cc-int.h"
#ifdef USE_KEYRING_CCACHE
#include <errno.h>
#include <keyutils.h>
#ifdef DEBUG
#define KRCC_DEBUG 1
#endif
#if KRCC_DEBUG
void debug_print(char *fmt, ...);
#include <syslog.h>
#define DEBUG_PRINT(x) debug_print x
void
debug_print(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
#ifdef DEBUG_STDERR
vfprintf(stderr, fmt, ap);
#else
vsyslog(LOG_ERR, fmt, ap);
#endif
va_end(ap);
}
#else
#define DEBUG_PRINT(x)
#endif
#ifdef HAVE_PERSISTENT_KEYRING
#define KRCC_CRED_KEY_TYPE "big_key"
#else
#define KRCC_CRED_KEY_TYPE "user"
#endif
#define KRCC_KEY_TYPE_USER "user"
#define KRCC_KEY_TYPE_KEYRING "keyring"
#define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"
#define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"
#define KRCC_COLLECTION_PRIMARY "krb_ccache:primary"
#define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__"
#define KRCC_CCCOL_PREFIX "_krb_"
#define KRCC_PERSISTENT_KEYRING_NAME "_krb"
#define KRCC_TIME_OFFSETS "__krb5_time_offsets__"
#define KRCC_NAME_PREFIX "krb_ccache_"
#define KRCC_NAME_RAND_CHARS 8
#define KRCC_COLLECTION_VERSION 1
#define KRCC_PERSISTENT_ANCHOR "persistent"
#define KRCC_PROCESS_ANCHOR "process"
#define KRCC_THREAD_ANCHOR "thread"
#define KRCC_SESSION_ANCHOR "session"
#define KRCC_USER_ANCHOR "user"
#define KRCC_LEGACY_ANCHOR "legacy"
typedef struct _krcc_cursor
{
int numkeys;
int currkey;
key_serial_t princ_id;
key_serial_t offsets_id;
key_serial_t *keys;
} *krcc_cursor;
typedef struct _krcc_data
{
char *name;
k5_cc_mutex lock;
key_serial_t collection_id;
key_serial_t cache_id;
key_serial_t princ_id;
krb5_boolean is_legacy_type;
} krcc_data;
k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
extern const krb5_cc_ops krb5_krcc_ops;
static const char *KRB5_CALLCONV
krcc_get_name(krb5_context context, krb5_ccache id);
static krb5_error_code KRB5_CALLCONV
krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
static krb5_error_code KRB5_CALLCONV
krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
krb5_creds *creds);
static krb5_error_code KRB5_CALLCONV
krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor);
static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor);
static krb5_error_code clear_cache_keyring(krb5_context context,
krb5_ccache id);
static krb5_error_code make_krcc_data(const char *anchor_name,
const char *collection_name,
const char *subsidiary_name,
key_serial_t cache_id, key_serial_t
collection_id, krcc_data **datapp);
static krb5_error_code save_principal(krb5_context context, krb5_ccache id,
krb5_principal princ);
static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id,
int32_t time_offset,
int32_t usec_offset);
static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id,
int32_t *time_offset,
int32_t *usec_offset);
extern krb5_error_code krb5_change_cache(void);
static key_serial_t
get_persistent_fallback(uid_t uid)
{
return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
}
#ifdef HAVE_PERSISTENT_KEYRING
#define GET_PERSISTENT get_persistent_real
static key_serial_t
get_persistent_real(uid_t uid)
{
key_serial_t key;
key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) :
key;
}
#else
#define GET_PERSISTENT get_persistent_fallback
#endif
static key_serial_t
session_write_anchor(void)
{
key_serial_t s, u;
s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
}
static krb5_error_code
find_or_create_keyring(key_serial_t parent, key_serial_t possess,
const char *name, key_serial_t *key_out)
{
key_serial_t key;
*key_out = -1;
key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
if (key == -1) {
if (possess != 0) {
key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
if (key == -1)
return errno;
if (keyctl_link(key, parent) == -1)
return errno;
} else {
key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
if (key == -1)
return errno;
}
}
*key_out = key;
return 0;
}
static krb5_error_code
parse_residual(const char *residual, char **anchor_name_out,
char **collection_name_out, char **subsidiary_name_out)
{
krb5_error_code ret;
char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
const char *sep;
*anchor_name_out = 0;
*collection_name_out = NULL;
*subsidiary_name_out = NULL;
sep = strchr(residual, ':');
if (sep == NULL) {
anchor_name = strdup(KRCC_LEGACY_ANCHOR);
if (anchor_name == NULL)
goto oom;
} else {
anchor_name = k5memdup0(residual, sep - residual, &ret);
if (anchor_name == NULL)
goto oom;
residual = sep + 1;
}
sep = strchr(residual, ':');
if (sep == NULL) {
collection_name = strdup(residual);
if (collection_name == NULL)
goto oom;
subsidiary_name = NULL;
} else {
collection_name = k5memdup0(residual, sep - residual, &ret);
if (collection_name == NULL)
goto oom;
subsidiary_name = strdup(sep + 1);
if (subsidiary_name == NULL)
goto oom;
}
*anchor_name_out = anchor_name;
*collection_name_out = collection_name;
*subsidiary_name_out = subsidiary_name;
return 0;
oom:
free(anchor_name);
free(collection_name);
free(subsidiary_name);
return ENOMEM;
}
static krb5_boolean
is_legacy_cache_name(const char *residual)
{
const char *sep, *aname, *cname, *sname;
size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
aname = residual;
sep = strchr(residual, ':');
if (sep == NULL)
return FALSE;
alen = sep - aname;
cname = sep + 1;
sep = strchr(cname, ':');
if (sep == NULL)
return FALSE;
clen = sep - cname;
sname = sep + 1;
return alen == legacy_len && clen == strlen(sname) &&
strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
strncmp(cname, sname, clen) == 0;
}
static krb5_error_code
get_default(krb5_context context, char **anchor_name_out,
char **collection_name_out, char **subsidiary_name_out)
{
const char *defname;
*anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
defname = krb5_cc_default_name(context);
if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
return 0;
return parse_residual(defname + 8, anchor_name_out, collection_name_out,
subsidiary_name_out);
}
static krb5_error_code
make_subsidiary_residual(const char *anchor_name, const char *collection_name,
const char *subsidiary_name, char **residual_out)
{
if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
subsidiary_name) < 0) {
*residual_out = NULL;
return ENOMEM;
}
return 0;
}
static krb5_error_code
get_collection(const char *anchor_name, const char *collection_name,
key_serial_t *collection_id_out)
{
krb5_error_code ret;
key_serial_t persistent_id, anchor_id, possess_id = 0;
char *ckname, *cnend;
long uidnum;
*collection_id_out = 0;
if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
if (*collection_name != '\0') {
errno = 0;
uidnum = strtol(collection_name, &cnend, 10);
if (errno || *cnend != '\0')
return KRB5_KCC_INVALID_UID;
} else {
uidnum = geteuid();
}
persistent_id = GET_PERSISTENT(uidnum);
if (persistent_id == -1)
return KRB5_KCC_INVALID_UID;
return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
KRCC_PERSISTENT_KEYRING_NAME,
collection_id_out);
}
if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
anchor_id = KEY_SPEC_PROCESS_KEYRING;
} else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
anchor_id = KEY_SPEC_THREAD_KEYRING;
} else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
anchor_id = session_write_anchor();
} else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
anchor_id = KEY_SPEC_USER_KEYRING;
possess_id = KEY_SPEC_PROCESS_KEYRING;
} else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
anchor_id = session_write_anchor();
} else {
return KRB5_KCC_INVALID_ANCHOR;
}
if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
return ENOMEM;
ret = find_or_create_keyring(anchor_id, possess_id, ckname,
collection_id_out);
free(ckname);
return ret;
}
static krb5_error_code
set_primary_name(krb5_context context, key_serial_t collection_id,
const char *subsidiary_name)
{
key_serial_t key;
uint32_t len = strlen(subsidiary_name), plen = 8 + len;
unsigned char *payload;
payload = malloc(plen);
if (payload == NULL)
return ENOMEM;
store_32_be(KRCC_COLLECTION_VERSION, payload);
store_32_be(len, payload + 4);
memcpy(payload + 8, subsidiary_name, len);
key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
payload, plen, collection_id);
free(payload);
return (key == -1) ? errno : 0;
}
static krb5_error_code
parse_index(krb5_context context, int32_t *version, char **primary,
const unsigned char *payload, size_t psize)
{
krb5_error_code ret;
uint32_t len;
if (psize < 8)
return KRB5_CC_END;
*version = load_32_be(payload);
len = load_32_be(payload + 4);
if (len > psize - 8)
return KRB5_CC_END;
*primary = k5memdup0(payload + 8, len, &ret);
return (*primary == NULL) ? ret : 0;
}
static krb5_error_code
get_primary_name(krb5_context context, const char *anchor_name,
const char *collection_name, key_serial_t collection_id,
char **subsidiary_out)
{
krb5_error_code ret;
key_serial_t primary_id, legacy;
void *payload = NULL;
int payloadlen;
int32_t version;
char *subsidiary_name = NULL;
*subsidiary_out = NULL;
primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
KRCC_COLLECTION_PRIMARY, 0);
if (primary_id == -1) {
subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
collection_name);
if (subsidiary_name == NULL) {
ret = ENOMEM;
goto cleanup;
}
ret = set_primary_name(context, collection_id, subsidiary_name);
if (ret)
goto cleanup;
if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
ret = errno;
goto cleanup;
}
}
} else {
payloadlen = keyctl_read_alloc(primary_id, &payload);
if (payloadlen == -1) {
ret = errno;
goto cleanup;
}
ret = parse_index(context, &version, &subsidiary_name, payload,
payloadlen);
if (ret)
goto cleanup;
if (version != KRCC_COLLECTION_VERSION) {
ret = KRB5_KCC_UNKNOWN_VERSION;
goto cleanup;
}
}
*subsidiary_out = subsidiary_name;
subsidiary_name = NULL;
cleanup:
free(payload);
free(subsidiary_name);
return ret;
}
static krb5_error_code
unique_keyring(krb5_context context, key_serial_t collection_id,
char **subsidiary_out, key_serial_t *cache_id_out)
{
key_serial_t key;
krb5_error_code ret;
char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
int tries;
*subsidiary_out = NULL;
*cache_id_out = 0;
memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
tries = 100;
while (tries-- > 0) {
ret = krb5int_random_string(context, uniquename + prefixlen,
KRCC_NAME_RAND_CHARS);
if (ret)
goto cleanup;
key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
0);
if (key < 0) {
key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0,
collection_id);
if (key < 0) {
ret = errno;
goto cleanup;
}
break;
}
}
if (tries <= 0) {
ret = KRB5_CC_BADNAME;
goto cleanup;
}
*subsidiary_out = strdup(uniquename);
if (*subsidiary_out == NULL) {
ret = ENOMEM;
goto cleanup;
}
*cache_id_out = key;
ret = 0;
cleanup:
k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
return ret;
}
static krb5_error_code
add_cred_key(const char *name, const void *payload, size_t plen,
key_serial_t cache_id, krb5_boolean legacy_type,
key_serial_t *key_out)
{
key_serial_t key;
*key_out = -1;
if (!legacy_type) {
key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id);
if (key != -1) {
*key_out = key;
return 0;
} else if (errno != EINVAL && errno != ENODEV) {
return errno;
}
}
key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id);
if (key == -1)
return errno;
*key_out = key;
return 0;
}
static void
update_keyring_expiration(krb5_context context, krb5_ccache id)
{
krcc_data *data = id->data;
krb5_cc_cursor cursor;
krb5_creds creds;
krb5_timestamp now, endtime = 0;
unsigned int timeout;
if (krcc_start_seq_get(context, id, &cursor) != 0)
return;
for (;;) {
if (krcc_next_cred(context, id, &cursor, &creds) != 0)
break;
if (ts_after(creds.times.endtime, endtime))
endtime = creds.times.endtime;
krb5_free_cred_contents(context, &creds);
}
(void)krcc_end_seq_get(context, id, &cursor);
if (endtime == 0)
return;
if (krb5_timeofday(context, &now) != 0)
return;
timeout = ts_after(endtime, now) ? ts_interval(now, endtime) : 1;
(void)keyctl_set_timeout(data->cache_id, timeout);
}
static krb5_error_code KRB5_CALLCONV
krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
{
krcc_data *data = (krcc_data *)id->data;
krb5_os_context os_ctx = &context->os_context;
krb5_error_code ret;
const char *cache_name, *p;
k5_cc_mutex_lock(context, &data->lock);
ret = clear_cache_keyring(context, id);
if (ret)
goto out;
if (!data->cache_id) {
p = strrchr(data->name, ':');
cache_name = (p != NULL) ? p + 1 : data->name;
ret = find_or_create_keyring(data->collection_id, 0, cache_name,
&data->cache_id);
if (ret)
goto out;
}
if (is_legacy_cache_name(data->name))
(void)keyctl_link(data->cache_id, session_write_anchor());
ret = save_principal(context, id, princ);
if (!is_legacy_cache_name(data->name) &&
(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
ret = save_time_offsets(context, id, os_ctx->time_offset,
os_ctx->usec_offset);
}
if (ret == 0)
krb5_change_cache();
out:
k5_cc_mutex_unlock(context, &data->lock);
return ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_close(krb5_context context, krb5_ccache id)
{
krcc_data *data = id->data;
k5_cc_mutex_destroy(&data->lock);
free(data->name);
free(data);
free(id);
return 0;
}
static krb5_error_code
clear_cache_keyring(krb5_context context, krb5_ccache id)
{
krcc_data *data = id->data;
int res;
k5_cc_mutex_assert_locked(context, &data->lock);
DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n",
data->cache_id, data->princ_id));
if (data->cache_id) {
res = keyctl_clear(data->cache_id);
if (res != 0)
return errno;
}
data->princ_id = 0;
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_destroy(krb5_context context, krb5_ccache id)
{
krb5_error_code ret = 0;
krcc_data *data = id->data;
int res;
k5_cc_mutex_lock(context, &data->lock);
clear_cache_keyring(context, id);
if (data->cache_id) {
res = keyctl_unlink(data->cache_id, data->collection_id);
if (res < 0) {
ret = errno;
DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id,
data->collection_id, error_message(errno)));
}
if (is_legacy_cache_name(data->name))
(void)keyctl_unlink(data->cache_id, session_write_anchor());
}
k5_cc_mutex_unlock(context, &data->lock);
k5_cc_mutex_destroy(&data->lock);
free(data->name);
free(data);
free(id);
krb5_change_cache();
return ret;
}
static krb5_error_code
make_cache(krb5_context context, key_serial_t collection_id,
key_serial_t cache_id, const char *anchor_name,
const char *collection_name, const char *subsidiary_name,
krb5_ccache *cache_out)
{
krb5_error_code ret;
krb5_os_context os_ctx = &context->os_context;
krb5_ccache ccache = NULL;
krcc_data *data;
key_serial_t pkey = 0;
pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
0);
if (pkey < 0)
pkey = 0;
ccache = malloc(sizeof(struct _krb5_ccache));
if (!ccache)
return ENOMEM;
ret = make_krcc_data(anchor_name, collection_name, subsidiary_name,
cache_id, collection_id, &data);
if (ret) {
free(ccache);
return ret;
}
data->princ_id = pkey;
ccache->ops = &krb5_krcc_ops;
ccache->data = data;
ccache->magic = KV5M_CCACHE;
*cache_out = ccache;
if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
!(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
if (get_time_offsets(context, ccache, &os_ctx->time_offset,
&os_ctx->usec_offset) == 0) {
os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME;
os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID;
}
}
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
{
krb5_error_code ret;
key_serial_t collection_id, cache_id;
char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
ret = parse_residual(residual, &anchor_name, &collection_name,
&subsidiary_name);
if (ret)
goto cleanup;
ret = get_collection(anchor_name, collection_name, &collection_id);
if (ret)
goto cleanup;
if (subsidiary_name == NULL) {
ret = get_primary_name(context, anchor_name, collection_name,
collection_id, &subsidiary_name);
if (ret)
goto cleanup;
}
cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING,
subsidiary_name, 0);
if (cache_id < 0)
cache_id = 0;
ret = make_cache(context, collection_id, cache_id, anchor_name,
collection_name, subsidiary_name, id);
if (ret)
goto cleanup;
cleanup:
free(anchor_name);
free(collection_name);
free(subsidiary_name);
return ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_start_seq_get(krb5_context context, krb5_ccache id,
krb5_cc_cursor *cursor)
{
krcc_cursor krcursor;
krcc_data *data = id->data;
void *keys;
long size;
k5_cc_mutex_lock(context, &data->lock);
if (!data->cache_id) {
k5_cc_mutex_unlock(context, &data->lock);
return KRB5_FCC_NOFILE;
}
size = keyctl_read_alloc(data->cache_id, &keys);
if (size == -1) {
DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
k5_cc_mutex_unlock(context, &data->lock);
return KRB5_CC_IO;
}
krcursor = calloc(1, sizeof(*krcursor));
if (krcursor == NULL) {
free(keys);
k5_cc_mutex_unlock(context, &data->lock);
return KRB5_CC_NOMEM;
}
krcursor->princ_id = data->princ_id;
krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER,
KRCC_TIME_OFFSETS, 0);
krcursor->numkeys = size / sizeof(key_serial_t);
krcursor->keys = keys;
k5_cc_mutex_unlock(context, &data->lock);
*cursor = krcursor;
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
krb5_creds *creds)
{
krcc_cursor krcursor;
krb5_error_code ret;
int psize;
void *payload = NULL;
memset(creds, 0, sizeof(krb5_creds));
krcursor = *cursor;
if (krcursor == NULL)
return KRB5_CC_END;
while (krcursor->currkey < krcursor->numkeys) {
if (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
krcursor->currkey++;
continue;
}
psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey],
&payload);
if (psize != -1) {
krcursor->currkey++;
ret = k5_unmarshal_cred(payload, psize, 4, creds);
free(payload);
return ret;
} else if (errno != ENOKEY && errno != EACCES) {
DEBUG_PRINT(("Error reading key %d: %s\n",
krcursor->keys[krcursor->currkey], strerror(errno)));
return KRB5_FCC_NOFILE;
}
krcursor->currkey++;
}
return KRB5_CC_END;
}
static krb5_error_code KRB5_CALLCONV
krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
{
krcc_cursor krcursor = *cursor;
if (krcursor != NULL) {
free(krcursor->keys);
free(krcursor);
}
*cursor = NULL;
return 0;
}
static krb5_error_code
make_krcc_data(const char *anchor_name, const char *collection_name,
const char *subsidiary_name, key_serial_t cache_id,
key_serial_t collection_id, krcc_data **data_out)
{
krb5_error_code ret;
krcc_data *data;
*data_out = NULL;
data = malloc(sizeof(krcc_data));
if (data == NULL)
return KRB5_CC_NOMEM;
ret = k5_cc_mutex_init(&data->lock);
if (ret) {
free(data);
return ret;
}
ret = make_subsidiary_residual(anchor_name, collection_name,
subsidiary_name, &data->name);
if (ret) {
k5_cc_mutex_destroy(&data->lock);
free(data);
return ret;
}
data->princ_id = 0;
data->cache_id = cache_id;
data->collection_id = collection_id;
data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
*data_out = data;
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_generate_new(krb5_context context, krb5_ccache *id_out)
{
krb5_ccache id = NULL;
krb5_error_code ret;
char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
char *new_subsidiary_name = NULL, *new_residual = NULL;
krcc_data *data;
key_serial_t collection_id;
key_serial_t cache_id = 0;
*id_out = NULL;
ret = get_default(context, &anchor_name, &collection_name,
&subsidiary_name);
if (ret)
return ret;
if (anchor_name == NULL) {
ret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
&collection_name, &subsidiary_name);
if (ret)
return ret;
}
if (subsidiary_name != NULL) {
k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
_("Can't create new subsidiary cache because default cache "
"is already a subsidiary"));
ret = KRB5_DCC_CANNOT_CREATE;
goto cleanup;
}
id = malloc(sizeof(struct _krb5_ccache));
if (id == NULL) {
ret = ENOMEM;
goto cleanup;
}
id->ops = &krb5_krcc_ops;
ret = get_collection(anchor_name, collection_name, &collection_id);
if (ret)
goto cleanup;
ret = unique_keyring(context, collection_id, &new_subsidiary_name,
&cache_id);
if (ret)
goto cleanup;
ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name,
cache_id, collection_id, &data);
if (ret)
goto cleanup;
id->data = data;
krb5_change_cache();
cleanup:
free(anchor_name);
free(collection_name);
free(subsidiary_name);
free(new_subsidiary_name);
free(new_residual);
if (ret) {
free(id);
return ret;
}
*id_out = id;
return 0;
}
static const char *KRB5_CALLCONV
krcc_get_name(krb5_context context, krb5_ccache id)
{
return ((krcc_data *)id->data)->name;
}
static krb5_error_code KRB5_CALLCONV
krcc_get_principal(krb5_context context, krb5_ccache id,
krb5_principal *princ_out)
{
krcc_data *data = id->data;
krb5_error_code ret;
void *payload = NULL;
int psize;
*princ_out = NULL;
k5_cc_mutex_lock(context, &data->lock);
if (!data->cache_id || !data->princ_id) {
ret = KRB5_FCC_NOFILE;
k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"),
data->name);
goto errout;
}
psize = keyctl_read_alloc(data->princ_id, &payload);
if (psize == -1) {
DEBUG_PRINT(("Reading principal key %d: %s\n",
data->princ_id, strerror(errno)));
ret = KRB5_CC_IO;
goto errout;
}
ret = k5_unmarshal_princ(payload, psize, 4, princ_out);
errout:
free(payload);
k5_cc_mutex_unlock(context, &data->lock);
return ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_retrieve(krb5_context context, krb5_ccache id,
krb5_flags whichfields, krb5_creds *mcreds,
krb5_creds *creds)
{
return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
creds);
}
static krb5_error_code KRB5_CALLCONV
krcc_remove_cred(krb5_context context, krb5_ccache cache,
krb5_flags flags, krb5_creds *creds)
{
krb5_error_code ret;
krcc_data *data = cache->data;
krb5_cc_cursor cursor;
krb5_creds c;
krcc_cursor krcursor;
key_serial_t key;
krb5_boolean match;
ret = krcc_start_seq_get(context, cache, &cursor);
if (ret)
return ret;
for (;;) {
ret = krcc_next_cred(context, cache, &cursor, &c);
if (ret)
break;
match = krb5int_cc_creds_match_request(context, flags, creds, &c);
krb5_free_cred_contents(context, &c);
if (match) {
krcursor = cursor;
key = krcursor->keys[krcursor->currkey - 1];
if (keyctl_unlink(key, data->cache_id) == -1) {
ret = errno;
break;
}
}
}
krcc_end_seq_get(context, cache, &cursor);
return (ret == KRB5_CC_END) ? 0 : ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
{
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out)
{
*flags_out = 0;
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
{
krb5_error_code ret;
krcc_data *data = id->data;
struct k5buf buf = EMPTY_K5BUF;
char *keyname = NULL;
key_serial_t cred_key;
krb5_timestamp now;
k5_cc_mutex_lock(context, &data->lock);
if (!data->cache_id) {
k5_cc_mutex_unlock(context, &data->lock);
return KRB5_FCC_NOFILE;
}
ret = krb5_unparse_name(context, creds->server, &keyname);
if (ret)
goto errout;
k5_buf_init_dynamic_zap(&buf);
k5_marshal_cred(&buf, 4, creds);
ret = k5_buf_status(&buf);
if (ret)
goto errout;
DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n",
keyname, data->cache_id));
ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id,
data->is_legacy_type, &cred_key);
if (ret)
goto errout;
ret = krb5_timeofday(context, &now);
if (ret)
goto errout;
if (ts_after(creds->times.endtime, now)) {
(void)keyctl_set_timeout(cred_key,
ts_interval(now, creds->times.endtime));
}
update_keyring_expiration(context, id);
errout:
k5_buf_free(&buf);
krb5_free_unparsed_name(context, keyname);
k5_cc_mutex_unlock(context, &data->lock);
return ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_lock(krb5_context context, krb5_ccache id)
{
krcc_data *data = id->data;
k5_cc_mutex_lock(context, &data->lock);
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_unlock(krb5_context context, krb5_ccache id)
{
krcc_data *data = id->data;
k5_cc_mutex_unlock(context, &data->lock);
return 0;
}
static krb5_error_code
save_principal(krb5_context context, krb5_ccache id, krb5_principal princ)
{
krcc_data *data = id->data;
krb5_error_code ret;
struct k5buf buf;
key_serial_t newkey;
k5_cc_mutex_assert_locked(context, &data->lock);
k5_buf_init_dynamic(&buf);
k5_marshal_princ(&buf, 4, princ);
if (k5_buf_status(&buf) != 0)
return ENOMEM;
#ifdef KRCC_DEBUG
{
krb5_error_code rc;
char *princname = NULL;
rc = krb5_unparse_name(context, princ, &princname);
DEBUG_PRINT(("save_principal: adding new key '%s' "
"to keyring %d for principal '%s'\n",
KRCC_SPEC_PRINC_KEYNAME, data->cache_id,
rc ? "<unknown>" : princname));
if (rc == 0)
krb5_free_unparsed_name(context, princname);
}
#endif
newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data,
buf.len, data->cache_id);
if (newkey < 0) {
ret = errno;
DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret)));
} else {
data->princ_id = newkey;
ret = 0;
}
k5_buf_free(&buf);
return ret;
}
static krb5_error_code
save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset,
int32_t usec_offset)
{
krcc_data *data = id->data;
key_serial_t newkey;
unsigned char payload[8];
k5_cc_mutex_assert_locked(context, &data->lock);
store_32_be(time_offset, payload);
store_32_be(usec_offset, payload + 4);
newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8,
data->cache_id);
if (newkey == -1)
return errno;
return 0;
}
static krb5_error_code
get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset,
int32_t *usec_offset)
{
krcc_data *data = id->data;
krb5_error_code ret = 0;
key_serial_t key;
void *payload = NULL;
int psize;
k5_cc_mutex_lock(context, &data->lock);
if (!data->cache_id) {
ret = KRB5_FCC_NOFILE;
goto errout;
}
key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS,
0);
if (key == -1) {
ret = ENOENT;
goto errout;
}
psize = keyctl_read_alloc(key, &payload);
if (psize == -1) {
DEBUG_PRINT(("Reading time offsets key %d: %s\n",
key, strerror(errno)));
ret = KRB5_CC_IO;
goto errout;
}
if (psize < 8) {
ret = KRB5_CC_END;
goto errout;
}
*time_offset = load_32_be(payload);
*usec_offset = load_32_be((char *)payload + 4);
errout:
free(payload);
k5_cc_mutex_unlock(context, &data->lock);
return ret;
}
struct krcc_ptcursor_data {
key_serial_t collection_id;
char *anchor_name;
char *collection_name;
char *subsidiary_name;
char *primary_name;
krb5_boolean first;
long num_keys;
long next_key;
key_serial_t *keys;
};
static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
{
struct krcc_ptcursor_data *ptd;
krb5_cc_ptcursor cursor;
krb5_error_code ret;
void *keys;
long size;
*cursor_out = NULL;
cursor = k5alloc(sizeof(*cursor), &ret);
if (cursor == NULL)
return ENOMEM;
ptd = k5alloc(sizeof(*ptd), &ret);
if (ptd == NULL)
goto error;
cursor->ops = &krb5_krcc_ops;
cursor->data = ptd;
ptd->first = TRUE;
ret = get_default(context, &ptd->anchor_name, &ptd->collection_name,
&ptd->subsidiary_name);
if (ret)
goto error;
if (ptd->anchor_name == NULL) {
*cursor_out = cursor;
return 0;
}
ret = get_collection(ptd->anchor_name, ptd->collection_name,
&ptd->collection_id);
if (ret)
goto error;
if (ptd->subsidiary_name == NULL) {
ret = get_primary_name(context, ptd->anchor_name,
ptd->collection_name, ptd->collection_id,
&ptd->primary_name);
if (ret)
goto error;
size = keyctl_read_alloc(ptd->collection_id, &keys);
if (size == -1) {
ret = errno;
goto error;
}
ptd->keys = keys;
ptd->num_keys = size / sizeof(key_serial_t);
}
*cursor_out = cursor;
return 0;
error:
krcc_ptcursor_free(context, &cursor);
return ret;
}
static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
krb5_ccache *cache_out)
{
krb5_error_code ret;
struct krcc_ptcursor_data *ptd = cursor->data;
key_serial_t key, cache_id = 0;
const char *first_name, *keytype, *sep, *subsidiary_name;
size_t keytypelen;
char *description = NULL;
*cache_out = NULL;
if (ptd->collection_id == 0)
return 0;
if (ptd->first) {
ptd->first = FALSE;
first_name = (ptd->primary_name != NULL) ? ptd->primary_name :
ptd->subsidiary_name;
cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING,
first_name, 0);
if (cache_id != -1) {
return make_cache(context, ptd->collection_id, cache_id,
ptd->anchor_name, ptd->collection_name,
first_name, cache_out);
}
}
if (ptd->subsidiary_name != NULL)
return 0;
keytype = KRCC_KEY_TYPE_KEYRING ";";
keytypelen = strlen(keytype);
for (; ptd->next_key < ptd->num_keys; ptd->next_key++) {
free(description);
description = NULL;
key = ptd->keys[ptd->next_key];
if (keyctl_describe_alloc(key, &description) < 0)
continue;
sep = strrchr(description, ';');
if (sep == NULL)
continue;
subsidiary_name = sep + 1;
if (strncmp(description, keytype, keytypelen) != 0)
continue;
if (strcmp(subsidiary_name, ptd->primary_name) == 0)
continue;
ptd->next_key++;
ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name,
ptd->collection_name, subsidiary_name, cache_out);
free(description);
return ret;
}
free(description);
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
{
struct krcc_ptcursor_data *ptd = (*cursor)->data;
if (ptd != NULL) {
free(ptd->anchor_name);
free(ptd->collection_name);
free(ptd->subsidiary_name);
free(ptd->primary_name);
free(ptd->keys);
free(ptd);
}
free(*cursor);
*cursor = NULL;
return 0;
}
static krb5_error_code KRB5_CALLCONV
krcc_switch_to(krb5_context context, krb5_ccache cache)
{
krcc_data *data = cache->data;
krb5_error_code ret;
char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
key_serial_t collection_id;
ret = parse_residual(data->name, &anchor_name, &collection_name,
&subsidiary_name);
if (ret)
goto cleanup;
ret = get_collection(anchor_name, collection_name, &collection_id);
if (ret)
goto cleanup;
ret = set_primary_name(context, collection_id, subsidiary_name);
cleanup:
free(anchor_name);
free(collection_name);
free(subsidiary_name);
return ret;
}
const krb5_cc_ops krb5_krcc_ops = {
0,
"KEYRING",
krcc_get_name,
krcc_resolve,
krcc_generate_new,
krcc_initialize,
krcc_destroy,
krcc_close,
krcc_store,
krcc_retrieve,
krcc_get_principal,
krcc_start_seq_get,
krcc_next_cred,
krcc_end_seq_get,
krcc_remove_cred,
krcc_set_flags,
krcc_get_flags,
krcc_ptcursor_new,
krcc_ptcursor_next,
krcc_ptcursor_free,
NULL,
NULL,
krcc_lock,
krcc_unlock,
krcc_switch_to,
};
#else
const krb5_cc_ops krb5_krcc_ops = {
0,
"KEYRING",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
#endif