[linux-cifs-client] [PATCH] cifs.upcall: do a brute-force search for KRB5 credcache

Jeff Layton jlayton at redhat.com
Sat Aug 29 04:48:19 MDT 2009


A few weeks ago, I added some code to cifs.upcall to take the pid sent
by the kernel and use that to get the value of the $KRB5CCNAME
environment var for the process. That works fine on the initial mount,
but could be problematic on reconnect.

There's no guarantee on a reconnect that the process that initiates the
upcall will have $KRB5CCNAME pointed at the correct credcache. Because
of this, the current scheme isn't going to be reliable enough and we
need to use something different.

This patch adds a scheme that's very similar to the one used by rpc.gssd
in nfs-utils. It basically searches the credcache dir (currently
hardcoded to /tmp) for a valid credcache for the given uid. If it finds
one then it uses that as the credentials cache. If it finds more than
one, it uses the one with the latest TGT expiration.

Signed-off-by: Jeff Layton <jlayton at redhat.com>
---
 client/cifs.upcall.c |  186 +++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 139 insertions(+), 47 deletions(-)

diff --git a/client/cifs.upcall.c b/client/cifs.upcall.c
index 1645322..71e60c6 100644
--- a/client/cifs.upcall.c
+++ b/client/cifs.upcall.c
@@ -31,6 +31,11 @@ create dns_resolver * * /usr/local/sbin/cifs.upcall %k
 
 #include "cifs_spnego.h"
 
