auth: Rewrite KeyFile handling code
[openafs.git] / src / auth / keys.c
index df8a618..e1440da 100644 (file)
@@ -1,18 +1,37 @@
 /*
- * Copyright 2000, International Business Machines Corporation and others.
- * All Rights Reserved.
+ * Copyright (c) 2010 Your File System Inc. All rights reserved.
  *
- * This software has been released under the terms of the IBM Public
- * License.  For details, see the LICENSE file in the top-level source
- * directory or online at http://www.openafs.org/dl/license10.html
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+
 #include <afsconfig.h>
 #include <afs/param.h>
 
 #include <roken.h>
 
+#include <opr/queue.h>
+
 /* Need rx/rx.h to get working assert(), used by LOCK_GLOBAL_MUTEX */
 #include <rx/rx.h>
+#include <rx/rx_atomic.h>
 
 #include <afs/stds.h>
 #include <afs/pthread_glock.h>
 #include "keys.h"
 #include "internal.h"
 
-/* called during opening of config file */
-int
-_afsconf_IntGetKeys(struct afsconf_dir *adir)
+struct afsconf_typedKey {
+    rx_atomic_t refcnt;
+    afsconf_keyType type;
+    int kvno;
+    int subType;
+    struct rx_opaque key;
+};
+
+static struct afsconf_typedKey *afsconf_typedKey_blank(void);
+
+/* Memory storage for keyfile contents. */
+
+struct keyTypeList {
+    struct opr_queue link;
+    afsconf_keyType type;
+    struct opr_queue kvnoList;
+};
+
+struct kvnoList {
+    struct opr_queue link;
+    int kvno;
+    struct opr_queue subTypeList;
+};
+
+struct subTypeList {
+    struct opr_queue link;
+    int subType;
+    struct afsconf_typedKey *key;
+};
+
+static int
+listToArray(struct kvnoList *kvnoEntry, struct afsconf_typedKeyList **keys)
 {
-    char tbuffer[256];
-    int fd;
-    struct afsconf_keys *tstr;
-    afs_int32 code;
+    struct afsconf_typedKeyList *retval;
+    struct opr_queue *cursor;
+    int i;
 
-#ifdef AFS_NT40_ENV
-    /* NT client config dir has no KeyFile; don't risk attempting open
-     * because there might be a random file of this name if dir is shared.
-     */
-    if (_afsconf_IsClientConfigDirectory(adir->name)) {
-       adir->keystr = ((struct afsconf_keys *)
-                       malloc(sizeof(struct afsconf_keys)));
-       adir->keystr->nkeys = 0;
-       return 0;
+    /* Allocate space for the keys we've got stored */
+    retval = malloc(sizeof(struct afsconf_typedKeyList));
+    retval->nkeys = opr_queue_Count(&kvnoEntry->subTypeList);
+    retval->keys  = calloc(retval->nkeys, sizeof(struct afsconf_typedKey *));
+
+    i = 0;
+    for(opr_queue_Scan(&kvnoEntry->subTypeList, cursor)) {
+       struct subTypeList *entry;
+
+       entry = opr_queue_Entry(cursor, struct subTypeList, link);
+       retval->keys[i] = afsconf_typedKey_get(entry->key);
+       i++;
     }
-#endif /* AFS_NT40_ENV */
 
-    LOCK_GLOBAL_MUTEX;
-    /* compute the key name and other setup */
-    strcompose(tbuffer, 256, adir->name, "/", AFSDIR_KEY_FILE, NULL);
-    tstr = (struct afsconf_keys *)malloc(sizeof(struct afsconf_keys));
-    adir->keystr = tstr;
-
-    /* read key file */
-    fd = open(tbuffer, O_RDONLY);
-    if (fd < 0) {
-       tstr->nkeys = 0;
-       UNLOCK_GLOBAL_MUTEX;
+    *keys = retval;
+    return 0;
+}
+
+static struct keyTypeList *
+findByType(struct afsconf_dir *dir, afsconf_keyType type)
+{
+    struct opr_queue *cursor;
+    struct keyTypeList *entry = NULL;
+
+    for (opr_queue_Scan(&dir->keyList, cursor)) {
+       entry = opr_queue_Entry(cursor, struct keyTypeList, link);
+       if (entry->type >= type)
+           break;
+    }
+    if (entry == NULL || entry->type != type)
+       return NULL;
+
+    return entry;
+}
+
+static struct kvnoList *
+findInTypeList(struct keyTypeList *parent, int kvno)
+{
+    struct opr_queue *cursor;
+    struct kvnoList *entry = NULL;
+
+    for (opr_queue_Scan(&parent->kvnoList, cursor)) {
+       entry = opr_queue_Entry(cursor, struct kvnoList, link);
+       if (entry->kvno >= kvno)
+           break;
+    }
+    if (entry == NULL || entry->kvno != kvno)
+       return NULL;
+
+    return entry;
+}
+
+static struct kvnoList *
+findByKvno(struct afsconf_dir *dir, afsconf_keyType type, int kvno)
+{
+    struct keyTypeList *entry;
+    entry = findByType(dir, type);
+
+    if (entry == NULL)
+       return NULL;
+
+    return findInTypeList(entry, kvno);
+}
+
+static struct subTypeList *
+findInKvnoList(struct kvnoList *parent, int subType)
+{
+    struct opr_queue *cursor;
+    struct subTypeList *entry = NULL;
+
+    for (opr_queue_Scan(&parent->subTypeList, cursor)) {
+       entry = opr_queue_Entry(cursor, struct subTypeList, link);
+       if (entry->subType >= subType)
+            break;
+    }
+    if (entry == NULL || entry->subType != subType)
+       return NULL;
+
+    return entry;
+}
+
+static struct subTypeList *
+findBySubType(struct afsconf_dir *dir, afsconf_keyType type, int kvno,
+             int subType)
+{
+   struct kvnoList *entry;
+
+   entry = findByKvno(dir, type, kvno);
+   if (entry == NULL)
+       return NULL;
+
+   return findInKvnoList(entry, subType);
+}
+
+
+/* Add key. */
+static int
+addMemoryKey(struct afsconf_dir *dir, struct afsconf_typedKey *key,
+            int overwrite)
+{
+    struct opr_queue   *cursor;
+    struct keyTypeList *typeEntry = NULL;
+    struct kvnoList    *kvnoEntry = NULL;
+    struct subTypeList *subType   = NULL;
+
+    /* Find the place in the keyType list to insert the key into */
+    for (opr_queue_Scan(&dir->keyList, cursor)) {
+       typeEntry = opr_queue_Entry(cursor, struct keyTypeList, link);
+       if (typeEntry->type >= key->type)
+           break;
+    }
+
+    if (typeEntry == NULL || typeEntry->type != key->type) {
+       struct keyTypeList *list;
+
+       list = malloc(sizeof(struct keyTypeList));
+       opr_queue_Init(&list->kvnoList);
+       list->type = key->type;
+       opr_queue_InsertBefore(cursor, &list->link);
+       typeEntry = list;
+    }
+
+    /* And the place in the kvno list */
+    for (opr_queue_Scan(&typeEntry->kvnoList, cursor)) {
+       kvnoEntry = opr_queue_Entry(cursor, struct kvnoList, link);
+       if (kvnoEntry->kvno >= key->kvno)
+           break;
+    }
+
+    if (kvnoEntry == NULL || kvnoEntry->kvno != key->kvno) {
+       struct kvnoList *list;
+
+       /* In the legacy rxkad key case, we need to check to see if we've
+        * gone over the maximum of 8 keys */
+       if (key->type == afsconf_rxkad &&
+           opr_queue_Count(&typeEntry->kvnoList)>=8)
+           return AFSCONF_FULL;
+
+       list = malloc(sizeof(struct kvnoList));
+       opr_queue_Init(&list->subTypeList);
+       list->kvno = key->kvno;
+       opr_queue_InsertBefore(cursor, &list->link);
+       kvnoEntry = list;
+    }
+
+    /* And the place in the subtype list */
+    for (opr_queue_Scan(&kvnoEntry->subTypeList, cursor)) {
+       subType = opr_queue_Entry(cursor, struct subTypeList, link);
+       if (subType->subType >= key->subType)
+           break;
+    }
+
+    if (subType == NULL || subType->subType != key->subType) {
+       struct subTypeList *list;
+
+       list = malloc(sizeof(struct subTypeList));
+       list->subType = key->subType;
+       list->key = afsconf_typedKey_get(key);
+       opr_queue_InsertBefore(cursor, &list->link);
+    } else {
+       if (overwrite) {
+           /* Give up our reference to the existing key */
+           afsconf_typedKey_put(&subType->key);
+           subType->key = afsconf_typedKey_get(key);
+       } else {
+           return AFSCONF_KEYINUSE;
+       }
+    }
+    return 0;
+}
+
+static void
+deleteKvnoEntry(struct kvnoList *entry)
+{
+    struct subTypeList *subTypeEntry;
+
+    while (!opr_queue_IsEmpty(&entry->subTypeList)) {
+       subTypeEntry = opr_queue_First(&entry->subTypeList,
+                                      struct subTypeList, link);
+       afsconf_typedKey_put(&subTypeEntry->key);
+       opr_queue_Remove(&subTypeEntry->link);
+       free(subTypeEntry);
+    }
+    opr_queue_Remove(&entry->link);
+    free(entry);
+}
+
+void
+_afsconf_FreeAllKeys(struct afsconf_dir *dir)
+{
+    struct keyTypeList *typeEntry;
+    struct kvnoList *kvnoEntry;
+
+    while (!opr_queue_IsEmpty(&dir->keyList)) {
+       typeEntry = opr_queue_First(&dir->keyList, struct keyTypeList, link);
+
+       while (!opr_queue_IsEmpty(&typeEntry->kvnoList)) {
+           kvnoEntry = opr_queue_First(&typeEntry->kvnoList,
+                                       struct kvnoList, link);
+
+           deleteKvnoEntry(kvnoEntry);
+       }
+       opr_queue_Remove(&typeEntry->link);
+       free(typeEntry);
+    }
+}
+void
+_afsconf_InitKeys(struct afsconf_dir *dir)
+{
+    opr_queue_Init(&dir->keyList);
+}
+
+/* Disk based key storage. This is made somewhat complicated because we
+ * store keys in more than one place - keys of type 'rxkad' (0) are stored
+ * in the original KeyFile, so that we can continue to be compatible with
+ * utilities that directly modify that file.
+ *
+ * All other keys are stored in the file KeyFileEx, which has the following
+ * format:
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                        version number                           |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                        number of keys                           |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   | Key data ...
+ *   +-+-+-+-+-+-+-+
+ *
+ * The version number is 1 at present. Version numbers higher than 1
+ * indicate a keyfile that is not backwards compatible with this
+ * specification.
+ *
+ * Key data is a sequence of the following records (note that these are
+ * not word aligned - the next record begins where the previous one ends)
+ *
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                         meta-data length                        |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                           key type                              |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                       key version number                        |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                         key sub type                            |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                      length of key material                     |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   | Key material
+ *   +-+-+-+-+-+-+-+
+ *
+ * All values are expressed in network byte order
+ *
+ * Meta data length is the length of the initial portion of the record
+ * (itself, key type, key version number, and key sub type). In this
+ * version of the specification it would be 16. It is there to allow
+ * additional fields to be added to this specification at a later date
+ * without breaking backwards compatibility.
+ */
+
+/* XXX - We need to be careful with failure here, because failure due to
+ * a missing file is fine, but failure due to read errors needs to be trapped,
+ * before it results in a corrupted file being written out.
+ */
+
+static int
+_parseOriginalKeyFile(struct afsconf_dir *dir, char *fileName)
+{
+    int fd, code, nkeys, i;
+    struct afsconf_typedKey *key;
+
+    fd = open(fileName, O_RDONLY);
+    if (fd < 0)
        return 0;
+
+    code = read(fd, &nkeys, sizeof(afs_int32));
+    if (code!= sizeof(afs_int32))
+       goto fail;
+
+    nkeys=ntohl(nkeys);
+    for(i=0; i<nkeys; i++) {
+
+       key = afsconf_typedKey_blank();
+
+       key->type = afsconf_rxkad;
+       key->subType = 0;
+
+       code = read(fd, &key->kvno, sizeof(afs_int32));
+       if (code != sizeof(afs_int32)) {
+           free(key);
+           goto fail;
+       }
+       key->kvno = ntohl(key->kvno);
+
+       rx_opaque_alloc(&key->key, 8);
+       code = read(fd, key->key.val, 8);
+       if (code != 8) {
+           rx_opaque_freeContents(&key->key);
+           free(key);
+           goto fail;
+       }
+       code = addMemoryKey(dir, key, 1);
+       afsconf_typedKey_put(&key); /* Done with key */
+       if (code)
+           goto fail;
     }
-    code = read(fd, tstr, sizeof(struct afsconf_keys));
     close(fd);
-    if (code < sizeof(afs_int32)) {
-       tstr->nkeys = 0;
-       UNLOCK_GLOBAL_MUTEX;
-       return 0;
+    return 0;
+
+fail:
+    close(fd);
+    return EIO;
+}
+
+static_inline int
+writeWord(int fd, afs_int32 data)
+{
+
+     data = htonl(data);
+
+     if (write(fd, &data, sizeof(afs_int32)) != sizeof(afs_int32))
+       return EIO;
+
+     return 0;
+}
+
+static int
+_writeOriginalKeyFile(struct afsconf_dir *dir, char *fileName)
+{
+    int nkeys, fd;
+    struct opr_queue *cursor;
+    struct keyTypeList *typeEntry;
+
+    fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600);
+    if (fd < 0)
+       return AFSCONF_FAILURE;
+
+    typeEntry = findByType(dir, afsconf_rxkad);
+    if (typeEntry == NULL)
+       goto out;
+
+    nkeys = opr_queue_Count(&typeEntry->kvnoList);
+
+    if (writeWord(fd, nkeys))
+       goto fail;
+
+    for (opr_queue_Scan(&typeEntry->kvnoList, cursor)) {
+       struct kvnoList *kvnoEntry;
+        struct subTypeList *subEntry;
+
+       kvnoEntry = opr_queue_Entry(cursor, struct kvnoList, link);
+        subEntry = opr_queue_First(&kvnoEntry->subTypeList,
+                                  struct subTypeList, link);
+       if (writeWord(fd, subEntry->key->kvno))
+           goto fail;
+       if (write(fd, subEntry->key->key.val, 8) != 8)
+           goto fail;
     }
 
-    /* convert key structure to host order */
-    tstr->nkeys = ntohl(tstr->nkeys);
+out:
+    close(fd);
+    return 0;
+
+fail:
+    close(fd);
+    return AFSCONF_FAILURE;
+}
+
+static int
+_parseExtendedKeyFile(struct afsconf_dir *dir, char *fileName)
+{
+    int fd, i, code;
+    afs_int32 nkeys;
+    struct afsconf_typedKey *key;
 
-    if (code < sizeof(afs_int32) + (tstr->nkeys*sizeof(struct afsconf_key))) {
-       tstr->nkeys = 0;
-       UNLOCK_GLOBAL_MUTEX;
+    fd = open(fileName, O_RDONLY);
+    if (fd < 0)
        return 0;
+
+    code = read(fd, &nkeys, sizeof(afs_int32));
+    if (code!= sizeof(afs_int32))
+       goto fail;
+
+    nkeys=ntohl(nkeys);
+    for(i=0; i<nkeys; i++) {
+       afs_int32 reclen;
+
+       key = afsconf_typedKey_blank();
+
+       /* The only data version we currently parse has a reclen of 16.
+        * Anything smaller indicates a corrupt key file. Anything more,
+        * and we just skip the extra fields */
+       code = read(fd, &reclen, sizeof(afs_int32));
+       if (code != sizeof(afs_int32))
+           goto fail;
+       reclen = ntohl(reclen);
+       if (reclen < 16)
+           goto fail;
+       reclen-=sizeof(afs_int32);
+
+       code = read(fd, &key->type, sizeof(afs_int32));
+       if (code != sizeof(afs_int32))
+           goto fail;
+       key->type = ntohl(key->type);
+       reclen-=sizeof(afs_int32);
+
+       code = read(fd, &key->kvno, sizeof(afs_int32));
+       if (code != sizeof(afs_int32))
+           goto fail;
+       key->kvno = ntohl(key->kvno);
+       reclen-=sizeof(afs_int32);
+
+       code = read(fd, &key->subType, sizeof(afs_int32));
+       if (code != sizeof(afs_int32))
+           goto fail;
+       key->subType = ntohl(key->subType);
+       reclen-=sizeof(afs_int32);
+
+       if (reclen > 0) {
+           code = lseek(fd, reclen, SEEK_CUR);
+           if (code < 0)
+               goto fail;
+       }
+
+       code = read(fd, &reclen, sizeof(afs_int32));
+       if (code != sizeof(afs_int32))
+           goto fail;
+       reclen = ntohl(reclen);
+
+       rx_opaque_alloc(&key->key, reclen);
+       code = read(fd, key->key.val, reclen);
+       if (code != reclen) {
+           rx_opaque_freeContents(&key->key);
+           free(key);
+           goto fail;
+       }
+       code = addMemoryKey(dir, key, 1);
+       afsconf_typedKey_put(&key);
+       if (code)
+           goto fail;
     }
+    close(fd);
+    return 0;
 
-    for (fd = 0; fd < tstr->nkeys; fd++)
-       tstr->key[fd].kvno = ntohl(tstr->key[fd].kvno);
+fail:
+    close(fd);
+    return EIO;
+}
+
+
+static int
+_writeExtendedKeyFile(struct afsconf_dir *dir, char *fileName)
+{
+    int nkeys;
+    int fd;
+
+    struct keyTypeList *typeEntry;
+    struct kvnoList    *kvnoEntry;
+    struct subTypeList *entry;
+    struct opr_queue   *keyCursor;
+    struct opr_queue   *kvnoCursor;
+    struct opr_queue   *subCursor;
+
+    fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600);
+    if (fd < 0)
+       return AFSCONF_FAILURE;
+
+    /* Iterate over the whole in-memory key store, and write everything
+     * except keys with type rxkad into the extended key file */
+
+    /* Write a 0 key count - we'll fill it in later */
+    nkeys = 0;
+    if (writeWord(fd, 0))
+       goto fail;
+
+    for (opr_queue_Scan(&dir->keyList, keyCursor)) {
+       typeEntry = opr_queue_Entry(keyCursor, struct keyTypeList, link);
+
+       if (typeEntry->type != afsconf_rxkad) {
+           for (opr_queue_Scan(&typeEntry->kvnoList, kvnoCursor)) {
+               kvnoEntry = opr_queue_Entry(kvnoCursor, struct kvnoList, link);
+               for (opr_queue_Scan(&kvnoEntry->subTypeList, subCursor)) {
+                   entry = opr_queue_Entry(subCursor, struct subTypeList, link);
+                   if (writeWord(fd, 16)) /* record length */
+                       goto fail;
+                   if (writeWord(fd, entry->key->type))
+                       goto fail;
+                   if (writeWord(fd, entry->key->kvno))
+                       goto fail;
+                   if (writeWord(fd, entry->key->subType))
+                       goto fail;
+                   if (writeWord(fd, entry->key->key.len))
+                       goto fail;
+                   if (write(fd, entry->key->key.val,
+                             entry->key->key.len) !=
+                       entry->key->key.len)
+                       goto fail;
+                   nkeys++;
+               }
+           }
+       }
+    }
+
+    if (lseek(fd, 0, SEEK_SET)<0)
+       goto fail;
+
+    if (writeWord(fd, nkeys))
+       goto fail;
+
+    close(fd);
 
-    UNLOCK_GLOBAL_MUTEX;
     return 0;
+
+fail:
+    close(fd);
+    return AFSCONF_FAILURE;
 }
 
+int
+_afsconf_LoadKeys(struct afsconf_dir *dir)
+{
+    int code;
+    char *fileName;
+
+    /* If we're running on Windows, and we are a client, we don't have a
+     * KeyFile, so don't try and open one */
+
+#ifdef AFS_NT40_ENV
+    if (_afsconf_IsClientConfigDirectory(dir->name))
+       return 0;
+#endif /* AFS_NT40_ENV */
+
+    LOCK_GLOBAL_MUTEX;
+
+    /* Delete all of our existing keys */
+    _afsconf_FreeAllKeys(dir);
+
+    /* Start by opening the original KeyFile */
+    asnprintf(&fileName, 256, "%s/%s", dir->name, AFSDIR_KEY_FILE);
+    code = _parseOriginalKeyFile(dir, fileName);
+    free(fileName);
+    if (code)
+       goto out;
+
+    /* Now open the new style KeyFile */
+    asnprintf(&fileName, 256, "%s/%s", dir->name, AFSDIR_EXT_KEY_FILE);
+    code = _parseExtendedKeyFile(dir, fileName);
+    free(fileName);
+    if (code)
+       goto out;
+
+out:
+    if (code)
+       _afsconf_FreeAllKeys(dir);
+
+    UNLOCK_GLOBAL_MUTEX;
+
+    return code;
+}
+
+static int
+_afsconf_SaveKeys(struct afsconf_dir *dir)
+{
+    char *fileName;
+    int code;
+
+    /* If we're running on Windows, and we are a client, we don't have a
+     * KeyFile, so don't try and open one */
+
+#ifdef AFS_NT40_ENV
+    if (_afsconf_IsClientConfigDirectory(dir->name))
+       return 0;
+#endif /* AFS_NT40_ENV */
+
+    LOCK_GLOBAL_MUTEX;
+
+    /* Start by opening the original KeyFile */
+    asnprintf(&fileName, 256, "%s/%s", dir->name, AFSDIR_KEY_FILE);
+    code = _writeOriginalKeyFile(dir, fileName);
+    free(fileName);
+    if (code)
+       goto out;
+
+    /* Now open the new style KeyFile */
+    asnprintf(&fileName, 256, "%s/%s", dir->name, AFSDIR_EXT_KEY_FILE);
+    code = _writeExtendedKeyFile(dir, fileName);
+    free(fileName);
+    if (code)
+       goto out;
+
+out:
+    UNLOCK_GLOBAL_MUTEX;
+
+    return code;
+}
+
+
+
 /* get keys structure */
 int
-afsconf_GetKeys(struct afsconf_dir *adir, struct afsconf_keys *astr)
+afsconf_GetKeys(struct afsconf_dir *dir, struct afsconf_keys *astr)
 {
     afs_int32 code;
+    struct keyTypeList *typeEntry;
+    struct opr_queue *cursor;
+
+    memset(astr, 0, sizeof(struct afsconf_keys));
 
     LOCK_GLOBAL_MUTEX;
-    code = _afsconf_Check(adir);
-    if (code) {
-       UNLOCK_GLOBAL_MUTEX;
-       return AFSCONF_FAILURE;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    typeEntry = findByType(dir, afsconf_rxkad);
+    if (typeEntry == NULL)
+       goto out;
+
+    for (opr_queue_Scan(&typeEntry->kvnoList, cursor)) {
+       struct kvnoList *kvnoEntry;
+        struct subTypeList *subEntry;
+
+       kvnoEntry = opr_queue_Entry(cursor, struct kvnoList, link);
+        subEntry = opr_queue_First(&kvnoEntry->subTypeList,
+                                  struct subTypeList, link);
+       /* XXX - If there is more than one key in this list, it's an error */
+       astr->key[astr->nkeys].kvno = subEntry->key->kvno;
+       /* XXX - If the opaque contains a number of bytes other than 8, it's
+        * an error */
+       memcpy(&astr->key[astr->nkeys].key, subEntry->key->key.val, 8);
+       astr->nkeys++;
     }
-    memcpy(astr, adir->keystr, sizeof(struct afsconf_keys));
+
+out:
     UNLOCK_GLOBAL_MUTEX;
-    return 0;
+    return code;
 }
 
-/* get latest key */
 afs_int32
-afsconf_GetLatestKey(struct afsconf_dir * adir, afs_int32 * avno,
-                    struct ktc_encryptionKey *akey)
+afsconf_GetLatestKey(struct afsconf_dir *dir, afs_int32 *kvno,
+                    struct ktc_encryptionKey *key)
 {
-    int i;
-    int maxa;
-    struct afsconf_key *tk;
-    afs_int32 best;
-    struct afsconf_key *bestk;
-    afs_int32 code;
+    struct afsconf_typedKey *typedKey;
+    int code;
 
-    LOCK_GLOBAL_MUTEX;
-    code = _afsconf_Check(adir);
-    if (code) {
-       UNLOCK_GLOBAL_MUTEX;
+    code = afsconf_GetLatestKeyByTypes(dir, afsconf_rxkad, 0, &typedKey);
+    if (code)
+       return code;
+
+    /* XXX - Should check that the key is of the correct length */
+
+    /* Copy out the relevant details */
+    *kvno = typedKey->kvno;
+    memcpy(key, typedKey->key.val, 8);
+
+    afsconf_typedKey_put(&typedKey);
+
+    return 0;
+}
+
+int
+afsconf_GetKey(void *rock, int kvno, struct ktc_encryptionKey *key)
+{
+    struct afsconf_typedKey *typedKey;
+    int code;
+
+    code = afsconf_GetKeyByTypes(rock, afsconf_rxkad, kvno, 0, &typedKey);
+    if (code)
+       return code;
+
+    memcpy(key, typedKey->key.val, 8);
+
+    afsconf_typedKey_put(&typedKey);
+
+    return 0;
+}
+
+int
+afsconf_AddKey(struct afsconf_dir *dir, afs_int32 kvno, char key[8],
+              afs_int32 overwrite)
+{
+    struct rx_opaque buffer;
+    struct afsconf_typedKey *typedKey;
+    int code;
+
+    rx_opaque_alloc(&buffer, 8);
+    memcpy(buffer.val, key, 8);
+    typedKey = afsconf_typedKey_new(afsconf_rxkad, kvno, 0, &buffer);
+    if (typedKey == NULL)
        return AFSCONF_FAILURE;
+
+    rx_opaque_freeContents(&buffer);
+
+    code = afsconf_AddTypedKey(dir, typedKey, overwrite);
+    afsconf_typedKey_put(&typedKey);
+    return code;
+}
+
+int
+afsconf_DeleteKey(struct afsconf_dir *dir, afs_int32 kvno)
+{
+    return afsconf_DeleteKeyByType(dir, afsconf_rxkad, kvno);
+}
+
+int
+afsconf_GetKeysByType(struct afsconf_dir *dir, afsconf_keyType type,
+                     int kvno, struct afsconf_typedKeyList **keys)
+{
+    struct kvnoList *kvnoEntry;
+    int code;
+
+    LOCK_GLOBAL_MUTEX;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    kvnoEntry = findByKvno(dir, type, kvno);
+    if (kvnoEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+        goto out;
     }
-    maxa = adir->keystr->nkeys;
-
-    best = -1;                 /* highest kvno we've seen yet */
-    bestk = (struct afsconf_key *)0;   /* ptr to structure providing best */
-    for (tk = adir->keystr->key, i = 0; i < maxa; i++, tk++) {
-       if (tk->kvno == 999)
-           continue;           /* skip bcrypt keys */
-       if (tk->kvno > best) {
-           best = tk->kvno;
-           bestk = tk;
-       }
+
+    code = listToArray(kvnoEntry, keys);
+
+out:
+    UNLOCK_GLOBAL_MUTEX;
+    return code;
+}
+
+int
+afsconf_GetKeyByTypes(struct afsconf_dir *dir, afsconf_keyType type,
+                     int kvno, int subType, struct afsconf_typedKey **key)
+{
+    int code = 0;
+    struct subTypeList *subTypeEntry;
+
+    LOCK_GLOBAL_MUTEX;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    subTypeEntry = findBySubType(dir, type, kvno, subType);
+    if (subTypeEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+       goto out;
     }
-    if (bestk) {               /* found any  */
-       if (akey)
-           memcpy(akey, bestk->key, 8);        /* copy out latest key */
-       if (avno)
-           *avno = bestk->kvno;        /* and kvno to caller */
-       UNLOCK_GLOBAL_MUTEX;
-       return 0;
+
+    *key = afsconf_typedKey_get(subTypeEntry->key);
+
+out:
+    UNLOCK_GLOBAL_MUTEX;
+    return code;
+}
+
+static struct kvnoList *
+pickBestKvno(struct afsconf_dir *dir, afsconf_keyType type)
+{
+    struct keyTypeList *typeEntry;
+    struct kvnoList    *kvnoEntry;
+
+    typeEntry = findByType(dir, type);
+    if (typeEntry == NULL)
+       return NULL;
+
+    /* We store all of the key lists ordered, so the last entry in the
+     * kvno list must be the highest kvno. */
+
+    kvnoEntry = opr_queue_Last(&typeEntry->kvnoList, struct kvnoList, link);
+
+    /* Except, if we're in the rxkad list, we might have a bcrypt entry that
+     * has a kvno of 999. So we need to skip that one
+     */
+    while (type == afsconf_rxgk && kvnoEntry->kvno == 999) {
+       kvnoEntry = opr_queue_Prev(&typeEntry->kvnoList, struct kvnoList,
+                                  link);
+       if (opr_queue_IsEnd(&typeEntry->kvnoList, &kvnoEntry->link))
+          return NULL;
+    }
+
+    return kvnoEntry;
+}
+
+
+int
+afsconf_GetLatestKeysByType(struct afsconf_dir *dir, afsconf_keyType type,
+                           struct afsconf_typedKeyList **keys)
+{
+    int code;
+    struct kvnoList *kvnoEntry;
+
+    LOCK_GLOBAL_MUTEX;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+
+    kvnoEntry = pickBestKvno(dir, type);
+    if (kvnoEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+       goto out;
     }
+
+    code = listToArray(kvnoEntry, keys);
+
+out:
     UNLOCK_GLOBAL_MUTEX;
-    return AFSCONF_NOTFOUND;   /* didn't find any keys */
+    return code;
 }
 
-/* get a particular key */
 int
-afsconf_GetKey(void *rock, int avno, struct ktc_encryptionKey *akey)
+afsconf_GetLatestKeyByTypes(struct afsconf_dir *dir, afsconf_keyType type,
+                           int subType, struct afsconf_typedKey **key)
 {
-    struct afsconf_dir *adir = (struct afsconf_dir *) rock;
-    int i, maxa;
-    struct afsconf_key *tk;
-    afs_int32 code;
+    int code;
+    struct kvnoList *kvnoEntry;
+    struct subTypeList *subTypeEntry;
 
     LOCK_GLOBAL_MUTEX;
-    code = _afsconf_Check(adir);
-    if (code) {
-       UNLOCK_GLOBAL_MUTEX;
-       return AFSCONF_FAILURE;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    kvnoEntry = pickBestKvno(dir, type);
+    if (kvnoEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+       goto out;
     }
-    maxa = adir->keystr->nkeys;
 
-    for (tk = adir->keystr->key, i = 0; i < maxa; i++, tk++) {
-       if (tk->kvno == avno) {
-           memcpy(akey, tk->key, 8);
-           UNLOCK_GLOBAL_MUTEX;
-           return 0;
-       }
+    subTypeEntry = findInKvnoList(kvnoEntry, subType);
+    if (subTypeEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+       goto out;
     }
 
+    *key = afsconf_typedKey_get(subTypeEntry->key);
+
+out:
     UNLOCK_GLOBAL_MUTEX;
-    return AFSCONF_NOTFOUND;
+    return code;
 }
 
-/* save the key structure in the appropriate file */
-static int
-SaveKeys(struct afsconf_dir *adir)
+void
+afsconf_PutTypedKeyList(struct afsconf_typedKeyList **keys)
 {
-    struct afsconf_keys tkeys;
-    int fd;
-    afs_int32 i;
-    char tbuffer[256];
+     int i;
 
-    memcpy(&tkeys, adir->keystr, sizeof(struct afsconf_keys));
+     for (i=0;i<(*keys)->nkeys;i++)
+       afsconf_typedKey_put(&((*keys)->keys[i]));
+     free((*keys)->keys);
+     free(*keys);
+     *keys = NULL;
+}
 
-    /* convert it to net byte order */
-    for (i = 0; i < tkeys.nkeys; i++)
-       tkeys.key[i].kvno = htonl(tkeys.key[i].kvno);
-    tkeys.nkeys = htonl(tkeys.nkeys);
+static struct afsconf_typedKey *
+afsconf_typedKey_blank(void)
+{
+    struct afsconf_typedKey *key;
 
-    /* rewrite keys file */
-    strcompose(tbuffer, 256, adir->name, "/", AFSDIR_KEY_FILE, NULL);
-    fd = open(tbuffer, O_RDWR | O_CREAT | O_TRUNC, 0600);
-    if (fd < 0)
-       return AFSCONF_FAILURE;
-    i = write(fd, &tkeys, sizeof(tkeys));
-    if (i != sizeof(tkeys)) {
-       close(fd);
-       return AFSCONF_FAILURE;
+    key = malloc(sizeof(struct afsconf_typedKey));
+    if (key == NULL)
+       return NULL;
+
+    memset(key, 0, sizeof(struct afsconf_typedKey));
+    rx_atomic_set(&key->refcnt, 1);
+
+    return key;
+}
+
+struct afsconf_typedKey *
+afsconf_typedKey_new(afsconf_keyType type, int kvno, int subType,
+                    struct rx_opaque *keyMaterial)
+{
+    struct afsconf_typedKey *key;
+    int code;
+
+    key = afsconf_typedKey_blank();
+    if (key == NULL)
+       return key;
+
+    key->type = type;
+    key->kvno = kvno;
+    key->subType = subType;
+
+    code = rx_opaque_copy(&key->key, keyMaterial);
+    if (code != 0) {
+       free(key);
+       return NULL;
     }
-    if (close(fd) < 0)
-       return AFSCONF_FAILURE;
-    return 0;
+
+    return key;
+}
+
+void
+afsconf_typedKey_free(struct afsconf_typedKey **key)
+{
+    rx_opaque_freeContents(&(*key)->key);
+    free(*key);
+    *key = NULL;
+}
+
+struct afsconf_typedKey *
+afsconf_typedKey_get(struct afsconf_typedKey *key)
+{
+     rx_atomic_inc(&key->refcnt);
+     return key;
+}
+
+void
+afsconf_typedKey_put(struct afsconf_typedKey **key)
+{
+    if (rx_atomic_dec_and_read(&(*key)->refcnt) == 0)
+       afsconf_typedKey_free(key);
+    else
+       *key = NULL;
+}
+
+void
+afsconf_typedKey_values(struct afsconf_typedKey *key, afsconf_keyType *type,
+                       int *kvno, int *subType, struct rx_opaque **material)
+{
+    *type = key->type;
+    *kvno = key->kvno;
+    *subType = key->subType;
+    *material = &key->key;
 }
 
 int
-afsconf_AddKey(struct afsconf_dir *adir, afs_int32 akvno, char akey[8],
-              afs_int32 overwrite)
+afsconf_AddTypedKey(struct afsconf_dir *dir,
+                   struct afsconf_typedKey *key,
+                   int overwrite)
 {
-    struct afsconf_keys *tk;
-    struct afsconf_key *tkey;
-    afs_int32 i;
-    int foundSlot;
+    int code;
 
     LOCK_GLOBAL_MUTEX;
-    tk = adir->keystr;
 
-    if (akvno != 999) {
-       if (akvno < 0 || akvno > 255) {
-           UNLOCK_GLOBAL_MUTEX;
-           return ERANGE;
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    if (key->type == afsconf_rxkad) {
+       /* There are restrictions on rxkad keys so that we can still
+        * return them using the old interface. We only enforce the
+        * same restrictions as that interface does - that is, we don't
+        * check that the key we're passed is a valid DES key */
+       if (key->key.len != 8 || key->subType != 0) {
+           code = AFSCONF_BADKEY;
+           goto out;
        }
     }
-    foundSlot = 0;
-    for (i = 0, tkey = tk->key; i < tk->nkeys; i++, tkey++) {
-       if (tkey->kvno == akvno) {
-           if (!overwrite) {
-               UNLOCK_GLOBAL_MUTEX;
-               return AFSCONF_KEYINUSE;
-           }
-           foundSlot = 1;
-           break;
-       }
+
+    code = addMemoryKey(dir, key, overwrite);
+    if (code)
+       goto out;
+
+    code = _afsconf_SaveKeys(dir);
+    _afsconf_Touch(dir);
+
+out:
+    UNLOCK_GLOBAL_MUTEX;
+    return code;
+}
+
+int
+afsconf_DeleteKeyByType(struct afsconf_dir *dir,
+                       afsconf_keyType type, int kvno)
+{
+    struct keyTypeList *typeEntry;
+    struct kvnoList *kvnoEntry;
+    int code;
+
+    LOCK_GLOBAL_MUTEX;
+
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    typeEntry = findByType(dir, type);
+    if (typeEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+        goto out;
     }
-    if (!foundSlot) {
-       if (tk->nkeys >= AFSCONF_MAXKEYS) {
-           UNLOCK_GLOBAL_MUTEX;
-           return AFSCONF_FULL;
-       }
-       tkey = &tk->key[tk->nkeys++];
+
+    kvnoEntry = findInTypeList(typeEntry, kvno);
+    if (kvnoEntry == NULL) {
+       code = AFSCONF_NOTFOUND;
+       goto out;
+    }
+
+    deleteKvnoEntry(kvnoEntry);
+
+    /* Remove the typeEntry, if it has no sub elements */
+    if (opr_queue_IsEmpty(&typeEntry->kvnoList)) {
+       opr_queue_Remove(&typeEntry->link);
+       free(typeEntry);
     }
-    tkey->kvno = akvno;
-    memcpy(tkey->key, akey, 8);
-    i = SaveKeys(adir);
-    _afsconf_Touch(adir);
+
+    code = _afsconf_SaveKeys(dir);
+    _afsconf_Touch(dir);
+
+out:
     UNLOCK_GLOBAL_MUTEX;
-    return i;
+    return code;
 }
 
-/* this proc works by sliding the other guys down, rather than using a funny
-    kvno value, so that callers can count on getting a good key in key[0].
-*/
 int
-afsconf_DeleteKey(struct afsconf_dir *adir, afs_int32 akvno)
+afsconf_DeleteKeyBySubType(struct afsconf_dir *dir,
+                          afsconf_keyType type, int kvno, int subType)
 {
-    struct afsconf_keys *tk;
-    struct afsconf_key *tkey;
-    int i;
-    int foundFlag = 0;
+    struct keyTypeList *typeEntry;
+    struct kvnoList *kvnoEntry;
+    struct subTypeList *subTypeEntry;
+    int code;
 
     LOCK_GLOBAL_MUTEX;
-    tk = adir->keystr;
 
-    for (i = 0, tkey = tk->key; i < tk->nkeys; i++, tkey++) {
-       if (tkey->kvno == akvno) {
-           foundFlag = 1;
-           break;
-       }
-    }
-    if (!foundFlag) {
-       UNLOCK_GLOBAL_MUTEX;
+    code = _afsconf_Check(dir);
+    if (code)
+       goto out;
+
+    typeEntry = findByType(dir, type);
+    if (typeEntry == NULL)
        return AFSCONF_NOTFOUND;
+
+    kvnoEntry = findInTypeList(typeEntry, kvno);
+    if (kvnoEntry == NULL)
+       return AFSCONF_NOTFOUND;
+
+    subTypeEntry = findInKvnoList(kvnoEntry, subType);
+    if (subTypeEntry == NULL)
+       return AFSCONF_NOTFOUND;
+
+    /* Remove the subTypeEntry */
+    afsconf_typedKey_put(&subTypeEntry->key);
+    opr_queue_Remove(&subTypeEntry->link);
+    free(subTypeEntry);
+
+    /* Remove the kvnoEntry, if it has no sub elements */
+    if (opr_queue_IsEmpty(&kvnoEntry->subTypeList)) {
+       opr_queue_Remove(&kvnoEntry->link);
+       free(kvnoEntry);
     }
 
-    /* otherwise slide the others down.  i and tkey point at the guy to delete */
-    for (; i < tk->nkeys - 1; i++, tkey++) {
-       tkey->kvno = (tkey + 1)->kvno;
-       memcpy(tkey->key, (tkey + 1)->key, 8);
+    /* Remove the typeEntry, if it has no sub elements */
+    if (opr_queue_IsEmpty(&typeEntry->kvnoList)) {
+       opr_queue_Remove(&typeEntry->link);
+       free(typeEntry);
     }
-    tk->nkeys--;
-    i = SaveKeys(adir);
-    _afsconf_Touch(adir);
+
+    code = _afsconf_SaveKeys(dir);
+    _afsconf_Touch(dir);
+
+out:
     UNLOCK_GLOBAL_MUTEX;
-    return i;
+    return code;
+}
+
+int
+afsconf_DeleteTypedKey(struct afsconf_dir *dir, struct afsconf_typedKey *key)
+{
+    return afsconf_DeleteKeyBySubType(dir, key->type, key->kvno, key->subType);
 }