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

Q (Igor Mammedov) qwerty0987654321 at mail.ru
Wed Nov 14 16:34:49 GMT 2007


Jeff Layton wrote:
> Igor. Thanks for doing this work. I've tested the program and it works
> great. I think it's definitely a good start and is certainly sufficient
> for simple domain configurations (no cross-realm trust, consistent DNS,
> etc).
> 
> Comments follow interspersed within the code (mostly minor points). If
> you post a respin, you may want to CC the samba-technical list for the
> benefit of samba developers who don't follow the linux-cifs-client list
> closely:
> 
>> >From 1adaf17dacea84027b0e77338d05cf4fdec52e98 Mon Sep 17 00:00:00 2001  
>> From: Igor Mammedov <niallain at gmail.com>
>> Date: Wed, 7 Nov 2007 12:54:21 +0300
>> Subject: [PATCH] helper source for handling cifs kernel module upcall for kerberos authorization
>>
>> Signed-off-by: Igor Mammedov <niallain at gmail.com>
>> ---
>>  source/client/cifs.spnego.c |  291 +++++++++++++++++++++++++++++++++++++++++++
>>  1 files changed, 291 insertions(+), 0 deletions(-)
>>  create mode 100644 source/client/cifs.spnego.c
>>
>> diff --git a/source/client/cifs.spnego.c b/source/client/cifs.spnego.c
>> new file mode 100644
>> index 0000000..65aa9da
>> --- /dev/null
>> +++ b/source/client/cifs.spnego.c
>> @@ -0,0 +1,291 @@
>> +/*
>> +* 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 %k %d
>> +
>> +*
>> +* 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>
>> +
>> +/* if we dont know where to take cifs_spnego.h
>> + * use this for a time being */
>> +#ifndef _CIFS_SPNEGO_H
>> +
>> +#define CIFS_SPNEGO_UPCALL_VERSION 1
>> +struct cifs_spnego_msg {
>> +	uint32_t        version;
>> +	uint32_t        flags;
>> +	uint32_t        sesskey_len;
>> +	uint32_t        secblob_len;
>> +	uint8_t         data[1];
>> +};
>> +
>> +#endif
>> +
> 
> Instead of copying this info into the file, it might be better to copy
> cifs_spnego.h from the kernel into the client/ dir and include it. I
> made cifs_spnego.h with that intention, so it should be safe and should
> make it easier to keep common kernel/userspace info in sync in the
> future. If we need to make changes we can just copy the file between
> the two source trees and we'll know that they're consistent.

Ok. Will do.


>> +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 errno;
>> +
>> +	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,NULL,16);
>> +			if ( errno != 0 ) {
>> +				syslog(LOG_WARNING,"Invalid uid format: %s",
>> +						strerror(errno) );
>> +				return 1;
>> +			} else {
>> +				retval |= DKD_HAVE_UID;
>> +			}
>> +		} else if (tkn == desc) { /* if version */
>> +			errno = 0;
>> +			*ver = strtol(tkn,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, const char** 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;
>> +	int kernel_upcall_version;
>> +	char* hostname = NULL;
>> +	uid_t uid;
>> +
>> +	openlog( prog, 0, LOG_DAEMON);
>> +	if ( argc < 2 ) {
>> +		syslog(LOG_WARNING,"Usage: %s key_serial upcall_description",
>> +				prog);
>> +		goto out;
>> +	}
>> +
>> +	/* get key and keyring values */
>> +	errno = 0;
>> +	key = strtol(argv[1],NULL,10);
>> +	if ( errno != 0 ) {
>> +		syslog(LOG_WARNING,"Invalid key format: %s",
>> +			       	strerror(errno) );
>> +		goto out;
>> +	}
>> +
>> +	rc = keyctl_assume_authority(key);
>> +	if( rc == -1 ) {
>> +		syslog(LOG_WARNING,"keyctl_assume_authority: %s",
>> +				strerror(errno) );
>> +		goto out;
>> +	}
>> +
> 
> I don't think this is necessary. I thought we only needed to call
> keyctl_assume_authority if we needed to access the callout_data
> from the request_key() call.
> 
>> +	rc = decode_key_description( argv[2], &kernel_upcall_version, &sectype,
>> +			&hostname, &uid);
> 
> Rather than depending on argv[2], I'd suggest a call to
> keyctl_describe_alloc() to get the string. That makes this less reliant
> on someone setting up request-key.conf correctly.

Yes, look like we don't need it.

> 
>> +	if ( (rc&DKD_MUSTHAVE_SET)!=DKD_MUSTHAVE_SET ) {
>> +		syslog(LOG_WARNING,
>> +			"unable to get from description necessary params");
>> +		rc = 1;
>> +		goto out;
>> +	}
>> +
>> +	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;
>> +			}
>> +			strncpy(princ,"cifs/",len);
>> +			strncpy(princ+5,hostname,len-5);
> 
> Idle speculation here -- do we need to allow for service principals
> that don't match cifs/hostname at REALM? If I'm looking at packet captures
> correctly, it looks like windows boxes use host/hostname at REALM. Perhaps
> we should consider falling back to that if we can't get a key for
> cifs/hostname?

Could be done but do we really need it?
I've tested in within w2003k environment and it happy with  "cifs/".
Can change to "host/" if somebody wish it.

> 
>> +
>> +			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 ) {
>> +		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;
>> +}
>> +
>> +
>> -- 
>> 1.5.2.1
> 
> 


-- 

Best regards,

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






More information about the linux-cifs-client mailing list