s3:net: add command registry check
authorGregor Beck <gbeck@sernet.de>
Thu, 30 Jun 2011 10:08:16 +0000 (12:08 +0200)
committerMichael Adam <obnox@samba.org>
Wed, 12 Oct 2011 20:45:53 +0000 (22:45 +0200)
Signed-off-by: Michael Adam <obnox@samba.org>
source3/Makefile.in
source3/utils/net.c
source3/utils/net.h
source3/utils/net_registry.c
source3/utils/net_registry_check.c [new file with mode: 0644]
source3/utils/net_registry_check.h [new file with mode: 0644]
source3/wscript_build

index 8298520..27c3393 100644 (file)
@@ -1195,7 +1195,7 @@ NET_OBJ1 = utils/net.o utils/net_ads.o utils/net_help.o \
           utils/net_util.o utils/net_rpc_sh_acct.o utils/net_rpc_audit.o \
           $(PASSWD_UTIL_OBJ) utils/net_dns.o utils/net_ads_gpo.o \
           utils/net_conf.o utils/net_join.o utils/net_user.o \
-          utils/net_group.o utils/net_file.o utils/net_registry.o \
+          utils/net_group.o utils/net_file.o utils/net_registry.o utils/net_registry_check.o\
           auth/token_util.o utils/net_dom.o utils/net_share.o \
           utils/net_g_lock.o \
           utils/net_serverid.o \
index e7edb6e..d05f614 100644 (file)
@@ -820,6 +820,10 @@ static struct functable net_func[] = {
                {"lock", 0, POPT_ARG_NONE,   &c->opt_lock},
                {"auto", 'a', POPT_ARG_NONE,   &c->opt_auto},
                {"repair", 0, POPT_ARG_NONE,   &c->opt_repair},
+               /* Options for 'net registry check'*/
+               {"reg-version", 0, POPT_ARG_INT, &c->opt_reg_version},
+               {"output", 'o', POPT_ARG_STRING, &c->opt_output},
+               {"wipe", 0, POPT_ARG_NONE, &c->opt_wipe},
                POPT_COMMON_SAMBA
                { 0, 0, 0, 0}
        };
index 1d1559f..2dc4430 100644 (file)
@@ -80,6 +80,9 @@ struct net_context {
        int opt_lock;
        int opt_auto;
        int opt_repair;
+       int opt_reg_version;
+       const char *opt_output;
+       int opt_wipe;
 
        int opt_have_ip;
        struct sockaddr_storage opt_dest_ip;
index 086c4e7..008d021 100644 (file)
@@ -35,6 +35,7 @@
 #include "../libcli/security/sddl.h"
 #include "../libcli/registry/util_reg.h"
 #include "passdb/machine_sid.h"
+#include "net_registry_check.h"
 
 /*
  *
@@ -1243,6 +1244,54 @@ static int net_registry_convert(struct net_context *c, int argc,
 }
 /**@}*/
 
+static int net_registry_check(struct net_context *c, int argc,
+                             const char **argv)
+{
+       const char* dbfile;
+       struct check_options opts;
+
+       if (argc > 1|| c->display_usage) {
+               d_printf("%s\n%s",
+                        _("Usage:"),
+                        _("net registry check  [-vraTfl] [-o <ODB>] [--wipe] [<TDB>]\n"
+                          "  Check a registry database.\n"
+                          "    -v|--verbose\t be verbose\n"
+                          "    -r|--repair\t\t interactive repair mode\n"
+                          "    -a|--auto\t\t noninteractive repair mode\n"
+                          "    -T|--test\t\t dry run\n"
+                          "    -f|--force\t\t force\n"
+                          "    -l|--lock\t\t lock <TDB> while doing the check\n"
+                          "    -o|--output=<ODB>\t output database\n"
+                          "    --reg-version=n\t assume database format version {n|1,2,3}\n"
+                          "    --wipe\t\t create a new database from scratch\n"
+                          "    --db=<TDB>\t\t registry database to open\n"));
+               return c->display_usage ? 0 : -1;
+       }
+
+       dbfile = c->opt_db ? c->opt_db : (
+               (argc > 0) ? argv[0] :
+               state_path("registry.tdb"));
+       if (dbfile == NULL) {
+               return -1;
+       }
+
+       opts = (struct check_options) {
+               .lock = c->opt_lock || c->opt_long_list_entries,
+               .test = c->opt_testmode,
+               .automatic = c->opt_auto,
+               .verbose = c->opt_verbose,
+               .force = c->opt_force,
+               .repair = c->opt_repair || c->opt_reboot,
+               .version = c->opt_reg_version,
+               .output  = c->opt_output,
+               .wipe = c->opt_wipe,
+               .implicit_db = (c->opt_db == NULL) && (argc == 0),
+       };
+
+       return net_registry_check_db(dbfile, &opts);
+}
+
+
 /******************************************************************************/
 
 int net_registry(struct net_context *c, int argc, const char **argv)
@@ -1386,6 +1435,14 @@ int net_registry(struct net_context *c, int argc, const char **argv)
                        N_("net registry convert\n"
                           "    Convert .reg file")
                },
+               {
+                       "check",
+                       net_registry_check,
+                       NET_TRANSPORT_LOCAL,
+                       N_("Check .reg file"),
+                       N_("net registry check\n"
+                          "    Check .reg file")
+               },
        { NULL, NULL, 0, NULL, NULL }
        };
 
