2008-01-15 16:55:04 +03:00
/*
2006-12-01 23:01:09 +03:00
* Unix SMB / CIFS implementation .
* Virtual Windows Registry Layer
* Copyright ( C ) Volker Lendecke 2006
2008-01-17 13:09:08 +03:00
* Copyright ( C ) Michael Adam 2007 - 2008
2006-12-01 23:01:09 +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
2006-12-01 23:01:09 +03:00
* ( at your option ) any later version .
2008-01-15 16:55:04 +03:00
*
2006-12-01 23:01:09 +03: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 .
2008-01-15 16:55:04 +03:00
*
2006-12-01 23:01:09 +03:00
* You should have received a copy of the GNU General Public License
2007-07-10 09:23:25 +04:00
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
2006-12-01 23:01:09 +03:00
*/
/* Attempt to wrap the existing API in a more winreg.idl-like way */
2008-01-17 13:07:28 +03:00
/*
* Here is a list of winreg . idl functions and corresponding implementations
* provided here :
*
* 0x00 winreg_OpenHKCR
* 0x01 winreg_OpenHKCU
* 0x02 winreg_OpenHKLM
* 0x03 winreg_OpenHKPD
* 0x04 winreg_OpenHKU
* 0x05 winreg_CloseKey
* 0x06 winreg_CreateKey reg_createkey
* 0x07 winreg_DeleteKey reg_deletekey
* 0x08 winreg_DeleteValue reg_deletevalue
* 0x09 winreg_EnumKey reg_enumkey
* 0x0a winreg_EnumValue reg_enumvalue
* 0x0b winreg_FlushKey
* 0x0c winreg_GetKeySecurity reg_getkeysecurity
* 0x0d winreg_LoadKey
* 0x0e winreg_NotifyChangeKeyValue
* 0x0f winreg_OpenKey reg_openkey
* 0x10 winreg_QueryInfoKey reg_queryinfokey
* 0x11 winreg_QueryValue reg_queryvalue
* 0x12 winreg_ReplaceKey
2008-02-15 17:31:31 +03:00
* 0x13 winreg_RestoreKey reg_restorekey
2008-02-15 17:14:20 +03:00
* 0x14 winreg_SaveKey reg_savekey
2008-01-17 13:07:28 +03:00
* 0x15 winreg_SetKeySecurity reg_setkeysecurity
* 0x16 winreg_SetValue reg_setvalue
* 0x17 winreg_UnLoadKey
* 0x18 winreg_InitiateSystemShutdown
* 0x19 winreg_AbortSystemShutdown
2008-01-17 13:22:01 +03:00
* 0x1a winreg_GetVersion reg_getversion
2008-01-17 13:07:28 +03:00
* 0x1b winreg_OpenHKCC
* 0x1c winreg_OpenHKDD
* 0x1d winreg_QueryMultipleValues
* 0x1e winreg_InitiateSystemShutdownEx
* 0x1f winreg_SaveKeyEx
* 0x20 winreg_OpenHKPT
* 0x21 winreg_OpenHKPN
* 0x22 winreg_QueryMultipleValues2
*
*/
2006-12-01 23:01:09 +03:00
# include "includes.h"
2008-02-15 17:14:20 +03:00
# include "regfio.h"
2006-12-01 23:01:09 +03:00
2007-09-29 03:05:52 +04:00
# undef DBGC_CLASS
# define DBGC_CLASS DBGC_REGISTRY
2008-01-17 02:57:53 +03:00
/**********************************************************************
* Helper functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2006-12-01 23:01:09 +03:00
static WERROR fill_value_cache ( struct registry_key * key )
{
if ( key - > values ! = NULL ) {
2008-01-14 20:31:11 +03:00
if ( ! reg_values_need_update ( key - > key , key - > values ) ) {
return WERR_OK ;
}
2006-12-01 23:01:09 +03:00
}
if ( ! ( key - > values = TALLOC_ZERO_P ( key , REGVAL_CTR ) ) ) {
return WERR_NOMEM ;
}
if ( fetch_reg_values ( key - > key , key - > values ) = = - 1 ) {
TALLOC_FREE ( key - > values ) ;
return WERR_BADFILE ;
}
return WERR_OK ;
}
static WERROR fill_subkey_cache ( struct registry_key * key )
{
if ( key - > subkeys ! = NULL ) {
2008-01-14 20:31:11 +03:00
if ( ! reg_subkeys_need_update ( key - > key , key - > subkeys ) ) {
return WERR_OK ;
}
2006-12-01 23:01:09 +03:00
}
if ( ! ( key - > subkeys = TALLOC_ZERO_P ( key , REGSUBKEY_CTR ) ) ) {
return WERR_NOMEM ;
}
if ( fetch_reg_keys ( key - > key , key - > subkeys ) = = - 1 ) {
TALLOC_FREE ( key - > subkeys ) ;
return WERR_NO_MORE_ITEMS ;
}
return WERR_OK ;
}
2007-06-22 15:03:48 +04:00
static int regkey_destructor ( REGISTRY_KEY * key )
{
return regdb_close ( ) ;
}
static WERROR regkey_open_onelevel ( TALLOC_CTX * mem_ctx ,
struct registry_key * parent ,
const char * name ,
const struct nt_user_token * token ,
uint32 access_desired ,
struct registry_key * * pregkey )
{
WERROR result = WERR_OK ;
struct registry_key * regkey ;
REGISTRY_KEY * key ;
REGSUBKEY_CTR * subkeys = NULL ;
DEBUG ( 7 , ( " regkey_open_onelevel: name = [%s] \n " , name ) ) ;
SMB_ASSERT ( strchr ( name , ' \\ ' ) = = NULL ) ;
if ( ! ( regkey = TALLOC_ZERO_P ( mem_ctx , struct registry_key ) ) | |
! ( regkey - > token = dup_nt_token ( regkey , token ) ) | |
! ( regkey - > key = TALLOC_ZERO_P ( regkey , REGISTRY_KEY ) ) ) {
result = WERR_NOMEM ;
goto done ;
}
if ( ! ( W_ERROR_IS_OK ( result = regdb_open ( ) ) ) ) {
goto done ;
}
key = regkey - > key ;
talloc_set_destructor ( key , regkey_destructor ) ;
/* initialization */
key - > type = REG_KEY_GENERIC ;
if ( name [ 0 ] = = ' \0 ' ) {
/*
* Open a copy of the parent key
*/
if ( ! parent ) {
result = WERR_BADFILE ;
goto done ;
}
key - > name = talloc_strdup ( key , parent - > key - > name ) ;
}
else {
/*
* Normal subkey open
*/
key - > name = talloc_asprintf ( key , " %s%s%s " ,
parent ? parent - > key - > name : " " ,
parent ? " \\ " : " " ,
name ) ;
}
if ( key - > name = = NULL ) {
result = WERR_NOMEM ;
goto done ;
}
/* Tag this as a Performance Counter Key */
if ( StrnCaseCmp ( key - > name , KEY_HKPD , strlen ( KEY_HKPD ) ) = = 0 )
key - > type = REG_KEY_HKPD ;
/* Look up the table of registry I/O operations */
2008-04-13 02:54:44 +04:00
if ( ! ( key - > ops = reghook_cache_find ( key - > name ) ) ) {
DEBUG ( 0 , ( " reg_open_onelevel: Failed to assign "
" REGISTRY_OPS to [%s] \n " , key - > name ) ) ;
2007-06-22 15:03:48 +04:00
result = WERR_BADFILE ;
goto done ;
}
2008-01-15 16:55:04 +03:00
2007-06-22 15:03:48 +04:00
/* check if the path really exists; failed is indicated by -1 */
/* if the subkey count failed, bail out */
if ( ! ( subkeys = TALLOC_ZERO_P ( key , REGSUBKEY_CTR ) ) ) {
result = WERR_NOMEM ;
goto done ;
}
if ( fetch_reg_keys ( key , subkeys ) = = - 1 ) {
result = WERR_BADFILE ;
goto done ;
}
2008-01-15 16:55:04 +03:00
2007-06-22 15:03:48 +04:00
TALLOC_FREE ( subkeys ) ;
if ( ! regkey_access_check ( key , access_desired , & key - > access_granted ,
token ) ) {
result = WERR_ACCESS_DENIED ;
goto done ;
}
* pregkey = regkey ;
result = WERR_OK ;
done :
if ( ! W_ERROR_IS_OK ( result ) ) {
TALLOC_FREE ( regkey ) ;
}
return result ;
}
2006-12-01 23:01:09 +03:00
WERROR reg_openhive ( TALLOC_CTX * mem_ctx , const char * hive ,
uint32 desired_access ,
const struct nt_user_token * token ,
struct registry_key * * pkey )
{
2006-12-05 10:36:14 +03:00
SMB_ASSERT ( hive ! = NULL ) ;
2006-12-01 23:01:09 +03:00
SMB_ASSERT ( hive [ 0 ] ! = ' \0 ' ) ;
SMB_ASSERT ( strchr ( hive , ' \\ ' ) = = NULL ) ;
2006-12-05 10:36:14 +03:00
return regkey_open_onelevel ( mem_ctx , NULL , hive , token , desired_access ,
pkey ) ;
2006-12-01 23:01:09 +03:00
}
2008-01-17 02:57:53 +03:00
/**********************************************************************
* The API functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2006-12-01 23:01:09 +03:00
WERROR reg_openkey ( TALLOC_CTX * mem_ctx , struct registry_key * parent ,
const char * name , uint32 desired_access ,
struct registry_key * * pkey )
{
2006-12-05 10:36:14 +03:00
struct registry_key * direct_parent = parent ;
2006-12-01 23:01:09 +03:00
WERROR err ;
2006-12-05 10:36:14 +03:00
char * p , * path , * to_free ;
size_t len ;
2006-12-01 23:01:09 +03:00
2006-12-05 10:36:14 +03:00
if ( ! ( path = SMB_STRDUP ( name ) ) ) {
2006-12-01 23:01:09 +03:00
return WERR_NOMEM ;
}
2006-12-05 10:36:14 +03:00
to_free = path ;
2006-12-01 23:01:09 +03:00
2006-12-05 10:36:14 +03:00
len = strlen ( path ) ;
2006-12-01 23:01:09 +03:00
2006-12-05 10:36:14 +03:00
if ( ( len > 0 ) & & ( path [ len - 1 ] = = ' \\ ' ) ) {
path [ len - 1 ] = ' \0 ' ;
2006-12-01 23:01:09 +03:00
}
2006-12-05 10:36:14 +03:00
while ( ( p = strchr ( path , ' \\ ' ) ) ! = NULL ) {
char * name_component ;
struct registry_key * tmp ;
2006-12-01 23:01:09 +03:00
2006-12-05 10:36:14 +03:00
if ( ! ( name_component = SMB_STRNDUP ( path , ( p - path ) ) ) ) {
err = WERR_NOMEM ;
goto error ;
}
2006-12-01 23:01:09 +03:00
2006-12-05 10:36:14 +03:00
err = regkey_open_onelevel ( mem_ctx , direct_parent ,
name_component , parent - > token ,
SEC_RIGHTS_ENUM_SUBKEYS , & tmp ) ;
SAFE_FREE ( name_component ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
goto error ;
}
if ( direct_parent ! = parent ) {
TALLOC_FREE ( direct_parent ) ;
}
direct_parent = tmp ;
path = p + 1 ;
2006-12-01 23:01:09 +03:00
}
2006-12-05 10:36:14 +03:00
err = regkey_open_onelevel ( mem_ctx , direct_parent , path , parent - > token ,
desired_access , pkey ) ;
error :
if ( direct_parent ! = parent ) {
TALLOC_FREE ( direct_parent ) ;
}
SAFE_FREE ( to_free ) ;
return err ;
2006-12-01 23:01:09 +03:00
}
WERROR reg_enumkey ( TALLOC_CTX * mem_ctx , struct registry_key * key ,
uint32 idx , char * * name , NTTIME * last_write_time )
{
WERROR err ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_ENUM_SUBKEYS ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! W_ERROR_IS_OK ( err = fill_subkey_cache ( key ) ) ) {
return err ;
}
if ( idx > = key - > subkeys - > num_subkeys ) {
return WERR_NO_MORE_ITEMS ;
}
if ( ! ( * name = talloc_strdup ( mem_ctx , key - > subkeys - > subkeys [ idx ] ) ) ) {
return WERR_NOMEM ;
}
if ( last_write_time ) {
* last_write_time = 0 ;
}
return WERR_OK ;
}
WERROR reg_enumvalue ( TALLOC_CTX * mem_ctx , struct registry_key * key ,
2006-12-02 13:51:54 +03:00
uint32 idx , char * * pname , struct registry_value * * pval )
2006-12-01 23:01:09 +03:00
{
struct registry_value * val ;
WERROR err ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_QUERY_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! ( W_ERROR_IS_OK ( err = fill_value_cache ( key ) ) ) ) {
return err ;
}
if ( idx > = key - > values - > num_values ) {
2007-04-24 04:12:28 +04:00
return WERR_NO_MORE_ITEMS ;
2006-12-01 23:01:09 +03:00
}
err = registry_pull_value ( mem_ctx , & val ,
key - > values - > values [ idx ] - > type ,
key - > values - > values [ idx ] - > data_p ,
key - > values - > values [ idx ] - > size ,
key - > values - > values [ idx ] - > size ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
return err ;
}
if ( pname
& & ! ( * pname = talloc_strdup (
mem_ctx , key - > values - > values [ idx ] - > valuename ) ) ) {
SAFE_FREE ( val ) ;
return WERR_NOMEM ;
}
2008-01-15 16:55:04 +03:00
2006-12-01 23:01:09 +03:00
* pval = val ;
return WERR_OK ;
}
WERROR reg_queryvalue ( TALLOC_CTX * mem_ctx , struct registry_key * key ,
const char * name , struct registry_value * * pval )
{
WERROR err ;
uint32 i ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_QUERY_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! ( W_ERROR_IS_OK ( err = fill_value_cache ( key ) ) ) ) {
return err ;
}
for ( i = 0 ; i < key - > values - > num_values ; i + + ) {
if ( strequal ( key - > values - > values [ i ] - > valuename , name ) ) {
return reg_enumvalue ( mem_ctx , key , i , NULL , pval ) ;
}
}
return WERR_BADFILE ;
}
WERROR reg_queryinfokey ( struct registry_key * key , uint32_t * num_subkeys ,
uint32_t * max_subkeylen , uint32_t * max_subkeysize ,
uint32_t * num_values , uint32_t * max_valnamelen ,
uint32_t * max_valbufsize , uint32_t * secdescsize ,
NTTIME * last_changed_time )
{
uint32 i , max_size ;
size_t max_len ;
TALLOC_CTX * mem_ctx ;
WERROR err ;
struct security_descriptor * secdesc ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_QUERY_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! W_ERROR_IS_OK ( fill_subkey_cache ( key ) ) | |
! W_ERROR_IS_OK ( fill_value_cache ( key ) ) ) {
return WERR_BADFILE ;
}
max_len = 0 ;
for ( i = 0 ; i < key - > subkeys - > num_subkeys ; i + + ) {
max_len = MAX ( max_len , strlen ( key - > subkeys - > subkeys [ i ] ) ) ;
}
* num_subkeys = key - > subkeys - > num_subkeys ;
* max_subkeylen = max_len ;
* max_subkeysize = 0 ; /* Class length? */
max_len = 0 ;
max_size = 0 ;
for ( i = 0 ; i < key - > values - > num_values ; i + + ) {
max_len = MAX ( max_len ,
strlen ( key - > values - > values [ i ] - > valuename ) ) ;
max_size = MAX ( max_size , key - > values - > values [ i ] - > size ) ;
}
* num_values = key - > values - > num_values ;
* max_valnamelen = max_len ;
* max_valbufsize = max_size ;
if ( ! ( mem_ctx = talloc_new ( key ) ) ) {
return WERR_NOMEM ;
}
err = regkey_get_secdesc ( mem_ctx , key - > key , & secdesc ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
TALLOC_FREE ( mem_ctx ) ;
return err ;
}
2007-12-30 01:00:49 +03:00
* secdescsize = ndr_size_security_descriptor ( secdesc , 0 ) ;
2006-12-01 23:01:09 +03:00
TALLOC_FREE ( mem_ctx ) ;
* last_changed_time = 0 ;
return WERR_OK ;
}
WERROR reg_createkey ( TALLOC_CTX * ctx , struct registry_key * parent ,
const char * subkeypath , uint32 desired_access ,
struct registry_key * * pkey ,
enum winreg_CreateAction * paction )
{
struct registry_key * key = parent ;
struct registry_key * create_parent ;
TALLOC_CTX * mem_ctx ;
char * path , * end ;
WERROR err ;
if ( ! ( mem_ctx = talloc_new ( ctx ) ) ) return WERR_NOMEM ;
if ( ! ( path = talloc_strdup ( mem_ctx , subkeypath ) ) ) {
err = WERR_NOMEM ;
goto done ;
}
while ( ( end = strchr ( path , ' \\ ' ) ) ! = NULL ) {
struct registry_key * tmp ;
enum winreg_CreateAction action ;
* end = ' \0 ' ;
err = reg_createkey ( mem_ctx , key , path ,
SEC_RIGHTS_ENUM_SUBKEYS , & tmp , & action ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
goto done ;
}
if ( key ! = parent ) {
TALLOC_FREE ( key ) ;
}
key = tmp ;
path = end + 1 ;
}
/*
* At this point , " path " contains the one - element subkey of " key " . We
* can try to open it .
*/
err = reg_openkey ( ctx , key , path , desired_access , pkey ) ;
if ( W_ERROR_IS_OK ( err ) ) {
2006-12-03 19:51:31 +03:00
if ( paction ! = NULL ) {
* paction = REG_OPENED_EXISTING_KEY ;
}
goto done ;
2006-12-01 23:01:09 +03:00
}
if ( ! W_ERROR_EQUAL ( err , WERR_BADFILE ) ) {
/*
* Something but " notfound " has happened , so bail out
*/
goto done ;
}
/*
* We have to make a copy of the current key , as we opened it only
* with ENUM_SUBKEY access .
*/
err = reg_openkey ( mem_ctx , key , " " , SEC_RIGHTS_CREATE_SUBKEY ,
& create_parent ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
goto done ;
}
/*
* Actually create the subkey
*/
err = fill_subkey_cache ( create_parent ) ;
if ( ! W_ERROR_IS_OK ( err ) ) goto done ;
err = regsubkey_ctr_addkey ( create_parent - > subkeys , path ) ;
if ( ! W_ERROR_IS_OK ( err ) ) goto done ;
if ( ! store_reg_keys ( create_parent - > key , create_parent - > subkeys ) ) {
TALLOC_FREE ( create_parent - > subkeys ) ;
err = WERR_REG_IO_FAILURE ;
goto done ;
}
/*
* Now open the newly created key
*/
err = reg_openkey ( ctx , create_parent , path , desired_access , pkey ) ;
if ( W_ERROR_IS_OK ( err ) & & ( paction ! = NULL ) ) {
* paction = REG_CREATED_NEW_KEY ;
}
done :
TALLOC_FREE ( mem_ctx ) ;
return err ;
}
WERROR reg_deletekey ( struct registry_key * parent , const char * path )
{
WERROR err ;
TALLOC_CTX * mem_ctx ;
char * name , * end ;
int num_subkeys ;
2007-11-06 02:50:47 +03:00
struct registry_key * tmp_key , * key ;
2006-12-01 23:01:09 +03:00
if ( ! ( mem_ctx = talloc_init ( " reg_createkey " ) ) ) return WERR_NOMEM ;
if ( ! ( name = talloc_strdup ( mem_ctx , path ) ) ) {
err = WERR_NOMEM ;
goto error ;
}
2007-06-22 02:18:42 +04:00
/* check if the key has subkeys */
2007-11-06 02:50:47 +03:00
err = reg_openkey ( mem_ctx , parent , name , REG_KEY_READ , & key ) ;
2007-06-22 02:18:42 +04:00
if ( ! W_ERROR_IS_OK ( err ) ) {
goto error ;
}
2007-11-06 02:50:47 +03:00
if ( ! W_ERROR_IS_OK ( err = fill_subkey_cache ( key ) ) ) {
2007-06-22 02:18:42 +04:00
goto error ;
}
2007-11-06 02:50:47 +03:00
if ( key - > subkeys - > num_subkeys > 0 ) {
2007-06-22 02:18:42 +04:00
err = WERR_ACCESS_DENIED ;
goto error ;
}
2006-12-01 23:01:09 +03:00
2007-06-22 02:18:42 +04:00
/* no subkeys - proceed with delete */
if ( ( end = strrchr ( name , ' \\ ' ) ) ! = NULL ) {
2006-12-01 23:01:09 +03:00
* end = ' \0 ' ;
err = reg_openkey ( mem_ctx , parent , name ,
2007-06-22 02:18:42 +04:00
SEC_RIGHTS_CREATE_SUBKEY , & tmp_key ) ;
2006-12-01 23:01:09 +03:00
if ( ! W_ERROR_IS_OK ( err ) ) {
goto error ;
}
2007-06-22 02:18:42 +04:00
parent = tmp_key ;
2006-12-01 23:01:09 +03:00
name = end + 1 ;
}
if ( name [ 0 ] = = ' \0 ' ) {
err = WERR_INVALID_PARAM ;
goto error ;
}
if ( ! W_ERROR_IS_OK ( err = fill_subkey_cache ( parent ) ) ) {
goto error ;
}
num_subkeys = parent - > subkeys - > num_subkeys ;
if ( regsubkey_ctr_delkey ( parent - > subkeys , name ) = = num_subkeys ) {
err = WERR_BADFILE ;
goto error ;
}
if ( ! store_reg_keys ( parent - > key , parent - > subkeys ) ) {
TALLOC_FREE ( parent - > subkeys ) ;
err = WERR_REG_IO_FAILURE ;
goto error ;
}
2007-11-06 02:50:47 +03:00
regkey_set_secdesc ( key - > key , NULL ) ;
2006-12-01 23:01:09 +03:00
err = WERR_OK ;
2007-11-06 02:50:47 +03:00
2006-12-01 23:01:09 +03:00
error :
TALLOC_FREE ( mem_ctx ) ;
return err ;
}
WERROR reg_setvalue ( struct registry_key * key , const char * name ,
const struct registry_value * val )
{
WERROR err ;
DATA_BLOB value_data ;
int res ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_SET_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! W_ERROR_IS_OK ( err = fill_value_cache ( key ) ) ) {
return err ;
}
err = registry_push_value ( key , val , & value_data ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
return err ;
}
res = regval_ctr_addvalue ( key - > values , name , val - > type ,
( char * ) value_data . data , value_data . length ) ;
TALLOC_FREE ( value_data . data ) ;
if ( res = = 0 ) {
TALLOC_FREE ( key - > values ) ;
return WERR_NOMEM ;
}
if ( ! store_reg_values ( key - > key , key - > values ) ) {
TALLOC_FREE ( key - > values ) ;
return WERR_REG_IO_FAILURE ;
}
return WERR_OK ;
}
2008-03-31 19:20:07 +04:00
static WERROR reg_value_exists ( struct registry_key * key , const char * name )
{
int i ;
for ( i = 0 ; i < key - > values - > num_values ; i + + ) {
if ( strequal ( key - > values - > values [ i ] - > valuename , name ) ) {
return WERR_OK ;
}
}
return WERR_BADFILE ;
}
2006-12-01 23:01:09 +03:00
WERROR reg_deletevalue ( struct registry_key * key , const char * name )
{
WERROR err ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_SET_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! W_ERROR_IS_OK ( err = fill_value_cache ( key ) ) ) {
return err ;
}
2008-03-31 19:20:07 +04:00
err = reg_value_exists ( key , name ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
return err ;
}
2006-12-01 23:01:09 +03:00
regval_ctr_delvalue ( key - > values , name ) ;
if ( ! store_reg_values ( key - > key , key - > values ) ) {
TALLOC_FREE ( key - > values ) ;
return WERR_REG_IO_FAILURE ;
}
return WERR_OK ;
}
2008-01-17 13:02:15 +03:00
WERROR reg_getkeysecurity ( TALLOC_CTX * mem_ctx , struct registry_key * key ,
struct security_descriptor * * psecdesc )
{
return regkey_get_secdesc ( mem_ctx , key - > key , psecdesc ) ;
}
WERROR reg_setkeysecurity ( struct registry_key * key ,
struct security_descriptor * psecdesc )
{
return regkey_set_secdesc ( key - > key , psecdesc ) ;
}
2008-01-17 02:57:53 +03:00
2008-01-17 13:22:01 +03:00
WERROR reg_getversion ( uint32_t * version )
{
if ( version = = NULL ) {
return WERR_INVALID_PARAM ;
}
* version = 0x00000005 ; /* Windows 2000 registry API version */
return WERR_OK ;
}
2008-02-15 17:31:31 +03:00
/*******************************************************************
Note : topkeypat is the * full * path that this * key will be
loaded into ( including the name of the key )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-02-15 19:25:16 +03:00
static WERROR reg_load_tree ( REGF_FILE * regfile , const char * topkeypath ,
REGF_NK_REC * key )
2008-02-15 17:31:31 +03:00
{
REGF_NK_REC * subkey ;
REGISTRY_KEY registry_key ;
REGVAL_CTR * values ;
REGSUBKEY_CTR * subkeys ;
int i ;
char * path = NULL ;
WERROR result = WERR_OK ;
/* initialize the REGISTRY_KEY structure */
2008-04-13 02:54:44 +04:00
registry_key . ops = reghook_cache_find ( topkeypath ) ;
if ( ! registry_key . ops ) {
DEBUG ( 0 , ( " reg_load_tree: Failed to assign REGISTRY_OPS "
2008-02-15 19:25:16 +03:00
" to [%s] \n " , topkeypath ) ) ;
2008-02-15 17:31:31 +03:00
return WERR_BADFILE ;
}
2008-02-15 19:25:16 +03:00
registry_key . name = talloc_strdup ( regfile - > mem_ctx , topkeypath ) ;
if ( ! registry_key . name ) {
DEBUG ( 0 , ( " reg_load_tree: Talloc failed for reg_key.name! \n " ) ) ;
2008-02-15 17:31:31 +03:00
return WERR_NOMEM ;
}
/* now start parsing the values and subkeys */
2008-02-15 19:25:16 +03:00
subkeys = TALLOC_ZERO_P ( regfile - > mem_ctx , REGSUBKEY_CTR ) ;
if ( subkeys = = NULL ) {
2008-02-15 17:31:31 +03:00
return WERR_NOMEM ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:31:31 +03:00
2008-02-15 19:25:16 +03:00
values = TALLOC_ZERO_P ( subkeys , REGVAL_CTR ) ;
if ( values = = NULL ) {
2008-02-15 17:31:31 +03:00
return WERR_NOMEM ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:31:31 +03:00
/* copy values into the REGVAL_CTR */
2008-02-15 19:25:16 +03:00
for ( i = 0 ; i < key - > num_values ; i + + ) {
regval_ctr_addvalue ( values , key - > values [ i ] . valuename ,
key - > values [ i ] . type ,
( char * ) key - > values [ i ] . data ,
( key - > values [ i ] . data_size & ~ VK_DATA_IN_OFFSET ) ) ;
2008-02-15 17:31:31 +03:00
}
/* copy subkeys into the REGSUBKEY_CTR */
key - > subkey_index = 0 ;
2008-02-15 19:25:16 +03:00
while ( ( subkey = regfio_fetch_subkey ( regfile , key ) ) ) {
regsubkey_ctr_addkey ( subkeys , subkey - > keyname ) ;
2008-02-15 17:31:31 +03:00
}
/* write this key and values out */
2008-02-15 19:25:16 +03:00
if ( ! store_reg_values ( & registry_key , values )
| | ! store_reg_keys ( & registry_key , subkeys ) )
2008-02-15 17:31:31 +03:00
{
DEBUG ( 0 , ( " reg_load_tree: Failed to load %s! \n " , topkeypath ) ) ;
result = WERR_REG_IO_FAILURE ;
}
2008-02-15 19:25:16 +03:00
TALLOC_FREE ( subkeys ) ;
2008-02-15 17:31:31 +03:00
2008-02-15 19:25:16 +03:00
if ( ! W_ERROR_IS_OK ( result ) ) {
2008-02-15 17:31:31 +03:00
return result ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:31:31 +03:00
/* now continue to load each subkey registry tree */
key - > subkey_index = 0 ;
2008-02-15 19:25:16 +03:00
while ( ( subkey = regfio_fetch_subkey ( regfile , key ) ) ) {
2008-02-15 17:31:31 +03:00
path = talloc_asprintf ( regfile - > mem_ctx ,
2008-02-15 19:25:16 +03:00
" %s \\ %s " ,
topkeypath ,
subkey - > keyname ) ;
if ( path = = NULL ) {
2008-02-15 17:31:31 +03:00
return WERR_NOMEM ;
}
2008-02-15 19:25:16 +03:00
result = reg_load_tree ( regfile , path , subkey ) ;
if ( ! W_ERROR_IS_OK ( result ) ) {
2008-02-15 17:31:31 +03:00
break ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:31:31 +03:00
}
return result ;
}
/*******************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-02-15 19:25:16 +03:00
static WERROR restore_registry_key ( REGISTRY_KEY * krecord , const char * fname )
2008-02-15 17:31:31 +03:00
{
REGF_FILE * regfile ;
REGF_NK_REC * rootkey ;
WERROR result ;
2008-02-15 19:25:16 +03:00
2008-02-15 17:31:31 +03:00
/* open the registry file....fail if the file already exists */
2008-02-15 19:25:16 +03:00
regfile = regfio_open ( fname , ( O_RDONLY ) , 0 ) ;
if ( regfile = = NULL ) {
DEBUG ( 0 , ( " restore_registry_key: failed to open \" %s \" (%s) \n " ,
fname , strerror ( errno ) ) ) ;
return ntstatus_to_werror ( map_nt_error_from_unix ( errno ) ) ;
}
2008-02-15 17:31:31 +03:00
/* get the rootkey from the regf file and then load the tree
via recursive calls */
2008-02-15 19:25:16 +03:00
if ( ! ( rootkey = regfio_rootkey ( regfile ) ) ) {
regfio_close ( regfile ) ;
2008-02-15 17:31:31 +03:00
return WERR_REG_FILE_INVALID ;
}
2008-02-15 19:25:16 +03:00
result = reg_load_tree ( regfile , krecord - > name , rootkey ) ;
2008-02-15 17:31:31 +03:00
/* cleanup */
2008-02-15 19:25:16 +03:00
regfio_close ( regfile ) ;
2008-02-15 17:31:31 +03:00
return result ;
}
WERROR reg_restorekey ( struct registry_key * key , const char * fname )
{
return restore_registry_key ( key - > key , fname ) ;
}
2008-02-15 17:14:20 +03:00
/********************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-02-15 19:25:16 +03:00
static WERROR reg_write_tree ( REGF_FILE * regfile , const char * keypath ,
2008-02-18 18:33:47 +03:00
REGF_NK_REC * parent )
2008-02-15 17:14:20 +03:00
{
REGF_NK_REC * key ;
REGVAL_CTR * values ;
REGSUBKEY_CTR * subkeys ;
int i , num_subkeys ;
char * key_tmp = NULL ;
char * keyname , * parentpath ;
char * subkeypath = NULL ;
char * subkeyname ;
REGISTRY_KEY registry_key ;
WERROR result = WERR_OK ;
2008-02-18 18:33:47 +03:00
SEC_DESC * sec_desc = NULL ;
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
if ( ! regfile ) {
2008-02-15 17:14:20 +03:00
return WERR_GENERAL_FAILURE ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
if ( ! keypath ) {
2008-02-15 17:14:20 +03:00
return WERR_OBJECT_PATH_INVALID ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
/* split up the registry key path */
key_tmp = talloc_strdup ( regfile - > mem_ctx , keypath ) ;
if ( ! key_tmp ) {
return WERR_NOMEM ;
}
2008-02-15 19:25:16 +03:00
if ( ! reg_split_key ( key_tmp , & parentpath , & keyname ) ) {
2008-02-15 17:14:20 +03:00
return WERR_OBJECT_PATH_INVALID ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
if ( ! keyname ) {
2008-02-15 17:14:20 +03:00
keyname = parentpath ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
/* we need a REGISTRY_KEY object here to enumerate subkeys and values */
2008-02-15 19:25:16 +03:00
ZERO_STRUCT ( registry_key ) ;
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
registry_key . name = talloc_strdup ( regfile - > mem_ctx , keypath ) ;
if ( registry_key . name = = NULL ) {
2008-02-15 17:14:20 +03:00
return WERR_NOMEM ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
2008-04-13 02:54:44 +04:00
registry_key . ops = reghook_cache_find ( registry_key . name ) ;
if ( registry_key . ops = = NULL ) {
2008-02-15 17:14:20 +03:00
return WERR_BADFILE ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
/* lookup the values and subkeys */
2008-02-15 19:25:16 +03:00
subkeys = TALLOC_ZERO_P ( regfile - > mem_ctx , REGSUBKEY_CTR ) ;
if ( subkeys = = NULL ) {
2008-02-15 17:14:20 +03:00
return WERR_NOMEM ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
values = TALLOC_ZERO_P ( subkeys , REGVAL_CTR ) ;
if ( values = = NULL ) {
2008-02-15 17:14:20 +03:00
return WERR_NOMEM ;
2008-02-15 19:25:16 +03:00
}
2008-02-15 17:14:20 +03:00
2008-02-15 19:25:16 +03:00
fetch_reg_keys ( & registry_key , subkeys ) ;
fetch_reg_values ( & registry_key , values ) ;
2008-02-15 17:14:20 +03:00
2008-02-18 18:33:47 +03:00
result = regkey_get_secdesc ( regfile - > mem_ctx , & registry_key , & sec_desc ) ;
if ( ! W_ERROR_IS_OK ( result ) ) {
goto done ;
}
2008-02-15 17:14:20 +03:00
/* write out this key */
2008-02-15 19:25:16 +03:00
key = regfio_write_key ( regfile , keyname , values , subkeys , sec_desc ,
parent ) ;
if ( key = = NULL ) {
2008-02-15 17:14:20 +03:00
result = WERR_CAN_NOT_COMPLETE ;
goto done ;
}
/* write each one of the subkeys out */
2008-02-15 19:25:16 +03:00
num_subkeys = regsubkey_ctr_numkeys ( subkeys ) ;
for ( i = 0 ; i < num_subkeys ; i + + ) {
subkeyname = regsubkey_ctr_specific_key ( subkeys , i ) ;
subkeypath = talloc_asprintf ( regfile - > mem_ctx , " %s \\ %s " ,
keypath , subkeyname ) ;
if ( subkeypath = = NULL ) {
2008-02-15 17:14:20 +03:00
result = WERR_NOMEM ;
goto done ;
}
2008-02-18 18:33:47 +03:00
result = reg_write_tree ( regfile , subkeypath , key ) ;
2008-02-15 19:25:16 +03:00
if ( ! W_ERROR_IS_OK ( result ) )
2008-02-15 17:14:20 +03:00
goto done ;
}
2008-02-15 19:25:16 +03:00
DEBUG ( 6 , ( " reg_write_tree: wrote key [%s] \n " , keypath ) ) ;
2008-02-15 17:14:20 +03:00
done :
2008-02-15 19:25:16 +03:00
TALLOC_FREE ( subkeys ) ;
TALLOC_FREE ( registry_key . name ) ;
2008-02-15 17:14:20 +03:00
return result ;
}
2008-02-15 19:25:16 +03:00
static WERROR backup_registry_key ( REGISTRY_KEY * krecord , const char * fname )
2008-02-15 17:14:20 +03:00
{
REGF_FILE * regfile ;
WERROR result ;
2008-02-15 19:25:16 +03:00
2008-02-15 17:14:20 +03:00
/* open the registry file....fail if the file already exists */
2008-02-15 19:25:16 +03:00
regfile = regfio_open ( fname , ( O_RDWR | O_CREAT | O_EXCL ) ,
( S_IREAD | S_IWRITE ) ) ;
if ( regfile = = NULL ) {
DEBUG ( 0 , ( " backup_registry_key: failed to open \" %s \" (%s) \n " ,
fname , strerror ( errno ) ) ) ;
return ntstatus_to_werror ( map_nt_error_from_unix ( errno ) ) ;
}
2008-02-15 17:14:20 +03:00
/* write the registry tree to the file */
2008-02-18 18:33:47 +03:00
result = reg_write_tree ( regfile , krecord - > name , NULL ) ;
2008-02-15 17:14:20 +03:00
/* cleanup */
2008-02-15 19:25:16 +03:00
regfio_close ( regfile ) ;
2008-02-15 17:14:20 +03:00
return result ;
}
WERROR reg_savekey ( struct registry_key * key , const char * fname )
{
return backup_registry_key ( key - > key , fname ) ;
}
2008-01-17 02:57:53 +03:00
/**********************************************************************
* Higher level utility functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2007-08-14 19:29:42 +04:00
WERROR reg_deleteallvalues ( struct registry_key * key )
{
WERROR err ;
int i ;
if ( ! ( key - > key - > access_granted & SEC_RIGHTS_SET_VALUE ) ) {
return WERR_ACCESS_DENIED ;
}
if ( ! W_ERROR_IS_OK ( err = fill_value_cache ( key ) ) ) {
return err ;
}
for ( i = 0 ; i < key - > values - > num_values ; i + + ) {
regval_ctr_delvalue ( key - > values , key - > values - > values [ i ] - > valuename ) ;
}
if ( ! store_reg_values ( key - > key , key - > values ) ) {
TALLOC_FREE ( key - > values ) ;
return WERR_REG_IO_FAILURE ;
}
return WERR_OK ;
}
2007-04-09 14:38:55 +04:00
/*
2008-02-15 16:23:31 +03:00
* Utility function to open a complete registry path including the hive prefix .
2007-04-09 14:38:55 +04:00
*/
WERROR reg_open_path ( TALLOC_CTX * mem_ctx , const char * orig_path ,
uint32 desired_access , const struct nt_user_token * token ,
struct registry_key * * pkey )
{
struct registry_key * hive , * key ;
char * path , * p ;
WERROR err ;
if ( ! ( path = SMB_STRDUP ( orig_path ) ) ) {
return WERR_NOMEM ;
}
p = strchr ( path , ' \\ ' ) ;
if ( ( p = = NULL ) | | ( p [ 1 ] = = ' \0 ' ) ) {
/*
* No key behind the hive , just return the hive
*/
err = reg_openhive ( mem_ctx , path , desired_access , token ,
& hive ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
SAFE_FREE ( path ) ;
return err ;
}
SAFE_FREE ( path ) ;
* pkey = hive ;
return WERR_OK ;
}
* p = ' \0 ' ;
err = reg_openhive ( mem_ctx , path , SEC_RIGHTS_ENUM_SUBKEYS , token ,
& hive ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
SAFE_FREE ( path ) ;
return err ;
}
err = reg_openkey ( mem_ctx , hive , p + 1 , desired_access , & key ) ;
TALLOC_FREE ( hive ) ;
SAFE_FREE ( path ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
return err ;
}
* pkey = key ;
return WERR_OK ;
}
2007-06-22 15:21:59 +04:00
/*
2008-01-15 16:55:04 +03:00
* Utility function to delete a registry key with all its subkeys .
* Note that reg_deletekey returns ACCESS_DENIED when called on a
2007-06-22 15:21:59 +04:00
* key that has subkeys .
*/
2008-01-17 02:16:58 +03:00
static WERROR reg_deletekey_recursive_internal ( TALLOC_CTX * ctx ,
struct registry_key * parent ,
const char * path ,
bool del_key )
2007-06-22 15:21:59 +04:00
{
TALLOC_CTX * mem_ctx = NULL ;
WERROR werr = WERR_OK ;
struct registry_key * key ;
char * subkey_name = NULL ;
mem_ctx = talloc_new ( ctx ) ;
if ( mem_ctx = = NULL ) {
werr = WERR_NOMEM ;
goto done ;
}
/* recurse through subkeys first */
2007-12-31 05:25:54 +03:00
werr = reg_openkey ( mem_ctx , parent , path , REG_KEY_ALL , & key ) ;
2007-06-22 15:21:59 +04:00
if ( ! W_ERROR_IS_OK ( werr ) ) {
goto done ;
}
2007-06-22 15:42:17 +04:00
while ( W_ERROR_IS_OK ( werr = reg_enumkey ( mem_ctx , key , 0 ,
2008-01-15 16:55:04 +03:00
& subkey_name , NULL ) ) )
2007-06-22 15:21:59 +04:00
{
2007-09-27 05:26:19 +04:00
werr = reg_deletekey_recursive_internal ( mem_ctx , key ,
subkey_name ,
2008-01-15 16:56:00 +03:00
true ) ;
2007-06-22 15:21:59 +04:00
if ( ! W_ERROR_IS_OK ( werr ) ) {
goto done ;
}
}
if ( ! W_ERROR_EQUAL ( WERR_NO_MORE_ITEMS , werr ) ) {
2007-09-27 05:26:19 +04:00
DEBUG ( 1 , ( " reg_deletekey_recursive_internal: "
" Error enumerating subkeys: %s \n " ,
dos_errstr ( werr ) ) ) ;
2007-06-22 15:21:59 +04:00
goto done ;
}
2007-09-27 05:26:19 +04:00
werr = WERR_OK ;
if ( del_key ) {
/* now delete the actual key */
werr = reg_deletekey ( parent , path ) ;
}
2007-06-22 15:21:59 +04:00
done :
TALLOC_FREE ( mem_ctx ) ;
return werr ;
}
2007-09-27 05:26:19 +04:00
WERROR reg_deletekey_recursive ( TALLOC_CTX * ctx ,
struct registry_key * parent ,
const char * path )
{
2008-01-15 16:56:00 +03:00
return reg_deletekey_recursive_internal ( ctx , parent , path , true ) ;
2007-09-27 05:26:19 +04:00
}
WERROR reg_deletesubkeys_recursive ( TALLOC_CTX * ctx ,
struct registry_key * parent ,
const char * path )
{
2008-01-15 16:56:00 +03:00
return reg_deletekey_recursive_internal ( ctx , parent , path , false ) ;
2007-09-27 05:26:19 +04:00
}
2008-01-17 12:19:12 +03:00
2008-01-17 12:30:56 +03:00
#if 0
/* these two functions are unused. */
/**
2008-01-17 12:19:12 +03:00
* Utility function to create a registry key without opening the hive
* before . Assumes the hive already exists .
*/
WERROR reg_create_path ( TALLOC_CTX * mem_ctx , const char * orig_path ,
uint32 desired_access ,
const struct nt_user_token * token ,
enum winreg_CreateAction * paction ,
struct registry_key * * pkey )
{
struct registry_key * hive ;
char * path , * p ;
WERROR err ;
if ( ! ( path = SMB_STRDUP ( orig_path ) ) ) {
return WERR_NOMEM ;
}
p = strchr ( path , ' \\ ' ) ;
if ( ( p = = NULL ) | | ( p [ 1 ] = = ' \0 ' ) ) {
/*
* No key behind the hive , just return the hive
*/
err = reg_openhive ( mem_ctx , path , desired_access , token ,
& hive ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
SAFE_FREE ( path ) ;
return err ;
}
SAFE_FREE ( path ) ;
* pkey = hive ;
* paction = REG_OPENED_EXISTING_KEY ;
return WERR_OK ;
}
* p = ' \0 ' ;
err = reg_openhive ( mem_ctx , path ,
( strchr ( p + 1 , ' \\ ' ) ! = NULL ) ?
SEC_RIGHTS_ENUM_SUBKEYS : SEC_RIGHTS_CREATE_SUBKEY ,
token , & hive ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
SAFE_FREE ( path ) ;
return err ;
}
err = reg_createkey ( mem_ctx , hive , p + 1 , desired_access , pkey , paction ) ;
SAFE_FREE ( path ) ;
TALLOC_FREE ( hive ) ;
return err ;
}
/*
* Utility function to create a registry key without opening the hive
* before . Will not delete a hive .
*/
WERROR reg_delete_path ( const struct nt_user_token * token ,
const char * orig_path )
{
struct registry_key * hive ;
char * path , * p ;
WERROR err ;
if ( ! ( path = SMB_STRDUP ( orig_path ) ) ) {
return WERR_NOMEM ;
}
p = strchr ( path , ' \\ ' ) ;
if ( ( p = = NULL ) | | ( p [ 1 ] = = ' \0 ' ) ) {
SAFE_FREE ( path ) ;
return WERR_INVALID_PARAM ;
}
* p = ' \0 ' ;
err = reg_openhive ( NULL , path ,
( strchr ( p + 1 , ' \\ ' ) ! = NULL ) ?
SEC_RIGHTS_ENUM_SUBKEYS : SEC_RIGHTS_CREATE_SUBKEY ,
token , & hive ) ;
if ( ! W_ERROR_IS_OK ( err ) ) {
SAFE_FREE ( path ) ;
return err ;
}
err = reg_deletekey ( hive , p + 1 ) ;
SAFE_FREE ( path ) ;
TALLOC_FREE ( hive ) ;
return err ;
}
2008-01-17 12:30:56 +03:00
# endif /* #if 0 */