diff mbox series

[nfs-utils,v3,2/4] nfsdcld: add support for upcall version 2

Message ID 20190910145003.4165-3-smayhew@redhat.com (mailing list archive)
State New, archived
Headers show
Series add hash of the kerberos principal to the data being tracked by nfsdcld | expand

Commit Message

Scott Mayhew Sept. 10, 2019, 2:50 p.m. UTC
Version 2 upcalls will allow the nfsd to include a hash of the kerberos
principal string in the Cld_Create upcall.  If present, the hash
will be stored along with the client id string in the database, and
will be included in the Cld_GraceStart downcall whenever nfsd restarts.

Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
 support/include/cld.h        |  30 ++++-
 utils/nfsdcld/cld-internal.h |  11 +-
 utils/nfsdcld/nfsdcld.c      |  97 +++++++++++---
 utils/nfsdcld/sqlite.c       | 237 +++++++++++++++++++++++++++++------
 utils/nfsdcld/sqlite.h       |   2 +
 5 files changed, 321 insertions(+), 56 deletions(-)
diff mbox series

Patch

diff --git a/support/include/cld.h b/support/include/cld.h
index 00a40da..88d3b63 100644
--- a/support/include/cld.h
+++ b/support/include/cld.h
@@ -23,11 +23,15 @@ 
 #define _NFSD_CLD_H
 
 /* latest upcall version available */
-#define CLD_UPCALL_VERSION 1
+#define CLD_UPCALL_VERSION 2
 
 /* defined by RFC3530 */
 #define NFS4_OPAQUE_LIMIT 1024
 
