idmap_autorid: add idmap_autorid_delete_range_by_sid()
[mat/samba.git] / source3 / winbindd / idmap_autorid_tdb.c
index fbb8a7cff60ea7e94a2dc2483a739f1baf321bb9..18026fb900f69948fdf203504ffa88a33c7917bc 100644 (file)
  */
 
 #include "idmap_autorid_tdb.h"
+#include "../libcli/security/dom_sid.h"
 
-static NTSTATUS idmap_autorid_get_domainrange_action(struct db_context *db,
+/**
+ * Build the database keystring for getting a range
+ * belonging to a domain sid and a range index.
+ */
+static void idmap_autorid_build_keystr(const char *domsid,
+                                      uint32_t domain_range_index,
+                                      fstring keystr)
+{
+       if (domain_range_index > 0) {
+               fstr_sprintf(keystr, "%s#%"PRIu32,
+                            domsid, domain_range_index);
+       } else {
+               fstrcpy(keystr, domsid);
+       }
+}
+
+static char *idmap_autorid_build_keystr_talloc(TALLOC_CTX *mem_ctx,
+                                             const char *domsid,
+                                             uint32_t domain_range_index)
+{
+       char *keystr;
+
+       if (domain_range_index > 0) {
+               keystr = talloc_asprintf(mem_ctx, "%s#%"PRIu32, domsid,
+                                        domain_range_index);
+       } else {
+               keystr = talloc_strdup(mem_ctx, domsid);
+       }
+
+       return keystr;
+}
+
+
+static bool idmap_autorid_validate_sid(const char *sid)
+{
+       struct dom_sid ignore;
+       if (sid == NULL) {
+               return false;
+       }
+
+       if (strcmp(sid, ALLOC_RANGE) == 0) {
+               return true;
+       }
+
+       return dom_sid_parse(sid, &ignore);
+}
+
+struct idmap_autorid_addrange_ctx {
+       struct autorid_range_config *range;
+       bool acquire;
+};
+
+static NTSTATUS idmap_autorid_addrange_action(struct db_context *db,
                                              void *private_data)
 {
+       struct idmap_autorid_addrange_ctx *ctx;
+       uint32_t requested_rangenum, stored_rangenum;
+       struct autorid_range_config *range;
+       bool acquire;
        NTSTATUS ret;
-       uint32_t rangenum, hwm;
+       uint32_t hwm;
        char *numstr;
-       struct autorid_range_config *range;
+       struct autorid_global_config *globalcfg;
+       fstring keystr;
+       uint32_t increment;
+       TALLOC_CTX *mem_ctx = NULL;
+
+       ctx = (struct idmap_autorid_addrange_ctx *)private_data;
+       range = ctx->range;
+       acquire = ctx->acquire;
+       requested_rangenum = range->rangenum;
+
+       if (db == NULL) {
+               DEBUG(3, ("Invalid database argument: NULL"));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (range == NULL) {
+               DEBUG(3, ("Invalid range argument: NULL"));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
 
-       range = (struct autorid_range_config *)private_data;
+       DEBUG(10, ("Adding new range for domain %s "
+                  "(domain_range_index=%"PRIu32")\n",
+                  range->domsid, range->domain_range_index));
+
+       if (!idmap_autorid_validate_sid(range->domsid)) {
+               DEBUG(3, ("Invalid SID: %s\n", range->domsid));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
 
-       ret = dbwrap_fetch_uint32_bystring(db, range->keystr,
-                                          &(range->rangenum));
+       idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
+                                  keystr);
+
+       ret = dbwrap_fetch_uint32_bystring(db, keystr, &stored_rangenum);
 
        if (NT_STATUS_IS_OK(ret)) {
                /* entry is already present*/
-               return ret;
-       }
+               if (acquire) {
+                       DEBUG(10, ("domain range already allocated - "
+                                  "Not adding!\n"));
+                       return NT_STATUS_OK;
+               }
 
-       DEBUG(10, ("Acquiring new range for domain %s "
-                  "(domain_range_index=%"PRIu32")\n",
-                  range->domsid, range->domain_range_index));
+               if (stored_rangenum != requested_rangenum) {
+                       DEBUG(1, ("Error: requested rangenumber (%u) differs "
+                                 "from stored one (%u).\n",
+                                 requested_rangenum, stored_rangenum));
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+
+               DEBUG(10, ("Note: stored range agrees with requested "
+                          "one - ok\n"));
+               return NT_STATUS_OK;
+       }
 
        /* fetch the current HWM */
        ret = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
        if (!NT_STATUS_IS_OK(ret)) {
                DEBUG(1, ("Fatal error while fetching current "
                          "HWM value: %s\n", nt_errstr(ret)));
-               ret = NT_STATUS_INTERNAL_ERROR;
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       mem_ctx = talloc_stackframe();
+
+       ret = idmap_autorid_loadconfig(db, mem_ctx, &globalcfg);
+       if (!NT_STATUS_IS_OK(ret)) {
+               DEBUG(1, ("Fatal error while fetching configuration: %s\n",
+                         nt_errstr(ret)));
                goto error;
        }
 
-       /* do we have a range left? */
-       if (hwm >= range->globalcfg->maxranges) {
-               DEBUG(1, ("No more domain ranges available!\n"));
+       if (acquire) {
+               /*
+                * automatically acquire the next range
+                */
+               requested_rangenum = hwm;
+       }
+
+       if (requested_rangenum >= globalcfg->maxranges) {
+               DEBUG(1, ("Not enough ranges available: New range %u must be "
+                         "smaller than configured maximum number of ranges "
+                         "(%u).\n",
+                         requested_rangenum, globalcfg->maxranges));
                ret = NT_STATUS_NO_MEMORY;
                goto error;
        }
 
-       /* increase the HWM */
-       ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &rangenum, 1);
-       if (!NT_STATUS_IS_OK(ret)) {
-               DEBUG(1, ("Fatal error while fetching a new "
-                         "domain range value!\n"));
-               goto error;
+       if (requested_rangenum < hwm) {
+               /*
+                * Set a specified range below the HWM:
+                * We need to check that it is not yet taken.
+                */
+
+               numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum);
+               if (!numstr) {
+                       ret = NT_STATUS_NO_MEMORY;
+                       goto error;
+               }
+
+               if (dbwrap_exists(db, string_term_tdb_data(numstr))) {
+                       DEBUG(1, ("Requested range already in use.\n"));
+                       ret = NT_STATUS_INVALID_PARAMETER;
+                       goto error;
+               }
+
+               TALLOC_FREE(numstr);
+       } else {
+               /*
+                * requested or automatic range >= HWM:
+                * increment the HWM.
+                */
+
+               /* HWM always contains current max range + 1 */
+               increment = requested_rangenum + 1 - hwm;
+
+               /* increase the HWM */
+               ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &hwm,
+                                                          increment);
+               if (!NT_STATUS_IS_OK(ret)) {
+                       DEBUG(1, ("Fatal error while incrementing the HWM "
+                                 "value in the database: %s\n",
+                                 nt_errstr(ret)));
+                       goto error;
+               }
        }
 
-       /* store away the new mapping in both directions */
-       ret = dbwrap_store_uint32_bystring(db, range->keystr, rangenum);
+       /*
+        * store away the new mapping in both directions
+        */
+
+       ret = dbwrap_store_uint32_bystring(db, keystr, requested_rangenum);
        if (!NT_STATUS_IS_OK(ret)) {
                DEBUG(1, ("Fatal error while storing new "
-                         "domain->range assignment!\n"));
+                         "domain->range assignment: %s\n", nt_errstr(ret)));
                goto error;
        }
 
-       numstr = talloc_asprintf(db, "%u", rangenum);
+       numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum);
        if (!numstr) {
                ret = NT_STATUS_NO_MEMORY;
                goto error;
        }
 
        ret = dbwrap_store_bystring(db, numstr,
-                       string_term_tdb_data(range->keystr), TDB_INSERT);
+                       string_term_tdb_data(keystr), TDB_INSERT);
 