diff --git a/source3/utils/net_registry_check.c b/source3/utils/net_registry_check.c
new file mode 100644 (file)
index 0000000..9bb7296
--- /dev/null
@@ -0,0 +1,1314 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * 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/>.
+ */
+
+/**
+ * @brief  Check the registry database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date   Mar 2011
+ */
+
+#include "net_registry_check.h"
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "net.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/secdesc.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include <termios.h>
+#include "util_tdb.h"
+#include "registry/reg_db.h"
+#include "libcli/registry/util_reg.h"
+#include "registry/reg_parse_internal.h"
+#include "interact.h"
+
+/*
+  check tree:
+  + every key has a subkeylist
+  + every key is referenced by the subkeylist of its parent
+  check path:
+  + starts with valid hive
+  + UTF-8 (option to convert ???)
+  + only uppercase
+  + separator ???
+  check value:
+  + REG_DWORD has size 4
+  + REG_QWORD has size 8
+  + STRINGS are zero terminated UTF-16
+*/
+
+struct regval {
+       char *name;
+       uint32_t type;
+       DATA_BLOB data;
+};
+
+struct regkey {
+       char *name;
+       char *path;
+       bool has_subkeylist;
+       bool needs_update;
+       struct regkey *parent;
+       size_t nsubkeys;
+       struct regkey **subkeys;
+       size_t nvalues;
+       struct regval **values;
+       struct security_descriptor *sd;
+};
+
+struct check_ctx {
+       char *fname;
+       struct check_options opt;
+
+       uint32_t version;
+       char sep;
+       struct db_context *idb;
+       struct db_context *odb;
+
+       struct regkey *root; /*dummy key to hold all basekeys*/
+       struct db_context *reg;
+       struct db_context *del;
+
+       bool transaction;
+       char auto_action;
+       char default_action;
+};
+
+static void* talloc_array_append(void *mem_ctx, void* array[], void *ptr)
+{
+       size_t size = array ? talloc_array_length(array) : 1;
+       void **tmp = talloc_realloc(mem_ctx, array, void*, size + 1);
+       if (tmp == NULL) {
+               talloc_free(array);
+               return NULL;
+       }
+       tmp[size-1] = ptr;
+       tmp[size] = NULL;
+       return tmp;
+}
+
+static void regkey_add_subkey(struct regkey *key, struct regkey *subkey)
+{
+       key->subkeys = (struct regkey**)
+               talloc_array_append(key, (void**)key->subkeys, subkey);
+       if (key->subkeys != NULL) {
+               key->nsubkeys++;
+       }
+}
+
+static struct regval* regval_copy(TALLOC_CTX *mem_ctx, const struct regval *val)
+{
+       struct regval *ret = talloc_zero(mem_ctx, struct regval);
+       if (ret == NULL) {
+               goto fail;
+       }
+
+       ret->name = talloc_strdup(ret, val->name);
+       if (ret->name == NULL) {
+               goto fail;
+       }
+
+       ret->data = data_blob_dup_talloc(ret, val->data);
+       if (ret->data.data == NULL) {
+               goto fail;
+       }
+
+       ret->type = val->type;
+
+       return ret;
+fail:
+       talloc_free(ret);
+       return NULL;
+}
+
+static void regkey_add_regval(struct regkey *key, struct regval *val)
+{
+       key->values = (struct regval**)
+               talloc_array_append(key, (void**)key->values, val);
+       if (key->values != NULL) {
+               key->nvalues++;
+       }
+}
+
+static bool tdb_data_read_uint32(TDB_DATA *buf, uint32_t *result)
+{
+       const size_t len = sizeof(uint32_t);
+       if (buf->dsize >= len) {
+               *result = IVAL(buf->dptr, 0);
+               buf->dptr += len;
+               buf->dsize -= len;
+               return true;
+       }
+       return false;
+}
+
+static bool tdb_data_read_cstr(TDB_DATA *buf, char **result)
+{
+       const size_t len = strnlen((char*)buf->dptr, buf->dsize) + 1;
+       if (buf->dsize >= len) {
+               *result = (char*)buf->dptr;
+               buf->dptr += len;
+               buf->dsize -= len;
+               return true;
+       }
+       return false;
+}
+
+static bool tdb_data_read_blob(TDB_DATA *buf, DATA_BLOB *result)
+{
+       TDB_DATA tmp = *buf;
+       uint32_t len;
+       if (!tdb_data_read_uint32(&tmp, &len)) {
+               return false;
+       }
+       if (tmp.dsize >= len) {
+               *buf = tmp;
+               result->data   = tmp.dptr;
+               result->length = len;
+               buf->dptr += len;
+               buf->dsize -= len;
+               return true;
+       }
+       return false;
+}
+
+static bool tdb_data_read_regval(TDB_DATA *buf, struct regval *result)
+{
+       TDB_DATA tmp = *buf;
+       struct regval value;
+       if (!tdb_data_read_cstr(&tmp, &value.name)
+           || !tdb_data_read_uint32(&tmp, &value.type)
+           || !tdb_data_read_blob(&tmp, &value.data))
+       {
+               return false;
+       }
+       *buf = tmp;
+       *result = value;
+       return true;
+}
+
+static bool tdb_data_is_empty(TDB_DATA d) {
+       return (d.dptr == NULL) || (d.dsize == 0);
+}
+
+static bool tdb_data_is_cstr(TDB_DATA d) {
+       if (tdb_data_is_empty(d) || (d.dptr[d.dsize-1] != '\0')) {
+               return false;
+       }
+       return rawmemchr(d.dptr, '\0') == &d.dptr[d.dsize-1];
+}
+
+static char* tdb_data_print(TALLOC_CTX *mem_ctx, TDB_DATA d)
+{
+       if (!tdb_data_is_empty(d)) {
+               char *ret = NULL;
+               cbuf *ost = cbuf_new(mem_ctx);
+               int len = cbuf_print_quoted(ost, (const char*)d.dptr, d.dsize);
+               if (len != -1) {
+                       cbuf_swapptr(ost, &ret, 0);
+                       talloc_steal(mem_ctx, ret);
+               }
+               talloc_free(ost);
+               return ret;
+       }
+       return talloc_strdup(mem_ctx, "<NULL>");
+}
+
+
+static TDB_DATA cbuf_make_tdb_data(cbuf *b)
+{
+       return make_tdb_data((void*)cbuf_gets(b, 0), cbuf_getpos(b));
+}
+
+static void remove_all(char *str, char c)
+{
+       char *out=str;
+       while (*str) {
+               if (*str != c) {
+                       *out = *str;
+                       out++;
+               }
+               str++;
+       }
+       *out = '\0';
+}
+
+static void remove_runs(char *str, char c)
+{
+       char *out=str;
+       while (*str) {
+               *out = *str;
+               if (*str == c) {
+                       while (*str == c) {
+                               str++;
+                       }
+               } else {
+                       str++;
+               }
+               out++;
+       }
+       *out = '\0';
+}
+
+
+static char* parent_path(const char *path, char sep)
+{
+       const char *p = strrchr(path, sep);
+       return p ? talloc_strndup(talloc_tos(), path, p-path) : NULL;
+}
+
+/* return the regkey corresponding to path, create if not yet existing */
+static struct regkey*
+check_ctx_lookup_key(struct check_ctx *ctx, const char *path) {
+       struct regkey *ret = NULL;
+       NTSTATUS status;
+       TDB_DATA val;
+
+       if ( path == NULL) {
+               return ctx->root;
+       }
+
+       status = dbwrap_fetch(ctx->reg, ctx, string_term_tdb_data(path), &val);
+       if (!NT_STATUS_IS_OK(status)) {
+               return NULL;
+       }
+       if (val.dptr != NULL) {
+               if (ctx->opt.verbose) {
+                       printf("Open: %s\n", path);
+               }
+               ret = *(struct regkey**)val.dptr;
+       } else {
+               /* not yet existing, create */
+               char *pp;
+               if (ctx->opt.verbose) {
+                       printf("New: %s\n", path);
+               }
+               ret = talloc_zero(ctx, struct regkey);
+               if (ret == NULL) {
+                       DEBUG(0, ("Out of memory!\n"));
+                       goto done;
+               }
+               ret->path = talloc_strdup(ret, path);
+
+               pp = parent_path(path, ctx->sep);
+               ret->parent = check_ctx_lookup_key(ctx, pp);
+               regkey_add_subkey(ret->parent, ret);
+               TALLOC_FREE(pp);
+
+               /* the dummy root key has no subkeylist so set the name */
+               if (ret->parent == ctx->root) {
+                       ret->name = talloc_strdup(ret, path);
+               }
+
+               dbwrap_store(ctx->reg, string_term_tdb_data(path),
+                            make_tdb_data((void*)&ret, sizeof(ret)), 0);
+       }
+done:
+       talloc_free(val.dptr);
+       return ret;
+}
+
+static struct check_ctx* check_ctx_create(TALLOC_CTX *mem_ctx, const char *db,
+                                         const struct check_options *opt)
+{
+       struct check_ctx *ctx = talloc_zero(mem_ctx, struct check_ctx);
+
+       ctx->opt = *opt;
+       ctx->reg = db_open_rbt(ctx);
+       ctx->del = db_open_rbt(ctx);
+       ctx->root = talloc_zero(ctx, struct regkey);
+       ctx->fname = talloc_strdup(ctx, db);
+
+       if (opt->automatic && (opt->output == NULL)) {
+               ctx->opt.repair = true;
+               ctx->opt.output = ctx->fname;
+       }
+
+       if (opt->repair) {
+               if (opt->output) {
+                       d_fprintf(stderr, "You can not specify --output "
+                                 "with --repair\n");
+                       goto fail;
+               } else {
+                       ctx->opt.output = ctx->fname;
+               }
+       }
+
+       ctx->default_action = opt->automatic ? 'd' : 'r';
+       return ctx;
+fail:
+       talloc_free(ctx);
+       return NULL;
+}
+
+static bool check_ctx_open_output(struct check_ctx *ctx)
+{
+       int oflags = O_RDWR | O_CREAT ;
+
+       if (ctx->opt.output == NULL) {
+               return true;
+       }
+
+       if (!ctx->opt.repair) {
+               if (!ctx->opt.wipe) {
+                       oflags |= O_EXCL;
+               }
+               ctx->opt.wipe = true;
+       }
+
+       ctx->odb = db_open(ctx, ctx->opt.output, 0, TDB_DEFAULT, oflags, 0644);
+       if (ctx->odb == NULL) {
+               d_fprintf(stderr,
+                         _("Could not open db (%s) for writing: %s\n"),
+                         ctx->opt.output, strerror(errno));
+               return false;
+       }
+       return true;
+}
+
+
+static bool check_ctx_open_input(struct check_ctx *ctx) {
+       ctx->idb = db_open(ctx, ctx->fname, 0, TDB_DEFAULT, O_RDONLY, 0);
+       if (ctx->idb == NULL) {
+               d_fprintf(stderr,
+                         _("Could not open db (%s) for reading: %s\n"),
+                         ctx->fname, strerror(errno));
+               return false;
+       }
+       return true;
+}
+
+static bool check_ctx_transaction_start(struct check_ctx *ctx) {
+       if (ctx->odb == NULL) {
+               return true;
+       }
+       if (dbwrap_transaction_start(ctx->odb) != 0) {
+               DEBUG(0, ("transaction_start failed\n"));
+               return false;
+       }
+       ctx->transaction = true;
+       return true;
+}
+
+static void check_ctx_transaction_stop(struct check_ctx *ctx, bool ok) {
+       if (!ctx->transaction) {
+               return;
+       }
+       if (!ctx->opt.test && ok) {
+               d_printf("Commiting changes\n");
+               if (dbwrap_transaction_commit(ctx->odb) != 0) {
+                       DEBUG(0, ("transaction_commit failed\n"));
+               }
+       } else {
+               d_printf("Discarding changes\n");
+               dbwrap_transaction_cancel(ctx->odb);
+       }
+}
+
+static bool read_info(struct check_ctx *ctx, const char *key, TDB_DATA val)
+{
+       if (val.dsize==sizeof(uint32_t) && strcmp(key, "version")==0) {
+               uint32_t v = IVAL(val.dptr, 0);
+               printf("INFO: %s = %d\n", key, v);
+               return true;
+       }
+       printf("INFO: %s = <invalid>\n", key);
+       return false;
+}
+
+static bool is_all_upper(const char *str) {
+       bool ret;
+       char *tmp = talloc_strdup(talloc_tos(), str);
+       strupper_m(tmp);
+       ret = (strcmp(tmp, str) == 0);
+       talloc_free(tmp);
+       return ret;
+}
+
+static void move_to_back(struct regkey *key, struct regkey *subkey)
+{
+       struct regkey **ptr;
+       size_t nidx;
+
+       DEBUG(5, ("Move to back subkey \"%s\" of \"%s\"\n",
+                 subkey->path, key->path));
+
+       for (ptr=key->subkeys; *ptr != subkey; ptr++)
+               ;
+
+       nidx = ptr + 1 - key->subkeys;
+       memmove(ptr, ptr+1, (key->nsubkeys - nidx) * sizeof(*ptr));
+
+       key->subkeys[key->nsubkeys-1] = subkey;
+}
+
+static void set_subkey_name(struct check_ctx *ctx, struct regkey *key,
+                           const char *name, int nlen)
+{
+       char *path = key->path;
+       TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+       char *p;
+       struct regkey *subkey;
+       char *nname = talloc_strndup(mem_ctx, name, nlen);
+       remove_all(nname, ctx->sep);
+
+       if (strncmp(name, nname, nlen) != 0) {
+               /* XXX interaction: delete/edit */
+               printf("Warning: invalid name: \"%s\" replace with \"%s\"\n",
+                      name, nname);
+               key->needs_update = true;
+       }
+       p = talloc_asprintf_strupper_m(mem_ctx, "%s%c%s",
+                                      path, ctx->sep, nname);
+       subkey = check_ctx_lookup_key(ctx, p);
+       if (subkey->name) {
+               bool do_replace = false;
+
+               if (strcmp(subkey->name, nname) != 0) {
+                       int action;
+                       char default_action;
+
+                       if (is_all_upper(nname)) {
+                               default_action = 'o';
+                       } else {
+                               default_action = 'n';
+                       }
+
+                       printf("Conflicting subkey names of [%s]: "
+                              "old: \"%s\", new: \"%s\"\n",
+                              key->path, subkey->name, nname);
+
+                       if (ctx->opt.output == NULL || ctx->opt.automatic) {
+                               action = default_action;
+                       } else {
+                               do {
+                                       action = interact_prompt(
+                                               "choose spelling [o]ld, [n]ew,"
+                                               "or [e]dit", "one",
+                                               default_action);
+                                       if (action == 'e') {
+                                               printf("Sorry, edit is not yet "
+                                                      "implemented here...\n");
+                                       }
+                               } while (action == 'e');
+                       }
+
+                       if (action == 'n') {
+                               do_replace = true;
+                       }
+               }
+
+               if (do_replace) {
+                       if (ctx->opt.verbose) {
+                               printf("Replacing name: %s: \"%s\""
+                                      " -> \"%s\"\n", path,
+                                      subkey->name, nname);
+                       }
+                       TALLOC_FREE(subkey->name);
+                       subkey->name = talloc_steal(subkey, nname);
+                       key->needs_update = true;
+               }
+       } else {
+               if (ctx->opt.verbose) {
+                       printf("Set name: %s: \"%s\"\n", path, nname);
+               }
+               subkey->name = talloc_steal(subkey, nname);
+       }
+
+       move_to_back(key, subkey);
+       TALLOC_FREE(mem_ctx);
+}
+
+static void
+read_subkeys(struct check_ctx *ctx, const char *path, TDB_DATA val, bool update)
+{
+       uint32_t num_items, found_items = 0;
+       char *subkey;
+       struct regkey *key = check_ctx_lookup_key(ctx, path);
+
+       key->needs_update |= update;
+
+       /* printf("SUBKEYS: %s\n", path); */
+       if (key->has_subkeylist) {
+               printf("Duplicate subkeylist \"%s\"\n",
+                      path);
+               found_items = key->nsubkeys;
+       }
+
+       /* exists as defined by regdb_key_exists() */
+       key->has_subkeylist = true;
+
+       /* name is set if a key is referenced by the */
+       /* subkeylist of its parent. */
+
+       if (!tdb_data_read_uint32(&val, &num_items) ) {
+               printf("Invalid subkeylist: \"%s\"\n", path);
+               return;
+       }
+
+       while (tdb_data_read_cstr(&val, &subkey)) {
+               /* printf(" SUBKEY: %s\n", subkey); */
+               set_subkey_name(ctx, key, subkey, strlen(subkey));
+               found_items++;
+       }
+
+       if (val.dsize != 0) {
+               printf("Subkeylist of \"%s\": trailing: \"%.*s\"\n",
+                      path, (int)val.dsize, val.dptr);
+               /* ask: best effort, delete or edit?*/
+               set_subkey_name(ctx, key, (char*)val.dptr, val.dsize);
+               found_items++;
+               key->needs_update = true;
+       }
+
+       if (num_items != found_items) {
+               printf("Subkeylist of \"%s\": invalid number of subkeys, "
+                      "expected: %d got: %d\n", path, num_items, found_items);
+               key->needs_update = true;
+       }
+
+}
+
+static void read_values(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+       struct regkey *key = check_ctx_lookup_key(ctx, path);
+       uint32_t num_items, found_items;
+       struct regval value;
+
+       /* printf("VALUES: %s\n", path); */
+
+       if (!tdb_data_read_uint32(&val, &num_items) ) {
+               printf("Invalid valuelist: \"%s\"\n", path);
+               return;
+       }
+
+       found_items=0;
+       while (tdb_data_read_regval(&val, &value)) {
+               /* printf(" VAL: %s type: %s(%d) length: %d\n", value.name, */
+               /*        str_regtype(value.type), value.type, */
+               /*        (int)value.data.length); */
+               regkey_add_regval(key, regval_copy(key, &value));
+               found_items++;
+       }
+
+       if (num_items != found_items) {
+               printf("Valuelist of \"%s\": invalid number of values, "
+                      "expected: %d got: %d\n", path, num_items, found_items);
+               key->needs_update = true;
+       }
+
+       if (val.dsize != 0) {
+               printf("Valuelist of \"%s\": trailing: \"%*s\"\n", path,
+                      (int)val.dsize, val.dptr);
+               key->needs_update = true;
+               /* XXX best effort ??? */
+               /* ZERO_STRUCT(value); */
+               /* if (tdb_data_read_cstr(&val, &value.name) */
+               /*     && tdb_data_read_uint32(&val, &value.type)) */
+               /* { */
+               /*      uint32_t len = -1; */
+               /*      tdb_data_read_uint32(&val, &len); */
+               /*      ... */
+               /*      found_items ++; */
+               /*      regkey_add_regval(key, regval_copy(key, value)); */
+               /* } */
+       }
+       if (found_items == 0) {
+               printf("Valuelist of \"%s\" empty\n", path);
+               key->needs_update = true;
+       }
+}
+
+static bool read_sorted(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+       if (ctx->version >= 3) {
+               return false;
+       }
+
+       if ((val.dptr == NULL) || (val.dsize<4)) {
+               return false;
+       }
+
+       /* ToDo: check */
+       /* struct regkey *key = check_ctx_lookup_key(ctx, path); */
+       /* printf("SORTED: %s\n", path); */
+       return true;
+}
+
+static bool read_sd(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+       NTSTATUS status;
+       struct regkey *key = check_ctx_lookup_key(ctx, path);
+       /* printf("SD: %s\n", path); */
+
+       status = unmarshall_sec_desc(key, val.dptr, val.dsize, &key->sd);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("Failed to read SD of %s: %s\n",
+                         path, nt_errstr(status)));
+       }
+       return true;
+}
+
+static bool srprs_path(const char **ptr, const char* prefix, char sep,
+                      const char **ppath)
+{
+       const char *path, *pos = *ptr;
+       if (prefix != NULL) {
+               if (!srprs_str(&pos, prefix, -1) || !srprs_char(&pos, sep) ) {
+                       return false;
+               }
+       }
+       path = pos;
+       if ( !srprs_hive(&pos, NULL) ) {
+               return false;
+       }
+       if ( !srprs_eos(&pos) && !srprs_char(&pos, sep) ) {
+               return false;
+       }
+       *ppath = path;
+       *ptr = rawmemchr(pos, '\0');
+       return true;
+}
+
+/* Fixme: this dosn't work in the general multibyte char case.
+   see string_replace()
+*/
+static bool normalize_path_internal(char* path, char sep) {
+       size_t len = strlen(path);
+       const char *orig = talloc_strndup(talloc_tos(), path, len);
+       char *optr = path, *iptr = path;
+       bool changed;
+
+       while (*iptr == sep ) {
+               iptr++;
+       }
+       while (*iptr) {
+               *optr = *iptr;
+               if (*iptr == sep) {
+                       while (*iptr == sep) {
+                               iptr++;
+                       }
+                       if (*iptr) {
+                               optr++;
+                       }
+               } else {
+                       iptr++;
+                       optr++;
+               }
+       }
+       *optr = '\0';
+
+       strupper_m(path);
+       changed = (strcmp(orig, path) != 0);
+       talloc_free(discard_const(orig));
+       return changed;
+}
+
+static bool normalize_path(char* path, char sep) {
+       static const char* SEPS = "\\/";
+       char* firstsep = strpbrk(path, SEPS);
+
+       assert (strchr(SEPS, sep));
+
+       if (firstsep && (*firstsep != sep)) {
+               string_replace(path, *firstsep, sep);
+       }
+       return normalize_path_internal(path, sep);
+}
+
+static int check_tdb_action(struct db_record *rec, void *check_ctx)
+{
+       struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+       TALLOC_CTX *frame = talloc_stackframe();
+       TDB_DATA val = dbwrap_record_get_value(rec);
+       TDB_DATA rec_key = dbwrap_record_get_key(rec);
+       char *key;
+       bool invalid_path = false;
+       bool once_more;
+
+       if (!tdb_data_is_cstr(rec_key)) {
+               printf("Key is not zero terminated: \"%.*s\"\ntry to go on.\n",
+                      (int)rec_key.dsize, rec_key.dptr);
+               invalid_path = true;
+       }
+       key = talloc_strndup(frame, (char*)rec_key.dptr, rec_key.dsize);
+
+       do {
+               const char *path, *pos = key;
+               once_more = false;
+
+               if (srprs_str(&pos, "INFO/", -1)) {
+                       if ( read_info(ctx, pos, val) ) {
+                               break;
+                       }
+                       invalid_path = true;
+                       /* ask: mark invalid */
+               } else if (srprs_str(&pos, "__db_sequence_number__", -1)) {
+                       printf("Skip key: \"%.*s\"\n",
+                              (int)rec_key.dsize, rec_key.dptr);
+                       /* skip: do nothing + break */
+                       break;
+
+               } else if (normalize_path(key, ctx->sep)) {
+                       printf("Unnormal key: \"%.*s\"\n",
+                              (int)rec_key.dsize, rec_key.dptr);
+                       printf("Normalize to: \"%s\"\n", key);
+                       invalid_path = true;
+               } else if (srprs_path(&pos, NULL,
+                                     ctx->sep, &path))
+               {
+                       read_subkeys(ctx, path, val, invalid_path);
+                       break;
+               } else if (srprs_path(&pos, REG_VALUE_PREFIX,
+                                     ctx->sep, &path))
+               {
+                       read_values(ctx, path, val);
+                       break;
+               } else if (srprs_path(&pos, REG_SECDESC_PREFIX,
+                                     ctx->sep, &path))
+               {
+                       read_sd(ctx, path, val);
+                       break;
+               } else if (srprs_path(&pos, REG_SORTED_SUBKEYS_PREFIX,
+                                     ctx->sep, &path))
+               {
+                       if (!read_sorted(ctx, path, val)) {
+                               /* delete: mark invalid + break */
+                               printf("Invalid sorted subkeys for: \"%s\"\n", path);
+                               invalid_path = true;
+                               key = NULL;
+                       }
+                       break;
+               } else {
+                       printf("Unrecognized key: \"%.*s\"\n",
+                              (int)rec_key.dsize, rec_key.dptr);
+                       invalid_path = true;
+               }
+
+               if (invalid_path) {
+                       int action;
+                       if (ctx->opt.output == NULL) {
+                               action = 's';
+                       } else if (ctx->opt.automatic) {
+                               action = (ctx->default_action == 'r') ? 'd' : 'r';
+                       } else if (ctx->auto_action != '\0') {
+                               action = ctx->auto_action;
+                       } else {
+                               action = interact_prompt("[s]kip,[S]kip all,"
+                                                        "[d]elete,[D]elete all"
+                                                        ",[e]dit,[r]etry"
+                                                        , "sder",
+                                                        ctx->default_action);
+                       }
+                       if (isupper(action)) {
+                               action = tolower(action);
+                               ctx->auto_action = action;
+                       }
+                       ctx->default_action = action;
+                       switch (action) {
+                       case 's': /* skip */
+                               invalid_path = false;
+                               break;
+                       case 'd': /* delete */
+                               invalid_path = true;
+                               key = NULL;
+                               break;
+                       case 'e': /* edit */ {
+                               char *p = interact_edit(frame, key);
+                               if (p) {
+                                       talloc_free(key);
+                                       key = p;
+                               }
+                       } /* fall through */
+                       case 'r': /* retry */
+                               once_more = true;
+                               break;
+                       }
+               }
+       } while (once_more);
+
+       if (invalid_path) {
+               dbwrap_store(ctx->del, rec_key, string_term_tdb_data(key), 0);
+       }
+
+       talloc_free(frame);
+       return 0;
+}
+
+static bool get_version(struct check_ctx *ctx) {
+       static const uint32_t curr_version = REGDB_CODE_VERSION;
+       uint32_t version = ctx->opt.version ? ctx->opt.version : curr_version;
+       uint32_t info_version = 0;
+       NTSTATUS status;
+
+       status = dbwrap_fetch_uint32(ctx->idb, "INFO/version", &info_version);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("Warning: no INFO/version found!\n");
+               /* info_version = guess_version(ctx); */
+       }
+
+       if (ctx->opt.version) {
+               version = ctx->opt.version;
+       } else if (ctx->opt.implicit_db) {
+               version = curr_version;
+       } else {
+               version = info_version;
+       }
+
+       if (!version) {
+               printf("Couldn't determine registry format version, "
+                      "specify with --version\n");
+               return false;
+       }
+
+
+       if ( version != info_version ) {
+               if (ctx->opt.force || !ctx->opt.repair) {
+                       printf("Warning: overwrite registry format "
+                              "version %d with %d\n", info_version, version);
+               } else {
+                       printf("Warning: found registry format version %d but "
+                              "expected %d\n", info_version, version);
+                       return false;
+               }
+       }
+
+       ctx->version = version;
+       ctx->sep = (version > 1) ? '\\' : '/';
+
+       return true;
+}
+
+static bool
+dbwrap_store_verbose(struct db_context *db, const char *key, TDB_DATA nval)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+       TDB_DATA oval;
+       NTSTATUS status;
+
+       status = dbwrap_fetch_bystring(db, mem_ctx, key, &oval);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf ("store %s failed to fetch old value: %s\n", key,
+                       nt_errstr(status));
+               goto done;
+       }
+
+       if (!tdb_data_is_empty(oval) && !tdb_data_equal(nval, oval)) {
+               printf("store %s:\n"
+                      "  overwrite: %s\n"
+                      "  with:      %s\n",
+                      key, tdb_data_print(mem_ctx, oval),
+                      tdb_data_print(mem_ctx, nval));
+       }
+
+       status = dbwrap_store_bystring(db, key, nval, 0);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf ("store %s failed: %s\n", key, nt_errstr(status));
+       }
+
+done:
+       talloc_free(mem_ctx);
+       return NT_STATUS_IS_OK(status);
+}
+
+
+static int cmp_keynames(char **p1, char **p2)
+{
+       return strcasecmp_m(*p1, *p2);
+}
+
+static bool
+write_subkeylist(struct db_context *db, struct regkey *key, char sep)
+{
+       cbuf *buf = cbuf_new(talloc_tos());
+       int i;
+       bool ret;
+
+       cbuf_putdw(buf, key->nsubkeys);
+
+       for (i=0; i < key->nsubkeys; i++) {
+               struct regkey *subkey = key->subkeys[i];
+               const char *name = subkey->name;
+               if (name == NULL) {
+                       printf("Warning: no explicite name for key %s\n",
+                              subkey->path);
+                       name = strrchr_m(subkey->path, sep);
+                       assert(name);
+                       name ++;
+               }
+               cbuf_puts(buf, name, -1);
+               cbuf_putc(buf, '\0');
+       }
+
+       ret = dbwrap_store_verbose(db, key->path, cbuf_make_tdb_data(buf));
+
+       talloc_free(buf);
+       return ret;
+}
+
+static bool write_sorted(struct db_context *db, struct regkey *key, char sep)
+{
+       cbuf *buf = cbuf_new(talloc_tos());
+       char *path;
+       int i;
+       bool ret = false;
+       char **sorted = talloc_zero_array(buf, char*, key->nsubkeys);
+       int offset =  (1 + key->nsubkeys) * sizeof(uint32_t);
+
+       for (i=0; i < key->nsubkeys; i++) {
+               sorted[i] = talloc_strdup_upper(sorted, key->subkeys[i]->name);
+       }
+       TYPESAFE_QSORT(sorted, key->nsubkeys, cmp_keynames);
+
+       cbuf_putdw(buf, key->nsubkeys);
+       for (i=0; i < key->nsubkeys; i++) {
+               cbuf_putdw(buf, offset);
+               offset += strlen(sorted[i]) + 1;
+       }
+       for (i=0; i < key->nsubkeys; i++) {
+               cbuf_puts(buf, sorted[i], -1);
+               cbuf_putc(buf, '\0');
+       }
+
+       path = talloc_asprintf(buf, "%s%c%s", REG_SORTED_SUBKEYS_PREFIX, sep,
+                              key->path);
+       if (path == NULL) {
+               DEBUG(0, ("Out of memory!\n"));
+               goto done;
+       }
+
+       ret = dbwrap_store_verbose(db, path, cbuf_make_tdb_data(buf));
+done:
+       talloc_free(buf);
+       return ret;
+}
+
+static bool write_values(struct db_context *db, struct regkey *key, char sep)
+{
+       cbuf *buf = cbuf_new(talloc_tos());
+       char *path;
+       int i;
+       bool ret = false;
+
+       cbuf_putdw(buf, key->nvalues);
+       for (i=0; i < key->nvalues; i++) {
+               struct regval *val = key->values[i];
+               cbuf_puts(buf, val->name, -1);
+               cbuf_putc(buf, '\0');
+               cbuf_putdw(buf, val->type);
+               cbuf_putdw(buf, val->data.length);
+               cbuf_puts(buf, (void*)val->data.data, val->data.length);
+       }
+
+       path = talloc_asprintf(buf, "%s%c%s", REG_VALUE_PREFIX, sep, key->path);
+       if (path == NULL) {
+               DEBUG(0, ("Out of memory!\n"));
+               goto done;
+       }
+
+       ret = dbwrap_store_verbose(db, path, cbuf_make_tdb_data(buf));
+done:
+       talloc_free(buf);
+       return ret;
+}
+
+static bool write_sd(struct db_context *db, struct regkey *key, char sep)
+{
+       TDB_DATA sd;
+       NTSTATUS status;
+       char *path;
+       bool ret = false;
+       TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+
+       status = marshall_sec_desc(mem_ctx, key->sd, &sd.dptr, &sd.dsize);
+       if (!NT_STATUS_IS_OK(status)) {
+               printf("marshall sec desc %s failed: %s\n",
+                      key->path, nt_errstr(status));
+               goto done;
+       }
+       path = talloc_asprintf(mem_ctx, "%s%c%s", REG_SECDESC_PREFIX,
+                              sep, key->path);
+       if (path == NULL) {
+               DEBUG(0, ("Out of memory!\n"));
+               goto done;
+       }
+
+       ret = dbwrap_store_verbose(db, path, sd);
+done:
+       talloc_free(mem_ctx);
+       return ret;
+}
+
+
+static int check_write_db_action(struct db_record *rec, void *check_ctx)
+{
+       struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+       TDB_DATA rec_val = dbwrap_record_get_value(rec);
+       struct regkey *key = *(struct regkey**)rec_val.dptr;
+       TALLOC_CTX *frame = talloc_stackframe();
+
+       /* write subkeylist */
+       if ((ctx->version > 2) || (key->nsubkeys > 0) || (key->has_subkeylist)) {
+               write_subkeylist(ctx->odb, key, ctx->sep);
+       }
+
+       /* write sorted subkeys */
+       if ((ctx->version < 3) && (key->nsubkeys > 0)) {
+               write_sorted(ctx->odb, key, ctx->sep);
+       }
+
+       /* write value list */
+       if (key->nvalues > 0) {
+               write_values(ctx->odb, key, ctx->sep);
+       }
+
+       /* write sd */
+       if (key->sd) {
+               write_sd(ctx->odb, key, ctx->sep);
+       }
+
+       talloc_free(frame);
+       return 0;
+}
+
+static int fix_tree_action(struct db_record *rec, void *check_ctx)
+{
+       struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+       TDB_DATA rec_key = dbwrap_record_get_key(rec);
+       TDB_DATA rec_val = dbwrap_record_get_value(rec);
+       struct regkey* key = *(struct regkey**)rec_val.dptr;
+       if (ctx->opt.verbose) {
+               printf("Check Tree: %s\n", key->path);
+       }
+
+       assert (strncmp(key->path, (char*)rec_key.dptr, rec_key.dsize) == 0);
+
+       /* assert(dbwrap_exists(ctx->db, string_term_tdb_data(key->path)) */
+       /*        == key->exists); */
+
+       if (key->needs_update) {
+               printf("Update key: \"%s\"\n", key->path);
+               if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+                       write_subkeylist(ctx->odb, key, ctx->sep);
+               }
+               if ((ctx->version <= 2) && (key->nsubkeys > 0)) {
+                       write_sorted(ctx->odb, key, ctx->sep);
+               }
+               if (key->nvalues > 0) {
+                       write_values(ctx->odb, key, ctx->sep);
+               }
+               if (key->sd) {
+                       write_sd(ctx->odb, key, ctx->sep);
+               }
+       } else if (!key->has_subkeylist) {
+               if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+                       printf("Missing subkeylist: %s\n", key->path);
+                       write_subkeylist(ctx->odb, key, ctx->sep);
+               }
+       }
+
+       if (key->name == NULL && key->parent->has_subkeylist) {
+               printf("Key not referenced by the its parents subkeylist: %s\n",
+                      key->path);
+               write_subkeylist(ctx->odb, key->parent, ctx->sep);
+       }
+
+/* XXX check that upcase(name) matches last part of path ??? */
+
+       return 0;
+}
+
+
+/* give the same warnings as fix_tree_action */
+static int check_tree_action(struct db_record *rec, void *check_ctx)
+{
+       struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+       TDB_DATA rec_key = dbwrap_record_get_key(rec);
+       TDB_DATA rec_val = dbwrap_record_get_value(rec);
+       struct regkey* key = *(struct regkey**)rec_val.dptr;
+       if (ctx->opt.verbose) {
+               printf("Check Tree: %s\n", key->path);
+       }
+
+       assert (strncmp(key->path, (char*)rec_key.dptr, rec_key.dsize) == 0);
+
+       if (!key->has_subkeylist) {
+               if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+                       printf("Missing subkeylist: %s\n", key->path);
+               }
+       }
+
+       if (key->name == NULL && key->parent->has_subkeylist) {
+               printf("Key not referenced by the its parents subkeylist: %s\n",
+                      key->path);
+       }
+
+       return 0;
+}
+
+static int delete_invalid_action(struct db_record *rec, void* check_ctx)
+{
+       NTSTATUS status;
+       struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+       TDB_DATA rec_key = dbwrap_record_get_key(rec);
+       TDB_DATA rec_val = dbwrap_record_get_value(rec);
+
+
+       printf("Delete key: \"%.*s\"",(int)rec_key.dsize, rec_key.dptr);
+       if (rec_val.dsize > 0) {
+               printf(" in favour of \"%s\"\n", rec_val.dptr);
+       } else {
+               putc('\n', stdout);
+       }
+
+       status = dbwrap_delete(ctx->odb, rec_key);
+       if (!NT_STATUS_IS_OK(status)) {
+               d_printf("delete key \"%.*s\" failed!\n",
+                        (int)rec_key.dsize, rec_key.dptr);
+               return -1;
+       }
+       return 0;
+}
+
+static bool check_ctx_check_tree(struct check_ctx *ctx) {
+       NTSTATUS status;
+
+       status = dbwrap_traverse(ctx->reg, check_tree_action, ctx, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("check traverse failed: %s\n",
+                         nt_errstr(status)));
+               return false;
+       }
+       return true;
+}
+static bool check_ctx_fix_inplace(struct check_ctx *ctx) {
+       NTSTATUS status;
+       status = dbwrap_traverse(ctx->reg, fix_tree_action, ctx, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("fix traverse failed: %s\n",
+                         nt_errstr(status)));
+               return false;
+       }
+
+       status = dbwrap_traverse(ctx->del, delete_invalid_action, ctx, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("delete traverse failed: %s\n",
+                         nt_errstr(status)));
+               return false;
+       }
+       return true;
+}
+
+static bool check_ctx_write_new_db(struct check_ctx *ctx) {
+       NTSTATUS status;
+
+       assert(ctx->odb);
+
+       if (ctx->opt.wipe) {
+               int ret = dbwrap_wipe(ctx->odb);
+               if (ret != 0) {
+                       DEBUG(0, ("wiping %s failed\n",
+                                 ctx->opt.output));
+                       return false;
+               }
+       }
+
+       status = dbwrap_traverse(ctx->reg, check_write_db_action, ctx, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("traverse2 failed: %s\n", nt_errstr(status)));
+               return false;
+       }
+
+       status = dbwrap_store_uint32(ctx->odb,
+                                    "INFO/version", ctx->version);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("write version failed: %s\n", nt_errstr(status)));
+               return false;
+       }
+       return true;
+}
+
+int net_registry_check_db(const char *name, const struct check_options *opt)
+{
+       NTSTATUS status;
+       int ret = -1;
+       struct check_ctx *ctx = check_ctx_create(talloc_tos(), name, opt);
+       if (ctx==NULL) {
+               goto done;
+       }
+
+       d_printf("Check database: %s\n", name);
+
+       /* 1. open output RW */
+       if (!check_ctx_open_output(ctx)) {
+               goto done;
+       }
+
+       /* 2. open input RO */
+       if (!check_ctx_open_input(ctx)) {
+               goto done;
+       }
+
+       if (opt->lock && !check_ctx_transaction_start(ctx)) {
+               goto done;
+       }
+
+       if (!get_version(ctx)) {
+               goto done;
+       }
+
+       status = dbwrap_traverse_read(ctx->idb, check_tdb_action, ctx, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("check traverse failed: %s\n", nt_errstr(status)));
+               goto done;
+       }
+
+       if (!opt->lock && !check_ctx_transaction_start(ctx)) {
+               goto done;
+       }
+
+       if (ctx->opt.repair && !ctx->opt.wipe) {
+               if (!check_ctx_fix_inplace(ctx)) {
+                       goto done;
+               }
+       } else {
+               if (!check_ctx_check_tree(ctx)) {
+                       goto done;
+               }
+               if (ctx->odb) {
+                       if (!check_ctx_write_new_db(ctx)) {
+                               goto done;
+                       }
+               }
+       }
+       ret = 0;
+done:
+       check_ctx_transaction_stop(ctx, ret == 0);
+
+       talloc_free(ctx);
+       return ret;
+}
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/net_registry_check.h b/source3/utils/net_registry_check.h
new file mode 100644 (file)
index 0000000..6ed68d8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * 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/>.
+ */
+
+/**
+ * @brief  Check the registry database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date   Jun 2011
+ */
+
+#ifndef NET_REGISTRY_CHECK_H
+#define NET_REGISTRY_CHECK_H
+
+#include <stdbool.h>
+
+struct net_context;
+
+struct check_options {
+       bool test;
+       bool verbose;
+       bool lock;
+       bool automatic;
+       bool force;
+       bool repair;
+       int version;
+       const char *output;
+       bool wipe;
+       bool implicit_db;
+};
+
+int net_registry_check_db(const char* db, const struct check_options* opts);
+
+#endif /* NET_REGISTRY_CHECK_H */
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
index a72a12d..8f3e506 100755 (executable)
@@ -529,6 +529,7 @@ NET_SRC1 = '''utils/net.c utils/net_ads.c utils/net_help.c
               utils/net_rpc_join.c utils/net_time.c utils/net_lookup.c
               utils/net_cache.c utils/net_groupmap.c
               utils/net_idmap.c utils/net_idmap_check.c
+              utils/interact.c
               utils/net_status.c utils/net_rpc_printer.c utils/net_rpc_rights.c
               utils/net_rpc_service.c utils/net_rpc_registry.c utils/net_usershare.c
               utils/netlookup.c utils/net_sam.c utils/net_rpc_shell.c
@@ -536,6 +537,7 @@ NET_SRC1 = '''utils/net.c utils/net_ads.c utils/net_help.c
               utils/net_dns.c utils/net_ads_gpo.c
               utils/net_conf.c utils/net_join.c utils/net_user.c
               utils/net_group.c utils/net_file.c utils/net_registry.c
+              utils/net_registry_check.c
               utils/net_dom.c utils/net_share.c
               utils/net_g_lock.c
               utils/net_serverid.c