+#ifndef SHA256_DIGEST_SIZE
+#define SHA256_DIGEST_SIZE      32
+#endif
+
 enum cld_command {
 	Cld_Create,		/* create a record for this cm_id */
 	Cld_Remove,		/* remove record of this cm_id */
@@ -43,6 +47,17 @@  struct cld_name {
 	unsigned char	cn_id[NFS4_OPAQUE_LIMIT];	/* client-provided */
 } __attribute__((packed));
 
+/* sha256 hash of the kerberos principal */
+struct cld_princhash {
+	uint8_t		cp_len;				/* length of cp_data */
+	unsigned char	cp_data[SHA256_DIGEST_SIZE];	/* hash of principal */
+} __attribute__((packed));
+
+struct cld_clntinfo {
+	struct cld_name		cc_name;
+	struct cld_princhash	cc_princhash;
+} __attribute__((packed));
+
 /* message struct for communication with userspace */
 struct cld_msg {
 	uint8_t		cm_vers;		/* upcall version */
@@ -56,6 +71,19 @@  struct cld_msg {
 	} __attribute__((packed)) cm_u;
 } __attribute__((packed));
 
+/* version 2 message can include hash of kerberos principal */
+struct cld_msg_v2 {
+	uint8_t		cm_vers;		/* upcall version */
+	uint8_t		cm_cmd;			/* upcall command */
+	int16_t		cm_status;		/* return code */
+	uint32_t	cm_xid;			/* transaction id */
+	union {
+		struct cld_name	cm_name;
+		uint8_t		cm_version;	/* for getting max version */
+		struct cld_clntinfo cm_clntinfo; /* name & princ hash */
+	} __attribute__((packed)) cm_u;
+} __attribute__((packed));
+
 struct cld_msg_hdr {
 	uint8_t		cm_vers;		/* upcall version */
 	uint8_t		cm_cmd;			/* upcall command */
diff --git a/utils/nfsdcld/cld-internal.h b/utils/nfsdcld/cld-internal.h
index f33cb04..05f01be 100644
--- a/utils/nfsdcld/cld-internal.h
+++ b/utils/nfsdcld/cld-internal.h
@@ -18,11 +18,20 @@ 
 #ifndef _CLD_INTERNAL_H_
 #define _CLD_INTERNAL_H_
 
+#if CLD_UPCALL_VERSION >= 2
+#define UPCALL_VERSION		2
+#else
+#define UPCALL_VERSION		1
+#endif
+
 struct cld_client {
 	int			cl_fd;
 	struct event		cl_event;
 	union {
-		struct cld_msg	cl_msg;
+		struct cld_msg		cl_msg;
+#if UPCALL_VERSION >= 2
+		struct cld_msg_v2	cl_msg_v2;
+#endif
 	} cl_u;
 };
 
diff --git a/utils/nfsdcld/nfsdcld.c b/utils/nfsdcld/nfsdcld.c
index aa5594b..277b4ca 100644
--- a/utils/nfsdcld/nfsdcld.c
+++ b/utils/nfsdcld/nfsdcld.c
@@ -60,8 +60,6 @@ 
 
 #define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace"
 
-#define UPCALL_VERSION		1
-
 /* private data structures */
 
 /* global variables */
@@ -338,20 +336,46 @@  cld_check_grace_period(void)
 	return ret;
 }
 
+#if UPCALL_VERSION >= 2
+static ssize_t cld_message_size(void *msg)
+{
+	struct cld_msg_hdr *hdr = (struct cld_msg_hdr *)msg;
+
+	switch (hdr->cm_vers) {
+	case 1:
+		return sizeof(struct cld_msg);
+	case 2:
+		return sizeof(struct cld_msg_v2);
+	default:
+		xlog(L_FATAL, "%s invalid upcall version %d", __func__,
+		     hdr->cm_vers);
+		exit(-EINVAL);
+	}
+}
+#else
+static ssize_t cld_message_size(void *UNUSED(msg))
+{
+	return sizeof(struct cld_msg);
+}
+#endif
+
 static void
 cld_not_implemented(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	xlog(D_GENERAL, "%s: downcalling with not implemented error", __func__);
 
 	/* set up reply */
 	cmsg->cm_status = -EOPNOTSUPP;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
 	if (wsize != bsize)
 		xlog(L_ERROR, "%s: problem writing to cld pipe (%ld): %m",
@@ -370,15 +394,18 @@  cld_get_version(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	xlog(D_GENERAL, "%s: version = %u.", __func__, UPCALL_VERSION);
 
 	cmsg->cm_u.cm_version = UPCALL_VERSION;
 	cmsg->cm_status = 0;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
 	if (wsize != bsize) {
@@ -398,7 +425,11 @@  cld_create(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	ret = cld_check_grace_period();
 	if (ret)
@@ -406,15 +437,25 @@  cld_create(struct cld_client *clnt)
 
 	xlog(D_GENERAL, "%s: create client record.", __func__);
 
-
+#if UPCALL_VERSION >= 2
+	if (cmsg->cm_vers >= 2)
+		ret = sqlite_insert_client_and_princhash(
+					cmsg->cm_u.cm_clntinfo.cc_name.cn_id,
+					cmsg->cm_u.cm_clntinfo.cc_name.cn_len,
+					cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data,
+					cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len);
+	else
+		ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id,
+					   cmsg->cm_u.cm_name.cn_len);
+#else
 	ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id,
 				   cmsg->cm_u.cm_name.cn_len);
+#endif
 
 reply:
 	cmsg->cm_status = ret ? -EREMOTEIO : ret;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
 	if (wsize != bsize) {
@@ -434,7 +475,11 @@  cld_remove(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	ret = cld_check_grace_period();
 	if (ret)
@@ -448,8 +493,7 @@  cld_remove(struct cld_client *clnt)
 reply:
 	cmsg->cm_status = ret ? -EREMOTEIO : ret;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
 			cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
@@ -470,7 +514,11 @@  cld_check(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	/*
 	 * If we get a check upcall at all, it means we're talking to an old
@@ -495,8 +543,7 @@  reply:
 	/* set up reply */
 	cmsg->cm_status = ret ? -EACCES : ret;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
 			cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
@@ -517,7 +564,11 @@  cld_gracedone(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	/*
 	 * If we got a "gracedone" upcall while we're not in grace, then
@@ -552,8 +603,7 @@  reply:
 	/* set up reply: downcall with 0 status */
 	cmsg->cm_status = ret ? -EREMOTEIO : ret;
 
-	bsize = sizeof(*cmsg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
 	if (wsize != bsize) {
@@ -571,12 +621,15 @@  reply:
 static int
 gracestart_callback(struct cld_client *clnt) {
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	cmsg->cm_status = -EINPROGRESS;
 
-	bsize = sizeof(struct cld_msg);
-
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "Sending client %.*s",
 			cmsg->cm_u.cm_name.cn_len, cmsg->cm_u.cm_name.cn_id);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
@@ -590,7 +643,11 @@  cld_gracestart(struct cld_client *clnt)
 {
 	int ret;
 	ssize_t bsize, wsize;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	xlog(D_GENERAL, "%s: updating grace epochs", __func__);
 
@@ -606,7 +663,7 @@  reply:
 	/* set up reply: downcall with 0 status */
 	cmsg->cm_status = ret ? -EREMOTEIO : ret;
 
-	bsize = sizeof(struct cld_msg);
+	bsize = cld_message_size(cmsg);
 	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
 	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
 	if (wsize != bsize) {
@@ -626,7 +683,11 @@  cldcb(int UNUSED(fd), short which, void *data)
 {
 	ssize_t len;
 	struct cld_client *clnt = data;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	if (which != EV_READ)
 		goto out;
diff --git a/utils/nfsdcld/sqlite.c b/utils/nfsdcld/sqlite.c
index 6525fc1..151ea21 100644
--- a/utils/nfsdcld/sqlite.c
+++ b/utils/nfsdcld/sqlite.c
@@ -37,8 +37,9 @@ 
  *        them in the database.
  *
  * rec-CCCCCCCCCCCCCCCC (where C is the hex representation of the epoch value):
- *        a single "id" column containing a BLOB with the long-form clientid
- *        as sent by the client.
+ *        an "id" column containing a BLOB with the long-form clientid
+ *        as sent by the client, and a "princhash" column containing a BLOB
+ *        with the sha256 hash of the kerberos principal (if available).
  */
 
 #ifdef HAVE_CONFIG_H
@@ -69,7 +70,7 @@ 
 #include "legacy.h"
 #include "nfslib.h"
 
-#define CLD_SQLITE_LATEST_SCHEMA_VERSION 3
+#define CLD_SQLITE_LATEST_SCHEMA_VERSION 4
 #define CLTRACK_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
 
 /* in milliseconds */
@@ -173,35 +174,56 @@  out:
 }
 
 static int
-sqlite_maindb_update_schema(int oldversion)
+sqlite_add_princ_col_cb(void *UNUSED(arg), int ncols, char **cols,
+			    char **UNUSED(colnames))
 {
-	int ret, ret2;
+	int ret;
 	char *err;
 
-	/* begin transaction */
-	ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
-				&err);
+	if (ncols > 1)
+		return -EINVAL;
+	ret = snprintf(buf, sizeof(buf), "ALTER TABLE \"%s\" "
+			"ADD COLUMN princhash BLOB;", cols[0]);
+	if (ret < 0) {
+		xlog(L_ERROR, "sprintf failed!");
+		return -EINVAL;
+	} else if ((size_t)ret >= sizeof(buf)) {
+		xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+		return -EINVAL;
+	}
+	ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
-		xlog(L_ERROR, "Unable to begin transaction: %s", err);
-		goto rollback;
+		xlog(L_ERROR, "Unable to add princhash column to table %s: %s",
+		     cols[0], err);
+		goto out;
 	}
+	xlog(D_GENERAL, "Added princhash column to table %s", cols[0]);
+out:
+	sqlite3_free(err);
+	return ret;
+}
 
-	/*
-	 * Check schema version again. This time, under an exclusive
-	 * transaction to guard against racing DB setup attempts
-	 */
-	ret = sqlite_query_schema_version();
-	if (ret != oldversion) {
-		if (ret == CLD_SQLITE_LATEST_SCHEMA_VERSION)
-			/* Someone else raced in and set it up */
-			ret = 0;
-		else
-			/* Something went wrong -- fail! */
-			ret = -EINVAL;
-		goto rollback;
+static int
+sqlite_maindb_update_v3_to_v4(void)
+{
+	int ret;
+	char *err;
+
+	ret = sqlite3_exec(dbh, "SELECT name FROM sqlite_master "
+			   "WHERE type=\"table\" AND name LIKE \"%rec-%\";",
+			   sqlite_add_princ_col_cb, NULL, &err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: Failed to update tables!: %s", __func__, err);
 	}
+	sqlite3_free(err);
+	return ret;
+}
 
-	/* Still at old version -- do conversion */
+static int
+sqlite_maindb_update_v1v2_to_v4(void)
+{
+	int ret;
+	char *err;
 
 	/* create grace table */
 	ret = sqlite3_exec(dbh, "CREATE TABLE grace "
@@ -209,7 +231,7 @@  sqlite_maindb_update_schema(int oldversion)
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to create grace table: %s", err);
-		goto rollback;
+		goto out;
 	}
 
 	/* insert initial epochs into grace table */
@@ -218,26 +240,26 @@  sqlite_maindb_update_schema(int oldversion)
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to set initial epochs: %s", err);
-		goto rollback;
+		goto out;
 	}
 
 	/* create recovery table for current epoch */
 	ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" "
-				"(id BLOB PRIMARY KEY);",
+				"(id BLOB PRIMARY KEY, princhash BLOB);",
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to create recovery table "
 				"for current epoch: %s", err);
-		goto rollback;
+		goto out;
 	}
 
 	/* copy records from old clients table */
-	ret = sqlite3_exec(dbh, "INSERT INTO \"rec-0000000000000001\" "
+	ret = sqlite3_exec(dbh, "INSERT INTO \"rec-0000000000000001\" (id) "
 				"SELECT id FROM clients;",
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to copy client records: %s", err);
-		goto rollback;
+		goto out;
 	}
 
 	/* drop the old clients table */
@@ -245,9 +267,57 @@  sqlite_maindb_update_schema(int oldversion)
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to drop old clients table: %s", err);
+	}
+out:
+	sqlite3_free(err);
+	return ret;
+}
+
+static int
+sqlite_maindb_update_schema(int oldversion)
+{
+	int ret, ret2;
+	char *err;
+
+	/* begin transaction */
+	ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+				&err);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "Unable to begin transaction: %s", err);
+		goto rollback;
+	}
+
+	/*
+	 * Check schema version again. This time, under an exclusive
+	 * transaction to guard against racing DB setup attempts
+	 */
+	ret = sqlite_query_schema_version();
+	if (ret != oldversion) {
+		if (ret == CLD_SQLITE_LATEST_SCHEMA_VERSION)
+			/* Someone else raced in and set it up */
+			ret = 0;
+		else
+			/* Something went wrong -- fail! */
+			ret = -EINVAL;
 		goto rollback;
 	}
 
+	/* Still at old version -- do conversion */
+
+	switch (oldversion) {
+	case 3:
+	case 2:
+		ret = sqlite_maindb_update_v3_to_v4();
+		break;
+	case 1:
+		ret = sqlite_maindb_update_v1v2_to_v4();
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	if (ret != SQLITE_OK)
+		goto rollback;
+
 	ret = snprintf(buf, sizeof(buf), "UPDATE parameters SET value = %d "
 			"WHERE key = \"version\";",
 			CLD_SQLITE_LATEST_SCHEMA_VERSION);
@@ -300,7 +370,7 @@  rollback:
  * transaction. On any error, rollback the transaction.
  */
 static int
-sqlite_maindb_init_v3(void)
+sqlite_maindb_init_v4(void)
 {
 	int ret, ret2;
 	char *err = NULL;
@@ -360,7 +430,7 @@  sqlite_maindb_init_v3(void)
 
 	/* create recovery table for current epoch */
 	ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" "
-				"(id BLOB PRIMARY KEY);",
+				"(id BLOB PRIMARY KEY, princhash BLOB);",
 				NULL, NULL, &err);
 	if (ret != SQLITE_OK) {
 		xlog(L_ERROR, "Unable to create recovery table "
@@ -675,7 +745,7 @@  sqlite_copy_cltrack_records(int *num_rec)
 		xlog(L_ERROR, "Unable to clear records from current epoch: %s", err);
 		goto rollback;
 	}
-	ret = snprintf(buf, sizeof(buf), "INSERT INTO \"rec-%016" PRIx64 "\" "
+	ret = snprintf(buf, sizeof(buf), "INSERT INTO \"rec-%016" PRIx64 "\" (id) "
 				"SELECT id FROM attached.clients;",
 				current_epoch);
 	if (ret < 0) {
@@ -763,6 +833,12 @@  sqlite_prepare_dbh(const char *topdir)
 		/* DB is already set up. Do nothing */
 		ret = 0;
 		break;
+	case 3:
+		/* Old DB -- update to new schema */
+		ret = sqlite_maindb_update_schema(3);
+		if (ret)
+			goto out_close;
+		break;
 	case 2:
 		/* Old DB -- update to new schema */
 		ret = sqlite_maindb_update_schema(2);
@@ -778,7 +854,7 @@  sqlite_prepare_dbh(const char *topdir)
 		break;
 	case 0:
 		/* Query failed -- try to set up new DB */
-		ret = sqlite_maindb_init_v3();
+		ret = sqlite_maindb_init_v4();
 		if (ret)
 			goto out_close;
 		break;
@@ -835,7 +911,62 @@  sqlite_insert_client(const unsigned char *clname, const size_t namelen)
 	int ret;
 	sqlite3_stmt *stmt = NULL;
 
-	ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" "
+	ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) "
+				"VALUES (?);", current_epoch);
+	if (ret < 0) {
+		xlog(L_ERROR, "sprintf failed!");
+		return ret;
+	} else if ((size_t)ret >= sizeof(buf)) {
+		xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+		return -EINVAL;
+	}
+
+	ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: insert statement prepare failed: %s",
+			__func__, sqlite3_errmsg(dbh));
+		return ret;
+	}
+
+	ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
+				SQLITE_STATIC);
+	if (ret != SQLITE_OK) {
+		xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+				sqlite3_errmsg(dbh));
+		goto out_err;
+	}
+
+	ret = sqlite3_step(stmt);
+	if (ret == SQLITE_DONE)
+		ret = SQLITE_OK;
+	else
+		xlog(L_ERROR, "%s: unexpected return code from insert: %s",
+				__func__, sqlite3_errmsg(dbh));
+
+out_err:
+	xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+	sqlite3_finalize(stmt);
+	return ret;
+}
+
+#if UPCALL_VERSION >= 2
+/*
+ * Create a client record including hash the kerberos principal
+ *
+ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
+ */
+int
+sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen,
+		const unsigned char *clprinchash, const size_t princhashlen)
+{
+	int ret;
+	sqlite3_stmt *stmt = NULL;
+
+	if (princhashlen > 0)
+		ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" "
+				"VALUES (?, ?);", current_epoch);
+	else
+		ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) "
 				"VALUES (?);", current_epoch);
 	if (ret < 0) {
 		xlog(L_ERROR, "sprintf failed!");
@@ -860,6 +991,16 @@  sqlite_insert_client(const unsigned char *clname, const size_t namelen)
 		goto out_err;
 	}
 
