diff options
Diffstat (limited to '')
-rw-r--r-- | source4/lib/registry/regf.c | 2319 |
1 files changed, 2319 insertions, 0 deletions
diff --git a/source4/lib/registry/regf.c b/source4/lib/registry/regf.c new file mode 100644 index 0000000..1e0b143 --- /dev/null +++ b/source4/lib/registry/regf.c @@ -0,0 +1,2319 @@ +/* + Samba CIFS implementation + Registry backend for REGF files + Copyright (C) 2005-2007 Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2006-2010 Wilco Baan Hofman, wilco@baanhofman.nl + + 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 <http://www.gnu.org/licenses/>. */ + +#include "includes.h" +#include "system/filesys.h" +#include "system/time.h" +#include "lib/registry/tdr_regf.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/winreg.h" +#include "lib/registry/registry.h" +#include "libcli/security/security.h" + +#undef strcasecmp + +static struct hive_operations reg_backend_regf; + +/** + * There are several places on the web where the REGF format is explained; + * + * TODO: Links + */ + +/* TODO: + * - Return error codes that make more sense + * - Locking + * - do more things in-memory + */ + +/* + * Read HBIN blocks into memory + */ + +struct regf_data { + int fd; + struct hbin_block **hbins; + struct regf_hdr *header; + time_t last_write; +}; + +static WERROR regf_save_hbin(struct regf_data *data, bool flush); + +struct regf_key_data { + struct hive_key key; + struct regf_data *hive; + uint32_t offset; + struct nk_block *nk; +}; + +static struct hbin_block *hbin_by_offset(const struct regf_data *data, + uint32_t offset, uint32_t *rel_offset) +{ + unsigned int i; + + for (i = 0; data->hbins[i]; i++) { + if (offset >= data->hbins[i]->offset_from_first && + offset < data->hbins[i]->offset_from_first+ + data->hbins[i]->offset_to_next) { + if (rel_offset != NULL) + *rel_offset = offset - data->hbins[i]->offset_from_first - 0x20; + return data->hbins[i]; + } + } + + return NULL; +} + +/** + * Validate a regf header + * For now, do nothing, but we should check the checksum + */ +static uint32_t regf_hdr_checksum(const uint8_t *buffer) +{ + uint32_t checksum = 0, x; + unsigned int i; + + for (i = 0; i < 0x01FB; i+= 4) { + x = IVAL(buffer, i); + checksum ^= x; + } + + return checksum; +} + +/** + * Obtain the contents of a HBIN block + */ +static DATA_BLOB hbin_get(const struct regf_data *data, uint32_t offset) +{ + DATA_BLOB ret; + struct hbin_block *hbin; + uint32_t rel_offset; + + ret.data = NULL; + ret.length = 0; + + hbin = hbin_by_offset(data, offset, &rel_offset); + + if (hbin == NULL) { + DEBUG(1, ("Can't find HBIN at 0x%04x\n", offset)); + return ret; + } + + ret.length = IVAL(hbin->data, rel_offset); + if (!(ret.length & 0x80000000)) { + DEBUG(0, ("Trying to use dirty block at 0x%04x\n", offset)); + return ret; + } + + /* remove high bit */ + ret.length = (ret.length ^ 0xffffffff) + 1; + + ret.length -= 4; /* 4 bytes for the length... */ + ret.data = hbin->data + + (offset - hbin->offset_from_first - 0x20) + 4; + + return ret; +} + +static bool hbin_get_tdr(struct regf_data *regf, uint32_t offset, + TALLOC_CTX *ctx, tdr_pull_fn_t pull_fn, void *p) +{ + struct tdr_pull *pull = tdr_pull_init(regf); + + pull->data = hbin_get(regf, offset); + if (!pull->data.data) { + DEBUG(1, ("Unable to get data at 0x%04x\n", offset)); + talloc_free(pull); + return false; + } + + if (NT_STATUS_IS_ERR(pull_fn(pull, ctx, p))) { + DEBUG(1, ("Error parsing record at 0x%04x using tdr\n", + offset)); + talloc_free(pull); + return false; + } + talloc_free(pull); + + return true; +} + +/* Allocate some new data */ +static DATA_BLOB hbin_alloc(struct regf_data *data, uint32_t size, + uint32_t *offset) +{ + DATA_BLOB ret; + uint32_t rel_offset = (uint32_t) -1; /* Relative offset ! */ + struct hbin_block *hbin = NULL; + unsigned int i; + + if (offset != NULL) { + *offset = 0; + } + + if (size == 0) + return data_blob(NULL, 0); + + size += 4; /* Need to include int32 for the length */ + + /* Allocate as a multiple of 8 */ + size = (size + 7) & ~7; + + ret.data = NULL; + ret.length = 0; + + for (i = 0; (hbin = data->hbins[i]); i++) { + int j; + int32_t my_size; + for (j = 0; j < hbin->offset_to_next-0x20; j+= my_size) { + my_size = IVALS(hbin->data, j); + + if (my_size == 0x0) { + DEBUG(0, ("Invalid zero-length block! File is corrupt.\n")); + return ret; + } + + if (my_size % 8 != 0) { + DEBUG(0, ("Encountered non-aligned block!\n")); + } + + if (my_size < 0) { /* Used... */ + my_size = -my_size; + } else if (my_size == size) { /* exact match */ + rel_offset = j; + DEBUG(4, ("Found free block of exact size %d in middle of HBIN\n", + size)); + break; + } else if (my_size > size) { /* data will remain */ + rel_offset = j; + /* Split this block and mark the next block as free */ + SIVAL(hbin->data, rel_offset+size, my_size-size); + DEBUG(4, ("Found free block of size %d (needing %d) in middle of HBIN\n", + my_size, size)); + break; + } + } + + if (rel_offset != -1) + break; + } + + /* No space available in previous hbins, + * allocate new one */ + if (data->hbins[i] == NULL) { + DEBUG(4, ("No space available in other HBINs for block of size %d, allocating new HBIN\n", + size)); + + /* Add extra hbin block */ + data->hbins = talloc_realloc(data, data->hbins, + struct hbin_block *, i+2); + hbin = talloc(data->hbins, struct hbin_block); + SMB_ASSERT(hbin != NULL); + + data->hbins[i] = hbin; + data->hbins[i+1] = NULL; + + /* Set hbin data */ + hbin->HBIN_ID = talloc_strdup(hbin, "hbin"); + hbin->offset_from_first = (i == 0?0:data->hbins[i-1]->offset_from_first+data->hbins[i-1]->offset_to_next); + hbin->offset_to_next = 0x1000; + hbin->unknown[0] = 0; + hbin->unknown[1] = 0; + unix_to_nt_time(&hbin->last_change, time(NULL)); + hbin->block_size = hbin->offset_to_next; + hbin->data = talloc_zero_array(hbin, uint8_t, hbin->block_size - 0x20); + /* Update the regf header */ + data->header->last_block += hbin->offset_to_next; + + /* Set the next block to it's proper size and set the + * rel_offset for this block */ + SIVAL(hbin->data, size, hbin->block_size - size - 0x20); + rel_offset = 0x0; + } + + /* Set size and mark as used */ + SIVAL(hbin->data, rel_offset, -size); + + ret.data = hbin->data + rel_offset + 0x4; /* Skip past length */ + ret.length = size - 0x4; + if (offset) { + uint32_t new_rel_offset = 0; + *offset = hbin->offset_from_first + rel_offset + 0x20; + SMB_ASSERT(hbin_by_offset(data, *offset, &new_rel_offset) == hbin); + SMB_ASSERT(new_rel_offset == rel_offset); + } + + return ret; +} + +/* Store a data blob. Return the offset at which it was stored */ +static uint32_t hbin_store (struct regf_data *data, DATA_BLOB blob) +{ + uint32_t ret; + DATA_BLOB dest = hbin_alloc(data, blob.length, &ret); + + memcpy(dest.data, blob.data, blob.length); + + /* Make sure that we have no tailing garbage in the block */ + if (dest.length > blob.length) { + memset(dest.data + blob.length, 0, dest.length - blob.length); + } + + return ret; +} + +static uint32_t hbin_store_tdr(struct regf_data *data, + tdr_push_fn_t push_fn, void *p) +{ + struct tdr_push *push = tdr_push_init(data); + uint32_t ret; + + if (NT_STATUS_IS_ERR(push_fn(push, p))) { + DEBUG(0, ("Error during push\n")); + return -1; + } + + ret = hbin_store(data, push->data); + + talloc_free(push); + + return ret; +} + + +/* Free existing data */ +static void hbin_free (struct regf_data *data, uint32_t offset) +{ + int32_t size; + uint32_t rel_offset; + int32_t next_size; + struct hbin_block *hbin; + + SMB_ASSERT (offset > 0); + + hbin = hbin_by_offset(data, offset, &rel_offset); + + if (hbin == NULL) + return; + + /* Get original size */ + size = IVALS(hbin->data, rel_offset); + + if (size > 0) { + DEBUG(1, ("Trying to free already freed block at 0x%04x\n", + offset)); + return; + } + /* Mark as unused */ + size = -size; + + /* If the next block is free, merge into big free block */ + if (rel_offset + size < hbin->offset_to_next - 0x20) { + next_size = IVALS(hbin->data, rel_offset+size); + if (next_size > 0) { + size += next_size; + } + } + + /* Write block size */ + SIVALS(hbin->data, rel_offset, size); +} + +/** + * Store a data blob data was already stored, but has changed in size + * Will try to save it at the current location if possible, otherwise + * does a free + store */ +static uint32_t hbin_store_resize(struct regf_data *data, + uint32_t orig_offset, DATA_BLOB blob) +{ + uint32_t rel_offset; + struct hbin_block *hbin = hbin_by_offset(data, orig_offset, + &rel_offset); + int32_t my_size; + int32_t orig_size; + int32_t needed_size; + int32_t possible_size; + unsigned int i; + + SMB_ASSERT(orig_offset > 0); + + if (!hbin) + return hbin_store(data, blob); + + /* Get original size */ + orig_size = -IVALS(hbin->data, rel_offset); + + needed_size = blob.length + 4; /* Add int32 containing length */ + needed_size = (needed_size + 7) & ~7; /* Align */ + + /* Fits into current allocated block */ + if (orig_size >= needed_size) { + memcpy(hbin->data + rel_offset + 0x4, blob.data, blob.length); + /* If the difference in size is greater than 0x4, split the block + * and free/merge it */ + if (orig_size - needed_size > 0x4) { + SIVALS(hbin->data, rel_offset, -needed_size); + SIVALS(hbin->data, rel_offset + needed_size, + needed_size-orig_size); + hbin_free(data, orig_offset + needed_size); + } + return orig_offset; + } + + possible_size = orig_size; + + /* Check if it can be combined with the next few free records */ + for (i = rel_offset; i < hbin->offset_to_next - 0x20; i += my_size) { + if (IVALS(hbin->data, i) < 0) /* Used */ + break; + + my_size = IVALS(hbin->data, i); + + if (my_size == 0x0) { + DEBUG(0, ("Invalid zero-length block! File is corrupt.\n")); + break; + } else { + possible_size += my_size; + } + + if (possible_size >= blob.length) { + SIVAL(hbin->data, rel_offset, -possible_size); + memcpy(hbin->data + rel_offset + 0x4, + blob.data, blob.length); + return orig_offset; + } + } + + hbin_free(data, orig_offset); + return hbin_store(data, blob); +} + +static uint32_t hbin_store_tdr_resize(struct regf_data *regf, + tdr_push_fn_t push_fn, + uint32_t orig_offset, void *p) +{ + struct tdr_push *push = tdr_push_init(regf); + uint32_t ret; + + if (NT_STATUS_IS_ERR(push_fn(push, p))) { + DEBUG(0, ("Error during push\n")); + return -1; + } + + ret = hbin_store_resize(regf, orig_offset, push->data); + + talloc_free(push); + + return ret; +} + +static uint32_t regf_create_lh_hash(const char *name) +{ + char *hash_name; + uint32_t ret = 0; + uint16_t i; + + hash_name = strupper_talloc(NULL, name); + for (i = 0; *(hash_name + i) != 0; i++) { + ret *= 37; + ret += *(hash_name + i); + } + talloc_free(hash_name); + return ret; +} + +static WERROR regf_get_info(TALLOC_CTX *mem_ctx, + const struct hive_key *key, + const char **classname, + uint32_t *num_subkeys, + uint32_t *num_values, + NTTIME *last_mod_time, + uint32_t *max_subkeynamelen, + uint32_t *max_valnamelen, + uint32_t *max_valbufsize) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + + if (num_subkeys != NULL) + *num_subkeys = private_data->nk->num_subkeys; + + if (num_values != NULL) + *num_values = private_data->nk->num_values; + + if (classname != NULL) { + if (private_data->nk->clsname_offset != -1) { + DATA_BLOB data = hbin_get(private_data->hive, + private_data->nk->clsname_offset); + *classname = talloc_strndup(mem_ctx, + (char*)data.data, + private_data->nk->clsname_length); + W_ERROR_HAVE_NO_MEMORY(*classname); + } else + *classname = NULL; + } + + /* TODO: Last mod time */ + + /* TODO: max valnamelen */ + + /* TODO: max valbufsize */ + + /* TODO: max subkeynamelen */ + + return WERR_OK; +} + +static struct regf_key_data *regf_get_key(TALLOC_CTX *ctx, + struct regf_data *regf, + uint32_t offset) +{ + struct nk_block *nk; + struct regf_key_data *ret; + + ret = talloc_zero(ctx, struct regf_key_data); + ret->key.ops = ®_backend_regf; + ret->hive = talloc_reference(ret, regf); + ret->offset = offset; + nk = talloc(ret, struct nk_block); + if (nk == NULL) + return NULL; + + ret->nk = nk; + + if (!hbin_get_tdr(regf, offset, nk, + (tdr_pull_fn_t)tdr_pull_nk_block, nk)) { + DEBUG(0, ("Unable to find HBIN data for offset 0x%x\n", offset)); + return NULL; + } + + if (strcmp(nk->header, "nk") != 0) { + DEBUG(0, ("Expected nk record, got %s\n", nk->header)); + talloc_free(ret); + return NULL; + } + + return ret; +} + + +static WERROR regf_get_value(TALLOC_CTX *ctx, struct hive_key *key, + uint32_t idx, const char **name, + uint32_t *data_type, DATA_BLOB *data) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct vk_block *vk; + struct regf_data *regf = private_data->hive; + uint32_t vk_offset; + DATA_BLOB tmp; + + if (idx >= private_data->nk->num_values) + return WERR_NO_MORE_ITEMS; + + tmp = hbin_get(regf, private_data->nk->values_offset); + if (!tmp.data) { + DEBUG(0, ("Unable to find value list at 0x%x\n", + private_data->nk->values_offset)); + return WERR_GEN_FAILURE; + } + + if (tmp.length < private_data->nk->num_values * 4) { + DEBUG(1, ("Value counts mismatch\n")); + } + + vk_offset = IVAL(tmp.data, idx * 4); + + vk = talloc(NULL, struct vk_block); + W_ERROR_HAVE_NO_MEMORY(vk); + + if (!hbin_get_tdr(regf, vk_offset, vk, + (tdr_pull_fn_t)tdr_pull_vk_block, vk)) { + DEBUG(0, ("Unable to get VK block at 0x%x\n", vk_offset)); + talloc_free(vk); + return WERR_GEN_FAILURE; + } + + /* FIXME: name character set ?*/ + if (name != NULL) { + *name = talloc_strndup(ctx, vk->data_name, vk->name_length); + W_ERROR_HAVE_NO_MEMORY(*name); + } + + if (data_type != NULL) + *data_type = vk->data_type; + + if (vk->data_length & 0x80000000) { + /* this is data of type "REG_DWORD" or "REG_DWORD_BIG_ENDIAN" */ + data->data = talloc_size(ctx, sizeof(uint32_t)); + W_ERROR_HAVE_NO_MEMORY(data->data); + SIVAL(data->data, 0, vk->data_offset); + data->length = sizeof(uint32_t); + } else { + *data = hbin_get(regf, vk->data_offset); + } + + if (data->length < vk->data_length) { + DEBUG(1, ("Read data less than indicated data length!\n")); + } + + talloc_free(vk); + + return WERR_OK; +} + +static WERROR regf_get_value_by_name(TALLOC_CTX *mem_ctx, + struct hive_key *key, const char *name, + uint32_t *type, DATA_BLOB *data) +{ + unsigned int i; + const char *vname; + WERROR error; + + /* FIXME: Do binary search? Is this list sorted at all? */ + + for (i = 0; W_ERROR_IS_OK(error = regf_get_value(mem_ctx, key, i, + &vname, type, data)); + i++) { + if (!strcmp(vname, name)) + return WERR_OK; + } + + if (W_ERROR_EQUAL(error, WERR_NO_MORE_ITEMS)) + return WERR_FILE_NOT_FOUND; + + return error; +} + + +static WERROR regf_get_subkey_by_index(TALLOC_CTX *ctx, + const struct hive_key *key, + uint32_t idx, const char **name, + const char **classname, + NTTIME *last_mod_time) +{ + DATA_BLOB data; + struct regf_key_data *ret; + const struct regf_key_data *private_data = (const struct regf_key_data *)key; + struct nk_block *nk = private_data->nk; + uint32_t key_off=0; + + if (idx >= nk->num_subkeys) + return WERR_NO_MORE_ITEMS; + + /* Make sure that we don't crash if the key is empty */ + if (nk->subkeys_offset == -1) { + return WERR_NO_MORE_ITEMS; + } + + data = hbin_get(private_data->hive, nk->subkeys_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list at 0x%x\n", + nk->subkeys_offset)); + return WERR_GEN_FAILURE; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, nk, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + if (li.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + key_off = li.nk_offset[idx]; + + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LF list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, nk, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + if (lf.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + key_off = lf.hr[idx].nk_offset; + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(private_data->hive); + + DEBUG(10, ("Subkeys in LH list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, nk, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + if (lh.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + key_off = lh.hr[idx].nk_offset; + } else if (!strncmp((char *)data.data, "ri", 2)) { + struct ri_block ri; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + uint16_t sublist_count = 0; + + DEBUG(10, ("Subkeys in RI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_ri_block(pull, nk, &ri))) { + DEBUG(0, ("Error parsing RI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(ri.header, "ri", 2)); + + for (i = 0; i < ri.key_count; i++) { + DATA_BLOB list_data; + + /* Get sublist data blob */ + list_data = hbin_get(private_data->hive, ri.offset[i]); + if (!list_data.data) { + DEBUG(0, ("Error getting RI list.")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + + pull->data = list_data; + + if (!strncmp((char *)list_data.data, "li", 2)) { + struct li_block li; + + DEBUG(10, ("Subkeys in RI->LI list\n")); + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, + nk, + &li))) { + DEBUG(0, ("Error parsing LI list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + /* Advance to next sublist if necessary */ + if (idx >= sublist_count + li.key_count) { + sublist_count += li.key_count; + continue; + } + key_off = li.nk_offset[idx - sublist_count]; + sublist_count += li.key_count; + break; + } else if (!strncmp((char *)list_data.data, "lh", 2)) { + struct lh_block lh; + + DEBUG(10, ("Subkeys in RI->LH list\n")); + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, + nk, + &lh))) { + DEBUG(0, ("Error parsing LH list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + /* Advance to next sublist if necessary */ + if (idx >= sublist_count + lh.key_count) { + sublist_count += lh.key_count; + continue; + } + key_off = lh.hr[idx - sublist_count].nk_offset; + sublist_count += lh.key_count; + break; + } else { + DEBUG(0,("Unknown sublist in ri block\n")); + talloc_free(pull); + + return WERR_GEN_FAILURE; + } + + } + talloc_free(pull); + + + if (idx > sublist_count) { + return WERR_NO_MORE_ITEMS; + } + + } else { + DEBUG(0, ("Unknown type for subkey list (0x%04x): %c%c\n", + nk->subkeys_offset, data.data[0], data.data[1])); + return WERR_GEN_FAILURE; + } + + ret = regf_get_key (ctx, private_data->hive, key_off); + + if (classname != NULL) { + if (ret->nk->clsname_offset != -1) { + DATA_BLOB db = hbin_get(ret->hive, + ret->nk->clsname_offset); + *classname = talloc_strndup(ctx, + (char*)db.data, + ret->nk->clsname_length); + W_ERROR_HAVE_NO_MEMORY(*classname); + } else + *classname = NULL; + } + + if (last_mod_time != NULL) + *last_mod_time = ret->nk->last_change; + + if (name != NULL) + *name = talloc_steal(ctx, ret->nk->key_name); + + talloc_free(ret); + + return WERR_OK; +} + +static WERROR regf_match_subkey_by_name(TALLOC_CTX *ctx, + const struct hive_key *key, + uint32_t offset, + const char *name, uint32_t *ret) +{ + DATA_BLOB subkey_data; + struct nk_block subkey; + struct tdr_pull *pull; + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + + subkey_data = hbin_get(private_data->hive, offset); + if (!subkey_data.data) { + DEBUG(0, ("Unable to retrieve subkey HBIN\n")); + return WERR_GEN_FAILURE; + } + + pull = tdr_pull_init(ctx); + + pull->data = subkey_data; + + if (NT_STATUS_IS_ERR(tdr_pull_nk_block(pull, ctx, &subkey))) { + DEBUG(0, ("Error parsing NK structure.\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + + if (strncmp(subkey.header, "nk", 2)) { + DEBUG(0, ("Not an NK structure.\n")); + return WERR_GEN_FAILURE; + } + + if (!strcasecmp(subkey.key_name, name)) { + *ret = offset; + } else { + *ret = 0; + } + return WERR_OK; +} + +static WERROR regf_get_subkey_by_name(TALLOC_CTX *ctx, + const struct hive_key *key, + const char *name, + struct hive_key **ret) +{ + DATA_BLOB data; + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct nk_block *nk = private_data->nk; + uint32_t key_off = 0; + + /* Make sure that we don't crash if the key is empty */ + if (nk->subkeys_offset == -1) { + return WERR_FILE_NOT_FOUND; + } + + data = hbin_get(private_data->hive, nk->subkeys_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_GEN_FAILURE; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + + DEBUG(10, ("Subkeys in LI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, nk, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + if (li.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; i < li.key_count; i++) { + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + li.nk_offset[i], + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + + DEBUG(10, ("Subkeys in LF list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, nk, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + if (lf.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; i < lf.key_count; i++) { + if (strncmp(lf.hr[i].hash, name, 4)) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, + key, + lf.hr[i].nk_offset, + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i; + uint32_t hash; + + DEBUG(10, ("Subkeys in LH list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, nk, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + if (lh.key_count != nk->num_subkeys) { + DEBUG(0, ("Subkey counts don't match\n")); + return WERR_GEN_FAILURE; + } + + hash = regf_create_lh_hash(name); + for (i = 0; i < lh.key_count; i++) { + if (lh.hr[i].base37 != hash) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, + key, + lh.hr[i].nk_offset, + name, + &key_off)); + if (key_off != 0) + break; + } + if (key_off == 0) + return WERR_FILE_NOT_FOUND; + } else if (!strncmp((char *)data.data, "ri", 2)) { + struct ri_block ri; + struct tdr_pull *pull = tdr_pull_init(ctx); + uint16_t i, j; + + DEBUG(10, ("Subkeys in RI list\n")); + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_ri_block(pull, nk, &ri))) { + DEBUG(0, ("Error parsing RI list\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(ri.header, "ri", 2)); + + for (i = 0; i < ri.key_count; i++) { + DATA_BLOB list_data; + + /* Get sublist data blob */ + list_data = hbin_get(private_data->hive, ri.offset[i]); + if (list_data.data == NULL) { + DEBUG(0, ("Error getting RI list.")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + + pull->data = list_data; + + if (!strncmp((char *)list_data.data, "li", 2)) { + struct li_block li; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, + nk, + &li))) { + DEBUG(0, ("Error parsing LI list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + for (j = 0; j < li.key_count; j++) { + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + li.nk_offset[j], + name, + &key_off)); + if (key_off) + break; + } + } else if (!strncmp((char *)list_data.data, "lh", 2)) { + struct lh_block lh; + uint32_t hash; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, + nk, + &lh))) { + DEBUG(0, ("Error parsing LH list from RI\n")); + talloc_free(pull); + return WERR_GEN_FAILURE; + } + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + hash = regf_create_lh_hash(name); + for (j = 0; j < lh.key_count; j++) { + if (lh.hr[j].base37 != hash) { + continue; + } + W_ERROR_NOT_OK_RETURN(regf_match_subkey_by_name(nk, key, + lh.hr[j].nk_offset, + name, + &key_off)); + if (key_off) + break; + } + } + if (key_off) + break; + } + talloc_free(pull); + if (!key_off) + return WERR_FILE_NOT_FOUND; + } else { + DEBUG(0, ("Unknown subkey list type.\n")); + return WERR_GEN_FAILURE; + } + + *ret = (struct hive_key *)regf_get_key(ctx, private_data->hive, + key_off); + return WERR_OK; +} + +static WERROR regf_set_sec_desc(struct hive_key *key, + const struct security_descriptor *sec_desc) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct sk_block cur_sk, sk, new_sk; + struct regf_data *regf = private_data->hive; + struct nk_block root; + DATA_BLOB data; + uint32_t sk_offset, cur_sk_offset; + bool update_cur_sk = false; + + /* Get the root nk */ + hbin_get_tdr(regf, regf->header->data_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &root); + + /* Push the security descriptor to a blob */ + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(&data, regf, + sec_desc, (ndr_push_flags_fn_t)ndr_push_security_descriptor))) { + DEBUG(0, ("Unable to push security descriptor\n")); + return WERR_GEN_FAILURE; + } + + /* Get the current security descriptor for the key */ + if (!hbin_get_tdr(regf, private_data->nk->sk_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &cur_sk)) { + DEBUG(0, ("Unable to find security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* If there's no change, change nothing. */ + if (memcmp(data.data, cur_sk.sec_desc, + MIN(data.length, cur_sk.rec_size)) == 0) { + return WERR_OK; + } + + /* Delete the current sk if only this key is using it */ + if (cur_sk.ref_cnt == 1) { + /* Get the previous security descriptor for the key */ + if (!hbin_get_tdr(regf, cur_sk.prev_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find prev security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the previous security descriptor */ + sk.next_offset = cur_sk.next_offset; + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.prev_offset, &sk); + + /* Get the next security descriptor for the key */ + if (!hbin_get_tdr(regf, cur_sk.next_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find next security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the next security descriptor */ + sk.prev_offset = cur_sk.prev_offset; + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.next_offset, &sk); + + hbin_free(regf, private_data->nk->sk_offset); + } else { + /* This key will no longer be referring to this sk */ + cur_sk.ref_cnt--; + update_cur_sk = true; + } + + sk_offset = root.sk_offset; + + do { + cur_sk_offset = sk_offset; + if (!hbin_get_tdr(regf, sk_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor\n")); + return WERR_FILE_NOT_FOUND; + } + if (memcmp(data.data, sk.sec_desc, MIN(data.length, sk.rec_size)) == 0) { + private_data->nk->sk_offset = sk_offset; + sk.ref_cnt++; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + sk_offset, &sk); + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, + private_data->nk); + return WERR_OK; + } + sk_offset = sk.next_offset; + } while (sk_offset != root.sk_offset); + + ZERO_STRUCT(new_sk); + new_sk.header = "sk"; + new_sk.prev_offset = cur_sk_offset; + new_sk.next_offset = root.sk_offset; + new_sk.ref_cnt = 1; + new_sk.rec_size = data.length; + new_sk.sec_desc = data.data; + + sk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_sk_block, + &new_sk); + if (sk_offset == -1) { + DEBUG(0, ("Error storing sk block\n")); + return WERR_GEN_FAILURE; + } + private_data->nk->sk_offset = sk_offset; + + if (update_cur_sk) { + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + private_data->nk->sk_offset, &cur_sk); + } + + /* Get the previous security descriptor for the key */ + if (!hbin_get_tdr(regf, new_sk.prev_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor for previous key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the previous security descriptor */ + sk.next_offset = sk_offset; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + cur_sk.prev_offset, &sk); + + /* Get the next security descriptor for the key (always root, as we append) */ + if (!hbin_get_tdr(regf, new_sk.next_offset, regf, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor for current key\n")); + return WERR_FILE_NOT_FOUND; + } + /* Change and store the next security descriptor (always root, as we append) */ + sk.prev_offset = sk_offset; + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + root.sk_offset, &sk); + + + /* Store the nk. */ + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_sk_block, + private_data->offset, private_data->nk); + return WERR_OK; +} + +static WERROR regf_get_sec_desc(TALLOC_CTX *ctx, const struct hive_key *key, + struct security_descriptor **sd) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)key; + struct sk_block sk; + struct regf_data *regf = private_data->hive; + DATA_BLOB data; + + if (!hbin_get_tdr(regf, private_data->nk->sk_offset, ctx, + (tdr_pull_fn_t) tdr_pull_sk_block, &sk)) { + DEBUG(0, ("Unable to find security descriptor\n")); + return WERR_GEN_FAILURE; + } + + if (strcmp(sk.header, "sk") != 0) { + DEBUG(0, ("Expected 'sk', got '%s'\n", sk.header)); + return WERR_GEN_FAILURE; + } + + *sd = talloc(ctx, struct security_descriptor); + W_ERROR_HAVE_NO_MEMORY(*sd); + + data.data = sk.sec_desc; + data.length = sk.rec_size; + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_pull_struct_blob(&data, ctx, *sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor))) { + DEBUG(0, ("Error parsing security descriptor\n")); + return WERR_GEN_FAILURE; + } + + return WERR_OK; +} + +static WERROR regf_sl_add_entry(struct regf_data *regf, uint32_t list_offset, + const char *name, + uint32_t key_offset, uint32_t *ret) +{ + DATA_BLOB data; + + /* Create a new key if necessary */ + if (list_offset == -1) { + if (regf->header->version.major != 1) { + DEBUG(0, ("Can't store keys in unknown registry format\n")); + return WERR_NOT_SUPPORTED; + } + if (regf->header->version.minor < 3) { + /* Store LI */ + struct li_block li; + ZERO_STRUCT(li); + li.header = "li"; + li.key_count = 1; + + li.nk_offset = talloc_array(regf, uint32_t, 1); + W_ERROR_HAVE_NO_MEMORY(li.nk_offset); + li.nk_offset[0] = key_offset; + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_li_block, + &li); + + talloc_free(li.nk_offset); + } else if (regf->header->version.minor == 3 || + regf->header->version.minor == 4) { + /* Store LF */ + struct lf_block lf; + ZERO_STRUCT(lf); + lf.header = "lf"; + lf.key_count = 1; + + lf.hr = talloc_array(regf, struct hash_record, 1); + W_ERROR_HAVE_NO_MEMORY(lf.hr); + lf.hr[0].nk_offset = key_offset; + lf.hr[0].hash = talloc_strndup(lf.hr, name, 4); + W_ERROR_HAVE_NO_MEMORY(lf.hr[0].hash); + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_lf_block, + &lf); + + talloc_free(lf.hr); + } else if (regf->header->version.minor == 5) { + /* Store LH */ + struct lh_block lh; + ZERO_STRUCT(lh); + lh.header = "lh"; + lh.key_count = 1; + + lh.hr = talloc_array(regf, struct lh_hash, 1); + W_ERROR_HAVE_NO_MEMORY(lh.hr); + lh.hr[0].nk_offset = key_offset; + lh.hr[0].base37 = regf_create_lh_hash(name); + + *ret = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_lh_block, + &lh); + + talloc_free(lh.hr); + } + return WERR_OK; + } + + data = hbin_get(regf, list_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + if (!strncmp((char *)data.data, "li", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct li_block li; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, regf, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + if (strncmp(li.header, "li", 2) != 0) { + abort(); + DEBUG(0, ("LI header corrupt\n")); + return WERR_FILE_NOT_FOUND; + } + + /* + * Find the position to store the pointer + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < li.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, li.nk_offset[i], regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + li.nk_offset = talloc_realloc(regf, li.nk_offset, + uint32_t, li.key_count+1); + W_ERROR_HAVE_NO_MEMORY(li.nk_offset); + + /* Move everything behind this offset */ + for (j = li.key_count - 1; j >= i; j--) { + li.nk_offset[j+1] = li.nk_offset[j]; + } + + li.nk_offset[i] = key_offset; + li.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_li_block, + list_offset, &li); + + talloc_free(li.nk_offset); + } else if (!strncmp((char *)data.data, "lf", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct lf_block lf; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, regf, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + /* + * Find the position to store the hash record + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < lf.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, lf.hr[i].nk_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + lf.hr = talloc_realloc(regf, lf.hr, struct hash_record, + lf.key_count+1); + W_ERROR_HAVE_NO_MEMORY(lf.hr); + + /* Move everything behind this hash record */ + for (j = lf.key_count - 1; j >= i; j--) { + lf.hr[j+1] = lf.hr[j]; + } + + lf.hr[i].nk_offset = key_offset; + lf.hr[i].hash = talloc_strndup(lf.hr, name, 4); + W_ERROR_HAVE_NO_MEMORY(lf.hr[lf.key_count].hash); + lf.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_lf_block, + list_offset, &lf); + + talloc_free(lf.hr); + } else if (!strncmp((char *)data.data, "lh", 2)) { + struct tdr_pull *pull = tdr_pull_init(regf); + struct lh_block lh; + struct nk_block sub_nk; + int32_t i, j; + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, regf, &lh))) { + DEBUG(0, ("Error parsing LH list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + /* + * Find the position to store the hash record + * Extensive testing reveils that at least on windows 7 subkeys + * *MUST* be stored in alphabetical order + */ + for (i = 0; i < lh.key_count; i++) { + /* Get the nk */ + hbin_get_tdr(regf, lh.hr[i].nk_offset, regf, + (tdr_pull_fn_t) tdr_pull_nk_block, &sub_nk); + if (strcasecmp(name, sub_nk.key_name) < 0) { + break; + } + } + + lh.hr = talloc_realloc(regf, lh.hr, struct lh_hash, + lh.key_count+1); + W_ERROR_HAVE_NO_MEMORY(lh.hr); + + /* Move everything behind this hash record */ + for (j = lh.key_count - 1; j >= i; j--) { + lh.hr[j+1] = lh.hr[j]; + } + + lh.hr[i].nk_offset = key_offset; + lh.hr[i].base37 = regf_create_lh_hash(name); + lh.key_count++; + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t)tdr_push_lh_block, + list_offset, &lh); + + talloc_free(lh.hr); + } else if (!strncmp((char *)data.data, "ri", 2)) { + /* FIXME */ + DEBUG(0, ("Adding to 'ri' subkey list is not supported yet.\n")); + return WERR_NOT_SUPPORTED; + } else { + DEBUG(0, ("Cannot add to unknown subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + return WERR_OK; +} + +static WERROR regf_sl_del_entry(struct regf_data *regf, uint32_t list_offset, + uint32_t key_offset, uint32_t *ret) +{ + DATA_BLOB data; + + data = hbin_get(regf, list_offset); + if (!data.data) { + DEBUG(0, ("Unable to find subkey list\n")); + return WERR_FILE_NOT_FOUND; + } + + if (strncmp((char *)data.data, "li", 2) == 0) { + struct li_block li; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LI list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_li_block(pull, regf, &li))) { + DEBUG(0, ("Error parsing LI list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(li.header, "li", 2)); + + for (i = 0; i < li.key_count; i++) { + if (found_offset) { + li.nk_offset[i-1] = li.nk_offset[i]; + } + if (li.nk_offset[i] == key_offset) { + found_offset = true; + continue; + } + } + if (!found_offset) { + DEBUG(2, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + li.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (li.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + } + + /* Store li block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_li_block, + list_offset, &li); + } else if (strncmp((char *)data.data, "lf", 2) == 0) { + struct lf_block lf; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LF list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull, regf, &lf))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(lf.header, "lf", 2)); + + for (i = 0; i < lf.key_count; i++) { + if (found_offset) { + lf.hr[i-1] = lf.hr[i]; + continue; + } + if (lf.hr[i].nk_offset == key_offset) { + found_offset = 1; + continue; + } + } + if (!found_offset) { + DEBUG(2, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + lf.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (lf.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + return WERR_OK; + } + + /* Store lf block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_lf_block, + list_offset, &lf); + } else if (strncmp((char *)data.data, "lh", 2) == 0) { + struct lh_block lh; + struct tdr_pull *pull = tdr_pull_init(regf); + uint16_t i; + bool found_offset = false; + + DEBUG(10, ("Subkeys in LH list\n")); + + pull->data = data; + + if (NT_STATUS_IS_ERR(tdr_pull_lh_block(pull, regf, &lh))) { + DEBUG(0, ("Error parsing LF list\n")); + talloc_free(pull); + return WERR_FILE_NOT_FOUND; + } + talloc_free(pull); + + SMB_ASSERT(!strncmp(lh.header, "lh", 2)); + + for (i = 0; i < lh.key_count; i++) { + if (found_offset) { + lh.hr[i-1] = lh.hr[i]; + continue; + } + if (lh.hr[i].nk_offset == key_offset) { + found_offset = 1; + continue; + } + } + if (!found_offset) { + DEBUG(0, ("Subkey not found\n")); + return WERR_FILE_NOT_FOUND; + } + lh.key_count--; + + /* If the there are no entries left, free the subkey list */ + if (lh.key_count == 0) { + hbin_free(regf, list_offset); + *ret = -1; + return WERR_OK; + } + + /* Store lh block */ + *ret = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_lh_block, + list_offset, &lh); + } else if (strncmp((char *)data.data, "ri", 2) == 0) { + /* FIXME */ + DEBUG(0, ("Sorry, deletion from ri block is not supported yet.\n")); + return WERR_NOT_SUPPORTED; + } else { + DEBUG (0, ("Unknown header found in subkey list.\n")); + return WERR_FILE_NOT_FOUND; + } + return WERR_OK; +} + +static WERROR regf_del_value(TALLOC_CTX *mem_ctx, struct hive_key *key, + const char *name) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + struct nk_block *nk = private_data->nk; + struct vk_block vk; + uint32_t vk_offset; + bool found_offset = false; + DATA_BLOB values; + unsigned int i; + + if (nk->values_offset == -1) { + return WERR_FILE_NOT_FOUND; + } + + values = hbin_get(regf, nk->values_offset); + + for (i = 0; i < nk->num_values; i++) { + if (found_offset) { + ((uint32_t *)values.data)[i-1] = ((uint32_t *) values.data)[i]; + } else { + vk_offset = IVAL(values.data, i * 4); + if (!hbin_get_tdr(regf, vk_offset, private_data, + (tdr_pull_fn_t)tdr_pull_vk_block, + &vk)) { + DEBUG(0, ("Unable to get VK block at %d\n", + vk_offset)); + return WERR_FILE_NOT_FOUND; + } + if (strcmp(vk.data_name, name) == 0) { + hbin_free(regf, vk_offset); + found_offset = true; + } + } + } + if (!found_offset) { + return WERR_FILE_NOT_FOUND; + } else { + nk->num_values--; + values.length = (nk->num_values)*4; + } + + /* Store values list and nk */ + if (nk->num_values == 0) { + hbin_free(regf, nk->values_offset); + nk->values_offset = -1; + } else { + nk->values_offset = hbin_store_resize(regf, + nk->values_offset, + values); + } + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, nk); + + return regf_save_hbin(private_data->hive, 0); +} + + +static WERROR regf_del_key(TALLOC_CTX *mem_ctx, const struct hive_key *parent, + const char *name) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)parent; + struct regf_key_data *key; + struct nk_block *parent_nk; + WERROR error; + + SMB_ASSERT(private_data); + + parent_nk = private_data->nk; + + if (parent_nk->subkeys_offset == -1) { + DEBUG(4, ("Subkey list is empty, this key cannot contain subkeys.\n")); + return WERR_FILE_NOT_FOUND; + } + + /* Find the key */ + if (!W_ERROR_IS_OK(regf_get_subkey_by_name(parent_nk, parent, name, + (struct hive_key **)&key))) { + DEBUG(2, ("Key '%s' not found\n", name)); + return WERR_FILE_NOT_FOUND; + } + + if (key->nk->subkeys_offset != -1) { + struct hive_key *sk = (struct hive_key *)key; + unsigned int i = key->nk->num_subkeys; + while (i--) { + char *sk_name; + const char *p = NULL; + + /* Get subkey information. */ + error = regf_get_subkey_by_index(parent_nk, sk, 0, + &p, + NULL, NULL); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve subkey by index.\n")); + return error; + } + sk_name = discard_const_p(char, p); + + /* Delete subkey. */ + error = regf_del_key(NULL, sk, sk_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete key '%s'.\n", sk_name)); + return error; + } + + talloc_free(sk_name); + } + } + + if (key->nk->values_offset != -1) { + struct hive_key *sk = (struct hive_key *)key; + DATA_BLOB data; + unsigned int i = key->nk->num_values; + while (i--) { + char *val_name; + const char *p = NULL; + + /* Get value information. */ + error = regf_get_value(parent_nk, sk, 0, + &p, + NULL, &data); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve value by index.\n")); + return error; + } + val_name = discard_const_p(char, p); + + /* Delete value. */ + error = regf_del_value(NULL, sk, val_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete value '%s'.\n", val_name)); + return error; + } + + talloc_free(val_name); + } + } + + /* Delete it from the subkey list. */ + error = regf_sl_del_entry(private_data->hive, parent_nk->subkeys_offset, + key->offset, &parent_nk->subkeys_offset); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't store new subkey list for parent key. Won't delete.\n")); + return error; + } + + /* Re-store parent key */ + parent_nk->num_subkeys--; + hbin_store_tdr_resize(private_data->hive, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, parent_nk); + + if (key->nk->clsname_offset != -1) { + hbin_free(private_data->hive, key->nk->clsname_offset); + } + hbin_free(private_data->hive, key->offset); + + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_add_key(TALLOC_CTX *ctx, const struct hive_key *parent, + const char *name, const char *classname, + struct security_descriptor *sec_desc, + struct hive_key **ret) +{ + const struct regf_key_data *private_data = + (const struct regf_key_data *)parent; + struct nk_block *parent_nk = private_data->nk, nk; + struct nk_block *root; + struct regf_data *regf = private_data->hive; + uint32_t offset; + WERROR error; + + nk.header = "nk"; + nk.type = REG_SUB_KEY; + unix_to_nt_time(&nk.last_change, time(NULL)); + nk.uk1 = 0; + nk.parent_offset = private_data->offset; + nk.num_subkeys = 0; + nk.uk2 = 0; + nk.subkeys_offset = -1; + nk.unknown_offset = -1; + nk.num_values = 0; + nk.values_offset = -1; + memset(nk.unk3, 0, sizeof(nk.unk3)); + nk.clsname_offset = -1; /* FIXME: fill in */ + nk.clsname_length = 0; + nk.key_name = name; + + /* Get the security descriptor of the root key */ + root = talloc_zero(ctx, struct nk_block); + W_ERROR_HAVE_NO_MEMORY(root); + + if (!hbin_get_tdr(regf, regf->header->data_offset, root, + (tdr_pull_fn_t)tdr_pull_nk_block, root)) { + DEBUG(0, ("Unable to find HBIN data for offset 0x%x\n", + regf->header->data_offset)); + return WERR_GEN_FAILURE; + } + nk.sk_offset = root->sk_offset; + talloc_free(root); + + /* Store the new nk key */ + offset = hbin_store_tdr(regf, (tdr_push_fn_t) tdr_push_nk_block, &nk); + + error = regf_sl_add_entry(regf, parent_nk->subkeys_offset, name, offset, + &parent_nk->subkeys_offset); + if (!W_ERROR_IS_OK(error)) { + hbin_free(regf, offset); + return error; + } + + parent_nk->num_subkeys++; + + /* Since the subkey offset of the parent can change, store it again */ + hbin_store_tdr_resize(regf, (tdr_push_fn_t) tdr_push_nk_block, + nk.parent_offset, parent_nk); + + *ret = (struct hive_key *)regf_get_key(ctx, regf, offset); + + DEBUG(9, ("Storing key %s\n", name)); + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_set_value(struct hive_key *key, const char *name, + uint32_t type, const DATA_BLOB data) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + struct nk_block *nk = private_data->nk; + struct vk_block vk; + uint32_t i; + uint32_t tmp_vk_offset, vk_offset, old_vk_offset = (uint32_t) -1; + DATA_BLOB values = {0}; + + ZERO_STRUCT(vk); + + /* find the value offset, if it exists */ + if (nk->values_offset != -1) { + values = hbin_get(regf, nk->values_offset); + + for (i = 0; i < nk->num_values; i++) { + tmp_vk_offset = IVAL(values.data, i * 4); + if (!hbin_get_tdr(regf, tmp_vk_offset, private_data, + (tdr_pull_fn_t)tdr_pull_vk_block, + &vk)) { + DEBUG(0, ("Unable to get VK block at 0x%x\n", + tmp_vk_offset)); + return WERR_GEN_FAILURE; + } + if (strcmp(vk.data_name, name) == 0) { + old_vk_offset = tmp_vk_offset; + break; + } + } + } + + /* If it's new, create the vk struct, if it's old, free the old data. */ + if (old_vk_offset == -1) { + vk.header = "vk"; + if (name != NULL && name[0] != '\0') { + vk.flag = 1; + vk.data_name = name; + vk.name_length = strlen(name); + } else { + vk.flag = 0; + vk.data_name = NULL; + vk.name_length = 0; + } + } else { + /* Free data, if any */ + if (!(vk.data_length & 0x80000000)) { + hbin_free(regf, vk.data_offset); + } + } + + /* Set the type and data */ + vk.data_length = data.length; + vk.data_type = type; + if ((type == REG_DWORD) || (type == REG_DWORD_BIG_ENDIAN)) { + if (vk.data_length != sizeof(uint32_t)) { + DEBUG(0, ("DWORD or DWORD_BIG_ENDIAN value with size other than 4 byte!\n")); + return WERR_NOT_SUPPORTED; + } + vk.data_length |= 0x80000000; + vk.data_offset = IVAL(data.data, 0); + } else { + /* Store data somewhere */ + vk.data_offset = hbin_store(regf, data); + } + if (old_vk_offset == -1) { + /* Store new vk */ + vk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_vk_block, + &vk); + } else { + /* Store vk at offset */ + vk_offset = hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_vk_block, + old_vk_offset ,&vk); + } + + /* Re-allocate the value list */ + if (nk->values_offset == -1) { + nk->values_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_uint32, + &vk_offset); + nk->num_values = 1; + } else { + + /* Change if we're changing, otherwise we're adding the value */ + if (old_vk_offset != -1) { + /* Find and overwrite the offset. */ + for (i = 0; i < nk->num_values; i++) { + if (IVAL(values.data, i * 4) == old_vk_offset) { + SIVAL(values.data, i * 4, vk_offset); + break; + } + } + } else { + /* Create a new value list */ + DATA_BLOB value_list; + + value_list.length = (nk->num_values+1)*4; + value_list.data = (uint8_t *)talloc_array(private_data, + uint32_t, + nk->num_values+1); + W_ERROR_HAVE_NO_MEMORY(value_list.data); + memcpy(value_list.data, values.data, nk->num_values * 4); + + SIVAL(value_list.data, nk->num_values * 4, vk_offset); + nk->num_values++; + nk->values_offset = hbin_store_resize(regf, + nk->values_offset, + value_list); + } + + } + hbin_store_tdr_resize(regf, + (tdr_push_fn_t) tdr_push_nk_block, + private_data->offset, nk); + return regf_save_hbin(private_data->hive, 0); +} + +static WERROR regf_save_hbin(struct regf_data *regf, bool flush) +{ + struct tdr_push *push = tdr_push_init(regf); + unsigned int i; + + W_ERROR_HAVE_NO_MEMORY(push); + + /* Only write once every 5 seconds, or when flush is set */ + if (!flush && regf->last_write + 5 >= time(NULL)) { + return WERR_OK; + } + + regf->last_write = time(NULL); + + if (lseek(regf->fd, 0, SEEK_SET) == -1) { + DEBUG(0, ("Error lseeking in regf file\n")); + return WERR_GEN_FAILURE; + } + + /* Recompute checksum */ + if (NT_STATUS_IS_ERR(tdr_push_regf_hdr(push, regf->header))) { + DEBUG(0, ("Failed to push regf header\n")); + return WERR_GEN_FAILURE; + } + regf->header->chksum = regf_hdr_checksum(push->data.data); + talloc_free(push); + + if (NT_STATUS_IS_ERR(tdr_push_to_fd(regf->fd, + (tdr_push_fn_t)tdr_push_regf_hdr, + regf->header))) { + DEBUG(0, ("Error writing registry file header\n")); + return WERR_GEN_FAILURE; + } + + if (lseek(regf->fd, 0x1000, SEEK_SET) == -1) { + DEBUG(0, ("Error lseeking to 0x1000 in regf file\n")); + return WERR_GEN_FAILURE; + } + + for (i = 0; regf->hbins[i]; i++) { + if (NT_STATUS_IS_ERR(tdr_push_to_fd(regf->fd, + (tdr_push_fn_t)tdr_push_hbin_block, + regf->hbins[i]))) { + DEBUG(0, ("Error writing HBIN block\n")); + return WERR_GEN_FAILURE; + } + } + + return WERR_OK; +} + +WERROR reg_create_regf_file(TALLOC_CTX *parent_ctx, + const char *location, + int minor_version, struct hive_key **key) +{ + struct regf_data *regf; + struct regf_hdr *regf_hdr; + struct nk_block nk; + struct sk_block sk; + WERROR error; + DATA_BLOB data; + struct security_descriptor *sd; + uint32_t sk_offset; + + regf = (struct regf_data *)talloc_zero(NULL, struct regf_data); + + W_ERROR_HAVE_NO_MEMORY(regf); + + DEBUG(5, ("Attempting to create registry file\n")); + + /* Get the header */ + regf->fd = creat(location, 0644); + + if (regf->fd == -1) { + DEBUG(0,("Could not create file: %s, %s\n", location, + strerror(errno))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf_hdr = talloc_zero(regf, struct regf_hdr); + W_ERROR_HAVE_NO_MEMORY(regf_hdr); + regf_hdr->REGF_ID = "regf"; + unix_to_nt_time(®f_hdr->modtime, time(NULL)); + regf_hdr->version.major = 1; + regf_hdr->version.minor = minor_version; + regf_hdr->last_block = 0x1000; /* Block size */ + regf_hdr->description = talloc_strdup(regf_hdr, + "Registry created by Samba 4"); + W_ERROR_HAVE_NO_MEMORY(regf_hdr->description); + regf_hdr->chksum = 0; + + regf->header = regf_hdr; + + /* Create all hbin blocks */ + regf->hbins = talloc_array(regf, struct hbin_block *, 1); + W_ERROR_HAVE_NO_MEMORY(regf->hbins); + regf->hbins[0] = NULL; + + nk.header = "nk"; + nk.type = REG_ROOT_KEY; + unix_to_nt_time(&nk.last_change, time(NULL)); + nk.uk1 = 0; + nk.parent_offset = -1; + nk.num_subkeys = 0; + nk.uk2 = 0; + nk.subkeys_offset = -1; + nk.unknown_offset = -1; + nk.num_values = 0; + nk.values_offset = -1; + memset(nk.unk3, 0, 5 * sizeof(uint32_t)); + nk.clsname_offset = -1; + nk.clsname_length = 0; + nk.sk_offset = 0x80; + nk.key_name = "SambaRootKey"; + + /* + * It should be noted that changing the key_name to something shorter + * creates a shorter nk block, which makes the position of the sk block + * change. All Windows registries I've seen have the sk at 0x80. + * I therefore recommend that our regf files share that offset -- Wilco + */ + + /* Create a security descriptor. */ + sd = security_descriptor_dacl_create(regf, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + /* Push the security descriptor to a blob */ + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(&data, regf, + sd, (ndr_push_flags_fn_t)ndr_push_security_descriptor))) { + DEBUG(0, ("Unable to push security descriptor\n")); + return WERR_GEN_FAILURE; + } + + ZERO_STRUCT(sk); + sk.header = "sk"; + sk.prev_offset = 0x80; + sk.next_offset = 0x80; + sk.ref_cnt = 1; + sk.rec_size = data.length; + sk.sec_desc = data.data; + + /* Store the new nk key */ + regf->header->data_offset = hbin_store_tdr(regf, + (tdr_push_fn_t)tdr_push_nk_block, + &nk); + /* Store the sk block */ + sk_offset = hbin_store_tdr(regf, + (tdr_push_fn_t) tdr_push_sk_block, + &sk); + if (sk_offset != 0x80) { + DEBUG(0, ("Error storing sk block, should be at 0x80, stored at 0x%x\n", nk.sk_offset)); + return WERR_GEN_FAILURE; + } + + + *key = (struct hive_key *)regf_get_key(parent_ctx, regf, + regf->header->data_offset); + + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + return error; + } + + /* We can drop our own reference now that *key will have created one */ + talloc_unlink(NULL, regf); + + return WERR_OK; +} + +static WERROR regf_flush_key(struct hive_key *key) +{ + struct regf_key_data *private_data = (struct regf_key_data *)key; + struct regf_data *regf = private_data->hive; + WERROR error; + + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Failed to flush regf to disk\n")); + return error; + } + + return WERR_OK; +} + +static int regf_destruct(struct regf_data *regf) +{ + WERROR error; + + /* Write to disk */ + error = regf_save_hbin(regf, 1); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Failed to flush registry to disk\n")); + return -1; + } + + /* Close file descriptor */ + close(regf->fd); + + return 0; +} + +WERROR reg_open_regf_file(TALLOC_CTX *parent_ctx, const char *location, + struct hive_key **key) +{ + struct regf_data *regf; + struct regf_hdr *regf_hdr; + struct tdr_pull *pull; + unsigned int i; + + regf = (struct regf_data *)talloc_zero(parent_ctx, struct regf_data); + W_ERROR_HAVE_NO_MEMORY(regf); + + talloc_set_destructor(regf, regf_destruct); + + DEBUG(5, ("Attempting to load registry file\n")); + + /* Get the header */ + regf->fd = open(location, O_RDWR); + + if (regf->fd == -1) { + DEBUG(0,("Could not load file: %s, %s\n", location, + strerror(errno))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + pull = tdr_pull_init(regf); + + pull->data.data = (uint8_t*)fd_load(regf->fd, &pull->data.length, 0, regf); + + if (pull->data.data == NULL) { + DEBUG(0, ("Error reading data from file: %s\n", location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf_hdr = talloc(regf, struct regf_hdr); + W_ERROR_HAVE_NO_MEMORY(regf_hdr); + + if (NT_STATUS_IS_ERR(tdr_pull_regf_hdr(pull, regf_hdr, regf_hdr))) { + DEBUG(0, ("Failed to pull regf header from file: %s\n", location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + regf->header = regf_hdr; + + if (strcmp(regf_hdr->REGF_ID, "regf") != 0) { + DEBUG(0, ("Unrecognized NT registry header id: %s, %s\n", + regf_hdr->REGF_ID, location)); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + /* Validate the header ... */ + if (regf_hdr_checksum(pull->data.data) != regf_hdr->chksum) { + DEBUG(0, ("Registry file checksum error: %s: %d,%d\n", + location, regf_hdr->chksum, + regf_hdr_checksum(pull->data.data))); + talloc_free(regf); + return WERR_GEN_FAILURE; + } + + pull->offset = 0x1000; + + i = 0; + /* Read in all hbin blocks */ + regf->hbins = talloc_array(regf, struct hbin_block *, 1); + W_ERROR_HAVE_NO_MEMORY(regf->hbins); + + regf->hbins[0] = NULL; + + while (pull->offset < pull->data.length && + pull->offset <= regf->header->last_block) { + struct hbin_block *hbin = talloc(regf->hbins, + struct hbin_block); + + W_ERROR_HAVE_NO_MEMORY(hbin); + + if (NT_STATUS_IS_ERR(tdr_pull_hbin_block(pull, hbin, hbin))) { + DEBUG(0, ("[%d] Error parsing HBIN block\n", i)); + talloc_free(regf); + return WERR_FOOBAR; + } + + if (strcmp(hbin->HBIN_ID, "hbin") != 0) { + DEBUG(0, ("[%d] Expected 'hbin', got '%s'\n", + i, hbin->HBIN_ID)); + talloc_free(regf); + return WERR_FOOBAR; + } + + regf->hbins[i] = hbin; + i++; + regf->hbins = talloc_realloc(regf, regf->hbins, + struct hbin_block *, i+2); + regf->hbins[i] = NULL; + } + + talloc_free(pull); + + DEBUG(1, ("%d HBIN blocks read\n", i)); + + *key = (struct hive_key *)regf_get_key(parent_ctx, regf, + regf->header->data_offset); + + /* We can drop our own reference now that *key will have created one */ + talloc_unlink(parent_ctx, regf); + + return WERR_OK; +} + +static struct hive_operations reg_backend_regf = { + .name = "regf", + .get_key_info = regf_get_info, + .enum_key = regf_get_subkey_by_index, + .get_key_by_name = regf_get_subkey_by_name, + .get_value_by_name = regf_get_value_by_name, + .enum_value = regf_get_value, + .get_sec_desc = regf_get_sec_desc, + .set_sec_desc = regf_set_sec_desc, + .add_key = regf_add_key, + .set_value = regf_set_value, + .del_key = regf_del_key, + .delete_value = regf_del_value, + .flush_key = regf_flush_key +}; |