New GetToken pioctl
authorSimon Wilkinson <sxw@inf.ed.ac.uk>
Wed, 7 Apr 2010 22:03:21 +0000 (23:03 +0100)
committerDerrick Brashear <shadow@dementia.org>
Mon, 30 Aug 2010 00:27:51 +0000 (17:27 -0700)
Implement a new, XDR based, GetToken pioctl which mirrors the new
SetToken pioctl.

Change-Id: I213e74edb3496baa40b5c8048e97df6888f742b4
Reviewed-on: http://gerrit.openafs.org/2584
Reviewed-by: Derrick Brashear <shadow@dementia.org>
Tested-by: Derrick Brashear <shadow@dementia.org>

src/afs/afs_cell.c
src/afs/afs_pioctl.c
src/afs/afs_prototypes.h
src/afs/afs_tokens.c
src/aklog/aklog.c
src/auth/auth.p.h
src/auth/ktc.c
src/auth/ktc.h
src/auth/token.c

index 9b3fd9a..a819767 100644 (file)
@@ -830,6 +830,24 @@ afs_GetPrimaryCell(afs_int32 locktype)
 }
 
 /*!
+ * Return number of the primary cell.
+ * \return
+ *    Cell number, or 0 if primary cell not found
+ */
+afs_int32
+afs_GetPrimaryCellNum(void)
+{
+    struct cell *cell;
+    afs_int32 cellNum = 0;
+    cell = afs_GetPrimaryCell(READ_LOCK);
+    if (cell) {
+       cellNum = cell->cellNum;
+       afs_PutCell(cell, READ_LOCK);
+    }
+    return cellNum;
+}
+
+/*!
  * Returns true if the given cell is the primary cell.
  * \param cell
  * \return
index 61a7cae..6b1a57c 100644 (file)
@@ -269,6 +269,7 @@ DECL_PIOCTL(PSetVolumeStatus);
 DECL_PIOCTL(PFlush);
 DECL_PIOCTL(PNewStatMount);
 DECL_PIOCTL(PGetTokens);
+DECL_PIOCTL(PGetTokens2);
 DECL_PIOCTL(PUnlog);
 DECL_PIOCTL(PMariner);
 DECL_PIOCTL(PCheckServers);
@@ -423,7 +424,7 @@ static pioctlFunction CpioctlSw[] = {
     PBogus,                     /* 4 */
     PDiscon,                    /* 5 -- get/set discon mode */
     PBogus,                     /* 6 */
-    PBogus,                     /* 7 */
+    PGetTokens2,                /* 7 */
     PSetTokens2,                /* 8 */
     PNewUuid,                   /* 9 */
     PBogus,                     /* 10 */