-       talloc_free(numstr);
        if (!NT_STATUS_IS_OK(ret)) {
-               DEBUG(1, ("Fatal error while storing "
-                         "new domain->range assignment!\n"));
+               DEBUG(1, ("Fatal error while storing new "
+                         "domain->range assignment: %s\n", nt_errstr(ret)));
                goto error;
        }
        DEBUG(5, ("Acquired new range #%d for domain %s "
-                 "(domain_range_index=%"PRIu32")\n", rangenum, range->keystr,
+                 "(domain_range_index=%"PRIu32")\n", requested_rangenum, keystr,
                  range->domain_range_index));
 
-       range->rangenum = rangenum;
+       range->rangenum = requested_rangenum;
 
-       return NT_STATUS_OK;
+       range->low_id = globalcfg->minvalue
+                     + range->rangenum * globalcfg->rangesize;
+
+       ret = NT_STATUS_OK;
 
 error:
+       talloc_free(mem_ctx);
        return ret;
+}
+
+static NTSTATUS idmap_autorid_addrange(struct db_context *db,
+                                      struct autorid_range_config *range,
+                                      bool acquire)
+{
+       NTSTATUS status;
+       struct idmap_autorid_addrange_ctx ctx;
+
+       ctx.acquire = acquire;
+       ctx.range = range;
+
+       status = dbwrap_trans_do(db, idmap_autorid_addrange_action, &ctx);
+       return status;
+}
+
+NTSTATUS idmap_autorid_setrange(struct db_context *db,
+                               const char *domsid,
+                               uint32_t domain_range_index,
+                               uint32_t rangenum)
+{
+       NTSTATUS status;
+       struct autorid_range_config range;
+
+       ZERO_STRUCT(range);
+       fstrcpy(range.domsid, domsid);
+       range.domain_range_index = domain_range_index;
+       range.rangenum = rangenum;
+
+       status = idmap_autorid_addrange(db, &range, false);
+       return status;
+}
+
+static NTSTATUS idmap_autorid_acquire_range(struct db_context *db,
+                                           struct autorid_range_config *range)
+{
+       return idmap_autorid_addrange(db, range, true);
+}
+
+static NTSTATUS idmap_autorid_getrange_int(struct db_context *db,
+                                          struct autorid_range_config *range)
+{
+       NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+       struct autorid_global_config *globalcfg = NULL;
+       fstring keystr;
+
+       if (db == NULL || range == NULL) {
+               DEBUG(3, ("Invalid arguments received\n"));
+               goto done;
+       }
+
+       idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
+                                  keystr);
+
+       DEBUG(10, ("reading domain range for key %s\n", keystr));
+       status = dbwrap_fetch_uint32_bystring(db, keystr, &(range->rangenum));
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Failed to read database for key '%s': %s\n",
+                         keystr, nt_errstr(status)));
+               goto done;
+       }
+
+       status = idmap_autorid_loadconfig(db, talloc_tos(), &globalcfg);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Failed to read global configuration"));
+               goto done;
+       }
+       range->low_id = globalcfg->minvalue
+                     + range->rangenum * globalcfg->rangesize;
+
+       TALLOC_FREE(globalcfg);
+done:
+       return status;
+}
+
+NTSTATUS idmap_autorid_getrange(struct db_context *db,
+                               const char *domsid,
+                               uint32_t domain_range_index,
+                               uint32_t *rangenum,
+                               uint32_t *low_id)
+{
+       NTSTATUS status;
+       struct autorid_range_config range;
 
+       if (rangenum == NULL) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       ZERO_STRUCT(range);
+       fstrcpy(range.domsid, domsid);
+       range.domain_range_index = domain_range_index;
+
+       status = idmap_autorid_getrange_int(db, &range);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       *rangenum = range.rangenum;
+
+       if (low_id != NULL) {
+               *low_id = range.low_id;
+       }
+
+       return NT_STATUS_OK;
 }
 
 NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
