2001-11-24 17:16:41 +03:00
/*
2002-01-30 09:08:46 +03:00
Unix SMB / CIFS implementation .
2001-11-24 17:16:41 +03:00
kerberos utility library
Copyright ( C ) Andrew Tridgell 2001
2001-12-20 06:54:52 +03:00
Copyright ( C ) Remus Koos 2001
2005-01-14 22:26:13 +03:00
Copyright ( C ) Nalin Dahyabhai < nalin @ redhat . com > 2004.
2004-10-30 02:38:10 +04:00
Copyright ( C ) Jeremy Allison 2004.
2006-07-11 22:45:22 +04:00
Copyright ( C ) Gerald Carter 2006.
2004-10-30 02:38:10 +04:00
2001-11-24 17:16:41 +03:00
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
2007-07-09 23:25:36 +04:00
the Free Software Foundation ; either version 3 of the License , or
2001-11-24 17:16:41 +03:00
( 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
2007-07-10 04:52:41 +04:00
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2001-11-24 17:16:41 +03:00
*/
# include "includes.h"
2009-11-27 17:52:57 +03:00
# include "smb_krb5.h"
2010-05-28 04:20:02 +04:00
# include "../librpc/gen_ndr/ndr_misc.h"
2010-07-02 02:32:52 +04:00
# include "libads/kerberos_proto.h"
2010-08-05 04:25:37 +04:00
# include "secrets.h"
2001-11-24 17:16:41 +03:00
# ifdef HAVE_KRB5
2008-01-17 00:21:46 +03:00
# define DEFAULT_KRB5_PORT 88
2004-10-30 02:38:10 +04:00
# define LIBADS_CCACHE_NAME "MEMORY:libads"
2002-07-15 14:35:28 +04:00
/*
we use a prompter to avoid a crash bug in the kerberos libs when
dealing with empty passwords
this prompter is just a string copy . . .
*/
static krb5_error_code
kerb_prompter ( krb5_context ctx , void * data ,
const char * name ,
const char * banner ,
int num_prompts ,
krb5_prompt prompts [ ] )
{
if ( num_prompts = = 0 ) return 0 ;
2004-10-30 02:38:10 +04:00
memset ( prompts [ 0 ] . reply - > data , ' \0 ' , prompts [ 0 ] . reply - > length ) ;
2002-07-15 14:35:28 +04:00
if ( prompts [ 0 ] . reply - > length > 0 ) {
2002-10-24 05:05:30 +04:00
if ( data ) {
2009-11-06 12:25:53 +03:00
strncpy ( ( char * ) prompts [ 0 ] . reply - > data , ( const char * ) data ,
2006-08-01 00:51:55 +04:00
prompts [ 0 ] . reply - > length - 1 ) ;
2009-11-06 12:25:53 +03:00
prompts [ 0 ] . reply - > length = strlen ( ( const char * ) prompts [ 0 ] . reply - > data ) ;
2002-10-24 05:05:30 +04:00
} else {
prompts [ 0 ] . reply - > length = 0 ;
}
2002-07-15 14:35:28 +04:00
}
return 0 ;
}
2007-10-19 04:40:25 +04:00
static bool smb_krb5_get_ntstatus_from_krb5_error ( krb5_error * error ,
2007-05-04 13:55:40 +04:00
NTSTATUS * nt_status )
{
DATA_BLOB edata ;
DATA_BLOB unwrapped_edata ;
TALLOC_CTX * mem_ctx ;
2009-02-05 04:08:40 +03:00
struct KRB5_EDATA_NTSTATUS parsed_edata ;
enum ndr_err_code ndr_err ;
2007-05-04 13:55:40 +04:00
# ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
edata = data_blob ( error - > e_data - > data , error - > e_data - > length ) ;
# else
edata = data_blob ( error - > e_data . data , error - > e_data . length ) ;
# endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
# ifdef DEVELOPER
dump_data ( 10 , edata . data , edata . length ) ;
# endif /* DEVELOPER */
mem_ctx = talloc_init ( " smb_krb5_get_ntstatus_from_krb5_error " ) ;
if ( mem_ctx = = NULL ) {
data_blob_free ( & edata ) ;
return False ;
}
if ( ! unwrap_edata_ntstatus ( mem_ctx , & edata , & unwrapped_edata ) ) {
data_blob_free ( & edata ) ;
TALLOC_FREE ( mem_ctx ) ;
return False ;
}
data_blob_free ( & edata ) ;
2010-05-10 02:42:06 +04:00
ndr_err = ndr_pull_struct_blob_all ( & unwrapped_edata , mem_ctx ,
& parsed_edata , ( ndr_pull_flags_fn_t ) ndr_pull_KRB5_EDATA_NTSTATUS ) ;
2009-02-05 04:08:40 +03:00
if ( ! NDR_ERR_CODE_IS_SUCCESS ( ndr_err ) ) {
2007-05-04 13:55:40 +04:00
data_blob_free ( & unwrapped_edata ) ;
TALLOC_FREE ( mem_ctx ) ;
return False ;
}
data_blob_free ( & unwrapped_edata ) ;
if ( nt_status ) {
* nt_status = parsed_edata . ntstatus ;
}
TALLOC_FREE ( mem_ctx ) ;
return True ;
}
2007-10-19 04:40:25 +04:00
static bool smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt ( krb5_context ctx ,
2007-05-04 13:55:40 +04:00
krb5_get_init_creds_opt * opt ,
NTSTATUS * nt_status )
{
2007-10-19 04:40:25 +04:00
bool ret = False ;
2007-05-04 13:55:40 +04:00
krb5_error * error = NULL ;
# ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR
ret = krb5_get_init_creds_opt_get_error ( ctx , opt , & error ) ;
if ( ret ) {
DEBUG ( 1 , ( " krb5_get_init_creds_opt_get_error gave: %s \n " ,
error_message ( ret ) ) ) ;
return False ;
}
# endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR */
if ( ! error ) {
DEBUG ( 1 , ( " no krb5_error \n " ) ) ;
return False ;
}
# ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
if ( ! error - > e_data ) {
# else
if ( error - > e_data . data = = NULL ) {
# endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
DEBUG ( 1 , ( " no edata in krb5_error \n " ) ) ;
krb5_free_error ( ctx , error ) ;
return False ;
}
ret = smb_krb5_get_ntstatus_from_krb5_error ( error , nt_status ) ;
krb5_free_error ( ctx , error ) ;
return ret ;
}
2001-12-06 08:41:53 +03:00
/*
2004-10-30 02:38:10 +04:00
simulate a kinit , putting the tgt in the given cache location . If cache_name = = NULL
place in default cache location .
2001-12-06 08:41:53 +03:00
remus @ snapserver . com
*/
2006-03-20 22:05:44 +03:00
int kerberos_kinit_password_ext ( const char * principal ,
2004-10-30 02:38:10 +04:00
const char * password ,
int time_offset ,
time_t * expire_time ,
2006-02-04 01:19:41 +03:00
time_t * renew_till_time ,
const char * cache_name ,
2007-10-19 04:40:25 +04:00
bool request_pac ,
bool add_netbios_addr ,
2007-05-04 14:21:39 +04:00
time_t renewable_time ,
NTSTATUS * ntstatus )
2001-12-06 08:41:53 +03:00
{
2004-05-07 06:48:03 +04:00
krb5_context ctx = NULL ;
2001-12-06 08:41:53 +03:00
krb5_error_code code = 0 ;
2004-05-07 06:48:03 +04:00
krb5_ccache cc = NULL ;
2007-05-04 13:46:17 +04:00
krb5_principal me = NULL ;
2001-12-06 08:41:53 +03:00
krb5_creds my_creds ;
2007-02-01 18:10:13 +03:00
krb5_get_init_creds_opt * opt = NULL ;
2006-04-25 16:24:25 +04:00
smb_krb5_addresses * addr = NULL ;
2001-12-13 14:30:13 +03:00
2007-05-04 13:46:17 +04:00
ZERO_STRUCT ( my_creds ) ;
2005-11-07 17:16:50 +03:00
initialize_krb5_error_table ( ) ;
2001-12-06 08:41:53 +03:00
if ( ( code = krb5_init_context ( & ctx ) ) )
2007-05-04 13:46:17 +04:00
goto out ;
2002-09-25 19:19:00 +04:00
if ( time_offset ! = 0 ) {
krb5_set_real_time ( ctx , time ( NULL ) + time_offset , 0 ) ;
}
2006-02-04 01:19:41 +03:00
2008-01-14 16:18:53 +03:00
DEBUG ( 10 , ( " kerberos_kinit_password: as %s using [%s] as ccache and config [%s] \n " ,
principal ,
2006-09-14 14:21:46 +04:00
cache_name ? cache_name : krb5_cc_default_name ( ctx ) ,
getenv ( " KRB5_CONFIG " ) ) ) ;
2006-02-04 01:19:41 +03:00
if ( ( code = krb5_cc_resolve ( ctx , cache_name ? cache_name : krb5_cc_default_name ( ctx ) , & cc ) ) ) {
2007-05-04 13:46:17 +04:00
goto out ;
2001-12-06 08:41:53 +03:00
}
2006-04-24 19:57:54 +04:00
if ( ( code = smb_krb5_parse_name ( ctx , principal , & me ) ) ) {
2007-05-04 13:46:17 +04:00
goto out ;
2001-12-06 08:41:53 +03:00
}
2006-02-04 01:19:41 +03:00
2007-05-04 13:46:17 +04:00
if ( ( code = smb_krb5_get_init_creds_opt_alloc ( ctx , & opt ) ) ) {
goto out ;
2007-02-01 18:10:13 +03:00
}
krb5_get_init_creds_opt_set_renew_life ( opt , renewable_time ) ;
krb5_get_init_creds_opt_set_forwardable ( opt , True ) ;
2007-02-08 20:02:39 +03:00
#if 0
/* insane testing */
krb5_get_init_creds_opt_set_tkt_life ( opt , 60 ) ;
# endif
2007-02-01 18:10:13 +03:00
2006-02-04 01:19:41 +03:00
# ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PAC_REQUEST
2007-02-01 18:10:13 +03:00
if ( request_pac ) {
2007-05-04 13:46:17 +04:00
if ( ( code = krb5_get_init_creds_opt_set_pac_request ( ctx , opt , ( krb5_boolean ) request_pac ) ) ) {
goto out ;
2006-04-25 16:24:25 +04:00
}
2006-02-04 01:19:41 +03:00
}
2007-02-01 18:10:13 +03:00
# endif
2006-04-25 16:24:25 +04:00
if ( add_netbios_addr ) {
2007-05-04 13:46:17 +04:00
if ( ( code = smb_krb5_gen_netbios_krb5_address ( & addr ) ) ) {
goto out ;
2006-04-25 16:24:25 +04:00
}
2007-02-01 18:10:13 +03:00
krb5_get_init_creds_opt_set_address_list ( opt , addr - > addrs ) ;
2006-04-25 16:24:25 +04:00
}
2005-10-18 07:24:00 +04:00
if ( ( code = krb5_get_init_creds_password ( ctx , & my_creds , me , CONST_DISCARD ( char * , password ) ,
2007-05-26 02:04:03 +04:00
kerb_prompter , CONST_DISCARD ( char * , password ) ,
0 , NULL , opt ) ) ) {
2007-05-04 13:46:17 +04:00
goto out ;
2001-12-06 08:41:53 +03:00
}
2007-02-01 18:10:13 +03:00
2001-12-06 08:41:53 +03:00
if ( ( code = krb5_cc_initialize ( ctx , cc , me ) ) ) {
2007-05-04 13:46:17 +04:00
goto out ;
2001-12-06 08:41:53 +03:00
}
if ( ( code = krb5_cc_store_cred ( ctx , cc , & my_creds ) ) ) {
2007-05-04 13:46:17 +04:00
goto out ;
2001-12-06 08:41:53 +03:00
}
2006-02-04 01:19:41 +03:00
if ( expire_time ) {
2004-03-24 20:32:55 +03:00
* expire_time = ( time_t ) my_creds . times . endtime ;
2006-02-04 01:19:41 +03:00
}
if ( renew_till_time ) {
* renew_till_time = ( time_t ) my_creds . times . renew_till ;
}
2007-05-04 13:46:17 +04:00
out :
2007-05-04 14:21:39 +04:00
if ( ntstatus ) {
NTSTATUS status ;
/* fast path */
if ( code = = 0 ) {
* ntstatus = NT_STATUS_OK ;
goto cleanup ;
}
/* try to get ntstatus code out of krb5_error when we have it
* inside the krb5_get_init_creds_opt - gd */
if ( opt & & smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt ( ctx , opt , & status ) ) {
* ntstatus = status ;
goto cleanup ;
}
/* fall back to self-made-mapping */
* ntstatus = krb5_to_nt_status ( code ) ;
}
cleanup :
2001-12-06 08:41:53 +03:00
krb5_free_cred_contents ( ctx , & my_creds ) ;
2007-05-04 13:46:17 +04:00
if ( me ) {
krb5_free_principal ( ctx , me ) ;
}
if ( addr ) {
smb_krb5_free_addresses ( ctx , addr ) ;
}
if ( opt ) {
smb_krb5_get_init_creds_opt_free ( ctx , opt ) ;
}
if ( cc ) {
krb5_cc_close ( ctx , cc ) ;
}
if ( ctx ) {
krb5_free_context ( ctx ) ;
}
return code ;
2001-12-06 08:41:53 +03:00
}
2004-03-24 20:32:55 +03:00
int ads_kdestroy ( const char * cc_name )
{
krb5_error_code code ;
2004-05-07 06:48:03 +04:00
krb5_context ctx = NULL ;
krb5_ccache cc = NULL ;
2004-03-24 20:32:55 +03:00
2005-11-07 17:16:50 +03:00
initialize_krb5_error_table ( ) ;
2004-03-24 20:32:55 +03:00
if ( ( code = krb5_init_context ( & ctx ) ) ) {
2004-08-21 00:18:28 +04:00
DEBUG ( 3 , ( " ads_kdestroy: kdb5_init_context failed: %s \n " ,
error_message ( code ) ) ) ;
2004-03-24 20:32:55 +03:00
return code ;
}
if ( ! cc_name ) {
if ( ( code = krb5_cc_default ( ctx , & cc ) ) ) {
krb5_free_context ( ctx ) ;
return code ;
}
} else {
if ( ( code = krb5_cc_resolve ( ctx , cc_name , & cc ) ) ) {
2004-08-21 00:18:28 +04:00
DEBUG ( 3 , ( " ads_kdestroy: krb5_cc_resolve failed: %s \n " ,
error_message ( code ) ) ) ;
2004-03-24 20:32:55 +03:00
krb5_free_context ( ctx ) ;
return code ;
}
}
if ( ( code = krb5_cc_destroy ( ctx , cc ) ) ) {
2004-08-21 00:18:28 +04:00
DEBUG ( 3 , ( " ads_kdestroy: krb5_cc_destroy failed: %s \n " ,
error_message ( code ) ) ) ;
2004-03-24 20:32:55 +03:00
}
krb5_free_context ( ctx ) ;
return code ;
}
2001-11-24 17:16:41 +03:00
2004-10-30 02:38:10 +04:00
/************************************************************************
Routine to fetch the salting principal for a service . Active
Directory may use a non - obvious principal name to generate the salt
when it determines the key to use for encrypting tickets for a service ,
and hopefully we detected that when we joined the domain .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char * kerberos_secrets_fetch_salting_principal ( const char * service , int enctype )
{
char * key = NULL ;
char * ret = NULL ;
2008-02-25 17:24:49 +03:00
if ( asprintf ( & key , " %s/%s/enctype=%d " ,
SECRETS_SALTING_PRINCIPAL , service , enctype ) = = - 1 ) {
2004-10-30 02:38:10 +04:00
return NULL ;
}
ret = ( char * ) secrets_fetch ( key , NULL ) ;
SAFE_FREE ( key ) ;
return ret ;
}
/************************************************************************
2006-07-11 22:45:22 +04:00
Return the standard DES salt key
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char * kerberos_standard_des_salt ( void )
{
fstring salt ;
fstr_sprintf ( salt , " host/%s.%s@ " , global_myname ( ) , lp_realm ( ) ) ;
strlower_m ( salt ) ;
fstrcat ( salt , lp_realm ( ) ) ;
return SMB_STRDUP ( salt ) ;
}
/************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char * des_salt_key ( void )
{
char * key ;
2008-02-25 17:24:49 +03:00
if ( asprintf ( & key , " %s/DES/%s " , SECRETS_SALTING_PRINCIPAL ,
lp_realm ( ) ) = = - 1 ) {
return NULL ;
}
2006-07-11 22:45:22 +04:00
return key ;
}
/************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-10-19 04:40:25 +04:00
bool kerberos_secrets_store_des_salt ( const char * salt )
2006-07-11 22:45:22 +04:00
{
char * key ;
2007-10-19 04:40:25 +04:00
bool ret ;
2006-07-11 22:45:22 +04:00
if ( ( key = des_salt_key ( ) ) = = NULL ) {
DEBUG ( 0 , ( " kerberos_secrets_store_des_salt: failed to generate key! \n " ) ) ;
return False ;
}
if ( ! salt ) {
DEBUG ( 8 , ( " kerberos_secrets_store_des_salt: deleting salt \n " ) ) ;
secrets_delete ( key ) ;
2006-07-13 04:11:34 +04:00
return True ;
2006-07-11 22:45:22 +04:00
}
DEBUG ( 3 , ( " kerberos_secrets_store_des_salt: Storing salt \" %s \" \n " , salt ) ) ;
ret = secrets_store ( key , salt , strlen ( salt ) + 1 ) ;
SAFE_FREE ( key ) ;
return ret ;
}
/************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char * kerberos_secrets_fetch_des_salt ( void )
{
char * salt , * key ;
if ( ( key = des_salt_key ( ) ) = = NULL ) {
DEBUG ( 0 , ( " kerberos_secrets_fetch_des_salt: failed to generate key! \n " ) ) ;
return False ;
}
2006-07-12 00:50:50 +04:00
salt = ( char * ) secrets_fetch ( key , NULL ) ;
2006-07-11 22:45:22 +04:00
SAFE_FREE ( key ) ;
return salt ;
}
2007-12-16 10:22:25 +03:00
/************************************************************************
Routine to get the default realm from the kerberos credentials cache .
Caller must free if the return value is not NULL .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char * kerberos_get_default_realm_from_ccache ( void )
{
char * realm = NULL ;
krb5_context ctx = NULL ;
krb5_ccache cc = NULL ;
krb5_principal princ = NULL ;
initialize_krb5_error_table ( ) ;
if ( krb5_init_context ( & ctx ) ) {
return NULL ;
}
DEBUG ( 5 , ( " kerberos_get_default_realm_from_ccache: "
" Trying to read krb5 cache: %s \n " ,
krb5_cc_default_name ( ctx ) ) ) ;
if ( krb5_cc_default ( ctx , & cc ) ) {
DEBUG ( 0 , ( " kerberos_get_default_realm_from_ccache: "
" failed to read default cache \n " ) ) ;
goto out ;
}
if ( krb5_cc_get_principal ( ctx , cc , & princ ) ) {
DEBUG ( 0 , ( " kerberos_get_default_realm_from_ccache: "
" failed to get default principal \n " ) ) ;
2007-12-16 10:32:28 +03:00
goto out ;
2007-12-16 10:22:25 +03:00
}
# if defined(HAVE_KRB5_PRINCIPAL_GET_REALM)
realm = SMB_STRDUP ( krb5_principal_get_realm ( ctx , princ ) ) ;
# elif defined(HAVE_KRB5_PRINC_REALM)
2007-12-17 23:21:38 +03:00
{
krb5_data * realm_data = krb5_princ_realm ( ctx , princ ) ;
realm = SMB_STRNDUP ( realm_data - > data , realm_data - > length ) ;
}
2007-12-16 10:22:25 +03:00
# endif
out :
if ( ctx ) {
2009-03-20 13:11:04 +03:00
if ( princ ) {
krb5_free_principal ( ctx , princ ) ;
}
if ( cc ) {
krb5_cc_close ( ctx , cc ) ;
}
2007-12-16 10:22:25 +03:00
krb5_free_context ( ctx ) ;
}
2007-12-16 10:32:28 +03:00
2007-12-16 10:22:25 +03:00
return realm ;
}
2010-01-31 06:24:28 +03:00
/************************************************************************
Routine to get the realm from a given DNS name . Returns malloc ' ed memory .
Caller must free ( ) if the return value is not NULL .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char * kerberos_get_realm_from_hostname ( const char * hostname )
{
# if defined(HAVE_KRB5_GET_HOST_REALM) && defined(HAVE_KRB5_FREE_HOST_REALM)
# if defined(HAVE_KRB5_REALM_TYPE)
/* Heimdal. */
krb5_realm * realm_list = NULL ;
# else
/* MIT */
char * * realm_list = NULL ;
# endif
char * realm = NULL ;
krb5_error_code kerr ;
krb5_context ctx = NULL ;
initialize_krb5_error_table ( ) ;
if ( krb5_init_context ( & ctx ) ) {
return NULL ;
}
kerr = krb5_get_host_realm ( ctx , hostname , & realm_list ) ;
if ( kerr ! = 0 ) {
DEBUG ( 3 , ( " kerberos_get_realm_from_hostname %s: "
" failed %s \n " ,
hostname ? hostname : " (NULL) " ,
error_message ( kerr ) ) ) ;
goto out ;
}
if ( realm_list & & realm_list [ 0 ] ) {
realm = SMB_STRDUP ( realm_list [ 0 ] ) ;
}
out :
if ( ctx ) {
if ( realm_list ) {
krb5_free_host_realm ( ctx , realm_list ) ;
realm_list = NULL ;
}
krb5_free_context ( ctx ) ;
ctx = NULL ;
}
return realm ;
# else
return NULL ;
# endif
}
2006-07-11 22:45:22 +04:00
/************************************************************************
Routine to get the salting principal for this service . This is
maintained for backwards compatibilty with releases prior to 3.0 .24 .
Since we store the salting principal string only at join , we may have
to look for the older tdb keys . Caller must free if return is not null .
2004-10-30 02:38:10 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
krb5_principal kerberos_fetch_salt_princ_for_host_princ ( krb5_context context ,
krb5_principal host_princ ,
int enctype )
{
char * unparsed_name = NULL , * salt_princ_s = NULL ;
krb5_principal ret_princ = NULL ;
2006-07-11 22:45:22 +04:00
/* lookup new key first */
2004-10-30 02:38:10 +04:00
2006-07-11 22:45:22 +04:00
if ( ( salt_princ_s = kerberos_secrets_fetch_des_salt ( ) ) = = NULL ) {
/* look under the old key. If this fails, just use the standard key */
2004-10-30 02:38:10 +04:00
2009-03-18 08:23:27 +03:00
if ( smb_krb5_unparse_name ( talloc_tos ( ) , context , host_princ , & unparsed_name ) ! = 0 ) {
2006-07-11 22:45:22 +04:00
return ( krb5_principal ) NULL ;
}
if ( ( salt_princ_s = kerberos_secrets_fetch_salting_principal ( unparsed_name , enctype ) ) = = NULL ) {
/* fall back to host/machine.realm@REALM */
salt_princ_s = kerberos_standard_des_salt ( ) ;
}
2004-10-30 02:38:10 +04:00
}
2006-04-24 19:57:54 +04:00
if ( smb_krb5_parse_name ( context , salt_princ_s , & ret_princ ) ! = 0 ) {
2006-07-11 22:45:22 +04:00
ret_princ = NULL ;
2004-10-30 02:38:10 +04:00
}
2006-07-11 22:45:22 +04:00
2009-03-18 08:23:27 +03:00
TALLOC_FREE ( unparsed_name ) ;
2004-10-30 02:38:10 +04:00
SAFE_FREE ( salt_princ_s ) ;
2006-07-11 22:45:22 +04:00
2004-10-30 02:38:10 +04:00
return ret_princ ;
}
/************************************************************************
Routine to set the salting principal for this service . Active
Directory may use a non - obvious principal name to generate the salt
when it determines the key to use for encrypting tickets for a service ,
and hopefully we detected that when we joined the domain .
Setting principal to NULL deletes this entry .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-10-19 04:40:25 +04:00
bool kerberos_secrets_store_salting_principal ( const char * service ,
2004-10-30 02:38:10 +04:00
int enctype ,
const char * principal )
{
char * key = NULL ;
2007-10-19 04:40:25 +04:00
bool ret = False ;
2004-10-30 02:38:10 +04:00
krb5_context context = NULL ;
krb5_principal princ = NULL ;
char * princ_s = NULL ;
char * unparsed_name = NULL ;
2008-03-15 01:26:28 +03:00
krb5_error_code code ;
2004-10-30 02:38:10 +04:00
2008-03-15 01:26:28 +03:00
if ( ( ( code = krb5_init_context ( & context ) ) ! = 0 ) | | ( context = = NULL ) ) {
DEBUG ( 5 , ( " kerberos_secrets_store_salting_pricipal: kdb5_init_context failed: %s \n " ,
error_message ( code ) ) ) ;
2004-10-30 02:38:10 +04:00
return False ;
}
if ( strchr_m ( service , ' @ ' ) ) {
2008-02-25 17:24:49 +03:00
if ( asprintf ( & princ_s , " %s " , service ) = = - 1 ) {
goto out ;
}
2004-10-30 02:38:10 +04:00
} else {
2008-02-25 17:24:49 +03:00
if ( asprintf ( & princ_s , " %s@%s " , service , lp_realm ( ) ) = = - 1 ) {
goto out ;
}
2004-10-30 02:38:10 +04:00
}
2006-04-24 19:57:54 +04:00
if ( smb_krb5_parse_name ( context , princ_s , & princ ) ! = 0 ) {
2004-10-30 02:38:10 +04:00
goto out ;
}
2009-03-18 08:23:27 +03:00
if ( smb_krb5_unparse_name ( talloc_tos ( ) , context , princ , & unparsed_name ) ! = 0 ) {
2004-10-30 02:38:10 +04:00
goto out ;
}
2008-02-25 17:24:49 +03:00
if ( asprintf ( & key , " %s/%s/enctype=%d " ,
SECRETS_SALTING_PRINCIPAL , unparsed_name , enctype )
= = - 1 ) {
2004-10-30 02:38:10 +04:00
goto out ;
}
if ( ( principal ! = NULL ) & & ( strlen ( principal ) > 0 ) ) {
ret = secrets_store ( key , principal , strlen ( principal ) + 1 ) ;
} else {
ret = secrets_delete ( key ) ;
}
out :
SAFE_FREE ( key ) ;
SAFE_FREE ( princ_s ) ;
2009-03-18 08:23:27 +03:00
TALLOC_FREE ( unparsed_name ) ;
2004-10-30 02:38:10 +04:00
2008-05-27 23:27:57 +04:00
if ( princ ) {
krb5_free_principal ( context , princ ) ;
}
2004-10-30 02:38:10 +04:00
if ( context ) {
krb5_free_context ( context ) ;
}
return ret ;
}
/************************************************************************
2006-07-11 22:45:22 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2006-03-20 22:05:44 +03:00
int kerberos_kinit_password ( const char * principal ,
const char * password ,
int time_offset ,
const char * cache_name )
{
return kerberos_kinit_password_ext ( principal ,
password ,
time_offset ,
0 ,
0 ,
cache_name ,
False ,
2006-04-25 16:24:25 +04:00
False ,
2007-05-04 14:21:39 +04:00
0 ,
NULL ) ;
2006-03-20 22:05:44 +03:00
}
2008-01-17 00:21:46 +03:00
/************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char * print_kdc_line ( char * mem_ctx ,
const char * prev_line ,
2010-05-15 01:23:34 +04:00
const struct sockaddr_storage * pss ,
const char * kdc_name )
2008-01-17 00:21:46 +03:00
{
char * kdc_str = NULL ;
if ( pss - > ss_family = = AF_INET ) {
kdc_str = talloc_asprintf ( mem_ctx , " %s \t kdc = %s \n " ,
prev_line ,
print_canonical_sockaddr ( mem_ctx , pss ) ) ;
} else {
char addr [ INET6_ADDRSTRLEN ] ;
uint16_t port = get_sockaddr_port ( pss ) ;
2010-05-15 02:34:35 +04:00
DEBUG ( 10 , ( " print_kdc_line: IPv6 case for kdc_name: %s, port: %d \n " ,
kdc_name , port ) ) ;
2008-01-17 00:21:46 +03:00
if ( port ! = 0 & & port ! = DEFAULT_KRB5_PORT ) {
/* Currently for IPv6 we can't specify a non-default
krb5 port with an address , as this requires a ' : ' .
Resolve to a name . */
char hostname [ MAX_DNS_NAME_LENGTH ] ;
2008-01-17 00:28:24 +03:00
int ret = sys_getnameinfo ( ( const struct sockaddr * ) pss ,
2008-01-17 00:21:46 +03:00
sizeof ( * pss ) ,
hostname , sizeof ( hostname ) ,
NULL , 0 ,
2008-01-17 00:28:24 +03:00
NI_NAMEREQD ) ;
if ( ret ) {
DEBUG ( 0 , ( " print_kdc_line: can't resolve name "
" for kdc with non-default port %s. "
" Error %s \n . " ,
print_canonical_sockaddr ( mem_ctx , pss ) ,
gai_strerror ( ret ) ) ) ;
2010-05-15 02:34:35 +04:00
return NULL ;
2008-01-17 00:28:24 +03:00
}
/* Success, use host:port */
kdc_str = talloc_asprintf ( mem_ctx ,
2008-01-17 00:21:46 +03:00
" %s \t kdc = %s:%u \n " ,
2008-01-17 00:28:24 +03:00
prev_line ,
2008-01-17 00:21:46 +03:00
hostname ,
( unsigned int ) port ) ;
2008-01-17 00:28:24 +03:00
} else {
2010-05-15 02:34:35 +04:00
/* no krb5 lib currently supports "kdc = ipv6 address"
* at all , so just fill in just the kdc_name if we have
* it and let the krb5 lib figure out the appropriate
* ipv6 address - gd */
if ( kdc_name ) {
kdc_str = talloc_asprintf ( mem_ctx , " %s \t kdc = %s \n " ,
prev_line , kdc_name ) ;
} else {
kdc_str = talloc_asprintf ( mem_ctx , " %s \t kdc = %s \n " ,
prev_line ,
print_sockaddr ( addr ,
sizeof ( addr ) ,
pss ) ) ;
}
2008-01-17 00:28:24 +03:00
}
2008-01-17 00:21:46 +03:00
}
return kdc_str ;
}
2006-09-02 23:27:44 +04:00
/************************************************************************
Create a string list of available kdc ' s , possibly searching by sitename .
Does DNS queries .
2008-05-19 04:48:09 +04:00
If " sitename " is given , the DC ' s in that site are listed first .
2006-09-02 23:27:44 +04:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-10-25 01:16:54 +04:00
static char * get_kdc_ip_string ( char * mem_ctx ,
const char * realm ,
const char * sitename ,
2010-05-15 01:23:34 +04:00
struct sockaddr_storage * pss ,
const char * kdc_name )
2006-09-02 23:27:44 +04:00
{
2007-08-31 17:39:51 +04:00
int i ;
struct ip_service * ip_srv_site = NULL ;
2008-01-17 00:21:46 +03:00
struct ip_service * ip_srv_nonsite = NULL ;
2007-08-31 17:39:51 +04:00
int count_site = 0 ;
2007-09-08 17:53:08 +04:00
int count_nonsite ;
2010-05-15 01:23:34 +04:00
char * kdc_str = print_kdc_line ( mem_ctx , " " , pss , kdc_name ) ;
2006-09-02 23:27:44 +04:00
if ( kdc_str = = NULL ) {
return NULL ;
}
2008-05-19 04:48:09 +04:00
/*
* First get the KDC ' s only in this site , the rest will be
* appended later
*/
2007-01-17 21:25:35 +03:00
2007-01-17 22:11:45 +03:00
if ( sitename ) {
2007-01-17 21:25:35 +03:00
2007-01-17 22:11:45 +03:00
get_kdc_list ( realm , sitename , & ip_srv_site , & count_site ) ;
for ( i = 0 ; i < count_site ; i + + ) {
2008-12-03 10:29:57 +03:00
if ( sockaddr_equal ( ( struct sockaddr * ) & ip_srv_site [ i ] . ss ,
2008-10-23 21:53:15 +04:00
( struct sockaddr * ) pss ) ) {
2007-01-17 22:11:45 +03:00
continue ;
}
2007-10-25 01:16:54 +04:00
/* Append to the string - inefficient
* but not done often . */
2008-01-17 00:21:46 +03:00
kdc_str = print_kdc_line ( mem_ctx ,
kdc_str ,
2010-05-15 01:23:34 +04:00
& ip_srv_site [ i ] . ss ,
NULL ) ;
2007-01-17 22:11:45 +03:00
if ( ! kdc_str ) {
SAFE_FREE ( ip_srv_site ) ;
return NULL ;
}
2007-01-17 21:25:35 +03:00
}
2006-09-02 23:27:44 +04:00
}
2007-01-17 21:25:35 +03:00
/* Get all KDC's. */
get_kdc_list ( realm , NULL , & ip_srv_nonsite , & count_nonsite ) ;
for ( i = 0 ; i < count_nonsite ; i + + ) {
int j ;
2008-12-03 10:29:57 +03:00
if ( sockaddr_equal ( ( struct sockaddr * ) & ip_srv_nonsite [ i ] . ss , ( struct sockaddr * ) pss ) ) {
2006-09-02 23:27:44 +04:00
continue ;
}
2007-01-17 21:25:35 +03:00
/* Ensure this isn't an IP already seen (YUK! this is n*n....) */
for ( j = 0 ; j < count_site ; j + + ) {
2008-12-03 10:29:57 +03:00
if ( sockaddr_equal ( ( struct sockaddr * ) & ip_srv_nonsite [ i ] . ss ,
2008-10-23 21:53:15 +04:00
( struct sockaddr * ) & ip_srv_site [ j ] . ss ) ) {
2007-01-17 21:25:35 +03:00
break ;
}
/* As the lists are sorted we can break early if nonsite > site. */
if ( ip_service_compare ( & ip_srv_nonsite [ i ] , & ip_srv_site [ j ] ) > 0 ) {
break ;
}
}
if ( j ! = i ) {
continue ;
}
2006-09-02 23:27:44 +04:00
/* Append to the string - inefficient but not done often. */
2008-01-17 00:21:46 +03:00
kdc_str = print_kdc_line ( mem_ctx ,
2007-10-25 01:16:54 +04:00
kdc_str ,
2010-05-15 01:23:34 +04:00
& ip_srv_nonsite [ i ] . ss ,
NULL ) ;
2006-09-02 23:27:44 +04:00
if ( ! kdc_str ) {
2007-01-17 21:25:35 +03:00
SAFE_FREE ( ip_srv_site ) ;
SAFE_FREE ( ip_srv_nonsite ) ;
2006-09-02 23:27:44 +04:00
return NULL ;
}
}
2007-01-17 21:25:35 +03:00
SAFE_FREE ( ip_srv_site ) ;
SAFE_FREE ( ip_srv_nonsite ) ;
2006-09-03 03:06:21 +04:00
2006-09-02 23:27:44 +04:00
DEBUG ( 10 , ( " get_kdc_ip_string: Returning %s \n " ,
kdc_str ) ) ;
return kdc_str ;
}
2006-08-31 05:20:21 +04:00
/************************************************************************
Create a specific krb5 . conf file in the private directory pointing
at a specific kdc for a realm . Keyed off domain name . Sets
KRB5_CONFIG environment variable to point to this file . Must be
run as root or will fail ( which is a good thing : - ) .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-10-25 01:16:54 +04:00
bool create_local_private_krb5_conf_for_domain ( const char * realm ,
const char * domain ,
const char * sitename ,
2010-05-15 01:23:34 +04:00
struct sockaddr_storage * pss ,
const char * kdc_name )
2006-08-31 05:20:21 +04:00
{
2009-08-26 16:56:41 +04:00
char * dname ;
2006-09-07 08:01:11 +04:00
char * tmpname = NULL ;
2006-09-02 05:33:13 +04:00
char * fname = NULL ;
2006-08-31 05:20:21 +04:00
char * file_contents = NULL ;
2006-09-07 08:17:23 +04:00
char * kdc_ip_string = NULL ;
2006-08-31 05:20:21 +04:00
size_t flen = 0 ;
2006-09-07 08:01:11 +04:00
ssize_t ret ;
int fd ;
2006-08-31 08:14:08 +04:00
char * realm_upper = NULL ;
2009-01-16 02:10:29 +03:00
bool result = false ;
2006-08-31 05:20:21 +04:00
2009-08-26 16:56:41 +04:00
if ( ! lp_create_krb5_conf ( ) ) {
return false ;
}
dname = lock_path ( " smb_krb5 " ) ;
2006-09-02 05:33:13 +04:00
if ( ! dname ) {
2009-01-16 02:10:29 +03:00
return false ;
2006-09-02 05:33:13 +04:00
}
2006-09-02 09:55:47 +04:00
if ( ( mkdir ( dname , 0755 ) = = - 1 ) & & ( errno ! = EEXIST ) ) {
2006-09-02 05:33:13 +04:00
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: "
" failed to create directory %s. Error was %s \n " ,
dname , strerror ( errno ) ) ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-02 05:33:13 +04:00
}
2009-01-16 02:17:51 +03:00
tmpname = lock_path ( " smb_tmp_krb5.XXXXXX " ) ;
2006-09-07 08:01:11 +04:00
if ( ! tmpname ) {
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-07 08:01:11 +04:00
}
2006-09-02 05:33:13 +04:00
fname = talloc_asprintf ( dname , " %s/krb5.conf.%s " , dname , domain ) ;
2006-08-31 05:20:21 +04:00
if ( ! fname ) {
2009-01-16 02:10:29 +03:00
goto done ;
2006-08-31 05:20:21 +04:00
}
2006-09-02 05:23:08 +04:00
DEBUG ( 10 , ( " create_local_private_krb5_conf_for_domain: fname = %s, realm = %s, domain = %s \n " ,
fname , realm , domain ) ) ;
2006-08-31 08:14:08 +04:00
realm_upper = talloc_strdup ( fname , realm ) ;
strupper_m ( realm_upper ) ;
2010-05-15 01:23:34 +04:00
kdc_ip_string = get_kdc_ip_string ( dname , realm , sitename , pss , kdc_name ) ;
2006-09-02 23:27:44 +04:00
if ( ! kdc_ip_string ) {
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-02 23:27:44 +04:00
}
2007-10-25 01:16:54 +04:00
2008-01-28 20:32:09 +03:00
file_contents = talloc_asprintf ( fname ,
" [libdefaults] \n \t default_realm = %s \n "
2008-09-04 17:11:22 +04:00
" \t default_tgs_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5 \n "
" \t default_tkt_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5 \n "
" \t preferred_enctypes = RC4-HMAC DES-CBC-CRC DES-CBC-MD5 \n \n "
2008-01-28 20:32:09 +03:00
" [realms] \n \t %s = { \n "
" \t %s \t } \n " ,
realm_upper , realm_upper , kdc_ip_string ) ;
2006-08-31 05:20:21 +04:00
if ( ! file_contents ) {
2009-01-16 02:10:29 +03:00
goto done ;
2006-08-31 05:20:21 +04:00
}
flen = strlen ( file_contents ) ;
2006-08-31 05:27:51 +04:00
2009-04-21 01:58:26 +04:00
fd = mkstemp ( tmpname ) ;
2006-09-07 08:01:11 +04:00
if ( fd = = - 1 ) {
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: smb_mkstemp failed, "
" for file %s. Errno %s \n " ,
tmpname , strerror ( errno ) ) ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-08-31 05:20:21 +04:00
}
2006-09-08 05:16:01 +04:00
if ( fchmod ( fd , 0644 ) = = - 1 ) {
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: fchmod failed for %s. "
" Errno %s \n " ,
tmpname , strerror ( errno ) ) ) ;
unlink ( tmpname ) ;
close ( fd ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-08 05:16:01 +04:00
}
2006-09-07 08:01:11 +04:00
ret = write ( fd , file_contents , flen ) ;
2006-09-02 08:50:08 +04:00
if ( flen ! = ret ) {
2006-09-07 08:01:11 +04:00
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: write failed, "
" returned %d (should be %u). Errno %s \n " ,
( int ) ret , ( unsigned int ) flen , strerror ( errno ) ) ) ;
unlink ( tmpname ) ;
close ( fd ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-08-31 05:20:21 +04:00
}
2006-09-07 08:01:11 +04:00
if ( close ( fd ) = = - 1 ) {
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: close failed. "
2006-09-02 08:50:08 +04:00
" Errno %s \n " , strerror ( errno ) ) ) ;
2006-09-07 08:01:11 +04:00
unlink ( tmpname ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-07 08:01:11 +04:00
}
if ( rename ( tmpname , fname ) = = - 1 ) {
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: rename "
" of %s to %s failed. Errno %s \n " ,
tmpname , fname , strerror ( errno ) ) ) ;
unlink ( tmpname ) ;
2009-01-16 02:10:29 +03:00
goto done ;
2006-08-31 05:20:21 +04:00
}
2006-08-31 08:14:08 +04:00
DEBUG ( 5 , ( " create_local_private_krb5_conf_for_domain: wrote "
2008-01-17 00:21:46 +03:00
" file %s with realm %s KDC list = %s \n " ,
fname , realm_upper , kdc_ip_string ) ) ;
2006-08-31 08:14:08 +04:00
2006-09-02 05:34:37 +04:00
/* Set the environment variable to this file. */
setenv ( " KRB5_CONFIG " , fname , 1 ) ;
2006-09-07 08:01:11 +04:00
2009-01-16 02:10:29 +03:00
result = true ;
2006-09-07 08:01:11 +04:00
# if defined(OVERWRITE_SYSTEM_KRB5_CONF)
2006-09-07 21:29:23 +04:00
# define SYSTEM_KRB5_CONF_PATH " / etc / krb5.conf"
2006-09-07 08:01:11 +04:00
/* Insanity, sheer insanity..... */
2006-09-07 22:34:02 +04:00
if ( strequal ( realm , lp_realm ( ) ) ) {
2007-11-21 05:55:36 +03:00
char linkpath [ PATH_MAX + 1 ] ;
2006-09-07 21:29:23 +04:00
int lret ;
lret = readlink ( SYSTEM_KRB5_CONF_PATH , linkpath , sizeof ( linkpath ) - 1 ) ;
2007-11-21 05:55:36 +03:00
if ( lret ! = - 1 ) {
linkpath [ lret ] = ' \0 ' ;
}
2006-09-07 21:29:23 +04:00
2007-11-21 05:55:36 +03:00
if ( lret ! = - 1 | | strcmp ( linkpath , fname ) = = 0 ) {
2006-09-07 21:29:23 +04:00
/* Symlink already exists. */
2009-01-16 02:10:29 +03:00
goto done ;
2006-09-07 08:01:11 +04:00
}
2006-09-07 21:29:23 +04:00
/* Try and replace with a symlink. */
if ( symlink ( fname , SYSTEM_KRB5_CONF_PATH ) = = - 1 ) {
2007-11-21 05:55:36 +03:00
const char * newpath = SYSTEM_KRB5_CONF_PATH # # " .saved " ;
2006-09-07 21:29:23 +04:00
if ( errno ! = EEXIST ) {
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: symlink "
" of %s to %s failed. Errno %s \n " ,
fname , SYSTEM_KRB5_CONF_PATH , strerror ( errno ) ) ) ;
2009-01-16 02:10:29 +03:00
goto done ; /* Not a fatal error. */
2006-09-07 21:29:23 +04:00
}
/* Yes, this is a race conditon... too bad. */
2007-11-21 05:55:36 +03:00
if ( rename ( SYSTEM_KRB5_CONF_PATH , newpath ) = = - 1 ) {
2006-09-07 21:29:23 +04:00
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: rename "
" of %s to %s failed. Errno %s \n " ,
2007-11-21 05:55:36 +03:00
SYSTEM_KRB5_CONF_PATH , newpath ,
2006-09-07 21:29:23 +04:00
strerror ( errno ) ) ) ;
2009-01-16 02:10:29 +03:00
goto done ; /* Not a fatal error. */
2006-09-07 21:29:23 +04:00
}
2007-11-21 05:55:36 +03:00
if ( symlink ( fname , SYSTEM_KRB5_CONF_PATH ) = = - 1 ) {
2006-09-07 21:29:23 +04:00
DEBUG ( 0 , ( " create_local_private_krb5_conf_for_domain: "
" forced symlink of %s to /etc/krb5.conf failed. Errno %s \n " ,
fname , strerror ( errno ) ) ) ;
2009-01-16 02:10:29 +03:00
goto done ; /* Not a fatal error. */
2006-09-07 21:29:23 +04:00
}
2006-09-07 08:01:11 +04:00
}
}
# endif
2009-01-16 02:10:29 +03:00
done :
2009-01-16 02:17:51 +03:00
TALLOC_FREE ( tmpname ) ;
2006-09-02 05:34:37 +04:00
TALLOC_FREE ( dname ) ;
2009-01-16 02:10:29 +03:00
return result ;
2006-08-31 05:20:21 +04:00
}
2001-11-24 17:16:41 +03:00
# endif