@@ -1353,7 +1354,8 @@ afs_HandlePioctl(struct vnode *avp, afs_int32 acom,
     if (code)
        goto out;
 
-    if (function == 8 && device == 'V') {      /* PGetTokens */
+    if ((function == 8 && device == 'V') ||
+       (function == 7 && device == 'C')) {     /* PGetTokens */
        code = afs_pd_alloc(&output, MAXPIOCTLTOKENLEN);
     } else {
        code = afs_pd_alloc(&output, AFS_LRALLOCSIZ);
@@ -2242,6 +2244,34 @@ DECL_PIOCTL(PNewStatMount)
 }
 
 /*!
+ * A helper function to get the n'th cell which a particular user has tokens
+ * for. This is racy. If new tokens are added whilst we're iterating, then
+ * we may return some cells twice. If tokens expire mid run, then we'll
+ * miss some cells from our output. So, could be better, but that would
+ * require an interface change.
+ */
+
+static struct unixuser *
+getNthCell(afs_int32 uid, afs_int32 iterator) {
+    int i;
+    struct unixuser *tu = NULL;
+
+    i = UHash(uid);
+    ObtainReadLock(&afs_xuser);
+    for (tu = afs_users[i]; tu; tu = tu->next) {
+       if (tu->uid == uid && (tu->states & UHasTokens)) {
+           if (iterator-- == 0)
+           break;      /* are we done yet? */
+       }
+    }
+    if (tu) {
+       tu->refCount++;
+    }
+    ReleaseReadLock(&afs_xuser);
+
+    return tu;
+}
+/*!
  * VIOCGETTOK (8) - Get authentication tokens
  *
  * \ingroup pioctl
@@ -2268,11 +2298,11 @@ DECL_PIOCTL(PNewStatMount)
 DECL_PIOCTL(PGetTokens)
 {
     struct cell *tcell;
-    afs_int32 i;
-    struct unixuser *tu;
+    struct unixuser *tu = NULL;
     union tokenUnion *token;
     afs_int32 iterator = 0;
     int newStyle;
+    int cellNum;
     int code = E2BIG;
 
     AFS_STATCNT(PGetTokens);
@@ -2293,27 +2323,13 @@ DECL_PIOCTL(PGetTokens)
        if (afs_pd_getInt(ain, &iterator) != 0)
            return EINVAL;
     }
-    i = UHash(areq->uid);
-    ObtainReadLock(&afs_xuser);
-    for (tu = afs_users[i]; tu; tu = tu->next) {
-       if (newStyle) {
-           if (tu->uid == areq->uid && (tu->states & UHasTokens)) {
-               if (iterator-- == 0)
-                   break;      /* are we done yet? */
-           }
-       } else {
-           if (tu->uid == areq->uid && afs_IsPrimaryCellNum(tu->cell))
-               break;
-       }
-    }
-    if (tu) {
-       /*
-        * No need to hold a read lock on each user entry
-        */
-       tu->refCount++;
+    if (newStyle) {
+       tu = getNthCell(areq->uid, iterator);
+    } else {
+       cellNum = afs_GetPrimaryCellNum();
+       if (cellNum)
+           tu = afs_FindUser(areq->uid, cellNum, READ_LOCK);
     }
-    ReleaseReadLock(&afs_xuser);
-
     if (!tu) {
        return EDOM;
     }
@@ -5348,6 +5364,82 @@ out:
     return code;
 }
 
