Blob Blame History Raw
commit fb4817a32d0c369049e0868468dd2eb75487630d
Author: Simo Sorce <simo@redhat.com>
Date:   Thu Nov 14 17:23:59 2013 -0500

    Add support to store time offsets in cc_keyring
    
    The code follows the same model used for the memory ccache type.  Time
    offsets are stored in each credential cache in a special key just like
    the principal name.  Legacy session caches do not store timestamps as
    legacy code would fail when iterating over the new offset key.
    
    [ghudson@mit.edu: minor formatting changes; note legacy session
    exception in commit message]
    
    ticket: 7768 (new)
    target_version: 1.12
    tags: pullup

diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c
index a07a0dc..2192fa5 100644
--- a/src/lib/krb5/ccache/cc_keyring.c
+++ b/src/lib/krb5/ccache/cc_keyring.c
@@ -189,6 +189,11 @@ debug_print(char *fmt, ...)
 #define KRCC_PERSISTENT_KEYRING_NAME "_krb"
 
 /*
+ * Name of the key holding time offsets for the individual cache
+ */
+#define KRCC_TIME_OFFSETS "__krb5_time_offsets__"
+
+/*
  * Keyring name prefix and length of random name part
  */
 #define KRCC_NAME_PREFIX "krb_ccache_"
@@ -217,6 +222,7 @@ typedef struct _krb5_krcc_cursor
     int     numkeys;
     int     currkey;
     key_serial_t princ_id;
+    key_serial_t offsets_id;
     key_serial_t *keys;
 } *krb5_krcc_cursor;
 
@@ -340,6 +346,12 @@ static krb5_error_code krb5_krcc_save_principal
 
 static krb5_error_code krb5_krcc_retrieve_principal
 (krb5_context context, krb5_ccache id, krb5_principal * princ);
+static krb5_error_code krb5_krcc_save_time_offsets
+(krb5_context context, krb5_ccache id, krb5_int32 time_offset,
+ krb5_int32 usec_offset);
+static krb5_error_code krb5_krcc_get_time_offsets
+(krb5_context context, krb5_ccache id, krb5_int32 *time_offset,
+ krb5_int32 *usec_offset);
 
 /* Routines to parse a key from a keyring into a cred structure */
 static krb5_error_code krb5_krcc_parse
