[linux-cifs-client] [PATCH 2/2] try#2 cifs.spnego helper in samba: helper source code

Q (Igor Mammedov) qwerty0987654321 at mail.ru
Thu Nov 15 18:33:52 GMT 2007


Signed-off-by: Igor Mammedov <niallain at gmail.com>
---
  source/client/cifs.spnego.c |  302 
+++++++++++++++++++++++++++++++++++++++++++
  source/client/cifs_spnego.h |   46 +++++++
  2 files changed, 348 insertions(+), 0 deletions(-)
  create mode 100644 source/client/cifs.spnego.c
  create mode 100644 source/client/cifs_spnego.h

diff --git a/source/client/cifs.spnego.c b/source/client/cifs.spnego.c
new file mode 100644
index 0000000..d70cb3f
--- /dev/null
+++ b/source/client/cifs.spnego.c
@@ -0,0 +1,302 @@
+
+/*
+* CIFS SPNEGO user-space helper. Used by /sbin/request-key for handling
+* cifs upcall for kerberos authorization of access to share.
+* You should have keyutils installed and add following line to
+* /etc/request-key.conf file
+
+create cifs.spnego * * /usr/local/sbin/cifs.spnego [-c] %k
+
+*
+* Author(s): Igor Mammedov (niallain at gmail.com)
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+* This program 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 General Public License for more details.
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "includes.h"
+#include <keyutils.h>
+
+#include "cifs_spnego.h"
+
+static const char *prog = "cifs.spnego";
+typedef enum _secType {
+	KRB5,
+	MS_KRB5
+} secType_t;
+
+/*
+ * Prepares AP-REQ data for mechToken and gets session key
+ * Uses credentials from cache. It will not ask for password
+ * you should receive credentials for yuor name manually using
+ * kinit or whatever you wish.
+ *
+ * in:
+ * 	oid -		string with OID/ Could be OID_KERBEROS5
+ * 			or OID_KERBEROS5_OLD
+ * 	principal -	Service name.
+ * 			Could be "cifs/FQDN" for KRB5 OID
+ * 			or for MS_KRB5 OID style server principal
+ * 			like "pdc$@YOUR.REALM.NAME"
+ *
+ * out:
+ * 	secblob -	pointer for spnego wrapped AP-REQ data to be stored
+ * 	sess_key-	pointer for SessionKey data to be stored
+ *
+ * ret: 0 - success, others - failure
+*/
+int handle_krb5_mech(const char *oid, const char *principal,
+		     DATA_BLOB * secblob, DATA_BLOB * sess_key)
+{
+	int retval;
+	DATA_BLOB tkt, tkt_wrapped;
+	const char *krb_mechs[] = { OID_KERBEROS5, NULL };
+
+	/* get a kerberos ticket for the service and extract the session key */
+	retval = cli_krb5_get_ticket(principal, 0,
+				     &tkt, sess_key, 0, NULL, NULL);
+
+	if (retval)
+		return retval;
+
+	/* wrap that up in a nice GSS-API wrapping */
+	tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ);
+
+	/* and wrap that in a shiny SPNEGO wrapper */
+	*secblob = gen_negTokenInit(OID_KERBEROS5, tkt_wrapped);
+
+	data_blob_free(&tkt_wrapped);
+	data_blob_free(&tkt);
+	return retval;
+}
+
+#define DKD_HAVE_HOSTNAME	1
+#define DKD_HAVE_VERSION	2
+#define DKD_HAVE_SEC		4
+#define DKD_HAVE_IPV4		8
+#define DKD_HAVE_IPV6		16
+#define DKD_HAVE_UID		32
+#define DKD_MUSTHAVE_SET (DKD_HAVE_HOSTNAME|DKD_HAVE_VERSION|DKD_HAVE_SEC)
+
+int decode_key_description(const char *desc, int *ver, secType_t * sec,
+			   char **hostname, uid_t * uid)
+{
+	int retval = 0;
+	char *pos;
+	const char *tkn = desc;
+	int skip;
+
+	do {
+		pos = index(tkn, ';');
+		if (strncmp(tkn, "host=", 5) == 0) {
+			int len;
+
+			if (pos == NULL) {
+				len = strlen(tkn);
+			} else {
+				len = pos - tkn;
+			}
+			len -= 5;
+			if (*hostname)
+				free(*hostname);
+			*hostname = malloc(len + 1);
+			strncpy(*hostname, tkn + 5, len);
+			(*hostname)[len] = '\0';
+			retval |= DKD_HAVE_HOSTNAME;
+		} else if (strncmp(tkn, "ipv4=", 5) == 0) {
+			/* BB: do we need it if we have hostname already? */
+		} else if (strncmp(tkn, "ipv6=", 5) == 0) {
+			/* BB: do we need it if we have hostname already? */
+		} else if (strncmp(tkn, "sec=", 4) == 0) {
+			if (strncmp(tkn + 4, "krb5", 4) == 0) {
+				retval |= DKD_HAVE_SEC;
+				*sec = KRB5;
+			}
+		} else if (strncmp(tkn, "uid=", 4) == 0) {
+			errno = 0;
+			*uid = strtol(tkn + 4, NULL, 16);
+			if (errno != 0) {
+				syslog(LOG_WARNING, "Invalid uid format: %s",
+				       strerror(errno));
+				return 1;
+			} else {
+				retval |= DKD_HAVE_UID;
+			}
+		} else if (strncmp(tkn, "ver=", 4) == 0) {	/* if version */
+			errno = 0;
+			*ver = strtol(tkn + 4, NULL, 16);
+			if (errno != 0) {
+				syslog(LOG_WARNING,
+				       "Invalid version format: %s",
+				       strerror(errno));
+				return 1;
+			} else {
+				retval |= DKD_HAVE_VERSION;
+			}
+		}
+		if (pos == NULL)
+			break;
+		tkn = pos + 1;
+	} while (tkn);
+	return retval;
+}
+
+int main(const int argc, char *const argv[])
+{
+	struct cifs_spnego_msg *keydata;
+	DATA_BLOB secblob = data_blob_null;
+	DATA_BLOB sess_key = data_blob_null;
+	secType_t sectype;
+	key_serial_t key;
+	size_t datalen;
+	long rc = 1;
+	uid_t uid;
+	int kernel_upcall_version;
+	int c, use_cifs_service_prefix = 0;
+	char *buf, *hostname = NULL;
+
+	openlog(prog, 0, LOG_DAEMON);
+	if (argc < 1) {
+		syslog(LOG_WARNING, "Usage: %s [-c] key_serial", prog);
+		goto out;
+	}
+
+	while ((c = getopt(argc, argv, "c")) != -1) {
+		switch (c) {
+		case 'c':{
+				use_cifs_service_prefix = 1;
+				break;
+			}
+		default:{
+				syslog(LOG_WARNING, "unknow option: %c", c);
+				goto out;
+			}
+		}
+	}
+	/* get key and keyring values */
+	errno = 0;
+	key = strtol(argv[optind], NULL, 10);
+	if (errno != 0) {
+		syslog(LOG_WARNING, "Invalid key format: %s", strerror(errno));
+		goto out;
+	}
+
+	rc = keyctl_describe_alloc(key, &buf);
+	if (rc == -1) {
+		syslog(LOG_WARNING, "keyctl_describe_alloc failed: %s",
+		       strerror(errno));
+		rc = 1;
+		goto out;
+	}
+
+	rc = decode_key_description(buf, &kernel_upcall_version, &sectype,
+				    &hostname, &uid);
+	if ((rc & DKD_MUSTHAVE_SET) != DKD_MUSTHAVE_SET) {
+		syslog(LOG_WARNING,
+		       "unable to get from description necessary params");
+		rc = 1;
+		free(buf);
+		goto out;
+	}
+	free(buf);
+
+	if (kernel_upcall_version != CIFS_SPNEGO_UPCALL_VERSION) {
+		syslog(LOG_WARNING,
+		       "incompatible kernel upcall version: 0x%x",
+		       kernel_upcall_version);
+		rc = 1;
+		goto out;
+	}
+
+	if (rc & DKD_HAVE_UID) {
+		rc = setuid(uid);
+		if (rc == -1) {
+			syslog(LOG_WARNING, "setuid: %s", strerror(errno));
+			goto out;
+		}
+	}
+
+	/* BB: someday upcall SPNEGO blob could be checked here to decide
+	 * what mech to use */
+
+	// do mech specific authorization
+	switch (sectype) {
+	case KRB5:{
+			char *princ;
+			size_t len;
+
+			/* for "cifs/" service name + terminating 0 */
+			len = strlen(hostname) + 6;
+			princ = malloc(len);
+			if (!princ) {
+				rc = 1;
+				break;
+			}
+			if (use_cifs_service_prefix) {
+				strncpy(princ, "cifs/", len);
+			} else {
+				strncpy(princ, "host/", len);
+			}
+			strncpy(princ + 5, hostname, len - 5);
+
+			rc = handle_krb5_mech(OID_KERBEROS5, princ,
+					      &secblob, &sess_key);
+			free(princ);
+			break;
+		}
+	default:{
+			syslog(LOG_WARNING, "sectype: %d is not implemented",
+			       sectype);
+			rc = 1;
+			break;
+		}
+	}
+
+	if (rc) {
+		goto out;
+	}
+
+	/* pack SecurityBLob and SessionKey into downcall packet */
+	datalen =
+	    sizeof(struct cifs_spnego_msg) + secblob.length + sess_key.length;
+	keydata = malloc(datalen);
+	if (!keydata) {
+		rc = 1;
+		goto out;
+	}
+	keydata->version = CIFS_SPNEGO_UPCALL_VERSION;
+	keydata->flags = 0;
+	keydata->sesskey_len = sess_key.length;
+	keydata->secblob_len = secblob.length;
+	memcpy(&(keydata->data), sess_key.data, sess_key.length);
+	memcpy(&(keydata->data) + keydata->sesskey_len,
+	       secblob.data, secblob.length);
+
+	/* setup key */
+	rc = keyctl_instantiate(key, keydata, datalen, 0);
+	if (rc == -1) {
+		syslog(LOG_WARNING, "keyctl_instantiate: %s", strerror(errno));
+		goto out;
+	}
+
+	/* BB: maybe we need use timeout for key: for example no more then
+	 * ticket lifietime? */
+	/* keyctl_set_timeout( key, 60); */
+      out:
+	data_blob_free(&secblob);
+	data_blob_free(&sess_key);
+	if (hostname)
+		free(hostname);
+	if (keydata)
+		free(keydata);
+	return rc;
+}
diff --git a/source/client/cifs_spnego.h b/source/client/cifs_spnego.h
new file mode 100644
index 0000000..13909dd
--- /dev/null
+++ b/source/client/cifs_spnego.h
@@ -0,0 +1,46 @@
+/*
+ *   fs/cifs/cifs_spnego.h -- SPNEGO upcall management for CIFS
+ *
+ *   Copyright (c) 2007 Red Hat, Inc.
+ *   Author(s): Jeff Layton (jlayton at redhat.com)
+ *              Steve French (sfrench at us.ibm.com)
+ *
+ *   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 _CIFS_SPNEGO_H
+#define _CIFS_SPNEGO_H
+
+#define CIFS_SPNEGO_UPCALL_VERSION 1
+
+/*
+ * The version field should always be set to CIFS_SPNEGO_UPCALL_VERSION.
+ * The flags field is for future use. The request-key callout should set
+ * sesskey_len and secblob_len, and then concatenate the SessKey+SecBlob
+ * and stuff it in the data field.
+ */
+struct cifs_spnego_msg {
+	uint32_t version;
+	uint32_t flags;
+	uint32_t sesskey_len;
+	uint32_t secblob_len;
+	uint8_t data[1];
+};
+
+#ifdef __KERNEL__
+extern struct key_type cifs_spnego_key_type;
+#endif				/* KERNEL */
+
+#endif				/* _CIFS_SPNEGO_H */
-- 1.5.2.1

------------------------

Best regards,

-------------------------
Igor Mammedov,
niallain "at" gmail.com






More information about the linux-cifs-client mailing list