Blame SOURCES/0019-Ticket-50727-correct-mistaken-options-in-filter-vali.patch

8394b4
From 918df0a60a9cf1e3a836165f1044ea63c88bdd72 Mon Sep 17 00:00:00 2001
8394b4
From: William Brown <william@blackhats.net.au>
8394b4
Date: Fri, 6 Dec 2019 14:04:45 +1000
8394b4
Subject: [PATCH] Ticket 50727 - correct mistaken options in filter validation
8394b4
 patch
8394b4
8394b4
Bug Description: Because William of the past missed (forgot) to make
8394b4
some agreed upon changes, we shipped the feature for filter validation
8394b4
in a state that was a bit unclear for users.
8394b4
8394b4
Fix Description: Fix the options to now be clearer in what is
8394b4
expected/demaned from admins. We now have 4 possible states for
8394b4
the value of the config:
8394b4
8394b4
* reject-invalid  (prev on)
8394b4
* process-safe  (prev warn)
8394b4
* warn-invalid (new!)
8394b4
* off (prev off)
8394b4
8394b4
These behave as:
8394b4
8394b4
* reject-invalid - reject queries that contain unknown attributes
8394b4
* process-safe - log a notes=F that an attr is missing, and idl_alloc(0)
8394b4
  the missing attribute for RFC4511 compliance.
8394b4
* warn-invalid - log a notes=F that an attr is missing, and process
8394b4
  as ALLIDS (the legacy behaviour)
8394b4
* off - process as ALLIDs (the legacy behaviour)
8394b4
8394b4
The default is "process-safe".
8394b4
8394b4
https://pagure.io/389-ds-base/issue/50727
8394b4
8394b4
Author: William Brown <william@blackhats.net.au>
8394b4
8394b4
Review by: tbordaz, lkrispen (thanks)
8394b4
---
8394b4
 .../suites/filter/schema_validation_test.py   | 112 ++++++++++++++++--
8394b4
 ldap/servers/slapd/back-ldbm/filterindex.c    |  28 +++--
8394b4
 ldap/servers/slapd/libglobs.c                 |  78 +++++++-----
8394b4
 ldap/servers/slapd/schema.c                   |  26 ++--
8394b4
 ldap/servers/slapd/slap.h                     |  21 ++--
8394b4
 ldap/servers/slapd/slapi-plugin.h             |   1 +
8394b4
 ldap/servers/slapd/slapi-private.h            |   3 +-
8394b4
 src/lib389/lib389/extensibleobject.py         |  53 +++++++++
8394b4
 8 files changed, 258 insertions(+), 64 deletions(-)
8394b4
 create mode 100644 src/lib389/lib389/extensibleobject.py
8394b4
8394b4
diff --git a/dirsrvtests/tests/suites/filter/schema_validation_test.py b/dirsrvtests/tests/suites/filter/schema_validation_test.py
8394b4
index 4ac9fa8ff..d0d67ca95 100644
8394b4
--- a/dirsrvtests/tests/suites/filter/schema_validation_test.py
8394b4
+++ b/dirsrvtests/tests/suites/filter/schema_validation_test.py
8394b4
@@ -9,14 +9,29 @@
8394b4
 
8394b4
 import pytest
8394b4
 import ldap
8394b4
-from lib389.topologies import topology_st
8394b4
+from lib389.topologies import topology_st as topology_st_pre
8394b4
 from lib389.dirsrv_log import DirsrvAccessLog
8394b4
 from lib389._mapped_object import DSLdapObjects
8394b4
 from lib389._constants import DEFAULT_SUFFIX
8394b4
+from lib389.extensibleobject import UnsafeExtensibleObjects
8394b4
 
8394b4
-def _check_value(inst_cfg, value):
8394b4
+def _check_value(inst_cfg, value, exvalue=None):
8394b4
+    if exvalue is None:
8394b4
+        exvalue = value
8394b4
     inst_cfg.set('nsslapd-verify-filter-schema', value)
8394b4
-    assert(inst_cfg.get_attr_val_utf8('nsslapd-verify-filter-schema') == value)
8394b4
+    assert(inst_cfg.get_attr_val_utf8('nsslapd-verify-filter-schema') == exvalue)
8394b4
+
8394b4
+@pytest.fixture(scope="module")
8394b4
+def topology_st(topology_st_pre):
8394b4
+    raw_objects = UnsafeExtensibleObjects(topology_st_pre.standalone, basedn=DEFAULT_SUFFIX)
8394b4
+    # Add an object that won't be able to be queried due to invalid attrs.
8394b4
+    raw_objects.create(properties = {
8394b4
+        "cn": "test_obj",
8394b4
+        "a": "a",
8394b4
+        "b": "b",
8394b4
+        "uid": "foo"
8394b4
+    })
8394b4
+    return topology_st_pre
8394b4
 
8394b4
 
8394b4
 @pytest.mark.ds50349
8394b4
@@ -51,8 +66,14 @@ def test_filter_validation_config(topology_st):
8394b4
 
8394b4
     initial_value = inst_cfg.get_attr_val_utf8('nsslapd-verify-filter-schema')
8394b4
 
8394b4
-    _check_value(inst_cfg, "on")
8394b4
-    _check_value(inst_cfg, "warn")
8394b4
+    # Check legacy values that may have been set
8394b4
+    _check_value(inst_cfg, "on", "reject-invalid")
8394b4
+    _check_value(inst_cfg, "warn", "process-safe")
8394b4
+    _check_value(inst_cfg, "off")
8394b4
+    # Check the more descriptive values
8394b4
+    _check_value(inst_cfg, "reject-invalid")
8394b4
+    _check_value(inst_cfg, "process-safe")
8394b4
+    _check_value(inst_cfg, "warn-invalid")
8394b4
     _check_value(inst_cfg, "off")
8394b4
 
8394b4
     # This should fail
8394b4
@@ -85,7 +106,7 @@ def test_filter_validation_enabled(topology_st):
8394b4
     inst = topology_st.standalone
8394b4
 
8394b4
     # In case the default has changed, we set the value to warn.
8394b4
-    inst.config.set("nsslapd-verify-filter-schema", "on")
8394b4
+    inst.config.set("nsslapd-verify-filter-schema", "reject-invalid")
8394b4
     raw_objects = DSLdapObjects(inst, basedn=DEFAULT_SUFFIX)
8394b4
 
8394b4
     # Check a good query has no errors.
8394b4
@@ -104,9 +125,9 @@ def test_filter_validation_enabled(topology_st):
8394b4
 
8394b4
 
8394b4
 @pytest.mark.ds50349