@@ -410,6 +422,12 @@ krb5_krcc_parse_index(krb5_context context, krb5_int32 *version,
 static krb5_error_code
 krb5_krcc_unparse_index(krb5_context context, krb5_int32 version,
                         const char *primary, void **datapp, int *lenptr);
+static krb5_error_code
+krb5_krcc_parse_offsets(krb5_context context, krb5_int32 *time_offset,
+                        krb5_int32 *usec_offset, void *payload, int psize);
+static krb5_error_code
+krb5_krcc_unparse_offsets(krb5_context context, krb5_int32 time_offset,
+                          krb5_int32 usec_offset, void **datapp, int *lenptr);
 
 /* Note the following is a stub function for Linux */
 extern krb5_error_code krb5_change_cache(void);
@@ -835,6 +853,7 @@ krb5_krcc_initialize(krb5_context context, krb5_ccache id,
                      krb5_principal princ)
 {
     krb5_krcc_data *data = (krb5_krcc_data *)id->data;
+    krb5_os_context os_ctx = &context->os_context;
     krb5_error_code kret;
     const char *cache_name, *p;
 
@@ -863,6 +882,15 @@ krb5_krcc_initialize(krb5_context context, krb5_ccache id,
         (void)keyctl_link(data->cache_id, KEY_SPEC_SESSION_KEYRING);
 
     kret = krb5_krcc_save_principal(context, id, princ);
+
+    /* Save time offset if it is valid and this is not a legacy cache.  Legacy
+     * applications would fail to parse the new key in the cache keyring. */
+    if (!is_legacy_cache_name(data->name) &&
+        (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+        kret = krb5_krcc_save_time_offsets(context, id, os_ctx->time_offset,
+                                           os_ctx->usec_offset);
+    }
+
     if (kret == KRB5_OK)
         krb5_change_cache();
 
@@ -1039,6 +1067,7 @@ make_cache(key_serial_t collection_id, key_serial_t cache_id,
 static krb5_error_code KRB5_CALLCONV
 krb5_krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
 {
+    krb5_os_context os_ctx = &context->os_context;
     krb5_error_code ret;
     key_serial_t collection_id, cache_id;
     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
@@ -1067,6 +1096,19 @@ krb5_krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
 
     ret = make_cache(collection_id, cache_id, anchor_name, collection_name,
                      subsidiary_name, id);
+    if (ret)
+        goto cleanup;
+
+    /* Lookup time offsets if necessary. */
+    if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
+        !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+        if (krb5_krcc_get_time_offsets(context, *id,
+                                       &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;
+        }
+    }
 
 cleanup:
     free(anchor_name);
@@ -1122,6 +1164,8 @@ krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id,
     }
 
     krcursor->princ_id = d->princ_id;
+    krcursor->offsets_id = keyctl_search(d->cache_id, KRCC_KEY_TYPE_USER,
+                                         KRCC_TIME_OFFSETS, 0);
     krcursor->numkeys = size / sizeof(key_serial_t);
     krcursor->keys = keys;
 
@@ -1174,8 +1218,10 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
     if (krcursor->currkey >= krcursor->numkeys)
         return KRB5_CC_END;
 
-    /* If we're pointing at the entry with the principal, skip it */
-    if (krcursor->keys[krcursor->currkey] == krcursor->princ_id) {
+    /* If we're pointing at the entry with the principal, or at the key
+     * with the time offsets, skip it. */
+    while (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
+           krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
         krcursor->currkey++;
         /* Check if we have now reached the end */
         if (krcursor->currkey >= krcursor->numkeys)
@@ -1621,6 +1667,84 @@ errout:
     return kret;
 }
 
+static  krb5_error_code
+krb5_krcc_save_time_offsets(krb5_context context, krb5_ccache id,
+                            krb5_int32 time_offset, krb5_int32 usec_offset)
+{
+    krb5_krcc_data *d = (krb5_krcc_data *)id->data;
+    krb5_error_code kret;
+    key_serial_t newkey;
+    void *payload = NULL;
+    int psize;
+
+    k5_cc_mutex_assert_locked(context, &d->lock);
+
+    /* Prepare the payload. */
+    kret = krb5_krcc_unparse_offsets(context, time_offset, usec_offset,
+                                     &payload, &psize);
+    CHECK_N_GO(kret, errout);
+
+    /* Add new key into keyring. */
+    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, psize,
+                     d->cache_id);
+    if (newkey == -1) {
+        kret = errno;
+        DEBUG_PRINT(("Error adding time offsets key: %s\n", strerror(kret)));
+    } else {
+        kret = KRB5_OK;
+        krb5_krcc_update_change_time(d);
+    }
+
+errout:
+    free(payload);
+    return kret;
+}
+
+static krb5_error_code
+krb5_krcc_get_time_offsets(krb5_context context, krb5_ccache id,
+                           krb5_int32 *time_offset, krb5_int32 *usec_offset)
+{
+    krb5_krcc_data *d = (krb5_krcc_data *)id->data;
+    krb5_error_code kret;
+    key_serial_t key;
+    krb5_int32 t, u;
+    void *payload = NULL;
+    int psize;
+
+    k5_cc_mutex_lock(context, &d->lock);
+
+    if (!d->cache_id) {
+        kret = KRB5_FCC_NOFILE;
+        goto errout;
+    }
+
+    key = keyctl_search(d->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, 0);
+    if (key == -1) {
+        kret = ENOENT;
+        goto errout;
+    }
+
+    psize = keyctl_read_alloc(key, &payload);
+    if (psize == -1) {
+        DEBUG_PRINT(("Reading time offsets key %d: %s\n",
+                     key, strerror(errno)));
+        kret = KRB5_CC_IO;
+        goto errout;
+    }
+
+    kret = krb5_krcc_parse_offsets(context, &t, &u, payload, psize);
+    if (kret)
+        goto errout;
+
+    *time_offset = t;
+    *usec_offset = u;
+
+errout:
+    free(payload);
+    k5_cc_mutex_unlock(context, &d->lock);
+    return kret;
+}
+
 struct krcc_ptcursor_data {
     key_serial_t collection_id;
     char *anchor_name;
@@ -2656,6 +2780,83 @@ errout:
     return kret;
 }
 
+static krb5_error_code
+krb5_krcc_parse_offsets(krb5_context context, krb5_int32 *time_offset,
+                        krb5_int32 *usec_offset, void *payload, int psize)
+{
+    krb5_error_code kret;
+    krb5_krcc_bc bc;
+
+    bc.bpp = payload;
+    bc.endp = bc.bpp + psize;
+
+    kret = krb5_krcc_parse_int32(context, time_offset, &bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_parse_int32(context, usec_offset, &bc);
+    CHECK_OUT(kret);
+
+    return KRB5_OK;
+}
+
+static krb5_error_code
+krb5_krcc_unparse_offsets_internal(krb5_context context,
+                                   krb5_int32 time_offset,
+                                   krb5_int32 usec_offset,
+                                   krb5_krcc_bc *bc)
+{
+    krb5_error_code kret;
+
+    kret = krb5_krcc_unparse_int32(context, time_offset, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_int32(context, usec_offset, bc);
+    CHECK_OUT(kret);
+
+    return KRB5_OK;
+}
+
+static krb5_error_code
+krb5_krcc_unparse_offsets(krb5_context context, krb5_int32 time_offset,
+                          krb5_int32 usec_offset, void **datapp, int *lenptr)
+{
+    krb5_error_code kret;
+    krb5_krcc_bc bc;
+    char *buf;
+
+    if (!datapp || !lenptr)
+        return EINVAL;
+
+    *datapp = NULL;
+    *lenptr = 0;
+
+    /* Do a dry run first to calculate the size. */
+    bc.bpp = bc.endp = NULL;
+    bc.size = 0;
+    kret = krb5_krcc_unparse_offsets_internal(context, time_offset,
+                                              usec_offset, &bc);
+    CHECK_OUT(kret);
+
+    buf = malloc(bc.size);
+    if (buf == NULL)
+        return ENOMEM;
+
+    bc.bpp = buf;
+    bc.endp = buf + bc.size;
+    kret = krb5_krcc_unparse_offsets_internal(context, time_offset,
+                                              usec_offset, &bc);
+    CHECK(kret);
+
+    /* Success! */
+    *datapp = buf;
+    *lenptr = bc.bpp - buf;
+    kret = KRB5_OK;
+
+errout:
+    if (kret)
+        free(buf);
+    return kret;
+}
 /*
  * Utility routine: called by krb5_krcc_* functions to keep
  * result of krb5_krcc_last_change_time up to date.