+DECL_PIOCTL(PGetTokens2)
+{
+    struct cell *cell;
+    struct unixuser *tu = NULL;
+    afs_int32 iterator;
+    char *cellName = NULL;
+    afs_int32 cellNum;
+    int code = 0;
+    time_t now;
+    XDR xdrs;
+    struct ktc_setTokenData tokenSet;
+
+    AFS_STATCNT(PGetTokens);
+    if (!afs_resourceinit_flag)
+       return EIO;
+
+    memset(&tokenSet, 0, sizeof(tokenSet));
+
+    /* No input data - return tokens for primary cell */
+    /* 4 octets of data is an iterator count */
+    /* Otherwise, treat as string & return tokens for that cell name */
+
+    if (afs_pd_remaining(ain) == sizeof(afs_int32)) {
+       /* Integer iterator - return tokens for the n'th cell found for user */
+       if (afs_pd_getInt(ain, &iterator) != 0)
+           return EINVAL;
+       tu = getNthCell(areq->uid, iterator);
+    } else {
+        if (afs_pd_remaining(ain) > 0) {
+           if (afs_pd_getStringPtr(ain, &cellName) != 0)
+               return EINVAL;
+        } else {
+           cellName = NULL;
+       }
+       code = _settok_tokenCell(cellName, &cellNum, NULL);
+       if (code)
+           return code;
+       tu = afs_FindUser(areq->uid, cellNum, READ_LOCK);
+    }
+    if (tu == NULL)
+       return EDOM;
+
+    now = osi_Time();
+
+    if (!(tu->states & UHasTokens)
+       || !afs_HasValidTokens(tu->tokens, now)) {
+       tu->states |= (UTokensBad | UNeedsReset);
+       afs_PutUser(tu, READ_LOCK);
+       return ENOTCONN;
+    }
+
+    code = afs_ExtractTokensForPioctl(tu->tokens, now, &tokenSet);
+    if (code)
+       goto out;
+
+    cell = afs_GetCell(tu->cell, READ_LOCK);
+    tokenSet.cell = cell->cellName;
+    afs_pd_xdrStart(aout, &xdrs, XDR_ENCODE);
+    if (!xdr_ktc_setTokenData(&xdrs, &tokenSet)) {
+       code = E2BIG;
+       goto out;
+    }
+    afs_pd_xdrEnd(aout, &xdrs);
+
+out:
+    tokenSet.cell = NULL;
+
+    if (tu)
+       afs_PutUser(tu, READ_LOCK);
+    if (cell)
+       afs_PutCell(cell, READ_LOCK);
+    xdr_free((xdrproc_t)xdr_ktc_setTokenData, &tokenSet);
+
+    return code;
+};
+
 DECL_PIOCTL(PNFSNukeCreds)
 {
     afs_uint32 addr;
index 8cc6a62..979d78d 100644 (file)
@@ -144,6 +144,7 @@ extern struct cell *afs_GetCellByHandle(void *handle, afs_int32 locktype);
 extern struct cell *afs_GetCellByIndex(afs_int32 cellidx, afs_int32 locktype);
 extern struct cell *afs_GetCellByName(char *acellName, afs_int32 locktype);
 extern struct cell *afs_GetPrimaryCell(afs_int32 locktype);
+extern afs_int32 afs_GetPrimaryCellNum(void);
 extern int afs_IsPrimaryCellNum(afs_int32 cellnum);
 extern int afs_IsPrimaryCell(struct cell *cell);
 extern void *afs_TraverseCells(void *(*cb) (struct cell *, void *),
@@ -917,14 +918,19 @@ extern int Afs_syscall(void);
 
 /* afs_tokens.c */
 struct ktc_tokenUnion;
+struct ktc_setTokenData;
+
 extern union tokenUnion *afs_FindToken(struct tokenJar *, rx_securityIndex);
 extern void afs_FreeTokens(struct tokenJar **);
 extern union tokenUnion *afs_AddToken(struct tokenJar **, rx_securityIndex);
 extern void afs_DiscardExpiredTokens(struct tokenJar **, afs_int32);
+extern int afs_HasValidTokens(struct tokenJar *, afs_int32);
 extern int afs_HasUsableTokens(struct tokenJar *, afs_int32);
 extern void afs_AddRxkadToken(struct tokenJar **, char *, int,
                              struct ClearToken *);
 extern int afs_AddTokenFromPioctl(struct tokenJar **, struct ktc_tokenUnion *);
+extern int afs_ExtractTokensForPioctl(struct tokenJar *, time_t,
+                                     struct ktc_setTokenData *);
 
 /* UKERNEL/afs_usrops.c */
 #ifdef UKERNEL
index ac0a080..64b4085 100644 (file)
@@ -248,6 +248,53 @@ afs_HasUsableTokens(struct tokenJar *token, afs_int32 now) {
 }
 
 /*!
+ * Indicate whether a token jar contains a valid (non-expired) token
+ *
+ * @param[in] token
+ *     The token jar to check
+ * @param[in] now
+ *     The time to use for the expiry check
+ *
+ * @returns
+ *     True if the jar contains valid tokens, otherwise false
+ *
+ */
+int
+afs_HasValidTokens(struct tokenJar *token, afs_int32 now) {
+    while (token != NULL) {
+        if (!afs_IsTokenExpired(token, now))
+           return 1;
+       token = token->next;
+    }
+    return 0;
+}
+
+/*!
+ * Count the number of valid tokens in a jar. A valid token is
+ * one which is not expired - note that valid tokens may not be
+ * usable by the kernel.
+ *
+ * @param[in] token
+ *     The token jar to check
+ * @param[in] now
+ *     The time to use for the expiry check
+ *
+ * @returns
+ *     The number of valid tokens in the jar
+ */
+static int
+countValidTokens(struct tokenJar *token, time_t now) {
+    int count = 0;
+
+    while (token != NULL) {
+        if (!afs_IsTokenExpired(token, now))
+           count ++;
+       token = token->next;
+    }
+    return count;
+}
+
+/*!
  * Add an rxkad token to the token jar
  *
  * @param[in] tokens
@@ -295,6 +342,31 @@ afs_AddRxkadTokenFromPioctl(struct tokenJar **tokens,
     return 0;
 }
 
+static int
+rxkad_extractTokenForPioctl(struct tokenJar *token,
+                              struct ktc_tokenUnion *pioctlToken) {
+
+    struct token_rxkad *rxkadPioctl;
+    struct rxkadToken *rxkadInternal;
+
+    rxkadPioctl = &pioctlToken->ktc_tokenUnion_u.at_kad;
+    rxkadInternal = &token->u.rxkad;
+
+    rxkadPioctl->rk_kvno = rxkadInternal->clearToken.AuthHandle;
+    rxkadPioctl->rk_viceid = rxkadInternal->clearToken.ViceId;
+    rxkadPioctl->rk_begintime = rxkadInternal->clearToken.BeginTimestamp;
+    rxkadPioctl->rk_endtime = rxkadInternal->clearToken.EndTimestamp;
+    memcpy(rxkadPioctl->rk_key, rxkadInternal->clearToken.HandShakeKey, 8);
+
+    rxkadPioctl->rk_ticket.rk_ticket_val = xdr_alloc(rxkadInternal->ticketLen);
+    if (rxkadPioctl->rk_ticket.rk_ticket_val == NULL)
+       return ENOMEM;
+    rxkadPioctl->rk_ticket.rk_ticket_len = rxkadInternal->ticketLen;
+    memcpy(rxkadPioctl->rk_ticket.rk_ticket_val,
+          rxkadInternal->ticket, rxkadInternal->ticketLen);
+
+    return 0;
+}
 
 /*!
  * Add a token to a token jar based on the input from a new-style
@@ -320,3 +392,101 @@ afs_AddTokenFromPioctl(struct tokenJar **tokens,
 
     return EINVAL;
 }
+
+static int
+extractPioctlToken(struct tokenJar *token,
+                  struct token_opaque *opaque) {
+    XDR xdrs;
+    struct ktc_tokenUnion *pioctlToken;
+    int code;
+
+    memset(opaque, 0, sizeof(token_opaque));
+
+    pioctlToken = osi_Alloc(sizeof(struct ktc_tokenUnion));
+    if (pioctlToken == NULL)
+       return ENOMEM;
+
+    pioctlToken->at_type = token->type;
+
+    switch (token->type) {
+      case RX_SECIDX_KAD:
+       code = rxkad_extractTokenForPioctl(token, pioctlToken);
+       break;
+      default:
+       code = EINVAL;;
+    }
+
+    if (code)
+       goto out;
+
+    xdrlen_create(&xdrs);
+    if (!xdr_ktc_tokenUnion(&xdrs, pioctlToken)) {
+       code = EINVAL;
+       xdr_destroy(&xdrs);
+       goto out;
+    }
+
+    opaque->token_opaque_len = xdr_getpos(&xdrs);
+    xdr_destroy(&xdrs);
+
+    opaque->token_opaque_val = osi_Alloc(opaque->token_opaque_len);
+    if (opaque->token_opaque_val == NULL) {
+       code = ENOMEM;
+       goto out;
+    }
+
+    xdrmem_create(&xdrs,
+                 opaque->token_opaque_val,
+                 opaque->token_opaque_len,
+                 XDR_ENCODE);
+    if (!xdr_ktc_tokenUnion(&xdrs, pioctlToken)) {
+       code = EINVAL;
+       xdr_destroy(&xdrs);
+       goto out;
+    }
+    xdr_destroy(&xdrs);
+
+out:
+    xdr_free((xdrproc_t) xdr_ktc_tokenUnion, &pioctlToken);
+    osi_Free(pioctlToken, sizeof(struct ktc_tokenUnion));
+
+    if (code != 0) {
+       osi_Free(opaque->token_opaque_val, opaque->token_opaque_len);
+       opaque->token_opaque_val = NULL;
+       opaque->token_opaque_len = 0;
+    }
+    return code;
+}
+
+int
+afs_ExtractTokensForPioctl(struct tokenJar *token,
+                          time_t now,
+                          struct ktc_setTokenData *tokenSet)
+{
+    int numTokens, pos;
+    int code = 0;
+
+    numTokens = countValidTokens(token, now);
+
+    tokenSet->tokens.tokens_len = numTokens;
+    tokenSet->tokens.tokens_val
+       = xdr_alloc(sizeof(struct token_opaque) * numTokens);
+
+    if (tokenSet->tokens.tokens_val == NULL)
+       return ENOMEM;
+
+    pos = 0;
+    while (token != NULL && pos < numTokens) {
+       code = extractPioctlToken(token, &tokenSet->tokens.tokens_val[pos]);
+       if (code)
+           goto out;
+       token = token->next;
+       pos++;
+    }
+
+out:
+    if (code)
+       xdr_free((xdrproc_t) xdr_ktc_setTokenData, tokenSet);
+
+    return code;
+}
index a38b66e..df81a75 100644 (file)
@@ -919,63 +919,7 @@ out:
     return status;
 }
 
-/*!
- * Get the set of tokens for a given cell out of the cache manager
- *
- * @param[in] cell
- *     The cellconf structure for the cell to retrieve tokens for
- * @param[out] tokenPtr
- *     The tokens held for that cell
- *
- * @returns
- *     0 on success, otherwise an error code
- */
-
-static int
-get_kernel_token(struct afsconf_cell *cell, struct ktc_token **tokenPtr) {
-    struct ktc_principal client, server;
-    struct ktc_token *token;
-    int ret;
-
-    *tokenPtr = NULL;
-
-    strncpy(server.name, AFSKEY, MAXKTCNAMELEN - 1);
-    strncpy(server.instance, AFSINST, MAXKTCNAMELEN - 1);
-    strncpy(server.cell, cell->name, MAXKTCREALMLEN - 1);
-
-    token = malloc(sizeof(struct ktc_token));
-    if (token == NULL)
-       return ENOMEM;
-
-    memset(token, 0, sizeof(struct ktc_token));
-
-    ret = ktc_GetToken(&server, token, sizeof(struct ktc_token), &client);
-    if (ret) {
-       free(token);
-       return ret;
-    }
-
-    *tokenPtr = token;
-    return 0;
-}
-
-/**
- * Return true if a pair of tokens are directly equivalent
- */
-static int
-tokens_equal(struct ktc_setTokenData *tokenA, struct ktc_token *tokenB) {
-   return 0;
-/* Bodge bodge bodge
-   return (tokenA != NULL && tokenB != NULL &&
-           tokenA->kvno == tokenB->kvno &&
-           tokenA->ticketLen == tokenB->ticketLen &&
-           !memcmp(&tokenA->sessionKey, &tokenB->sessionKey,
-                   sizeof(tokenA->sessionKey)) &&
-           !memcmp(tokenA->ticket, tokenB->ticket, tokenA->ticketLen));
-*/
-}
-
-/*
+/* 
  * Log to a cell.  If the cell has already been logged to, return without
  * doing anything.  Otherwise, log to it and mark that it has been logged
  * to.
@@ -991,7 +935,7 @@ auth_to_cell(krb5_context context, char *cell, char *realm, char **linkedcell)
     char *local_cell = NULL;
     struct ktc_tokenUnion *rxkadToken = NULL;
     struct ktc_setTokenData *token;
-    struct ktc_token *btoken;
+    struct ktc_setTokenData *btoken = NULL;
     struct afsconf_cell cellconf;
 
     /* NULL or empty cell returns information on local cell */
@@ -1067,13 +1011,18 @@ auth_to_cell(krb5_context context, char *cell, char *realm, char **linkedcell)
        }
 
        if (!force &&
-           !get_kernel_token(&cellconf, &btoken) &&
-           tokens_equal(token, btoken)) {
+           ktc_GetTokenEx(cellconf.name, &btoken) == 0 &&
+           token_SetsEquivalent(token, btoken)) {
+
+           token_FreeSet(&btoken);
            afs_dprintf("Identical tokens already exist; skipping.\n");
            status = AKLOG_SUCCESS;
            goto out;
        }
 
+       if (btoken)
+           token_FreeSet(&btoken);
+
 #ifdef FORCE_NOPRDB
        noprdb = 1;
 #endif
index 5d473f3..84f8c0c 100644 (file)
@@ -31,6 +31,7 @@ int ktc_GetToken(struct ktc_principal *, struct ktc_token *,
 
 struct ktc_setTokenData;
 int ktc_SetTokenEx(struct ktc_setTokenData *);
+int ktc_GetTokenEx(char *, struct ktc_setTokenData **);
 
 int ktc_ListTokens(int, int *, struct ktc_principal *);
 int ktc_ForgetToken(struct ktc_principal *);
index e52f1c8..a29613c 100644 (file)
@@ -451,6 +451,94 @@ ktc_SetToken(struct ktc_principal *aserver,
     return 0;
 }
 
+/*!
+ * Get a token, given the cell that we need to get information for
+ *
+ * @param cellName
+ *     The name of the cell we're getting the token for - if NULL, we'll
+ *     get information for the primary cell
+ */
+int
+ktc_GetTokenEx(char *cellName, struct ktc_setTokenData **tokenSet) {
+    struct ViceIoctl iob;
+    char tbuffer[MAXPIOCTLTOKENLEN];
+    char *tp;
+    afs_int32 code;
+    XDR xdrs;
+
+    tp = tbuffer;
+
+    /* If we have a cellName, write it out here */
+    if (cellName) {
+       memcpy(tp, cellName, strlen(cellName) +1);
+       tp += strlen(cellName)+1;
+    }
+
+    iob.in = tbuffer;
+    iob.in_size = tp - tbuffer;
+    iob.out = tbuffer;
+    iob.out_size = sizeof(tbuffer);
+
+    code = PIOCTL(0, VIOC_GETTOK2, &iob, 0);
+
+    /* If we can't use the new pioctl, the fall back to the old one. We then
+     * need to convert the rxkad token we get back into the new format
+     */
+    if (code == -1 && errno == EINVAL) {
+       struct ktc_principal server;
+       struct ktc_principal client;
+       struct ktc_tokenUnion token;
+       struct ktc_token *ktcToken; /* too huge for the stack */
+
+       memset(&server, 0, sizeof(server));
+       ktcToken = malloc(sizeof(struct ktc_token));
+       if (ktcToken == NULL)
+           return ENOMEM;
+       memset(ktcToken, 0, sizeof(struct ktc_token));
+
+       strcpy(server.name, "afs");
+       strcpy(server.cell, cellName);
+       code = ktc_GetToken(&server, ktcToken, sizeof(struct ktc_token),
+                           &client);
+       if (code == 0) {
+           *tokenSet = token_buildTokenJar(cellName);
+           token.at_type = AFSTOKEN_UNION_KAD;
+           token.ktc_tokenUnion_u.at_kad.rk_kvno = ktcToken->kvno;
+           memcpy(token.ktc_tokenUnion_u.at_kad.rk_key,
+                  ktcToken->sessionKey.data, 8);
+
+           token.ktc_tokenUnion_u.at_kad.rk_begintime = ktcToken->startTime;
+           token.ktc_tokenUnion_u.at_kad.rk_endtime   = ktcToken->endTime;
+           token.ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_len
+               = ktcToken->ticketLen;
+           token.ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_val
+               = ktcToken->ticket;
+
+           token_addToken(*tokenSet, &token);
+
+           memset(ktcToken, 0, sizeof(struct ktc_token));
+       }
+       free(ktcToken);
+    }
+    if (code)
+       return KTC_PIOCTLFAIL;
+
+    *tokenSet = malloc(sizeof(struct ktc_setTokenData));
+    if (*tokenSet == NULL)
+       return ENOMEM;
+    memset(*tokenSet, 0, sizeof(struct ktc_setTokenData));
+
+    xdrmem_create(&xdrs, iob.out, iob.out_size, XDR_DECODE);
+    if (!xdr_ktc_setTokenData(&xdrs, *tokenSet)) {
+       free(*tokenSet);
+       *tokenSet = NULL;
+       xdr_destroy(&xdrs);
+       return EINVAL;
+    }
+    xdr_destroy(&xdrs);
+    return 0;
+}
+
 /* get token, given server we need and token buffer.  aclient will eventually
  * be set to our identity to the server.
  */
index 441e5c3..3507639 100644 (file)
@@ -23,7 +23,10 @@ extern struct ktc_setTokenData *token_buildTokenJar(char *);
 extern int token_addToken(struct ktc_setTokenData *, struct ktc_tokenUnion *);
 extern int token_replaceToken(struct ktc_setTokenData *,
                              struct ktc_tokenUnion *);
+extern int token_SetsEquivalent(struct ktc_setTokenData *,
+                               struct ktc_setTokenData *);
 extern void token_setPag(struct ktc_setTokenData *, int);
+extern void token_FreeSet(struct ktc_setTokenData **);
 
 struct ktc_token;
 struct ktc_principal;
index 36281b4..4070bb8 100644 (file)
  * otherwise noted, the implementation is new
  */
 
+/* Take a peak at the enumerator in a given encoded token, in order to
+ * return its type
+ */
+static int
+tokenType(struct token_opaque *opaque) {
+    XDR xdrs;
+    int type;
+
+    xdrmem_create(&xdrs, opaque->token_opaque_val, opaque->token_opaque_len,
+                 XDR_DECODE);
+
+    if (!xdr_enum(&xdrs, &type))
+       type = -1;
+
+    xdr_destroy(&xdrs);
+
+    return type;
+}
+
+static int
+decodeToken(struct token_opaque *opaque, struct ktc_tokenUnion *token) {
+    XDR xdrs;
+    int code;
+
+    memset(token, 0, sizeof(struct ktc_tokenUnion));
+    xdrmem_create(&xdrs, opaque->token_opaque_val, opaque->token_opaque_len,
+                 XDR_DECODE);
+    code = xdr_ktc_tokenUnion(&xdrs, token);
+    xdr_destroy(&xdrs);
+
+    return code;
+}
+
+static void
+freeToken(struct ktc_tokenUnion *token) {
+    xdr_free((xdrproc_t)xdr_ktc_tokenUnion, token);
+}
+
+static int
+rxkadTokenEqual(struct ktc_tokenUnion *tokenA, struct ktc_tokenUnion *tokenB) {
+    return (tokenA->ktc_tokenUnion_u.at_kad.rk_kvno ==
+           tokenB->ktc_tokenUnion_u.at_kad.rk_kvno
+        && tokenA->ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_len ==
+           tokenB->ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_len
+        && !memcmp(tokenA->ktc_tokenUnion_u.at_kad.rk_key,
+                   tokenB->ktc_tokenUnion_u.at_kad.rk_key, 8)
+        && !memcmp(tokenA->ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_val,
+                   tokenB->ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_val,
+                   tokenA->ktc_tokenUnion_u.at_kad.rk_ticket.rk_ticket_len));
+}
+
+static int
+tokenEqual(struct ktc_tokenUnion *tokenA,
+          struct ktc_tokenUnion *tokenB) {
+    switch (tokenA->at_type) {
+      case AFSTOKEN_UNION_KAD:
+       return rxkadTokenEqual(tokenA, tokenB);
+    }
+    return 0;
+}
+
+static int
+rawTokenEqual(struct token_opaque *tokenA, struct token_opaque *tokenB) {
+    return (tokenA->token_opaque_len == tokenB->token_opaque_len &&
+           !memcmp(tokenA->token_opaque_val, tokenB->token_opaque_val,
+                   tokenA->token_opaque_len));
+}
+
 /* Given a token type, return the entry number of the first token of that
  * type */
 static int
 findTokenEntry(struct ktc_setTokenData *token,
               int targetType)
 {
-    XDR xdrs;
-    int i, type;
+    int i;
 
     for (i = 0; i < token->tokens.tokens_len; i++) {
-       xdrmem_create(&xdrs,
-                     token->tokens.tokens_val[i].token_opaque_val,
-                     token->tokens.tokens_val[i].token_opaque_len,
-                     XDR_DECODE);
-       /* Take a peak at the discriminator. */
-       if (!xdr_enum(&xdrs, &type)) {
-           type = -1;
-       }
-       xdr_destroy(&xdrs);
-
-       if (type == targetType)
+       if (tokenType(&token->tokens.tokens_val[i]) == targetType)
            return i;
     }
     return -1;
@@ -147,35 +204,22 @@ token_findByType(struct ktc_setTokenData *token,
                 int targetType,
                 struct ktc_tokenUnion *output)
 {
-    XDR xdrs;
     int entry;
-    int code = EINVAL;
 
     memset(output, 0, sizeof *output);
     entry = findTokenEntry(token, targetType);
     if (entry == -1)
-       goto out;
+       return EINVAL;
 
-    xdrmem_create(&xdrs,
-                 token->tokens.tokens_val[entry].token_opaque_val,
-                 token->tokens.tokens_val[entry].token_opaque_len,
-                 XDR_DECODE);
-
-    if (!xdr_ktc_tokenUnion(&xdrs, output)) {
-       xdr_destroy(&xdrs);
-       goto out;
-    }
+    if (!decodeToken(&token->tokens.tokens_val[entry], output))
+       return EINVAL;
 
-    xdr_destroy(&xdrs);
     if (output->at_type != targetType) {
        xdr_free((xdrproc_t)xdr_ktc_tokenUnion, output);
-       goto out;
+       return EINVAL;
     }
 
-    code = 0;
-
-out:
-    return code;
+    return 0;
 }
 
 /*!
@@ -317,6 +361,64 @@ out:
     return code;
 }
 
+/*!
+ * Work out if a pair of token sets are equivalent. Equivalence
+ * is defined as both sets containing the same number of tokens,
+ * and every token in the first set having an equivalent token
+ * in the second set. Cell name and flags value are not compared.
+ *
+ * @param[in] tokensA
+ *     First set of tokens
+ * @param[in] tokensB
+ *     Second set of tokens
+ *
+ * @returns
+ *     True if token sets are equivalent, false otherwise
+ */
+int
+token_SetsEquivalent(struct ktc_setTokenData *tokenSetA,
+                    struct ktc_setTokenData *tokenSetB) {
+    int i, j;
+    int decodedOK, found;
+    struct ktc_tokenUnion tokenA, tokenB;
+
+    if (tokenSetA->tokens.tokens_len != tokenSetB->tokens.tokens_len)
+       return 0;
+
+    for (i=0; i<tokenSetA->tokens.tokens_len; i++) {
+       found = 0;
+
+       decodedOK = decodeToken(&tokenSetA->tokens.tokens_val[i], &tokenA);
+
+       for (j=0; j<tokenSetB->tokens.tokens_len && !found; j++) {
+           if (rawTokenEqual(&tokenSetA->tokens.tokens_val[i],
+                             &tokenSetB->tokens.tokens_val[j])) {
+               found = 1;
+               break;
+           }
+
+           if (decodedOK &&
+               tokenType(&tokenSetB->tokens.tokens_val[j]) == tokenA.at_type
+               && decodeToken(&tokenSetB->tokens.tokens_val[j], &tokenB)) {
+
+               if (tokenEqual(&tokenA, &tokenB)) {
+                   found = 1;
+                   break;
+               }
+               freeToken(&tokenB);
+           }
+       }
+       if (decodedOK)
+           freeToken(&tokenA);
+
+       if (!found)
+           return 0;
+    }
+    /* If we made it this far without exiting, we must have found equivalents
+     * for all of our tokens */
+    return 1;
+}
+
 void
 token_setPag(struct ktc_setTokenData *jar, int setpag) {
     if (setpag)
@@ -324,3 +426,10 @@ token_setPag(struct ktc_setTokenData *jar, int setpag) {
     else
        jar->flags &= ~AFSTOKEN_EX_SETPAG;
 }
+
+void
+token_FreeSet(struct ktc_setTokenData **jar) {
+    xdr_free((xdrproc_t)xdr_ktc_setTokenData, *jar);
+    memset(*jar, 0, sizeof(struct ktc_setTokenData));
+    *jar = NULL;
+}