+#define	CIFS_DEFAULT_KRB5_DIR		"/tmp"
+#define	CIFS_DEFAULT_KRB5_PREFIX	"krb5cc_"
+
+#define	MAX_CCNAME_LEN			PATH_MAX + 5
+
 const char *CIFSSPNEGO_VERSION = "1.3";
 static const char *prog = "cifs.upcall";
 typedef enum _sectype {
@@ -39,60 +44,148 @@ typedef enum _sectype {
 	MS_KRB5
 } sectype_t;
 
-/*
- * given a process ID, get the value of the KRB5CCNAME environment variable
- * in the context of that process. On error, just return NULL.
- */
-static char *
-get_krb5_ccname(pid_t pid)
+static inline int
+k5_data_equal(krb5_data d1, krb5_data d2, unsigned int length)
 {
-	int fd;
-	ssize_t len, left;
+	if (!length)
+		length = d1.length;
 
-	/*
-	 * FIXME: sysconf for ARG_MAX instead? Kernel seems to be limited to a
-	 * page however, so it may not matter.
-	 */
-	char buf[4096];
-	char *p, *value = NULL;
-	
-	buf[4095] = '\0';
-	snprintf(buf, 4095, "/proc/%d/environ", pid);
-	fd = open(buf, O_RDONLY);
-	if (fd < 0) {
-		syslog(LOG_DEBUG, "%s: unable to open %s: %d", __func__, buf,
-			errno);
-		return NULL;
+	return (d1.length == length &&
+		d1.length == d2.length &&
+		memcmp(d1.data, d2.data, length) == 0);
+
+}
+
+/* does the ccache have a valid TGT? */
+static time_t
+get_tgt_time(const char *ccname) {
+	krb5_context context;
+	krb5_ccache ccache;
+	krb5_cc_cursor cur;
+	krb5_creds creds;
+	krb5_principal principal;
+	krb5_data tgt = { .data =	"krbtgt",
+			  .length =	6 };
+	time_t credtime = 0;
+
+	if (krb5_init_context(&context)) {
+		syslog(LOG_DEBUG, "%s: unable to init krb5 context", __func__);
+		return 0;
 	}
 
-	/* FIXME: don't assume that we get it all in the first read? */
-	len = read(fd, buf, 4096);
-	close(fd);
-	if (len < 0) {
-		syslog(LOG_DEBUG, "%s: unable to read from /proc/%d/environ: "
-				  "%d", __func__, pid, errno);
+	if (krb5_cc_resolve(context, ccname, &ccache)) {
+		syslog(LOG_DEBUG, "%s: unable to resolve krb5 cache", __func__);
+		goto err_cache;
+	}
+
+	if (krb5_cc_set_flags(context, ccache, 0)) {
+		syslog(LOG_DEBUG, "%s: unable to set flags", __func__);
+		goto err_cache;
+	}
+
+	if (krb5_cc_get_principal(context, ccache, &principal)) {
+		syslog(LOG_DEBUG, "%s: unable to get principal", __func__);
+		goto err_princ;
+	}
+
+	if (krb5_cc_start_seq_get(context, ccache, &cur)) {
+		syslog(LOG_DEBUG, "%s: unable to seq start", __func__);
+		goto err_ccstart;
+	}
+
+	while (!credtime && !krb5_cc_next_cred(context, ccache, &cur, &creds)) {
+		if (k5_data_equal(creds.server->realm, principal->realm, 0) &&
+		    k5_data_equal(creds.server->data[0], tgt, tgt.length) &&
+		    k5_data_equal(creds.server->data[1], principal->realm, 0) &&
+		    creds.times.endtime > time(NULL))
+			credtime = creds.times.endtime;
+                krb5_free_cred_contents(context, &creds);
+        }
+        krb5_cc_end_seq_get(context, ccache, &cur);
+
+err_ccstart:
+	krb5_free_principal(context, principal);
+err_princ:
+	krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
+	krb5_cc_close(context, ccache);
+err_cache:
+	krb5_free_context(context);
+	return credtime;
+}
+
+static int
+krb5cc_filter(const struct dirent *dirent)
+{
+	if (strstr(dirent->d_name, CIFS_DEFAULT_KRB5_PREFIX))
+		return 1;
+	else
+		return 0;
+}
+
+/* search for a credcache that looks like a likely candidate */
+static char *
+find_krb5_cc(const char *dirname, uid_t uid)
+{
+	struct dirent **namelist;
+	struct stat sbuf;
+	char ccname[MAX_CCNAME_LEN], *credpath, *best_cache = NULL;
+	int i, n;
+	time_t cred_time, best_time = 0;
+
+	n = scandir(dirname, &namelist, krb5cc_filter, NULL);
+	if (n < 0) {
+		syslog(LOG_DEBUG, "%s: scandir error on directory '%s': %s",
+				  __func__, dirname, strerror(errno));
 		return NULL;
 	}
 
-	left = len;
-	p = buf;
+	for (i = 0; i < n; i++) {
+		snprintf(ccname, sizeof(ccname), "FILE:%s/%s", dirname,
+			 namelist[i]->d_name);
+		credpath = ccname + 5;
+		syslog(LOG_DEBUG, "%s: considering %s", __func__, credpath);
 
-	/* can't have valid KRB5CCNAME if there are < 13 bytes left */
-	while (left > 12) {
-		if (strncmp("KRB5CCNAME=", p, 11)) {
-			p += strnlen(p, left);
-			++p;
-			left = buf + len - p;
+		if (lstat(credpath, &sbuf)) {
+			syslog(LOG_DEBUG, "%s: stat error on '%s': %s",
+					  __func__, credpath, strerror(errno));
+			free(namelist[i]);
 			continue;
 		}
-		p += 11;
-		left -= 11;
-		value = SMB_STRNDUP(p, left);
-		break;
+		if (sbuf.st_uid != uid) {
+			syslog(LOG_DEBUG, "%s: %s is owned by %u, not %u",
+					__func__, credpath, sbuf.st_uid, uid);
+			free(namelist[i]);
+			continue;
+		}
+		if (!S_ISREG(sbuf.st_mode)) {
+			syslog(LOG_DEBUG, "%s: %s is not a regular file",
+					__func__, credpath);
+			free(namelist[i]);
+			continue;
+		}
+		if (!(cred_time = get_tgt_time(ccname))) {
+			syslog(LOG_DEBUG, "%s: %s is not a valid credcache.",
+					__func__, ccname);
+			free(namelist[i]);
+			continue;
+		}
+
+		if (cred_time <= best_time) {
+			syslog(LOG_DEBUG, "%s: %s expires sooner than current "
+					  "best.", __func__, ccname);
+			free(namelist[i]);
+			continue;
+		}
+
+		syslog(LOG_DEBUG, "%s: %s is valid ccache", __func__, ccname);
+		free(best_cache);
+		best_cache = SMB_STRNDUP(ccname, MAX_CCNAME_LEN);
+		best_time = cred_time;
+		free(namelist[i]);
 	}
-	syslog(LOG_DEBUG, "%s: KRB5CCNAME=%s", __func__,
-				value ? value : "(null)");
-	return value;
+	free(namelist);
+
+	return best_cache;
 }
 
 /*
@@ -453,10 +546,9 @@ int main(const int argc, char *const argv[])
 			syslog(LOG_ERR, "setuid: %s", strerror(errno));
 			goto out;
 		}
-	}
 
-	if (have & DKD_HAVE_PID)
-		ccname = get_krb5_ccname(arg.pid);
+		ccname = find_krb5_cc(CIFS_DEFAULT_KRB5_DIR, arg.uid);
+	}
 
 	host = arg.hostname;
 
-- 
1.6.0.6



More information about the linux-cifs-client mailing list