2006-08-19 05:04:54 +04:00
/*
Unix SMB / CIFS implementation .
Winbind daemon - cached credentials funcions
Copyright ( C ) Robert O ' Callahan 2006
Copyright ( C ) Jeremy Allison 2006 ( minor fixes to fit into Samba and
protect against integer wrap ) .
2011-12-27 07:59:17 +04:00
Copyright ( C ) Andrew Bartlett 2011
2009-12-21 17:11:55 +03:00
2006-08-19 05:04:54 +04: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
2006-08-19 05:04:54 +04:00
( at your option ) any later version .
2009-12-21 17:11:55 +03:00
2006-08-19 05:04:54 +04:00
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 .
2009-12-21 17:11:55 +03:00
2006-08-19 05:04:54 +04:00
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/>.
2006-08-19 05:04:54 +04:00
*/
# include "includes.h"
# include "winbindd.h"
2011-12-27 07:59:17 +04:00
# include "auth/gensec/gensec.h"
# include "auth_generic.h"
2006-08-19 05:04:54 +04:00
# undef DBGC_CLASS
# define DBGC_CLASS DBGC_WINBIND
2007-10-19 04:40:25 +04:00
static bool client_can_access_ccache_entry ( uid_t client_uid ,
2006-08-29 03:01:30 +04:00
struct WINBINDD_MEMORY_CREDS * entry )
2006-08-19 05:04:54 +04:00
{
if ( client_uid = = entry - > uid | | client_uid = = 0 ) {
2009-05-12 08:56:57 +04:00
DEBUG ( 10 , ( " Access granted to uid %u \n " , ( unsigned int ) client_uid ) ) ;
2006-08-19 05:04:54 +04:00
return True ;
}
2009-05-12 08:56:57 +04:00
DEBUG ( 1 , ( " Access denied to uid %u (expected %u) \n " ,
( unsigned int ) client_uid , ( unsigned int ) entry - > uid ) ) ;
2006-08-19 05:04:54 +04:00
return False ;
}
2011-12-27 07:59:17 +04:00
static NTSTATUS do_ntlm_auth_with_stored_pw ( const char * username ,
const char * domain ,
const char * password ,
const DATA_BLOB initial_msg ,
const DATA_BLOB challenge_msg ,
DATA_BLOB * auth_msg ,
uint8_t session_key [ 16 ] )
2006-08-19 05:04:54 +04:00
{
NTSTATUS status ;
2011-12-27 07:59:17 +04:00
struct auth_generic_state * auth_generic_state = NULL ;
DATA_BLOB dummy_msg , reply , session_key_blob ;
2006-08-19 05:04:54 +04:00
2011-12-27 07:59:17 +04:00
status = auth_generic_client_prepare ( NULL , & auth_generic_state ) ;
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " Could not start NTLMSSP client: %s \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
2011-12-27 07:59:17 +04:00
status = auth_generic_set_username ( auth_generic_state , username ) ;
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " Could not set username: %s \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
2011-12-27 07:59:17 +04:00
status = auth_generic_set_domain ( auth_generic_state , domain ) ;
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " Could not set domain: %s \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
2011-12-27 07:59:17 +04:00
status = auth_generic_set_password ( auth_generic_state , password ) ;
2009-12-21 17:11:55 +03:00
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
2011-12-27 07:59:17 +04:00
DEBUG ( 1 , ( " Could not set password: %s \n " ,
2006-08-19 05:04:54 +04:00
nt_errstr ( status ) ) ) ;
goto done ;
}
2011-12-27 07:59:17 +04:00
gensec_want_feature ( auth_generic_state - > gensec_security , GENSEC_FEATURE_SESSION_KEY ) ;
status = auth_generic_client_start ( auth_generic_state , GENSEC_OID_NTLMSSP ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " Could not start NTLMSSP mech: %s \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
2010-01-24 18:41:30 +03:00
2006-08-19 05:04:54 +04:00
/* We need to get our protocol handler into the right state. So first
we ask it to generate the initial message . Actually the client has already
sent its own initial message , so we ' re going to drop this one on the floor .
The client might have sent a different message , for example with different
negotiation options , but as far as I can tell this won ' t hurt us . ( Unless
the client sent a different username or domain , in which case that ' s their
problem for telling us the wrong username or domain . )
Since we have a copy of the initial message that the client sent , we could
resolve any discrepancies if we had to .
*/
2007-05-14 16:16:20 +04:00
dummy_msg = data_blob_null ;
reply = data_blob_null ;
2011-12-27 07:59:17 +04:00
status = gensec_update ( auth_generic_state - > gensec_security ,
2013-12-13 22:56:13 +04:00
talloc_tos ( ) , dummy_msg , & reply ) ;
2006-08-19 05:04:54 +04:00
data_blob_free ( & reply ) ;
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_MORE_PROCESSING_REQUIRED ) ) {
DEBUG ( 1 , ( " Failed to create initial message! [%s] \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
/* Now we are ready to handle the server's actual response. */
2011-12-27 07:59:17 +04:00
status = gensec_update ( auth_generic_state - > gensec_security ,
2013-12-13 22:56:13 +04:00
NULL , challenge_msg , & reply ) ;
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_OK ) ) {
DEBUG ( 1 , ( " We didn't get a response to the challenge! [%s] \n " ,
nt_errstr ( status ) ) ) ;
data_blob_free ( & reply ) ;
goto done ;
}
2010-01-24 18:41:30 +03:00
2011-12-27 07:59:17 +04:00
status = gensec_session_key ( auth_generic_state - > gensec_security ,
talloc_tos ( ) , & session_key_blob ) ;
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_OK ) ) {
DEBUG ( 1 , ( " We didn't get the session key we requested! [%s] \n " ,
nt_errstr ( status ) ) ) ;
2010-01-24 18:41:30 +03:00
data_blob_free ( & reply ) ;
goto done ;
}
2011-12-27 07:59:17 +04:00
if ( session_key_blob . length ! = 16 ) {
DEBUG ( 1 , ( " invalid session key length %d \n " ,
( int ) session_key_blob . length ) ) ;
data_blob_free ( & reply ) ;
goto done ;
}
memcpy ( session_key , session_key_blob . data , 16 ) ;
data_blob_free ( & session_key_blob ) ;
* auth_msg = reply ;
2006-08-19 05:04:54 +04:00
status = NT_STATUS_OK ;
done :
2011-12-27 07:59:17 +04:00
TALLOC_FREE ( auth_generic_state ) ;
2006-08-19 05:04:54 +04:00
return status ;
}
2007-10-19 04:40:25 +04:00
static bool check_client_uid ( struct winbindd_cli_state * state , uid_t uid )
2006-08-19 05:04:54 +04:00
{
int ret ;
uid_t ret_uid ;
2012-03-24 19:00:36 +04:00
gid_t ret_gid ;
2006-08-19 05:04:54 +04:00
ret_uid = ( uid_t ) - 1 ;
2012-03-24 19:00:36 +04:00
ret = getpeereid ( state - > sock , & ret_uid , & ret_gid ) ;
2006-08-19 05:04:54 +04:00
if ( ret ! = 0 ) {
DEBUG ( 1 , ( " check_client_uid: Could not get socket peer uid: %s; "
" denying access \n " , strerror ( errno ) ) ) ;
return False ;
}
2013-07-01 15:02:48 +04:00
if ( uid ! = ret_uid & & ret_uid ! = sec_initial_uid ( ) ) {
2009-05-12 08:56:57 +04:00
DEBUG ( 1 , ( " check_client_uid: Client lied about its uid: said %u, "
" actually was %u; denying access \n " ,
( unsigned int ) uid , ( unsigned int ) ret_uid ) ) ;
2006-08-19 05:04:54 +04:00
return False ;
}
return True ;
}
void winbindd_ccache_ntlm_auth ( struct winbindd_cli_state * state )
{
struct winbindd_domain * domain ;
fstring name_domain , name_user ;
2010-04-18 16:14:43 +04:00
NTSTATUS result = NT_STATUS_NOT_SUPPORTED ;
struct WINBINDD_MEMORY_CREDS * entry ;
DATA_BLOB initial , challenge , auth ;
2015-04-24 05:04:23 +03:00
uint32_t initial_blob_len , challenge_blob_len , extra_len ;
2006-08-19 05:04:54 +04:00
/* Ensure null termination */
2009-05-08 00:46:27 +04:00
state - > request - > data . ccache_ntlm_auth . user [
sizeof ( state - > request - > data . ccache_ntlm_auth . user ) - 1 ] = ' \0 ' ;
2006-08-19 05:04:54 +04:00
DEBUG ( 3 , ( " [%5lu]: perform NTLM auth on behalf of user %s \n " , ( unsigned long ) state - > pid ,
2009-05-08 00:46:27 +04:00
state - > request - > data . ccache_ntlm_auth . user ) ) ;
2006-08-19 05:04:54 +04:00
/* Parse domain and username */
2009-05-08 00:46:27 +04:00
if ( ! canonicalize_username ( state - > request - > data . ccache_ntlm_auth . user ,
2006-08-19 05:04:54 +04:00
name_domain , name_user ) ) {
DEBUG ( 5 , ( " winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s] \n " ,
2009-05-08 00:46:27 +04:00
state - > request - > data . ccache_ntlm_auth . user ) ) ;
2006-08-19 05:04:54 +04:00
request_error ( state ) ;
return ;
}
2009-09-27 13:49:11 +04:00
domain = find_auth_domain ( state - > request - > flags , name_domain ) ;
2006-08-19 05:04:54 +04:00
if ( domain = = NULL ) {
DEBUG ( 5 , ( " winbindd_ccache_ntlm_auth: can't get domain [%s] \n " ,
name_domain ) ) ;
request_error ( state ) ;
return ;
}
2009-05-08 00:46:27 +04:00
if ( ! check_client_uid ( state , state - > request - > data . ccache_ntlm_auth . uid ) ) {
2006-08-19 05:04:54 +04:00
request_error ( state ) ;
return ;
}
/* validate blob lengths */
2009-05-08 00:46:27 +04:00
initial_blob_len = state - > request - > data . ccache_ntlm_auth . initial_blob_len ;
challenge_blob_len = state - > request - > data . ccache_ntlm_auth . challenge_blob_len ;
extra_len = state - > request - > extra_len ;
2006-08-19 05:04:54 +04:00
if ( initial_blob_len > extra_len | | challenge_blob_len > extra_len | |
initial_blob_len + challenge_blob_len > extra_len | |
initial_blob_len + challenge_blob_len < initial_blob_len | |
initial_blob_len + challenge_blob_len < challenge_blob_len ) {
DEBUG ( 10 , ( " winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
" or wrap. Buffer [%d+%d > %d] \n " ,
initial_blob_len ,
challenge_blob_len ,
extra_len ) ) ;
goto process_result ;
}
/* Parse domain and username */
2009-05-08 00:46:27 +04:00
if ( ! parse_domain_user ( state - > request - > data . ccache_ntlm_auth . user , name_domain , name_user ) ) {
2006-08-19 05:04:54 +04:00
DEBUG ( 10 , ( " winbindd_dual_ccache_ntlm_auth: cannot parse "
" domain and user from name [%s] \n " ,
2009-05-08 00:46:27 +04:00
state - > request - > data . ccache_ntlm_auth . user ) ) ;
2006-08-19 05:04:54 +04:00
goto process_result ;
}
2009-05-08 00:46:27 +04:00
entry = find_memory_creds_by_name ( state - > request - > data . ccache_ntlm_auth . user ) ;
2006-08-29 03:01:30 +04:00
if ( entry = = NULL | | entry - > nt_hash = = NULL | | entry - > lm_hash = = NULL ) {
2006-08-19 05:04:54 +04:00
DEBUG ( 10 , ( " winbindd_dual_ccache_ntlm_auth: could not find "
" credentials for user %s \n " ,
2009-05-08 00:46:27 +04:00
state - > request - > data . ccache_ntlm_auth . user ) ) ;
2006-08-19 05:04:54 +04:00
goto process_result ;
}
2006-08-29 03:01:30 +04:00
DEBUG ( 10 , ( " winbindd_dual_ccache_ntlm_auth: found ccache [%s] \n " , entry - > username ) ) ;
2006-08-19 05:04:54 +04:00
2009-05-08 00:46:27 +04:00
if ( ! client_can_access_ccache_entry ( state - > request - > data . ccache_ntlm_auth . uid , entry ) ) {
2006-08-19 05:04:54 +04:00
goto process_result ;
}
if ( initial_blob_len = = 0 & & challenge_blob_len = = 0 ) {
/* this is just a probe to see if credentials are available. */
result = NT_STATUS_OK ;
2009-06-14 14:41:46 +04:00
state - > response - > data . ccache_ntlm_auth . auth_blob_len = 0 ;
2006-08-19 05:04:54 +04:00
goto process_result ;
}
2009-12-20 02:27:34 +03:00
initial = data_blob_const ( state - > request - > extra_data . data ,
initial_blob_len ) ;
challenge = data_blob_const (
state - > request - > extra_data . data + initial_blob_len ,
state - > request - > data . ccache_ntlm_auth . challenge_blob_len ) ;
2011-12-27 07:59:17 +04:00
result = do_ntlm_auth_with_stored_pw (
name_user , name_domain , entry - > pass ,
2010-01-24 18:41:30 +03:00
initial , challenge , & auth ,
state - > response - > data . ccache_ntlm_auth . session_key ) ;
2006-08-19 05:04:54 +04:00
if ( ! NT_STATUS_IS_OK ( result ) ) {
goto process_result ;
}
2009-06-14 14:41:46 +04:00
state - > response - > extra_data . data = talloc_memdup (
2009-05-12 19:47:22 +04:00
state - > mem_ctx , auth . data , auth . length ) ;
2009-06-14 14:41:46 +04:00
if ( ! state - > response - > extra_data . data ) {
2006-08-19 05:04:54 +04:00
result = NT_STATUS_NO_MEMORY ;
goto process_result ;
}
2009-06-14 14:41:46 +04:00
state - > response - > length + = auth . length ;
state - > response - > data . ccache_ntlm_auth . auth_blob_len = auth . length ;
2006-08-19 05:04:54 +04:00
data_blob_free ( & auth ) ;
process_result :
2010-04-18 16:14:43 +04:00
if ( ! NT_STATUS_IS_OK ( result ) ) {
request_error ( state ) ;
return ;
}
request_ok ( state ) ;
2006-08-19 05:04:54 +04:00
}
2010-01-09 22:20:36 +03:00
void winbindd_ccache_save ( struct winbindd_cli_state * state )
{
struct winbindd_domain * domain ;
fstring name_domain , name_user ;
2010-04-18 16:14:43 +04:00
NTSTATUS status ;
2010-01-09 22:20:36 +03:00
/* Ensure null termination */
state - > request - > data . ccache_save . user [
sizeof ( state - > request - > data . ccache_save . user ) - 1 ] = ' \0 ' ;
state - > request - > data . ccache_save . pass [
sizeof ( state - > request - > data . ccache_save . pass ) - 1 ] = ' \0 ' ;
2010-01-28 17:10:54 +03:00
DEBUG ( 3 , ( " [%5lu]: save password of user %s \n " ,
2010-01-09 22:20:36 +03:00
( unsigned long ) state - > pid ,
state - > request - > data . ccache_save . user ) ) ;
/* Parse domain and username */
2010-04-17 21:24:34 +04:00
if ( ! canonicalize_username ( state - > request - > data . ccache_save . user ,
2010-01-09 22:20:36 +03:00
name_domain , name_user ) ) {
DEBUG ( 5 , ( " winbindd_ccache_save: cannot parse domain and user "
" from name [%s] \n " ,
state - > request - > data . ccache_save . user ) ) ;
request_error ( state ) ;
return ;
}
2010-04-18 16:14:43 +04:00
/*
* The domain is checked here only for compatibility
* reasons . We used to do the winbindd memory ccache for
* ntlm_auth in the domain child . With that code , we had to
* make sure that we do have a domain around to send this
* to . Now we do the memory cache in the parent winbindd ,
* where it would not matter if we have a domain or not .
*/
2010-01-09 22:20:36 +03:00
2010-04-18 16:14:43 +04:00
domain = find_auth_domain ( state - > request - > flags , name_domain ) ;
2010-01-09 22:20:36 +03:00
if ( domain = = NULL ) {
DEBUG ( 5 , ( " winbindd_ccache_save: can't get domain [%s] \n " ,
name_domain ) ) ;
request_error ( state ) ;
return ;
}
if ( ! check_client_uid ( state , state - > request - > data . ccache_save . uid ) ) {
request_error ( state ) ;
return ;
}
status = winbindd_add_memory_creds (
state - > request - > data . ccache_save . user ,
state - > request - > data . ccache_save . uid ,
state - > request - > data . ccache_save . pass ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " winbindd_add_memory_creds failed %s \n " ,
nt_errstr ( status ) ) ) ;
2010-04-18 16:14:43 +04:00
request_error ( state ) ;
return ;
2010-01-09 22:20:36 +03:00
}
2010-04-18 16:14:43 +04:00
request_ok ( state ) ;
2010-01-09 22:20:36 +03:00
}