[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, §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.
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