2003-11-03 09:22:45 +03:00
/*
Unix SMB / CIFS implementation .
test suite for lsa rpc operations
2003-11-04 12:10:31 +03:00
2003-11-03 09:22:45 +03:00
Copyright ( C ) Andrew Tridgell 2003
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"
2003-11-19 06:20:32 +03:00
/*
this makes the debug code display the right thing
*/
static void init_lsa_Name ( struct lsa_Name * name , const char * s )
{
name - > name = s ;
name - > name_len = strlen_m ( s ) * 2 ;
name - > name_size = name - > name_len ;
}
2003-11-09 07:40:50 +03:00
static BOOL test_OpenPolicy ( struct dcerpc_pipe * p , TALLOC_CTX * mem_ctx )
2003-11-03 09:22:45 +03:00
{
2003-11-04 12:10:31 +03:00
struct lsa_ObjectAttribute attr ;
struct policy_handle handle ;
struct lsa_QosInfo qos ;
2003-11-09 07:40:50 +03:00
struct lsa_OpenPolicy r ;
2003-11-04 12:10:31 +03:00
NTSTATUS status ;
2003-11-09 07:40:50 +03:00
uint16 system_name = ' \\ ' ;
2003-11-03 09:22:45 +03:00
2003-11-04 14:16:07 +03:00
printf ( " \n testing OpenPolicy \n " ) ;
2003-11-04 12:48:33 +03:00
2003-11-15 00:39:13 +03:00
qos . len = 0 ;
2003-11-04 12:10:31 +03:00
qos . impersonation_level = 2 ;
qos . context_mode = 1 ;
qos . effective_only = 0 ;
2003-11-03 09:22:45 +03:00
2003-11-15 00:39:13 +03:00
attr . len = 0 ;
2003-11-04 12:10:31 +03:00
attr . root_dir = NULL ;
attr . object_name = NULL ;
attr . attributes = 0 ;
attr . sec_desc = NULL ;
attr . sec_qos = & qos ;
2003-11-03 09:22:45 +03:00
2003-11-09 07:40:50 +03:00
r . in . system_name = & system_name ;
r . in . attr = & attr ;
r . in . desired_access = SEC_RIGHTS_MAXIMUM_ALLOWED ;
r . out . handle = & handle ;
status = dcerpc_lsa_OpenPolicy ( p , mem_ctx , & r ) ;
2003-11-04 12:10:31 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " OpenPolicy failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
2003-11-03 09:22:45 +03:00
}
2003-11-04 12:10:31 +03:00
return True ;
2003-11-03 09:22:45 +03:00
}
2003-11-04 12:48:33 +03:00
2003-11-09 07:40:50 +03:00
static BOOL test_OpenPolicy2 ( struct dcerpc_pipe * p , TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
2003-11-04 12:48:33 +03:00
{
struct lsa_ObjectAttribute attr ;
struct lsa_QosInfo qos ;
2003-11-09 07:40:50 +03:00
struct lsa_OpenPolicy2 r ;
2003-11-04 12:48:33 +03:00
NTSTATUS status ;
2003-11-04 14:16:07 +03:00
printf ( " \n testing OpenPolicy2 \n " ) ;
2003-11-04 12:48:33 +03:00
2003-11-15 00:39:13 +03:00
qos . len = 0 ;
2003-11-04 12:48:33 +03:00
qos . impersonation_level = 2 ;
qos . context_mode = 1 ;
qos . effective_only = 0 ;
2003-11-15 00:39:13 +03:00
attr . len = 0 ;
2003-11-04 12:48:33 +03:00
attr . root_dir = NULL ;
attr . object_name = NULL ;
attr . attributes = 0 ;
attr . sec_desc = NULL ;
attr . sec_qos = & qos ;
2003-11-09 07:40:50 +03:00
r . in . system_name = " \\ " ;
r . in . attr = & attr ;
r . in . desired_access = SEC_RIGHTS_MAXIMUM_ALLOWED ;
r . out . handle = handle ;
status = dcerpc_lsa_OpenPolicy2 ( p , mem_ctx , & r ) ;
2003-11-04 12:48:33 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " OpenPolicy2 failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-09 05:21:24 +03:00
static BOOL test_LookupNames ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle ,
struct lsa_TransNameArray * tnames )
{
struct lsa_LookupNames r ;
struct lsa_TransSidArray sids ;
struct lsa_Name * names ;
uint32 count = 0 ;
NTSTATUS status ;
int i ;
printf ( " \n Testing LookupNames \n " ) ;
sids . count = 0 ;
sids . sids = NULL ;
names = talloc ( mem_ctx , tnames - > count * sizeof ( names [ 0 ] ) ) ;
for ( i = 0 ; i < tnames - > count ; i + + ) {
2003-11-19 06:20:32 +03:00
init_lsa_Name ( & names [ i ] , tnames - > names [ i ] . name . name ) ;
2003-11-09 05:21:24 +03:00
}
r . in . handle = handle ;
r . in . num_names = tnames - > count ;
r . in . names = names ;
r . in . sids = & sids ;
r . in . level = 1 ;
r . in . count = & count ;
r . out . count = & count ;
r . out . sids = & sids ;
status = dcerpc_lsa_LookupNames ( p , mem_ctx , & r ) ;
2003-11-09 14:01:39 +03:00
if ( ! NT_STATUS_IS_OK ( status ) & & ! NT_STATUS_EQUAL ( status , STATUS_SOME_UNMAPPED ) ) {
2003-11-09 05:21:24 +03:00
printf ( " LookupNames failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
printf ( " \n " ) ;
return True ;
}
2003-11-04 14:16:07 +03:00
2003-11-09 03:58:40 +03:00
static BOOL test_LookupSids ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle ,
struct lsa_SidArray * sids )
{
struct lsa_LookupSids r ;
struct lsa_TransNameArray names ;
uint32 count = sids - > num_sids ;
NTSTATUS status ;
printf ( " \n Testing LookupSids \n " ) ;
names . count = 0 ;
names . names = NULL ;
r . in . handle = handle ;
r . in . sids = sids ;
r . in . names = & names ;
r . in . level = 1 ;
r . in . count = & count ;
r . out . count = & count ;
r . out . names = & names ;
status = dcerpc_lsa_LookupSids ( p , mem_ctx , & r ) ;
2003-11-09 14:01:39 +03:00
if ( ! NT_STATUS_IS_OK ( status ) & & ! NT_STATUS_EQUAL ( status , STATUS_SOME_UNMAPPED ) ) {
2003-11-09 03:58:40 +03:00
printf ( " LookupSids failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
printf ( " \n " ) ;
2003-11-09 05:21:24 +03:00
if ( ! test_LookupNames ( p , mem_ctx , handle , & names ) ) {
return False ;
}
2003-11-09 03:58:40 +03:00
return True ;
}
2003-11-10 15:42:45 +03:00
static BOOL test_LookupPrivName ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle ,
struct lsa_LUID * luid )
{
NTSTATUS status ;
struct lsa_LookupPrivName r ;
r . in . handle = handle ;
2003-11-14 10:56:43 +03:00
r . in . luid = luid ;
2003-11-10 15:42:45 +03:00
status = dcerpc_lsa_LookupPrivName ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " \n LookupPrivName failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-10 14:37:19 +03:00
static BOOL test_EnumPrivsAccount ( struct dcerpc_pipe * p ,
2003-11-10 15:42:45 +03:00
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle ,
2003-11-10 14:37:19 +03:00
struct policy_handle * acct_handle )
{
NTSTATUS status ;
struct lsa_EnumPrivsAccount r ;
printf ( " Testing EnumPrivsAccount \n " ) ;
r . in . handle = acct_handle ;
status = dcerpc_lsa_EnumPrivsAccount ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " EnumPrivsAccount failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
if ( r . out . privs ) {
int i ;
2003-11-11 07:04:36 +03:00
for ( i = 0 ; i < r . out . privs - > count ; i + + ) {
2003-11-10 15:42:45 +03:00
test_LookupPrivName ( p , mem_ctx , handle ,
2003-11-11 07:04:36 +03:00
& r . out . privs - > set [ i ] . luid ) ;
2003-11-10 14:37:19 +03:00
}
}
return True ;
}
2003-11-10 15:12:22 +03:00
static BOOL test_EnumAccountRights ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * acct_handle ,
struct dom_sid * sid )
{
NTSTATUS status ;
struct lsa_EnumAccountRights r ;
struct lsa_RightSet rights ;
printf ( " Testing EnumAccountRights \n " ) ;
r . in . handle = acct_handle ;
r . in . sid = sid ;
r . out . rights = & rights ;
status = dcerpc_lsa_EnumAccountRights ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " EnumAccountRights failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-15 07:42:48 +03:00
static BOOL test_QuerySecObj ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
2003-11-15 09:00:21 +03:00
struct policy_handle * handle ,
struct policy_handle * acct_handle )
2003-11-15 07:42:48 +03:00
{
NTSTATUS status ;
struct lsa_QuerySecObj r ;
printf ( " Testing QuerySecObj \n " ) ;
r . in . handle = acct_handle ;
r . in . sec_info = 7 ;
status = dcerpc_lsa_QuerySecObj ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " QuerySecObj failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-09 14:01:39 +03:00
static BOOL test_OpenAccount ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle ,
struct dom_sid * sid )
{
NTSTATUS status ;
struct lsa_OpenAccount r ;
struct policy_handle acct_handle ;
2003-11-15 09:00:21 +03:00
printf ( " Testing OpenAccount \n " ) ;
2003-11-09 14:01:39 +03:00
r . in . handle = handle ;
r . in . sid = sid ;
r . in . desired_access = SEC_RIGHTS_MAXIMUM_ALLOWED ;
r . out . acct_handle = & acct_handle ;
status = dcerpc_lsa_OpenAccount ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " OpenAccount failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
2003-11-10 15:42:45 +03:00
if ( ! test_EnumPrivsAccount ( p , mem_ctx , handle , & acct_handle ) ) {
2003-11-10 14:37:19 +03:00
return False ;
}
2003-11-15 07:42:48 +03:00
if ( ! test_QuerySecObj ( p , mem_ctx , handle , & acct_handle ) ) {
return False ;
}
2003-11-09 14:01:39 +03:00
return True ;
}
2003-11-09 11:30:29 +03:00
static BOOL test_EnumAccounts ( struct dcerpc_pipe * p ,
2003-11-04 14:16:07 +03:00
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
NTSTATUS status ;
2003-11-09 11:30:29 +03:00
struct lsa_EnumAccounts r ;
2003-11-08 14:21:57 +03:00
struct lsa_SidArray sids1 , sids2 ;
2003-11-09 07:40:50 +03:00
uint32 resume_handle = 0 ;
2003-11-04 14:16:07 +03:00
int i ;
2003-11-09 11:30:29 +03:00
printf ( " \n testing EnumAccounts \n " ) ;
2003-11-04 14:16:07 +03:00
2003-11-09 07:40:50 +03:00
r . in . handle = handle ;
r . in . resume_handle = & resume_handle ;
r . in . num_entries = 100 ;
r . out . resume_handle = & resume_handle ;
r . out . sids = & sids1 ;
2003-11-08 14:21:57 +03:00
resume_handle = 0 ;
2003-11-09 11:30:29 +03:00
status = dcerpc_lsa_EnumAccounts ( p , mem_ctx , & r ) ;
2003-11-04 14:16:07 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
2003-11-09 11:30:29 +03:00
printf ( " EnumAccounts failed - %s \n " , nt_errstr ( status ) ) ;
2003-11-04 14:16:07 +03:00
return False ;
}
2003-11-09 03:58:40 +03:00
if ( ! test_LookupSids ( p , mem_ctx , handle , & sids1 ) ) {
return False ;
}
2003-11-09 14:01:39 +03:00
printf ( " testing all accounts \n " ) ;
for ( i = 0 ; i < sids1 . num_sids ; i + + ) {
test_OpenAccount ( p , mem_ctx , handle , sids1 . sids [ i ] . sid ) ;
2003-11-10 15:12:22 +03:00
test_EnumAccountRights ( p , mem_ctx , handle , sids1 . sids [ i ] . sid ) ;
2003-11-09 14:01:39 +03:00
}
printf ( " \n " ) ;
2003-11-10 15:12:22 +03:00
2003-11-08 14:21:57 +03:00
if ( sids1 . num_sids < 3 ) {
return True ;
2003-11-04 14:16:07 +03:00
}
2003-11-09 11:30:29 +03:00
printf ( " trying EnumAccounts partial listing (asking for 1 at 2) \n " ) ;
2003-11-08 14:21:57 +03:00
resume_handle = 2 ;
2003-11-09 07:40:50 +03:00
r . in . num_entries = 1 ;
r . out . sids = & sids2 ;
2003-11-09 11:30:29 +03:00
status = dcerpc_lsa_EnumAccounts ( p , mem_ctx , & r ) ;
2003-11-08 14:21:57 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
2003-11-09 11:30:29 +03:00
printf ( " EnumAccounts failed - %s \n " , nt_errstr ( status ) ) ;
2003-11-08 14:21:57 +03:00
return False ;
}
if ( sids2 . num_sids ! = 1 ) {
printf ( " Returned wrong number of entries (%d) \n " , sids2 . num_sids ) ;
return False ;
}
2003-11-04 14:16:07 +03:00
return True ;
}
2003-11-09 12:04:28 +03:00
2003-11-09 12:44:06 +03:00
static BOOL test_EnumPrivs ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
NTSTATUS status ;
struct lsa_EnumPrivs r ;
struct lsa_PrivArray privs1 ;
uint32 resume_handle = 0 ;
printf ( " \n testing EnumPrivs \n " ) ;
r . in . handle = handle ;
r . in . resume_handle = & resume_handle ;
r . in . max_count = 1000 ;
r . out . resume_handle = & resume_handle ;
r . out . privs = & privs1 ;
resume_handle = 0 ;
status = dcerpc_lsa_EnumPrivs ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " EnumPrivs failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-09 13:01:24 +03:00
static BOOL test_EnumTrustDom ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
struct lsa_EnumTrustDom r ;
NTSTATUS status ;
uint32 resume_handle = 0 ;
2003-11-10 14:37:19 +03:00
struct lsa_DomainList domains ;
2003-11-09 13:01:24 +03:00
printf ( " \n Testing EnumTrustDom \n " ) ;
r . in . handle = handle ;
r . in . resume_handle = & resume_handle ;
r . in . num_entries = 1000 ;
2003-11-09 14:01:39 +03:00
r . out . domains = & domains ;
2003-11-09 13:01:24 +03:00
r . out . resume_handle = & resume_handle ;
status = dcerpc_lsa_EnumTrustDom ( p , mem_ctx , & r ) ;
2003-11-20 04:02:09 +03:00
/* NO_MORE_ENTRIES is allowed */
if ( NT_STATUS_EQUAL ( status , NT_STATUS_NO_MORE_ENTRIES ) ) {
return True ;
}
2003-11-09 13:01:24 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " EnumTrustDom failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
return True ;
}
2003-11-11 05:02:29 +03:00
static BOOL test_QueryInfoPolicy ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
struct lsa_QueryInfoPolicy r ;
NTSTATUS status ;
2003-11-11 09:22:58 +03:00
int i ;
BOOL ret = True ;
2003-11-11 05:02:29 +03:00
printf ( " \n Testing QueryInfoPolicy \n " ) ;
2003-11-11 09:22:58 +03:00
for ( i = 1 ; i < 13 ; i + + ) {
r . in . handle = handle ;
r . in . level = i ;
2003-11-11 05:02:29 +03:00
2003-11-11 09:54:54 +03:00
printf ( " \n trying QueryInfoPolicy level %d \n " , i ) ;
2003-11-11 09:22:58 +03:00
status = dcerpc_lsa_QueryInfoPolicy ( p , mem_ctx , & r ) ;
2003-11-20 04:02:09 +03:00
if ( ( i = = 9 | | i = = 10 ) & &
NT_STATUS_EQUAL ( status , NT_STATUS_INVALID_PARAMETER ) ) {
continue ;
}
2003-11-11 09:22:58 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " QueryInfoPolicy failed - %s \n " , nt_errstr ( status ) ) ;
ret = False ;
continue ;
}
}
2003-11-11 05:02:29 +03:00
2003-11-11 09:22:58 +03:00
return ret ;
2003-11-11 05:02:29 +03:00
}
2003-11-09 12:44:06 +03:00
static BOOL test_Delete ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
NTSTATUS status ;
struct lsa_Delete r ;
printf ( " \n testing Delete - but what does it do? \n " ) ;
r . in . handle = handle ;
status = dcerpc_lsa_Delete ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " Delete failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
printf ( " \n " ) ;
return True ;
}
2003-11-09 12:04:28 +03:00
static BOOL test_Close ( struct dcerpc_pipe * p ,
TALLOC_CTX * mem_ctx ,
struct policy_handle * handle )
{
NTSTATUS status ;
struct lsa_Close r ;
2003-11-09 14:01:39 +03:00
struct policy_handle handle2 ;
2003-11-09 12:04:28 +03:00
printf ( " \n testing Close \n " ) ;
r . in . handle = handle ;
2003-11-09 14:01:39 +03:00
r . out . handle = & handle2 ;
2003-11-09 12:04:28 +03:00
status = dcerpc_lsa_Close ( p , mem_ctx , & r ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
printf ( " Close failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
2003-11-09 12:44:06 +03:00
status = dcerpc_lsa_Close ( p , mem_ctx , & r ) ;
/* its really a fault - we need a status code for rpc fault */
2003-11-15 09:00:21 +03:00
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_NET_WRITE_FAULT ) ) {
2003-11-09 12:44:06 +03:00
printf ( " Close failed - %s \n " , nt_errstr ( status ) ) ;
return False ;
}
2003-11-09 12:04:28 +03:00
printf ( " \n " ) ;
return True ;
}
2003-11-03 09:22:45 +03:00
BOOL torture_rpc_lsa ( int dummy )
{
NTSTATUS status ;
struct dcerpc_pipe * p ;
TALLOC_CTX * mem_ctx ;
2003-11-04 12:48:33 +03:00
BOOL ret = True ;
2003-11-04 14:16:07 +03:00
struct policy_handle handle ;
2003-11-03 09:22:45 +03:00
mem_ctx = talloc_init ( " torture_rpc_lsa " ) ;
2003-11-18 08:01:10 +03:00
status = torture_rpc_connection ( & p ,
DCERPC_LSARPC_NAME ,
DCERPC_LSARPC_UUID ,
DCERPC_LSARPC_VERSION ) ;
2003-11-03 09:22:45 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
return False ;
}
2003-11-22 11:41:40 +03:00
p - > flags | = DCERPC_DEBUG_PRINT_BOTH ;
2003-11-18 04:18:24 +03:00
2003-11-09 07:40:50 +03:00
if ( ! test_OpenPolicy ( p , mem_ctx ) ) {
2003-11-04 12:48:33 +03:00
ret = False ;
}
2003-11-09 07:40:50 +03:00
if ( ! test_OpenPolicy2 ( p , mem_ctx , & handle ) ) {
2003-11-04 12:48:33 +03:00
ret = False ;
}
2003-11-03 09:22:45 +03:00
2003-11-09 11:30:29 +03:00
if ( ! test_EnumAccounts ( p , mem_ctx , & handle ) ) {
2003-11-04 14:16:07 +03:00
ret = False ;
}
2003-11-09 12:44:06 +03:00
if ( ! test_EnumPrivs ( p , mem_ctx , & handle ) ) {
ret = False ;
}
2003-11-09 13:01:24 +03:00
if ( ! test_EnumTrustDom ( p , mem_ctx , & handle ) ) {
ret = False ;
}
2003-11-11 05:02:29 +03:00
if ( ! test_QueryInfoPolicy ( p , mem_ctx , & handle ) ) {
ret = False ;
}
2003-11-04 14:16:07 +03:00
2003-11-09 12:44:06 +03:00
#if 0
if ( ! test_Delete ( p , mem_ctx , & handle ) ) {
2003-11-09 12:04:28 +03:00
ret = False ;
}
2003-11-09 12:44:06 +03:00
# endif
2003-11-09 12:04:28 +03:00
2003-11-09 12:44:06 +03:00
if ( ! test_Close ( p , mem_ctx , & handle ) ) {
ret = False ;
}
2003-11-22 11:11:32 +03:00
talloc_destroy ( mem_ctx ) ;
2003-11-03 09:22:45 +03:00
torture_rpc_close ( p ) ;
2003-11-04 12:48:33 +03:00
return ret ;
2003-11-03 09:22:45 +03:00
}