@@ -113,31 +365,14 @@ NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
 {
        NTSTATUS ret;
 
-       /*
-        * try to find mapping without locking the database,
-        * if it is not found create a mapping in a transaction unless
-        * read-only mode has been set
-        */
-       if (range->domain_range_index > 0) {
-               snprintf(range->keystr, FSTRING_LEN, "%s#%"PRIu32,
-                        range->domsid, range->domain_range_index);
-       } else {
-               fstrcpy(range->keystr, range->domsid);
-       }
-
-       ret = dbwrap_fetch_uint32_bystring(db, range->keystr,
-                                          &(range->rangenum));
-
+       ret = idmap_autorid_getrange_int(db, range);
        if (!NT_STATUS_IS_OK(ret)) {
                if (read_only) {
                        return NT_STATUS_NOT_FOUND;
                }
-               ret = dbwrap_trans_do(db,
-                             idmap_autorid_get_domainrange_action, range);
-       }
 
-       range->low_id = range->globalcfg->minvalue
-                     + range->rangenum * range->globalcfg->rangesize;
+               ret = idmap_autorid_acquire_range(db, range);
+       }
 
        DEBUG(10, ("Using range #%d for domain %s "
                   "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n",
@@ -171,6 +406,125 @@ NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm)
        return NT_STATUS_OK;
 }
 
