Repository: ansible-collections/community.general

Issue:
ldap_attrs does not escape attribute values
### Summary

using the `ldap_attrs` module with an attribute value containing a special character (e.g. `(` or `*` etc.) will cause the module to fail with an `Bad search filter` ldap error.

This bug was introduced in https://github.com/ansible-collections/community.general/pull/5385. The reason is, that the `filterstr` parameter passed to `search_s` is not escaped.

### Issue Type

Bug Report

### Component Name

ldap_attrs

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.13.5]
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
Collection        Version
----------------- -------
community.general 5.8.0  

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

Centos 7

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: configure olcSyncRepl peers
  community.general.ldap_attrs:
    dn: "olcDatabase={3}mdb,cn=config"
    attributes:
      olcSyncRepl: "{0}rid=008 provider=ldaps://ldap10.exampple.com:636 bindmethod=simple timeout=0 network-timeout=0 keepalive=0:0:0 starttls=no filter="(objectclass=*)" searchbase="dc=example,dc=com" scope=sub schemachecking=off type=refreshAndPersist retry="10 +" binddn="cn=syncrepl,dc=example,dc=com" credentials="secret""
    state: present
```


### Expected Results

I expect that the `ldap_attrs` configures the defined ldap attributes.

### Actual Results

```python
TASK [openldap : configure olcSyncRepl peers *********************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ldap.FILTER_ERROR: {'desc': 'Bad search filter'}
fatal: [ldap11.example.com]: FAILED! => changed=false 
  module_stderr: |-
    Traceback (most recent call last):
      File "<stdin>", line 107, in <module>
      File "<stdin>", line 99, in _ansiballz_main
      File "<stdin>", line 48, in invoke_module
      File "/usr/lib64/python2.7/runpy.py", line 176, in run_module
        fname, loader, pkg_name)
      File "/usr/lib64/python2.7/runpy.py", line 82, in _run_module_code
        mod_name, mod_fname, mod_loader, pkg_name)
      File "/usr/lib64/python2.7/runpy.py", line 72, in _run_code
        exec code in run_globals
      File "/tmp/ansible_community.general.ldap_attrs_payload_GFEEAq/ansible_community.general.ldap_attrs_payload.zip/ansible_collections/community/general/plugins/modules/net_tools/ldap/ldap_attrs.py", line 324, in <module>
      File "/tmp/ansible_community.general.ldap_attrs_payload_GFEEAq/ansible_community.general.ldap_attrs_payload.zip/ansible_collections/community/general/plugins/modules/net_tools/ldap/ldap_attrs.py", line 303, in main
      File "/tmp/ansible_community.general.ldap_attrs_payload_GFEEAq/ansible_community.general.ldap_attrs_payload.zip/ansible_collections/community/general/plugins/modules/net_tools/ldap/ldap_attrs.py", line 228, in add
      File "/tmp/ansible_community.general.ldap_attrs_payload_GFEEAq/ansible_community.general.ldap_attrs_payload.zip/ansible_collections/community/general/plugins/modules/net_tools/ldap/ldap_attrs.py", line 279, in _is_value_absent
      File "/tmp/ansible_community.general.ldap_attrs_payload_GFEEAq/ansible_community.general.ldap_attrs_payload.zip/ansible_collections/community/general/plugins/modules/net_tools/ldap/ldap_attrs.py", line 270, in _is_value_present
      File "/usr/lib64/python2.7/site-packages/ldap/ldapobject.py", line 552, in search_s
        return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
      File "/usr/lib64/python2.7/site-packages/ldap/ldapobject.py", line 545, in search_ext_s
        msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit)
      File "/usr/lib64/python2.7/site-packages/ldap/ldapobject.py", line 541, in search_ext
        timeout,sizelimit,
      File "/usr/lib64/python2.7/site-packages/ldap/ldapobject.py", line 99, in _ldap_call
        result = func(*args,**kwargs)
    ldap.FILTER_ERROR: {'desc': 'Bad search filter'}
  module_stdout: ''
  msg: |-
    MODULE FAILURE
    See stdout/stderr for the exact error
  rc: 1
```

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/net_tools/ldap/ldap_attrs.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/net_tools/ldap/ldap_attrs.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @drybjed @jtyr @noles
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/net_tools/ldap/ldap_attrs.py b/plugins/modules/net_tools/ldap/ldap_attrs.py
--- a/plugins/modules/net_tools/ldap/ldap_attrs.py
+++ b/plugins/modules/net_tools/ldap/ldap_attrs.py
@@ -168,7 +168,7 @@
 import traceback
 
 from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.common.text.converters import to_native, to_bytes
+from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
 from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs
 
 import re
@@ -176,6 +176,7 @@
 LDAP_IMP_ERR = None
 try:
     import ldap
+    import ldap.filter
 
     HAS_LDAP = True
 except ImportError:
@@ -264,7 +265,8 @@ def exact(self):
     def _is_value_present(self, name, value):
         """ True if the target attribute has the given value. """
         try:
-            filterstr = "(%s=%s)" % (name, value.decode())
+            escaped_value = ldap.filter.escape_filter_chars(to_text(value))
+            filterstr = "(%s=%s)" % (name, escaped_value)
             dns = self.connection.search_s(self.dn, ldap.SCOPE_BASE, filterstr)
             is_present = len(dns) == 1
         except ldap.NO_SUCH_OBJECT:


Test patch: