s3:registry: make regdb_fetch_keys() static
[abartlet/samba.git/.git] / source3 / registry / reg_backend_db.c
index e22a73ad05779d1f979829b9db5108f8f46df427..3ac8d9459973f19281e95fcaa74254796b32e353 100644 (file)
@@ -26,6 +26,7 @@
 #include "registry.h"
 #include "reg_db.h"
 #include "reg_util_internal.h"
+#include "reg_parse_internal.h"
 #include "reg_backend_db.h"
 #include "reg_objects.h"
 #include "nt_printing.h"
@@ -71,7 +72,13 @@ static NTSTATUS regdb_trans_do_action(struct db_context *db, void *private_data)
        int32_t version_id;
        struct regdb_trans_ctx *ctx = (struct regdb_trans_ctx *)private_data;
 
-       version_id = dbwrap_fetch_int32(db, REGDB_VERSION_KEYNAME);
+       status = dbwrap_fetch_int32(db, REGDB_VERSION_KEYNAME, &version_id);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("ERROR: could not fetch registry db version: %s. "
+                         "Denying access.\n", nt_errstr(status)));
+               return NT_STATUS_ACCESS_DENIED;
+       }
 
        if (version_id != REGDB_CODE_VERSION) {
                DEBUG(0, ("ERROR: changed registry version %d found while "
@@ -415,46 +422,50 @@ static int regdb_normalize_keynames_fn(struct db_record *rec,
        TALLOC_CTX *mem_ctx = talloc_tos();
        const char *keyname;
        NTSTATUS status;
+       TDB_DATA key;
+       TDB_DATA value;
        struct db_context *db = (struct db_context *)private_data;
 
-       if (rec->key.dptr == NULL || rec->key.dsize == 0) {
+       key = dbwrap_record_get_key(rec);
+       if (key.dptr == NULL || key.dsize == 0) {
                return 0;
        }
 
+       value = dbwrap_record_get_value(rec);
+
        if (db == NULL) {
                DEBUG(0, ("regdb_normalize_keynames_fn: ERROR: "
                          "NULL db context handed in via private_data\n"));
                return 1;
        }
 
-       if (strncmp((const char *)rec->key.dptr, REGDB_VERSION_KEYNAME,
+       if (strncmp((const char *)key.dptr, REGDB_VERSION_KEYNAME,
            strlen(REGDB_VERSION_KEYNAME)) == 0)
        {
                return 0;
        }
 
-       keyname = strchr((const char *) rec->key.dptr, '/');
+       keyname = strchr((const char *)key.dptr, '/');
        if (keyname) {
                keyname = talloc_string_sub(mem_ctx,
-                                           (const char *) rec->key.dptr,
+                                           (const char *)key.dptr,
                                            "/",
                                            "\\");
 
                DEBUG(2, ("regdb_normalize_keynames_fn: Convert %s to %s\n",
-                         (const char *) rec->key.dptr,
+                         (const char *)key.dptr,
                          keyname));
 
                /* Delete the original record and store the normalized key */
-               status = rec->delete_rec(rec);
+               status = dbwrap_record_delete(rec);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0,("regdb_normalize_keynames_fn: "
                                 "tdb_delete for [%s] failed!\n",
-                                rec->key.dptr));
+                                (const char *)key.dptr));
                        return 1;
                }
 
-               status = dbwrap_store_bystring(db, keyname, rec->value,
-                                              TDB_REPLACE);
+               status = dbwrap_store_bystring(db, keyname, value, TDB_REPLACE);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0,("regdb_normalize_keynames_fn: "
                                 "failed to store new record for [%s]!\n",
@@ -488,36 +499,118 @@ static WERROR regdb_store_regdb_version(struct db_context *db, uint32_t version)
 static WERROR regdb_upgrade_v1_to_v2(struct db_context *db)
 {
        TALLOC_CTX *mem_ctx;
-       int rc;
+       NTSTATUS status;
        WERROR werr;
 
        mem_ctx = talloc_stackframe();
 
-       rc = db->traverse(db, regdb_normalize_keynames_fn, db);
+       status = dbwrap_traverse(db, regdb_normalize_keynames_fn, db, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
+               werr = WERR_REG_IO_FAILURE;
+               goto done;
+       }
+
+       werr = regdb_store_regdb_version(db, REGDB_VERSION_V2);
 
+done:
        talloc_free(mem_ctx);
+       return werr;
+}
 
-       if (rc < 0) {
-               return WERR_REG_IO_FAILURE;
+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_is_cstr(TDB_DATA d) {
+       if (tdb_data_is_empty(d) || (d.dptr[d.dsize-1] != '\0')) {
+               return false;
        }
+       return strchr((char *)d.dptr, '\0') == (char *)&d.dptr[d.dsize-1];
+}
 
-       werr = regdb_store_regdb_version(db, REGDB_VERSION_V2);
-       return werr;
+static bool upgrade_v2_to_v3_check_subkeylist(struct db_context *db,
+                                             const char *key,
+                                             const char *subkey)
+{
+       static uint32_t zero = 0;
+       static TDB_DATA empty_subkey_list = {
+               .dptr = (unsigned char*)&zero,
+               .dsize = sizeof(uint32_t),
+       };
+       bool success = false;
+       char *path = talloc_asprintf(talloc_tos(), "%s\\%s", key, subkey);
+       strupper_m(path);
+
+       if (!dbwrap_exists(db, string_term_tdb_data(path))) {
+               NTSTATUS status;
+
+               DEBUG(10, ("regdb_upgrade_v2_to_v3: writing subkey list [%s]\n",
+                          path));
+
+               status = dbwrap_store_bystring(db, path, empty_subkey_list,
+                                              TDB_INSERT);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(0, ("regdb_upgrade_v2_to_v3: writing subkey list "
+                                 "[%s] failed\n", path));
+                       goto done;
+               }
+       }
+       success = true;
+done:
+       talloc_free(path);
+       return success;
+}
+
+static bool upgrade_v2_to_v3_check_parent(struct db_context *db,
+                                         const char *key)
+{
+       const char *sep = strrchr_m(key, '\\');
+       if (sep != NULL) {
+               char *pkey = talloc_strndup(talloc_tos(), key, sep-key);
+               if (!dbwrap_exists(db, string_term_tdb_data(pkey))) {
+                       DEBUG(0, ("regdb_upgrade_v2_to_v3: missing subkey list "
+                                 "[%s]\nrun \"net registry check\"\n", pkey));
+               }
+               talloc_free(pkey);
+       }
+       return true;
 }
 
+
+#define IS_EQUAL(d,s) (((d).dsize == strlen(s)+1) &&   \
+                      (strcmp((char*)(d).dptr, (s)) == 0))
+#define STARTS_WITH(d,s) (((d).dsize > strlen(s)) &&                   \
+                         (strncmp((char*)(d).dptr, (s), strlen(s)) == 0))
+#define SSTR(d) (int)(d).dsize , (char*)(d).dptr
+
+
 static int regdb_upgrade_v2_to_v3_fn(struct db_record *rec, void *private_data)
 {
-       const char *keyname;
-       fstring subkeyname;
-       NTSTATUS status;
-       WERROR werr;
-       uint8_t *buf;
-       uint32_t buflen, len;
-       uint32_t num_items;
-       uint32_t i;
        struct db_context *db = (struct db_context *)private_data;
+       TDB_DATA key = dbwrap_record_get_key(rec);
+       TDB_DATA val = dbwrap_record_get_value(rec);
 
-       if (rec->key.dptr == NULL || rec->key.dsize == 0) {
+       if (tdb_data_is_empty(key)) {
                return 0;
        }
 
@@ -527,69 +620,73 @@ static int regdb_upgrade_v2_to_v3_fn(struct db_record *rec, void *private_data)
                return 1;
        }
 
-       keyname = (const char *)rec->key.dptr;
-
-       if (strncmp(keyname, REGDB_VERSION_KEYNAME,
-                   strlen(REGDB_VERSION_KEYNAME)) == 0)
+       if (IS_EQUAL(key, REGDB_VERSION_KEYNAME) ||
+           STARTS_WITH(key, REG_VALUE_PREFIX) ||
+           STARTS_WITH(key, REG_SECDESC_PREFIX))
        {
+               DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping [%.*s]\n",
+                          SSTR(key)));
                return 0;
        }
 
-       if (strncmp(keyname, REG_SORTED_SUBKEYS_PREFIX,
-                   strlen(REG_SORTED_SUBKEYS_PREFIX)) == 0)
-       {
+       if (STARTS_WITH(key, REG_SORTED_SUBKEYS_PREFIX)) {
+               NTSTATUS status;
                /* Delete the deprecated sorted subkeys cache. */
 
-               DEBUG(10, ("regdb_upgrade_v2_to_v3: deleting [%s]\n", keyname));
+               DEBUG(10, ("regdb_upgrade_v2_to_v3: deleting [%.*s]\n",
+                          SSTR(key)));
 
-               status = rec->delete_rec(rec);
+               status = dbwrap_record_delete(rec);
                if (!NT_STATUS_IS_OK(status)) {
-                       DEBUG(0, ("regdb_upgrade_v2_to_v3: tdb_delete for [%s] "
-                                 "failed!\n", keyname));
+                       DEBUG(0, ("regdb_upgrade_v2_to_v3: deleting [%.*s] "
+                                 "failed!\n", SSTR(key)));
                        return 1;
                }
 
                return 0;
        }
 
-       if (strncmp(keyname, REG_VALUE_PREFIX, strlen(REG_VALUE_PREFIX)) == 0) {
-               DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping [%s]\n", keyname));
-               return 0;
-       }
-
-       if (strncmp(keyname, REG_SECDESC_PREFIX,
-                   strlen(REG_SECDESC_PREFIX)) == 0)
+       if ( tdb_data_is_cstr(key) &&
+            hive_info((char*)key.dptr) != NULL )
        {
-               DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping [%s]\n", keyname));
-               return 0;
-       }
-
-       /*
-        * Found a regular subkey list record.
-        * Walk the list and create the list record for those
-        * subkeys that don't already have one.
-        */
-       DEBUG(10, ("regdb_upgrade_v2_to_v3: scanning subkey list of [%s]\n",
-                  keyname));
+               /*
+                * Found a regular subkey list record.
+                * Walk the list and create the list record for those
+                * subkeys that don't already have one.
+                */
+               TDB_DATA pos = val;
+               char *subkey, *path = (char*)key.dptr;
+               uint32_t num_items, found_items = 0;
+
+
+               DEBUG(10, ("regdb_upgrade_v2_to_v3: scanning subkeylist of "
+                          "[%s]\n", path));
+
+               if (!tdb_data_read_uint32(&pos, &num_items)) {
+                       /* invalid or empty - skip */
+                       return 0;
+               }
 
-       buf = rec->value.dptr;
-       buflen = rec->value.dsize;
+               while (tdb_data_read_cstr(&pos, &subkey)) {
+                       found_items++;
 
-       len = tdb_unpack(buf, buflen, "d", &num_items);
-       if (len == (uint32_t)-1) {
-               /* invalid or empty - skip */
-               return 0;
-       }
+                       if (!upgrade_v2_to_v3_check_subkeylist(db, path, subkey))
+                       {
+                               return 1;
+                       }
 
-       for (i=0; i<num_items; i++) {
-               len += tdb_unpack(buf+len, buflen-len, "f", subkeyname);
-               DEBUG(10, ("regdb_upgrade_v2_to_v3: "
-                          "writing subkey list for [%s\\%s]\n",
-                          keyname, subkeyname));
-               werr = regdb_store_subkey_list(db, keyname, subkeyname);
-               if (!W_ERROR_IS_OK(werr)) {
-                       return 1;
+                       if (!upgrade_v2_to_v3_check_parent(db, path)) {
+                               return 1;
+                       }
+               }
+               if (found_items != num_items) {
+                       DEBUG(0, ("regdb_upgrade_v2_to_v3: inconsistent subkey "
+                                 "list [%s]\nrun \"net registry check\"\n",
+                                 path));
                }
+       } else {
+               DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping invalid [%.*s]\n"
+                          "run \"net registry check\"\n", SSTR(key)));
        }
 
        return 0;
@@ -597,11 +694,11 @@ static int regdb_upgrade_v2_to_v3_fn(struct db_record *rec, void *private_data)
 
 static WERROR regdb_upgrade_v2_to_v3(struct db_context *db)
 {
-       int rc;
+       NTSTATUS status;
        WERROR werr;
 
-       rc = regdb->traverse(db, regdb_upgrade_v2_to_v3_fn, db);
-       if (rc < 0) {
+       status = dbwrap_traverse(db, regdb_upgrade_v2_to_v3_fn, db, NULL);
+       if (!NT_STATUS_IS_OK(status)) {
                werr = WERR_REG_IO_FAILURE;
                goto done;
        }
@@ -618,8 +715,9 @@ done:
 
 WERROR regdb_init(void)
 {
-       uint32 vers_id;
+       int32_t vers_id;
        WERROR werr;
+       NTSTATUS status;
 
        if (regdb) {
                DEBUG(10, ("regdb_init: incrementing refcount (%d->%d)\n",
@@ -629,10 +727,12 @@ WERROR regdb_init(void)
        }
 
        regdb = db_open(NULL, state_path("registry.tdb"), 0,
-                             REG_TDB_FLAGS, O_RDWR, 0600);
+                       REG_TDB_FLAGS, O_RDWR, 0600,
+                       DBWRAP_LOCK_ORDER_1);
        if (!regdb) {
                regdb = db_open(NULL, state_path("registry.tdb"), 0,
-                                     REG_TDB_FLAGS, O_RDWR|O_CREAT, 0600);
+                               REG_TDB_FLAGS, O_RDWR|O_CREAT, 0600,
+                               DBWRAP_LOCK_ORDER_1);
                if (!regdb) {
                        werr = ntstatus_to_werror(map_nt_error_from_unix(errno));
                        DEBUG(1,("regdb_init: Failed to open registry %s (%s)\n",
@@ -640,6 +740,13 @@ WERROR regdb_init(void)
                        return werr;
                }
 
+               werr = regdb_store_regdb_version(regdb, REGDB_CODE_VERSION);
+               if (!W_ERROR_IS_OK(werr)) {
+                       DEBUG(1, ("regdb_init: Failed to store version: %s\n",
+                                 win_errstr(werr)));
+                       return werr;
+               }
+
                DEBUG(10,("regdb_init: Successfully created registry tdb\n"));
        }
 
@@ -647,14 +754,32 @@ WERROR regdb_init(void)
        DEBUG(10, ("regdb_init: registry db openend. refcount reset (%d)\n",
                   regdb_refcount));
 
-       vers_id = dbwrap_fetch_int32(regdb, REGDB_VERSION_KEYNAME);
-       if (vers_id == -1) {
+       status = dbwrap_fetch_int32(regdb, REGDB_VERSION_KEYNAME, &vers_id);
+       if (!NT_STATUS_IS_OK(status)) {
                DEBUG(10, ("regdb_init: registry version uninitialized "
                           "(got %d), initializing to version %d\n",
-                          vers_id, REGDB_CODE_VERSION));
+                          vers_id, REGDB_VERSION_V1));
+
+               /*
+                * There was a regdb format version prior to version 1
+                * which did not store a INFO/version key. The format
+                * of this version was identical to version 1 except for
+                * the lack of the sorted subkey cache records.
+                * Since these are disposable, we can safely assume version
+                * 1 if no INFO/version key is found and run the db through
+                * the whole chain of upgrade. If the database was not
+                * initialized, this does not harm. If it was the unversioned
+                * version ("0"), then it do the right thing with the records.
+                */
+               werr = regdb_store_regdb_version(regdb, REGDB_VERSION_V1);
+               if (!W_ERROR_IS_OK(werr)) {
+                       return werr;
+               }
+               vers_id = REGDB_VERSION_V1;
+       }
 
-               werr = regdb_store_regdb_version(regdb, REGDB_CODE_VERSION);
-               return werr;
+       if (vers_id == REGDB_CODE_VERSION) {
+               return WERR_OK;
        }
 
        if (vers_id > REGDB_CODE_VERSION || vers_id == 0) {
@@ -664,7 +789,7 @@ WERROR regdb_init(void)
                return WERR_CAN_NOT_COMPLETE;
        }
 
-       if (regdb->transaction_start(regdb) != 0) {
+       if (dbwrap_transaction_start(regdb) != 0) {
                return WERR_REG_IO_FAILURE;
        }
 
@@ -674,7 +799,7 @@ WERROR regdb_init(void)
 
                werr = regdb_upgrade_v1_to_v2(regdb);
                if (!W_ERROR_IS_OK(werr)) {
-                       regdb->transaction_cancel(regdb);
+                       dbwrap_transaction_cancel(regdb);
                        return werr;
                }
 
@@ -687,7 +812,7 @@ WERROR regdb_init(void)
 
                werr = regdb_upgrade_v2_to_v3(regdb);
                if (!W_ERROR_IS_OK(werr)) {
-                       regdb->transaction_cancel(regdb);
+                       dbwrap_transaction_cancel(regdb);
                        return werr;
                }
 
@@ -696,7 +821,7 @@ WERROR regdb_init(void)
 
        /* future upgrade code should go here */
 
-       if (regdb->transaction_commit(regdb) != 0) {
+       if (dbwrap_transaction_commit(regdb) != 0) {
                return WERR_REG_IO_FAILURE;
        }
 
@@ -721,7 +846,8 @@ WERROR regdb_open( void )
        become_root();
 
        regdb = db_open(NULL, state_path("registry.tdb"), 0,
-                             REG_TDB_FLAGS, O_RDWR, 0600);
+                       REG_TDB_FLAGS, O_RDWR, 0600,
+                       DBWRAP_LOCK_ORDER_1);
        if ( !regdb ) {
                result = ntstatus_to_werror( map_nt_error_from_unix( errno ) );
                DEBUG(0,("regdb_open: Failed to open %s! (%s)\n",
@@ -762,19 +888,19 @@ int regdb_close( void )
 
 WERROR regdb_transaction_start(void)
 {
-       return (regdb->transaction_start(regdb) == 0) ?
+       return (dbwrap_transaction_start(regdb) == 0) ?
                WERR_OK : WERR_REG_IO_FAILURE;
 }
 
 WERROR regdb_transaction_commit(void)
 {
-       return (regdb->transaction_commit(regdb) == 0) ?
+       return (dbwrap_transaction_commit(regdb) == 0) ?
                WERR_OK : WERR_REG_IO_FAILURE;
 }
 
 WERROR regdb_transaction_cancel(void)
 {
-       return (regdb->transaction_cancel(regdb) == 0) ?
+       return (dbwrap_transaction_cancel(regdb) == 0) ?
                WERR_OK : WERR_REG_IO_FAILURE;
 }
 
@@ -785,7 +911,7 @@ WERROR regdb_transaction_cancel(void)
  ***********************************************************************/
 int regdb_get_seqnum(void)
 {
-       return regdb->get_seqnum(regdb);
+       return dbwrap_get_seqnum(regdb);
 }
 
 
@@ -1214,7 +1340,7 @@ done:
        return ret;
 }
 
-bool regdb_store_keys(const char *key, struct regsubkey_ctr *ctr)
+static bool regdb_store_keys(const char *key, struct regsubkey_ctr *ctr)
 {
        return regdb_store_keys_internal(regdb, key, ctr);
 }
@@ -1282,8 +1408,18 @@ static WERROR regdb_create_subkey_internal(struct db_context *db,
        W_ERROR_NOT_OK_GOTO_DONE(werr);
 
        if (regsubkey_ctr_key_exists(subkeys, subkey)) {
-               werr = WERR_OK;
-               goto done;
+               char *newkey;
+
+               newkey = talloc_asprintf(mem_ctx, "%s\\%s", key, subkey);
+               if (newkey == NULL) {
+                       werr = WERR_NOMEM;
+                       goto done;
+               }
+
+               if (regdb_key_exists(db, newkey)) {
+                       werr = WERR_OK;
+                       goto done;
+               }
        }
 
        talloc_free(subkeys);
@@ -1431,13 +1567,17 @@ static TDB_DATA regdb_fetch_key_internal(struct db_context *db,
 {
        char *path = NULL;
        TDB_DATA data;
+       NTSTATUS status;
 
        path = normalize_reg_path(mem_ctx, key);
        if (!path) {
                return make_tdb_data(NULL, 0);
        }
 
-       data = dbwrap_fetch_bystring(db, mem_ctx, path);
+       status = dbwrap_fetch_bystring(db, mem_ctx, path, &data);
+       if (!NT_STATUS_IS_OK(status)) {
+               data = tdb_null;
+       }
 
        TALLOC_FREE(path);
        return data;
@@ -1580,7 +1720,7 @@ static WERROR regdb_fetch_keys_internal(struct db_context *db, const char *key,
        werr = regsubkey_ctr_reinit(ctr);
        W_ERROR_NOT_OK_GOTO_DONE(werr);
 
-       werr = regsubkey_ctr_set_seqnum(ctr, db->get_seqnum(db));
+       werr = regsubkey_ctr_set_seqnum(ctr, dbwrap_get_seqnum(db));
        W_ERROR_NOT_OK_GOTO_DONE(werr);
 
        value = regdb_fetch_key_internal(db, frame, key);
@@ -1617,7 +1757,7 @@ done:
        return werr;
 }
 
-int regdb_fetch_keys(const char *key, struct regsubkey_ctr *ctr)
+static int regdb_fetch_keys(const char *key, struct regsubkey_ctr *ctr)
 {
        WERROR werr;
 
@@ -1729,7 +1869,7 @@ static int regdb_fetch_values_internal(struct db_context *db, const char* key,
                goto done;
        }
 
-       werr = regval_ctr_set_seqnum(values, db->get_seqnum(db));
+       werr = regval_ctr_set_seqnum(values, dbwrap_get_seqnum(db));
        W_ERROR_NOT_OK_GOTO_DONE(werr);
 
        value = regdb_fetch_key_internal(db, ctx, keystr);
@@ -1769,6 +1909,11 @@ static NTSTATUS regdb_store_values_internal(struct db_context *db,
                goto done;
        }
 
+       if (regval_ctr_numvals(values) == 0) {
+               WERROR werr = regdb_delete_values(db, key);
+               return werror_to_ntstatus(werr);
+       }
+
        ZERO_STRUCT(data);
 
        len = regdb_pack_values(values, data.dptr, data.dsize);
@@ -1796,9 +1941,10 @@ static NTSTATUS regdb_store_values_internal(struct db_context *db,
                goto done;
        }
 
-       old_data = dbwrap_fetch_bystring(db, ctx, keystr);
+       status = dbwrap_fetch_bystring(db, ctx, keystr, &old_data);
 
-       if ((old_data.dptr != NULL)
+       if (NT_STATUS_IS_OK(status)
+           && (old_data.dptr != NULL)
            && (old_data.dsize == data.dsize)
            && (memcmp(old_data.dptr, data.dptr, data.dsize) == 0))
        {
@@ -1871,8 +2017,8 @@ static WERROR regdb_get_secdesc(TALLOC_CTX *mem_ctx, const char *key,
                goto done;
        }
 
-       data = dbwrap_fetch_bystring(regdb, tmp_ctx, tdbkey);
-       if (data.dptr == NULL) {
+       status = dbwrap_fetch_bystring(regdb, tmp_ctx, tdbkey, &data);
+       if (!NT_STATUS_IS_OK(status)) {
                err = WERR_BADFILE;
                goto done;
        }