/* Unix SMB/CIFS implementation. Reading registry patch files Copyright (C) Jelmer Vernooij 2004-2007 Copyright (C) Wilco Baan Hofman 2006 Copyright (C) Matthias Dieter Wallnöfer 2008-2010 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 . */ #include "includes.h" #include "lib/registry/registry.h" #include "system/filesys.h" _PUBLIC_ WERROR reg_preg_diff_load(int fd, struct smb_iconv_convenience *iconv_convenience, const struct reg_diff_callbacks *callbacks, void *callback_data); _PUBLIC_ WERROR reg_dotreg_diff_load(int fd, struct smb_iconv_convenience *iconv_convenience, const struct reg_diff_callbacks *callbacks, void *callback_data); /* * Generate difference between two keys */ WERROR reg_generate_diff_key(struct registry_key *oldkey, struct registry_key *newkey, const char *path, const struct reg_diff_callbacks *callbacks, void *callback_data) { unsigned int i; struct registry_key *t1 = NULL, *t2 = NULL; char *tmppath; const char *keyname1; WERROR error, error1, error2; TALLOC_CTX *mem_ctx = talloc_init("writediff"); uint32_t old_num_subkeys, old_num_values, new_num_subkeys, new_num_values; if (oldkey != NULL) { error = reg_key_get_info(mem_ctx, oldkey, NULL, &old_num_subkeys, &old_num_values, NULL, NULL, NULL, NULL); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error occurred while getting key info: %s\n", win_errstr(error))); talloc_free(mem_ctx); return error; } } else { old_num_subkeys = 0; old_num_values = 0; } /* Subkeys that were changed or deleted */ for (i = 0; i < old_num_subkeys; i++) { error1 = reg_key_get_subkey_by_index(mem_ctx, oldkey, i, &keyname1, NULL, NULL); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Error occurred while getting subkey by index: %s\n", win_errstr(error1))); continue; } if (newkey != NULL) { error2 = reg_open_key(mem_ctx, newkey, keyname1, &t2); } else { error2 = WERR_BADFILE; t2 = NULL; } if (!W_ERROR_IS_OK(error2) && !W_ERROR_EQUAL(error2, WERR_BADFILE)) { DEBUG(0, ("Error occurred while getting subkey by name: %s\n", win_errstr(error2))); talloc_free(mem_ctx); return error2; } /* if "error2" is going to be "WERR_BADFILE", then newkey */ /* didn't have such a subkey and therefore add a del diff */ tmppath = talloc_asprintf(mem_ctx, "%s\\%s", path, keyname1); if (tmppath == NULL) { DEBUG(0, ("Out of memory\n")); talloc_free(mem_ctx); return WERR_NOMEM; } if (!W_ERROR_IS_OK(error2)) callbacks->del_key(callback_data, tmppath); /* perform here also the recursive invocation */ error1 = reg_open_key(mem_ctx, oldkey, keyname1, &t1); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Error occurred while getting subkey by name: %s\n", win_errstr(error1))); talloc_free(mem_ctx); return error1; } reg_generate_diff_key(t1, t2, tmppath, callbacks, callback_data); talloc_free(tmppath); } if (newkey != NULL) { error = reg_key_get_info(mem_ctx, newkey, NULL, &new_num_subkeys, &new_num_values, NULL, NULL, NULL, NULL); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error occurred while getting key info: %s\n", win_errstr(error))); talloc_free(mem_ctx); return error; } } else { new_num_subkeys = 0; new_num_values = 0; } /* Subkeys that were added */ for(i = 0; i < new_num_subkeys; i++) { error1 = reg_key_get_subkey_by_index(mem_ctx, newkey, i, &keyname1, NULL, NULL); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Error occurred while getting subkey by index: %s\n", win_errstr(error1))); talloc_free(mem_ctx); return error1; } if (oldkey != NULL) { error2 = reg_open_key(mem_ctx, oldkey, keyname1, &t1); if (W_ERROR_IS_OK(error2)) continue; } else { error2 = WERR_BADFILE; t1 = NULL; } if (!W_ERROR_EQUAL(error2, WERR_BADFILE)) { DEBUG(0, ("Error occurred while getting subkey by name: %s\n", win_errstr(error2))); talloc_free(mem_ctx); return error2; } /* oldkey didn't have such a subkey, add a add diff */ tmppath = talloc_asprintf(mem_ctx, "%s\\%s", path, keyname1); if (tmppath == NULL) { DEBUG(0, ("Out of memory\n")); talloc_free(mem_ctx); return WERR_NOMEM; } callbacks->add_key(callback_data, tmppath); /* perform here also the recursive invocation */ error1 = reg_open_key(mem_ctx, newkey, keyname1, &t2); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Error occurred while getting subkey by name: %s\n", win_errstr(error1))); talloc_free(mem_ctx); return error1; } reg_generate_diff_key(t1, t2, tmppath, callbacks, callback_data); talloc_free(tmppath); } /* Values that were added or changed */ for(i = 0; i < new_num_values; i++) { const char *name; uint32_t type1, type2; DATA_BLOB contents1 = { NULL, 0 }, contents2 = { NULL, 0 }; error1 = reg_key_get_value_by_index(mem_ctx, newkey, i, &name, &type1, &contents1); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Unable to get value by index: %s\n", win_errstr(error1))); talloc_free(mem_ctx); return error1; } if (oldkey != NULL) { error2 = reg_key_get_value_by_name(mem_ctx, oldkey, name, &type2, &contents2); } else error2 = WERR_BADFILE; if (!W_ERROR_IS_OK(error2) && !W_ERROR_EQUAL(error2, WERR_BADFILE)) { DEBUG(0, ("Error occurred while getting value by name: %s\n", win_errstr(error2))); talloc_free(mem_ctx); return error2; } if (W_ERROR_IS_OK(error2) && (data_blob_cmp(&contents1, &contents2) == 0) && (type1 == type2)) { talloc_free(discard_const_p(char, name)); talloc_free(contents1.data); talloc_free(contents2.data); continue; } callbacks->set_value(callback_data, path, name, type1, contents1); talloc_free(discard_const_p(char, name)); talloc_free(contents1.data); talloc_free(contents2.data); } /* Values that were deleted */ for (i = 0; i < old_num_values; i++) { const char *name; uint32_t type; DATA_BLOB contents = { NULL, 0 }; error1 = reg_key_get_value_by_index(mem_ctx, oldkey, i, &name, &type, &contents); if (!W_ERROR_IS_OK(error1)) { DEBUG(0, ("Unable to get value by index: %s\n", win_errstr(error1))); talloc_free(mem_ctx); return error1; } if (newkey != NULL) error2 = reg_key_get_value_by_name(mem_ctx, newkey, name, &type, &contents); else error2 = WERR_BADFILE; if (W_ERROR_IS_OK(error2)) { talloc_free(discard_const_p(char, name)); talloc_free(contents.data); continue; } if (!W_ERROR_EQUAL(error2, WERR_BADFILE)) { DEBUG(0, ("Error occurred while getting value by name: %s\n", win_errstr(error2))); talloc_free(mem_ctx); return error2; } callbacks->del_value(callback_data, path, name); talloc_free(discard_const_p(char, name)); talloc_free(contents.data); } talloc_free(mem_ctx); return WERR_OK; } /** * Generate diff between two registry contexts */ _PUBLIC_ WERROR reg_generate_diff(struct registry_context *ctx1, struct registry_context *ctx2, const struct reg_diff_callbacks *callbacks, void *callback_data) { unsigned int i; WERROR error; for (i = 0; reg_predefined_keys[i].name; i++) { struct registry_key *r1 = NULL, *r2 = NULL; error = reg_get_predefined_key(ctx1, reg_predefined_keys[i].handle, &r1); if (!W_ERROR_IS_OK(error) && !W_ERROR_EQUAL(error, WERR_BADFILE)) { DEBUG(0, ("Unable to open hive %s for backend 1\n", reg_predefined_keys[i].name)); continue; } error = reg_get_predefined_key(ctx2, reg_predefined_keys[i].handle, &r2); if (!W_ERROR_IS_OK(error) && !W_ERROR_EQUAL(error, WERR_BADFILE)) { DEBUG(0, ("Unable to open hive %s for backend 2\n", reg_predefined_keys[i].name)); continue; } /* if "r1" is NULL (old hive) and "r2" isn't (new hive) then * the hive doesn't exist yet and we have to generate an add * diff */ if ((r1 == NULL) && (r2 != NULL)) { callbacks->add_key(callback_data, reg_predefined_keys[i].name); } /* if "r1" isn't NULL (old hive) and "r2" is (new hive) then * the hive shouldn't exist anymore and we have to generate a * del diff */ if ((r1 != NULL) && (r2 == NULL)) { callbacks->del_key(callback_data, reg_predefined_keys[i].name); } error = reg_generate_diff_key(r1, r2, reg_predefined_keys[i].name, callbacks, callback_data); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Unable to determine diff: %s\n", win_errstr(error))); return error; } } if (callbacks->done != NULL) { callbacks->done(callback_data); } return WERR_OK; } /** * Load diff file */ _PUBLIC_ WERROR reg_diff_load(const char *filename, struct smb_iconv_convenience *iconv_convenience, const struct reg_diff_callbacks *callbacks, void *callback_data) { int fd; char hdr[4]; fd = open(filename, O_RDONLY, 0); if (fd == -1) { DEBUG(0, ("Error opening registry patch file `%s'\n", filename)); return WERR_GENERAL_FAILURE; } if (read(fd, &hdr, 4) != 4) { DEBUG(0, ("Error reading registry patch file `%s'\n", filename)); close(fd); return WERR_GENERAL_FAILURE; } /* Reset position in file */ lseek(fd, 0, SEEK_SET); #if 0 /* These backends are not supported yet. */ if (strncmp(hdr, "CREG", 4) == 0) { /* Must be a W9x CREG Config.pol file */ return reg_creg_diff_load(diff, fd); } else if (strncmp(hdr, "regf", 4) == 0) { /* Must be a REGF NTConfig.pol file */ return reg_regf_diff_load(diff, fd); } else #endif if (strncmp(hdr, "PReg", 4) == 0) { /* Must be a GPO Registry.pol file */ return reg_preg_diff_load(fd, iconv_convenience, callbacks, callback_data); } else { /* Must be a normal .REG file */ return reg_dotreg_diff_load(fd, iconv_convenience, callbacks, callback_data); } } /** * The reg_diff_apply functions */ static WERROR reg_diff_apply_add_key(void *_ctx, const char *key_name) { struct registry_context *ctx = (struct registry_context *)_ctx; struct registry_key *tmp; char *buf, *buf_ptr; WERROR error; /* Recursively create the path */ buf = talloc_strdup(ctx, key_name); buf_ptr = buf; while (*buf_ptr++ != '\0' ) { if (*buf_ptr == '\\') { *buf_ptr = '\0'; error = reg_key_add_abs(ctx, ctx, buf, 0, NULL, &tmp); if (!W_ERROR_EQUAL(error, WERR_ALREADY_EXISTS) && !W_ERROR_IS_OK(error)) { DEBUG(0, ("Error adding new key '%s': %s\n", key_name, win_errstr(error))); return error; } *buf_ptr++ = '\\'; talloc_free(tmp); } } talloc_free(buf); /* Add the key */ error = reg_key_add_abs(ctx, ctx, key_name, 0, NULL, &tmp); if (!W_ERROR_EQUAL(error, WERR_ALREADY_EXISTS) && !W_ERROR_IS_OK(error)) { DEBUG(0, ("Error adding new key '%s': %s\n", key_name, win_errstr(error))); return error; } talloc_free(tmp); return WERR_OK; } static WERROR reg_diff_apply_del_key(void *_ctx, const char *key_name) { struct registry_context *ctx = (struct registry_context *)_ctx; /* We can't proof here for success, because a common superkey could */ /* have been deleted before the subkey's (diff order). This removed */ /* therefore all children recursively and the "WERR_BADFILE" result is */ /* expected. */ reg_key_del_abs(ctx, key_name); return WERR_OK; } static WERROR reg_diff_apply_set_value(void *_ctx, const char *path, const char *value_name, uint32_t value_type, DATA_BLOB value) { struct registry_context *ctx = (struct registry_context *)_ctx; struct registry_key *tmp; WERROR error; /* Open key */ error = reg_open_key_abs(ctx, ctx, path, &tmp); if (W_ERROR_EQUAL(error, WERR_BADFILE)) { DEBUG(0, ("Error opening key '%s'\n", path)); return error; } /* Set value */ error = reg_val_set(tmp, value_name, value_type, value); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error setting value '%s'\n", value_name)); return error; } talloc_free(tmp); return WERR_OK; } static WERROR reg_diff_apply_del_value(void *_ctx, const char *key_name, const char *value_name) { struct registry_context *ctx = (struct registry_context *)_ctx; struct registry_key *tmp; WERROR error; /* Open key */ error = reg_open_key_abs(ctx, ctx, key_name, &tmp); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error opening key '%s'\n", key_name)); return error; } error = reg_del_value(tmp, value_name); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error deleting value '%s'\n", value_name)); return error; } talloc_free(tmp); return WERR_OK; } static WERROR reg_diff_apply_del_all_values(void *_ctx, const char *key_name) { struct registry_context *ctx = (struct registry_context *)_ctx; struct registry_key *key; WERROR error; const char *value_name; error = reg_open_key_abs(ctx, ctx, key_name, &key); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error opening key '%s'\n", key_name)); return error; } W_ERROR_NOT_OK_RETURN(reg_key_get_info(ctx, key, NULL, NULL, NULL, NULL, NULL, NULL, NULL)); while (W_ERROR_IS_OK(reg_key_get_value_by_index( ctx, key, 0, &value_name, NULL, NULL))) { error = reg_del_value(key, value_name); if (!W_ERROR_IS_OK(error)) { DEBUG(0, ("Error deleting value '%s'\n", value_name)); return error; } talloc_free(discard_const_p(char, value_name)); } talloc_free(key); return WERR_OK; } /** * Apply diff to a registry context */ _PUBLIC_ WERROR reg_diff_apply(struct registry_context *ctx, struct smb_iconv_convenience *iconv_convenience, const char *filename) { struct reg_diff_callbacks callbacks; callbacks.add_key = reg_diff_apply_add_key; callbacks.del_key = reg_diff_apply_del_key; callbacks.set_value = reg_diff_apply_set_value; callbacks.del_value = reg_diff_apply_del_value; callbacks.del_all_values = reg_diff_apply_del_all_values; callbacks.done = NULL; return reg_diff_load(filename, iconv_convenience, &callbacks, ctx); }