2005-04-25 09:03:50 +04:00
/*
Unix SMB / Netbios implementation .
Version 3.0
handle NLTMSSP , client server side parsing
Copyright ( C ) Andrew Tridgell 2001
Copyright ( C ) Andrew Bartlett < abartlet @ samba . org > 2001 - 2005
Copyright ( C ) Stefan Metzmacher 2005
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-10 06:07:03 +04:00
the Free Software Foundation ; either version 3 of the License , or
2005-04-25 09:03:50 +04: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 06:07:03 +04:00
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2005-04-25 09:03:50 +04:00
*/
# include "includes.h"
# include "auth/ntlmssp/ntlmssp.h"
2006-03-09 15:42:45 +03:00
# include "auth/ntlmssp/msrpc_parse.h"
2005-04-25 09:03:50 +04:00
# include "lib/crypto/crypto.h"
2005-06-15 05:08:54 +04:00
# include "system/filesys.h"
2006-03-14 18:03:25 +03:00
# include "libcli/auth/libcli_auth.h"
2006-11-07 03:48:36 +03:00
# include "auth/credentials/credentials.h"
# include "auth/gensec/gensec.h"
# include "auth/auth.h"
2008-05-05 13:28:38 +04:00
# include "auth/ntlm/auth_proto.h"
2007-09-08 16:42:09 +04:00
# include "param/param.h"
2008-04-02 06:53:27 +04:00
# include "auth/session_proto.h"
2005-04-25 09:03:50 +04:00
2005-04-25 10:33:20 +04:00
/**
* Set a username on an NTLMSSP context - ensures it is talloc ( ) ed
*
*/
2005-04-25 14:33:00 +04:00
static NTSTATUS ntlmssp_set_username ( struct gensec_ntlmssp_state * gensec_ntlmssp_state , const char * user )
2005-04-25 10:33:20 +04:00
{
if ( ! user ) {
/* it should be at least "" */
DEBUG ( 1 , ( " NTLMSSP failed to set username - cannot accept NULL username \n " ) ) ;
return NT_STATUS_INVALID_PARAMETER ;
}
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > user = talloc_strdup ( gensec_ntlmssp_state , user ) ;
if ( ! gensec_ntlmssp_state - > user ) {
2005-04-25 10:33:20 +04:00
return NT_STATUS_NO_MEMORY ;
}
return NT_STATUS_OK ;
}
/**
* Set a domain on an NTLMSSP context - ensures it is talloc ( ) ed
*
*/
2005-04-25 14:33:00 +04:00
static NTSTATUS ntlmssp_set_domain ( struct gensec_ntlmssp_state * gensec_ntlmssp_state , const char * domain )
2005-04-25 10:33:20 +04:00
{
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > domain = talloc_strdup ( gensec_ntlmssp_state , domain ) ;
if ( ! gensec_ntlmssp_state - > domain ) {
2005-04-25 10:33:20 +04:00
return NT_STATUS_NO_MEMORY ;
}
return NT_STATUS_OK ;
}
/**
* Set a workstation on an NTLMSSP context - ensures it is talloc ( ) ed
*
*/
2005-04-25 14:33:00 +04:00
static NTSTATUS ntlmssp_set_workstation ( struct gensec_ntlmssp_state * gensec_ntlmssp_state , const char * workstation )
2005-04-25 10:33:20 +04:00
{
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > workstation = talloc_strdup ( gensec_ntlmssp_state , workstation ) ;
if ( ! gensec_ntlmssp_state - > workstation ) {
2005-04-25 10:33:20 +04:00
return NT_STATUS_NO_MEMORY ;
}
return NT_STATUS_OK ;
}
2005-04-25 09:03:50 +04:00
/**
* Determine correct target name flags for reply , given server role
* and negotiated flags
*
2005-04-25 14:33:00 +04:00
* @ param gensec_ntlmssp_state NTLMSSP State
2005-04-25 09:03:50 +04:00
* @ param neg_flags The flags from the packet
* @ param chal_flags The flags to be set in the reply packet
* @ return The ' target name ' string .
*/
2005-04-25 14:33:00 +04:00
static const char * ntlmssp_target_name ( struct gensec_ntlmssp_state * gensec_ntlmssp_state ,
2005-04-25 09:03:50 +04:00
uint32_t neg_flags , uint32_t * chal_flags )
{
if ( neg_flags & NTLMSSP_REQUEST_TARGET ) {
* chal_flags | = NTLMSSP_CHAL_TARGET_INFO ;
* chal_flags | = NTLMSSP_REQUEST_TARGET ;
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > server_role = = ROLE_STANDALONE ) {
2005-04-25 09:03:50 +04:00
* chal_flags | = NTLMSSP_TARGET_TYPE_SERVER ;
2005-04-25 14:33:00 +04:00
return gensec_ntlmssp_state - > server_name ;
2005-04-25 09:03:50 +04:00
} else {
* chal_flags | = NTLMSSP_TARGET_TYPE_DOMAIN ;
2007-09-28 05:17:46 +04:00
return gensec_ntlmssp_state - > domain ;
2005-04-25 09:03:50 +04:00
} ;
} else {
return " " ;
}
}
2005-06-15 05:08:54 +04:00
2005-04-25 09:03:50 +04:00
/**
* Next state function for the Negotiate packet
*
2005-04-25 14:33:00 +04:00
* @ param gensec_security GENSEC state
* @ param out_mem_ctx Memory context for * out
* @ param in The request , as a DATA_BLOB . reply . data must be NULL
* @ param out The reply , as an allocated DATA_BLOB , caller to free .
* @ return Errors or MORE_PROCESSING_REQUIRED if ( normal ) a reply is required .
2005-04-25 09:03:50 +04:00
*/
2005-04-25 10:33:20 +04:00
NTSTATUS ntlmssp_server_negotiate ( struct gensec_security * gensec_security ,
2005-04-25 09:03:50 +04:00
TALLOC_CTX * out_mem_ctx ,
const DATA_BLOB in , DATA_BLOB * out )
{
2007-09-07 19:08:14 +04:00
struct gensec_ntlmssp_state * gensec_ntlmssp_state = ( struct gensec_ntlmssp_state * ) gensec_security - > private_data ;
2005-04-25 09:03:50 +04:00
DATA_BLOB struct_blob ;
2007-09-07 20:30:06 +04:00
char dnsname [ MAXHOSTNAMELEN ] , dnsdomname [ MAXHOSTNAMELEN ] ;
const char * p ;
2005-04-25 09:03:50 +04:00
uint32_t neg_flags = 0 ;
uint32_t ntlmssp_command , chal_flags ;
const uint8_t * cryptkey ;
const char * target_name ;
/* parse the NTLMSSP packet */
#if 0
file_save ( " ntlmssp_negotiate.dat " , request . data , request . length ) ;
# endif
if ( in . length ) {
2008-01-04 02:22:04 +03:00
if ( ( in . length < 16 ) | | ! msrpc_parse ( out_mem_ctx ,
lp_iconv_convenience ( gensec_security - > lp_ctx ) ,
& in , " Cdd " ,
2005-10-15 04:48:47 +04:00
" NTLMSSP " ,
& ntlmssp_command ,
& neg_flags ) ) {
DEBUG ( 1 , ( " ntlmssp_server_negotiate: failed to parse "
" NTLMSSP Negotiate of length %u: \n " ,
( unsigned int ) in . length ) ) ;
2005-04-25 09:03:50 +04:00
dump_data ( 2 , in . data , in . length ) ;
return NT_STATUS_INVALID_PARAMETER ;
}
debug_ntlmssp_flags ( neg_flags ) ;
}
2005-04-25 14:33:00 +04:00
ntlmssp_handle_neg_flags ( gensec_ntlmssp_state , neg_flags , gensec_ntlmssp_state - > allow_lm_key ) ;
2005-04-25 09:03:50 +04:00
/* Ask our caller what challenge they would like in the packet */
2005-04-25 14:33:00 +04:00
cryptkey = gensec_ntlmssp_state - > get_challenge ( gensec_ntlmssp_state ) ;
2008-08-01 18:10:06 +04:00
if ( ! cryptkey ) {
DEBUG ( 1 , ( " ntlmssp_server_negotiate: backend doesn't give a challenge \n " ) ) ;
return NT_STATUS_INTERNAL_ERROR ;
}
2005-04-25 09:03:50 +04:00
/* Check if we may set the challenge */
2005-04-25 14:33:00 +04:00
if ( ! gensec_ntlmssp_state - > may_set_challenge ( gensec_ntlmssp_state ) ) {
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_NTLM2 ;
2005-04-25 09:03:50 +04:00
}
/* The flags we send back are not just the negotiated flags,
* they are also ' what is in this packet ' . Therfore , we
* operate on ' chal_flags ' from here on
*/
2005-04-25 14:33:00 +04:00
chal_flags = gensec_ntlmssp_state - > neg_flags ;
2005-04-25 09:03:50 +04:00
/* get the right name to fill in as 'target' */
2005-04-25 14:33:00 +04:00
target_name = ntlmssp_target_name ( gensec_ntlmssp_state ,
2005-04-25 09:03:50 +04:00
neg_flags , & chal_flags ) ;
if ( target_name = = NULL )
return NT_STATUS_INVALID_PARAMETER ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > chal = data_blob_talloc ( gensec_ntlmssp_state , cryptkey , 8 ) ;
gensec_ntlmssp_state - > internal_chal = data_blob_talloc ( gensec_ntlmssp_state , cryptkey , 8 ) ;
2005-04-25 09:03:50 +04:00
dnsname [ 0 ] = ' \0 ' ;
2007-09-07 20:30:06 +04:00
if ( gethostname ( dnsname , sizeof ( dnsname ) ) = = - 1 ) {
DEBUG ( 0 , ( " gethostname failed \n " ) ) ;
return NT_STATUS_UNSUCCESSFUL ;
}
/* This should be a 'netbios domain -> DNS domain' mapping */
p = strchr ( dnsname , ' . ' ) ;
if ( p ! = NULL ) {
safe_strcpy ( dnsdomname , p + 1 , sizeof ( dnsdomname ) ) ;
strlower_m ( dnsdomname ) ;
} else {
dnsdomname [ 0 ] = ' \0 ' ;
}
2005-04-25 09:03:50 +04:00
/* This creates the 'blob' of names that appears at the end of the packet */
if ( chal_flags & NTLMSSP_CHAL_TARGET_INFO )
{
const char * target_name_dns = " " ;
if ( chal_flags | = NTLMSSP_TARGET_TYPE_DOMAIN ) {
target_name_dns = dnsdomname ;
} else if ( chal_flags | = NTLMSSP_TARGET_TYPE_SERVER ) {
target_name_dns = dnsname ;
}
msrpc_gen ( out_mem_ctx ,
2008-01-04 02:22:04 +03:00
lp_iconv_convenience ( gensec_security - > lp_ctx ) ,
2005-04-25 09:03:50 +04:00
& struct_blob , " aaaaa " ,
NTLMSSP_NAME_TYPE_DOMAIN , target_name ,
2005-04-25 14:33:00 +04:00
NTLMSSP_NAME_TYPE_SERVER , gensec_ntlmssp_state - > server_name ,
2005-04-25 09:03:50 +04:00
NTLMSSP_NAME_TYPE_DOMAIN_DNS , dnsdomname ,
NTLMSSP_NAME_TYPE_SERVER_DNS , dnsname ,
0 , " " ) ;
} else {
struct_blob = data_blob ( NULL , 0 ) ;
}
{
/* Marshel the packet in the right format, be it unicode or ASCII */
const char * gen_string ;
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > unicode ) {
2005-04-25 09:03:50 +04:00
gen_string = " CdUdbddB " ;
} else {
gen_string = " CdAdbddB " ;
}
msrpc_gen ( out_mem_ctx ,
2008-01-04 02:22:04 +03:00
lp_iconv_convenience ( gensec_security - > lp_ctx ) ,
2005-04-25 09:03:50 +04:00
out , gen_string ,
" NTLMSSP " ,
NTLMSSP_CHALLENGE ,
target_name ,
chal_flags ,
cryptkey , 8 ,
0 , 0 ,
struct_blob . data , struct_blob . length ) ;
}
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > expected_state = NTLMSSP_AUTH ;
2005-04-25 09:03:50 +04:00
return NT_STATUS_MORE_PROCESSING_REQUIRED ;
}
/**
* Next state function for the Authenticate packet
*
2005-04-25 14:33:00 +04:00
* @ param gensec_ntlmssp_state NTLMSSP State
2005-04-25 09:03:50 +04:00
* @ param request The request , as a DATA_BLOB
* @ return Errors or NT_STATUS_OK .
*/
2005-04-25 14:33:00 +04:00
static NTSTATUS ntlmssp_server_preauth ( struct gensec_ntlmssp_state * gensec_ntlmssp_state ,
2005-04-25 09:03:50 +04:00
const DATA_BLOB request )
{
uint32_t ntlmssp_command , auth_flags ;
NTSTATUS nt_status ;
uint8_t session_nonce_hash [ 16 ] ;
const char * parse_string ;
char * domain = NULL ;
char * user = NULL ;
char * workstation = NULL ;
#if 0
file_save ( " ntlmssp_auth.dat " , request . data , request . length ) ;
# endif
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > unicode ) {
2005-04-25 09:03:50 +04:00
parse_string = " CdBBUUUBd " ;
} else {
parse_string = " CdBBAAABd " ;
}
/* zero these out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > lm_resp ) ;
data_blob_free ( & gensec_ntlmssp_state - > nt_resp ) ;
2005-04-25 14:58:46 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > user = NULL ;
gensec_ntlmssp_state - > domain = NULL ;
gensec_ntlmssp_state - > workstation = NULL ;
2005-04-25 09:03:50 +04:00
/* now the NTLMSSP encoded auth hashes */
2005-04-25 14:33:00 +04:00
if ( ! msrpc_parse ( gensec_ntlmssp_state ,
2008-01-04 02:22:04 +03:00
lp_iconv_convenience ( gensec_ntlmssp_state - > gensec_security - > lp_ctx ) ,
2005-04-25 09:03:50 +04:00
& request , parse_string ,
" NTLMSSP " ,
& ntlmssp_command ,
2005-04-25 14:33:00 +04:00
& gensec_ntlmssp_state - > lm_resp ,
& gensec_ntlmssp_state - > nt_resp ,
2005-04-25 09:03:50 +04:00
& domain ,
& user ,
& workstation ,
2005-04-25 14:33:00 +04:00
& gensec_ntlmssp_state - > encrypted_session_key ,
2005-04-25 09:03:50 +04:00
& auth_flags ) ) {
DEBUG ( 10 , ( " ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal): \n " ) ) ;
dump_data ( 10 , request . data , request . length ) ;
/* zero this out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
auth_flags = 0 ;
/* Try again with a shorter string (Win9X truncates this packet) */
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > unicode ) {
2005-04-25 09:03:50 +04:00
parse_string = " CdBBUUU " ;
} else {
parse_string = " CdBBAAA " ;
}
/* now the NTLMSSP encoded auth hashes */
2005-04-25 14:33:00 +04:00
if ( ! msrpc_parse ( gensec_ntlmssp_state ,
2008-01-04 02:22:04 +03:00
lp_iconv_convenience ( gensec_ntlmssp_state - > gensec_security - > lp_ctx ) ,
2005-04-25 09:03:50 +04:00
& request , parse_string ,
" NTLMSSP " ,
& ntlmssp_command ,
2005-04-25 14:33:00 +04:00
& gensec_ntlmssp_state - > lm_resp ,
& gensec_ntlmssp_state - > nt_resp ,
2005-04-25 09:03:50 +04:00
& domain ,
& user ,
& workstation ) ) {
DEBUG ( 1 , ( " ntlmssp_server_auth: failed to parse NTLMSSP: \n " ) ) ;
dump_data ( 2 , request . data , request . length ) ;
return NT_STATUS_INVALID_PARAMETER ;
}
}
if ( auth_flags )
2005-04-25 14:33:00 +04:00
ntlmssp_handle_neg_flags ( gensec_ntlmssp_state , auth_flags , gensec_ntlmssp_state - > allow_lm_key ) ;
2005-04-25 09:03:50 +04:00
2005-04-25 14:33:00 +04:00
if ( ! NT_STATUS_IS_OK ( nt_status = ntlmssp_set_domain ( gensec_ntlmssp_state , domain ) ) ) {
2005-04-25 09:03:50 +04:00
/* zero this out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
2005-04-25 14:33:00 +04:00
if ( ! NT_STATUS_IS_OK ( nt_status = ntlmssp_set_username ( gensec_ntlmssp_state , user ) ) ) {
2005-04-25 09:03:50 +04:00
/* zero this out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
2005-04-25 14:33:00 +04:00
if ( ! NT_STATUS_IS_OK ( nt_status = ntlmssp_set_workstation ( gensec_ntlmssp_state , workstation ) ) ) {
2005-04-25 09:03:50 +04:00
/* zero this out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
DEBUG ( 3 , ( " Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu \n " ,
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > user , gensec_ntlmssp_state - > domain , gensec_ntlmssp_state - > workstation , ( unsigned long ) gensec_ntlmssp_state - > lm_resp . length , ( unsigned long ) gensec_ntlmssp_state - > nt_resp . length ) ) ;
2005-04-25 09:03:50 +04:00
#if 0
2005-04-25 14:33:00 +04:00
file_save ( " nthash1.dat " , & gensec_ntlmssp_state - > nt_resp . data , & gensec_ntlmssp_state - > nt_resp . length ) ;
file_save ( " lmhash1.dat " , & gensec_ntlmssp_state - > lm_resp . data , & gensec_ntlmssp_state - > lm_resp . length ) ;
2005-04-25 09:03:50 +04:00
# endif
/* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a
client challenge
However , the NTLM2 flag may still be set for the real NTLMv2 logins , be careful .
*/
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > neg_flags & NTLMSSP_NEGOTIATE_NTLM2 ) {
if ( gensec_ntlmssp_state - > nt_resp . length = = 24 & & gensec_ntlmssp_state - > lm_resp . length = = 24 ) {
2005-04-25 09:03:50 +04:00
struct MD5Context md5_session_nonce_ctx ;
2005-04-25 14:33:00 +04:00
SMB_ASSERT ( gensec_ntlmssp_state - > internal_chal . data
& & gensec_ntlmssp_state - > internal_chal . length = = 8 ) ;
2005-04-25 09:03:50 +04:00
2007-10-07 02:16:19 +04:00
gensec_ntlmssp_state - > doing_ntlm2 = true ;
2005-04-25 09:03:50 +04:00
2005-05-11 23:22:22 +04:00
memcpy ( gensec_ntlmssp_state - > crypt . ntlm2 . session_nonce , gensec_ntlmssp_state - > internal_chal . data , 8 ) ;
memcpy ( & gensec_ntlmssp_state - > crypt . ntlm2 . session_nonce [ 8 ] , gensec_ntlmssp_state - > lm_resp . data , 8 ) ;
2005-04-25 09:03:50 +04:00
MD5Init ( & md5_session_nonce_ctx ) ;
2005-05-11 23:22:22 +04:00
MD5Update ( & md5_session_nonce_ctx , gensec_ntlmssp_state - > crypt . ntlm2 . session_nonce , 16 ) ;
2005-04-25 09:03:50 +04:00
MD5Final ( session_nonce_hash , & md5_session_nonce_ctx ) ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > chal = data_blob_talloc ( gensec_ntlmssp_state ,
2005-04-25 09:03:50 +04:00
session_nonce_hash , 8 ) ;
/* LM response is no longer useful, zero it out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > lm_resp ) ;
2005-04-25 09:03:50 +04:00
/* We changed the effective challenge - set it */
if ( ! NT_STATUS_IS_OK ( nt_status =
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > set_challenge ( gensec_ntlmssp_state ,
2005-05-11 23:22:22 +04:00
& gensec_ntlmssp_state - > chal ) ) ) {
2005-04-25 09:03:50 +04:00
/* zero this out */
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
/* LM Key is incompatible... */
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_LM_KEY ;
2005-04-25 09:03:50 +04:00
}
}
return NT_STATUS_OK ;
}
/**
* Next state function for the Authenticate packet
* ( after authentication - figures out the session keys etc )
*
2005-04-25 14:33:00 +04:00
* @ param gensec_ntlmssp_state NTLMSSP State
2005-04-25 09:03:50 +04:00
* @ return Errors or NT_STATUS_OK .
*/
2005-05-11 23:22:22 +04:00
static NTSTATUS ntlmssp_server_postauth ( struct gensec_security * gensec_security ,
2005-04-25 09:03:50 +04:00
DATA_BLOB * user_session_key ,
DATA_BLOB * lm_session_key )
{
2007-09-07 19:08:14 +04:00
struct gensec_ntlmssp_state * gensec_ntlmssp_state = ( struct gensec_ntlmssp_state * ) gensec_security - > private_data ;
2005-04-25 09:03:50 +04:00
NTSTATUS nt_status ;
DATA_BLOB session_key = data_blob ( NULL , 0 ) ;
if ( user_session_key )
dump_data_pw ( " USER session key: \n " , user_session_key - > data , user_session_key - > length ) ;
if ( lm_session_key )
dump_data_pw ( " LM first-8: \n " , lm_session_key - > data , lm_session_key - > length ) ;
/* Handle the different session key derivation for NTLM2 */
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > doing_ntlm2 ) {
2005-04-25 09:03:50 +04:00
if ( user_session_key & & user_session_key - > data & & user_session_key - > length = = 16 ) {
2005-04-25 14:33:00 +04:00
session_key = data_blob_talloc ( gensec_ntlmssp_state , NULL , 16 ) ;
2005-05-11 23:22:22 +04:00
hmac_md5 ( user_session_key - > data , gensec_ntlmssp_state - > crypt . ntlm2 . session_nonce ,
sizeof ( gensec_ntlmssp_state - > crypt . ntlm2 . session_nonce ) , session_key . data ) ;
2005-04-25 09:03:50 +04:00
DEBUG ( 10 , ( " ntlmssp_server_auth: Created NTLM2 session key. \n " ) ) ;
dump_data_pw ( " NTLM2 session key: \n " , session_key . data , session_key . length ) ;
} else {
DEBUG ( 10 , ( " ntlmssp_server_auth: Failed to create NTLM2 session key. \n " ) ) ;
session_key = data_blob ( NULL , 0 ) ;
}
2005-04-25 14:33:00 +04:00
} else if ( ( gensec_ntlmssp_state - > neg_flags & NTLMSSP_NEGOTIATE_LM_KEY )
2005-04-25 09:03:50 +04:00
/* Ensure we can never get here on NTLMv2 */
2005-04-25 14:33:00 +04:00
& & ( gensec_ntlmssp_state - > nt_resp . length = = 0 | | gensec_ntlmssp_state - > nt_resp . length = = 24 ) ) {
2005-04-25 09:03:50 +04:00
if ( lm_session_key & & lm_session_key - > data & & lm_session_key - > length > = 8 ) {
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > lm_resp . data & & gensec_ntlmssp_state - > lm_resp . length = = 24 ) {
session_key = data_blob_talloc ( gensec_ntlmssp_state , NULL , 16 ) ;
SMBsesskeygen_lm_sess_key ( lm_session_key - > data , gensec_ntlmssp_state - > lm_resp . data ,
2005-04-25 09:03:50 +04:00
session_key . data ) ;
DEBUG ( 10 , ( " ntlmssp_server_auth: Created NTLM session key. \n " ) ) ;
dump_data_pw ( " LM session key: \n " , session_key . data , session_key . length ) ;
} else {
/* When there is no LM response, just use zeros */
static const uint8_t zeros [ 24 ] ;
2005-04-25 14:33:00 +04:00
session_key = data_blob_talloc ( gensec_ntlmssp_state , NULL , 16 ) ;
2005-04-25 09:03:50 +04:00
SMBsesskeygen_lm_sess_key ( zeros , zeros ,
session_key . data ) ;
DEBUG ( 10 , ( " ntlmssp_server_auth: Created NTLM session key. \n " ) ) ;
dump_data_pw ( " LM session key: \n " , session_key . data , session_key . length ) ;
}
} else {
/* LM Key not selected */
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_LM_KEY ;
2005-04-25 09:03:50 +04:00
DEBUG ( 10 , ( " ntlmssp_server_auth: Failed to create NTLM session key. \n " ) ) ;
session_key = data_blob ( NULL , 0 ) ;
}
} else if ( user_session_key & & user_session_key - > data ) {
session_key = * user_session_key ;
DEBUG ( 10 , ( " ntlmssp_server_auth: Using unmodified nt session key. \n " ) ) ;
dump_data_pw ( " unmodified session key: \n " , session_key . data , session_key . length ) ;
/* LM Key not selected */
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_LM_KEY ;
2005-04-25 09:03:50 +04:00
} else if ( lm_session_key & & lm_session_key - > data ) {
/* Very weird to have LM key, but no user session key, but anyway.. */
session_key = * lm_session_key ;
DEBUG ( 10 , ( " ntlmssp_server_auth: Using unmodified lm session key. \n " ) ) ;
dump_data_pw ( " unmodified session key: \n " , session_key . data , session_key . length ) ;
/* LM Key not selected */
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_LM_KEY ;
2005-04-25 09:03:50 +04:00
} else {
DEBUG ( 10 , ( " ntlmssp_server_auth: Failed to create unmodified session key. \n " ) ) ;
session_key = data_blob ( NULL , 0 ) ;
/* LM Key not selected */
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags & = ~ NTLMSSP_NEGOTIATE_LM_KEY ;
2005-04-25 09:03:50 +04:00
}
/* With KEY_EXCH, the client supplies the proposed session key,
but encrypts it with the long - term key */
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH ) {
if ( ! gensec_ntlmssp_state - > encrypted_session_key . data
| | gensec_ntlmssp_state - > encrypted_session_key . length ! = 16 ) {
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
DEBUG ( 1 , ( " Client-supplied KEY_EXCH session key was of invalid length (%u)! \n " ,
2005-07-17 13:20:52 +04:00
( unsigned ) gensec_ntlmssp_state - > encrypted_session_key . length ) ) ;
2005-04-25 09:03:50 +04:00
return NT_STATUS_INVALID_PARAMETER ;
} else if ( ! session_key . data | | session_key . length ! = 16 ) {
DEBUG ( 5 , ( " server session key is invalid (len == %u), cannot do KEY_EXCH! \n " ,
2005-07-17 13:20:52 +04:00
( unsigned ) session_key . length ) ) ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > session_key = session_key ;
2005-04-25 09:03:50 +04:00
} else {
dump_data_pw ( " KEY_EXCH session key (enc): \n " ,
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > encrypted_session_key . data ,
gensec_ntlmssp_state - > encrypted_session_key . length ) ;
arcfour_crypt ( gensec_ntlmssp_state - > encrypted_session_key . data ,
2005-04-25 09:03:50 +04:00
session_key . data ,
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > encrypted_session_key . length ) ;
gensec_ntlmssp_state - > session_key = data_blob_talloc ( gensec_ntlmssp_state ,
gensec_ntlmssp_state - > encrypted_session_key . data ,
gensec_ntlmssp_state - > encrypted_session_key . length ) ;
dump_data_pw ( " KEY_EXCH session key: \n " , gensec_ntlmssp_state - > encrypted_session_key . data ,
gensec_ntlmssp_state - > encrypted_session_key . length ) ;
2005-04-25 09:03:50 +04:00
}
} else {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > session_key = session_key ;
2005-04-25 09:03:50 +04:00
}
2006-01-31 06:15:16 +03:00
/* keep the session key around on the new context */
talloc_steal ( gensec_ntlmssp_state , session_key . data ) ;
2005-05-11 23:22:22 +04:00
if ( ( gensec_security - > want_features & GENSEC_FEATURE_SIGN )
| | ( gensec_security - > want_features & GENSEC_FEATURE_SEAL ) ) {
nt_status = ntlmssp_sign_init ( gensec_ntlmssp_state ) ;
} else {
nt_status = NT_STATUS_OK ;
}
2005-04-25 09:03:50 +04:00
2005-04-25 14:33:00 +04:00
data_blob_free ( & gensec_ntlmssp_state - > encrypted_session_key ) ;
2005-04-25 09:03:50 +04:00
/* allow arbitarily many authentications, but watch that this will cause a
2005-04-25 14:33:00 +04:00
memory leak , until the gensec_ntlmssp_state is shutdown
2005-04-25 09:03:50 +04:00
*/
2005-04-25 14:33:00 +04:00
if ( gensec_ntlmssp_state - > server_multiple_authentications ) {
gensec_ntlmssp_state - > expected_state = NTLMSSP_AUTH ;
2005-04-25 09:03:50 +04:00
} else {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > expected_state = NTLMSSP_DONE ;
2005-04-25 09:03:50 +04:00
}
return nt_status ;
}
/**
* Next state function for the Authenticate packet
*
2005-04-25 14:33:00 +04:00
* @ param gensec_security GENSEC state
* @ param out_mem_ctx Memory context for * out
* @ param in The request , as a DATA_BLOB . reply . data must be NULL
2005-04-25 09:03:50 +04:00
* @ param out The reply , as an allocated DATA_BLOB , caller to free .
2005-04-25 14:33:00 +04:00
* @ return Errors or NT_STATUS_OK if authentication sucessful
2005-04-25 09:03:50 +04:00
*/
2005-04-25 10:33:20 +04:00
NTSTATUS ntlmssp_server_auth ( struct gensec_security * gensec_security ,
2005-04-25 09:03:50 +04:00
TALLOC_CTX * out_mem_ctx ,
const DATA_BLOB in , DATA_BLOB * out )
2005-05-11 23:22:22 +04:00
{
2007-09-07 19:08:14 +04:00
struct gensec_ntlmssp_state * gensec_ntlmssp_state = ( struct gensec_ntlmssp_state * ) gensec_security - > private_data ;
2005-04-25 09:03:50 +04:00
DATA_BLOB user_session_key = data_blob ( NULL , 0 ) ;
DATA_BLOB lm_session_key = data_blob ( NULL , 0 ) ;
NTSTATUS nt_status ;
2006-01-31 06:15:16 +03:00
TALLOC_CTX * mem_ctx = talloc_new ( out_mem_ctx ) ;
if ( ! mem_ctx ) {
return NT_STATUS_NO_MEMORY ;
}
2005-04-25 09:03:50 +04:00
/* zero the outbound NTLMSSP packet */
* out = data_blob_talloc ( out_mem_ctx , NULL , 0 ) ;
2005-04-25 14:33:00 +04:00
if ( ! NT_STATUS_IS_OK ( nt_status = ntlmssp_server_preauth ( gensec_ntlmssp_state , in ) ) ) {
2006-01-31 06:15:16 +03:00
talloc_free ( mem_ctx ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
/*
* Note we don ' t check here for NTLMv2 auth settings . If NTLMv2 auth
* is required ( by " ntlm auth = no " and " lm auth = no " being set in the
* smb . conf file ) and no NTLMv2 response was sent then the password check
* will fail here . JRA .
*/
/* Finally, actually ask if the password is OK */
2006-01-31 06:15:16 +03:00
if ( ! NT_STATUS_IS_OK ( nt_status = gensec_ntlmssp_state - > check_password ( gensec_ntlmssp_state , mem_ctx ,
2005-05-16 03:40:22 +04:00
& user_session_key , & lm_session_key ) ) ) {
2006-01-31 06:15:16 +03:00
talloc_free ( mem_ctx ) ;
2005-04-25 09:03:50 +04:00
return nt_status ;
}
2005-05-16 03:40:22 +04:00
if ( gensec_security - > want_features
& ( GENSEC_FEATURE_SIGN | GENSEC_FEATURE_SEAL | GENSEC_FEATURE_SESSION_KEY ) ) {
2006-01-31 06:15:16 +03:00
nt_status = ntlmssp_server_postauth ( gensec_security , & user_session_key , & lm_session_key ) ;
talloc_free ( mem_ctx ) ;
return nt_status ;
2005-04-25 09:03:50 +04:00
} else {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > session_key = data_blob ( NULL , 0 ) ;
2006-01-31 06:15:16 +03:00
talloc_free ( mem_ctx ) ;
2005-04-25 09:03:50 +04:00
return NT_STATUS_OK ;
}
}
/**
* Return the challenge as determined by the authentication subsystem
* @ return an 8 byte random challenge
*/
2005-04-25 14:33:00 +04:00
static const uint8_t * auth_ntlmssp_get_challenge ( const struct gensec_ntlmssp_state * gensec_ntlmssp_state )
2005-04-25 09:03:50 +04:00
{
NTSTATUS status ;
const uint8_t * chal ;
status = auth_get_challenge ( gensec_ntlmssp_state - > auth_context , & chal ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
2008-08-01 18:10:06 +04:00
DEBUG ( 1 , ( " auth_ntlmssp_get_challenge: failed to get challenge: %s \n " ,
nt_errstr ( status ) ) ) ;
2005-04-25 09:03:50 +04:00
return NULL ;
}
return chal ;
}
/**
* Some authentication methods ' fix ' the challenge , so we may not be able to set it
*
* @ return If the effective challenge used by the auth subsystem may be modified
*/
2007-10-07 02:16:19 +04:00
static bool auth_ntlmssp_may_set_challenge ( const struct gensec_ntlmssp_state * gensec_ntlmssp_state )
2005-04-25 09:03:50 +04:00
{
return auth_challenge_may_be_modified ( gensec_ntlmssp_state - > auth_context ) ;
}
/**
* NTLM2 authentication modifies the effective challenge ,
* @ param challenge The new challenge value
*/
2005-04-25 14:33:00 +04:00
static NTSTATUS auth_ntlmssp_set_challenge ( struct gensec_ntlmssp_state * gensec_ntlmssp_state , DATA_BLOB * challenge )
2005-04-25 09:03:50 +04:00
{
NTSTATUS nt_status ;
struct auth_context * auth_context = gensec_ntlmssp_state - > auth_context ;
const uint8_t * chal ;
if ( challenge - > length ! = 8 ) {
return NT_STATUS_INVALID_PARAMETER ;
}
chal = challenge - > data ;
nt_status = auth_context_set_challenge ( auth_context , chal , " NTLMSSP callback (NTLM2) " ) ;
return nt_status ;
}
/**
* Check the password on an NTLMSSP login .
*
* Return the session keys used on the connection .
*/
2006-01-31 06:15:16 +03:00
static NTSTATUS auth_ntlmssp_check_password ( struct gensec_ntlmssp_state * gensec_ntlmssp_state ,
TALLOC_CTX * mem_ctx ,
DATA_BLOB * user_session_key , DATA_BLOB * lm_session_key )
2005-04-25 09:03:50 +04:00
{
NTSTATUS nt_status ;
2006-01-31 06:15:16 +03:00
struct auth_usersupplied_info * user_info = talloc ( mem_ctx , struct auth_usersupplied_info ) ;
2005-07-22 08:10:07 +04:00
if ( ! user_info ) {
return NT_STATUS_NO_MEMORY ;
}
2005-04-25 09:03:50 +04:00
2005-10-28 12:54:37 +04:00
user_info - > logon_parameters = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT ;
2005-07-22 08:10:07 +04:00
user_info - > flags = 0 ;
2007-10-07 02:16:19 +04:00
user_info - > mapped_state = false ;
2005-07-22 08:10:07 +04:00
user_info - > client . account_name = gensec_ntlmssp_state - > user ;
user_info - > client . domain_name = gensec_ntlmssp_state - > domain ;
user_info - > workstation_name = gensec_ntlmssp_state - > workstation ;
2006-01-10 01:12:53 +03:00
user_info - > remote_host = gensec_get_peer_addr ( gensec_ntlmssp_state - > gensec_security ) ;
2005-07-22 08:10:07 +04:00
user_info - > password_state = AUTH_PASSWORD_RESPONSE ;
user_info - > password . response . lanman = gensec_ntlmssp_state - > lm_resp ;
user_info - > password . response . lanman . data = talloc_steal ( user_info , gensec_ntlmssp_state - > lm_resp . data ) ;
user_info - > password . response . nt = gensec_ntlmssp_state - > nt_resp ;
user_info - > password . response . nt . data = talloc_steal ( user_info , gensec_ntlmssp_state - > nt_resp . data ) ;
2005-04-25 09:03:50 +04:00
2006-01-31 06:15:16 +03:00
nt_status = auth_check_password ( gensec_ntlmssp_state - > auth_context , mem_ctx ,
2005-04-25 09:03:50 +04:00
user_info , & gensec_ntlmssp_state - > server_info ) ;
talloc_free ( user_info ) ;
NT_STATUS_NOT_OK_RETURN ( nt_status ) ;
2006-01-31 06:15:16 +03:00
talloc_steal ( gensec_ntlmssp_state , gensec_ntlmssp_state - > server_info ) ;
2005-04-25 09:03:50 +04:00
if ( gensec_ntlmssp_state - > server_info - > user_session_key . length ) {
2005-07-17 13:20:52 +04:00
DEBUG ( 10 , ( " Got NT session key of length %u \n " ,
( unsigned ) gensec_ntlmssp_state - > server_info - > user_session_key . length ) ) ;
2006-01-31 06:15:16 +03:00
if ( ! talloc_reference ( mem_ctx , gensec_ntlmssp_state - > server_info - > user_session_key . data ) ) {
return NT_STATUS_NO_MEMORY ;
}
* user_session_key = gensec_ntlmssp_state - > server_info - > user_session_key ;
2005-04-25 09:03:50 +04:00
}
if ( gensec_ntlmssp_state - > server_info - > lm_session_key . length ) {
2005-07-17 13:20:52 +04:00
DEBUG ( 10 , ( " Got LM session key of length %u \n " ,
( unsigned ) gensec_ntlmssp_state - > server_info - > lm_session_key . length ) ) ;
2006-01-31 06:15:16 +03:00
if ( ! talloc_reference ( mem_ctx , gensec_ntlmssp_state - > server_info - > lm_session_key . data ) ) {
return NT_STATUS_NO_MEMORY ;
}
* lm_session_key = gensec_ntlmssp_state - > server_info - > lm_session_key ;
2005-04-25 09:03:50 +04:00
}
return nt_status ;
}
/**
* Return the credentials of a logged on user , including session keys
* etc .
*
* Only valid after a successful authentication
*
* May only be called once per authentication .
*
*/
NTSTATUS gensec_ntlmssp_session_info ( struct gensec_security * gensec_security ,
struct auth_session_info * * session_info )
{
NTSTATUS nt_status ;
2007-09-07 19:08:14 +04:00
struct gensec_ntlmssp_state * gensec_ntlmssp_state = ( struct gensec_ntlmssp_state * ) gensec_security - > private_data ;
2005-04-25 09:03:50 +04:00
2008-04-17 14:23:44 +04:00
nt_status = auth_generate_session_info ( gensec_ntlmssp_state , gensec_security - > event_ctx , gensec_security - > lp_ctx , gensec_ntlmssp_state - > server_info , session_info ) ;
2005-04-25 09:03:50 +04:00
NT_STATUS_NOT_OK_RETURN ( nt_status ) ;
( * session_info ) - > session_key = data_blob_talloc ( * session_info ,
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > session_key . data ,
gensec_ntlmssp_state - > session_key . length ) ;
return NT_STATUS_OK ;
}
/**
* Start NTLMSSP on the server side
*
*/
NTSTATUS gensec_ntlmssp_server_start ( struct gensec_security * gensec_security )
{
NTSTATUS nt_status ;
struct gensec_ntlmssp_state * gensec_ntlmssp_state ;
nt_status = gensec_ntlmssp_start ( gensec_security ) ;
NT_STATUS_NOT_OK_RETURN ( nt_status ) ;
2007-09-07 19:08:14 +04:00
gensec_ntlmssp_state = ( struct gensec_ntlmssp_state * ) gensec_security - > private_data ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > role = NTLMSSP_SERVER ;
gensec_ntlmssp_state - > workstation = NULL ;
2007-12-03 19:41:50 +03:00
gensec_ntlmssp_state - > server_name = lp_netbios_name ( gensec_security - > lp_ctx ) ;
2005-04-25 14:33:00 +04:00
2007-12-03 19:41:50 +03:00
gensec_ntlmssp_state - > domain = lp_workgroup ( gensec_security - > lp_ctx ) ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > expected_state = NTLMSSP_NEGOTIATE ;
2007-12-03 19:41:50 +03:00
gensec_ntlmssp_state - > allow_lm_key = ( lp_lanman_auth ( gensec_security - > lp_ctx )
& & lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " allow_lm_key " , false ) ) ;
2005-04-25 14:33:00 +04:00
2007-10-07 02:16:19 +04:00
gensec_ntlmssp_state - > server_multiple_authentications = false ;
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags =
2006-07-12 04:02:50 +04:00
NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_UNKNOWN_02000000 ;
2005-04-25 14:33:00 +04:00
2005-04-25 14:58:46 +04:00
gensec_ntlmssp_state - > lm_resp = data_blob ( NULL , 0 ) ;
gensec_ntlmssp_state - > nt_resp = data_blob ( NULL , 0 ) ;
gensec_ntlmssp_state - > encrypted_session_key = data_blob ( NULL , 0 ) ;
2007-12-03 19:41:50 +03:00
if ( lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " 128bit " , true ) ) {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_128 ;
}
2007-12-03 19:41:50 +03:00
if ( lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " 56bit " , true ) ) {
2006-07-12 04:02:50 +04:00
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_56 ;
}
2007-12-03 19:41:50 +03:00
if ( lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " keyexchange " , true ) ) {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_KEY_EXCH ;
}
2007-12-03 19:41:50 +03:00
if ( lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " alwayssign " , true ) ) {
2006-11-20 23:58:00 +03:00
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_ALWAYS_SIGN ;
}
2007-12-03 19:41:50 +03:00
if ( lp_parm_bool ( gensec_security - > lp_ctx , NULL , " ntlmssp_server " , " ntlm2 " , true ) ) {
2005-04-25 14:33:00 +04:00
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_NTLM2 ;
}
if ( gensec_security - > want_features & GENSEC_FEATURE_SIGN ) {
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_SIGN ;
}
if ( gensec_security - > want_features & GENSEC_FEATURE_SEAL ) {
gensec_ntlmssp_state - > neg_flags | = NTLMSSP_NEGOTIATE_SEAL ;
}
2007-07-03 12:05:55 +04:00
nt_status = auth_context_create ( gensec_ntlmssp_state ,
2006-07-31 18:05:08 +04:00
gensec_security - > event_ctx ,
gensec_security - > msg_ctx ,
2007-12-03 19:41:50 +03:00
gensec_security - > lp_ctx ,
2006-07-31 18:05:08 +04:00
& gensec_ntlmssp_state - > auth_context ) ;
2005-04-25 14:33:00 +04:00
NT_STATUS_NOT_OK_RETURN ( nt_status ) ;
gensec_ntlmssp_state - > get_challenge = auth_ntlmssp_get_challenge ;
gensec_ntlmssp_state - > may_set_challenge = auth_ntlmssp_may_set_challenge ;
gensec_ntlmssp_state - > set_challenge = auth_ntlmssp_set_challenge ;
gensec_ntlmssp_state - > check_password = auth_ntlmssp_check_password ;
2007-12-03 19:41:50 +03:00
gensec_ntlmssp_state - > server_role = lp_server_role ( gensec_security - > lp_ctx ) ;
2005-04-25 09:03:50 +04:00
return NT_STATUS_OK ;
}
2005-04-25 14:33:00 +04:00