s3:idmap_tdb2: add allocation of new mappings to idmap_tdb2_sids_to_unixids
authorMichael Adam <obnox@samba.org>
Thu, 29 Jul 2010 21:13:54 +0000 (23:13 +0200)
committerMichael Adam <obnox@samba.org>
Sat, 14 Aug 2010 00:10:42 +0000 (02:10 +0200)
This moves the new_mapping feature inside the tdb2 backend to make creations
of mappings atomic.

Note: The new internal function idmap_tdb2_get_new_id() that is used to allocate
a new unix id is prepared to function for multiple explicitly configured idmap
domains, but currently it does only work for the default domain. The extended
allocation support requires extension of the data base format to store multiple
counters (per domain). This will be added in a later step (TODO!).

source3/winbindd/idmap_tdb2.c

index 0adf0f236c3f80bc7508f2f70e9a407d5cda0c84..1db880a48b14bf0e104031f88c4a2130ef7ef3ef 100644 (file)
@@ -48,6 +48,8 @@ static struct idmap_tdb2_state {
 } idmap_tdb2_state;
 
 
+static NTSTATUS idmap_tdb2_new_mapping(struct idmap_domain *dom,
+                                      struct id_map *map);
 
 /* handle to the permanent tdb */
 static struct db_context *idmap_tdb2;
@@ -285,6 +287,30 @@ static NTSTATUS idmap_tdb2_allocate_id(struct unixid *xid)
        return status;
 }
 
+/**
+ * Allocate a new unix-ID.
+ * For now this is for the default idmap domain only.
+ * Should be extended later on.
+ */
+static NTSTATUS idmap_tdb2_get_new_id(struct idmap_domain *dom,
+                                     struct unixid *id)
+{
+       NTSTATUS ret;
+
+       if (!strequal(dom->name, "*")) {
+               DEBUG(3, ("idmap_tdb2_get_new_id: "
+                         "Refusing creation of mapping for domain'%s'. "
+                         "Currently only supported for the default "
+                         "domain \"*\".\n",
+                          dom->name));
+               return NT_STATUS_NOT_IMPLEMENTED;
+       }
+
+       ret = idmap_tdb2_allocate_id(id);
+
+       return ret;
+}
+
 /*
   IDMAP MAPPING TDB BACKEND
 */
@@ -744,42 +770,97 @@ done:
 /*
   lookup a set of sids. 
 */
-static NTSTATUS idmap_tdb2_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+
+struct idmap_tdb2_sids_to_unixids_context {
+       struct idmap_domain *dom;
+       struct id_map **ids;
+       bool allocate_unmapped;
+};
+
+static NTSTATUS idmap_tdb2_sids_to_unixids_action(struct db_context *db,
+                                                 void *private_data)
 {
-       struct idmap_tdb2_context *ctx;
-       NTSTATUS ret;
+       struct idmap_tdb2_sids_to_unixids_context *state;
        int i;
+       struct idmap_tdb2_context *ctx;
+       NTSTATUS ret = NT_STATUS_OK;
 
-       /* initialize the status to avoid suprise */
-       for (i = 0; ids[i]; i++) {
-               ids[i]->status = ID_UNKNOWN;
-       }
+       state = (struct idmap_tdb2_sids_to_unixids_context *)private_data;
 
-       ctx = talloc_get_type(dom->private_data, struct idmap_tdb2_context);
+       DEBUG(10, ("idmap_tdb2_sids_to_unixids_action: "
+                  " domain: [%s], allocate: %s\n",
+                  state->dom->name,
+                  state->allocate_unmapped ? "yes" : "no"));
 
-       for (i = 0; ids[i]; i++) {
-               ret = idmap_tdb2_sid_to_id(ctx, ids[i]);
-               if ( ! NT_STATUS_IS_OK(ret)) {
+       ctx = talloc_get_type(state->dom->private_data,
+                             struct idmap_tdb2_context);
 
-                       /* if it is just a failed mapping continue */
-                       if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
 
-                               /* make sure it is marked as unmapped */
-                               ids[i]->status = ID_UNMAPPED;
-                               continue;
+       for (i = 0; state->ids[i]; i++) {
+               if ((state->ids[i]->status == ID_UNKNOWN) ||
+                   /* retry if we could not map in previous run: */
+                   (state->ids[i]->status == ID_UNMAPPED))
+               {
+                       NTSTATUS ret2;
+
+                       ret2 = idmap_tdb2_sid_to_id(ctx, state->ids[i]);
+                       if (!NT_STATUS_IS_OK(ret2)) {
+
+                               /* if it is just a failed mapping, continue */
+                               if (NT_STATUS_EQUAL(ret2, NT_STATUS_NONE_MAPPED)) {
+
+                                       /* make sure it is marked as unmapped */
+                                       state->ids[i]->status = ID_UNMAPPED;
+                                       ret = STATUS_SOME_UNMAPPED;
+                               } else {
+                                       /* some fatal error occurred, return immediately */
+                                       ret = ret2;
+                                       goto done;
+                               }
+                       } else {
+                               /* all ok, id is mapped */
+                               state->ids[i]->status = ID_MAPPED;
                        }
+               }
 
-                       /* some fatal error occurred, return immediately */
-                       goto done;
+               if ((state->ids[i]->status == ID_UNMAPPED) &&
+                   state->allocate_unmapped)
+               {
+                       ret = idmap_tdb2_new_mapping(state->dom, state->ids[i]);
+                       if (!NT_STATUS_IS_OK(ret)) {
+                               goto done;
+                       }
                }
+       }
 
-               /* all ok, id is mapped */
-               ids[i]->status = ID_MAPPED;
+done:
+       return ret;
+}
+
+static NTSTATUS idmap_tdb2_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+       NTSTATUS ret;
+       int i;
+       struct idmap_tdb2_sids_to_unixids_context state;
+
+       /* initialize the status to avoid suprise */
+       for (i = 0; ids[i]; i++) {
+               ids[i]->status = ID_UNKNOWN;
        }
 
-       ret = NT_STATUS_OK;
+       state.dom = dom;
+       state.ids = ids;
+       state.allocate_unmapped = false;
+
+       ret = idmap_tdb2_sids_to_unixids_action(idmap_tdb2, &state);
+
+       if (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) {
+               state.allocate_unmapped = true;
+               ret = dbwrap_trans_do(idmap_tdb2,
+                                     idmap_tdb2_sids_to_unixids_action,
+                                     &state);
+       }
 
-done:
        return ret;
 }
 
@@ -845,6 +926,76 @@ done:
        return ret;
 }
 
