[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, §ype,
> + &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