[linux-cifs-client] [PATCH] cifs.upcall: do a brute-force search for KRB5 credcache
Jeff Layton
jlayton at redhat.com
Fri Sep 4 04:33:54 MDT 2009
On Sat, 29 Aug 2009 06:48:19 -0400
Jeff Layton <jlayton at redhat.com> wrote:
> 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;
>
Committed to samba master branch.
--
Jeff Layton <jlayton at redhat.com>
More information about the linux-cifs-client
mailing list