+/**
+ * Create a new mapping for an unmapped SID, also allocating a new ID.
+ * This should be run inside a transaction.
+ *
+ * TODO:
+*  Properly integrate this with multi domain idmap config:
+ * Currently, the allocator is default-config only.
+ */
+static NTSTATUS idmap_tdb2_new_mapping(struct idmap_domain *dom, struct id_map *map)
+{
+       NTSTATUS ret;
+       char *sidstr;
+       TDB_DATA data;
+       TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+       if (map == NULL) {
+               ret = NT_STATUS_INVALID_PARAMETER;
+               goto done;
+       }
+
+       if ((map->xid.type != ID_TYPE_UID) && (map->xid.type != ID_TYPE_GID)) {
+               ret = NT_STATUS_INVALID_PARAMETER;
+               goto done;
+       }
+
+       if (map->sid == NULL) {
+               ret = NT_STATUS_INVALID_PARAMETER;
+               goto done;
+       }
+
+       /* check wheter the SID is already mapped in the db */
+       sidstr = sid_string_talloc(mem_ctx, map->sid);
+       if (sidstr == NULL) {
+               DEBUG(0, ("Out of memory!\n"));
+               ret = NT_STATUS_NO_MEMORY;
+               goto done;
+       }
+
+       data = dbwrap_fetch_bystring(idmap_tdb2, mem_ctx, sidstr);
+       if (data.dptr) {
+               ret = NT_STATUS_OBJECT_NAME_COLLISION;
+               goto done;
+       }
+
+       /* unmapped - get a new id */
+       ret = idmap_tdb2_get_new_id(dom, &map->xid);
+       if (!NT_STATUS_IS_OK(ret)) {
+               DEBUG(3, ("Could not allocate id: %s\n", nt_errstr(ret)));
+               goto done;
+       }
+
+       DEBUG(10, ("Setting mapping: %s <-> %s %lu\n",
+                  sid_string_dbg(map->sid),
+                  (map->xid.type == ID_TYPE_UID) ? "UID" : "GID",
+                  (unsigned long)map->xid.id));
+
+       map->status = ID_MAPPED;
+
+       /* store the mapping */
+       ret = idmap_tdb2_set_mapping(dom, map);
+       if (!NT_STATUS_IS_OK(ret)) {
+               DEBUG(3, ("Could not store the new mapping: %s\n",
+                         nt_errstr(ret)));
+       }
+
+done:
+       talloc_free(mem_ctx);
+       return ret;
+}
+
 /*
   Close the idmap tdb instance
 */