+	if (princhashlen > 0) {
+		ret = sqlite3_bind_blob(stmt, 2, (const void *)clprinchash, princhashlen,
+					SQLITE_STATIC);
+		if (ret != SQLITE_OK) {
+			xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+					sqlite3_errmsg(dbh));
+			goto out_err;
+		}
+	}
+
 	ret = sqlite3_step(stmt);
 	if (ret == SQLITE_DONE)
 		ret = SQLITE_OK;
@@ -872,6 +1013,14 @@  out_err:
 	sqlite3_finalize(stmt);
 	return ret;
 }
+#else
+int
+sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen,
+		const unsigned char *clprinchash, const size_t princhashlen)
+{
+	return -EINVAL;
+}
+#endif
 
 /* Remove a client record */
 int
@@ -1024,7 +1173,7 @@  sqlite_grace_start(void)
 		}
 
 		ret = snprintf(buf, sizeof(buf), "CREATE TABLE \"rec-%016" PRIx64 "\" "
-				"(id BLOB PRIMARY KEY);",
+				"(id BLOB PRIMARY KEY, princhash blob);",
 				tcur);
 		if (ret < 0) {
 			xlog(L_ERROR, "sprintf failed!");
@@ -1152,7 +1301,11 @@  sqlite_iterate_recovery(int (*cb)(struct cld_client *clnt), struct cld_client *c
 {
 	int ret;
 	sqlite3_stmt *stmt = NULL;
+#if UPCALL_VERSION >= 2
+	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
 	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
 
 	if (recovery_epoch == 0) {
 		xlog(D_GENERAL, "%s: not in grace!", __func__);
@@ -1177,9 +1330,21 @@  sqlite_iterate_recovery(int (*cb)(struct cld_client *clnt), struct cld_client *c
 	}
 
 	while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) {
+		memset(&cmsg->cm_u, 0, sizeof(cmsg->cm_u));
+#if UPCALL_VERSION >= 2
+		memcpy(&cmsg->cm_u.cm_clntinfo.cc_name.cn_id,
+			sqlite3_column_blob(stmt, 0), NFS4_OPAQUE_LIMIT);
+		cmsg->cm_u.cm_clntinfo.cc_name.cn_len = sqlite3_column_bytes(stmt, 0);
+		if (sqlite3_column_bytes(stmt, 1) > 0) {
+			memcpy(&cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data,
+				sqlite3_column_blob(stmt, 1), NFS4_OPAQUE_LIMIT);
+			cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len = sqlite3_column_bytes(stmt, 1);
+		}
+#else
 		memcpy(&cmsg->cm_u.cm_name.cn_id, sqlite3_column_blob(stmt, 0),
 			NFS4_OPAQUE_LIMIT);
 		cmsg->cm_u.cm_name.cn_len = sqlite3_column_bytes(stmt, 0);
+#endif
 		cb(clnt);
 	}
 	if (ret == SQLITE_DONE)
diff --git a/utils/nfsdcld/sqlite.h b/utils/nfsdcld/sqlite.h
index 7741382..0a26ad6 100644
--- a/utils/nfsdcld/sqlite.h
+++ b/utils/nfsdcld/sqlite.h
@@ -24,6 +24,8 @@  struct cld_client;
 
 int sqlite_prepare_dbh(const char *topdir);
 int sqlite_insert_client(const unsigned char *clname, const size_t namelen);
+int sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen,
+		const unsigned char *clprinchash, const size_t princhashlen);
 int sqlite_remove_client(const unsigned char *clname, const size_t namelen);
 int sqlite_check_client(const unsigned char *clname, const size_t namelen);
 int sqlite_grace_start(void);