+/*
+ * Delete a domain#index <-> range mapping from the database.
+ * The mapping is specified by the sid and index.
+ * If force == true, invalid mapping records are deleted as far
+ * as possible, otherwise they are left untouched.
+ */
+
+struct idmap_autorid_delete_range_by_sid_ctx {
+       const char *domsid;
+       uint32_t domain_range_index;
+       bool force;
+};
+
+static NTSTATUS idmap_autorid_delete_range_by_sid_action(struct db_context *db,
+                                                        void *private_data)
+{
+       struct idmap_autorid_delete_range_by_sid_ctx *ctx =
+               (struct idmap_autorid_delete_range_by_sid_ctx *)private_data;
+       const char *domsid;
+       uint32_t domain_range_index;
+       uint32_t rangenum;
+       char *keystr;
+       char *range_keystr;
+       TDB_DATA data;
+       NTSTATUS status;
+       TALLOC_CTX *frame = talloc_stackframe();
+       bool is_valid_range_mapping = true;
+       bool force;
+
+       domsid = ctx->domsid;
+       domain_range_index = ctx->domain_range_index;
+       force = ctx->force;
+
+       keystr = idmap_autorid_build_keystr_talloc(frame, domsid,
+                                                  domain_range_index);
+       if (keystr == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto done;
+       }
+
+       status = dbwrap_fetch_uint32_bystring(db, keystr, &rangenum);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto done;
+       }
+
+       range_keystr = talloc_asprintf(frame, "%"PRIu32, rangenum);
+       if (range_keystr == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto done;
+       }
+
+       status = dbwrap_fetch_bystring(db, frame, range_keystr, &data);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               DEBUG(1, ("Incomplete mapping %s -> %s: no backward mapping\n",
+                         keystr, range_keystr));
+               is_valid_range_mapping = false;
+       } else if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Error fetching reverse mapping for %s -> %s:  %s\n",
+                         keystr, range_keystr, nt_errstr(status)));
+               goto done;
+       } else if (strncmp((const char *)data.dptr, keystr, strlen(keystr))
+                  != 0)
+       {
+               DEBUG(1, ("Invalid mapping: %s -> %s -> %s\n",
+                         keystr, range_keystr, (const char *)data.dptr));
+               is_valid_range_mapping = false;
+       }
+
+       if (!is_valid_range_mapping && !force) {
+               DEBUG(10, ("Not deleting invalid mapping, since not in force "
+                          "mode.\n"));
+               status = NT_STATUS_FILE_INVALID;
+               goto done;
+       }
+
+       status = dbwrap_delete_bystring(db, keystr);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Deletion of '%s' failed: %s\n",
+                         keystr, nt_errstr(status)));
+               goto done;
+       }
+
+       if (!is_valid_range_mapping) {
+               goto done;
+       }
+
+       status = dbwrap_delete_bystring(db, range_keystr);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Deletion of '%s' failed: %s\n",
+                         range_keystr, nt_errstr(status)));
+               goto done;
+       }
+
+       DEBUG(10, ("Deleted range mapping %s <--> %s\n", keystr,
+                  range_keystr));
+
+done:
+       TALLOC_FREE(frame);
+       return status;
+}
+
+NTSTATUS idmap_autorid_delete_range_by_sid(struct db_context *db,
+                                          const char *domsid,
+                                          uint32_t domain_range_index,
+                                          bool force)
+{
+       NTSTATUS status;
+       struct idmap_autorid_delete_range_by_sid_ctx ctx;
+
+       ctx.domain_range_index = domain_range_index;
+       ctx.domsid = domsid;
+       ctx.force = force;
+
+       status = dbwrap_trans_do(db, idmap_autorid_delete_range_by_sid_action,
+                                &ctx);
+       return status;
+}
+
+
 /*
  * open and initialize the database which stores the ranges for the domains
  */
