/* * Unix SMB/CIFS implementation. * Virtual Windows Registry Layer * Copyright (C) Gerald Carter 2002-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 * the Free Software Foundation; either version 3 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, see . */ /* Implementation of internal registry database functions. */ #include "includes.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_REGISTRY static struct tdb_wrap *tdb_reg = NULL; static int tdb_refcount; /* List the deepest path into the registry. All part components will be created.*/ /* If you want to have a part of the path controlled by the tdb and part by a virtual registry db (e.g. printing), then you have to list the deepest path. For example,"HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Print" allows the reg_db backend to handle everything up to "HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion" and then we'll hook the reg_printing backend onto the last component of the path (see KEY_PRINTING_2K in include/rpc_reg.h) --jerry */ static const char *builtin_registry_paths[] = { KEY_PRINTING_2K, KEY_PRINTING_PORTS, KEY_PRINTING, KEY_SHARES, KEY_EVENTLOG, KEY_SMBCONF, "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib", "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\DefaultUserConfiguration", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\TcpIp\\Parameters", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters", "HKU", "HKCR", "HKPD", "HKPT", NULL }; struct builtin_regkey_value { const char *path; const char *valuename; uint32 type; union { const char *string; uint32 dw_value; } data; }; static struct builtin_regkey_value builtin_registry_values[] = { { KEY_PRINTING_PORTS, SAMBA_PRINTER_PORT_NAME, REG_SZ, { "" } }, { KEY_PRINTING_2K, "DefaultSpoolDirectory", REG_SZ, { "C:\\Windows\\System32\\Spool\\Printers" } }, { KEY_EVENTLOG, "DisplayName", REG_SZ, { "Event Log" } }, { KEY_EVENTLOG, "ErrorControl", REG_DWORD, { (char*)0x00000001 } }, { NULL, NULL, 0, { NULL } } }; /*********************************************************************** Open the registry data in the tdb ***********************************************************************/ static bool init_registry_data( void ) { char *path = NULL; char *base = NULL; char *remaining = NULL; TALLOC_CTX *frame = NULL; char *keyname; char *subkeyname; REGSUBKEY_CTR *subkeys; REGVAL_CTR *values; int i; const char *p, *p2; UNISTR2 data; /* * There are potentially quite a few store operations which are all * indiviually wrapped in tdb transactions. Wrapping them in a single * transaction gives just a single transaction_commit() to actually do * its fsync()s. See tdb/common/transaction.c for info about nested * transaction behaviour. */ if ( tdb_transaction_start( tdb_reg->tdb ) == -1 ) { DEBUG(0, ("init_registry_data: tdb_transaction_start " "failed\n")); return false; } /* loop over all of the predefined paths and add each component */ for ( i=0; builtin_registry_paths[i] != NULL; i++ ) { frame = talloc_stackframe(); DEBUG(6,("init_registry_data: Adding [%s]\n", builtin_registry_paths[i])); path = talloc_strdup(talloc_tos(), builtin_registry_paths[i]); base = talloc_strdup(talloc_tos(), ""); if (!path || !base) { goto fail; } p = path; while (next_token_talloc(talloc_tos(), &p, &keyname, "\\")) { /* build up the registry path from the components */ if (*base) { base = talloc_asprintf(talloc_tos(), "%s\\", base); if (!base) { goto fail; } } base = talloc_asprintf_append(base, "%s", keyname); if (!base) { goto fail; } /* get the immediate subkeyname (if we have one ) */ subkeyname = talloc_strdup(talloc_tos(), ""); if (!subkeyname) { goto fail; } if (*p) { remaining = talloc_strdup(talloc_tos(), p); if (!remaining) { goto fail; } p2 = remaining; if (!next_token_talloc(talloc_tos(), &p2, &subkeyname, "\\")) { subkeyname = talloc_strdup(talloc_tos(),p2); if (!subkeyname) { goto fail; } } } DEBUG(10,("init_registry_data: Storing key [%s] with subkey [%s]\n", base, *subkeyname ? subkeyname : "NULL")); /* we don't really care if the lookup succeeds or not since we are about to update the record. We just want any subkeys already present */ if ( !(subkeys = TALLOC_ZERO_P(talloc_tos(), REGSUBKEY_CTR )) ) { DEBUG(0,("talloc() failure!\n")); goto fail; } regdb_fetch_keys(base, subkeys); if (*subkeyname) { regsubkey_ctr_addkey( subkeys, subkeyname); } if (!regdb_store_keys( base, subkeys)) { goto fail; } } TALLOC_FREE(frame); } /* loop over all of the predefined values and add each component */ for (i=0; builtin_registry_values[i].path != NULL; i++) { if (!(values = TALLOC_ZERO_P(talloc_tos(), REGVAL_CTR))) { goto fail; } regdb_fetch_values( builtin_registry_values[i].path, values); /* preserve existing values across restarts. Only add new ones */ if (!regval_ctr_key_exists(values, builtin_registry_values[i].valuename)) { switch(builtin_registry_values[i].type) { case REG_DWORD: regval_ctr_addvalue( values, builtin_registry_values[i].valuename, REG_DWORD, (char*)&builtin_registry_values[i].data.dw_value, sizeof(uint32) ); break; case REG_SZ: init_unistr2( &data, builtin_registry_values[i].data.string, UNI_STR_TERMINATE); regval_ctr_addvalue( values, builtin_registry_values[i].valuename, REG_SZ, (char*)data.buffer, data.uni_str_len*sizeof(uint16) ); break; default: DEBUG(0,("init_registry_data: invalid value type in builtin_registry_values [%d]\n", builtin_registry_values[i].type)); } regdb_store_values( builtin_registry_values[i].path, values ); } TALLOC_FREE( values ); } TALLOC_FREE(frame); if (tdb_transaction_commit( tdb_reg->tdb ) == -1) { DEBUG(0, ("init_registry_data: Could not commit " "transaction\n")); return false; } return true; fail: TALLOC_FREE(frame); if (tdb_transaction_cancel( tdb_reg->tdb ) == -1) { smb_panic("init_registry_data: tdb_transaction_cancel " "failed\n"); } return false; } /*********************************************************************** Open the registry database ***********************************************************************/ bool regdb_init( void ) { const char *vstring = "INFO/version"; uint32 vers_id; if ( tdb_reg ) return true; if ( !(tdb_reg = tdb_wrap_open(NULL, state_path("registry.tdb"), 0, REG_TDB_FLAGS, O_RDWR, 0600)) ) { tdb_reg = tdb_wrap_open(NULL, state_path("registry.tdb"), 0, REG_TDB_FLAGS, O_RDWR|O_CREAT, 0600); if ( !tdb_reg ) { DEBUG(0,("regdb_init: Failed to open registry %s (%s)\n", state_path("registry.tdb"), strerror(errno) )); return false; } DEBUG(10,("regdb_init: Successfully created registry tdb\n")); } tdb_refcount = 1; vers_id = tdb_fetch_int32(tdb_reg->tdb, vstring); if ( vers_id != REGVER_V1 ) { /* any upgrade code here if needed */ DEBUG(10, ("regdb_init: got INFO/version = %d != %d\n", vers_id, REGVER_V1)); } /* always setup the necessary keys and values */ if ( !init_registry_data() ) { DEBUG(0,("regdb_init: Failed to initialize data in registry!\n")); return false; } return true; } /*********************************************************************** Open the registry. Must already have been initialized by regdb_init() ***********************************************************************/ WERROR regdb_open( void ) { WERROR result = WERR_OK; if ( tdb_reg ) { DEBUG(10,("regdb_open: incrementing refcount (%d)\n", tdb_refcount)); tdb_refcount++; return WERR_OK; } become_root(); tdb_reg = tdb_wrap_open(NULL, state_path("registry.tdb"), 0, REG_TDB_FLAGS, O_RDWR, 0600); if ( !tdb_reg ) { result = ntstatus_to_werror( map_nt_error_from_unix( errno ) ); DEBUG(0,("regdb_open: Failed to open %s! (%s)\n", state_path("registry.tdb"), strerror(errno) )); } unbecome_root(); tdb_refcount = 1; DEBUG(10,("regdb_open: refcount reset (%d)\n", tdb_refcount)); return result; } /*********************************************************************** ***********************************************************************/ int regdb_close( void ) { if (tdb_refcount == 0) { return 0; } tdb_refcount--; DEBUG(10,("regdb_close: decrementing refcount (%d)\n", tdb_refcount)); if ( tdb_refcount > 0 ) return 0; SMB_ASSERT( tdb_refcount >= 0 ); TALLOC_FREE(tdb_reg); return 0; } /*********************************************************************** return the tdb sequence number of the registry tdb. this is an indicator for the content of the registry having changed. it will change upon regdb_init, too, though. ***********************************************************************/ int regdb_get_seqnum(void) { return tdb_get_seqnum(tdb_reg->tdb); } /*********************************************************************** Add subkey strings to the registry tdb under a defined key fmt is the same format as tdb_pack except this function only supports fstrings ***********************************************************************/ static bool regdb_store_keys_internal(const char *key, REGSUBKEY_CTR *ctr) { TDB_DATA dbuf; uint8 *buffer = NULL; int i = 0; uint32 len, buflen; bool ret = true; uint32 num_subkeys = regsubkey_ctr_numkeys(ctr); char *keyname = NULL; TALLOC_CTX *ctx = talloc_tos(); if (!key) { return false; } keyname = talloc_strdup(ctx, key); if (!keyname) { return false; } keyname = normalize_reg_path(ctx, keyname); /* allocate some initial memory */ if (!(buffer = (uint8 *)SMB_MALLOC(1024))) { return false; } buflen = 1024; len = 0; /* store the number of subkeys */ len += tdb_pack(buffer+len, buflen-len, "d", num_subkeys ); /* pack all the strings */ for (i=0; i buflen ) { /* allocate some extra space */ if ((buffer = (uint8 *)SMB_REALLOC( buffer, len*2 )) == NULL) { DEBUG(0,("regdb_store_keys: Failed to realloc memory of size [%d]\n", len*2)); ret = false; goto done; } buflen = len*2; len = tdb_pack( buffer+len, buflen-len, "f", regsubkey_ctr_specific_key(ctr, i) ); } } /* finally write out the data */ dbuf.dptr = buffer; dbuf.dsize = len; if ( tdb_store_bystring( tdb_reg->tdb, keyname, dbuf, TDB_REPLACE ) == -1) { ret = false; goto done; } done: SAFE_FREE( buffer ); return ret; } /*********************************************************************** Store the new subkey record and create any child key records that do not currently exist ***********************************************************************/ bool regdb_store_keys(const char *key, REGSUBKEY_CTR *ctr) { int num_subkeys, i; char *path = NULL; REGSUBKEY_CTR *subkeys = NULL, *old_subkeys = NULL; char *oldkeyname = NULL; TALLOC_CTX *ctx = talloc_tos(); /* * fetch a list of the old subkeys so we can determine if anything has * changed */ if (!(old_subkeys = TALLOC_ZERO_P(ctr, REGSUBKEY_CTR))) { DEBUG(0,("regdb_store_keys: talloc() failure!\n")); return false; } regdb_fetch_keys(key, old_subkeys); if (ctr->num_subkeys == old_subkeys->num_subkeys) { for (i = 0; inum_subkeys; i++) { if (strcmp(ctr->subkeys[i], old_subkeys->subkeys[i]) != 0) { break; } } if (i == ctr->num_subkeys) { /* * Nothing changed, no point to even start a tdb * transaction */ TALLOC_FREE(old_subkeys); return true; } } if (tdb_transaction_start( tdb_reg->tdb ) == -1) { DEBUG(0, ("regdb_store_keys: tdb_transaction_start failed\n")); return false; } /* * Re-fetch the old keys inside the transaction */ TALLOC_FREE(old_subkeys); if (!(old_subkeys = TALLOC_ZERO_P(ctr, REGSUBKEY_CTR))) { DEBUG(0,("regdb_store_keys: talloc() failure!\n")); goto fail; } regdb_fetch_keys(key, old_subkeys); /* store the subkey list for the parent */ if (!regdb_store_keys_internal(key, ctr) ) { DEBUG(0,("regdb_store_keys: Failed to store new subkey list " "for parent [%s]\n", key)); goto fail; } /* now delete removed keys */ num_subkeys = regsubkey_ctr_numkeys(old_subkeys); for (i=0; itdb, path) == -1) { DEBUG(1, ("Deleting %s failed\n", path)); goto fail; } TALLOC_FREE(path); path = talloc_asprintf(ctx, "%s/%s/%s", REG_VALUE_PREFIX, key, oldkeyname ); if (!path) { goto fail; } path = normalize_reg_path(ctx, path); if (!path) { goto fail; } /* * Ignore errors here, we might have no values around */ tdb_delete_bystring( tdb_reg->tdb, path ); TALLOC_FREE(path); } TALLOC_FREE(old_subkeys); /* now create records for any subkeys that don't already exist */ num_subkeys = regsubkey_ctr_numkeys(ctr); for (i=0; itdb ) == -1) { DEBUG(0, ("regdb_store_keys: Could not commit transaction\n")); return false; } return true; fail: TALLOC_FREE(old_subkeys); TALLOC_FREE(subkeys); if (tdb_transaction_cancel(tdb_reg->tdb) == -1) { smb_panic("regdb_store_keys: tdb_transaction_cancel failed\n"); } return false; } /*********************************************************************** Retrieve an array of strings containing subkeys. Memory should be released by the caller. ***********************************************************************/ int regdb_fetch_keys(const char *key, REGSUBKEY_CTR *ctr) { char *path = NULL; uint32 num_items; TDB_DATA dbuf; uint8 *buf; uint32 buflen, len; int i; fstring subkeyname; int ret = -1; TALLOC_CTX *frame = talloc_stackframe(); DEBUG(11,("regdb_fetch_keys: Enter key => [%s]\n", key ? key : "NULL")); path = talloc_strdup(talloc_tos(), key); if (!path) { goto fail; } /* convert to key format */ path = talloc_string_sub(talloc_tos(), path, "\\", "/"); if (!path) { goto fail; } strupper_m(path); if (tdb_read_lock_bystring_with_timeout(tdb_reg->tdb, path, 10) == -1) { return 0; } dbuf = tdb_fetch_bystring(tdb_reg->tdb, path); ctr->seqnum = regdb_get_seqnum(); tdb_read_unlock_bystring(tdb_reg->tdb, path); buf = dbuf.dptr; buflen = dbuf.dsize; if ( !buf ) { DEBUG(5,("regdb_fetch_keys: tdb lookup failed to locate key [%s]\n", key)); goto fail; } len = tdb_unpack( buf, buflen, "d", &num_items); for (i=0; itdb, keystr, 10) == -1) { return 0; } data = tdb_fetch_bystring(tdb_reg->tdb, keystr); values->seqnum = regdb_get_seqnum(); tdb_read_unlock_bystring(tdb_reg->tdb, keystr); if (!data.dptr) { /* all keys have zero values by default */ return 0; } regdb_unpack_values(values, data.dptr, data.dsize); SAFE_FREE(data.dptr); return regval_ctr_numvals(values); } bool regdb_store_values( const char *key, REGVAL_CTR *values ) { TDB_DATA old_data, data; char *keystr = NULL; TALLOC_CTX *ctx = talloc_tos(); int len, ret; DEBUG(10,("regdb_store_values: Looking for value of key [%s] \n", key)); ZERO_STRUCT(data); len = regdb_pack_values(values, data.dptr, data.dsize); if (len <= 0) { DEBUG(0,("regdb_store_values: unable to pack values. len <= 0\n")); return false; } data.dptr = SMB_MALLOC_ARRAY( uint8, len ); data.dsize = len; len = regdb_pack_values(values, data.dptr, data.dsize); SMB_ASSERT( len == data.dsize ); keystr = talloc_asprintf(ctx, "%s/%s", REG_VALUE_PREFIX, key ); if (!keystr) { SAFE_FREE(data.dptr); return false; } keystr = normalize_reg_path(ctx, keystr); if (!keystr) { SAFE_FREE(data.dptr); return false; } old_data = tdb_fetch_bystring(tdb_reg->tdb, keystr); if ((old_data.dptr != NULL) && (old_data.dsize == data.dsize) && (memcmp(old_data.dptr, data.dptr, data.dsize) == 0)) { SAFE_FREE(old_data.dptr); SAFE_FREE(data.dptr); return true; } ret = tdb_trans_store_bystring(tdb_reg->tdb, keystr, data, TDB_REPLACE); SAFE_FREE( old_data.dptr ); SAFE_FREE( data.dptr ); return ret != -1 ; } static WERROR regdb_get_secdesc(TALLOC_CTX *mem_ctx, const char *key, struct security_descriptor **psecdesc) { char *tdbkey; TDB_DATA data; NTSTATUS status; DEBUG(10, ("regdb_get_secdesc: Getting secdesc of key [%s]\n", key)); if (asprintf(&tdbkey, "%s/%s", REG_SECDESC_PREFIX, key) == -1) { return WERR_NOMEM; } normalize_dbkey(tdbkey); data = tdb_fetch_bystring(tdb_reg->tdb, tdbkey); SAFE_FREE(tdbkey); if (data.dptr == NULL) { return WERR_BADFILE; } status = unmarshall_sec_desc(mem_ctx, (uint8 *)data.dptr, data.dsize, psecdesc); SAFE_FREE(data.dptr); if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { return WERR_NOMEM; } if (!NT_STATUS_IS_OK(status)) { return WERR_REG_CORRUPT; } return WERR_OK; } static WERROR regdb_set_secdesc(const char *key, struct security_descriptor *secdesc) { prs_struct ps; TALLOC_CTX *mem_ctx; char *tdbkey; WERROR err = WERR_NOMEM; TDB_DATA tdbdata; if (!(mem_ctx = talloc_init("regdb_set_secdesc"))) { return WERR_NOMEM; } ZERO_STRUCT(ps); if (!(tdbkey = talloc_asprintf(mem_ctx, "%s/%s", REG_SECDESC_PREFIX, key))) { goto done; } normalize_dbkey(tdbkey); if (secdesc == NULL) { /* assuming a delete */ int tdb_ret; tdb_ret = tdb_trans_delete(tdb_reg->tdb, string_term_tdb_data(tdbkey)); if (tdb_ret == -1) { err = ntstatus_to_werror(map_nt_error_from_unix(errno)); } else { err = WERR_OK; } goto done; } err = ntstatus_to_werror(marshall_sec_desc(mem_ctx, secdesc, &tdbdata.dptr, &tdbdata.dsize)); if (!W_ERROR_IS_OK(err)) { goto done; } if (tdb_trans_store_bystring(tdb_reg->tdb, tdbkey, tdbdata, 0) == -1) { err = ntstatus_to_werror(map_nt_error_from_unix(errno)); goto done; } done: prs_mem_free(&ps); TALLOC_FREE(mem_ctx); return err; } bool regdb_subkeys_need_update(REGSUBKEY_CTR *subkeys) { return (regdb_get_seqnum() != subkeys->seqnum); } bool regdb_values_need_update(REGVAL_CTR *values) { return (regdb_get_seqnum() != values->seqnum); } /* * Table of function pointers for default access */ REGISTRY_OPS regdb_ops = { regdb_fetch_keys, regdb_fetch_values, regdb_store_keys, regdb_store_values, NULL, regdb_get_secdesc, regdb_set_secdesc, regdb_subkeys_need_update, regdb_values_need_update };