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
krb5 set password implementation
Copyright ( C ) Andrew Tridgell 2001
2001-12-20 06:54:52 +03:00
Copyright ( C ) Remus Koos 2001 ( remuskoos @ yahoo . com )
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
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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include "includes.h"
2001-11-29 02:54:07 +03:00
# ifdef HAVE_KRB5
2001-11-24 17:16:41 +03:00
# define DEFAULT_KPASSWD_PORT 464
# define KRB5_KPASSWD_VERS_CHANGEPW 1
# define KRB5_KPASSWD_VERS_SETPW 0xff80
# define KRB5_KPASSWD_ACCESSDENIED 5
# define KRB5_KPASSWD_BAD_VERSION 6
/* This implements the Kerb password change protocol as specifed in
* kerb - chg - password - 02. txt
*/
2001-12-20 06:54:52 +03:00
static DATA_BLOB encode_krb5_setpw ( const char * principal , const char * password )
2001-11-24 17:16:41 +03:00
{
2001-12-20 06:54:52 +03:00
char * princ_part1 = NULL ;
char * princ_part2 = NULL ;
char * realm = NULL ;
char * c ;
char * princ ;
2001-11-24 17:16:41 +03:00
ASN1_DATA req ;
DATA_BLOB ret ;
2001-12-20 06:54:52 +03:00
princ = strdup ( principal ) ;
if ( ( c = strchr ( princ , ' / ' ) ) = = NULL ) {
c = princ ;
} else {
* c = ' \0 ' ;
c + + ;
princ_part1 = princ ;
}
princ_part2 = c ;
if ( ( c = strchr ( c , ' @ ' ) ) ! = NULL ) {
* c = ' \0 ' ;
c + + ;
realm = c ;
}
2001-11-24 17:16:41 +03:00
memset ( & req , 0 , sizeof ( req ) ) ;
asn1_push_tag ( & req , ASN1_SEQUENCE ( 0 ) ) ;
asn1_push_tag ( & req , ASN1_CONTEXT ( 0 ) ) ;
asn1_write_OctetString ( & req , password , strlen ( password ) ) ;
asn1_pop_tag ( & req ) ;
asn1_push_tag ( & req , ASN1_CONTEXT ( 1 ) ) ;
asn1_push_tag ( & req , ASN1_SEQUENCE ( 0 ) ) ;
asn1_push_tag ( & req , ASN1_CONTEXT ( 0 ) ) ;
asn1_write_Integer ( & req , 1 ) ;
asn1_pop_tag ( & req ) ;
asn1_push_tag ( & req , ASN1_CONTEXT ( 1 ) ) ;
asn1_push_tag ( & req , ASN1_SEQUENCE ( 0 ) ) ;
2001-12-20 06:54:52 +03:00
if ( princ_part1 )
asn1_write_GeneralString ( & req , princ_part1 ) ;
asn1_write_GeneralString ( & req , princ_part2 ) ;
2001-11-24 17:16:41 +03:00
asn1_pop_tag ( & req ) ;
asn1_pop_tag ( & req ) ;
asn1_pop_tag ( & req ) ;
asn1_pop_tag ( & req ) ;
asn1_push_tag ( & req , ASN1_CONTEXT ( 2 ) ) ;
asn1_write_GeneralString ( & req , realm ) ;
asn1_pop_tag ( & req ) ;
asn1_pop_tag ( & req ) ;
ret = data_blob ( req . data , req . length ) ;
asn1_free ( & req ) ;
2001-12-20 06:54:52 +03:00
free ( princ ) ;
2001-11-24 17:16:41 +03:00
return ret ;
}
static krb5_error_code build_setpw_request ( krb5_context context ,
krb5_auth_context auth_context ,
krb5_data * ap_req ,
2001-12-20 06:54:52 +03:00
const char * princ ,
2001-11-24 17:16:41 +03:00
const char * passwd ,
krb5_data * packet )
{
krb5_error_code ret ;
krb5_data cipherpw ;
krb5_data encoded_setpw ;
krb5_replay_data replay ;
char * p ;
DATA_BLOB setpw ;
ret = krb5_auth_con_setflags ( context ,
auth_context , KRB5_AUTH_CONTEXT_DO_SEQUENCE ) ;
if ( ret ) {
DEBUG ( 1 , ( " krb5_auth_con_setflags failed (%s) \n " ,
error_message ( ret ) ) ) ;
return ret ;
}
2001-12-20 06:54:52 +03:00
setpw = encode_krb5_setpw ( princ , passwd ) ;
2001-11-24 17:16:41 +03:00
encoded_setpw . data = setpw . data ;
encoded_setpw . length = setpw . length ;
ret = krb5_mk_priv ( context , auth_context ,
& encoded_setpw , & cipherpw , & replay ) ;
2001-12-20 06:54:52 +03:00
data_blob_free ( & setpw ) ; /*from 'encode_krb5_setpw(...)' */
2001-11-24 17:16:41 +03:00
if ( ret ) {
DEBUG ( 1 , ( " krb5_mk_priv failed (%s) \n " , error_message ( ret ) ) ) ;
return ret ;
}
packet - > data = ( char * ) malloc ( ap_req - > length + cipherpw . length + 6 ) ;
2003-02-05 02:44:28 +03:00
if ( ! packet - > data )
return - 1 ;
2001-11-24 17:16:41 +03:00
/* see the RFC for details */
2003-01-21 09:23:49 +03:00
p = ( ( char * ) packet - > data ) + 2 ;
2003-02-05 02:44:28 +03:00
RSSVAL ( p , 0 , 0xff80 ) ;
p + = 2 ;
RSSVAL ( p , 0 , ap_req - > length ) ;
p + = 2 ;
memcpy ( p , ap_req - > data , ap_req - > length ) ;
p + = ap_req - > length ;
memcpy ( p , cipherpw . data , cipherpw . length ) ;
p + = cipherpw . length ;
2001-11-24 17:16:41 +03:00
packet - > length = PTR_DIFF ( p , packet - > data ) ;
RSSVAL ( packet - > data , 0 , packet - > length ) ;
2001-12-20 06:54:52 +03:00
free ( cipherpw . data ) ; /* from 'krb5_mk_priv(...)' */
2001-11-24 17:16:41 +03:00
return 0 ;
}
static krb5_error_code parse_setpw_reply ( krb5_context context ,
krb5_auth_context auth_context ,
krb5_data * packet )
{
krb5_data ap_rep ;
char * p ;
int vnum , ret , res_code ;
krb5_data cipherresult ;
krb5_data clearresult ;
krb5_ap_rep_enc_part * ap_rep_enc ;
krb5_replay_data replay ;
if ( packet - > length < 4 ) {
return KRB5KRB_AP_ERR_MODIFIED ;
}
p = packet - > data ;
2003-01-21 09:23:49 +03:00
if ( ( ( char * ) packet - > data ) [ 0 ] = = 0x7e | | ( ( char * ) packet - > data ) [ 0 ] = = 0x5e ) {
2001-11-24 17:16:41 +03:00
/* it's an error packet. We should parse it ... */
DEBUG ( 1 , ( " Got error packet 0x%x from kpasswd server \n " ,
2003-01-21 09:23:49 +03:00
( ( char * ) packet - > data ) [ 0 ] ) ) ;
2001-11-24 17:16:41 +03:00
return KRB5KRB_AP_ERR_MODIFIED ;
}
if ( RSVAL ( p , 0 ) ! = packet - > length ) {
DEBUG ( 1 , ( " Bad packet length (%d/%d) from kpasswd server \n " ,
RSVAL ( p , 0 ) , packet - > length ) ) ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
p + = 2 ;
vnum = RSVAL ( p , 0 ) ; p + = 2 ;
if ( vnum ! = KRB5_KPASSWD_VERS_SETPW & & vnum ! = KRB5_KPASSWD_VERS_CHANGEPW ) {
DEBUG ( 1 , ( " Bad vnum (%d) from kpasswd server \n " , vnum ) ) ;
return KRB5KDC_ERR_BAD_PVNO ;
}
ap_rep . length = RSVAL ( p , 0 ) ; p + = 2 ;
2003-01-21 09:23:49 +03:00
if ( p + ap_rep . length > = ( char * ) packet - > data + packet - > length ) {
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " ptr beyond end of packet from kpasswd server \n " ) ) ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
if ( ap_rep . length = = 0 ) {
DEBUG ( 1 , ( " got unencrypted setpw result?! \n " ) ) ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
/* verify ap_rep */
ap_rep . data = p ;
p + = ap_rep . length ;
ret = krb5_rd_rep ( context , auth_context , & ap_rep , & ap_rep_enc ) ;
if ( ret ) {
DEBUG ( 1 , ( " failed to rd setpw reply (%s) \n " , error_message ( ret ) ) ) ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
krb5_free_ap_rep_enc_part ( context , ap_rep_enc ) ;
cipherresult . data = p ;
2003-01-21 09:23:49 +03:00
cipherresult . length = ( ( char * ) packet - > data + packet - > length ) - p ;
2001-11-24 17:16:41 +03:00
ret = krb5_rd_priv ( context , auth_context , & cipherresult , & clearresult ,
& replay ) ;
if ( ret ) {
DEBUG ( 1 , ( " failed to decrypt setpw reply (%s) \n " , error_message ( ret ) ) ) ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
if ( clearresult . length < 2 ) {
2001-12-20 06:54:52 +03:00
free ( clearresult . data ) ;
2001-11-24 17:16:41 +03:00
ret = KRB5KRB_AP_ERR_MODIFIED ;
return KRB5KRB_AP_ERR_MODIFIED ;
}
p = clearresult . data ;
res_code = RSVAL ( p , 0 ) ;
2001-12-20 06:54:52 +03:00
free ( clearresult . data ) ;
2001-11-24 17:16:41 +03:00
if ( ( res_code < KRB5_KPASSWD_SUCCESS ) | |
2001-12-20 06:54:52 +03:00
( res_code > = KRB5_KPASSWD_ACCESSDENIED ) ) {
2001-11-24 17:16:41 +03:00
return KRB5KRB_AP_ERR_MODIFIED ;
}
return 0 ;
}
2002-09-25 19:19:00 +04:00
ADS_STATUS krb5_set_password ( const char * kdc_host , const char * princ , const char * newpw ,
int time_offset )
2001-11-24 17:16:41 +03:00
{
krb5_context context ;
krb5_auth_context auth_context = NULL ;
krb5_principal principal ;
char * princ_name ;
2001-12-20 06:54:52 +03:00
char * realm ;
2001-11-24 17:16:41 +03:00
krb5_creds creds , * credsp ;
krb5_ccache ccache ;
krb5_data ap_req , chpw_req , chpw_rep ;
int ret , sock , addr_len ;
struct sockaddr remote_addr , local_addr ;
krb5_address local_kaddr , remote_kaddr ;
ret = krb5_init_context ( & context ) ;
if ( ret ) {
DEBUG ( 1 , ( " Failed to init krb5 context (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
2002-09-25 19:19:00 +04:00
if ( time_offset ! = 0 ) {
krb5_set_real_time ( context , time ( NULL ) + time_offset , 0 ) ;
}
2001-11-24 17:16:41 +03:00
ret = krb5_cc_default ( context , & ccache ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " Failed to get default creds (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
ZERO_STRUCT ( creds ) ;
2001-12-20 06:54:52 +03:00
realm = strchr ( princ , ' @ ' ) ;
realm + + ;
2001-11-24 17:16:41 +03:00
asprintf ( & princ_name , " kadmin/changepw@%s " , realm ) ;
ret = krb5_parse_name ( context , princ_name , & creds . server ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " Failed to parse kadmin/changepw (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
free ( princ_name ) ;
2001-12-20 06:54:52 +03:00
/* parse the principal we got as a function argument */
ret = krb5_parse_name ( context , princ , & principal ) ;
2001-11-24 17:16:41 +03:00
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " Failed to parse %s (%s) \n " , princ_name , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
krb5_princ_set_realm ( context , creds . server ,
krb5_princ_realm ( context , principal ) ) ;
ret = krb5_cc_get_principal ( context , ccache , & creds . client ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " Failed to get principal from ccache (%s) \n " ,
error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
ret = krb5_get_credentials ( context , 0 , ccache , & creds , & credsp ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " krb5_get_credentials failed (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
2002-01-01 05:31:32 +03:00
/* we might have to call krb5_free_creds(...) from now on ... */
2001-11-24 17:16:41 +03:00
ret = krb5_mk_req_extended ( context , & auth_context , AP_OPTS_USE_SUBKEY ,
NULL , credsp , & ap_req ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " krb5_mk_req_extended failed (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
sock = open_udp_socket ( kdc_host , DEFAULT_KPASSWD_PORT ) ;
if ( sock = = - 1 ) {
2001-12-19 15:21:12 +03:00
int rc = errno ;
2001-12-20 06:54:52 +03:00
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " failed to open kpasswd socket to %s (%s) \n " ,
kdc_host , strerror ( errno ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_SYSTEM ( rc ) ;
2001-11-24 17:16:41 +03:00
}
addr_len = sizeof ( remote_addr ) ;
getpeername ( sock , & remote_addr , & addr_len ) ;
addr_len = sizeof ( local_addr ) ;
getsockname ( sock , & local_addr , & addr_len ) ;
2003-01-21 09:23:49 +03:00
setup_kaddr ( & remote_kaddr , & remote_addr ) ;
setup_kaddr ( & local_kaddr , & local_addr ) ;
2001-11-24 17:16:41 +03:00
ret = krb5_auth_con_setaddrs ( context , auth_context , & local_kaddr , NULL ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
close ( sock ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " krb5_auth_con_setaddrs failed (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
ret = build_setpw_request ( context , auth_context , & ap_req ,
2001-12-20 06:54:52 +03:00
princ , newpw , & chpw_req ) ;
2001-11-24 17:16:41 +03:00
if ( ret ) {
2001-12-20 06:54:52 +03:00
close ( sock ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " build_setpw_request failed (%s) \n " , error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
if ( write ( sock , chpw_req . data , chpw_req . length ) ! = chpw_req . length ) {
2001-12-20 06:54:52 +03:00
close ( sock ) ;
free ( chpw_req . data ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " send of chpw failed (%s) \n " , strerror ( errno ) ) ) ;
2001-12-31 14:14:38 +03:00
return ADS_ERROR_SYSTEM ( errno ) ;
2001-11-24 17:16:41 +03:00
}
free ( chpw_req . data ) ;
chpw_rep . length = 1500 ;
chpw_rep . data = ( char * ) malloc ( chpw_rep . length ) ;
2003-02-05 02:44:28 +03:00
if ( ! chpw_rep . data ) {
close ( sock ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
DEBUG ( 1 , ( " send of chpw failed (%s) \n " , strerror ( errno ) ) ) ;
errno = ENOMEM ;
return ADS_ERROR_SYSTEM ( errno ) ;
}
2001-11-24 17:16:41 +03:00
ret = read ( sock , chpw_rep . data , chpw_rep . length ) ;
if ( ret < 0 ) {
2001-12-20 06:54:52 +03:00
close ( sock ) ;
free ( chpw_rep . data ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
DEBUG ( 1 , ( " recv of chpw reply failed (%s) \n " , strerror ( errno ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_SYSTEM ( errno ) ;
2001-11-24 17:16:41 +03:00
}
close ( sock ) ;
chpw_rep . length = ret ;
ret = krb5_auth_con_setaddrs ( context , auth_context , NULL , & remote_kaddr ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
free ( chpw_rep . data ) ;
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " krb5_auth_con_setaddrs on reply failed (%s) \n " ,
error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
ret = parse_setpw_reply ( context , auth_context , & chpw_rep ) ;
free ( chpw_rep . data ) ;
if ( ret ) {
2001-12-20 06:54:52 +03:00
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-11-24 17:16:41 +03:00
DEBUG ( 1 , ( " parse_setpw_reply failed (%s) \n " ,
error_message ( ret ) ) ) ;
2001-12-19 15:21:12 +03:00
return ADS_ERROR_KRB5 ( ret ) ;
2001-11-24 17:16:41 +03:00
}
2001-12-20 06:54:52 +03:00
free ( ap_req . data ) ;
krb5_free_creds ( context , credsp ) ;
krb5_free_principal ( context , creds . client ) ;
krb5_free_principal ( context , principal ) ;
krb5_free_context ( context ) ;
2001-12-19 15:21:12 +03:00
return ADS_SUCCESS ;
2001-11-24 17:16:41 +03:00
}
2001-12-20 06:54:52 +03:00
ADS_STATUS kerberos_set_password ( const char * kpasswd_server ,
const char * auth_principal , const char * auth_password ,
2002-09-25 19:19:00 +04:00
const char * target_principal , const char * new_password ,
int time_offset )
2001-12-20 06:54:52 +03:00
{
int ret ;
2002-09-25 19:19:00 +04:00
if ( ( ret = kerberos_kinit_password ( auth_principal , auth_password , time_offset ) ) ) {
2001-12-20 06:54:52 +03:00
DEBUG ( 1 , ( " Failed kinit for principal %s (%s) \n " , auth_principal , error_message ( ret ) ) ) ;
return ADS_ERROR_KRB5 ( ret ) ;
}
2002-09-25 19:19:00 +04:00
return krb5_set_password ( kpasswd_server , target_principal , new_password , time_offset ) ;
2001-12-20 06:54:52 +03:00
}
2002-10-01 22:26:00 +04:00
/**
* Set the machine account password
* @ param ads connection to ads server
* @ param hostname machine whose password is being set
* @ param password new password
* @ return status of password change
* */
ADS_STATUS ads_set_machine_password ( ADS_STRUCT * ads ,
const char * hostname ,
const char * password )
{
ADS_STATUS status ;
char * host = strdup ( hostname ) ;
char * principal ;
strlower ( host ) ;
/*
we need to use the ' $ ' form of the name here , as otherwise the
server might end up setting the password for a user instead
*/
asprintf ( & principal , " %s$@%s " , host , ads - > auth . realm ) ;
status = krb5_set_password ( ads - > auth . kdc_server , principal , password , ads - > auth . time_offset ) ;
free ( host ) ;
free ( principal ) ;
return status ;
}
2001-11-24 17:16:41 +03:00
# endif