@@ -207,70 +561,215 @@ NTSTATUS idmap_autorid_db_init(const char *path,
        return status;
 }
 
-struct autorid_global_config *idmap_autorid_loadconfig(struct db_context *db,
-                                                      TALLOC_CTX *ctx)
+struct idmap_autorid_fetch_config_state {
+       TALLOC_CTX *mem_ctx;
+       char *configstr;
+};
+
+static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value,
+                                       void *private_data)
 {
+       struct idmap_autorid_fetch_config_state *state;
 
-       TDB_DATA data;
-       struct autorid_global_config *cfg;
-       unsigned long minvalue, rangesize, maxranges;
+       state = (struct idmap_autorid_fetch_config_state *)private_data;
+
+       /*
+        * strndup because we have non-nullterminated strings in the db
+        */
+       state->configstr = talloc_strndup(
+               state->mem_ctx, (const char *)value.dptr, value.dsize);
+}
+
+NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
+                                   char **result)
+{
+       TDB_DATA key;
        NTSTATUS status;
+       struct idmap_autorid_fetch_config_state state;
+
+       if (result == NULL) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
 
-       status = dbwrap_fetch_bystring(db, ctx, CONFIGKEY, &data);
+       key = string_term_tdb_data(CONFIGKEY);
 
+       state.mem_ctx = mem_ctx;
+       state.configstr = NULL;
+
+       status = dbwrap_parse_record(db, key, idmap_autorid_config_parser,
+                                    &state);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(10, ("No saved config found\n"));
-               return NULL;
+               DEBUG(1, ("Error while retrieving config: %s\n",
+                         nt_errstr(status)));
+               return status;
        }
 
