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

Jeff Layton jlayton at redhat.com
Tue Nov 13 13:09:36 GMT 2007


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.

> +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.

> +	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?

> +
> +			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


-- 
Jeff Layton <jlayton at redhat.com>


More information about the linux-cifs-client mailing list