s4:ldb Don't allow modifcation of distinguishedName
[ira/wip.git] / source4 / lib / ldb / ldb_tdb / ldb_tdb.c
index 24ec06ea32056cfe1d9ef2a9b5cbc34097bff326..7427b9816323e9a9981e538dfa3e9626bf2adcec 100644 (file)
@@ -254,7 +254,7 @@ static int ltdb_add_internal(struct ldb_module *module,
                             const struct ldb_message *msg)
 {
        struct ldb_context *ldb = ldb_module_get_ctx(module);
-       int ret;
+       int ret, i;
 
        ret = ltdb_check_special_dn(module, msg);
        if (ret != LDB_SUCCESS) {
@@ -265,6 +265,24 @@ static int ltdb_add_internal(struct ldb_module *module,
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
+       for (i=0;i<msg->num_elements;i++) {
+               struct ldb_message_element *el = &msg->elements[i];
+               const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(ldb, el->name);
+
+               if (el->num_values == 0) {
+                       ldb_asprintf_errstring(ldb, "attribute %s on %s specified, but with 0 values (illegal)", 
+                                              el->name, ldb_dn_get_linearized(msg->dn));
+                       return LDB_ERR_CONSTRAINT_VIOLATION;
+               }
+               if (a && a->flags & LDB_ATTR_FLAG_SINGLE_VALUE) {
+                       if (el->num_values > 1) {
+                               ldb_asprintf_errstring(ldb, "SINGLE-VALUE attribute %s on %s speicified more than once", 
+                                                      el->name, ldb_dn_get_linearized(msg->dn));
+                               return LDB_ERR_CONSTRAINT_VIOLATION;
+                       }
+               }
+       }
+
        ret = ltdb_store(module, msg, TDB_INSERT);
 
        if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
@@ -602,15 +620,34 @@ int ltdb_modify_internal(struct ldb_module *module,
                struct ldb_message_element *el2;
                struct ldb_val *vals;
                const char *dn;
+               const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(ldb, el->name);
 
-               switch (msg->elements[i].flags & LDB_FLAG_MOD_MASK) {
+               if (ldb_attr_cmp(el->name, "distinguishedName") == 0) {
+                       ldb_asprintf_errstring(ldb, "it is not permitted to perform a modify on distinguishedName (use rename instead): %s",
+                                              ldb_dn_get_linearized(msg->dn));
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               }
 
+               switch (msg->elements[i].flags & LDB_FLAG_MOD_MASK) {
                case LDB_FLAG_MOD_ADD:
+                       
                        /* add this element to the message. fail if it
                           already exists */
                        idx = find_element(msg2, el->name);
 
+                       if (el->num_values == 0) {
+                               ldb_asprintf_errstring(ldb, "attribute %s on %s speicified, but with 0 values (illigal)", 
+                                                 el->name, ldb_dn_get_linearized(msg->dn));
+                               return LDB_ERR_CONSTRAINT_VIOLATION;
+                       }
                        if (idx == -1) {
+                               if (a && a->flags & LDB_ATTR_FLAG_SINGLE_VALUE) {
+                                       if (el->num_values > 1) {
+                                               ldb_asprintf_errstring(ldb, "SINGLE-VALUE attribute %s on %s speicified more than once", 
+                                                              el->name, ldb_dn_get_linearized(msg->dn));
+                                               return LDB_ERR_CONSTRAINT_VIOLATION;
+                                       }
+                               }
                                if (msg_add_element(ldb, msg2, el) != 0) {
                                        ret = LDB_ERR_OTHER;
                                        goto failed;
@@ -618,6 +655,13 @@ int ltdb_modify_internal(struct ldb_module *module,
                                continue;
                        }
 
+                       /* If this is an add, then if it already
+                        * exists in the object, then we violoate the
+                        * single-value rule */
+                       if (a && a->flags & LDB_ATTR_FLAG_SINGLE_VALUE) {
+                               return LDB_ERR_CONSTRAINT_VIOLATION;
+                       }
+
                        el2 = &msg2->elements[idx];
 
                        /* An attribute with this name already exists,
@@ -657,6 +701,13 @@ int ltdb_modify_internal(struct ldb_module *module,
                        break;
 
                case LDB_FLAG_MOD_REPLACE:
+                       if (a && a->flags & LDB_ATTR_FLAG_SINGLE_VALUE) {
+                               if (el->num_values > 1) {
+                                       ldb_asprintf_errstring(ldb, "SINGLE-VALUE attribute %s on %s speicified more than once", 
+                                                              el->name, ldb_dn_get_linearized(msg->dn));
+                                       return LDB_ERR_CONSTRAINT_VIOLATION;
+                               }
+                       }
                        /* replace all elements of this attribute name with the elements
                           listed. The attribute not existing is not an error */
                        msg_delete_attribute(module, ldb, msg2, el->name);
@@ -805,37 +856,18 @@ static int ltdb_rename(struct ltdb_context *ctx)
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       if (ldb_dn_compare(req->op.rename.olddn, req->op.rename.newdn) == 0) {
-               /* The rename operation is apparently only changing case -
-                  the DNs are the same.  Delete the old DN before adding
-                  the new one to avoid a TDB_ERR_EXISTS error.
-
-                  The only drawback to this is that if the delete
-                  succeeds but the add fails, we rely on the
-                  transaction to roll this all back. */
-               tret = ltdb_delete_internal(module, req->op.rename.olddn);
-               if (tret != LDB_SUCCESS) {
-                       return tret;
-               }
-
-               tret = ltdb_add_internal(module, msg);
-               if (tret != LDB_SUCCESS) {
-                       return tret;
-               }
-       } else {
-               /* The rename operation is changing DNs.  Try to add the new
-                  DN first to avoid clobbering another DN not related to
-                  this rename operation. */
-               tret = ltdb_add_internal(module, msg);
-               if (tret != LDB_SUCCESS) {
-                       return tret;
-               }
+       /* Always delete first then add, to avoid conflicts with
+        * unique indexes. We rely on the transaction to make this
+        * atomic
+        */
+       tret = ltdb_delete_internal(module, req->op.rename.olddn);
+       if (tret != LDB_SUCCESS) {
+               return tret;
+       }
 
-               tret = ltdb_delete_internal(module, req->op.rename.olddn);
-               if (tret != LDB_SUCCESS) {
-                       ltdb_delete_internal(module, req->op.rename.newdn);
-                       return LDB_ERR_OPERATIONS_ERROR;
-               }
+       tret = ltdb_add_internal(module, msg);
+       if (tret != LDB_SUCCESS) {
+               return tret;
        }
 
        return LDB_SUCCESS;
@@ -857,18 +889,46 @@ static int ltdb_start_trans(struct ldb_module *module)
        return LDB_SUCCESS;
 }
 
-static int ltdb_end_trans(struct ldb_module *module)
+static int ltdb_prepare_commit(struct ldb_module *module)
 {
        void *data = ldb_module_get_private(module);
        struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
 
-       ltdb->in_transaction--;
+       if (ltdb->in_transaction != 1) {
+               return LDB_SUCCESS;
+       }
 
        if (ltdb_index_transaction_commit(module) != 0) {
                tdb_transaction_cancel(ltdb->tdb);
+               ltdb->in_transaction--;
+               return ltdb_err_map(tdb_error(ltdb->tdb));
+       }
+
+       if (tdb_transaction_prepare_commit(ltdb->tdb) != 0) {
+               ltdb->in_transaction--;
                return ltdb_err_map(tdb_error(ltdb->tdb));
        }
 
+       ltdb->prepared_commit = true;
+
+       return LDB_SUCCESS;
+}
+
+static int ltdb_end_trans(struct ldb_module *module)
+{
+       void *data = ldb_module_get_private(module);
+       struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+
+       if (!ltdb->prepared_commit) {
+               int ret = ltdb_prepare_commit(module);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+       }
+
+       ltdb->in_transaction--;
+       ltdb->prepared_commit = false;
+
        if (tdb_transaction_commit(ltdb->tdb) != 0) {
                return ltdb_err_map(tdb_error(ltdb->tdb));
        }
@@ -1019,7 +1079,16 @@ static void ltdb_timeout(struct tevent_context *ev,
        struct ltdb_context *ctx;
        ctx = talloc_get_type(private_data, struct ltdb_context);
 
-       ltdb_request_done(ctx, LDB_ERR_TIME_LIMIT_EXCEEDED);
+       if (!ctx->request_terminated) {
+               /* request is done now */
+               ltdb_request_done(ctx, LDB_ERR_TIME_LIMIT_EXCEEDED);
+       }
+
+       if (!ctx->request_terminated) {
+               /* neutralize the spy */
+               ctx->spy->ctx = NULL;
+       }
+       talloc_free(ctx);
 }
 
 static void ltdb_request_extended_done(struct ltdb_context *ctx,
@@ -1078,6 +1147,10 @@ static void ltdb_callback(struct tevent_context *ev,
 
        ctx = talloc_get_type(private_data, struct ltdb_context);
 
+       if (ctx->request_terminated) {
+               goto done;
+       }
+
        switch (ctx->req->operation) {
        case LDB_SEARCH:
                ret = ltdb_search(ctx);
@@ -1096,17 +1169,34 @@ static void ltdb_callback(struct tevent_context *ev,
                break;
        case LDB_EXTENDED:
                ltdb_handle_extended(ctx);
-               return;
+               goto done;
        default:
                /* no other op supported */
                ret = LDB_ERR_UNWILLING_TO_PERFORM;
        }
 
-       if (!ctx->callback_failed) {
-               /* Once we are done, we do not need timeout events */
-               talloc_free(ctx->timeout_event);
+       if (!ctx->request_terminated) {
+               /* request is done now */
                ltdb_request_done(ctx, ret);
        }
+
+done:
+       if (!ctx->request_terminated) {
+               /* neutralize the spy */
+               ctx->spy->ctx = NULL;
+       }
+       talloc_free(ctx);
+}
+
+static int ltdb_request_destructor(void *ptr)
+{
+       struct ltdb_req_spy *spy = talloc_get_type(ptr, struct ltdb_req_spy);
+
+       if (spy->ctx != NULL) {
+               spy->ctx->request_terminated = true;
+       }
+
+       return 0;
 }
 
 static int ltdb_handle_request(struct ldb_module *module,
@@ -1131,7 +1221,7 @@ static int ltdb_handle_request(struct ldb_module *module,
 
        ev = ldb_get_event_context(ldb);
 
-       ac = talloc_zero(req, struct ltdb_context);
+       ac = talloc_zero(ldb, struct ltdb_context);
        if (ac == NULL) {
                ldb_set_errstring(ldb, "Out of Memory");
                return LDB_ERR_OPERATIONS_ERROR;
@@ -1144,15 +1234,28 @@ static int ltdb_handle_request(struct ldb_module *module,
        tv.tv_usec = 0;
        te = tevent_add_timer(ev, ac, tv, ltdb_callback, ac);
        if (NULL == te) {
+               talloc_free(ac);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
        tv.tv_sec = req->starttime + req->timeout;
        ac->timeout_event = tevent_add_timer(ev, ac, tv, ltdb_timeout, ac);
        if (NULL == ac->timeout_event) {
+               talloc_free(ac);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
+       /* set a spy so that we do not try to use the request context
+        * if it is freed before ltdb_callback fires */
+       ac->spy = talloc(req, struct ltdb_req_spy);
+       if (NULL == ac->spy) {
+               talloc_free(ac);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       ac->spy->ctx = ac;
+
+       talloc_set_destructor((TALLOC_CTX *)ac->spy, ltdb_request_destructor);
+
        return LDB_SUCCESS;
 }
 
@@ -1166,6 +1269,7 @@ static const struct ldb_module_ops ltdb_ops = {
        .extended          = ltdb_handle_request,
        .start_transaction = ltdb_start_trans,
        .end_transaction   = ltdb_end_trans,
+       .prepare_commit    = ltdb_prepare_commit,
        .del_transaction   = ltdb_del_trans,
 };
 
@@ -1223,7 +1327,7 @@ static int ltdb_connect(struct ldb_context *ldb, const char *url,
                                   ldb_get_create_perms(ldb), ldb);
        if (!ltdb->tdb) {
                ldb_debug(ldb, LDB_DEBUG_ERROR,
-                         "Unable to open tdb '%s'\n", path);
+                         "Unable to open tdb '%s'", path);
                talloc_free(ltdb);
                return -1;
        }