-       cfg = talloc_zero(ctx, struct autorid_global_config);
-       if (!cfg) {
-               return NULL;
+       if (state.configstr == NULL) {
+               DEBUG(1, ("Error while retrieving config\n"));
+               return NT_STATUS_NO_MEMORY;
        }
 
-       if (sscanf((char *)data.dptr,
+       DEBUG(5, ("found CONFIG: %s\n", state.configstr));
+
+       *result = state.configstr;
+       return NT_STATUS_OK;
+}
+
+bool idmap_autorid_parse_configstr(const char *configstr,
+                                  struct autorid_global_config *cfg)
+{
+       unsigned long minvalue, rangesize, maxranges;
+
+       if (sscanf(configstr,
                   "minvalue:%lu rangesize:%lu maxranges:%lu",
                   &minvalue, &rangesize, &maxranges) != 3) {
                DEBUG(1,
                      ("Found invalid configuration data"
                       "creating new config\n"));
-               return NULL;
+               return false;
        }
 
        cfg->minvalue = minvalue;
        cfg->rangesize = rangesize;
        cfg->maxranges = maxranges;
 
+       return true;
+}
+
+NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
+                                 TALLOC_CTX *mem_ctx,
+                                 struct autorid_global_config **result)
+{
+       struct autorid_global_config *cfg;
+       NTSTATUS status;
+       bool ok;
+       char *configstr = NULL;
+
+       if (result == NULL) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       status = idmap_autorid_getconfigstr(db, mem_ctx, &configstr);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       cfg = talloc_zero(mem_ctx, struct autorid_global_config);
+       if (cfg == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ok = idmap_autorid_parse_configstr(configstr, cfg);
+       if (!ok) {
+               talloc_free(cfg);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
        DEBUG(10, ("Loaded previously stored configuration "
                   "minvalue:%d rangesize:%d\n",
                   cfg->minvalue, cfg->rangesize));
 
-       return cfg;
+       *result = cfg;
 
+       return NT_STATUS_OK;
 }
 
 NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
                                  struct autorid_global_config *cfg)
 {
 
-       NTSTATUS status;
+       struct autorid_global_config *storedconfig = NULL;
+       NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
        TDB_DATA data;
        char *cfgstr;
+       uint32_t hwm;
+       TALLOC_CTX *frame = talloc_stackframe();
+
+       DEBUG(10, ("New configuration provided for storing is "
+                  "minvalue:%d rangesize:%d maxranges:%d\n",
+                  cfg->minvalue, cfg->rangesize, cfg->maxranges));
+
+       if (cfg->rangesize < 2000) {
+               DEBUG(1, ("autorid rangesize must be at least 2000\n"));
+               goto done;
+       }
+
+       if (cfg->maxranges == 0) {
+               DEBUG(1, ("An autorid maxranges value of 0 is invalid. "
+                         "Must have at least one range available.\n"));
+               goto done;
+       }
+
+       status = idmap_autorid_loadconfig(db, frame, &storedconfig);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               DEBUG(5, ("No configuration found. Storing initial "
+                         "configuration.\n"));
+       } else if (!NT_STATUS_IS_OK(status)) {
+               goto done;
+       }
+
+       /* did the minimum value or rangesize change? */
+       if (storedconfig &&
+           ((storedconfig->minvalue != cfg->minvalue) ||
+            (storedconfig->rangesize != cfg->rangesize)))
+       {
+               DEBUG(1, ("New configuration values for rangesize or "
+                         "minimum uid value conflict with previously "
+                         "used values! Not storing new config.\n"));
+               status = NT_STATUS_INVALID_PARAMETER;
+               goto done;
+       }
+
+       status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Fatal error while fetching current "
+                         "HWM value: %s\n", nt_errstr(status)));
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto done;
+       }
+
+       /*
+        * has the highest uid value been reduced to setting that is not
+        * sufficient any more for already existing ranges?
+        */
+       if (hwm > cfg->maxranges) {
+               DEBUG(1, ("New upper uid limit is too low to cover "
+                         "existing mappings! Not storing new config.\n"));
+               status = NT_STATUS_INVALID_PARAMETER;
+               goto done;
+       }
 
        cfgstr =
-           talloc_asprintf(talloc_tos(),
+           talloc_asprintf(frame,
                            "minvalue:%u rangesize:%u maxranges:%u",
                            cfg->minvalue, cfg->rangesize, cfg->maxranges);
 
-       if (!cfgstr) {
-               return NT_STATUS_NO_MEMORY;
+       if (cfgstr == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto done;
        }
 
        data = string_tdb_data(cfgstr);
 
        status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE);
 
-       talloc_free(cfgstr);
+done:
+       TALLOC_FREE(frame);
+       return status;
+}
+
+NTSTATUS idmap_autorid_saveconfigstr(struct db_context *db,
+                                    const char *configstr)
+{
+       bool ok;
+       NTSTATUS status;
+       struct autorid_global_config cfg;
+
+       ok = idmap_autorid_parse_configstr(configstr, &cfg);
+       if (!ok) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
 
+       status = idmap_autorid_saveconfig(db, &cfg);
        return status;
 }