8394b4
-def test_filter_validation_warning(topology_st):
8394b4
+def test_filter_validation_warn_safe(topology_st):
8394b4
     """Test that queries which are invalid, are correctly marked as "notes=F" in
8394b4
-    the access log.
8394b4
+    the access log, and return no entries or partial sets.
8394b4
 
8394b4
     :id: 8b2b23fe-d878-435c-bc84-8c298be4ca1f
8394b4
     :setup: Standalone instance
8394b4
@@ -122,7 +143,7 @@ def test_filter_validation_warning(topology_st):
8394b4
     inst = topology_st.standalone
8394b4
 
8394b4
     # In case the default has changed, we set the value to warn.
8394b4
-    inst.config.set("nsslapd-verify-filter-schema", "warn")
8394b4
+    inst.config.set("nsslapd-verify-filter-schema", "process-safe")
8394b4
     # Set the access log to un-buffered so we get it immediately.
8394b4
     inst.config.set("nsslapd-accesslog-logbuffering", "off")
8394b4
 
8394b4
@@ -139,20 +160,93 @@ def test_filter_validation_warning(topology_st):
8394b4
 
8394b4
     # Check a good query has no warnings.
8394b4
     r = raw_objects.filter("(objectClass=*)")
8394b4
+    assert(len(r) > 0)
8394b4
     r_s1 = access_log.match(".*notes=F.*")
8394b4
     # Should be the same number of log lines IE 0.
8394b4
     assert(len(r_init) == len(r_s1))
8394b4
 
8394b4
     # Check a bad one DOES emit a warning.
8394b4
     r = raw_objects.filter("(a=a)")
8394b4
+    assert(len(r) == 0)
8394b4
     r_s2 = access_log.match(".*notes=F.*")
8394b4
     # Should be the greate number of log lines IE +1
8394b4
     assert(len(r_init) + 1 == len(r_s2))
8394b4
 
8394b4
     # Check a bad complex one does emit a warning.
8394b4
     r = raw_objects.filter("(&(a=a)(b=b)(objectClass=*))")
8394b4
+    assert(len(r) == 0)
8394b4
     r_s3 = access_log.match(".*notes=F.*")
8394b4
     # Should be the greate number of log lines IE +2
8394b4
     assert(len(r_init) + 2 == len(r_s3))
8394b4
 
8394b4
+    # Check that we can still get things when partial
8394b4
+    r = raw_objects.filter("(|(a=a)(b=b)(uid=foo))")
8394b4
+    assert(len(r) == 1)
8394b4
+    r_s4 = access_log.match(".*notes=F.*")
8394b4
+    # Should be the greate number of log lines IE +2
8394b4
+    assert(len(r_init) + 3 == len(r_s4))
8394b4
+
8394b4
+
8394b4
+@pytest.mark.ds50349
8394b4
+def test_filter_validation_warn_unsafe(topology_st):
8394b4
+    """Test that queries which are invalid, are correctly marked as "notes=F" in
8394b4
+    the access log, and uses the legacy query behaviour to return unsafe sets.
8394b4
+
8394b4
+    :id: 8b2b23fe-d878-435c-bc84-8c298be4ca1f
8394b4
+    :setup: Standalone instance
8394b4
+    :steps:
8394b4
+        1. Search a well formed query
8394b4
+        2. Search a poorly formed query
8394b4
+        3. Search a poorly formed complex (and/or) query
8394b4
+    :expectedresults:
8394b4
+        1. No warnings
8394b4
+        2. notes=F is present
8394b4
+        3. notes=F is present
8394b4
+    """
8394b4
+    inst = topology_st.standalone
8394b4
+
8394b4
+    # In case the default has changed, we set the value to warn.
8394b4
+    inst.config.set("nsslapd-verify-filter-schema", "warn-invalid")
8394b4
+    # Set the access log to un-buffered so we get it immediately.
8394b4
+    inst.config.set("nsslapd-accesslog-logbuffering", "off")
8394b4
+
8394b4
+    # Setup the query object.
8394b4
+    # Now we don't care if there are any results, we only care about good/bad queries.
8394b4
+    # To do this we have to bypass some of the lib389 magic, and just emit raw queries
8394b4
+    # to check them. Turns out lib389 is well designed and this just works as expected
8394b4
+    # if you use a single DSLdapObjects and filter. :)
8394b4
+    raw_objects = DSLdapObjects(inst, basedn=DEFAULT_SUFFIX)
8394b4
+
8394b4
+    # Find any initial notes=F
8394b4
+    access_log = DirsrvAccessLog(inst)
8394b4
+    r_init = access_log.match(".*notes=(U,)?F.*")
8394b4
+
8394b4
+    # Check a good query has no warnings.
8394b4
+    r = raw_objects.filter("(objectClass=*)")
8394b4
+    assert(len(r) > 0)
8394b4
+    r_s1 = access_log.match(".*notes=(U,)?F.*")
8394b4
+    # Should be the same number of log lines IE 0.
8394b4
+    assert(len(r_init) == len(r_s1))
8394b4
+
8394b4
+    # Check a bad one DOES emit a warning.
8394b4
+    r = raw_objects.filter("(a=a)")
8394b4
+    assert(len(r) == 1)
8394b4
+    # NOTE: Unlike warn-process-safely, these become UNINDEXED and show in the logs.
8394b4
+    r_s2 = access_log.match(".*notes=(U,)?F.*")
8394b4
+    # Should be the greate number of log lines IE +1
8394b4
+    assert(len(r_init) + 1 == len(r_s2))
8394b4
+
8394b4
+    # Check a bad complex one does emit a warning.
8394b4
+    r = raw_objects.filter("(&(a=a)(b=b)(objectClass=*))")
8394b4
+    assert(len(r) == 1)
8394b4
+    r_s3 = access_log.match(".*notes=(U,)?F.*")
8394b4
+    # Should be the greate number of log lines IE +2
8394b4
+    assert(len(r_init) + 2 == len(r_s3))
8394b4
+
8394b4
+    # Check that we can still get things when partial
8394b4
+    r = raw_objects.filter("(|(a=a)(b=b)(uid=foo))")
8394b4
+    assert(len(r) == 1)
8394b4
+    r_s4 = access_log.match(".*notes=(U,)?F.*")
8394b4
+    # Should be the greate number of log lines IE +2
8394b4
+    assert(len(r_init) + 3 == len(r_s4))
8394b4
 
8394b4
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
8394b4
index 7e65f73ca..8a79848c3 100644
8394b4
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
8394b4
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
8394b4
@@ -223,13 +223,15 @@ ava_candidates(
8394b4
 
8394b4
     switch (ftype) {
8394b4
     case LDAP_FILTER_GE:
8394b4
-        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
             /*
8394b4
              * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
              * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
              * be set on the filter at all!
8394b4
              */
8394b4
             slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+        }
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
             idl = idl_alloc(0);
8394b4
         } else {
8394b4
             idl = range_candidates(pb, be, type, bval, NULL, err, &sattr, allidslimit);
8394b4
@@ -239,13 +241,15 @@ ava_candidates(
8394b4
         goto done;
8394b4
         break;
8394b4
     case LDAP_FILTER_LE:
8394b4
-        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
             /*
8394b4
              * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
              * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
              * be set on the filter at all!
8394b4
              */
8394b4
             slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+        }
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
             idl = idl_alloc(0);
8394b4
         } else {
8394b4
             idl = range_candidates(pb, be, type, NULL, bval, err, &sattr, allidslimit);
8394b4
@@ -293,13 +297,15 @@ ava_candidates(
8394b4
         ptr[1] = NULL;
8394b4
         ivals = ptr;
8394b4
 
8394b4
-        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
             /*
8394b4
              * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
              * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
              * be set on the filter at all!
8394b4
              */
8394b4
             slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+        }
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
             idl = idl_alloc(0);
8394b4
         } else {
8394b4
             slapi_attr_assertion2keys_ava_sv(&sattr, &tmp, (Slapi_Value ***)&ivals, LDAP_FILTER_EQUALITY_FAST);
8394b4
@@ -326,13 +332,15 @@ ava_candidates(
8394b4
             slapi_ch_free((void **)&ivals);
8394b4
         }
8394b4
     } else {
8394b4
-        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
             /*
8394b4
              * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
              * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
              * be set on the filter at all!
8394b4
              */
8394b4
             slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+        }
8394b4
+        if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
             idl = idl_alloc(0);
8394b4
         } else {
8394b4
             slapi_value_init_berval(&sv, bval);
8394b4
@@ -382,13 +390,15 @@ presence_candidates(
8394b4
     }
8394b4
     slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
8394b4
 
8394b4
-    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
         /*
8394b4
          * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
          * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
          * be set on the filter at all!
8394b4
          */
8394b4
         slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+    }
8394b4
+    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
         idl = idl_alloc(0);
8394b4
     } else {
8394b4
         idl = index_read_ext_allids(pb, be, type, indextype_PRESENCE,
8394b4
@@ -485,13 +495,15 @@ extensible_candidates(
8394b4
                         slapi_pblock_get(pb, SLAPI_PLUGIN_MR_KEYS, &keys)) {
8394b4
                         /* something went wrong.  bail. */
8394b4
                         break;
8394b4
-                    } else if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+                    } else if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
                         /*
8394b4
                          * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
                          * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
                          * be set on the filter at all!
8394b4
                          */
8394b4
                         slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+                    }
8394b4
+                    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
                         idl = idl_alloc(0);
8394b4
                     } else if (keys == NULL || keys[0] == NULL) {
8394b4
                         /* no keys */
8394b4
@@ -986,13 +998,15 @@ substring_candidates(
8394b4
      * look up each key in the index, ANDing the resulting
8394b4
      * IDLists together.
8394b4
      */
8394b4
-    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR) {
8394b4
+    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_WARN) {
8394b4
         /*
8394b4
          * REMEMBER: this flag is only set on WARN levels. If the filter verify
8394b4
          * is on strict, we reject in search.c, if we ar off, the flag will NOT
8394b4
          * be set on the filter at all!
8394b4
          */
8394b4
         slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_FILTER_INVALID);
8394b4
+    }
8394b4
+    if (f->f_flags & SLAPI_FILTER_INVALID_ATTR_UNDEFINE) {
8394b4
         idl = idl_alloc(0);
8394b4
     } else {
8394b4
         slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
8394b4
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
8394b4
index b9cdb6b37..66170ebc6 100644
8394b4
--- a/ldap/servers/slapd/libglobs.c
8394b4
+++ b/ldap/servers/slapd/libglobs.c
8394b4
@@ -166,7 +166,7 @@ typedef enum {
8394b4
     CONFIG_SPECIAL_VALIDATE_CERT_SWITCH, /* maps strings to an enumeration */
8394b4
     CONFIG_SPECIAL_UNHASHED_PW_SWITCH,   /* unhashed pw: on/off/nolog */
8394b4
     CONFIG_SPECIAL_TLS_CHECK_CRL,        /* maps enum tls_check_crl_t to char * */
8394b4
-    CONFIG_ON_OFF_WARN,                  /* maps to a config on/warn/off enum */
8394b4
+    CONFIG_SPECIAL_FILTER_VERIFY,      /* maps to a config strict/warn-strict/warn/off enum */
8394b4
 } ConfigVarType;
8394b4
 
8394b4
 static int32_t config_set_onoff(const char *attrname, char *value, int32_t *configvalue, char *errorbuf, int apply);
8394b4
@@ -256,7 +256,7 @@ slapi_int_t init_malloc_mmap_threshold;
8394b4
 slapi_onoff_t init_extract_pem;
8394b4
 slapi_onoff_t init_ignore_vattrs;
8394b4
 slapi_onoff_t init_enable_upgrade_hash;
8394b4
-slapi_onwarnoff_t init_verify_filter_schema;
8394b4
+slapi_special_filter_verify_t init_verify_filter_schema;
8394b4
 
8394b4
 static int
8394b4
 isInt(ConfigVarType type)
8394b4
@@ -1248,7 +1248,7 @@ static struct config_get_and_set
8394b4
     {CONFIG_VERIFY_FILTER_SCHEMA, config_set_verify_filter_schema,
8394b4
      NULL, 0,
8394b4
      (void **)&global_slapdFrontendConfig.verify_filter_schema,
8394b4
-     CONFIG_ON_OFF_WARN, (ConfigGetFunc)config_get_verify_filter_schema,
8394b4
+     CONFIG_SPECIAL_FILTER_VERIFY, (ConfigGetFunc)config_get_verify_filter_schema,
8394b4
      &init_verify_filter_schema},
8394b4
     /* End config */
8394b4
     };
8394b4
@@ -7659,18 +7659,21 @@ config_initvalue_to_onoff(struct config_get_and_set *cgas, char *initvalbuf, siz
8394b4
 }
8394b4
 
8394b4
 static char *
8394b4
-config_initvalue_to_onwarnoff(struct config_get_and_set *cgas, char *initvalbuf, size_t initvalbufsize) {
8394b4
+config_initvalue_to_special_filter_verify(struct config_get_and_set *cgas, char *initvalbuf, size_t initvalbufsize) {
8394b4
     char *retval = NULL;
8394b4
-    if (cgas->config_var_type == CONFIG_ON_OFF_WARN) {
8394b4
-        slapi_onwarnoff_t *value = (slapi_onwarnoff_t *)(intptr_t)cgas->initvalue;
8394b4
+    if (cgas->config_var_type == CONFIG_SPECIAL_FILTER_VERIFY) {
8394b4
+        slapi_special_filter_verify_t *value = (slapi_special_filter_verify_t *)(intptr_t)cgas->initvalue;
8394b4
         if (value != NULL) {
8394b4
-            if (*value == SLAPI_ON) {
8394b4
-                PR_snprintf(initvalbuf, initvalbufsize, "%s", "on");
8394b4
+            if (*value == SLAPI_STRICT) {
8394b4
+                PR_snprintf(initvalbuf, initvalbufsize, "%s", "reject-invalid");
8394b4
                 retval = initvalbuf;
8394b4
-            } else if (*value == SLAPI_WARN) {
8394b4
-                PR_snprintf(initvalbuf, initvalbufsize, "%s", "warn");
8394b4
+            } else if (*value == SLAPI_WARN_SAFE) {
8394b4
+                PR_snprintf(initvalbuf, initvalbufsize, "%s", "process-safe");
8394b4
                 retval = initvalbuf;
8394b4
-            } else if (*value == SLAPI_OFF) {
8394b4
+            } else if (*value == SLAPI_WARN_UNSAFE) {
8394b4
+                PR_snprintf(initvalbuf, initvalbufsize, "%s", "warn-invalid");
8394b4
+                retval = initvalbuf;
8394b4
+            } else if (*value == SLAPI_OFF_UNSAFE) {
8394b4
                 PR_snprintf(initvalbuf, initvalbufsize, "%s", "off");
8394b4
                 retval = initvalbuf;
8394b4
             }
8394b4
@@ -7680,7 +7683,7 @@ config_initvalue_to_onwarnoff(struct config_get_and_set *cgas, char *initvalbuf,
8394b4
 }
8394b4
 
8394b4
 static int32_t
8394b4
-config_set_onoffwarn(slapdFrontendConfig_t *slapdFrontendConfig, slapi_onwarnoff_t *target, const char *attrname, char *value, char *errorbuf, int apply) {
8394b4
+config_set_specialfilterverify(slapdFrontendConfig_t *slapdFrontendConfig, slapi_special_filter_verify_t *target, const char *attrname, char *value, char *errorbuf, int apply) {
8394b4
     if (target == NULL) {
8394b4
         return LDAP_OPERATIONS_ERROR;
8394b4
     }
8394b4
@@ -7691,15 +7694,23 @@ config_set_onoffwarn(slapdFrontendConfig_t *slapdFrontendConfig, slapi_onwarnoff
8394b4
 
8394b4
     slapi_special_filter_verify_t p_val = SLAPI_WARN_UNSAFE;
8394b4
 
8394b4
+    /* on/warn/off retained for legacy reasons due to wbrown making terrible mistakes :( :( */
8394b4
     if (strcasecmp(value, "on") == 0) {
8394b4
-        p_val = SLAPI_ON;
8394b4
+        p_val = SLAPI_STRICT;
8394b4
     } else if (strcasecmp(value, "warn") == 0) {
8394b4
-        p_val = SLAPI_WARN;
8394b4
+        p_val = SLAPI_WARN_SAFE;
8394b4
+    /* The new fixed/descriptive names */
8394b4
+    } else if (strcasecmp(value, "reject-invalid") == 0) {
8394b4
+        p_val = SLAPI_STRICT;
8394b4
+    } else if (strcasecmp(value, "process-safe") == 0) {
8394b4
+        p_val = SLAPI_WARN_SAFE;
8394b4
+    } else if (strcasecmp(value, "warn-invalid") == 0) {
8394b4
+        p_val = SLAPI_WARN_UNSAFE;
8394b4
     } else if (strcasecmp(value, "off") == 0) {
8394b4
-        p_val = SLAPI_OFF;
8394b4
+        p_val = SLAPI_OFF_UNSAFE;
8394b4
     } else {
8394b4
         slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
8394b4
-                              "%s: invalid value \"%s\". Valid values are \"on\", \"warn\" or \"off\".", attrname, value);
8394b4
+                              "%s: invalid value \"%s\". Valid values are \"reject-invalid\", \"process-safe\", \"warn-invalid\" or \"off\". If in doubt, choose \"process-safe\"", attrname, value);
8394b4
         return LDAP_OPERATIONS_ERROR;
8394b4
     }
8394b4
 
8394b4
@@ -7718,14 +7729,14 @@ config_set_onoffwarn(slapdFrontendConfig_t *slapdFrontendConfig, slapi_onwarnoff
8394b4
 int32_t
8394b4
 config_set_verify_filter_schema(const char *attrname, char *value, char *errorbuf, int apply) {
8394b4
     slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
8394b4
-    slapi_onwarnoff_t *target = &(slapdFrontendConfig->verify_filter_schema);
8394b4
-    return config_set_onoffwarn(slapdFrontendConfig, target, attrname, value, errorbuf, apply);
8394b4
+    slapi_special_filter_verify_t *target = &(slapdFrontendConfig->verify_filter_schema);
8394b4
+    return config_set_specialfilterverify(slapdFrontendConfig, target, attrname, value, errorbuf, apply);
8394b4
 }
8394b4
 
8394b4
 Slapi_Filter_Policy
8394b4
 config_get_verify_filter_schema()
8394b4
 {
8394b4
-    slapi_onwarnoff_t retVal;
8394b4
+    slapi_special_filter_verify_t retVal;
8394b4
     slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
8394b4
     CFG_LOCK_READ(slapdFrontendConfig);
8394b4
     retVal = slapdFrontendConfig->verify_filter_schema;
8394b4
@@ -7733,10 +7744,13 @@ config_get_verify_filter_schema()
8394b4
 
8394b4
     /* Now map this to a policy that the fns understand. */
8394b4
     switch (retVal) {
8394b4
-    case SLAPI_ON:
8394b4
+    case SLAPI_STRICT:
8394b4
         return FILTER_POLICY_STRICT;
8394b4
         break;
8394b4
-    case SLAPI_WARN:
8394b4
+    case SLAPI_WARN_SAFE:
8394b4
+        return FILTER_POLICY_PROTECT;
8394b4
+        break;
8394b4
+    case SLAPI_WARN_UNSAFE:
8394b4
         return FILTER_POLICY_WARNING;
8394b4
         break;
8394b4
     default:
8394b4
@@ -7794,8 +7808,8 @@ config_set(const char *attr, struct berval **values, char *errorbuf, int apply)
8394b4
             void *initval = cgas->initvalue;
8394b4
             if (cgas->config_var_type == CONFIG_ON_OFF) {
8394b4
                 initval = (void *)config_initvalue_to_onoff(cgas, initvalbuf, sizeof(initvalbuf));
8394b4
-            } else if (cgas->config_var_type == CONFIG_ON_OFF_WARN) {
8394b4
-                initval = (void *)config_initvalue_to_onwarnoff(cgas, initvalbuf, sizeof(initvalbuf));
8394b4
+            } else if (cgas->config_var_type == CONFIG_SPECIAL_FILTER_VERIFY) {
8394b4
+                initval = (void *)config_initvalue_to_special_filter_verify(cgas, initvalbuf, sizeof(initvalbuf));
8394b4
             }
8394b4
             if (cgas->setfunc) {
8394b4
                 retval = (cgas->setfunc)(cgas->attr_name, initval, errorbuf, apply);
8394b4
@@ -8021,20 +8035,24 @@ config_set_value(
8394b4
 
8394b4
         break;
8394b4
 
8394b4
-    case CONFIG_ON_OFF_WARN:
8394b4
+    case CONFIG_SPECIAL_FILTER_VERIFY:
8394b4
         /* Is this the right default here? */
8394b4
         if (!value) {
8394b4
-            slapi_entry_attr_set_charptr(e, cgas->attr_name, "off");
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "process-safe");
8394b4
             break;
8394b4
         }
8394b4
 
8394b4
-        if (*((slapi_onwarnoff_t *)value) == SLAPI_ON) {
8394b4
-            slapi_entry_attr_set_charptr(e, cgas->attr_name, "on");
8394b4
-        } else if (*((slapi_onwarnoff_t *)value) == SLAPI_WARN) {
8394b4
-            slapi_entry_attr_set_charptr(e, cgas->attr_name, "warn");
8394b4
+        if (*((slapi_special_filter_verify_t *)value) == SLAPI_STRICT) {
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "reject-invalid");
8394b4
+        } else if (*((slapi_special_filter_verify_t *)value) == SLAPI_WARN_SAFE) {
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "process-safe");
8394b4
+        } else if (*((slapi_special_filter_verify_t *)value) == SLAPI_WARN_UNSAFE) {
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "warn-invalid");
8394b4
+        } else if (*((slapi_special_filter_verify_t *)value) == SLAPI_OFF_UNSAFE) {
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "off");
8394b4
         } else {
8394b4
             /* Default to safe warn-proccess-safely */
8394b4
-            slapi_entry_attr_set_charptr(e, cgas->attr_name, "warn-invalid");
8394b4
+            slapi_entry_attr_set_charptr(e, cgas->attr_name, "process-safe");
8394b4
         }
8394b4
 
8394b4
         break;
8394b4
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
8394b4
index 6e853fc7c..d7c3c139e 100644
8394b4
--- a/ldap/servers/slapd/schema.c
8394b4
+++ b/ldap/servers/slapd/schema.c
8394b4
@@ -698,7 +698,7 @@ out:
8394b4
 }
8394b4
 
8394b4
 static Slapi_Filter_Result
8394b4
-slapi_filter_schema_check_inner(Slapi_Filter *f) {
8394b4
+slapi_filter_schema_check_inner(Slapi_Filter *f, slapi_filter_flags flags) {
8394b4
     /*
8394b4
      * Default response to Ok. If any more severe things happen we
8394b4
      * alter this to reflect it. IE we bubble up more severe errors
8394b4
@@ -712,26 +712,26 @@ slapi_filter_schema_check_inner(Slapi_Filter *f) {
8394b4
     case LDAP_FILTER_LE:
8394b4
     case LDAP_FILTER_APPROX:
8394b4
         if (!attr_syntax_exist_by_name_nolock(f->f_avtype)) {
8394b4
-            f->f_flags |= SLAPI_FILTER_INVALID_ATTR;
8394b4
+            f->f_flags |= flags;
8394b4
             r = FILTER_SCHEMA_WARNING;
8394b4
         }
8394b4
         break;
8394b4
     case LDAP_FILTER_PRESENT:
8394b4
         if (!attr_syntax_exist_by_name_nolock(f->f_type)) {
8394b4
-            f->f_flags |= SLAPI_FILTER_INVALID_ATTR;
8394b4
+            f->f_flags |= flags;
8394b4
             r = FILTER_SCHEMA_WARNING;
8394b4
         }
8394b4
         break;
8394b4
     case LDAP_FILTER_SUBSTRINGS:
8394b4
         if (!attr_syntax_exist_by_name_nolock(f->f_sub_type)) {
8394b4
-            f->f_flags |= SLAPI_FILTER_INVALID_ATTR;
8394b4
+            f->f_flags |= flags;
8394b4
             r = FILTER_SCHEMA_WARNING;
8394b4
         }
8394b4
         break;
8394b4
     case LDAP_FILTER_EXTENDED:
8394b4
         /* I don't have any examples of this, so I'm not 100% on how to check it */
8394b4
         if (!attr_syntax_exist_by_name_nolock(f->f_mr_type)) {
8394b4
-            f->f_flags |= SLAPI_FILTER_INVALID_ATTR;
8394b4
+            f->f_flags |= flags;
8394b4
             r = FILTER_SCHEMA_WARNING;
8394b4
         }
8394b4
         break;
8394b4
@@ -740,7 +740,7 @@ slapi_filter_schema_check_inner(Slapi_Filter *f) {
8394b4
     case LDAP_FILTER_NOT:
8394b4
         /* Recurse and check all elemments of the filter */
8394b4
         for (Slapi_Filter *f_child = f->f_list; f_child != NULL; f_child = f_child->f_next) {
8394b4
-            Slapi_Filter_Result ri = slapi_filter_schema_check_inner(f_child);
8394b4
+            Slapi_Filter_Result ri = slapi_filter_schema_check_inner(f_child, flags);
8394b4
             if (ri > r) {
8394b4
                 r = ri;
8394b4
             }
8394b4
@@ -769,12 +769,24 @@ slapi_filter_schema_check(Slapi_Filter *f, Slapi_Filter_Policy fp) {
8394b4
         return FILTER_SCHEMA_SUCCESS;
8394b4
     }
8394b4
 
8394b4
+    /*
8394b4
+     * There are two possible warning types - it's not up to us to warn into
8394b4
+     * the logs, that's the backends job. So we have to flag a hint into the
8394b4
+     * filter about what it should do. This is why there are two FILTER_INVALID
8394b4
+     * types in filter_flags, one for logging it, and one for actually doing
8394b4
+     * the rejection.
8394b4
+     */
8394b4
+    slapi_filter_flags flags = SLAPI_FILTER_INVALID_ATTR_WARN;
8394b4
+    if (fp == FILTER_POLICY_PROTECT) {
8394b4
+        flags |= SLAPI_FILTER_INVALID_ATTR_UNDEFINE;
8394b4
+    }
8394b4
+
8394b4
     /*
8394b4
      * Filters are nested, recursive structures, so we actually have to call an inner
8394b4
      * function until we have a result!
8394b4
      */
8394b4
     attr_syntax_read_lock();
8394b4
-    Slapi_Filter_Result r = slapi_filter_schema_check_inner(f);
8394b4
+    Slapi_Filter_Result r = slapi_filter_schema_check_inner(f, flags);
8394b4
     attr_syntax_unlock_read();
8394b4
 
8394b4
     /* If any warning occured, ensure we fail it. */
8394b4
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
8394b4
index 8a2748519..d73e9aaae 100644
8394b4
--- a/ldap/servers/slapd/slap.h
8394b4
+++ b/ldap/servers/slapd/slap.h
8394b4
@@ -457,12 +457,12 @@ typedef enum _tls_check_crl_t {
8394b4
     TLS_CHECK_ALL = 2,
8394b4
 } tls_check_crl_t;
8394b4
 
8394b4
-typedef enum _slapi_onwarnoff_t {
8394b4
-    SLAPI_OFF = 0,
8394b4
-    SLAPI_WARN = 1,
8394b4
-    SLAPI_ON = 2,
8394b4
-} slapi_onwarnoff_t;
8394b4
-
8394b4
+typedef enum _slapi_special_filter_verify_t {
8394b4
+    SLAPI_STRICT = 0,
8394b4
+    SLAPI_WARN_SAFE = 1,
8394b4
+    SLAPI_WARN_UNSAFE = 2,
8394b4
+    SLAPI_OFF_UNSAFE = 3,
8394b4
+} slapi_special_filter_verify_t;
8394b4
 
8394b4
 struct subfilt
8394b4
 {
8394b4
@@ -2547,11 +2547,12 @@ typedef struct _slapdFrontendConfig
8394b4
     slapi_onoff_t enable_upgrade_hash; /* If on, upgrade hashes for PW at bind */
8394b4
     /*
8394b4
      * Do we verify the filters we recieve by schema?
8394b4
-     * on - yes, and reject if attribute not found
8394b4
-     * warn - yes, and warn that the attribute is unknown and unindexed
8394b4
-     * off - no, do whatever (old status-quo)
8394b4
+     * reject-invalid - reject filter if there is anything invalid
8394b4
+     * process-safe - allow the filter, warn about what's invalid, and then idl_alloc(0) with rfc compliance
8394b4
+     * warn-invalid - allow the filter, warn about the invalid, and then do a ALLIDS (may lead to full table scan)
8394b4
+     * off - don't warn, just allow anything. This is the legacy behaviour.
8394b4
      */
8394b4
-    slapi_onwarnoff_t verify_filter_schema;
8394b4
+    slapi_special_filter_verify_t verify_filter_schema;
8394b4
 } slapdFrontendConfig_t;
8394b4
 
8394b4
 /* possible values for slapdFrontendConfig_t.schemareplace */
8394b4
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
8394b4
index 50b8d12c8..40b5c911a 100644
8394b4
--- a/ldap/servers/slapd/slapi-plugin.h
8394b4
+++ b/ldap/servers/slapd/slapi-plugin.h
8394b4
@@ -1571,6 +1571,7 @@ int slapi_entry_syntax_check(Slapi_PBlock *pb, Slapi_Entry *e, int override);
8394b4
 typedef enum {
8394b4
     FILTER_POLICY_OFF,
8394b4
     FILTER_POLICY_WARNING,
8394b4
+    FILTER_POLICY_PROTECT,
8394b4
     FILTER_POLICY_STRICT,
8394b4
 } Slapi_Filter_Policy;
8394b4
 
8394b4
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
8394b4
index 04c045532..1f17eda12 100644
8394b4
--- a/ldap/servers/slapd/slapi-private.h
8394b4
+++ b/ldap/servers/slapd/slapi-private.h
8394b4
@@ -54,7 +54,8 @@ typedef enum _slapi_filter_flags_t {
8394b4
     SLAPI_FILTER_RUV = 4,
8394b4
     SLAPI_FILTER_NORMALIZED_TYPE = 8,
8394b4
     SLAPI_FILTER_NORMALIZED_VALUE = 16,
8394b4
-    SLAPI_FILTER_INVALID_ATTR = 32,
8394b4
+    SLAPI_FILTER_INVALID_ATTR_UNDEFINE = 32,
8394b4
+    SLAPI_FILTER_INVALID_ATTR_WARN = 64,
8394b4
 } slapi_filter_flags;
8394b4
 
8394b4
 #define SLAPI_ENTRY_LDAPSUBENTRY 2
8394b4
diff --git a/src/lib389/lib389/extensibleobject.py b/src/lib389/lib389/extensibleobject.py
8394b4
new file mode 100644
8394b4
index 000000000..8fe37f980
8394b4
--- /dev/null
8394b4
+++ b/src/lib389/lib389/extensibleobject.py
8394b4
@@ -0,0 +1,53 @@
8394b4
+# --- BEGIN COPYRIGHT BLOCK ---
8394b4
+# Copyright (C) 2019, William Brown <william at blackhats.net.au>
8394b4
+# All rights reserved.
8394b4
+#
8394b4
+# License: GPL (version 3 or any later version).
8394b4
+# See LICENSE for details.
8394b4
+# --- END COPYRIGHT BLOCK ---
8394b4
+
8394b4
+from lib389._mapped_object import DSLdapObject, DSLdapObjects
8394b4
+from lib389.utils import ensure_str
8394b4
+
8394b4
+class UnsafeExtensibleObject(DSLdapObject):
8394b4
+    """A single instance of an extensible object. Extensible object by it's
8394b4
+    nature is unsafe, eliminating rules around attribute checking. It may
8394b4
+    cause unsafe or other unknown behaviour if not handled correctly.
8394b4
+
8394b4
+    :param instance: An instance
8394b4
+    :type instance: lib389.DirSrv
8394b4
+    :param dn: Entry DN
8394b4
+    :type dn: str
8394b4
+    """
8394b4
+
8394b4
+    def __init__(self, instance, dn=None):
8394b4
+        super(UnsafeExtensibleObject, self).__init__(instance, dn)
8394b4
+        self._rdn_attribute = "cn"
8394b4
+        # Can I generate these from schema?
8394b4
+        self._must_attributes = []
8394b4
+        self._create_objectclasses = [
8394b4
+            'top',
8394b4
+            'extensibleObject',
8394b4
+        ]
8394b4
+        self._protected = False
8394b4
+
8394b4
+class UnsafeExtensibleObjects(DSLdapObjects):
8394b4
+    """DSLdapObjects that represents all extensible objects. Extensible Objects
8394b4
+    are unsafe in their nature, disabling many checks around schema and attribute
8394b4
+    handling. You should really really REALLY not use this unless you have specific
8394b4
+    needs for testing.
8394b4
+
8394b4
+    :param instance: An instance
8394b4
+    :type instance: lib389.DirSrv
8394b4
+    :param basedn: Base DN for all group entries below
8394b4
+    :type basedn: str
8394b4
+    """
8394b4
+
8394b4
+    def __init__(self, instance, basedn):
8394b4
+        super(UnsafeExtensibleObjects, self).__init__(instance)
8394b4
+        self._objectclasses = [
8394b4
+            'extensibleObject',
8394b4
+        ]
8394b4
+        self._filterattrs = ["cn"]
8394b4
+        self._childobject = UnsafeExtensibleObject
8394b4
+        self._basedn = ensure_str(basedn)
8394b4
-- 
8394b4
2.21.1
8394b4