diff mbox

[2/2] cifs: infrastructure for stashing passwords in keyring

Message ID 1282249633-27756-2-git-send-email-jaxbrigs@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Igor Druzhinin Aug. 19, 2010, 8:27 p.m. UTC
None
diff mbox

Patch

diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile
index adefa60..babab37 100644
--- a/fs/cifs/Makefile
+++ b/fs/cifs/Makefile
@@ -6,7 +6,7 @@  obj-$(CONFIG_CIFS) += cifs.o
 cifs-y := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o \
 	  link.o misc.o netmisc.o smbdes.o smbencrypt.o transport.o asn1.o \
 	  md4.o md5.o cifs_unicode.o nterr.o xattr.o cifsencrypt.o \
-	  readdir.o ioctl.o sess.o export.o cifsacl.o
+	  readdir.o ioctl.o sess.o export.o cifsacl.o cifscreds.o
 
 cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
 
diff --git a/fs/cifs/cifscreds.c b/fs/cifs/cifscreds.c
new file mode 100644
index 0000000..828631f
--- /dev/null
+++ b/fs/cifs/cifscreds.c
@@ -0,0 +1,192 @@ 
+/*
+ *   fs/cifs/cifscreds.c - CIFS credentinals stashing rountine
+ *
+ *   Copyright (c) 2010 Igor Druzhinin
+ *   Copyright (c) 2010 Jeff Layton
+ *   Author(s): Igor Druzhinin (jaxbrigs@gmail.com)
+ *		Jeff Layton (jlayton@samba.org)
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published
+ *   by the Free Software Foundation; either version 2.1 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This library is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ *   the GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/inet.h>
+#include <linux/key.h>
+#include <linux/keyctl.h>
+#include <linux/key-type.h>
+#include <keys/keyring-type.h>
+#include <keys/user-type.h>
+#include "cifsglob.h"
+#include "cifs_debug.h"
+#include "cifscreds.h"
+
+/* create key description from given credentials */
+static char *
+create_key_description(const char *addr, const char *user,
+			const char *domain, char *desc)
+{
+	char *str_end;
+	int str_len;
+
+	sprintf(desc, "%s:%s:%s:", CIFSCREDS_PROG_NAME, addr, user);
+
+	if (domain != NULL) {
+		str_end = desc + strlen(desc);
+		str_len = strlen(domain);
+		while (str_len--) {
+			*str_end = tolower(*domain++);
+			str_end++;
+		}
+		*str_end = '\0';
+	}
+
+	return desc;
+}
+
+/*
+ * searching for requested key in the uid or session specific keyring
+ * according to DEST_KEYRING macro. After all operations with key caller
+ * must do key_put()
+ */
+static struct key *
+cifs_key_search(const char *addr, const char *user, const char *domain)
+{
+	char *desc;
+	struct key *keyring, *key;
+	struct keyring_list *klist;
+	int loop;
+
+	switch (DEST_KEYRING) {
+	case KEY_SPEC_USER_KEYRING:
+		keyring = current_user()->uid_keyring;
+		break;
+
+	case KEY_SPEC_SESSION_KEYRING:
+		keyring = current_user()->session_keyring;
+		break;
+
+	default:
+		printk(KERN_WARNING "CIFS creds: unsupported keyring\n");
+		return NULL;
+	}
+	if (keyring == NULL) {
+		cFYI(1, "CIFS creds: keyring is not installed");
+		return NULL;
+	}
+
+	desc = kmalloc(INET6_ADDRSTRLEN + 200 + 256 + \
+			sizeof(CIFSCREDS_PROG_NAME) + 3, GFP_KERNEL);
+	if (desc == NULL) {
+		printk(KERN_WARNING "CIFS creds: no memory for description\n");
+		return NULL;
+	}
+
+	create_key_description(addr, user, domain, desc);
+	cFYI(1, "CIFS creds: key description: %s for uid:%d",
+		desc, current_uid());
+
+	klist = rcu_dereference_protected(keyring->payload.subscriptions,
+		rwsem_is_locked((struct rw_semaphore *)&keyring->sem));
+	if (klist) {
+		for (loop = 0; loop < klist->nkeys; loop++) {
+			key = klist->keys[loop];
+
+			if (key->type == &key_type_user &&
+				(!key->type->match ||
+				key->type->match(key, desc)) &&
+				!test_bit(KEY_FLAG_REVOKED, &key->flags)
+			)
+				goto key_search_found;
+		}
+	}
+	kfree(desc);
+	return NULL;
+
+key_search_found:
+	atomic_inc(&key->usage);
+	kfree(desc);
+	return key;
+}
+
+/* read and copy key data to kernel buffer */
+static long cifs_key_read(const struct key *key, char *buffer, size_t buflen)
+{
+	struct user_key_payload *upayload;
+	long ret;
+
+	upayload = rcu_dereference_protected(key->payload.data,
+		rwsem_is_locked(&((struct key *)key)->sem));
+	ret = upayload->datalen;
+
+	/* we can return the data as is */
+	if (buffer && buflen > 0) {
+		if (buflen > upayload->datalen)
+			buflen = upayload->datalen;
+
+		if (memcpy(buffer, upayload->data, buflen) == NULL)
+			ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+/**
+ * cifs_get_key_password - Reading password from the keyring
+ *		according to provided description.
+ * @addr: IP address of target server.
+ * @user: Name of the intrested user.
+ * @domain: Possible domain name. Can be NULL.
+ *
+ * Returns the pointer to password or NULL, and the caller is
+ * responsible for freeing it.
+ */
+char *
+cifs_get_key_password(const char *addr, const char *user, const char *domain)
+{
+	struct key *pass_key;
+	char *key_password = NULL;
+	long pass_len;
+
+	pass_key = cifs_key_search(addr, user, domain);
+	if (pass_key == NULL) {
+		cFYI(1, "CIFS creds: Appropriate key for %s not found",	addr);
+		return NULL;
+	}
+
+	pass_len = cifs_key_read(pass_key, NULL, 0);
+	if (pass_len == 0) {
+		cFYI(1, "CIFS creds: Password key for %s is empty", addr);
+		goto no_key_password;
+	}
+
+	key_password = kzalloc(pass_len, GFP_KERNEL);
+	if (key_password == NULL) {
+		printk(KERN_WARNING "CIFS creds: no memory for password\n");
+		goto no_key_password;
+	}
+
+	pass_len = cifs_key_read(pass_key, key_password, pass_len);
+	if (pass_len < 0) {
+		printk(KERN_WARNING "CIFS creds: unable to read key's "
+			"payload for %s\n", addr);
+		kzfree(key_password);
+		key_password = NULL;
+	}
+
+no_key_password:
+	key_put(pass_key);
+	return key_password;
+}
diff --git a/fs/cifs/cifscreds.h b/fs/cifs/cifscreds.h
new file mode 100644
index 0000000..466ecf9
--- /dev/null
+++ b/fs/cifs/cifscreds.h
@@ -0,0 +1,34 @@ 
+/*
+ *   fs/cifs/cifscreds.c - CIFS credentinals stashing rountine definition
+ *
+ *   Copyright (c) 2010 Igor Druzhinin
+ *   Copyright (c) 2010 Jeff Layton
+ *   Author(s): Igor Druzhinin (jaxbrigs@gmail.com)
+ *		Jeff Layton (jlayton@samba.org)
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published
+ *   by the Free Software Foundation; either version 2.1 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This library is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+ *   the GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef _CIFSCREDS_H
+#define _CIFSCREDS_H
+
+#include <linux/keyctl.h>
+
+#define CIFSCREDS_PROG_NAME "cifscreds"
+#define DEST_KEYRING KEY_SPEC_USER_KEYRING
+
+extern char *
+cifs_get_key_password(const char *addr,	const char *user, const char *domain);
+
+#endif /* _CIFSCREDS_H */
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 95c2ea6..ef4371e 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -49,6 +49,7 @@ 
 #include "rfc1002pdu.h"
 #include "cn_cifs.h"
 #include "fscache.h"
+#include "cifscreds.h"
 
 #define CIFS_PORT 445
 #define RFC1001_PORT 139
@@ -2568,6 +2569,7 @@  cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
 	struct TCP_Server_Info *srvTcp;
 	char   *full_path;
 	char *mount_data = mount_data_global;
+	char *key_password;
 #ifdef CONFIG_CIFS_DFS_UPCALL
 	struct dfs_info3_param *referrals = NULL;
 	unsigned int num_referrals = 0;
@@ -2622,6 +2624,17 @@  try_mount_again:
 	}
 	cifs_sb->local_nls = volume_info->local_nls;
 
+	/* search for key with password and read its payload */
+	if (volume_info->password == NULL &&
+		!(volume_info->secFlg & CIFSSEC_MAY_KRB5)
+	) {
+		key_password = cifs_get_key_password(volume_info->UNCip,
+						volume_info->username,
+						volume_info->domainname);
+		if (key_password != NULL)
+			volume_info->password = key_password;
+	}
+
 	/* get a reference to a tcp session */
 	srvTcp = cifs_get_tcp_session(volume_info);
 	if (IS_ERR(srvTcp)) {