From 2387fe6df83bd438536b175938d8b6323070f387 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 15 Jan 2018 23:17:58 +0100 Subject: [PATCH 001/206] Use correct types for BER en/decode ber_scanf() and ber_printf() "i" format uses ber_int_t. lber_types.h defines the type as int but Python code assumes the type to be unsigned long: #define LBER_INT_T int typedef LBER_INT_T ber_int_t; The code was working fine on little endian machines but broke on big endian machines. ber_int_t is now correctly parsed as signed int. https://github.com/python-ldap/python-ldap/pull/162 Fixes: https://github.com/python-ldap/python-ldap/issues/161 Signed-off-by: Christian Heimes --- Modules/ldapcontrol.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 9522d572..ec506256 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -242,7 +242,7 @@ encode_rfc2696(PyObject *self, PyObject *args) BerElement *ber = 0; struct berval cookie, *ctrl_val; Py_ssize_t cookie_len; - unsigned long size; + int size = 0; /* ber_int_t is int */ ber_tag_t tag; if (!PyArg_ParseTuple(args, "is#:encode_page_control", &size, @@ -300,7 +300,7 @@ decode_rfc2696(PyObject *self, PyObject *args) struct berval ldctl_value; ber_tag_t tag; struct berval *cookiep; - unsigned long count = 0; + int count = 0; /* ber_int_t is int */ Py_ssize_t ldctl_value_len; if (!PyArg_ParseTuple(args, "s#:decode_page_control", @@ -320,7 +320,7 @@ decode_rfc2696(PyObject *self, PyObject *args) goto endlbl; } - res = Py_BuildValue("(kO&)", count, LDAPberval_to_object, cookiep); + res = Py_BuildValue("(iO&)", count, LDAPberval_to_object, cookiep); ber_bvfree(cookiep); endlbl: From 1266c65b75bfe8a74892874163d268ef5541487d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 15 Jan 2018 23:20:50 +0100 Subject: [PATCH 002/206] Ignore SASL methods in DSE test OpenLDAP may not offer SASL in restricted environments. https://github.com/python-ldap/python-ldap/pull/163 Closes: https://github.com/python-ldap/python-ldap/issues/160 Signed-off-by: Christian Heimes --- Tests/t_ldapobject.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 27deadcd..2c8ce751 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -520,12 +520,15 @@ def test_dse(self): dse = self._ldap_conn.read_rootdse_s() self.assertIsInstance(dse, dict) self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) + keys = set(dse) + # SASL info may be missing in restricted build environments + keys.discard(u'supportedSASLMechanisms') self.assertEqual( - sorted(dse), - [u'configContext', u'entryDN', u'namingContexts', u'objectClass', + keys, + {u'configContext', u'entryDN', u'namingContexts', u'objectClass', u'structuralObjectClass', u'subschemaSubentry', u'supportedControl', u'supportedExtension', u'supportedFeatures', - u'supportedLDAPVersion', u'supportedSASLMechanisms'] + u'supportedLDAPVersion'} ) self.assertEqual( self._ldap_conn.get_naming_contexts(), From 7d69bbdeb9b9777b9048d3fb40aa9d7c1fd83127 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 16 Jan 2018 13:21:56 +0100 Subject: [PATCH 003/206] Remove filterstr workaround from syncrpl test https://github.com/python-ldap/python-ldap/pull/159 Closes: https://github.com/python-ldap/python-ldap/issues/147 Signed-off-by: Christian Heimes --- Tests/t_ldap_syncrepl.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 8c4eb344..71f240c1 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -132,8 +132,7 @@ class SyncreplClient(SimpleLDAPObject, SyncreplConsumer): search, it can't be used for anything else. """ - def __init__(self, uri, dn, password, storage=None, filterstr=None, - **kwargs): + def __init__(self, uri, dn, password, storage=None, **kwargs): """ Set up our object by creating a search client, connecting, and binding. """ @@ -152,7 +151,6 @@ def __init__(self, uri, dn, password, storage=None, filterstr=None, self.data['cookie'] = None self.present = [] self.refresh_done = False - self.filterstr = filterstr SimpleLDAPObject.__init__(self, uri, **kwargs) self.simple_bind_s(dn, password) @@ -175,7 +173,6 @@ def search(self, search_base, search_mode): search_base, ldap.SCOPE_SUBTREE, mode=search_mode, - filterstr=self.filterstr ) def cancel(self): @@ -431,7 +428,6 @@ def setUp(self): self.server.ldap_uri, self.server.root_dn, self.server.root_pw, - filterstr=u'(objectClass=*)', bytes_mode=False ) self.suffix = self.server.suffix @@ -445,7 +441,6 @@ def setUp(self): self.server.ldap_uri, self.server.root_dn.encode('utf-8'), self.server.root_pw.encode('utf-8'), - filterstr=b'(objectClass=*)', bytes_mode=True ) self.suffix = self.server.suffix.encode('utf-8') From 22c3ccf7eab3fc0d05acd0b18d9e1c6f08ec3f0f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 18 Jan 2018 11:35:50 +0100 Subject: [PATCH 004/206] Set LDAPNOINIT env in all tests All test suites are now setting LDAPNOINIT to prevent processing of /etc/openldap/ldap.conf. This fixes TLS tests when ldap.conf contains a relaxed TLS_REQCERT option. See: https://github.com/python-ldap/python-ldap/issues/169 Signed-off-by: Christian Heimes --- Tests/t_bind.py | 10 ++++++++-- Tests/t_cext.py | 3 +-- Tests/t_cidict.py | 8 +++++--- Tests/t_edit.py | 9 +++++++-- Tests/t_ldap_controls_libldap.py | 1 - Tests/t_ldap_dn.py | 4 +++- Tests/t_ldap_filter.py | 6 ++++-- Tests/t_ldap_functions.py | 6 ++++-- Tests/t_ldap_modlist.py | 6 +++++- Tests/t_ldap_options.py | 4 +++- Tests/t_ldap_sasl.py | 1 - Tests/t_ldap_schema_tokenizer.py | 4 ++++ Tests/t_ldap_syncrepl.py | 4 ++-- Tests/t_ldapobject.py | 6 ++++-- Tests/t_ldapurl.py | 5 +++++ Tests/t_ldif.py | 8 +++++--- Tests/t_untested_mods.py | 5 +++++ 17 files changed, 65 insertions(+), 25 deletions(-) diff --git a/Tests/t_bind.py b/Tests/t_bind.py index 6e9a61ca..3e2b67f8 100644 --- a/Tests/t_bind.py +++ b/Tests/t_bind.py @@ -9,9 +9,15 @@ PY2 = False text_type = str -import ldap, unittest -from slapdtest import SlapdTestCase +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap from ldap.ldapobject import LDAPObject +from slapdtest import SlapdTestCase class TestBinds(SlapdTestCase): diff --git a/Tests/t_cext.py b/Tests/t_cext.py index d8233dce..af267069 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -10,13 +10,12 @@ import os import unittest -from slapdtest import SlapdTestCase, requires_tls - # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' # import the plain C wrapper module import _ldap +from slapdtest import SlapdTestCase, requires_tls class TestLdapCExtension(SlapdTestCase): diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 00d07266..8e5d8d6e 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -5,11 +5,13 @@ See https://www.python-ldap.org/ for details. """ -# from Python's standard lib +import os import unittest -# from python-ldap -import ldap, ldap.cidict +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' +import ldap +import ldap.cidict class TestCidict(unittest.TestCase): diff --git a/Tests/t_edit.py b/Tests/t_edit.py index 0012c9cc..a5b3f657 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -9,10 +9,15 @@ PY2 = False text_type = str -import ldap, unittest -from slapdtest import SlapdTestCase +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' +import ldap from ldap.ldapobject import LDAPObject +from slapdtest import SlapdTestCase class EditionTests(SlapdTestCase): diff --git a/Tests/t_ldap_controls_libldap.py b/Tests/t_ldap_controls_libldap.py index 229935ef..d3978613 100644 --- a/Tests/t_ldap_controls_libldap.py +++ b/Tests/t_ldap_controls_libldap.py @@ -4,7 +4,6 @@ # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' -import ldap from ldap.controls import pagedresults from ldap.controls import libldap diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 459c1dcf..4b4dd319 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -8,9 +8,11 @@ from __future__ import unicode_literals # from Python's standard lib +import os import unittest -# from python-ldap +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' import ldap.dn diff --git a/Tests/t_ldap_filter.py b/Tests/t_ldap_filter.py index 5eda05dd..da96446c 100644 --- a/Tests/t_ldap_filter.py +++ b/Tests/t_ldap_filter.py @@ -5,10 +5,12 @@ See https://www.python-ldap.org/ for details. """ -# from Python's standard lib +import os import unittest -# from python-ldap +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + from ldap.filter import escape_filter_chars diff --git a/Tests/t_ldap_functions.py b/Tests/t_ldap_functions.py index cdd2ffb4..45931dab 100644 --- a/Tests/t_ldap_functions.py +++ b/Tests/t_ldap_functions.py @@ -5,10 +5,12 @@ See https://www.python-ldap.org/ for details. """ -# from Python's standard lib +import os import unittest -# from python-ldap +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + import ldap from ldap.dn import escape_dn_chars from ldap.filter import escape_filter_chars diff --git a/Tests/t_ldap_modlist.py b/Tests/t_ldap_modlist.py index a206cde4..db248b7b 100644 --- a/Tests/t_ldap_modlist.py +++ b/Tests/t_ldap_modlist.py @@ -5,12 +5,16 @@ See https://www.python-ldap.org/ for details. """ +import os import unittest -import ldap +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' +import ldap from ldap.modlist import addModlist,modifyModlist + class TestModlist(unittest.TestCase): addModlist_tests = [ diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index a681a9b4..ffc2451f 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -4,12 +4,14 @@ # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' +from slapdtest import SlapdTestCase, requires_tls + import ldap from ldap.controls import RequestControlTuples from ldap.controls.pagedresults import SimplePagedResultsControl from ldap.controls.openldap import SearchNoOpControl from ldap.ldapobject import SimpleLDAPObject -from slapdtest import SlapdTestCase, requires_tls + SENTINEL = object() diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index d1044681..e60ac12b 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -5,7 +5,6 @@ See https://www.python-ldap.org/ for details. """ import os -import socket import unittest # Switch off processing .ldaprc or ldap.conf before importing _ldap diff --git a/Tests/t_ldap_schema_tokenizer.py b/Tests/t_ldap_schema_tokenizer.py index d676e8a7..c8581771 100644 --- a/Tests/t_ldap_schema_tokenizer.py +++ b/Tests/t_ldap_schema_tokenizer.py @@ -5,8 +5,12 @@ See https://www.python-ldap.org/ for details. """ +import os import unittest +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + import ldap.schema # basic test cases diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 71f240c1..73ba1fb1 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -16,8 +16,6 @@ else: PY2 = False -from slapdtest import SlapdObject, SlapdTestCase - # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -25,6 +23,8 @@ from ldap.ldapobject import SimpleLDAPObject from ldap.syncrepl import SyncreplConsumer +from slapdtest import SlapdObject, SlapdTestCase + # a template string for generating simple slapd.conf file SLAPD_CONF_PROVIDER_TEMPLATE = r""" serverID %(serverid)s diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 2c8ce751..b57ecf16 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -22,8 +22,6 @@ import unittest import warnings import pickle -from slapdtest import SlapdTestCase -from slapdtest import requires_ldapi, requires_sasl, requires_tls # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -31,6 +29,10 @@ import ldap from ldap.ldapobject import SimpleLDAPObject, ReconnectLDAPObject +from slapdtest import SlapdTestCase +from slapdtest import requires_ldapi, requires_sasl, requires_tls + + LDIF_TEMPLATE = """dn: %(suffix)s objectClass: dcObject objectClass: organization diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 2be03f63..c4a813dd 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -7,7 +7,12 @@ from __future__ import unicode_literals +import os import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + from ldap.compat import quote import ldapurl diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index adf0d262..4f181df1 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -7,16 +7,18 @@ from __future__ import unicode_literals -# from Python's standard lib -import unittest +import os import textwrap +import unittest try: from StringIO import StringIO except ImportError: from io import StringIO -# from python-ldap +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + import ldif diff --git a/Tests/t_untested_mods.py b/Tests/t_untested_mods.py index 7f0f30d8..5e726a6a 100644 --- a/Tests/t_untested_mods.py +++ b/Tests/t_untested_mods.py @@ -1,5 +1,10 @@ # modules without any tests +import os + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + import ldap.controls.deref import ldap.controls.openldap import ldap.controls.ppolicy From dfbe5234aa6008cb164c3e9107685cc53d2b0144 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 18 Jan 2018 11:37:35 +0100 Subject: [PATCH 005/206] Add test for secure TLS default Now test that the default value for cert validation is DEMAND. See: https://github.com/python-ldap/python-ldap/issues/169 Signed-off-by: Christian Heimes --- Tests/t_cext.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index af267069..350651c6 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -826,10 +826,28 @@ def test_tls_ext(self): l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() + @requires_tls() + def test_tls_require_cert(self): + # libldap defaults to secure cert validation + # see libraries/libldap/init.c + # gopts->ldo_tls_require_cert = LDAP_OPT_X_TLS_DEMAND; + + self.assertEqual( + _ldap.get_option(_ldap.OPT_X_TLS_REQUIRE_CERT), + _ldap.OPT_X_TLS_DEMAND + ) + l = self._open_conn(bind=False) + self.assertEqual( + l.get_option(_ldap.OPT_X_TLS_REQUIRE_CERT), + _ldap.OPT_X_TLS_DEMAND + ) + @requires_tls() def test_tls_ext_noca(self): l = self._open_conn(bind=False) l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + # fails because libldap defaults to secure cert validation but + # the test CA is not installed as trust anchor. with self.assertRaises(_ldap.CONNECT_ERROR) as e: l.start_tls_s() # known resaons: From e148184281c5d5e3bc98d4da95c913c495065374 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 22 Jan 2018 12:50:07 +0100 Subject: [PATCH 006/206] Make bytes mode TypeError more useful Make bytes mode TypeError more useful It's a bit hard to understand where the TypeError excactly comes from. The exception now contains the argument name. https://github.com/python-ldap/python-ldap/pull/165 Signed-off-by: Christian Heimes --- Lib/ldap/ldapobject.py | 10 ++++++-- Modules/LDAPObject.c | 54 +++++++++++++++++++++--------------------- Modules/functions.c | 2 +- Modules/ldapcontrol.c | 8 +++---- Tests/t_cext.py | 10 ++++++-- Tests/t_ldapobject.py | 48 ++++++++++++++++++++++++++++++++----- 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 5a80bff7..a0712a34 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -144,7 +144,10 @@ def _bytesify_input(self, arg_name, value): return value else: if self.bytes_mode_hardfail: - raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,)) + raise TypeError( + "All provided fields *must* be bytes when bytes mode is on; " + "got type '{}' for '{}'.".format(type(value).__name__, arg_name) + ) else: _raise_byteswarning( "Received non-bytes value for '{}' with default (disabled) bytes mode; " @@ -153,7 +156,10 @@ def _bytesify_input(self, arg_name, value): return value.encode('utf-8') else: if not isinstance(value, text_type): - raise TypeError("All provided fields *must* be text when bytes mode is off; got %r" % (value,)) + raise TypeError( + "All provided fields *must* be text when bytes mode is off; " + "got type '{}' for '{}'.".format(type(value).__name__, arg_name) + ) assert not isinstance(value, bytes) return value.encode('utf-8') diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index f8720fa4..d7265a42 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -108,16 +108,16 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) Py_ssize_t i, len, nstrs; if (!PyTuple_Check(tup)) { - LDAPerror_TypeError("expected a tuple", tup); + LDAPerror_TypeError("Tuple_to_LDAPMod(): expected a tuple", tup); return NULL; } if (no_op) { - if (!PyArg_ParseTuple( tup, "sO", &type, &list )) + if (!PyArg_ParseTuple( tup, "sO:Tuple_to_LDAPMod", &type, &list )) return NULL; op = 0; } else { - if (!PyArg_ParseTuple( tup, "isO", &op, &type, &list )) + if (!PyArg_ParseTuple( tup, "isO:Tuple_to_LDAPMod", &op, &type, &list )) return NULL; } @@ -161,7 +161,7 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) if (item == NULL) goto error; if (!PyBytes_Check(item)) { - LDAPerror_TypeError("expected a byte string in the list", item); + LDAPerror_TypeError("Tuple_to_LDAPMod(): expected a byte string in the list", item); goto error; } lm->mod_bvalues[i]->bv_len = PyBytes_Size(item); @@ -206,14 +206,14 @@ List_to_LDAPMods( PyObject *list, int no_op ) { PyObject *item; if (!PySequence_Check(list)) { - LDAPerror_TypeError("expected list of tuples", list); + LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", list); return NULL; } len = PySequence_Length(list); if (len < 0) { - LDAPerror_TypeError("expected list of tuples", list); + LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", list); return NULL; } @@ -262,7 +262,7 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { #endif /* caught by John Benninghoff */ LDAPerror_TypeError( - "expected *list* of strings, not a string", attrlist); + "attrs_from_List(): expected *list* of strings, not a string", attrlist); goto error; } else { PyObject *item = NULL; @@ -291,7 +291,7 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { #if PY_MAJOR_VERSION == 2 /* Encoded in Python to UTF-8 */ if (!PyBytes_Check(item)) { - LDAPerror_TypeError("expected bytes in list", item); + LDAPerror_TypeError("attrs_from_List(): expected bytes in list", item); goto error; } if (PyBytes_AsStringAndSize(item, &str, &strlen) == -1) { @@ -299,7 +299,7 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { } #else if (!PyUnicode_Check(item)) { - LDAPerror_TypeError("expected string in list", item); + LDAPerror_TypeError("attrs_from_List(): expected string in list", item); goto error; } str = PyUnicode_AsUTF8AndSize(item, &strlen); @@ -361,7 +361,7 @@ l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) int ldaperror; - if (!PyArg_ParseTuple( args, "|OO", &serverctrls, &clientctrls)) return NULL; + if (!PyArg_ParseTuple( args, "|OO:unbind_ext", &serverctrls, &clientctrls)) return NULL; if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { @@ -404,7 +404,7 @@ l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) int ldaperror; - if (!PyArg_ParseTuple( args, "i|OO", &msgid, &serverctrls, &clientctrls)) return NULL; + if (!PyArg_ParseTuple( args, "i|OO:abandon_ext", &msgid, &serverctrls, &clientctrls)) return NULL; if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { @@ -449,7 +449,7 @@ l_ldap_add_ext( LDAPObject* self, PyObject *args ) int ldaperror; LDAPMod **mods; - if (!PyArg_ParseTuple( args, "sO|OO", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "sO|OO:add_ext", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; mods = List_to_LDAPMods( modlist, 1 ); @@ -499,7 +499,7 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) LDAPControl** client_ldcs = NULL; struct berval cred; - if (!PyArg_ParseTuple( args, "zz#|OO", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "zz#|OO:simple_bind", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; cred.bv_len = (ber_len_t) cred_len; if (not_valid(self)) return NULL; @@ -649,7 +649,7 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) struct berval *servercred; int ldaperror; - if (!PyArg_ParseTuple(args, "zzz#OO", &dn, &mechanism, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple(args, "zzz#OO:sasl_bind_s", &dn, &mechanism, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; @@ -713,9 +713,9 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) * "i" otherwise. */ #if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 3) - if (!PyArg_ParseTuple(args, "sOOOi", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) + if (!PyArg_ParseTuple(args, "sOOOi:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) #else - if (!PyArg_ParseTuple(args, "sOOOI", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) + if (!PyArg_ParseTuple(args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) #endif return NULL; @@ -780,7 +780,7 @@ l_ldap_cancel( LDAPObject* self, PyObject* args ) int ldaperror; - if (!PyArg_ParseTuple( args, "i|OO", &cancelid, &serverctrls, &clientctrls)) return NULL; + if (!PyArg_ParseTuple( args, "i|OO:cancel", &cancelid, &serverctrls, &clientctrls)) return NULL; if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { @@ -826,7 +826,7 @@ l_ldap_compare_ext( LDAPObject* self, PyObject *args ) Py_ssize_t value_len; struct berval value; - if (!PyArg_ParseTuple( args, "sss#|OO", &dn, &attr, &value.bv_val, &value_len, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "sss#|OO:compare_ext", &dn, &attr, &value.bv_val, &value_len, &serverctrls, &clientctrls )) return NULL; value.bv_len = (ber_len_t) value_len; if (not_valid(self)) return NULL; @@ -871,7 +871,7 @@ l_ldap_delete_ext( LDAPObject* self, PyObject *args ) int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "s|OO", &dn, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "s|OO:delete_ext", &dn, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { @@ -916,7 +916,7 @@ l_ldap_modify_ext( LDAPObject* self, PyObject *args ) int ldaperror; LDAPMod **mods; - if (!PyArg_ParseTuple( args, "sO|OO", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "sO|OO:modify_ext", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; mods = List_to_LDAPMods( modlist, 0 ); @@ -969,7 +969,7 @@ l_ldap_rename( LDAPObject* self, PyObject *args ) int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "ss|ziOO", &dn, &newrdn, &newSuperior, &delold, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple( args, "ss|ziOO:rename", &dn, &newrdn, &newSuperior, &delold, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; @@ -1022,7 +1022,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) char **refs = NULL; LDAPControl **serverctrls = 0; - if (!PyArg_ParseTuple( args, "|iidiii", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) + if (!PyArg_ParseTuple( args, "|iidiii:result4", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) return NULL; if (not_valid(self)) return NULL; @@ -1162,7 +1162,7 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "sis|OiOOdi", + if (!PyArg_ParseTuple( args, "sis|OiOOdi:search_ext", &base, &scope, &filter, &attrlist, &attrsonly, &serverctrls, &clientctrls, &timeout, &sizelimit )) return NULL; if (not_valid(self)) return NULL; @@ -1224,7 +1224,7 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) int ldaperror; - if (!PyArg_ParseTuple( args, "|OO", &serverctrls, &clientctrls)) return NULL; + if (!PyArg_ParseTuple( args, "|OO:whoami_s", &serverctrls, &clientctrls)) return NULL; if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { @@ -1265,7 +1265,7 @@ l_ldap_start_tls_s( LDAPObject* self, PyObject* args ) { int ldaperror; - if (!PyArg_ParseTuple( args, "" )) return NULL; + if (!PyArg_ParseTuple( args, ":start_tls_s" )) return NULL; if (not_valid(self)) return NULL; LDAP_BEGIN_ALLOW_THREADS( self ); @@ -1331,7 +1331,7 @@ l_ldap_passwd( LDAPObject* self, PyObject *args ) int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "z#z#z#|OO", &user.bv_val, &user_len, &oldpw.bv_val, &oldpw_len, &newpw.bv_val, &newpw_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple( args, "z#z#z#|OO:passwd", &user.bv_val, &user_len, &oldpw.bv_val, &oldpw_len, &newpw.bv_val, &newpw_len, &serverctrls, &clientctrls )) return NULL; user.bv_len = (ber_len_t) user_len; @@ -1387,7 +1387,7 @@ l_ldap_extended_operation( LDAPObject* self, PyObject *args ) int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "sz#|OO", &reqoid, &reqvalue.bv_val, &reqvalue.bv_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple( args, "sz#|OO:extended_operation", &reqoid, &reqvalue.bv_val, &reqvalue.bv_len, &serverctrls, &clientctrls )) return NULL; if (not_valid(self)) return NULL; diff --git a/Modules/functions.c b/Modules/functions.c index a31be05e..6bbf487b 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -16,7 +16,7 @@ l_ldap_initialize(PyObject* unused, PyObject *args) LDAP *ld = NULL; int ret; - if (!PyArg_ParseTuple(args, "s", &uri)) + if (!PyArg_ParseTuple(args, "s:initialize", &uri)) return NULL; Py_BEGIN_ALLOW_THREADS diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index ec506256..c8f9dfce 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -72,11 +72,11 @@ Tuple_to_LDAPControl( PyObject* tup ) Py_ssize_t len; if (!PyTuple_Check(tup)) { - LDAPerror_TypeError("expected a tuple", tup); + LDAPerror_TypeError("Tuple_to_LDAPControl(): expected a tuple", tup); return NULL; } - if (!PyArg_ParseTuple( tup, "sbO", &oid, &iscritical, &bytes )) + if (!PyArg_ParseTuple( tup, "sbO:Tuple_to_LDAPControl", &oid, &iscritical, &bytes )) return NULL; lc = PyMem_NEW(LDAPControl, 1); @@ -106,7 +106,7 @@ Tuple_to_LDAPControl( PyObject* tup ) berbytes.bv_val = PyBytes_AsString(bytes); } else { - LDAPerror_TypeError("expected bytes", bytes); + LDAPerror_TypeError("Tuple_to_LDAPControl(): expected bytes", bytes); LDAPControl_DEL(lc); return NULL; } @@ -128,7 +128,7 @@ LDAPControls_from_object(PyObject* list, LDAPControl ***controls_ret) PyObject* item; if (!PySequence_Check(list)) { - LDAPerror_TypeError("expected a list", list); + LDAPerror_TypeError("LDAPControls_from_object(): expected a list", list); return 0; } diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 350651c6..ff5fb6c2 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -784,10 +784,16 @@ def assertInvalidControls(self, func, *args, **kwargs): # last two args are serverctrls, clientctrls with self.assertRaises(TypeError) as e: func(*(args + (object, None) + post)) - self.assertEqual(e.exception.args, ('expected a list', object)) + self.assertEqual( + e.exception.args, + ('LDAPControls_from_object(): expected a list', object) + ) with self.assertRaises(TypeError) as e: func(*(args + (None, object) + post)) - self.assertEqual(e.exception.args, ('expected a list', object)) + self.assertEqual( + e.exception.args, + ('LDAPControls_from_object(): expected a list', object) + ) def test_invalid_controls(self): l = self._open_conn() diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index b57ecf16..1c847428 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -106,12 +106,48 @@ def test_reject_bytes_base(self): base = self.server.suffix l = self._ldap_conn - with self.assertRaises(TypeError): - l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', ['*']) - with self.assertRaises(TypeError): - l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) - with self.assertRaises(TypeError): - l.search_s(base, ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) + with self.assertRaises(TypeError) as e: + l.search_s( + base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', ['*'] + ) + if PY2: + self.assertIn( + u"got type 'str' for 'base'", text_type(e.exception) + ) + elif sys.version_info >= (3, 5, 0): + # Python 3.4.x does not include 'search_ext()' in message + self.assertEqual( + "search_ext() argument 1 must be str, not bytes", + text_type(e.exception) + ) + + with self.assertRaises(TypeError) as e: + l.search_s( + base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*'] + ) + if PY2: + self.assertIn( + u"got type 'str' for 'filterstr'", text_type(e.exception) + ) + elif sys.version_info >= (3, 5, 0): + self.assertEqual( + "search_ext() argument 3 must be str, not bytes", + text_type(e.exception) + ) + + with self.assertRaises(TypeError) as e: + l.search_s( + base, ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*'] + ) + if PY2: + self.assertIn( + u"got type 'str' for 'attrlist'", text_type(e.exception) + ) + elif sys.version_info >= (3, 5, 0): + self.assertEqual( + ('attrs_from_List(): expected string in list', b'*'), + e.exception.args + ) def test_search_keys_are_text(self): base = self.server.suffix From 1b17e853c48e48a033ceb2eec257ca0a481397f0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 15 Jan 2018 17:55:18 +0100 Subject: [PATCH 007/206] Add bytes_strictness to allow configuring behavior on bytes/text mismatch Fixes: https://github.com/python-ldap/python-ldap/issues/166 --- Doc/bytes_mode.rst | 52 ++++++++++++++++----------- Doc/reference/ldap.rst | 5 +-- Lib/ldap/ldapobject.py | 82 ++++++++++++++++++++++++++---------------- Tests/t_ldapobject.py | 70 +++++++++++++++++++++++++++++++++--- 4 files changed, 152 insertions(+), 57 deletions(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index bbd83db0..135125fa 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -43,37 +43,47 @@ Encoding/decoding to other formats – text, images, etc. – is left to the cal The bytes mode -------------- -The behavior of python-ldap 3.0 in Python 2 is influenced by a ``bytes_mode`` -argument to :func:`ldap.initialize`. -The argument can take these values: +In Python 3, text values are represented as ``str``, the Unicode text type. -``bytes_mode=True``: backwards-compatible +In Python 2, the behavior of python-ldap 3.0 is influenced by a ``bytes_mode`` +argument to :func:`ldap.initialize`: - Text values returned from python-ldap are always bytes (``str``). - Text values supplied to python-ldap may be either bytes or Unicode. - The encoding for bytes is always assumed to be UTF-8. +``bytes_mode=True`` (backwards compatible): + Text values are represented as bytes (``str``) encoded using UTF-8. - Not available in Python 3. +``bytes_mode=False`` (future compatible): + Text values are represented as ``unicode``. -``bytes_mode=False``: strictly future-compatible +If not given explicitly, python-ldap will default to ``bytes_mode=True``, +but if an ``unicode`` value supplied to it, if will warn and use that value. - Text values must be represented as ``unicode``. - An error is raised if python-ldap receives a text value as bytes (``str``). +Backwards-compatible behavior is not scheduled for removal until Python 2 +itself reaches end of life. -Unspecified: relaxed mode with warnings - Causes a warning on Python 2. +Errors, warnings, and automatic encoding +---------------------------------------- - Text values returned from python-ldap are always ``unicode``. - Text values supplied to python-ldap should be ``unicode``; - warnings are emitted when they are not. +While the type of values *returned* from python-ldap is always given by +``bytes_mode``, the behavior for “wrong-type” values *passed in* can be +controlled by the ``bytes_strictness`` argument to :func:`ldap.initialize`: - The warnings are of type :class:`~ldap.LDAPBytesWarning`, which - is a subclass of :class:`BytesWarning` designed to be easily - :ref:`filtered out ` if needed. +``bytes_strictness='error'`` (default if ``bytes_mode`` is specified): + A ``TypeError`` is raised. -Backwards-compatible behavior is not scheduled for removal until Python 2 -itself reaches end of life. +``bytes_strictness='warn'`` (default when ``bytes_mode`` is not given explicitly): + A warning is raised, and the value is encoded/decoded + using the UTF-8 encoding. + + The warnings are of type :class:`~ldap.LDAPBytesWarning`, which + is a subclass of :class:`BytesWarning` designed to be easily + :ref:`filtered out ` if needed. + +``bytes_strictness='silent'``: + The value is automatically encoded/decoded using the UTF-8 encoding. + +When setting ``bytes_strictness``, an explicit value for ``bytes_mode`` needs +to be given as well. Porting recommendations diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 9cb1d520..585c34c3 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -29,7 +29,7 @@ Functions This module defines the following functions: -.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None]]]]) -> LDAPObject object +.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None]]]]]) -> LDAPObject object Initializes a new connection object for accessing the given LDAP server, and return an LDAP object (see :ref:`ldap-objects`) used to perform operations @@ -53,7 +53,8 @@ This module defines the following functions: *trace_file* specifies a file-like object as target of the debug log and *trace_stack_limit* specifies the stack limit of tracebacks in debug log. - The *bytes_mode* argument specifies text/bytes behavior under Python 2. + The *bytes_mode* and *bytes_strictness* arguments specify text/bytes + behavior under Python 2. See :ref:`text-bytes` for a complete documentation. Possible values for *trace_level* are diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index a0712a34..9e92ce66 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -93,7 +93,8 @@ class SimpleLDAPObject: def __init__( self,uri, - trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None + trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, + bytes_strictness=None, ): self._trace_level = trace_level self._trace_file = trace_file or sys.stdout @@ -107,20 +108,26 @@ def __init__( # Bytes mode # ---------- - # By default, raise a TypeError when receiving invalid args - self.bytes_mode_hardfail = True - if bytes_mode is None and PY2: - _raise_byteswarning( - "Under Python 2, python-ldap uses bytes by default. " - "This will be removed in Python 3 (no bytes for DN/RDN/field names). " - "Please call initialize(..., bytes_mode=False) explicitly.") - bytes_mode = True - # Disable hard failure when running in backwards compatibility mode. - self.bytes_mode_hardfail = False - elif bytes_mode and not PY2: - raise ValueError("bytes_mode is *not* supported under Python 3.") - # On by default on Py2, off on Py3. + if PY2: + if bytes_mode is None: + bytes_mode = True + if bytes_strictness is None: + _raise_byteswarning( + "Under Python 2, python-ldap uses bytes by default. " + "This will be removed in Python 3 (no bytes for " + "DN/RDN/field names). " + "Please call initialize(..., bytes_mode=False) explicitly.") + bytes_strictness = 'warn' + else: + if bytes_strictness is None: + bytes_strictness = 'error' + else: + if bytes_mode: + raise ValueError("bytes_mode is *not* supported under Python 3.") + bytes_mode = False + bytes_strictness = 'error' self.bytes_mode = bytes_mode + self.bytes_strictness = bytes_strictness def _bytesify_input(self, arg_name, value): """Adapt a value following bytes_mode in Python 2. @@ -130,38 +137,46 @@ def _bytesify_input(self, arg_name, value): With bytes_mode ON, takes bytes or None and returns bytes or None. With bytes_mode OFF, takes unicode or None and returns bytes or None. - This function should be applied on all text inputs (distinguished names - and attribute names in modlists) to convert them to the bytes expected - by the C bindings. + For the wrong argument type (unicode or bytes, respectively), + behavior depends on the bytes_strictness setting. + In all cases, bytes or None are returned (or an exception is raised). """ if not PY2: return value - if value is None: return value + elif self.bytes_mode: if isinstance(value, bytes): return value + elif self.bytes_strictness == 'silent': + pass + elif self.bytes_strictness == 'warn': + _raise_byteswarning( + "Received non-bytes value for '{}' in bytes mode; " + "please choose an explicit " + "option for bytes_mode on your LDAP connection".format(arg_name)) else: - if self.bytes_mode_hardfail: raise TypeError( "All provided fields *must* be bytes when bytes mode is on; " "got type '{}' for '{}'.".format(type(value).__name__, arg_name) ) - else: - _raise_byteswarning( - "Received non-bytes value for '{}' with default (disabled) bytes mode; " - "please choose an explicit " - "option for bytes_mode on your LDAP connection".format(arg_name)) - return value.encode('utf-8') + return value.encode('utf-8') else: - if not isinstance(value, text_type): + if isinstance(value, unicode): + return value.encode('utf-8') + elif self.bytes_strictness == 'silent': + pass + elif self.bytes_strictness == 'warn': + _raise_byteswarning( + "Received non-text value for '{}' with bytes_mode off and " + "bytes_strictness='warn'".format(arg_name)) + else: raise TypeError( "All provided fields *must* be text when bytes mode is off; " "got type '{}' for '{}'.".format(type(value).__name__, arg_name) ) - assert not isinstance(value, bytes) - return value.encode('utf-8') + return value def _bytesify_modlist(self, arg_name, modlist, with_opcode): """Adapt a modlist according to bytes_mode. @@ -1064,7 +1079,7 @@ class ReconnectLDAPObject(SimpleLDAPObject): def __init__( self,uri, trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, - retry_max=1,retry_delay=60.0 + bytes_strictness=None, retry_max=1, retry_delay=60.0 ): """ Parameters like SimpleLDAPObject.__init__() with these @@ -1078,7 +1093,9 @@ def __init__( self._uri = uri self._options = [] self._last_bind = None - SimpleLDAPObject.__init__(self,uri,trace_level,trace_file,trace_stack_limit,bytes_mode) + SimpleLDAPObject.__init__(self, uri, trace_level, trace_file, + trace_stack_limit, bytes_mode, + bytes_strictness=bytes_strictness) self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) self._retry_max = retry_max self._retry_delay = retry_delay @@ -1097,6 +1114,11 @@ def __getstate__(self): def __setstate__(self,d): """set up the object from pickled data""" + hardfail = d.get('bytes_mode_hardfail') + if hardfail: + d.setdefault('bytes_strictness', 'error') + else: + d.setdefault('bytes_strictness', 'warn') self.__dict__.update(d) self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2] self._ldap_object_lock = self._ldap_lock() diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 1c847428..0a8e78ef 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -162,9 +162,9 @@ def test_search_keys_are_text(self): for value in values: self.assertEqual(type(value), bytes) - def _get_bytes_ldapobject(self, explicit=True): + def _get_bytes_ldapobject(self, explicit=True, **kwargs): if explicit: - kwargs = {'bytes_mode': True} + kwargs.setdefault('bytes_mode', True) else: kwargs = {} return self._open_ldap_conn( @@ -231,6 +231,68 @@ def test_unset_bytesmode_search_warns_bytes(self): l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) + def _search_wrong_type(self, bytes_mode, strictness): + if bytes_mode: + l = self._get_bytes_ldapobject(bytes_strictness=strictness) + else: + l = self._open_ldap_conn(bytes_mode=False, + bytes_strictness=strictness) + base = 'cn=Foo1,' + self.server.suffix + if not bytes_mode: + base = base.encode('utf-8') + result = l.search_s(base, scope=ldap.SCOPE_SUBTREE) + return result[0][-1]['cn'] + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_bytesmode_silent(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + self._search_wrong_type(bytes_mode=True, strictness='silent') + self.assertEqual(w, []) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_bytesmode_warn(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + self._search_wrong_type(bytes_mode=True, strictness='warn') + self.assertEqual(len(w), 1) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_bytesmode_error(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + with self.assertRaises(TypeError): + self._search_wrong_type(bytes_mode=True, strictness='error') + self.assertEqual(w, []) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_textmode_silent(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + self._search_wrong_type(bytes_mode=True, strictness='silent') + self.assertEqual(w, []) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_textmode_warn(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + self._search_wrong_type(bytes_mode=True, strictness='warn') + self.assertEqual(len(w), 1) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_textmode_error(self): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', ldap.LDAPBytesWarning) + with self.assertRaises(TypeError): + self._search_wrong_type(bytes_mode=True, strictness='error') + self.assertEqual(w, []) + def test_search_accepts_unicode_dn(self): base = self.server.suffix l = self._ldap_conn @@ -470,7 +532,7 @@ def test_ldapbyteswarning(self): self.assertIs(msg.category, ldap.LDAPBytesWarning) self.assertEqual( text_type(msg.message), - "Received non-bytes value for 'base' with default (disabled) bytes " + "Received non-bytes value for 'base' in bytes " "mode; please choose an explicit option for bytes_mode on your " "LDAP connection" ) @@ -632,7 +694,7 @@ def test103_reconnect_get_state(self): str('_trace_stack_limit'): 5, str('_uri'): self.server.ldap_uri, str('bytes_mode'): l1.bytes_mode, - str('bytes_mode_hardfail'): l1.bytes_mode_hardfail, + str('bytes_strictness'): l1.bytes_strictness, str('timeout'): -1, }, ) From 671d9f1722a2c8f6c8a15e8c240a8a1f6b3a898f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 30 Jan 2018 12:11:26 +0100 Subject: [PATCH 008/206] Link with lber setup.cfg overrides settings in setup.py. Include lber in list of libraries in setup.cfg, too. https://github.com/python-ldap/python-ldap/pull/173 Signed-off-by: Christian Heimes --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 374dd42e..87cdfd7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ extra_objects = # Example for full-featured build: # Support for StartTLS/LDAPS, SASL bind and reentrant libldap_r. -libs = ldap_r +libs = ldap_r lber # Installation options [install] From ef0c5140c7599a4ac02af11dd81c719d8f7b4828 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2018 11:00:36 +0100 Subject: [PATCH 009/206] Document that bytes_strictness is ignored on Python 3. Thi is done to better support programs compatible with both py2 and py3. --- Doc/bytes_mode.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index 135125fa..d7c2aa4f 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -65,8 +65,9 @@ Errors, warnings, and automatic encoding ---------------------------------------- While the type of values *returned* from python-ldap is always given by -``bytes_mode``, the behavior for “wrong-type” values *passed in* can be -controlled by the ``bytes_strictness`` argument to :func:`ldap.initialize`: +``bytes_mode``, for Python 2 the behavior for “wrong-type” values *passed in* +can be controlled by the ``bytes_strictness`` argument to +:func:`ldap.initialize`: ``bytes_strictness='error'`` (default if ``bytes_mode`` is specified): A ``TypeError`` is raised. @@ -82,6 +83,9 @@ controlled by the ``bytes_strictness`` argument to :func:`ldap.initialize`: ``bytes_strictness='silent'``: The value is automatically encoded/decoded using the UTF-8 encoding. +On Python 3, ``bytes_strictness`` is ignored and a ``TypeError`` is always +raised. + When setting ``bytes_strictness``, an explicit value for ``bytes_mode`` needs to be given as well. From 2920ac2e3748ea8c96419bb7f88036d2e4507398 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2018 11:26:40 +0100 Subject: [PATCH 010/206] Doc: Link to bytes mode from text-string arguments in the ldap module --- Doc/reference/ldap.rst | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 585c34c3..5d15158e 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -697,6 +697,9 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + The *dn* argument, and mod_type (second item) of *modlist* are text strings; + see :ref:`bytes_mode`. + .. py:method:: LDAPObject.bind(who, cred, method) -> int @@ -738,6 +741,8 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + The *dn* and *attr* arguments are text strings; see :ref:`bytes_mode`. + .. note:: A design fault in the LDAP API prevents *value* @@ -758,6 +763,8 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + The *dn* argument is text string; see :ref:`bytes_mode`. + .. py:method:: LDAPObject.extop(extreq[,serverctrls=None[,clientctrls=None]]]) -> int @@ -811,6 +818,9 @@ and wait for and return with the server's result, or with You might want to look into sub-module :py:mod:`ldap.modlist` for generating *modlist*. + The *dn* argument, and mod_type (second item) of *modlist* are text strings; + see :ref:`bytes_mode`. + .. py:method:: LDAPObject.modrdn(dn, newrdn [, delold=1]) -> int @@ -827,6 +837,8 @@ and wait for and return with the server's result, or with This operation is emulated by :py:meth:`rename()` and :py:meth:`rename_s()` methods since the modrdn2* routines in the C library are deprecated. + The *dn* and *newrdn* arguments are text strings; see :ref:`bytes_mode`. + .. py:method:: LDAPObject.passwd(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> int @@ -845,6 +857,8 @@ and wait for and return with the server's result, or with The asynchronous version returns the initiated message id. + The *user*, *oldpw* and *newpw* arguments are text strings; see :ref:`bytes_mode`. + .. seealso:: :rfc:`3062` - LDAP Password Modify Extended Operation @@ -866,6 +880,8 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + The *dn* and *newdn* arguments are text strings; see :ref:`bytes_mode`. + .. py:method:: LDAPObject.result([msgid=RES_ANY [, all=1 [, timeout=None]]]) -> 2-tuple @@ -1016,12 +1032,13 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + The *who* and *cred* arguments are text strings; see :ref:`bytes_mode`. + .. versionchanged:: 3.0 :meth:`~LDAPObject.simple_bind` and :meth:`~LDAPObject.simple_bind_s` now accept ``None`` for *who* and *cred*, too. - .. py:method:: LDAPObject.search(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->int .. py:method:: LDAPObject.search_s(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->list|None @@ -1074,6 +1091,9 @@ and wait for and return with the server's result, or with or :py:meth:`search_ext_s()` (client-side search limit). If non-zero not more than *sizelimit* results are returned by the server. + The *base* and *filterstr* arguments, and *attrlist* contents, + are text strings; see :ref:`bytes_mode`. + .. versionchanged:: 3.0 ``filterstr=None`` is equivalent to ``filterstr='(objectClass=*)'``. From 6e0dca67594dd90270864fcf24780472458ae642 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Mar 2018 15:53:36 +0100 Subject: [PATCH 011/206] Add changelog and bump version to 3.0.0 --- CHANGES | 33 +++++++++++++++++++++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 03d892f5..7a5e102d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,36 @@ +---------------------------------------------------------------- +Released 3.0.0 2018-03-12 + +Notable changes since 2.4.45 (please see detailed logs below): +* Python 3 support and bytes_mode + see: https://python-ldap.readthedocs.io/en/latest/bytes_mode.html +* The module `ldap.async` is renamed to `ldap.asyncsearch` +* New dependencies: pyasn1, pyasn1_modules +* Dropped support for Python 2.6 and 3.3 + + +Changes since 3.0.0b4: + +Lib/ +* Add bytes_strictness to allow configuring behavior on bytes/text mismatch + +Modules/ +* Add argument name to bytes mode TypeError +* Use correct integer types for BER encode/decode (fix for big endian machines) + +Test/ +* Set $LDAPNOINIT in all tests +* Add test for secure TLS default +* Ignore SASL methods in DSE test (fix for restricted environments) +* Remove filterstr workaround from syncrepl test +* Explicitly set TLS_REQUIRE_CERT option to TLS_HARD in test_tls_ext_noca + +Doc/ +* Link to bytes mode from text-string arguments in the ldap module + +Infrastructure: +* Include lber in list of libraries in setup.cfg + ---------------------------------------------------------------- Released 3.0.0b4 2018-01-10 diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 24e2017c..47199e92 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.0.0b4' +__version__ = '3.0.0' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index ca28de3d..b3e11712 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0b4' +__version__ = '3.0.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 29c118c3..cdcccc0a 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.0.0b4' +__version__ = '3.0.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 727aa528..82970243 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -5,7 +5,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0b4' +__version__ = '3.0.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 361b1fb48c3ced4ff44b77570df9ed1b946f9b61 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Mar 2018 15:57:55 +0100 Subject: [PATCH 012/206] slapdtest: Fix error message for missing commands --- Lib/slapdtest/_slapdtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 484eb54b..adcccb80 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -254,7 +254,7 @@ def _find_command(self, cmd, in_sbin=False): if command is None: raise ValueError( "Command '{}' not found. Set the {} environment variable to " - "override slapdtest's search path.".format(value, var_name) + "override slapdtest's search path.".format(cmd, var_name) ) return command From f3868eaabc1c35c487934856257916ee968b7616 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 13 Mar 2018 17:06:41 +0100 Subject: [PATCH 013/206] Doc: Remove warning about unreleased version --- Doc/installing.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index bb06c212..6df062f0 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -5,13 +5,6 @@ Installing python-ldap ###################### -.. warning:: - - You are reading documentation for an unreleased version. - - Following these instructions will currently get you version 2.5.2, - which does not support Python 3. - Installing from PyPI ==================== From e33ad515aa29cc3fbaa1d81fe9d3092f67ff0f34 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 22 Nov 2017 13:43:56 +0100 Subject: [PATCH 014/206] Add indent.pro from cpython --- .indent.pro | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .indent.pro diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 00000000..02cceb62 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,24 @@ +--blank-lines-after-declarations +--blank-lines-after-procedures +--braces-after-func-def-line +--braces-on-if-line +--braces-on-struct-decl-line +--break-after-boolean-operator +--comment-indentation25 +--comment-line-length79 +--continue-at-parentheses +--dont-cuddle-do-while +--dont-cuddle-else +--indent-level4 +--line-length79 +--no-space-after-casts +--no-space-after-function-call-names +--no-space-after-parentheses +--no-tabs +--procnames-start-lines +--space-after-for +--space-after-if +--space-after-while +--swallow-optional-blank-lines +-T PyCFunction +-T PyObject From b0f364247cb2a42841d175b8949330f4eb360335 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 22 Nov 2017 13:57:22 +0100 Subject: [PATCH 015/206] Add Makefile and LDAP related types to indent.pro Makefile contains targets to run indent and autopep8. --- .indent.pro | 7 +++++++ Makefile | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.indent.pro b/.indent.pro index 02cceb62..4e8adc1b 100644 --- a/.indent.pro +++ b/.indent.pro @@ -22,3 +22,10 @@ --swallow-optional-blank-lines -T PyCFunction -T PyObject +-T PyMethodDef +-T LDAP +-T LDAPMod +-T LDAPMessage +-T LDAPControl +-T LDAPObject +-T sasl_interact_t diff --git a/Makefile b/Makefile index e4ff75ac..c4c2ca11 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ LCOV_REPORT_OPTIONS=--show-details -no-branch-coverage \ --title "python-ldap LCOV report" SCAN_REPORT=build/scan_report PYTHON_SUPP=/usr/share/doc/python3-devel/valgrind-python.supp +AUTOPEP8_OPTS=--aggressive .NOTPARALLEL: @@ -13,7 +14,7 @@ all: .PHONY: clean clean: - rm -rf build dist *.egg-info $(VENV) .tox MANIFEST + rm -rf build dist *.egg-info .tox MANIFEST rm -f .coverage .coverage.* find . \( -name '*.py[co]' -or -name '*.so*' -or -name '*.dylib' \) \ -delete @@ -77,3 +78,14 @@ valgrind: build $(PYTHON_SUPP) echo "Found definitive leak, see build/valgrind.log"; \ exit 1; \ fi + +# Code autoformatter +.PHONY: indent +indent: + indent Modules/*.c Modules/*.h + rm -f Modules/*.c~ Modules/*.h~ + +.PHONY: autopep8 +autopep8: + $(PYTHON) -m autopep8 -r -i -j0 $(AUTOPEP8_OPTS) \ + Demo Lib Tests setup.py From db242769139785e59cec1c5f8de95a2c2bae0a64 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 12:49:26 +0100 Subject: [PATCH 016/206] Makefile: Add "autoformat" rule to format all the code --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c4c2ca11..2d3293e6 100644 --- a/Makefile +++ b/Makefile @@ -80,12 +80,13 @@ valgrind: build $(PYTHON_SUPP) fi # Code autoformatter -.PHONY: indent +.PHONY: autoformat indent autopep8 +autoformat: indent autopep8 + indent: indent Modules/*.c Modules/*.h rm -f Modules/*.c~ Modules/*.h~ -.PHONY: autopep8 autopep8: $(PYTHON) -m autopep8 -r -i -j0 $(AUTOPEP8_OPTS) \ Demo Lib Tests setup.py From 61c21a089972c533517897e5f7028981d7f3abb4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 13 Dec 2017 18:11:59 +0100 Subject: [PATCH 017/206] Add tests and coverage for tracing python-ldap can trace LDAP calls, but it was not possible to use the feature without changing code manually. Tracing can now be enabled with the env var PYTHON_LDAP_TRACE_LEVEL and redirected to a file with PYTHON_LDAP_TRACE_FILE. Signed-off-by: Christian Heimes --- Lib/ldap/__init__.py | 11 +++++++++-- Lib/ldap/ldapobject.py | 7 ++++--- Tests/t_ldapobject.py | 2 +- tox.ini | 11 ++++++++++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 3a86095f..72e1e4bc 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -8,13 +8,20 @@ from ldap.pkginfo import __version__, __author__, __license__ +import os import sys if __debug__: # Tracing is only supported in debugging mode + import atexit import traceback - _trace_level = 0 - _trace_file = sys.stderr + _trace_level = int(os.environ.get("PYTHON_LDAP_TRACE_LEVEL", 0)) + _trace_file = os.environ.get("PYTHON_LDAP_TRACE_FILE") + if _trace_file is None: + _trace_file = sys.stderr + else: + _trace_file = open(_trace_file, 'a') + atexit.register(_trace_file.close) _trace_stack_limit = None import _ldap diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 9e92ce66..36bf034f 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -96,8 +96,8 @@ def __init__( trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, bytes_strictness=None, ): - self._trace_level = trace_level - self._trace_file = trace_file or sys.stdout + self._trace_level = trace_level or ldap._trace_level + self._trace_file = trace_file or ldap._trace_file self._trace_stack_limit = trace_stack_limit self._uri = uri self._ldap_object_lock = self._ldap_lock('opcall') @@ -1123,7 +1123,8 @@ def __setstate__(self,d): self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2] self._ldap_object_lock = self._ldap_lock() self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) - self._trace_file = sys.stdout + # XXX cannot pickle file, use default trace file + self._trace_file = ldap._trace_file self.reconnect(self._uri) def _store_last_bind(self,method,*args,**kwargs): diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 0a8e78ef..33c2c5a9 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -690,7 +690,7 @@ def test103_reconnect_get_state(self): str('_retry_delay'): 60.0, str('_retry_max'): 1, str('_start_tls'): 0, - str('_trace_level'): 0, + str('_trace_level'): ldap._trace_level, str('_trace_stack_limit'): 5, str('_uri'): self.server.ldap_uri, str('bytes_mode'): l1.bytes_mode, diff --git a/tox.ini b/tox.ini index abba0b01..bb9adac3 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report +envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,py3-trace,coverage-report minver = 1.8 [testenv] @@ -49,6 +49,15 @@ passenv = {[testenv]passenv} setenv = {[testenv:py2-nosasltls]setenv} commands = {[testenv:py2-nosasltls]commands} +[testenv:py3-trace] +basepython = python3 +deps = {[testenv]deps} +passenv = {[testenv]passenv} +setenv = + PYTHON_LDAP_TRACE_LEVEL=9 + PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log +commands = {[testenv]commands} + [testenv:pypy] # PyPy doesn't have working setup.py test deps = pytest From 06800b3307bb08d99252b4dbd4d6566497485c61 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 13:42:30 +0100 Subject: [PATCH 018/206] Add py3-trace tox environment to Travis CI config --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5479573f..3c7e9892 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,9 @@ matrix: env: - TOXENV=py3-nosasltls - WITH_GCOV=1 + - python: 3.6 + env: + - TOXENV=py3-trace - python: 3.6 env: TOXENV=doc allow_failures: From 0473e8764215a6586aef36d470f4eaa7f81e3fdc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 12:54:24 +0100 Subject: [PATCH 019/206] Doc: Add `make autoformat` to the contributing guide --- Doc/contributing.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 9061332d..badb9315 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -102,6 +102,17 @@ Specify a different one using, for example:: Notable targets are: +``make autoformat`` + Automatically re-formats C and Python code to conform to Python style + guides (`PEP 7`_ and `PEP 8`_). + Note that no backups are made – please commit any other changes before + using this target. + + Requires the ``indent`` program and the ``autopep8`` Python module. + +.. _PEP 7: https://www.python.org/dev/peps/pep-0007/ +.. _PEP 8: https://www.python.org/dev/peps/pep-0008/ + ``make lcov lcov-open`` Generate and view test coverage for C code. Requires LCOV_. From 9a74c29012f9173197b717b559214c4c21cf57ce Mon Sep 17 00:00:00 2001 From: automatic Date: Wed, 14 Mar 2018 14:44:03 +0100 Subject: [PATCH 020/206] Reformat and indent all C files Generated with: make indent make indent --- Modules/LDAPObject.c | 1076 ++++++++++++++++++--------------- Modules/LDAPObject.h | 19 +- Modules/berval.h | 4 +- Modules/common.c | 15 +- Modules/common.h | 10 +- Modules/constants.c | 189 +++--- Modules/constants.h | 12 +- Modules/constants_generated.h | 35 +- Modules/functions.c | 130 ++-- Modules/functions.h | 2 +- Modules/ldapcontrol.c | 210 ++++--- Modules/ldapcontrol.h | 6 +- Modules/ldapmodule.c | 81 +-- Modules/message.c | 473 ++++++++------- Modules/message.h | 7 +- Modules/options.c | 258 ++++---- Modules/options.h | 2 +- 17 files changed, 1320 insertions(+), 1209 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index d7265a42..2bd6209c 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -16,15 +16,16 @@ #include #endif -static void free_attrs(char***); +static void free_attrs(char ***); /* constructor */ -LDAPObject* -newLDAPObject( LDAP* l ) +LDAPObject * +newLDAPObject(LDAP *l) { - LDAPObject* self = (LDAPObject*) PyObject_NEW(LDAPObject, &LDAP_Type); - if (self == NULL) + LDAPObject *self = (LDAPObject *)PyObject_NEW(LDAPObject, &LDAP_Type); + + if (self == NULL) return NULL; self->ldap = l; self->_save = NULL; @@ -35,13 +36,13 @@ newLDAPObject( LDAP* l ) /* destructor */ static void -dealloc( LDAPObject* self ) +dealloc(LDAPObject *self) { if (self->ldap) { if (self->valid) { - LDAP_BEGIN_ALLOW_THREADS( self ); - ldap_unbind_ext( self->ldap, NULL, NULL ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldap_unbind_ext(self->ldap, NULL, NULL); + LDAP_END_ALLOW_THREADS(self); self->valid = 0; } self->ldap = NULL; @@ -59,11 +60,13 @@ dealloc( LDAPObject* self ) */ static int -not_valid( LDAPObject* l ) { +not_valid(LDAPObject *l) +{ if (l->valid) { return 0; - } else { - PyErr_SetString( LDAPexception_class, "LDAP connection invalid" ); + } + else { + PyErr_SetString(LDAPexception_class, "LDAP connection invalid"); return 1; } } @@ -71,7 +74,7 @@ not_valid( LDAPObject* l ) { /* free a LDAPMod (complete or partially) allocated in Tuple_to_LDAPMod() */ static void -LDAPMod_DEL( LDAPMod* lm ) +LDAPMod_DEL(LDAPMod *lm) { Py_ssize_t i; @@ -98,8 +101,8 @@ LDAPMod_DEL( LDAPMod* lm ) /* XXX - there is no way to pass complex-structured BER objects in here! */ -static LDAPMod* -Tuple_to_LDAPMod( PyObject* tup, int no_op ) +static LDAPMod * +Tuple_to_LDAPMod(PyObject *tup, int no_op) { int op; char *type; @@ -113,12 +116,13 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) } if (no_op) { - if (!PyArg_ParseTuple( tup, "sO:Tuple_to_LDAPMod", &type, &list )) - return NULL; + if (!PyArg_ParseTuple(tup, "sO:Tuple_to_LDAPMod", &type, &list)) + return NULL; op = 0; - } else { - if (!PyArg_ParseTuple( tup, "isO:Tuple_to_LDAPMod", &op, &type, &list )) - return NULL; + } + else { + if (!PyArg_ParseTuple(tup, "isO:Tuple_to_LDAPMod", &op, &type, &list)) + return NULL; } lm = PyMem_NEW(LDAPMod, 1); @@ -130,43 +134,52 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) len = strlen(type); lm->mod_type = PyMem_NEW(char, len + 1); + if (lm->mod_type == NULL) goto nomem; memcpy(lm->mod_type, type, len + 1); if (list == Py_None) { /* None indicates a NULL mod_bvals */ - } else if (PyBytes_Check(list)) { + } + else if (PyBytes_Check(list)) { /* Single string is a singleton list */ lm->mod_bvalues = PyMem_NEW(struct berval *, 2); + if (lm->mod_bvalues == NULL) goto nomem; lm->mod_bvalues[0] = PyMem_NEW(struct berval, 1); + if (lm->mod_bvalues[0] == NULL) goto nomem; lm->mod_bvalues[1] = NULL; lm->mod_bvalues[0]->bv_len = PyBytes_Size(list); lm->mod_bvalues[0]->bv_val = PyBytes_AsString(list); - } else if (PySequence_Check(list)) { + } + else if (PySequence_Check(list)) { nstrs = PySequence_Length(list); lm->mod_bvalues = PyMem_NEW(struct berval *, nstrs + 1); + if (lm->mod_bvalues == NULL) goto nomem; for (i = 0; i < nstrs; i++) { - lm->mod_bvalues[i] = PyMem_NEW(struct berval, 1); - if (lm->mod_bvalues[i] == NULL) - goto nomem; - lm->mod_bvalues[i+1] = NULL; - item = PySequence_GetItem(list, i); - if (item == NULL) - goto error; - if (!PyBytes_Check(item)) { - LDAPerror_TypeError("Tuple_to_LDAPMod(): expected a byte string in the list", item); - goto error; - } - lm->mod_bvalues[i]->bv_len = PyBytes_Size(item); - lm->mod_bvalues[i]->bv_val = PyBytes_AsString(item); - Py_DECREF(item); + lm->mod_bvalues[i] = PyMem_NEW(struct berval, 1); + + if (lm->mod_bvalues[i] == NULL) + goto nomem; + lm->mod_bvalues[i + 1] = NULL; + item = PySequence_GetItem(list, i); + if (item == NULL) + goto error; + if (!PyBytes_Check(item)) { + LDAPerror_TypeError + ("Tuple_to_LDAPMod(): expected a byte string in the list", + item); + goto error; + } + lm->mod_bvalues[i]->bv_len = PyBytes_Size(item); + lm->mod_bvalues[i]->bv_val = PyBytes_AsString(item); + Py_DECREF(item); } if (nstrs == 0) lm->mod_bvalues[0] = NULL; @@ -174,10 +187,10 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) return lm; -nomem: + nomem: PyErr_NoMemory(); -error: - if (lm) + error: + if (lm) LDAPMod_DEL(lm); return NULL; @@ -186,10 +199,12 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) /* free the structure allocated in List_to_LDAPMods() */ static void -LDAPMods_DEL( LDAPMod** lms ) { - LDAPMod** lmp; - for ( lmp = lms; *lmp; lmp++ ) - LDAPMod_DEL( *lmp ); +LDAPMods_DEL(LDAPMod **lms) +{ + LDAPMod **lmp; + + for (lmp = lms; *lmp; lmp++) + LDAPMod_DEL(*lmp); PyMem_DEL(lms); } @@ -198,33 +213,36 @@ LDAPMods_DEL( LDAPMod** lms ) { * NOTE: list of tuples must live longer than the LDAPMods */ -static LDAPMod** -List_to_LDAPMods( PyObject *list, int no_op ) { +static LDAPMod ** +List_to_LDAPMods(PyObject *list, int no_op) +{ Py_ssize_t i, len; - LDAPMod** lms; + LDAPMod **lms; PyObject *item; if (!PySequence_Check(list)) { - LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", list); + LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", + list); return NULL; } len = PySequence_Length(list); if (len < 0) { - LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", list); + LDAPerror_TypeError("List_to_LDAPMods(): expected list of tuples", + list); return NULL; } lms = PyMem_NEW(LDAPMod *, len + 1); - if (lms == NULL) + if (lms == NULL) goto nomem; for (i = 0; i < len; i++) { lms[i] = NULL; item = PySequence_GetItem(list, i); - if (item == NULL) + if (item == NULL) goto error; lms[i] = Tuple_to_LDAPMod(item, no_op); Py_DECREF(item); @@ -234,9 +252,9 @@ List_to_LDAPMods( PyObject *list, int no_op ) { lms[len] = NULL; return lms; -nomem: + nomem: PyErr_NoMemory(); -error: + error: if (lms) LDAPMods_DEL(lms); return NULL; @@ -248,7 +266,8 @@ List_to_LDAPMods( PyObject *list, int no_op ) { */ int -attrs_from_List( PyObject *attrlist, char***attrsp) { +attrs_from_List(PyObject *attrlist, char ***attrsp) +{ char **attrs = NULL; PyObject *seq = NULL; @@ -256,17 +275,22 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { if (attrlist == Py_None) { /* None means a NULL attrlist */ #if PY_MAJOR_VERSION == 2 - } else if (PyBytes_Check(attrlist)) { + } + else if (PyBytes_Check(attrlist)) { #else - } else if (PyUnicode_Check(attrlist)) { + } + else if (PyUnicode_Check(attrlist)) { #endif /* caught by John Benninghoff */ - LDAPerror_TypeError( - "attrs_from_List(): expected *list* of strings, not a string", attrlist); + LDAPerror_TypeError + ("attrs_from_List(): expected *list* of strings, not a string", + attrlist); goto error; - } else { + } + else { PyObject *item = NULL; Py_ssize_t i, len, strlen; + #if PY_MAJOR_VERSION >= 3 const char *str; #else @@ -280,8 +304,9 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { len = PySequence_Length(attrlist); attrs = PyMem_NEW(char *, len + 1); + if (attrs == NULL) - goto nomem; + goto nomem; for (i = 0; i < len; i++) { attrs[i] = NULL; @@ -291,7 +316,8 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { #if PY_MAJOR_VERSION == 2 /* Encoded in Python to UTF-8 */ if (!PyBytes_Check(item)) { - LDAPerror_TypeError("attrs_from_List(): expected bytes in list", item); + LDAPerror_TypeError + ("attrs_from_List(): expected bytes in list", item); goto error; } if (PyBytes_AsStringAndSize(item, &str, &strlen) == -1) { @@ -299,7 +325,8 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { } #else if (!PyUnicode_Check(item)) { - LDAPerror_TypeError("attrs_from_List(): expected string in list", item); + LDAPerror_TypeError + ("attrs_from_List(): expected string in list", item); goto error; } str = PyUnicode_AsUTF8AndSize(item, &strlen); @@ -309,6 +336,7 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { * 3.7 actually returns a const char. */ attrs[i] = (char *)PyMem_NEW(char *, strlen + 1); + if (attrs[i] == NULL) goto nomem; memcpy(attrs[i], str, strlen + 1); @@ -320,9 +348,9 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { *attrsp = attrs; return 1; -nomem: + nomem: PyErr_NoMemory(); -error: + error: Py_XDECREF(seq); free_attrs(&attrs); return 0; @@ -331,7 +359,8 @@ attrs_from_List( PyObject *attrlist, char***attrsp) { /* free memory allocated from above routine */ static void -free_attrs( char*** attrsp) { +free_attrs(char ***attrsp) +{ char **attrs = *attrsp; char **p; @@ -351,18 +380,20 @@ free_attrs( char*** attrsp) { /* ldap_unbind_ext */ -static PyObject* -l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_unbind_ext(LDAPObject *self, PyObject *args) { PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int ldaperror; - if (!PyArg_ParseTuple( args, "|OO:unbind_ext", &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple(args, "|OO:unbind_ext", &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -371,20 +402,20 @@ l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_unbind_ext( self->ldap, server_ldcs, client_ldcs ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_unbind_ext(self->ldap, server_ldcs, client_ldcs); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_unbind_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_unbind_ext"); self->valid = 0; Py_INCREF(Py_None); @@ -393,19 +424,22 @@ l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) /* ldap_abandon_ext */ -static PyObject* -l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_abandon_ext(LDAPObject *self, PyObject *args) { int msgid; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int ldaperror; - if (!PyArg_ParseTuple( args, "i|OO:abandon_ext", &msgid, &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple + (args, "i|OO:abandon_ext", &msgid, &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -414,20 +448,20 @@ l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_abandon_ext( self->ldap, msgid, server_ldcs, client_ldcs ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_abandon_ext(self->ldap, msgid, server_ldcs, client_ldcs); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_abandon_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_abandon_ext"); Py_INCREF(Py_None); return Py_None; @@ -436,58 +470,62 @@ l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) /* ldap_add_ext */ static PyObject * -l_ldap_add_ext( LDAPObject* self, PyObject *args ) +l_ldap_add_ext(LDAPObject *self, PyObject *args) { char *dn; PyObject *modlist; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; LDAPMod **mods; - if (!PyArg_ParseTuple( args, "sO|OO:add_ext", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple + (args, "sO|OO:add_ext", &dn, &modlist, &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; - mods = List_to_LDAPMods( modlist, 1 ); + mods = List_to_LDAPMods(modlist, 1); if (mods == NULL) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { - LDAPMods_DEL( mods ); + LDAPMods_DEL(mods); return NULL; } } if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPMods_DEL( mods ); - LDAPControl_List_DEL( server_ldcs ); + LDAPMods_DEL(mods); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_add_ext( self->ldap, dn, mods, server_ldcs, client_ldcs, &msgid); - LDAP_END_ALLOW_THREADS( self ); - LDAPMods_DEL( mods ); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_add_ext(self->ldap, dn, mods, server_ldcs, client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); + LDAPMods_DEL(mods); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_add_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_add_ext"); return PyInt_FromLong(msgid); } /* ldap_simple_bind */ -static PyObject* -l_ldap_simple_bind( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_simple_bind(LDAPObject *self, PyObject *args) { char *who; int msgid; @@ -495,14 +533,18 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) Py_ssize_t cred_len; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; struct berval cred; - if (!PyArg_ParseTuple( args, "zz#|OO:simple_bind", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple + (args, "zz#|OO:simple_bind", &who, &cred.bv_val, &cred_len, + &serverctrls, &clientctrls)) + return NULL; cred.bv_len = (ber_len_t) cred_len; - if (not_valid(self)) return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -511,25 +553,26 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_sasl_bind( self->ldap, who, LDAP_SASL_SIMPLE, &cred, server_ldcs, client_ldcs, &msgid); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_sasl_bind(self->ldap, who, LDAP_SASL_SIMPLE, &cred, server_ldcs, + client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_simple_bind" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_simple_bind"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } - #ifdef HAVE_SASL /* The following functions implement SASL binds. A new method sasl_interactive_bind_s(bind_dn, sasl_mechanism) has been introduced. @@ -566,46 +609,41 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) argument specifies, which information should be passed back to the SASL lib (see SASL_CB_xxx in sasl.h) */ -static int interaction ( unsigned flags, - sasl_interact_t *interact, - PyObject* SASLObject ) +static int +interaction(unsigned flags, sasl_interact_t *interact, PyObject *SASLObject) { /* const char *dflt = interact->defresult; */ - PyObject *result; - char *c_result; - result = PyObject_CallMethod(SASLObject, - "callback", - "isss", - interact->id, /* see sasl.h */ - interact->challenge, - interact->prompt, - interact->defresult); - - if (result == NULL) - /*searching for a better error code */ - return LDAP_OPERATIONS_ERROR; - c_result = PyBytes_AsString(result); /*xxx Error checking?? */ - - /* according to the sasl docs, we should malloc() the returned - string only for calls where interact->id == SASL_CB_PASS, so we - probably leak a few bytes per ldap bind. However, if I restrict - the strdup() to this case, I get segfaults. Should probably be - fixed sometimes. - */ - interact->result = strdup( c_result ); - if (interact->result == NULL) - return LDAP_OPERATIONS_ERROR; - interact->len = strlen(c_result); - /* We _should_ overwrite the python string buffer for security - reasons, however we may not (api/stringObjects.html). Any ideas? - */ - - Py_DECREF(result); /*not needed any longer */ - result = NULL; - - return LDAP_SUCCESS; -} + PyObject *result; + char *c_result; + + result = PyObject_CallMethod(SASLObject, "callback", "isss", interact->id, /* see sasl.h */ + interact->challenge, + interact->prompt, interact->defresult); + + if (result == NULL) + /*searching for a better error code */ + return LDAP_OPERATIONS_ERROR; + c_result = PyBytes_AsString(result); /*xxx Error checking?? */ + + /* according to the sasl docs, we should malloc() the returned + string only for calls where interact->id == SASL_CB_PASS, so we + probably leak a few bytes per ldap bind. However, if I restrict + the strdup() to this case, I get segfaults. Should probably be + fixed sometimes. + */ + interact->result = strdup(c_result); + if (interact->result == NULL) + return LDAP_OPERATIONS_ERROR; + interact->len = strlen(c_result); + /* We _should_ overwrite the python string buffer for security + reasons, however we may not (api/stringObjects.html). Any ideas? + */ + Py_DECREF(result); /*not needed any longer */ + result = NULL; + + return LDAP_SUCCESS; +} /* This function will be called by ldap_sasl_interactive_bind(). The @@ -615,26 +653,27 @@ static int interaction ( unsigned flags, */ -int py_ldap_sasl_interaction( LDAP *ld, - unsigned flags, - void *defaults, - void *in ) +int +py_ldap_sasl_interaction(LDAP *ld, unsigned flags, void *defaults, void *in) { - /* These are just typecasts */ - sasl_interact_t *interact = (sasl_interact_t *) in; - PyObject *SASLObject = (PyObject *) defaults; - /* Loop over the array of sasl_interact_t structs */ - while( interact->id != SASL_CB_LIST_END ) { - int rc = 0; - rc = interaction( flags, interact, SASLObject ); - if( rc ) return rc; - interact++; - } - return LDAP_SUCCESS; + /* These are just typecasts */ + sasl_interact_t *interact = (sasl_interact_t *)in; + PyObject *SASLObject = (PyObject *)defaults; + + /* Loop over the array of sasl_interact_t structs */ + while (interact->id != SASL_CB_LIST_END) { + int rc = 0; + + rc = interaction(flags, interact, SASLObject); + if (rc) + return rc; + interact++; + } + return LDAP_SUCCESS; } -static PyObject* -l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_sasl_bind_s(LDAPObject *self, PyObject *args) { const char *dn; const char *mechanism; @@ -643,16 +682,19 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; struct berval *servercred; int ldaperror; - if (!PyArg_ParseTuple(args, "zzz#OO:sasl_bind_s", &dn, &mechanism, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple + (args, "zzz#OO:sasl_bind_s", &dn, &mechanism, &cred.bv_val, &cred_len, + &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self)) + return NULL; cred.bv_len = cred_len; @@ -662,44 +704,45 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); ldaperror = ldap_sasl_bind_s(self->ldap, dn, mechanism, cred.bv_val ? &cred : NULL, - (LDAPControl**) server_ldcs, - (LDAPControl**) client_ldcs, - &servercred); - LDAP_END_ALLOW_THREADS( self ); + (LDAPControl **)server_ldcs, + (LDAPControl **)client_ldcs, &servercred); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); if (ldaperror == LDAP_SASL_BIND_IN_PROGRESS) { if (servercred && servercred->bv_val && *servercred->bv_val) - return PyBytes_FromStringAndSize( servercred->bv_val, servercred->bv_len ); - } else if (ldaperror != LDAP_SUCCESS) - return LDAPerror( self->ldap, "l_ldap_sasl_bind_s" ); - return PyInt_FromLong( ldaperror ); + return PyBytes_FromStringAndSize(servercred->bv_val, + servercred->bv_len); + } + else if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "l_ldap_sasl_bind_s"); + return PyInt_FromLong(ldaperror); } -static PyObject* -l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) { char *c_mechanism; char *who; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; - PyObject *SASLObject = NULL; + PyObject *SASLObject = NULL; PyObject *mechanism = NULL; int msgid; @@ -713,13 +756,18 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) * "i" otherwise. */ #if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 3) - if (!PyArg_ParseTuple(args, "sOOOi:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) + if (!PyArg_ParseTuple + (args, "sOOOi:sasl_interactive_bind_s", &who, &SASLObject, + &serverctrls, &clientctrls, &sasl_flags)) #else - if (!PyArg_ParseTuple(args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags )) + if (!PyArg_ParseTuple + (args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, + &serverctrls, &clientctrls, &sasl_flags)) #endif - return NULL; + return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -728,14 +776,15 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } /* now we extract the sasl mechanism from the SASL Object */ mechanism = PyObject_GetAttrString(SASLObject, "mech"); - if (mechanism == NULL) return NULL; + if (mechanism == NULL) + return NULL; c_mechanism = PyBytes_AsString(mechanism); Py_DECREF(mechanism); mechanism = NULL; @@ -745,43 +794,44 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) Python object SASLObject, but passing it through some static variable would destroy thread safety, IMHO. */ - msgid = ldap_sasl_interactive_bind_s(self->ldap, - who, - c_mechanism, - (LDAPControl**) server_ldcs, - (LDAPControl**) client_ldcs, - sasl_flags, - py_ldap_sasl_interaction, - SASLObject); - - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + msgid = ldap_sasl_interactive_bind_s(self->ldap, + who, + c_mechanism, + (LDAPControl **)server_ldcs, + (LDAPControl **)client_ldcs, + sasl_flags, + py_ldap_sasl_interaction, SASLObject); + + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); if (msgid != LDAP_SUCCESS) - return LDAPerror( self->ldap, "ldap_sasl_interactive_bind_s" ); - return PyInt_FromLong( msgid ); + return LDAPerror(self->ldap, "ldap_sasl_interactive_bind_s"); + return PyInt_FromLong(msgid); } #endif - #ifdef LDAP_API_FEATURE_CANCEL /* ldap_cancel */ -static PyObject* -l_ldap_cancel( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_cancel(LDAPObject *self, PyObject *args) { int msgid; int cancelid; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int ldaperror; - if (!PyArg_ParseTuple( args, "i|OO:cancel", &cancelid, &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple + (args, "i|OO:cancel", &cancelid, &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -790,22 +840,23 @@ l_ldap_cancel( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_cancel( self->ldap, cancelid, server_ldcs, client_ldcs, &msgid ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_cancel(self->ldap, cancelid, server_ldcs, client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_cancel" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_cancel"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } #endif @@ -813,23 +864,27 @@ l_ldap_cancel( LDAPObject* self, PyObject* args ) /* ldap_compare_ext */ static PyObject * -l_ldap_compare_ext( LDAPObject* self, PyObject *args ) +l_ldap_compare_ext(LDAPObject *self, PyObject *args) { char *dn, *attr; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; Py_ssize_t value_len; struct berval value; - if (!PyArg_ParseTuple( args, "sss#|OO:compare_ext", &dn, &attr, &value.bv_val, &value_len, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple + (args, "sss#|OO:compare_ext", &dn, &attr, &value.bv_val, &value_len, + &serverctrls, &clientctrls)) + return NULL; value.bv_len = (ber_len_t) value_len; - if (not_valid(self)) return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -838,41 +893,45 @@ l_ldap_compare_ext( LDAPObject* self, PyObject *args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_compare_ext( self->ldap, dn, attr, &value, server_ldcs, client_ldcs, &msgid ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_compare_ext(self->ldap, dn, attr, &value, server_ldcs, + client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_compare_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_compare_ext"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } - /* ldap_delete_ext */ static PyObject * -l_ldap_delete_ext( LDAPObject* self, PyObject *args ) +l_ldap_delete_ext(LDAPObject *self, PyObject *args) { char *dn; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "s|OO:delete_ext", &dn, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple + (args, "s|OO:delete_ext", &dn, &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -881,97 +940,104 @@ l_ldap_delete_ext( LDAPObject* self, PyObject *args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_delete_ext( self->ldap, dn, server_ldcs, client_ldcs, &msgid ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_delete_ext(self->ldap, dn, server_ldcs, client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_delete_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_delete_ext"); return PyInt_FromLong(msgid); } - /* ldap_modify_ext */ static PyObject * -l_ldap_modify_ext( LDAPObject* self, PyObject *args ) +l_ldap_modify_ext(LDAPObject *self, PyObject *args) { char *dn; PyObject *modlist; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; LDAPMod **mods; - if (!PyArg_ParseTuple( args, "sO|OO:modify_ext", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple + (args, "sO|OO:modify_ext", &dn, &modlist, &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; - mods = List_to_LDAPMods( modlist, 0 ); + mods = List_to_LDAPMods(modlist, 0); if (mods == NULL) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { - LDAPMods_DEL( mods ); + LDAPMods_DEL(mods); return NULL; } } if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPMods_DEL( mods ); - LDAPControl_List_DEL( server_ldcs ); + LDAPMods_DEL(mods); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_modify_ext( self->ldap, dn, mods, server_ldcs, client_ldcs, &msgid ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_modify_ext(self->ldap, dn, mods, server_ldcs, client_ldcs, + &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPMods_DEL( mods ); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPMods_DEL(mods); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_modify_ext" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_modify_ext"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } - /* ldap_rename */ static PyObject * -l_ldap_rename( LDAPObject* self, PyObject *args ) +l_ldap_rename(LDAPObject *self, PyObject *args) { char *dn, *newrdn; char *newSuperior = NULL; int delold = 1; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "ss|ziOO:rename", &dn, &newrdn, &newSuperior, &delold, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple + (args, "ss|ziOO:rename", &dn, &newrdn, &newSuperior, &delold, + &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) return NULL; - if (not_valid(self)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -980,29 +1046,30 @@ l_ldap_rename( LDAPObject* self, PyObject *args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_rename( self->ldap, dn, newrdn, newSuperior, delold, server_ldcs, client_ldcs, &msgid ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_rename(self->ldap, dn, newrdn, newSuperior, delold, server_ldcs, + client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_rename" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_rename"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } - /* ldap_result4 */ static PyObject * -l_ldap_result4( LDAPObject* self, PyObject *args ) +l_ldap_result4(LDAPObject *self, PyObject *args) { int msgid = LDAP_RES_ANY; int all = 1; @@ -1011,7 +1078,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) int add_intermediates = 0; int add_extop = 0; struct timeval tv; - struct timeval* tvp; + struct timeval *tvp; int res_type; LDAPMessage *msg = NULL; PyObject *result_str, *retval, *pmsg, *pyctrls = 0; @@ -1022,31 +1089,38 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) char **refs = NULL; LDAPControl **serverctrls = 0; - if (!PyArg_ParseTuple( args, "|iidiii:result4", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) + if (!PyArg_ParseTuple + (args, "|iidiii:result4", &msgid, &all, &timeout, &add_ctrls, + &add_intermediates, &add_extop)) return NULL; - if (not_valid(self)) return NULL; - + if (not_valid(self)) + return NULL; + if (timeout >= 0) { tvp = &tv; - set_timeval_from_double( tvp, timeout ); - } else { + set_timeval_from_double(tvp, timeout); + } + else { tvp = NULL; } - LDAP_BEGIN_ALLOW_THREADS( self ); - res_type = ldap_result( self->ldap, msgid, all, tvp, &msg ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + res_type = ldap_result(self->ldap, msgid, all, tvp, &msg); + LDAP_END_ALLOW_THREADS(self); if (res_type < 0) /* LDAP or system error */ - return LDAPerror( self->ldap, "ldap_result4" ); + return LDAPerror(self->ldap, "ldap_result4"); if (res_type == 0) { /* Polls return (None, None, None, None); timeouts raise an exception */ if (timeout == 0) { if (add_extop) { - return Py_BuildValue("(OOOOOO)", Py_None, Py_None, Py_None, Py_None, Py_None, Py_None); - } else { - return Py_BuildValue("(OOOO)", Py_None, Py_None, Py_None, Py_None); + return Py_BuildValue("(OOOOOO)", Py_None, Py_None, Py_None, + Py_None, Py_None, Py_None); + } + else { + return Py_BuildValue("(OOOO)", Py_None, Py_None, Py_None, + Py_None); } } else @@ -1058,75 +1132,90 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) if (res_type == LDAP_RES_SEARCH_ENTRY) { /* LDAPmessage_to_python will parse entries and read the controls for each entry */ - } else if (res_type == LDAP_RES_SEARCH_REFERENCE) { + } + else if (res_type == LDAP_RES_SEARCH_REFERENCE) { /* LDAPmessage_to_python will parse refs and read the controls for each res */ - } else if (res_type == LDAP_RES_INTERMEDIATE) { + } + else if (res_type == LDAP_RES_INTERMEDIATE) { /* LDAPmessage_to_python will parse intermediates and controls */ - } else { + } + else { int rc; + if (res_type == LDAP_RES_EXTENDED) { struct berval *retdata = 0; - LDAP_BEGIN_ALLOW_THREADS( self ); - rc = ldap_parse_extended_result( self->ldap, msg, &retoid, &retdata, 0 ); - LDAP_END_ALLOW_THREADS( self ); + + LDAP_BEGIN_ALLOW_THREADS(self); + rc = ldap_parse_extended_result(self->ldap, msg, &retoid, &retdata, + 0); + LDAP_END_ALLOW_THREADS(self); /* handle error rc!=0 here? */ if (rc == LDAP_SUCCESS) { valuestr = LDAPberval_to_object(retdata); } - ber_bvfree( retdata ); + ber_bvfree(retdata); } - - LDAP_BEGIN_ALLOW_THREADS( self ); - rc = ldap_parse_result( self->ldap, msg, &result, NULL, NULL, &refs, - &serverctrls, 0 ); - LDAP_END_ALLOW_THREADS( self ); + + LDAP_BEGIN_ALLOW_THREADS(self); + rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, &refs, + &serverctrls, 0); + LDAP_END_ALLOW_THREADS(self); } - if (result != LDAP_SUCCESS) { /* result error */ + if (result != LDAP_SUCCESS) { /* result error */ char *e, err[1024]; + if (result == LDAP_REFERRAL && refs && refs[0]) { snprintf(err, sizeof(err), "Referral:\n%s", refs[0]); e = err; - } else + } + else e = "ldap_parse_result"; ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror( self->ldap, e ); + return LDAPerror(self->ldap, e); } if (!(pyctrls = LDAPControls_to_List(serverctrls))) { int err = LDAP_NO_MEMORY; - LDAP_BEGIN_ALLOW_THREADS( self ); + + LDAP_BEGIN_ALLOW_THREADS(self); ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &err); - LDAP_END_ALLOW_THREADS( self ); + LDAP_END_ALLOW_THREADS(self); ldap_msgfree(msg); Py_XDECREF(valuestr); return LDAPerror(self->ldap, "LDAPControls_to_List"); } ldap_controls_free(serverctrls); - pmsg = LDAPmessage_to_python( self->ldap, msg, add_ctrls, add_intermediates ); + pmsg = + LDAPmessage_to_python(self->ldap, msg, add_ctrls, add_intermediates); if (res_type == 0) { result_str = Py_None; Py_INCREF(Py_None); - } else { - result_str = PyInt_FromLong( res_type ); + } + else { + result_str = PyInt_FromLong(res_type); } if (pmsg == NULL) { - retval = NULL; - } else { + retval = NULL; + } + else { /* s handles NULL, but O does not */ if (add_extop) { retval = Py_BuildValue("(OOiOsO)", result_str, pmsg, res_msgid, - pyctrls, retoid, valuestr ? valuestr : Py_None); - } else { - retval = Py_BuildValue("(OOiO)", result_str, pmsg, res_msgid, pyctrls); + pyctrls, retoid, + valuestr ? valuestr : Py_None); + } + else { + retval = + Py_BuildValue("(OOiO)", result_str, pmsg, res_msgid, pyctrls); } if (pmsg != Py_None) { - Py_DECREF(pmsg); + Py_DECREF(pmsg); } } Py_XDECREF(valuestr); @@ -1135,11 +1224,10 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) return retval; } - /* ldap_search_ext */ -static PyObject* -l_ldap_search_ext( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_search_ext(LDAPObject *self, PyObject *args) { char *base; int scope; @@ -1150,73 +1238,76 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; double timeout = -1.0; struct timeval tv; - struct timeval* tvp; + struct timeval *tvp; int sizelimit = 0; int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "sis|OiOOdi:search_ext", - &base, &scope, &filter, &attrlist, &attrsonly, - &serverctrls, &clientctrls, &timeout, &sizelimit )) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple(args, "sis|OiOOdi:search_ext", + &base, &scope, &filter, &attrlist, &attrsonly, + &serverctrls, &clientctrls, &timeout, &sizelimit)) + return NULL; + if (not_valid(self)) + return NULL; - if (!attrs_from_List( attrlist, &attrs )) - return NULL; + if (!attrs_from_List(attrlist, &attrs)) + return NULL; if (timeout >= 0) { tvp = &tv; - set_timeval_from_double( tvp, timeout ); - } else { + set_timeval_from_double(tvp, timeout); + } + else { tvp = NULL; } if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { - free_attrs( &attrs ); + free_attrs(&attrs); return NULL; } } if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - free_attrs( &attrs ); - LDAPControl_List_DEL( server_ldcs ); + free_attrs(&attrs); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_search_ext( self->ldap, base, scope, filter, attrs, attrsonly, - server_ldcs, client_ldcs, tvp, sizelimit, &msgid ); - LDAP_END_ALLOW_THREADS( self ); - - free_attrs( &attrs ); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = + ldap_search_ext(self->ldap, base, scope, filter, attrs, attrsonly, + server_ldcs, client_ldcs, tvp, sizelimit, &msgid); + LDAP_END_ALLOW_THREADS(self); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_search_ext" ); + free_attrs(&attrs); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - return PyInt_FromLong( msgid ); -} + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_search_ext"); + return PyInt_FromLong(msgid); +} /* ldap_whoami_s (available since OpenLDAP 2.1.13) */ -static PyObject* -l_ldap_whoami_s( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_whoami_s(LDAPObject *self, PyObject *args) { PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; struct berval *bvalue = NULL; @@ -1224,8 +1315,10 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) int ldaperror; - if (!PyArg_ParseTuple( args, "|OO:whoami_s", &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple(args, "|OO:whoami_s", &serverctrls, &clientctrls)) + return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1234,21 +1327,21 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_whoami_s( self->ldap, &bvalue, server_ldcs, client_ldcs ); - LDAP_END_ALLOW_THREADS( self ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_whoami_s(self->ldap, &bvalue, server_ldcs, client_ldcs); + LDAP_END_ALLOW_THREADS(self); - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) { + if (ldaperror != LDAP_SUCCESS) { ber_bvfree(bvalue); - return LDAPerror( self->ldap, "ldap_whoami_s" ); + return LDAPerror(self->ldap, "ldap_whoami_s"); } result = LDAPberval_to_unicode_object(bvalue); @@ -1260,20 +1353,22 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) #ifdef HAVE_TLS /* ldap_start_tls_s */ -static PyObject* -l_ldap_start_tls_s( LDAPObject* self, PyObject* args ) +static PyObject * +l_ldap_start_tls_s(LDAPObject *self, PyObject *args) { int ldaperror; - if (!PyArg_ParseTuple( args, ":start_tls_s" )) return NULL; - if (not_valid(self)) return NULL; + if (!PyArg_ParseTuple(args, ":start_tls_s")) + return NULL; + if (not_valid(self)) + return NULL; - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_start_tls_s( self->ldap, NULL, NULL ); - LDAP_END_ALLOW_THREADS( self ); - if ( ldaperror != LDAP_SUCCESS ){ + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_start_tls_s(self->ldap, NULL, NULL); + LDAP_END_ALLOW_THREADS(self); + if (ldaperror != LDAP_SUCCESS) { ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &ldaperror); - return LDAPerror( self->ldap, "ldap_start_tls_s" ); + return LDAPerror(self->ldap, "ldap_start_tls_s"); } Py_INCREF(Py_None); @@ -1284,8 +1379,8 @@ l_ldap_start_tls_s( LDAPObject* self, PyObject* args ) /* ldap_set_option */ -static PyObject* -l_ldap_set_option(PyObject* self, PyObject *args) +static PyObject * +l_ldap_set_option(PyObject *self, PyObject *args) { PyObject *value; int option; @@ -1298,11 +1393,10 @@ l_ldap_set_option(PyObject* self, PyObject *args) return Py_None; } - /* ldap_get_option */ -static PyObject* -l_ldap_get_option(PyObject* self, PyObject *args) +static PyObject * +l_ldap_get_option(PyObject *self, PyObject *args) { int option; @@ -1311,11 +1405,10 @@ l_ldap_get_option(PyObject* self, PyObject *args) return LDAP_get_option((LDAPObject *)self, option); } - /* ldap_passwd */ static PyObject * -l_ldap_passwd( LDAPObject* self, PyObject *args ) +l_ldap_passwd(LDAPObject *self, PyObject *args) { struct berval user; Py_ssize_t user_len; @@ -1325,20 +1418,23 @@ l_ldap_passwd( LDAPObject* self, PyObject *args ) Py_ssize_t newpw_len; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "z#z#z#|OO:passwd", &user.bv_val, &user_len, &oldpw.bv_val, &oldpw_len, &newpw.bv_val, &newpw_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple + (args, "z#z#z#|OO:passwd", &user.bv_val, &user_len, &oldpw.bv_val, + &oldpw_len, &newpw.bv_val, &newpw_len, &serverctrls, &clientctrls)) return NULL; user.bv_len = (ber_len_t) user_len; oldpw.bv_len = (ber_len_t) oldpw_len; newpw.bv_len = (ber_len_t) newpw_len; - - if (not_valid(self)) return NULL; + + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1347,50 +1443,50 @@ l_ldap_passwd( LDAPObject* self, PyObject *args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_passwd( self->ldap, - user.bv_val != NULL ? &user : NULL, - oldpw.bv_val != NULL ? &oldpw : NULL, - newpw.bv_val != NULL ? &newpw : NULL, - server_ldcs, - client_ldcs, - &msgid ); - LDAP_END_ALLOW_THREADS( self ); - - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_passwd(self->ldap, + user.bv_val != NULL ? &user : NULL, + oldpw.bv_val != NULL ? &oldpw : NULL, + newpw.bv_val != NULL ? &newpw : NULL, + server_ldcs, client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_passwd" ); + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - return PyInt_FromLong( msgid ); -} + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_passwd"); + return PyInt_FromLong(msgid); +} /* ldap_extended_operation */ static PyObject * -l_ldap_extended_operation( LDAPObject* self, PyObject *args ) +l_ldap_extended_operation(LDAPObject *self, PyObject *args) { char *reqoid = NULL; - struct berval reqvalue = {0, NULL}; + struct berval reqvalue = { 0, NULL }; PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - LDAPControl** server_ldcs = NULL; - LDAPControl** client_ldcs = NULL; + LDAPControl **server_ldcs = NULL; + LDAPControl **client_ldcs = NULL; int msgid; int ldaperror; - if (!PyArg_ParseTuple( args, "sz#|OO:extended_operation", &reqoid, &reqvalue.bv_val, &reqvalue.bv_len, &serverctrls, &clientctrls )) + if (!PyArg_ParseTuple + (args, "sz#|OO:extended_operation", &reqoid, &reqvalue.bv_val, + &reqvalue.bv_len, &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self)) + return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1399,91 +1495,91 @@ l_ldap_extended_operation( LDAPObject* self, PyObject *args ) if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - LDAPControl_List_DEL( server_ldcs ); + LDAPControl_List_DEL(server_ldcs); return NULL; } } - LDAP_BEGIN_ALLOW_THREADS( self ); - ldaperror = ldap_extended_operation( self->ldap, reqoid, - reqvalue.bv_val != NULL ? &reqvalue : NULL, - server_ldcs, - client_ldcs, - &msgid ); - LDAP_END_ALLOW_THREADS( self ); - - LDAPControl_List_DEL( server_ldcs ); - LDAPControl_List_DEL( client_ldcs ); + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_extended_operation(self->ldap, reqoid, + reqvalue.bv_val != + NULL ? &reqvalue : NULL, server_ldcs, + client_ldcs, &msgid); + LDAP_END_ALLOW_THREADS(self); + + LDAPControl_List_DEL(server_ldcs); + LDAPControl_List_DEL(client_ldcs); - if ( ldaperror!=LDAP_SUCCESS ) - return LDAPerror( self->ldap, "ldap_extended_operation" ); + if (ldaperror != LDAP_SUCCESS) + return LDAPerror(self->ldap, "ldap_extended_operation"); - return PyInt_FromLong( msgid ); + return PyInt_FromLong(msgid); } /* methods */ static PyMethodDef methods[] = { - {"unbind_ext", (PyCFunction)l_ldap_unbind_ext, METH_VARARGS }, - {"abandon_ext", (PyCFunction)l_ldap_abandon_ext, METH_VARARGS }, - {"add_ext", (PyCFunction)l_ldap_add_ext, METH_VARARGS }, - {"simple_bind", (PyCFunction)l_ldap_simple_bind, METH_VARARGS }, + {"unbind_ext", (PyCFunction)l_ldap_unbind_ext, METH_VARARGS}, + {"abandon_ext", (PyCFunction)l_ldap_abandon_ext, METH_VARARGS}, + {"add_ext", (PyCFunction)l_ldap_add_ext, METH_VARARGS}, + {"simple_bind", (PyCFunction)l_ldap_simple_bind, METH_VARARGS}, #ifdef HAVE_SASL - {"sasl_interactive_bind_s", (PyCFunction)l_ldap_sasl_interactive_bind_s, METH_VARARGS }, - {"sasl_bind_s", (PyCFunction)l_ldap_sasl_bind_s, METH_VARARGS }, + {"sasl_interactive_bind_s", (PyCFunction)l_ldap_sasl_interactive_bind_s, + METH_VARARGS}, + {"sasl_bind_s", (PyCFunction)l_ldap_sasl_bind_s, METH_VARARGS}, #endif - {"compare_ext", (PyCFunction)l_ldap_compare_ext, METH_VARARGS }, - {"delete_ext", (PyCFunction)l_ldap_delete_ext, METH_VARARGS }, - {"modify_ext", (PyCFunction)l_ldap_modify_ext, METH_VARARGS }, - {"rename", (PyCFunction)l_ldap_rename, METH_VARARGS }, - {"result4", (PyCFunction)l_ldap_result4, METH_VARARGS }, - {"search_ext", (PyCFunction)l_ldap_search_ext, METH_VARARGS }, + {"compare_ext", (PyCFunction)l_ldap_compare_ext, METH_VARARGS}, + {"delete_ext", (PyCFunction)l_ldap_delete_ext, METH_VARARGS}, + {"modify_ext", (PyCFunction)l_ldap_modify_ext, METH_VARARGS}, + {"rename", (PyCFunction)l_ldap_rename, METH_VARARGS}, + {"result4", (PyCFunction)l_ldap_result4, METH_VARARGS}, + {"search_ext", (PyCFunction)l_ldap_search_ext, METH_VARARGS}, #ifdef HAVE_TLS - {"start_tls_s", (PyCFunction)l_ldap_start_tls_s, METH_VARARGS }, + {"start_tls_s", (PyCFunction)l_ldap_start_tls_s, METH_VARARGS}, #endif - {"whoami_s", (PyCFunction)l_ldap_whoami_s, METH_VARARGS }, - {"passwd", (PyCFunction)l_ldap_passwd, METH_VARARGS }, - {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS }, - {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS }, + {"whoami_s", (PyCFunction)l_ldap_whoami_s, METH_VARARGS}, + {"passwd", (PyCFunction)l_ldap_passwd, METH_VARARGS}, + {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, + {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, #ifdef LDAP_API_FEATURE_CANCEL - {"cancel", (PyCFunction)l_ldap_cancel, METH_VARARGS }, + {"cancel", (PyCFunction)l_ldap_cancel, METH_VARARGS}, #endif - {"extop", (PyCFunction)l_ldap_extended_operation, METH_VARARGS }, - { NULL, NULL } + {"extop", (PyCFunction)l_ldap_extended_operation, METH_VARARGS}, + {NULL, NULL} }; /* type entry */ PyTypeObject LDAP_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "LDAP", /*tp_name*/ - sizeof(LDAPObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - 0, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0) + "LDAP", /*tp_name */ + sizeof(LDAPObject), /*tp_basicsize */ + 0, /*tp_itemsize */ + /* methods */ + (destructor) dealloc, /*tp_dealloc */ + 0, /*tp_print */ + 0, /*tp_getattr */ + 0, /*tp_setattr */ + 0, /*tp_compare */ + 0, /*tp_repr */ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash */ + 0, /*tp_call */ + 0, /*tp_str */ + 0, /*tp_getattro */ + 0, /*tp_setattro */ + 0, /*tp_as_buffer */ + 0, /*tp_flags */ + 0, /*tp_doc */ + 0, /*tp_traverse */ + 0, /*tp_clear */ + 0, /*tp_richcompare */ + 0, /*tp_weaklistoffset */ + 0, /*tp_iter */ + 0, /*tp_iternext */ + methods, /*tp_methods */ + 0, /*tp_members */ + 0, /*tp_getset */ }; diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index 8cd6fc3e..a456bce0 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -1,7 +1,7 @@ /* See https://www.python-ldap.org/ for details. */ -#ifndef __h_LDAPObject -#define __h_LDAPObject +#ifndef __h_LDAPObject +#define __h_LDAPObject #include "common.h" @@ -12,22 +12,22 @@ #endif #if PYTHON_API_VERSION < 1007 -typedef PyObject* _threadstate; +typedef PyObject *_threadstate; #else -typedef PyThreadState* _threadstate; +typedef PyThreadState *_threadstate; #endif typedef struct { - PyObject_HEAD - LDAP* ldap; - _threadstate _save; /* for thread saving on referrals */ - int valid; + PyObject_HEAD LDAP *ldap; + _threadstate _save; /* for thread saving on referrals */ + int valid; } LDAPObject; extern PyTypeObject LDAP_Type; + #define LDAPObject_Check(v) (Py_TYPE(v) == &LDAP_Type) -extern LDAPObject *newLDAPObject( LDAP* ); +extern LDAPObject *newLDAPObject(LDAP *); /* macros to allow thread saving in the context of an LDAP connection */ @@ -48,4 +48,3 @@ extern LDAPObject *newLDAPObject( LDAP* ); } #endif /* __h_LDAPObject */ - diff --git a/Modules/berval.h b/Modules/berval.h index 9702b8ce..2aa9c977 100644 --- a/Modules/berval.h +++ b/Modules/berval.h @@ -1,7 +1,7 @@ /* See https://www.python-ldap.org/ for details. */ -#ifndef __h_berval -#define __h_berval +#ifndef __h_berval +#define __h_berval #include "common.h" #include "lber.h" diff --git a/Modules/common.c b/Modules/common.c index 0f0cd36a..9d7001c0 100644 --- a/Modules/common.c +++ b/Modules/common.c @@ -6,21 +6,24 @@ /* dynamically add the methods into the module dictionary d */ void -LDAPadd_methods( PyObject* d, PyMethodDef* methods ) +LDAPadd_methods(PyObject *d, PyMethodDef *methods) { PyMethodDef *meth; - for( meth = methods; meth->ml_meth; meth++ ) { - PyObject *f = PyCFunction_New( meth, NULL ); - PyDict_SetItemString( d, meth->ml_name, f ); + for (meth = methods; meth->ml_meth; meth++) { + PyObject *f = PyCFunction_New(meth, NULL); + + PyDict_SetItemString(d, meth->ml_name, f); Py_DECREF(f); } } /* Raise TypeError with custom message and object */ -PyObject* -LDAPerror_TypeError(const char *msg, PyObject *obj) { +PyObject * +LDAPerror_TypeError(const char *msg, PyObject *obj) +{ PyObject *args = Py_BuildValue("sO", msg, obj); + if (args == NULL) { return NULL; } diff --git a/Modules/common.h b/Modules/common.h index 029f234f..affa5f93 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -1,8 +1,8 @@ /* common utility macros * See https://www.python-ldap.org/ for details. */ -#ifndef __h_common -#define __h_common +#ifndef __h_common +#define __h_common #define PY_SSIZE_T_CLEAN @@ -24,9 +24,10 @@ #define streq( a, b ) \ ( (*(a)==*(b)) && 0==strcmp(a,b) ) -extern PyObject* LDAPerror_TypeError(const char *, PyObject *); +extern PyObject *LDAPerror_TypeError(const char *, PyObject *); + +void LDAPadd_methods(PyObject *d, PyMethodDef *methods); -void LDAPadd_methods( PyObject*d, PyMethodDef*methods ); #define PyNone_Check(o) ((o) == Py_None) /* Py2/3 compatibility */ @@ -36,4 +37,3 @@ void LDAPadd_methods( PyObject*d, PyMethodDef*methods ); #endif #endif /* __h_common_ */ - diff --git a/Modules/constants.c b/Modules/constants.c index c2b595c1..f8da3736 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -8,133 +8,136 @@ /* the base exception class */ -PyObject* -LDAPexception_class; +PyObject *LDAPexception_class; /* list of exception classes */ #define LDAP_ERROR_MIN LDAP_REFERRAL_LIMIT_EXCEEDED #ifdef LDAP_PROXIED_AUTHORIZATION_DENIED - #define LDAP_ERROR_MAX LDAP_PROXIED_AUTHORIZATION_DENIED +#define LDAP_ERROR_MAX LDAP_PROXIED_AUTHORIZATION_DENIED #else - #ifdef LDAP_ASSERTION_FAILED - #define LDAP_ERROR_MAX LDAP_ASSERTION_FAILED - #else - #define LDAP_ERROR_MAX LDAP_OTHER - #endif +#ifdef LDAP_ASSERTION_FAILED +#define LDAP_ERROR_MAX LDAP_ASSERTION_FAILED +#else +#define LDAP_ERROR_MAX LDAP_OTHER +#endif #endif #define LDAP_ERROR_OFFSET -LDAP_ERROR_MIN -static PyObject* errobjects[ LDAP_ERROR_MAX-LDAP_ERROR_MIN+1 ]; - +static PyObject *errobjects[LDAP_ERROR_MAX - LDAP_ERROR_MIN + 1]; /* Convert a bare LDAP error number into an exception */ -PyObject* +PyObject * LDAPerr(int errnum) { - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) { - PyErr_SetNone(errobjects[errnum+LDAP_ERROR_OFFSET]); - } else { - PyObject *args = Py_BuildValue("{s:i}", "errnum", errnum); - if (args == NULL) - return NULL; - PyErr_SetObject(LDAPexception_class, args); - Py_DECREF(args); - } - return NULL; + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) { + PyErr_SetNone(errobjects[errnum + LDAP_ERROR_OFFSET]); + } + else { + PyObject *args = Py_BuildValue("{s:i}", "errnum", errnum); + + if (args == NULL) + return NULL; + PyErr_SetObject(LDAPexception_class, args); + Py_DECREF(args); + } + return NULL; } /* Convert an LDAP error into an informative python exception */ -PyObject* -LDAPerror( LDAP *l, char *msg ) +PyObject * +LDAPerror(LDAP *l, char *msg) { - if (l == NULL) { - PyErr_SetFromErrno( LDAPexception_class ); - return NULL; - } - else { - int myerrno, errnum, opt_errnum; - PyObject *errobj; - PyObject *info; - PyObject *str; - PyObject *pyerrno; - char *matched, *error; - - /* at first save errno for later use before it gets overwritten by another call */ - myerrno = errno; - - opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); - if (opt_errnum != LDAP_OPT_SUCCESS) - errnum = opt_errnum; - - if (errnum == LDAP_NO_MEMORY) - return PyErr_NoMemory(); - - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) - errobj = errobjects[errnum+LDAP_ERROR_OFFSET]; - else - errobj = LDAPexception_class; - - info = PyDict_New(); - if (info == NULL) - return NULL; - - str = PyUnicode_FromString(ldap_err2string(errnum)); - if (str) - PyDict_SetItemString( info, "desc", str ); - Py_XDECREF(str); - - if (myerrno != 0) { - pyerrno = PyInt_FromLong(myerrno); - if (pyerrno) - PyDict_SetItemString( info, "errno", pyerrno ); - Py_XDECREF(pyerrno); + if (l == NULL) { + PyErr_SetFromErrno(LDAPexception_class); + return NULL; } + else { + int myerrno, errnum, opt_errnum; + PyObject *errobj; + PyObject *info; + PyObject *str; + PyObject *pyerrno; + char *matched, *error; + + /* at first save errno for later use before it gets overwritten by another call */ + myerrno = errno; + + opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); + if (opt_errnum != LDAP_OPT_SUCCESS) + errnum = opt_errnum; + + if (errnum == LDAP_NO_MEMORY) + return PyErr_NoMemory(); + + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) + errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; + else + errobj = LDAPexception_class; + + info = PyDict_New(); + if (info == NULL) + return NULL; + + str = PyUnicode_FromString(ldap_err2string(errnum)); + if (str) + PyDict_SetItemString(info, "desc", str); + Py_XDECREF(str); - if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 - && matched != NULL) { - if (*matched != '\0') { - str = PyUnicode_FromString(matched); - if (str) - PyDict_SetItemString( info, "matched", str ); - Py_XDECREF(str); + if (myerrno != 0) { + pyerrno = PyInt_FromLong(myerrno); + if (pyerrno) + PyDict_SetItemString(info, "errno", pyerrno); + Py_XDECREF(pyerrno); } - ldap_memfree(matched); - } - if (errnum == LDAP_REFERRAL) { - str = PyUnicode_FromString(msg); - if (str) - PyDict_SetItemString( info, "info", str ); - Py_XDECREF(str); - } else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { - if (error != NULL && *error != '\0') { - str = PyUnicode_FromString(error); + if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 + && matched != NULL) { + if (*matched != '\0') { + str = PyUnicode_FromString(matched); + if (str) + PyDict_SetItemString(info, "matched", str); + Py_XDECREF(str); + } + ldap_memfree(matched); + } + + if (errnum == LDAP_REFERRAL) { + str = PyUnicode_FromString(msg); if (str) - PyDict_SetItemString( info, "info", str ); + PyDict_SetItemString(info, "info", str); Py_XDECREF(str); } - ldap_memfree(error); + else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { + if (error != NULL && *error != '\0') { + str = PyUnicode_FromString(error); + if (str) + PyDict_SetItemString(info, "info", str); + Py_XDECREF(str); + } + ldap_memfree(error); + } + PyErr_SetObject(errobj, info); + Py_DECREF(info); + return NULL; } - PyErr_SetObject( errobj, info ); - Py_DECREF(info); - return NULL; - } } /* initialise the module constants */ int -LDAPinit_constants( PyObject* m ) +LDAPinit_constants(PyObject *m) { PyObject *exc; /* simple constants */ - if (PyModule_AddIntConstant(m, "OPT_ON", 1) != 0) return -1; - if (PyModule_AddIntConstant(m, "OPT_OFF", 0) != 0) return -1; + if (PyModule_AddIntConstant(m, "OPT_ON", 1) != 0) + return -1; + if (PyModule_AddIntConstant(m, "OPT_OFF", 0) != 0) + return -1; /* exceptions */ @@ -143,11 +146,13 @@ LDAPinit_constants( PyObject* m ) return -1; } - if (PyModule_AddObject(m, "LDAPError", LDAPexception_class) != 0) return -1; + if (PyModule_AddObject(m, "LDAPError", LDAPexception_class) != 0) + return -1; Py_INCREF(LDAPexception_class); /* XXX - backward compatibility with pre-1.8 */ - if (PyModule_AddObject(m, "error", LDAPexception_class) != 0) return -1; + if (PyModule_AddObject(m, "error", LDAPexception_class) != 0) + return -1; Py_INCREF(LDAPexception_class); /* Generated constants -- see Lib/ldap/constants.py */ diff --git a/Modules/constants.h b/Modules/constants.h index 4056f907..8a390b5b 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -7,19 +7,19 @@ #include "lber.h" #include "ldap.h" -extern int LDAPinit_constants( PyObject* m ); -extern PyObject* LDAPconstant( int ); +extern int LDAPinit_constants(PyObject *m); +extern PyObject *LDAPconstant(int); -extern PyObject* LDAPexception_class; -extern PyObject* LDAPerror( LDAP*, char*msg ); -PyObject* LDAPerr(int errnum); +extern PyObject *LDAPexception_class; +extern PyObject *LDAPerror(LDAP *, char *msg); +PyObject *LDAPerr(int errnum); #ifndef LDAP_CONTROL_PAGE_OID #define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" #endif /* !LDAP_CONTROL_PAGE_OID */ #ifndef LDAP_CONTROL_VALUESRETURNFILTER -#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ +#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ #endif /* !LDAP_CONTROL_VALUESRETURNFILTER */ #endif /* __h_constants_ */ diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 083ba161..455852ed 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -76,12 +76,10 @@ add_err(TOO_LATE); add_err(CANNOT_CANCEL); #endif - #if defined(LDAP_ASSERTION_FAILED) add_err(ASSERTION_FAILED); #endif - #if defined(LDAP_PROXIED_AUTHORIZATION_DENIED) add_err(PROXIED_AUTHORIZATION_DENIED); #endif @@ -194,7 +192,6 @@ add_int(OPT_URI); add_int(OPT_DEFBASE); #endif - #if HAVE_TLS #if defined(LDAP_OPT_X_TLS) @@ -220,27 +217,22 @@ add_int(OPT_X_TLS_TRY); add_int(OPT_X_TLS_PEERCERT); #endif - #if defined(LDAP_OPT_X_TLS_VERSION) add_int(OPT_X_TLS_VERSION); #endif - #if defined(LDAP_OPT_X_TLS_CIPHER) add_int(OPT_X_TLS_CIPHER); #endif - #if defined(LDAP_OPT_X_TLS_PEERCERT) add_int(OPT_X_TLS_PEERCERT); #endif - #if defined(LDAP_OPT_X_TLS_CRLCHECK) add_int(OPT_X_TLS_CRLCHECK); #endif - #if defined(LDAP_OPT_X_TLS_CRLFILE) add_int(OPT_X_TLS_CRLFILE); #endif @@ -253,12 +245,10 @@ add_int(OPT_X_TLS_CRL_ALL); add_int(OPT_X_TLS_NEWCTX); #endif - #if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN) add_int(OPT_X_TLS_PROTOCOL_MIN); #endif - #if defined(LDAP_OPT_X_TLS_PACKAGE) add_int(OPT_X_TLS_PACKAGE); #endif @@ -279,27 +269,22 @@ add_int(OPT_X_SASL_SSF_MAX); add_int(OPT_X_SASL_NOCANON); #endif - #if defined(LDAP_OPT_X_SASL_USERNAME) add_int(OPT_X_SASL_USERNAME); #endif - #if defined(LDAP_OPT_CONNECT_ASYNC) add_int(OPT_CONNECT_ASYNC); #endif - #if defined(LDAP_OPT_X_KEEPALIVE_IDLE) add_int(OPT_X_KEEPALIVE_IDLE); #endif - #if defined(LDAP_OPT_X_KEEPALIVE_PROBES) add_int(OPT_X_KEEPALIVE_PROBES); #endif - #if defined(LDAP_OPT_X_KEEPALIVE_INTERVAL) add_int(OPT_X_KEEPALIVE_INTERVAL); #endif @@ -325,23 +310,27 @@ add_int(URL_ERR_BADSCOPE); add_int(URL_ERR_MEM); #ifdef HAVE_LIBLDAP_R -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 1) != 0) return -1; +if (PyModule_AddIntConstant(m, "LIBLDAP_R", 1) != 0) + return -1; #else -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 0) != 0) return -1; +if (PyModule_AddIntConstant(m, "LIBLDAP_R", 0) != 0) + return -1; #endif - #ifdef HAVE_SASL -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) + return -1; #else -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) + return -1; #endif - #ifdef HAVE_TLS -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) + return -1; #else -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) + return -1; #endif add_string(CONTROL_MANAGEDSAIT); diff --git a/Modules/functions.c b/Modules/functions.c index 6bbf487b..4731efb8 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -9,29 +9,26 @@ /* ldap_initialize */ -static PyObject* -l_ldap_initialize(PyObject* unused, PyObject *args) +static PyObject * +l_ldap_initialize(PyObject *unused, PyObject *args) { char *uri; LDAP *ld = NULL; int ret; if (!PyArg_ParseTuple(args, "s:initialize", &uri)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - ret = ldap_initialize(&ld, uri); - Py_END_ALLOW_THREADS - if (ret != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_initialize"); - return (PyObject*)newLDAPObject(ld); -} + return NULL; + Py_BEGIN_ALLOW_THREADS ret = ldap_initialize(&ld, uri); + Py_END_ALLOW_THREADS if (ret != LDAP_SUCCESS) + return LDAPerror(ld, "ldap_initialize"); + return (PyObject *)newLDAPObject(ld); +} /* ldap_str2dn */ -static PyObject* -l_ldap_str2dn( PyObject* unused, PyObject *args ) +static PyObject * +l_ldap_str2dn(PyObject *unused, PyObject *args) { struct berval str; LDAPDN dn; @@ -46,58 +43,59 @@ l_ldap_str2dn( PyObject* unused, PyObject *args ) * ((('a','b',1),('c','d',1)),(('e','f',1),)) * The integers are a bit combination of the AVA_* flags */ - if (!PyArg_ParseTuple( args, "z#|i:str2dn", - &str.bv_val, &str_len, &flags )) - return NULL; + if (!PyArg_ParseTuple(args, "z#|i:str2dn", &str.bv_val, &str_len, &flags)) + return NULL; str.bv_len = (ber_len_t) str_len; res = ldap_bv2dn(&str, &dn, flags); if (res != LDAP_SUCCESS) - return LDAPerr(res); + return LDAPerr(res); tmp = PyList_New(0); if (!tmp) - goto failed; + goto failed; for (i = 0; dn[i]; i++) { - LDAPRDN rdn; - PyObject *rdnlist; - - rdn = dn[i]; - rdnlist = PyList_New(0); - if (!rdnlist) - goto failed; - if (PyList_Append(tmp, rdnlist) == -1) { - Py_DECREF(rdnlist); - goto failed; - } - - for (j = 0; rdn[j]; j++) { - LDAPAVA *ava = rdn[j]; - PyObject *tuple; - - tuple = Py_BuildValue("(O&O&i)", - LDAPberval_to_unicode_object, &ava->la_attr, - LDAPberval_to_unicode_object, &ava->la_value, - ava->la_flags & ~(LDAP_AVA_FREE_ATTR|LDAP_AVA_FREE_VALUE)); - if (!tuple) { - Py_DECREF(rdnlist); - goto failed; - } - - if (PyList_Append(rdnlist, tuple) == -1) { - Py_DECREF(tuple); - goto failed; - } - Py_DECREF(tuple); - } - Py_DECREF(rdnlist); + LDAPRDN rdn; + PyObject *rdnlist; + + rdn = dn[i]; + rdnlist = PyList_New(0); + if (!rdnlist) + goto failed; + if (PyList_Append(tmp, rdnlist) == -1) { + Py_DECREF(rdnlist); + goto failed; + } + + for (j = 0; rdn[j]; j++) { + LDAPAVA *ava = rdn[j]; + PyObject *tuple; + + tuple = Py_BuildValue("(O&O&i)", + LDAPberval_to_unicode_object, &ava->la_attr, + LDAPberval_to_unicode_object, &ava->la_value, + ava-> + la_flags & ~(LDAP_AVA_FREE_ATTR | + LDAP_AVA_FREE_VALUE)); + if (!tuple) { + Py_DECREF(rdnlist); + goto failed; + } + + if (PyList_Append(rdnlist, tuple) == -1) { + Py_DECREF(tuple); + goto failed; + } + Py_DECREF(tuple); + } + Py_DECREF(rdnlist); } result = tmp; tmp = NULL; -failed: + failed: Py_XDECREF(tmp); ldap_dnfree(dn); return result; @@ -105,46 +103,46 @@ l_ldap_str2dn( PyObject* unused, PyObject *args ) /* ldap_set_option (global options) */ -static PyObject* -l_ldap_set_option(PyObject* self, PyObject *args) +static PyObject * +l_ldap_set_option(PyObject *self, PyObject *args) { PyObject *value; int option; if (!PyArg_ParseTuple(args, "iO:set_option", &option, &value)) - return NULL; + return NULL; if (!LDAP_set_option(NULL, option, value)) - return NULL; + return NULL; Py_INCREF(Py_None); return Py_None; } /* ldap_get_option (global options) */ -static PyObject* -l_ldap_get_option(PyObject* self, PyObject *args) +static PyObject * +l_ldap_get_option(PyObject *self, PyObject *args) { int option; if (!PyArg_ParseTuple(args, "i:get_option", &option)) - return NULL; + return NULL; return LDAP_get_option(NULL, option); } - /* methods */ static PyMethodDef methods[] = { - { "initialize", (PyCFunction)l_ldap_initialize, METH_VARARGS }, - { "str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS }, - { "set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS }, - { "get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS }, - { NULL, NULL } + {"initialize", (PyCFunction)l_ldap_initialize, METH_VARARGS}, + {"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS}, + {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, + {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, + {NULL, NULL} }; /* initialisation */ void -LDAPinit_functions( PyObject* d ) { - LDAPadd_methods( d, methods ); +LDAPinit_functions(PyObject *d) +{ + LDAPadd_methods(d, methods); } diff --git a/Modules/functions.h b/Modules/functions.h index 854a9403..2aef9740 100644 --- a/Modules/functions.h +++ b/Modules/functions.h @@ -4,6 +4,6 @@ #define __h_functions_ #include "common.h" -extern void LDAPinit_functions( PyObject* ); +extern void LDAPinit_functions(PyObject *); #endif /* __h_functions_ */ diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index c8f9dfce..f53e681a 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -27,13 +27,13 @@ LDAPControl_DumpList( LDAPControl** lcs ) { } */ /* Free a single LDAPControl object created by Tuple_to_LDAPControl */ - + static void -LDAPControl_DEL( LDAPControl* lc ) +LDAPControl_DEL(LDAPControl *lc) { if (lc == NULL) return; - + if (lc->ldctl_oid) PyMem_DEL(lc->ldctl_oid); PyMem_DEL(lc); @@ -42,16 +42,17 @@ LDAPControl_DEL( LDAPControl* lc ) /* Free an array of LDAPControl objects created by LDAPControls_from_object */ void -LDAPControl_List_DEL( LDAPControl** lcs ) +LDAPControl_List_DEL(LDAPControl **lcs) { - LDAPControl** lcp; + LDAPControl **lcp; + if (lcs == NULL) return; - for ( lcp = lcs; *lcp; lcp++ ) - LDAPControl_DEL( *lcp ); + for (lcp = lcs; *lcp; lcp++) + LDAPControl_DEL(*lcp); - PyMem_DEL( lcs ); + PyMem_DEL(lcs); } /* Takes a tuple of the form: @@ -61,8 +62,8 @@ LDAPControl_List_DEL( LDAPControl** lcs ) * The Value string should represent an ASN.1 encoded structure. */ -static LDAPControl* -Tuple_to_LDAPControl( PyObject* tup ) +static LDAPControl * +Tuple_to_LDAPControl(PyObject *tup) { char *oid; char iscritical; @@ -72,13 +73,14 @@ Tuple_to_LDAPControl( PyObject* tup ) Py_ssize_t len; if (!PyTuple_Check(tup)) { - LDAPerror_TypeError("Tuple_to_LDAPControl(): expected a tuple", tup); - return NULL; + LDAPerror_TypeError("Tuple_to_LDAPControl(): expected a tuple", tup); + return NULL; } - if (!PyArg_ParseTuple( tup, "sbO:Tuple_to_LDAPControl", &oid, &iscritical, &bytes )) + if (!PyArg_ParseTuple + (tup, "sbO:Tuple_to_LDAPControl", &oid, &iscritical, &bytes)) return NULL; - + lc = PyMem_NEW(LDAPControl, 1); if (lc == NULL) { PyErr_NoMemory(); @@ -110,7 +112,7 @@ Tuple_to_LDAPControl( PyObject* tup ) LDAPControl_DEL(lc); return NULL; } - + lc->ldctl_value = berbytes; return lc; @@ -120,41 +122,42 @@ Tuple_to_LDAPControl( PyObject* tup ) * function) into an array of LDAPControl objects. */ int -LDAPControls_from_object(PyObject* list, LDAPControl ***controls_ret) +LDAPControls_from_object(PyObject *list, LDAPControl ***controls_ret) { Py_ssize_t len, i; - LDAPControl** ldcs; - LDAPControl* ldc; - PyObject* item; - + LDAPControl **ldcs; + LDAPControl *ldc; + PyObject *item; + if (!PySequence_Check(list)) { - LDAPerror_TypeError("LDAPControls_from_object(): expected a list", list); + LDAPerror_TypeError("LDAPControls_from_object(): expected a list", + list); return 0; } len = PySequence_Length(list); - ldcs = PyMem_NEW(LDAPControl*, len + 1); + ldcs = PyMem_NEW(LDAPControl *, len + 1); if (ldcs == NULL) { PyErr_NoMemory(); return 0; } for (i = 0; i < len; i++) { - item = PySequence_GetItem(list, i); - if (item == NULL) { - PyMem_DEL(ldcs); - return 0; - } - - ldc = Tuple_to_LDAPControl(item); - if (ldc == NULL) { - Py_DECREF(item); - PyMem_DEL(ldcs); - return 0; - } - - ldcs[i] = ldc; - Py_DECREF(item); + item = PySequence_GetItem(list, i); + if (item == NULL) { + PyMem_DEL(ldcs); + return 0; + } + + ldc = Tuple_to_LDAPControl(item); + if (ldc == NULL) { + Py_DECREF(item); + PyMem_DEL(ldcs); + return 0; + } + + ldcs[i] = ldc; + Py_DECREF(item); } ldcs[len] = NULL; @@ -162,7 +165,7 @@ LDAPControls_from_object(PyObject* list, LDAPControl ***controls_ret) return 1; } -PyObject* +PyObject * LDAPControls_to_List(LDAPControl **ldcs) { PyObject *res = 0, *pyctrl; @@ -170,7 +173,8 @@ LDAPControls_to_List(LDAPControl **ldcs) Py_ssize_t num_ctrls = 0, i; if (tmp) - while (*tmp++) num_ctrls++; + while (*tmp++) + num_ctrls++; if ((res = PyList_New(num_ctrls)) == NULL) { return NULL; @@ -190,59 +194,58 @@ LDAPControls_to_List(LDAPControl **ldcs) return res; } - - /* --------------- en-/decoders ------------- */ /* Matched Values, aka, Values Return Filter */ -static PyObject* +static PyObject * encode_rfc3876(PyObject *self, PyObject *args) { - PyObject *res = 0; - int err; - BerElement *vrber = 0; - char *vrFilter; - struct berval *ctrl_val; - - if (!PyArg_ParseTuple(args, "s:encode_valuesreturnfilter_control", &vrFilter)) { - goto endlbl; - } - - if (!(vrber = ber_alloc_t(LBER_USE_DER))) { - LDAPerr(LDAP_NO_MEMORY); - goto endlbl; - } - - err = ldap_put_vrFilter(vrber, vrFilter); - if (err == -1) { - LDAPerr(LDAP_FILTER_ERROR); - goto endlbl; - } - - err = ber_flatten(vrber, &ctrl_val); - if (err == -1) { - LDAPerr(LDAP_NO_MEMORY); - goto endlbl; - } - - res = LDAPberval_to_object(ctrl_val); - ber_bvfree(ctrl_val); - -endlbl: - if (vrber) - ber_free(vrber, 1); - - return res; + PyObject *res = 0; + int err; + BerElement *vrber = 0; + char *vrFilter; + struct berval *ctrl_val; + + if (!PyArg_ParseTuple + (args, "s:encode_valuesreturnfilter_control", &vrFilter)) { + goto endlbl; + } + + if (!(vrber = ber_alloc_t(LBER_USE_DER))) { + LDAPerr(LDAP_NO_MEMORY); + goto endlbl; + } + + err = ldap_put_vrFilter(vrber, vrFilter); + if (err == -1) { + LDAPerr(LDAP_FILTER_ERROR); + goto endlbl; + } + + err = ber_flatten(vrber, &ctrl_val); + if (err == -1) { + LDAPerr(LDAP_NO_MEMORY); + goto endlbl; + } + + res = LDAPberval_to_object(ctrl_val); + ber_bvfree(ctrl_val); + + endlbl: + if (vrber) + ber_free(vrber, 1); + + return res; } -static PyObject* +static PyObject * encode_rfc2696(PyObject *self, PyObject *args) { PyObject *res = 0; BerElement *ber = 0; struct berval cookie, *ctrl_val; Py_ssize_t cookie_len; - int size = 0; /* ber_int_t is int */ + int size = 0; /* ber_int_t is int */ ber_tag_t tag; if (!PyArg_ParseTuple(args, "is#:encode_page_control", &size, @@ -285,14 +288,13 @@ encode_rfc2696(PyObject *self, PyObject *args) res = LDAPberval_to_object(ctrl_val); ber_bvfree(ctrl_val); - endlbl: + endlbl: if (ber) ber_free(ber, 1); return res; } - -static PyObject* +static PyObject * decode_rfc2696(PyObject *self, PyObject *args) { PyObject *res = 0; @@ -300,7 +302,7 @@ decode_rfc2696(PyObject *self, PyObject *args) struct berval ldctl_value; ber_tag_t tag; struct berval *cookiep; - int count = 0; /* ber_int_t is int */ + int count = 0; /* ber_int_t is int */ Py_ssize_t ldctl_value_len; if (!PyArg_ParseTuple(args, "s#:decode_page_control", @@ -323,13 +325,13 @@ decode_rfc2696(PyObject *self, PyObject *args) res = Py_BuildValue("(iO&)", count, LDAPberval_to_object, cookiep); ber_bvfree(cookiep); - endlbl: + endlbl: if (ber) ber_free(ber, 1); return res; } -static PyObject* +static PyObject * encode_assertion_control(PyObject *self, PyObject *args) { int err; @@ -346,41 +348,35 @@ encode_assertion_control(PyObject *self, PyObject *args) /* XXX: ldap_create() is a nasty and slow hack. It's creating a full blown * LDAP object just to encode assertion controls. */ - Py_BEGIN_ALLOW_THREADS - err = ldap_create(&ld); - Py_END_ALLOW_THREADS - - if (err != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_create"); + Py_BEGIN_ALLOW_THREADS err = ldap_create(&ld); + Py_END_ALLOW_THREADS if (err != LDAP_SUCCESS) + return LDAPerror(ld, "ldap_create"); - err = ldap_create_assertion_control_value(ld,assertion_filterstr,&ctrl_val); + err = + ldap_create_assertion_control_value(ld, assertion_filterstr, + &ctrl_val); if (err != LDAP_SUCCESS) { LDAPerror(ld, "ldap_create_assertion_control_value"); - Py_BEGIN_ALLOW_THREADS - ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS - return NULL; + Py_BEGIN_ALLOW_THREADS ldap_unbind_ext(ld, NULL, NULL); + Py_END_ALLOW_THREADS return NULL; } - Py_BEGIN_ALLOW_THREADS - ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS - - res = LDAPberval_to_object(&ctrl_val); + Py_BEGIN_ALLOW_THREADS ldap_unbind_ext(ld, NULL, NULL); + Py_END_ALLOW_THREADS res = LDAPberval_to_object(&ctrl_val); if (ctrl_val.bv_val != NULL) { ber_memfree(ctrl_val.bv_val); } - endlbl: + endlbl: return res; } static PyMethodDef methods[] = { - {"encode_page_control", encode_rfc2696, METH_VARARGS }, - {"decode_page_control", decode_rfc2696, METH_VARARGS }, - {"encode_valuesreturnfilter_control", encode_rfc3876, METH_VARARGS }, - {"encode_assertion_control", encode_assertion_control, METH_VARARGS }, - { NULL, NULL } + {"encode_page_control", encode_rfc2696, METH_VARARGS}, + {"decode_page_control", decode_rfc2696, METH_VARARGS}, + {"encode_valuesreturnfilter_control", encode_rfc3876, METH_VARARGS}, + {"encode_assertion_control", encode_assertion_control, METH_VARARGS}, + {NULL, NULL} }; void diff --git a/Modules/ldapcontrol.h b/Modules/ldapcontrol.h index 1c09d954..de694c07 100644 --- a/Modules/ldapcontrol.h +++ b/Modules/ldapcontrol.h @@ -7,8 +7,8 @@ #include "ldap.h" void LDAPinit_control(PyObject *d); -void LDAPControl_List_DEL( LDAPControl** ); -int LDAPControls_from_object(PyObject *, LDAPControl ***); -PyObject* LDAPControls_to_List(LDAPControl **ldcs); +void LDAPControl_List_DEL(LDAPControl **); +int LDAPControls_from_object(PyObject *, LDAPControl ***); +PyObject *LDAPControls_to_List(LDAPControl **ldcs); #endif /* __h_ldapcontrol */ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index f37c1b8d..8bd55ab4 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -21,56 +21,56 @@ static char author_str[] = STR(LDAPMODULE_AUTHOR); static char license_str[] = STR(LDAPMODULE_LICENSE); static void -init_pkginfo( PyObject* m ) +init_pkginfo(PyObject *m) { - PyModule_AddStringConstant(m, "__version__", version_str); - PyModule_AddStringConstant(m, "__author__", author_str); - PyModule_AddStringConstant(m, "__license__", license_str); + PyModule_AddStringConstant(m, "__version__", version_str); + PyModule_AddStringConstant(m, "__author__", author_str); + PyModule_AddStringConstant(m, "__license__", license_str); } /* dummy module methods */ -static PyMethodDef methods[] = { - { NULL, NULL } +static PyMethodDef methods[] = { + {NULL, NULL} }; /* module initialisation */ - /* Common initialization code */ -PyObject* init_ldap_module(void) +PyObject * +init_ldap_module(void) { - PyObject *m, *d; + PyObject *m, *d; - /* Create the module and add the functions */ + /* Create the module and add the functions */ #if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef ldap_moduledef = { - PyModuleDef_HEAD_INIT, - "_ldap", /* m_name */ - "", /* m_doc */ - -1, /* m_size */ - methods, /* m_methods */ - }; - m = PyModule_Create(&ldap_moduledef); + static struct PyModuleDef ldap_moduledef = { + PyModuleDef_HEAD_INIT, + "_ldap", /* m_name */ + "", /* m_doc */ + -1, /* m_size */ + methods, /* m_methods */ + }; + m = PyModule_Create(&ldap_moduledef); #else - m = Py_InitModule("_ldap", methods); + m = Py_InitModule("_ldap", methods); #endif - /* Initialize LDAP class */ - if (PyType_Ready(&LDAP_Type) < 0) { - Py_DECREF(m); - return NULL; - } + /* Initialize LDAP class */ + if (PyType_Ready(&LDAP_Type) < 0) { + Py_DECREF(m); + return NULL; + } - /* Add some symbolic constants to the module */ - d = PyModule_GetDict(m); + /* Add some symbolic constants to the module */ + d = PyModule_GetDict(m); - init_pkginfo(m); + init_pkginfo(m); - if (LDAPinit_constants(m) == -1) { - return NULL; - } + if (LDAPinit_constants(m) == -1) { + return NULL; + } - LDAPinit_functions(d); - LDAPinit_control(d); + LDAPinit_functions(d); + LDAPinit_control(d); /* Marker for LDAPBytesWarning stack walking * See _raise_byteswarning in ldapobject.py @@ -79,20 +79,23 @@ PyObject* init_ldap_module(void) return NULL; } - /* Check for errors */ - if (PyErr_Occurred()) - Py_FatalError("can't initialize module _ldap"); + /* Check for errors */ + if (PyErr_Occurred()) + Py_FatalError("can't initialize module _ldap"); - return m; + return m; } - #if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC init_ldap() { +PyMODINIT_FUNC +init_ldap() +{ init_ldap_module(); } #else -PyMODINIT_FUNC PyInit__ldap() { +PyMODINIT_FUNC +PyInit__ldap() +{ return init_ldap_module(); } #endif diff --git a/Modules/message.c b/Modules/message.c index b7f3ae79..cf3ea3b4 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -21,261 +21,268 @@ * be returned */ PyObject * -LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermediates) +LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, + int add_intermediates) { /* we convert an LDAP message into a python structure. * It is always a list of dictionaries. * We always free m. */ - PyObject *result, *pyctrls = 0; - LDAPMessage* entry; - LDAPControl **serverctrls = 0; - int rc; + PyObject *result, *pyctrls = 0; + LDAPMessage *entry; + LDAPControl **serverctrls = 0; + int rc; - result = PyList_New(0); - if (result == NULL) { - ldap_msgfree( m ); - return NULL; - } + result = PyList_New(0); + if (result == NULL) { + ldap_msgfree(m); + return NULL; + } - for(entry = ldap_first_entry(ld,m); - entry != NULL; - entry = ldap_next_entry(ld,entry)) - { - char *dn; - char *attr; - BerElement *ber = NULL; - PyObject* entrytuple; - PyObject* attrdict; - PyObject* pydn; + for (entry = ldap_first_entry(ld, m); + entry != NULL; entry = ldap_next_entry(ld, entry)) { + char *dn; + char *attr; + BerElement *ber = NULL; + PyObject *entrytuple; + PyObject *attrdict; + PyObject *pydn; - dn = ldap_get_dn( ld, entry ); - if (dn == NULL) { - Py_DECREF(result); - ldap_msgfree( m ); - return LDAPerror( ld, "ldap_get_dn" ); - } + dn = ldap_get_dn(ld, entry); + if (dn == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + return LDAPerror(ld, "ldap_get_dn"); + } - attrdict = PyDict_New(); - if (attrdict == NULL) { - Py_DECREF(result); - ldap_msgfree( m ); - ldap_memfree(dn); - return NULL; - } + attrdict = PyDict_New(); + if (attrdict == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + ldap_memfree(dn); + return NULL; + } - rc = ldap_get_entry_controls( ld, entry, &serverctrls ); - if (rc) { - Py_DECREF(result); - ldap_msgfree( m ); - ldap_memfree(dn); - return LDAPerror( ld, "ldap_get_entry_controls" ); - } + rc = ldap_get_entry_controls(ld, entry, &serverctrls); + if (rc) { + Py_DECREF(result); + ldap_msgfree(m); + ldap_memfree(dn); + return LDAPerror(ld, "ldap_get_entry_controls"); + } - /* convert serverctrls to list of tuples */ - if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) { - int err = LDAP_NO_MEMORY; - ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err ); - Py_DECREF(result); - ldap_msgfree( m ); - ldap_memfree(dn); - ldap_controls_free(serverctrls); - return LDAPerror( ld, "LDAPControls_to_List" ); - } - ldap_controls_free(serverctrls); + /* convert serverctrls to list of tuples */ + if (!(pyctrls = LDAPControls_to_List(serverctrls))) { + int err = LDAP_NO_MEMORY; - /* Fill attrdict with lists */ - for( attr = ldap_first_attribute( ld, entry, &ber ); - attr != NULL; - attr = ldap_next_attribute( ld, entry, ber ) - ) { - PyObject* valuelist; - PyObject* pyattr; - struct berval **bvals; + ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &err); + Py_DECREF(result); + ldap_msgfree(m); + ldap_memfree(dn); + ldap_controls_free(serverctrls); + return LDAPerror(ld, "LDAPControls_to_List"); + } + ldap_controls_free(serverctrls); - pyattr = PyUnicode_FromString(attr); + /* Fill attrdict with lists */ + for (attr = ldap_first_attribute(ld, entry, &ber); + attr != NULL; attr = ldap_next_attribute(ld, entry, ber) + ) { + PyObject *valuelist; + PyObject *pyattr; + struct berval **bvals; - bvals = ldap_get_values_len( ld, entry, attr ); + pyattr = PyUnicode_FromString(attr); - /* Find which list to append to */ - if ( PyDict_Contains( attrdict, pyattr ) ) { - valuelist = PyDict_GetItem( attrdict, pyattr ); - } else { - valuelist = PyList_New(0); - if (valuelist != NULL && PyDict_SetItem(attrdict, - pyattr, valuelist) == -1) { - Py_DECREF(valuelist); - valuelist = NULL; /* catch error later */ - } - } + bvals = ldap_get_values_len(ld, entry, attr); - if (valuelist == NULL) { - Py_DECREF(pyattr); - Py_DECREF(attrdict); - Py_DECREF(result); - if (ber != NULL) - ber_free(ber, 0); - ldap_msgfree( m ); - ldap_memfree(attr); - ldap_memfree(dn); - Py_XDECREF(pyctrls); - return NULL; - } + /* Find which list to append to */ + if (PyDict_Contains(attrdict, pyattr)) { + valuelist = PyDict_GetItem(attrdict, pyattr); + } + else { + valuelist = PyList_New(0); + if (valuelist != NULL && PyDict_SetItem(attrdict, + pyattr, + valuelist) == -1) { + Py_DECREF(valuelist); + valuelist = NULL; /* catch error later */ + } + } - if (bvals != NULL) { - Py_ssize_t i; - for (i=0; bvals[i]; i++) { - PyObject *valuestr; + if (valuelist == NULL) { + Py_DECREF(pyattr); + Py_DECREF(attrdict); + Py_DECREF(result); + if (ber != NULL) + ber_free(ber, 0); + ldap_msgfree(m); + ldap_memfree(attr); + ldap_memfree(dn); + Py_XDECREF(pyctrls); + return NULL; + } - valuestr = LDAPberval_to_object(bvals[i]); - if (PyList_Append( valuelist, valuestr ) == -1) { - Py_DECREF(pyattr); - Py_DECREF(attrdict); - Py_DECREF(result); - Py_DECREF(valuestr); - Py_DECREF(valuelist); - if (ber != NULL) - ber_free(ber, 0); - ldap_msgfree( m ); - ldap_memfree(attr); - ldap_memfree(dn); - Py_XDECREF(pyctrls); - return NULL; - } - Py_DECREF(valuestr); - } - ldap_value_free_len(bvals); - } - Py_DECREF(pyattr); - Py_DECREF( valuelist ); - ldap_memfree(attr); - } + if (bvals != NULL) { + Py_ssize_t i; - pydn = PyUnicode_FromString(dn); - if (pydn == NULL) { - Py_DECREF(result); - ldap_msgfree( m ); - ldap_memfree(dn); - return NULL; - } + for (i = 0; bvals[i]; i++) { + PyObject *valuestr; - if (add_ctrls) { - entrytuple = Py_BuildValue("(OOO)", pydn, attrdict, pyctrls); - } else { - entrytuple = Py_BuildValue("(OO)", pydn, attrdict); - } - Py_DECREF(pydn); - ldap_memfree(dn); - Py_DECREF(attrdict); - Py_XDECREF(pyctrls); - PyList_Append(result, entrytuple); - Py_DECREF(entrytuple); - if (ber != NULL) - ber_free(ber, 0); - } - for(entry = ldap_first_reference(ld,m); - entry != NULL; - entry = ldap_next_reference(ld,entry)) - { - char **refs = NULL; - PyObject* entrytuple; - PyObject* reflist = PyList_New(0); + valuestr = LDAPberval_to_object(bvals[i]); + if (PyList_Append(valuelist, valuestr) == -1) { + Py_DECREF(pyattr); + Py_DECREF(attrdict); + Py_DECREF(result); + Py_DECREF(valuestr); + Py_DECREF(valuelist); + if (ber != NULL) + ber_free(ber, 0); + ldap_msgfree(m); + ldap_memfree(attr); + ldap_memfree(dn); + Py_XDECREF(pyctrls); + return NULL; + } + Py_DECREF(valuestr); + } + ldap_value_free_len(bvals); + } + Py_DECREF(pyattr); + Py_DECREF(valuelist); + ldap_memfree(attr); + } - if (reflist == NULL) { - Py_DECREF(result); - ldap_msgfree( m ); - return NULL; - } - if (ldap_parse_reference(ld, entry, &refs, &serverctrls, 0) != LDAP_SUCCESS) { - Py_DECREF(reflist); - Py_DECREF(result); - ldap_msgfree( m ); - return LDAPerror( ld, "ldap_parse_reference" ); - } - /* convert serverctrls to list of tuples */ - if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) { - int err = LDAP_NO_MEMORY; - ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err ); - Py_DECREF(reflist); - Py_DECREF(result); - ldap_msgfree( m ); - ldap_controls_free(serverctrls); - return LDAPerror( ld, "LDAPControls_to_List" ); - } - ldap_controls_free(serverctrls); - if (refs) { - Py_ssize_t i; - for (i=0; refs[i] != NULL; i++) { - /* A referal is a distinguishedName => unicode */ - PyObject *refstr = PyUnicode_FromString(refs[i]); - PyList_Append(reflist, refstr); - Py_DECREF(refstr); - } - ber_memvfree( (void **) refs ); - } - if (add_ctrls) { - entrytuple = Py_BuildValue("(sOO)", NULL, reflist, pyctrls); - } else { - entrytuple = Py_BuildValue("(sO)", NULL, reflist); - } - Py_DECREF(reflist); - Py_XDECREF(pyctrls); - PyList_Append(result, entrytuple); - Py_DECREF(entrytuple); - } - if (add_intermediates) { - for(entry = ldap_first_message(ld,m); - entry != NULL; - entry = ldap_next_message(ld,entry)) - { - /* list of tuples */ - /* each tuple is OID, Berval, controllist */ - if ( LDAP_RES_INTERMEDIATE == ldap_msgtype( entry ) ) { - PyObject* valtuple; - PyObject *valuestr; - char *retoid = 0; - PyObject *pyoid; - struct berval *retdata = 0; + pydn = PyUnicode_FromString(dn); + if (pydn == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + ldap_memfree(dn); + return NULL; + } - if (ldap_parse_intermediate( ld, entry, &retoid, &retdata, &serverctrls, 0 ) != LDAP_SUCCESS) { - Py_DECREF(result); - ldap_msgfree( m ); - return LDAPerror( ld, "ldap_parse_intermediate" ); - } - /* convert serverctrls to list of tuples */ - if ( ! ( pyctrls = LDAPControls_to_List( serverctrls ) ) ) { - int err = LDAP_NO_MEMORY; - ldap_set_option( ld, LDAP_OPT_ERROR_NUMBER, &err ); - Py_DECREF(result); - ldap_msgfree( m ); - ldap_controls_free(serverctrls); - ldap_memfree( retoid ); - ber_bvfree( retdata ); - return LDAPerror( ld, "LDAPControls_to_List" ); - } - ldap_controls_free(serverctrls); + if (add_ctrls) { + entrytuple = Py_BuildValue("(OOO)", pydn, attrdict, pyctrls); + } + else { + entrytuple = Py_BuildValue("(OO)", pydn, attrdict); + } + Py_DECREF(pydn); + ldap_memfree(dn); + Py_DECREF(attrdict); + Py_XDECREF(pyctrls); + PyList_Append(result, entrytuple); + Py_DECREF(entrytuple); + if (ber != NULL) + ber_free(ber, 0); + } + for (entry = ldap_first_reference(ld, m); + entry != NULL; entry = ldap_next_reference(ld, entry)) { + char **refs = NULL; + PyObject *entrytuple; + PyObject *reflist = PyList_New(0); - valuestr = LDAPberval_to_object(retdata); - ber_bvfree( retdata ); - pyoid = PyUnicode_FromString(retoid); - ldap_memfree( retoid ); - if (pyoid == NULL) { - Py_DECREF(result); - ldap_msgfree( m ); - return NULL; - } - valtuple = Py_BuildValue("(OOO)", pyoid, - valuestr ? valuestr : Py_None, - pyctrls); - Py_DECREF(pyoid); - Py_DECREF(valuestr); - Py_XDECREF(pyctrls); - PyList_Append(result, valtuple); - Py_DECREF(valtuple); - } - } - } - ldap_msgfree( m ); - return result; + if (reflist == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + return NULL; + } + if (ldap_parse_reference(ld, entry, &refs, &serverctrls, 0) != + LDAP_SUCCESS) { + Py_DECREF(reflist); + Py_DECREF(result); + ldap_msgfree(m); + return LDAPerror(ld, "ldap_parse_reference"); + } + /* convert serverctrls to list of tuples */ + if (!(pyctrls = LDAPControls_to_List(serverctrls))) { + int err = LDAP_NO_MEMORY; + + ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &err); + Py_DECREF(reflist); + Py_DECREF(result); + ldap_msgfree(m); + ldap_controls_free(serverctrls); + return LDAPerror(ld, "LDAPControls_to_List"); + } + ldap_controls_free(serverctrls); + if (refs) { + Py_ssize_t i; + + for (i = 0; refs[i] != NULL; i++) { + /* A referal is a distinguishedName => unicode */ + PyObject *refstr = PyUnicode_FromString(refs[i]); + + PyList_Append(reflist, refstr); + Py_DECREF(refstr); + } + ber_memvfree((void **)refs); + } + if (add_ctrls) { + entrytuple = Py_BuildValue("(sOO)", NULL, reflist, pyctrls); + } + else { + entrytuple = Py_BuildValue("(sO)", NULL, reflist); + } + Py_DECREF(reflist); + Py_XDECREF(pyctrls); + PyList_Append(result, entrytuple); + Py_DECREF(entrytuple); + } + if (add_intermediates) { + for (entry = ldap_first_message(ld, m); + entry != NULL; entry = ldap_next_message(ld, entry)) { + /* list of tuples */ + /* each tuple is OID, Berval, controllist */ + if (LDAP_RES_INTERMEDIATE == ldap_msgtype(entry)) { + PyObject *valtuple; + PyObject *valuestr; + char *retoid = 0; + PyObject *pyoid; + struct berval *retdata = 0; + + if (ldap_parse_intermediate + (ld, entry, &retoid, &retdata, &serverctrls, + 0) != LDAP_SUCCESS) { + Py_DECREF(result); + ldap_msgfree(m); + return LDAPerror(ld, "ldap_parse_intermediate"); + } + /* convert serverctrls to list of tuples */ + if (!(pyctrls = LDAPControls_to_List(serverctrls))) { + int err = LDAP_NO_MEMORY; + + ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &err); + Py_DECREF(result); + ldap_msgfree(m); + ldap_controls_free(serverctrls); + ldap_memfree(retoid); + ber_bvfree(retdata); + return LDAPerror(ld, "LDAPControls_to_List"); + } + ldap_controls_free(serverctrls); + + valuestr = LDAPberval_to_object(retdata); + ber_bvfree(retdata); + pyoid = PyUnicode_FromString(retoid); + ldap_memfree(retoid); + if (pyoid == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + return NULL; + } + valtuple = Py_BuildValue("(OOO)", pyoid, + valuestr ? valuestr : Py_None, + pyctrls); + Py_DECREF(pyoid); + Py_DECREF(valuestr); + Py_XDECREF(pyctrls); + PyList_Append(result, valtuple); + Py_DECREF(valtuple); + } + } + } + ldap_msgfree(m); + return result; } diff --git a/Modules/message.h b/Modules/message.h index c3522ac3..2978ea56 100644 --- a/Modules/message.h +++ b/Modules/message.h @@ -1,12 +1,13 @@ /* See https://www.python-ldap.org/ for details. */ -#ifndef __h_message -#define __h_message +#ifndef __h_message +#define __h_message #include "common.h" #include "lber.h" #include "ldap.h" -extern PyObject* LDAPmessage_to_python( LDAP*ld, LDAPMessage*m, int add_ctrls, int add_intermediates ); +extern PyObject *LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, + int add_intermediates); #endif /* __h_message_ */ diff --git a/Modules/options.c b/Modules/options.c index ee606d46..85560e62 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -7,9 +7,10 @@ #include "options.h" void -set_timeval_from_double( struct timeval *tv, double d ) { - tv->tv_usec = (long) ( fmod(d, 1.0) * 1000000.0 ); - tv->tv_sec = (long) floor(d); +set_timeval_from_double(struct timeval *tv, double d) +{ + tv->tv_usec = (long)(fmod(d, 1.0) * 1000000.0); + tv->tv_sec = (long)floor(d); } /** @@ -23,7 +24,7 @@ option_error(int res, const char *fn) PyErr_SetString(PyExc_ValueError, "option error"); else if (res == LDAP_PARAM_ERROR) PyErr_SetString(PyExc_ValueError, "parameter error"); - else if (res == LDAP_NO_MEMORY) + else if (res == LDAP_NO_MEMORY) PyErr_NoMemory(); else PyErr_Format(PyExc_SystemError, "error %d from %s", res, fn); @@ -48,15 +49,15 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) ld = self ? self->ldap : NULL; - switch(option) { + switch (option) { case LDAP_OPT_API_INFO: case LDAP_OPT_API_FEATURE_INFO: #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: #endif - /* Read-only options */ - PyErr_SetString(PyExc_ValueError, "read-only option"); - return 0; + /* Read-only options */ + PyErr_SetString(PyExc_ValueError, "read-only option"); + return 0; case LDAP_OPT_REFERRALS: case LDAP_OPT_RESTART: #ifdef LDAP_OPT_X_SASL_NOCANON @@ -65,9 +66,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_CONNECT_ASYNC case LDAP_OPT_CONNECT_ASYNC: #endif - /* Truth-value options */ - ptr = PyObject_IsTrue(value) ? LDAP_OPT_ON : LDAP_OPT_OFF; - break; + /* Truth-value options */ + ptr = PyObject_IsTrue(value) ? LDAP_OPT_ON : LDAP_OPT_OFF; + break; case LDAP_OPT_DEREF: case LDAP_OPT_SIZELIMIT: @@ -102,11 +103,11 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) case LDAP_OPT_X_KEEPALIVE_INTERVAL: #endif - /* integer value options */ - if (!PyArg_Parse(value, "i:set_option", &intval)) - return 0; - ptr = &intval; - break; + /* integer value options */ + if (!PyArg_Parse(value, "i:set_option", &intval)) + return 0; + ptr = &intval; + break; case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE @@ -129,69 +130,71 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SECPROPS: #endif - /* String valued options */ - if (!PyArg_Parse(value, "s:set_option", &strval)) - return 0; - ptr = strval; - break; + /* String valued options */ + if (!PyArg_Parse(value, "s:set_option", &strval)) + return 0; + ptr = strval; + break; case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Float valued timeval options */ if (value == Py_None) { /* None is mapped to infinity timeout */ doubleval = -1; - } else { + } + else { /* 'd' handles int/long */ if (!PyArg_Parse(value, "d:set_option", &doubleval)) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { /* TypeError: mention either float or None is expected */ PyErr_Clear(); - PyErr_Format( - PyExc_TypeError, - "A float or None is expected for timeout, got %.100s", - Py_TYPE(value)->tp_name - ); + PyErr_Format(PyExc_TypeError, + "A float or None is expected for timeout, got %.100s", + Py_TYPE(value)->tp_name); } return 0; } } if (doubleval >= 0) { - set_timeval_from_double( &tv, doubleval ); + set_timeval_from_double(&tv, doubleval); ptr = &tv; - } else if (doubleval == -1) { + } + else if (doubleval == -1) { /* -1 is infinity timeout */ tv.tv_sec = -1; tv.tv_usec = 0; ptr = &tv; - } else { - PyErr_Format( - PyExc_ValueError, - "timeout must be >= 0 or -1/None for infinity, got %d", - option - ); + } + else { + PyErr_Format(PyExc_ValueError, + "timeout must be >= 0 or -1/None for infinity, got %d", + option); return 0; } break; case LDAP_OPT_SERVER_CONTROLS: case LDAP_OPT_CLIENT_CONTROLS: - if (!LDAPControls_from_object(value, &controls)) - return 0; - ptr = controls; - break; + if (!LDAPControls_from_object(value, &controls)) + return 0; + ptr = controls; + break; default: - PyErr_Format(PyExc_ValueError, "unknown option %d", option); - return 0; + PyErr_Format(PyExc_ValueError, "unknown option %d", option); + return 0; } - - if (self) LDAP_BEGIN_ALLOW_THREADS(self); + + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); res = ldap_set_option(ld, option, ptr); - if (self) LDAP_END_ALLOW_THREADS(self); + if (self) + LDAP_END_ALLOW_THREADS(self); - if ((option == LDAP_OPT_SERVER_CONTROLS) || (option == LDAP_OPT_CLIENT_CONTROLS)) + if ((option == LDAP_OPT_SERVER_CONTROLS) || + (option == LDAP_OPT_CLIENT_CONTROLS)) LDAPControl_List_DEL(controls); - + if (res != LDAP_OPT_SUCCESS) { option_error(res, "ldap_set_option"); return 0; @@ -215,41 +218,44 @@ LDAP_get_option(LDAPObject *self, int option) ld = self ? self->ldap : NULL; - switch(option) { + switch (option) { case LDAP_OPT_API_INFO: - apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION; - if (self) LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option( ld, option, &apiinfo ); - if (self) LDAP_END_ALLOW_THREADS(self); - if (res != LDAP_OPT_SUCCESS) - return option_error(res, "ldap_get_option"); - - /* put the extensions into tuple form */ - num_extensions = 0; - while (apiinfo.ldapai_extensions[num_extensions]) - num_extensions++; - extensions = PyTuple_New(num_extensions); - for (i = 0; i < num_extensions; i++) - PyTuple_SET_ITEM(extensions, i, - PyUnicode_FromString(apiinfo.ldapai_extensions[i])); + apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION; + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(ld, option, &apiinfo); + if (self) + LDAP_END_ALLOW_THREADS(self); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + + /* put the extensions into tuple form */ + num_extensions = 0; + while (apiinfo.ldapai_extensions[num_extensions]) + num_extensions++; + extensions = PyTuple_New(num_extensions); + for (i = 0; i < num_extensions; i++) + PyTuple_SET_ITEM(extensions, i, + PyUnicode_FromString(apiinfo. + ldapai_extensions[i])); - /* return api info as a dictionary */ - v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}", - "info_version", apiinfo.ldapai_info_version, - "api_version", apiinfo.ldapai_api_version, - "protocol_version", apiinfo.ldapai_protocol_version, - "vendor_name", apiinfo.ldapai_vendor_name, - "vendor_version", apiinfo.ldapai_vendor_version, - "extensions", extensions); + /* return api info as a dictionary */ + v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}", + "info_version", apiinfo.ldapai_info_version, + "api_version", apiinfo.ldapai_api_version, + "protocol_version", apiinfo.ldapai_protocol_version, + "vendor_name", apiinfo.ldapai_vendor_name, + "vendor_version", apiinfo.ldapai_vendor_version, + "extensions", extensions); - if (apiinfo.ldapai_vendor_name) - ldap_memfree(apiinfo.ldapai_vendor_name); - for (i = 0; i < num_extensions; i++) - ldap_memfree(apiinfo.ldapai_extensions[i]); - ldap_memfree(apiinfo.ldapai_extensions); - Py_DECREF(extensions); + if (apiinfo.ldapai_vendor_name) + ldap_memfree(apiinfo.ldapai_vendor_name); + for (i = 0; i < num_extensions; i++) + ldap_memfree(apiinfo.ldapai_extensions[i]); + ldap_memfree(apiinfo.ldapai_extensions); + Py_DECREF(extensions); - return v; + return v; #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: @@ -292,13 +298,15 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL case LDAP_OPT_X_KEEPALIVE_INTERVAL: #endif - /* Integer-valued options */ - if (self) LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &intval); - if (self) LDAP_END_ALLOW_THREADS(self); - if (res != LDAP_OPT_SUCCESS) - return option_error(res, "ldap_get_option"); - return PyInt_FromLong(intval); + /* Integer-valued options */ + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(ld, option, &intval); + if (self) + LDAP_END_ALLOW_THREADS(self); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyInt_FromLong(intval); case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: @@ -338,53 +346,59 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_X_SASL_USERNAME: #endif #endif - /* String-valued options */ - if (self) LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &strval); - if (self) LDAP_END_ALLOW_THREADS(self); - if (res != LDAP_OPT_SUCCESS) - return option_error(res, "ldap_get_option"); - if (strval == NULL) { - Py_INCREF(Py_None); - return Py_None; - } - v = PyUnicode_FromString(strval); - ldap_memfree(strval); - return v; + /* String-valued options */ + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(ld, option, &strval); + if (self) + LDAP_END_ALLOW_THREADS(self); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + if (strval == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + v = PyUnicode_FromString(strval); + ldap_memfree(strval); + return v; case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: - /* Double-valued timeval options */ - if (self) LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &tv); - if (self) LDAP_END_ALLOW_THREADS(self); - if (res != LDAP_OPT_SUCCESS) - return option_error(res, "ldap_get_option"); - if (tv == NULL) { - Py_INCREF(Py_None); - return Py_None; - } - v = PyFloat_FromDouble( - (double) tv->tv_sec + ( (double) tv->tv_usec / 1000000.0 ) + /* Double-valued timeval options */ + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(ld, option, &tv); + if (self) + LDAP_END_ALLOW_THREADS(self); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + if (tv == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + v = PyFloat_FromDouble((double)tv->tv_sec + + ((double)tv->tv_usec / 1000000.0) ); - ldap_memfree(tv); - return v; + ldap_memfree(tv); + return v; case LDAP_OPT_SERVER_CONTROLS: case LDAP_OPT_CLIENT_CONTROLS: - if (self) LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &lcs); - if (self) LDAP_END_ALLOW_THREADS(self); + if (self) + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(ld, option, &lcs); + if (self) + LDAP_END_ALLOW_THREADS(self); + + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); - if (res != LDAP_OPT_SUCCESS) - return option_error(res, "ldap_get_option"); + v = LDAPControls_to_List(lcs); + ldap_controls_free(lcs); + return v; - v = LDAPControls_to_List(lcs); - ldap_controls_free(lcs); - return v; - default: - PyErr_Format(PyExc_ValueError, "unknown option %d", option); - return NULL; + PyErr_Format(PyExc_ValueError, "unknown option %d", option); + return NULL; } } diff --git a/Modules/options.h b/Modules/options.h index 570fdc15..fd6a5ce2 100644 --- a/Modules/options.h +++ b/Modules/options.h @@ -4,4 +4,4 @@ int LDAP_optionval_by_name(const char *name); int LDAP_set_option(LDAPObject *self, int option, PyObject *value); PyObject *LDAP_get_option(LDAPObject *self, int option); -void set_timeval_from_double( struct timeval *tv, double d ); +void set_timeval_from_double(struct timeval *tv, double d); From 60b761b5b2ac4da380f89a3357a1628acb3b8471 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 14 Mar 2018 07:12:42 -0700 Subject: [PATCH 021/206] Remove long deprecated functions ldap.open() and ldap.init() The functions have been deprecated since at least 2011. That has been lots of time for library users to see the warning and update calling code. https://github.com/python-ldap/python-ldap/pull/124 --- Doc/reference/ldap.rst | 26 ++++---------------------- Lib/ldap/__init__.py | 2 +- Lib/ldap/functions.py | 30 ------------------------------ 3 files changed, 5 insertions(+), 53 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 5d15158e..6203a2d9 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -67,28 +67,6 @@ This module defines the following functions: :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator -.. py:function:: open(host [, port=PORT]) -> LDAPObject object - - Opens a new connection with an LDAP server, and return an LDAP object (see - :ref:`ldap-objects`) used to perform operations on that server. *host* is a - string containing solely the host name. *port* is an integer specifying the - port where the LDAP server is listening (default is 389). - - .. deprecated:: 3.0 - - ``ldap.open()`` is deprecated. It will be removed in version 3.1. Use - :func:`ldap.initialize()` with a URI like ``'ldap://:'`` - instead. - -.. py:function:: init(host [, port=PORT]) -> LDAPObject object - - Alias of :func:`ldap.open()`. - - .. deprecated:: 3.0 - - ``ldap.init()`` is deprecated. It will be removed in version 3.1. Use - :func:`ldap.initialize()` with a URI like ``'ldap://:'`` - instead. .. py:function:: get_option(option) -> int|string @@ -100,6 +78,10 @@ This module defines the following functions: This function sets the value of the global option specified by *option* to *invalue*. +.. versionchanged:: 3.1 + + The deprecated functions ``ldap.init()`` and ``ldap.open()`` were removed. + .. _ldap-constants: diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 72e1e4bc..068f9e67 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -91,7 +91,7 @@ def release(self): # Create module-wide lock for serializing all calls into underlying LDAP lib _ldap_module_lock = LDAPLock(desc='Module wide') -from ldap.functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs +from ldap.functions import initialize,get_option,set_option,escape_str,strf_secs,strp_secs from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 6c351eff..ae83d08a 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -85,36 +85,6 @@ def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, b return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode) -def open(host,port=389,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None,bytes_mode=None): - """ - Return LDAPObject instance by opening LDAP connection to - specified LDAP host - - Parameters: - host - LDAP host and port, e.g. localhost - port - integer specifying the port number to use, e.g. 389 - trace_level - If non-zero a trace output of LDAP calls is generated. - trace_file - File object where to write the trace output to. - Default is to use stdout. - bytes_mode - Whether to enable "bytes_mode" for backwards compatibility under Py2. - """ - import warnings - warnings.warn( - 'ldap.open() is deprecated. Use ldap.initialize() instead. It will be ' - 'removed in python-ldap 3.1.', - category=DeprecationWarning, - stacklevel=2, - ) - return initialize('ldap://%s:%d' % (host,port),trace_level,trace_file,trace_stack_limit,bytes_mode) - -init = open - - def get_option(option): """ get_option(name) -> value From 28528ea20114b44b1bab6f01041d985c0873ee78 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 14 Mar 2018 12:32:24 +0100 Subject: [PATCH 022/206] cidict: Make iteration over cidict yield same values as keys() --- Lib/ldap/cidict.py | 3 +++ Tests/t_cidict.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index f7e8d398..3ba2feba 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -44,6 +44,9 @@ def has_key(self,key): def __contains__(self,key): return IterableUserDict.__contains__(self, key.lower()) + def __iter__(self): + return iter(self.keys()) + def keys(self): return self._keys.values() diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 8e5d8d6e..f8b993f0 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -36,6 +36,8 @@ def test_cidict(self): self.assertEqual(cix.get("xyz", None), 987) cix_keys = sorted(cix.keys()) self.assertEqual(cix_keys, ['AbCDeF','xYZ']) + cix_keys = sorted(cix) + self.assertEqual(cix_keys, ['AbCDeF','xYZ']) cix_items = sorted(cix.items()) self.assertEqual(cix_items, [('AbCDeF',123), ('xYZ',987)]) del cix["abcdEF"] From 4ea6e31b361eacf5afc7c831211e5d908c340e1b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 11:18:23 +0100 Subject: [PATCH 023/206] Lib: Use items() when appropriate in dict iteration When both key and value are needed, for key, value in dictionary.items(): ... is used instead of: for key in dictionary.keys(): value = dictionary[key] ... Thanks to Jon Dufresne for finding keys() usages. --- Lib/ldap/cidict.py | 4 ++-- Lib/ldap/modlist.py | 14 +++++++------- Lib/ldap/schema/models.py | 4 ++-- Lib/ldap/schema/subentry.py | 7 +++---- Lib/ldapurl.py | 4 ++-- Lib/ldif.py | 4 ++-- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 3ba2feba..4f7e0910 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -35,8 +35,8 @@ def __delitem__(self,key): del self.data[lower_key] def update(self,dict): - for key in dict.keys(): - self[key] = dict[key] + for key, value in dict.items(): + self[key] = value def has_key(self,key): return key in self diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 6f513127..4acf4e9f 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -13,14 +13,14 @@ def addModlist(entry,ignore_attr_types=None): """Build modify list for call of method LDAPObject.add()""" ignore_attr_types = {v.lower() for v in ignore_attr_types or []} modlist = [] - for attrtype in entry.keys(): + for attrtype, value in entry.items(): if attrtype.lower() in ignore_attr_types: # This attribute type is ignored continue # Eliminate empty attr value strings in list - attrvaluelist = [item for item in entry[attrtype] if item is not None] + attrvaluelist = [item for item in value if item is not None] if attrvaluelist: - modlist.append((attrtype,entry[attrtype])) + modlist.append((attrtype, value)) return modlist # addModlist() @@ -52,13 +52,13 @@ def modifyModlist( attrtype_lower_map = {} for a in old_entry.keys(): attrtype_lower_map[a.lower()]=a - for attrtype in new_entry.keys(): + for attrtype, value in new_entry.items(): attrtype_lower = attrtype.lower() if attrtype_lower in ignore_attr_types: # This attribute type is ignored continue # Filter away null-strings - new_value = [item for item in new_entry[attrtype] if item is not None] + new_value = [item for item in value if item is not None] if attrtype_lower in attrtype_lower_map: old_value = old_entry.get(attrtype_lower_map[attrtype_lower],[]) old_value = [item for item in old_value if item is not None] @@ -88,10 +88,10 @@ def modifyModlist( if not ignore_oldexistent: # Remove all attributes of old_entry which are not present # in new_entry at all - for a in attrtype_lower_map.keys(): + for a, val in attrtype_lower_map.items(): if a in ignore_attr_types: # This attribute type is ignored continue - attrtype = attrtype_lower_map[a] + attrtype = val modlist.append((ldap.MOD_DELETE,attrtype,None)) return modlist # modifyModlist() diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 9a8cb5b2..feb7bff1 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -655,8 +655,8 @@ def _at2key(self,nameoroid): return t def update(self,dict): - for key in dict.keys(): - self[key] = dict[key] + for key, value in dict.values(): + self[key] = value def __contains__(self,nameoroid): return self._at2key(nameoroid) in self.data diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 6091a752..aa094cb5 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -136,8 +136,8 @@ def ldap_entry(self): entry = {} # Collect the schema elements and store them in # entry's attributes - for se_class in self.sed.keys(): - for se in self.sed[se_class].values(): + for se_class, elements in self.sed.items(): + for se in elements.values(): se_str = str(se) try: entry[SCHEMA_ATTR_MAPPING[se_class]].append(se_str) @@ -153,8 +153,7 @@ def listall(self,schema_element_class,schema_element_filters=None): avail_se = self.sed[schema_element_class] if schema_element_filters: result = [] - for se_key in avail_se.keys(): - se = avail_se[se_key] + for se_key, se in avail_se.items(): for fk,fv in schema_element_filters: try: if getattr(se,fk) in fv: diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index b3e11712..5159d895 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -313,9 +313,9 @@ def applyDefaults(self,defaults): Dictionary containing a mapping from class attributes to default values """ - for k in defaults.keys(): + for k, value in defaults.items(): if getattr(self,k) is None: - setattr(self,k,defaults[k]) + setattr(self, k, value) def initializeUrl(self): """ diff --git a/Lib/ldif.py b/Lib/ldif.py index cdcccc0a..3fbb2b1c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -150,8 +150,8 @@ def _unparseEntryRecord(self,entry): entry dictionary holding an entry """ - for attr_type in sorted(entry.keys()): - for attr_value in entry[attr_type]: + for attr_type, values in sorted(entry.items()): + for attr_value in values: self._unparseAttrTypeandValue(attr_type,attr_value) def _unparseChangeRecord(self,modlist): From 6ae0e0afc41a14ecbd79c34dabf5beb5e57822a0 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 14 Mar 2018 16:58:14 -0700 Subject: [PATCH 024/206] Trim white space throughout the project Many editors clean up trailing white space on save. By removing it all in one go, it helps keep future diffs cleaner by avoiding spurious white space changes on unrelated lines. --- .gitignore | 1 - Build/build-openbsd/Makefile | 2 +- Build/build-openbsd/pkg/DESCR | 2 +- Build/setup.cfg.mingw | 4 ++-- Build/setup.cfg.suse-linux | 2 +- Demo/Lib/ldap/async/deltree.py | 2 +- Demo/Lib/ldif/ldifcopy.py | 1 - Demo/matchedvalues.py | 1 - Demo/options.py | 4 ---- Demo/pyasn1/sessiontrack.py | 1 - Demo/pyasn1/syncrepl.py | 4 ++-- Demo/schema.py | 2 +- Demo/schema_tree.py | 1 - Demo/simple.py | 5 ++--- Demo/simplebrowse.py | 5 ++--- Doc/installing.rst | 1 - Doc/reference/ldap-async.rst | 1 - Doc/reference/ldap-dn.rst | 1 - Doc/reference/ldap-extop.rst | 1 - Doc/reference/ldap-filter.rst | 1 - Doc/reference/ldap-sasl.rst | 3 +-- Doc/reference/ldap-syncrepl.rst | 1 - Doc/reference/ldap.rst | 5 ++--- Doc/reference/ldapurl.rst | 1 - Doc/reference/ldif.rst | 1 - Doc/sample_workflow.rst | 1 - Lib/ldap/constants.py | 2 +- Lib/ldap/controls/deref.py | 2 +- Lib/ldapurl.py | 1 - Modules/LDAPObject.c | 20 ++++++++++---------- Tests/t_ldap_asyncsearch.py | 1 - Tests/t_ldap_sasl.py | 1 - Tests/t_ldif.py | 4 ++-- setup.cfg | 4 ++-- setup.py | 2 +- tox.ini | 2 +- 36 files changed, 34 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 962248fe..0034b3a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Auto-generated .*.swp *.pyc diff --git a/Build/build-openbsd/Makefile b/Build/build-openbsd/Makefile index ce91fafe..40ad81cb 100644 --- a/Build/build-openbsd/Makefile +++ b/Build/build-openbsd/Makefile @@ -10,7 +10,7 @@ HOMEPAGE= https://www.python-ldap.org/ FAKE= Yes CONFIGURE_STYLE= gnu SEPARATE_BUILD= Yes -EXTRACT_ONLY= +EXTRACT_ONLY= CONFIGURE_ARGS+= --with-ldap=${LOCALBASE} diff --git a/Build/build-openbsd/pkg/DESCR b/Build/build-openbsd/pkg/DESCR index d3cf0ccf..9e58e627 100644 --- a/Build/build-openbsd/pkg/DESCR +++ b/Build/build-openbsd/pkg/DESCR @@ -1,2 +1,2 @@ -This Python library provides access to the LDAP (Lightweight Directory Access +This Python library provides access to the LDAP (Lightweight Directory Access Protocol) RFC1823 C interface. diff --git a/Build/setup.cfg.mingw b/Build/setup.cfg.mingw index 16ab57fc..ffbd0d8a 100644 --- a/Build/setup.cfg.mingw +++ b/Build/setup.cfg.mingw @@ -15,8 +15,8 @@ defines = WIN32 library_dirs = C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/libraries/libldap_r/.libs C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/libraries/liblber/.libs C:\msys\1.0\home\mcicogni\openldap-mingw-build-4\openssl-0.9.7e include_dirs = C:/msys/1.0/home/mcicogni/openldap-mingw-build-4/openldap-2.2.18/include -extra_compile_args = -extra_objects = +extra_compile_args = +extra_objects = libs = ldap_r lber ssl crypto ws2_32 gdi32 diff --git a/Build/setup.cfg.suse-linux b/Build/setup.cfg.suse-linux index 3ea064d2..0a48ef2d 100644 --- a/Build/setup.cfg.suse-linux +++ b/Build/setup.cfg.suse-linux @@ -8,7 +8,7 @@ library_dirs = /usr/lib/sasl2 include_dirs = /usr/include/sasl -extra_compile_args = +extra_compile_args = extra_objects = # Example for full-featured SuSE build: diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 68d3643e..d3fc6203 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -5,7 +5,7 @@ class DeleteLeafs(ldap.async.AsyncSearchHandler): """ Class for deleting entries which are results of a search. - + DNs of Non-leaf entries are collected in DeleteLeafs.nonLeafEntries. """ _entryResultTypes = ldap.async._entryResultTypes diff --git a/Demo/Lib/ldif/ldifcopy.py b/Demo/Lib/ldif/ldifcopy.py index 62cb3919..3bbe3f30 100644 --- a/Demo/Lib/ldif/ldifcopy.py +++ b/Demo/Lib/ldif/ldifcopy.py @@ -20,4 +20,3 @@ process_url_schemes=['file','ftp','http'] ) ldif_collector.parse() - diff --git a/Demo/matchedvalues.py b/Demo/matchedvalues.py index 59c594ff..7a6e7b6b 100644 --- a/Demo/matchedvalues.py +++ b/Demo/matchedvalues.py @@ -61,4 +61,3 @@ def print_result(search_result): res = ld.search_ext_s(base, scope, filter, attrlist = ['mail'], serverctrls = [mv]) print("Matched values control: %s" % control_filter) print_result(res) - diff --git a/Demo/options.py b/Demo/options.py index 8b4e2159..28a04374 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -24,7 +24,3 @@ #print("time limit:",l.get_option(ldap.OPT_TIMELIMIT)) print("Binding...") l.simple_bind_s("","") - - - - diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 33ddddab..58a34e82 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -58,4 +58,3 @@ ldap_url.attrs or ['*'], serverctrls=[st_ctrl] ) - diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 61d63ad9..7103bc64 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -93,8 +93,8 @@ def syncrepl_present(self,uuids,refreshDeletes=False): # If we have not been given any UUID values, # then we have recieved all the present controls... if uuids is None: - # We only do things if refreshDeletes is false as the syncrepl - # extension will call syncrepl_delete instead when it detects a + # We only do things if refreshDeletes is false as the syncrepl + # extension will call syncrepl_delete instead when it detects a # delete notice if refreshDeletes is False: deletedEntries = [ diff --git a/Demo/schema.py b/Demo/schema.py index 4d350f02..03f90a83 100644 --- a/Demo/schema.py +++ b/Demo/schema.py @@ -47,7 +47,7 @@ attr_type_filter = [ ('no_user_mod',[0]), ('usage',range(2)), - ] + ] ) except KeyError as e: print('***KeyError',str(e)) diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index 79c8a837..648bb86f 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -98,4 +98,3 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): print('\n*** Attribute types tree ***\n') PrintSchemaTree(schema,ldap.schema.AttributeType,at_tree,'_',0) - diff --git a/Demo/simple.py b/Demo/simple.py index 6004c9ef..5fa04250 100644 --- a/Demo/simple.py +++ b/Demo/simple.py @@ -97,11 +97,10 @@ # res = l.search_s( - "ou=CSEE, o=UQ, c=AU", - _ldap.SCOPE_SUBTREE, + "ou=CSEE, o=UQ, c=AU", + _ldap.SCOPE_SUBTREE, "objectclass=*", ) print(res) l.unbind() - diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index d13c3680..9d138003 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -50,7 +50,7 @@ # We're not interested in attributes at this stage, so # we specify [] as the list of attribute names to retreive. # - for name,attrs in l.search_s(dn, ldap.SCOPE_ONELEVEL, + for name,attrs in l.search_s(dn, ldap.SCOPE_ONELEVEL, "objectclass=*", []): #-- shorten resulting dns for output brevity if name.startswith(dn+", "): @@ -100,7 +100,7 @@ print(" %-24s" % name) for k,vals in attrs.items(): for v in vals: - if len(v) > 200: + if len(v) > 200: v = `v[:200]` + \ ("... (%d bytes)" % len(v)) else: @@ -125,4 +125,3 @@ except: print_exc() - diff --git a/Doc/installing.rst b/Doc/installing.rst index 6df062f0..dc4f5b95 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -246,4 +246,3 @@ Debugging symbols are preserved with compile option ``-g``. extra_objects = libs = ldap_r lber sasl2 ssl crypto - diff --git a/Doc/reference/ldap-async.rst b/Doc/reference/ldap-async.rst index 76245f60..d7a18405 100644 --- a/Doc/reference/ldap-async.rst +++ b/Doc/reference/ldap-async.rst @@ -112,4 +112,3 @@ for writing search results as LDIF to stdout. :: s.endResultBreak-s.beginResultsDropped ) ) - diff --git a/Doc/reference/ldap-dn.rst b/Doc/reference/ldap-dn.rst index c22a64c4..3e0bbb5f 100644 --- a/Doc/reference/ldap-dn.rst +++ b/Doc/reference/ldap-dn.rst @@ -107,4 +107,3 @@ Splitting a LDAPv3 DN with a multi-valued RDN into its AVA parts: >>> ldap.dn.str2dn('cn=John Doe+mail=john.doe@example.com,dc=example,dc=com') [[('cn', 'John Doe', 1), ('mail', 'john.doe@example.com', 1)], [('dc', 'example', 1)], [('dc', 'com', 1)]] - diff --git a/Doc/reference/ldap-extop.rst b/Doc/reference/ldap-extop.rst index 607f3f00..8fe49f42 100644 --- a/Doc/reference/ldap-extop.rst +++ b/Doc/reference/ldap-extop.rst @@ -38,4 +38,3 @@ This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. .. autoclass:: ldap.extop.dds.RefreshResponse :members: - diff --git a/Doc/reference/ldap-filter.rst b/Doc/reference/ldap-filter.rst index 577befbc..b08d5e9b 100644 --- a/Doc/reference/ldap-filter.rst +++ b/Doc/reference/ldap-filter.rst @@ -35,4 +35,3 @@ The :mod:`ldap.filter` module defines the following functions: whole filter string. .. % -> string - diff --git a/Doc/reference/ldap-sasl.rst b/Doc/reference/ldap-sasl.rst index 96f13aa5..9a8c96aa 100644 --- a/Doc/reference/ldap-sasl.rst +++ b/Doc/reference/ldap-sasl.rst @@ -35,7 +35,7 @@ Classes .. autoclass:: ldap.sasl.sasl :members: - + This class is used with :py:meth:`ldap.LDAPObject.sasl_interactive_bind_s()`. @@ -82,4 +82,3 @@ and sends a SASL external bind request. ldap_conn.sasl_non_interactive_bind_s('EXTERNAL') # Find out the SASL Authorization Identity print ldap_conn.whoami_s() - diff --git a/Doc/reference/ldap-syncrepl.rst b/Doc/reference/ldap-syncrepl.rst index d3717dfb..b3b2cf9a 100644 --- a/Doc/reference/ldap-syncrepl.rst +++ b/Doc/reference/ldap-syncrepl.rst @@ -20,4 +20,3 @@ This module defines the following classes: .. autoclass:: ldap.syncrepl.SyncreplConsumer :members: - diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 6203a2d9..a3ee63cc 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -968,7 +968,7 @@ and wait for and return with the server's result, or with .. py:method:: LDAPObject.sasl_interactive_bind_s(who, auth[, serverctrls=None [, clientctrls=None [, sasl_flags=ldap.SASL_QUIET]]]) -> None This call is used to bind to the directory with a SASL bind request. - + *auth* is an :py:class:`ldap.sasl.sasl()` instance. *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. @@ -1079,7 +1079,7 @@ and wait for and return with the server's result, or with .. versionchanged:: 3.0 ``filterstr=None`` is equivalent to ``filterstr='(objectClass=*)'``. - + .. py:method:: LDAPObject.start_tls_s() -> None @@ -1220,4 +1220,3 @@ subtree search. >>> for dn,entry in r: >>> print('Processing',repr(dn)) >>> handle_ldap_entry(entry) - diff --git a/Doc/reference/ldapurl.rst b/Doc/reference/ldapurl.rst index 86d0817b..96b5ed24 100644 --- a/Doc/reference/ldapurl.rst +++ b/Doc/reference/ldapurl.rst @@ -124,4 +124,3 @@ with \module{ldapurl} module. >>> ldap_url = ldapurl.LDAPUrl(hostport='localhost:1389',dn='dc=stroeder,dc=com',attrs=['cn','mail'],who='cn=Michael,dc=stroeder,dc=com',cred='secret') >>> ldap_url.unparse() 'ldap://localhost:1389/dc=stroeder,dc=com?cn,mail?base?(objectclass=*)?bindname=cn=Michael%2Cdc=stroeder%2Cdc=com,X-BINDPW=secret' - diff --git a/Doc/reference/ldif.rst b/Doc/reference/ldif.rst index 64afbb02..c508f7dc 100644 --- a/Doc/reference/ldif.rst +++ b/Doc/reference/ldif.rst @@ -94,4 +94,3 @@ with :mod:`ldif` module, skip some entries and write the result to stdout. :: parser = MyLDIF(open("input.ldif", 'rb'), sys.stdout) parser.parse() - diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index 97e51c67..c2010c1a 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -1,4 +1,3 @@ - .. _sample workflow: Sample workflow for python-ldap development diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 9eb5ceb9..7a7982bb 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -16,7 +16,7 @@ from __future__ import print_function class Constant(object): - """Base class for a definition of an OpenLDAP constant + """Base class for a definition of an OpenLDAP constant """ def __init__(self, name, optional=False, requirements=(), doc=None): diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index 0d43ab1f..b9994eb6 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -ldap.controls.deref - classes for +ldap.controls.deref - classes for (see https://tools.ietf.org/html/draft-masarati-ldap-deref) See https://www.python-ldap.org/ for project details. diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 5159d895..12eafdcf 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -429,4 +429,3 @@ def __delattr__(self,name): pass else: del self.__dict__[name] - diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 2bd6209c..bc26727e 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -54,8 +54,8 @@ dealloc(LDAPObject *self) * utility functions */ -/* - * check to see if the LDAPObject is valid, +/* + * check to see if the LDAPObject is valid, * ie has been opened, and not closed. An exception is set if not valid. */ @@ -89,8 +89,8 @@ LDAPMod_DEL(LDAPMod *lm) PyMem_DEL(lm); } -/* - * convert a tuple of the form (int,str,[str,...]) +/* + * convert a tuple of the form (int,str,[str,...]) * or (str, [str,...]) if no_op is true, into an LDAPMod structure. * See ldap_modify(3) for details. * @@ -208,8 +208,8 @@ LDAPMods_DEL(LDAPMod **lms) PyMem_DEL(lms); } -/* - * convert a list of tuples into a LDAPMod*[] array structure +/* + * convert a list of tuples into a LDAPMod*[] array structure * NOTE: list of tuples must live longer than the LDAPMods */ @@ -589,7 +589,7 @@ l_ldap_simple_bind(LDAPObject *self, PyObject *args) auth modules ("mechanisms"), or try ldapsearch -b "" -s base -LLL -x supportedSASLMechanisms - + (perhaps with an additional -h and -p argument for ldap host and port). The latter will show you which SASL mechanisms are known to the LDAP server. If you do not want to set up Kerberos, you @@ -645,7 +645,7 @@ interaction(unsigned flags, sasl_interact_t *interact, PyObject *SASLObject) return LDAP_SUCCESS; } -/* +/* This function will be called by ldap_sasl_interactive_bind(). The "*in" is an array of sasl_interact_t's (see sasl.h for a reference). The last interact in the array has an interact->id of @@ -748,12 +748,12 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) static unsigned sasl_flags = LDAP_SASL_QUIET; - /* + /* * In Python 2.3+, a "I" format argument indicates that we're either converting * the Python object into a long or an unsigned int. In versions prior to that, * it will always convert to a long. Since the sasl_flags variable is an * unsigned int, we need to use the "I" flag if we're running Python 2.3+ and a - * "i" otherwise. + * "i" otherwise. */ #if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 3) if (!PyArg_ParseTuple diff --git a/Tests/t_ldap_asyncsearch.py b/Tests/t_ldap_asyncsearch.py index 8b469669..1b89050d 100644 --- a/Tests/t_ldap_asyncsearch.py +++ b/Tests/t_ldap_asyncsearch.py @@ -22,4 +22,3 @@ def test_deprecated(self): if __name__ == '__main__': unittest.main() - diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index e60ac12b..9cf675af 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -94,4 +94,3 @@ def test_external_tlscert(self): if __name__ == '__main__': unittest.main() - diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 4f181df1..048b3f40 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -467,7 +467,7 @@ def test_weird_empty_lines(self): """ # comment before version - + version: 1 @@ -586,7 +586,7 @@ def test_weird_empty_lines(self): """ # comment before version - + version: 1 diff --git a/setup.cfg b/setup.cfg index 87cdfd7e..c6546068 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,8 +14,8 @@ # ./configure --with-cyrus-sasl --with-tls defines = HAVE_SASL HAVE_TLS HAVE_LIBLDAP_R -extra_compile_args = -extra_objects = +extra_compile_args = +extra_objects = # Example for full-featured build: # Support for StartTLS/LDAPS, SASL bind and reentrant libldap_r. diff --git a/setup.py b/setup.py index 034c4dd3..1b3dcdee 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ class OpenLDAP2: from Python programs. Mainly it wraps the OpenLDAP 2.x libs for that purpose. Additionally the package contains modules for other LDAP-related stuff (e.g. processing LDIF, LDAPURLs, LDAPv3 schema, LDAPv3 extended operations - and controls, etc.). + and controls, etc.). """, author = 'python-ldap project', author_email = 'python-ldap@python.org', diff --git a/tox.ini b/tox.ini index bb9adac3..992a699e 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ minver = 1.8 [testenv] deps = coverage passenv = WITH_GCOV -# - Enable BytesWarning +# - Enable BytesWarning # - Turn all warnings into exceptions. # - 'ignore:the imp module is deprecated' is required to ignore import of 'imp' # in distutils. Python < 3.6 use PendingDeprecationWarning; Python >= 3.6 use From 97aa653cd8577580b65d6a5faa2ce711c5f57d77 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 14 Mar 2018 17:52:16 -0700 Subject: [PATCH 025/206] Remove assignment of variable to itself Looks like a simple mistake. Introduced in 2ce408cf8b7d5b084556de0d36bf5666c1fda515. --- Lib/slapdtest/_slapdtest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index adcccb80..2856a84e 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -566,7 +566,6 @@ def _open_ldap_conn(self, who=None, cred=None, **kwargs): def setUpClass(cls): cls.server = cls.server_class() cls.server.start() - cls.server = cls.server @classmethod def tearDownClass(cls): From fd0ec9057e6aaab67b2514c7802d4a4ad0712cb8 Mon Sep 17 00:00:00 2001 From: Gabriel Briones Date: Thu, 15 Mar 2018 12:10:49 -0600 Subject: [PATCH 026/206] Disable SASL external when missing SASL support https://github.com/python-ldap/python-ldap/pull/190 Closes: https://github.com/python-ldap/python-ldap/issues/189 --- Lib/slapdtest/_slapdtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 2856a84e..7e230c88 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -213,7 +213,7 @@ def __init__(self): self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) self.default_ldap_uri = self.ldapi_uri # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools - self.cli_sasl_external = True + self.cli_sasl_external = ldap.SASL_AVAIL else: self.ldapi_uri = None self.default_ldap_uri = self.ldap_uri From f477486b9185b92ff774096ed41effd3a90ca6b0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 Mar 2018 10:52:27 +0200 Subject: [PATCH 027/206] Tests: Disable warnings-as-errors for Python 3.4 Python 3.4 is in its security fix only phase. We should make sure the tests pass, but warnings should no longer break CI. --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index 992a699e..f7751d0a 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,12 @@ passenv = {[testenv]passenv} commands = {envpython} \ -m coverage run --parallel setup.py test +[testenv:py34] +# No warnings with Python 3.4 +passenv = {[testenv]passenv} +commands = {envpython} \ + -m coverage run --parallel setup.py test + [testenv:py2-nosasltls] basepython = python2 deps = {[testenv]deps} From 4a6a719f0da66a3764c979ff423ca2c6c320d57a Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 27 Mar 2018 11:24:57 +0200 Subject: [PATCH 028/206] Fix parsing of PPolicyControl ASN.1 structure Password policy control decoder failed to handle graceAuthNsRemaining correctly. The warning.getComponentByName('timeBeforeExpiration') call materialized the time before expiration CHOICE element. The fixed implementation uses pyasn1's dict interface to check which CHOICE element is set. https://github.com/python-ldap/python-ldap/pull/194 Closes: https://github.com/python-ldap/python-ldap/issues/192 See: https://github.com/python-ldap/python-ldap/issues/193 Signed-off-by: Christian Heimes --- Lib/ldap/controls/ppolicy.py | 35 +++++++++++++++----------------- Tests/t_ldap_controls_ppolicy.py | 33 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 Tests/t_ldap_controls_ppolicy.py diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index aa761f34..67efe3ad 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -11,13 +11,13 @@ ] # Imports from python-ldap 2.4+ -import ldap.controls -from ldap.controls import RequestControl,ResponseControl,ValueLessRequestControl,KNOWN_RESPONSE_CONTROLS +from ldap.controls import ( + ResponseControl, ValueLessRequestControl, KNOWN_RESPONSE_CONTROLS +) # Imports from pyasn1 from pyasn1.type import tag,namedtype,namedval,univ,constraint -from pyasn1.codec.ber import encoder,decoder -from pyasn1_modules.rfc2251 import LDAPDN +from pyasn1.codec.der import decoder class PasswordPolicyWarning(univ.Choice): @@ -70,25 +70,22 @@ def __init__(self,criticality=False): def decodeControlValue(self,encodedControlValue): ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) + self.timeBeforeExpiration = None + self.graceAuthNsRemaining = None + self.error = None + warning = ppolicyValue.getComponentByName('warning') - if not warning.hasValue(): - self.timeBeforeExpiration,self.graceAuthNsRemaining = None,None - else: - timeBeforeExpiration = warning.getComponentByName('timeBeforeExpiration') - if timeBeforeExpiration.hasValue(): - self.timeBeforeExpiration = int(timeBeforeExpiration) - else: - self.timeBeforeExpiration = None - graceAuthNsRemaining = warning.getComponentByName('graceAuthNsRemaining') - if graceAuthNsRemaining.hasValue(): - self.graceAuthNsRemaining = int(graceAuthNsRemaining) - else: - self.graceAuthNsRemaining = None + if warning.hasValue(): + if 'timeBeforeExpiration' in warning: + self.timeBeforeExpiration = int( + warning.getComponentByName('timeBeforeExpiration')) + if 'graceAuthNsRemaining' in warning: + self.graceAuthNsRemaining = int( + warning.getComponentByName('graceAuthNsRemaining')) + error = ppolicyValue.getComponentByName('error') if error.hasValue(): self.error = int(error) - else: - self.error = None KNOWN_RESPONSE_CONTROLS[PasswordPolicyControl.controlType] = PasswordPolicyControl diff --git a/Tests/t_ldap_controls_ppolicy.py b/Tests/t_ldap_controls_ppolicy.py new file mode 100644 index 00000000..8644e563 --- /dev/null +++ b/Tests/t_ldap_controls_ppolicy.py @@ -0,0 +1,33 @@ +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +from ldap.controls import ppolicy + + +PP_GRACEAUTH = b'0\x84\x00\x00\x00\t\xa0\x84\x00\x00\x00\x03\x81\x01\x02' +PP_TIMEBEFORE = b'0\x84\x00\x00\x00\t\xa0\x84\x00\x00\x00\x03\x80\x012' + + +class TestControlsPPolicy(unittest.TestCase): + def assertPPolicy(self, pp, timeBeforeExpiration=None, + graceAuthNsRemaining=None, error=None): + self.assertEqual(pp.timeBeforeExpiration, timeBeforeExpiration) + self.assertEqual(pp.graceAuthNsRemaining, graceAuthNsRemaining) + self.assertEqual(pp.error, error) + + def test_ppolicy_graceauth(self): + pp = ppolicy.PasswordPolicyControl() + pp.decodeControlValue(PP_GRACEAUTH) + self.assertPPolicy(pp, graceAuthNsRemaining=2) + + def test_ppolicy_timebefore(self): + pp = ppolicy.PasswordPolicyControl() + pp.decodeControlValue(PP_TIMEBEFORE) + self.assertPPolicy(pp, timeBeforeExpiration=50) + + +if __name__ == '__main__': + unittest.main() From 06be5eb9acd0f27554011819be7a938d250ef125 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 27 Mar 2018 02:28:09 -0700 Subject: [PATCH 029/206] Make SlapdObject.root_dn a property Allows overriding SlapdObject.suffix or SlapdObject.root_cn without also requiring root_dn be defined. It is now computed from the existing values at runtime. The result is an easier to use SlapdObject class. https://github.com/python-ldap/python-ldap/pull/195 --- Lib/slapdtest/_slapdtest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 7e230c88..2bbd1411 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -169,7 +169,6 @@ class SlapdObject(object): database = 'mdb' suffix = 'dc=slapd-test,dc=python-ldap,dc=org' root_cn = 'Manager' - root_dn = 'cn=%s,%s' % (root_cn, suffix) root_pw = 'password' slapd_loglevel = 'stats stats2' local_host = '127.0.0.1' @@ -232,6 +231,10 @@ def __init__(self): self.clientcert = os.path.join(HERE, 'certs/client.pem') self.clientkey = os.path.join(HERE, 'certs/client.key') + @property + def root_dn(self): + return 'cn={self.root_cn},{self.suffix}'.format(self=self) + def _find_commands(self): self.PATH_LDAPADD = self._find_command('ldapadd') self.PATH_LDAPDELETE = self._find_command('ldapdelete') From adc40d00f4fa7a5ba47f6b988778e985b3702fc2 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 27 Mar 2018 02:37:10 -0700 Subject: [PATCH 030/206] In SlapdObject, build include directives dynamically Allows overriding openldap_schema_files without requiring rewriting the conf file. https://github.com/python-ldap/python-ldap/pull/196 --- Lib/slapdtest/_slapdtest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 2bbd1411..f248ff68 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -29,7 +29,7 @@ SLAPD_CONF_TEMPLATE = r""" serverID %(serverid)s moduleload back_%(database)s -include "%(schema_prefix)s/core.schema" +%(include_directives)s loglevel %(loglevel)s allow bind_v2 @@ -316,9 +316,17 @@ def gen_config(self): for generating specific static configuration files you have to override this method """ + include_directives = '\n'.join( + 'include "{schema_prefix}/{schema_file}"'.format( + schema_prefix=self._schema_prefix, + schema_file=schema_file, + ) + for schema_file in self.openldap_schema_files + ) config_dict = { 'serverid': hex(self.server_id), 'schema_prefix':self._schema_prefix, + 'include_directives': include_directives, 'loglevel': self.slapd_loglevel, 'database': self.database, 'directory': self._db_directory, From e5492c7490a5bfdfee8944f04628d2d8e7ce854a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 30 Mar 2018 06:15:03 -0700 Subject: [PATCH 031/206] Stop catching ImportError for pyasn1 pyasn1 and pyasn1_modules are hard dependencies of python-ldap. They have been listed in install_requires since commit 5d158578ce5a01f159378294ae7a88af66d4a05b. As they are not optional, can expect imports to succeed. --- Lib/ldap/controls/__init__.py | 5 +---- Lib/ldap/extop/__init__.py | 9 ++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index 43b938d8..811b3be1 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -17,10 +17,7 @@ import ldap -try: - from pyasn1.error import PyAsn1Error -except ImportError: - PyAsn1Error = None +from pyasn1.error import PyAsn1Error __all__ = [ diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index d18c6829..874166d9 100644 --- a/Lib/ldap/extop/__init__.py +++ b/Lib/ldap/extop/__init__.py @@ -63,10 +63,5 @@ def decodeResponseValue(self,value): return value -# Optionally import sub-modules which need pyasn1 et al -try: - import pyasn1,pyasn1_modules.rfc2251 -except ImportError: - pass -else: - from ldap.extop.dds import * +# Import sub-modules +from ldap.extop.dds import * From 55c96b9953650e0e19a4d99ac5ef6a64f5a5db75 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 30 Mar 2018 05:56:06 -0700 Subject: [PATCH 032/206] Make SlapdObject a context manager Allows for automatic cleanup of resources using the with statement. For example: with slapdtest.SlapdObject() as server: server.ldapadd(...) ... When using SlapdObject in a function, it is more convenient and concise than using try/finally pattern. --- Lib/slapdtest/_slapdtest.py | 11 +++++++++++ Tests/t_slapdobject.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Tests/t_slapdobject.py diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index f248ff68..03ad8d89 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -164,6 +164,10 @@ class SlapdObject(object): When a reference to an instance of this class is lost, the slapd server is shut down. + + An instance can be used as a context manager. When exiting the context + manager, the slapd server is shut down and the temporary data store is + removed. """ slapd_conf_template = SLAPD_CONF_TEMPLATE database = 'mdb' @@ -553,6 +557,13 @@ def ldapdelete(self, dn, recursive=False, extra_args=None): extra_args.append(dn) self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + class SlapdTestCase(unittest.TestCase): """ diff --git a/Tests/t_slapdobject.py b/Tests/t_slapdobject.py new file mode 100644 index 00000000..e1cc971b --- /dev/null +++ b/Tests/t_slapdobject.py @@ -0,0 +1,18 @@ +import unittest + +import slapdtest + + +class TestSlapdObject(unittest.TestCase): + def test_context_manager(self): + with slapdtest.SlapdObject() as server: + self.assertIsNotNone(server._proc) + self.assertIsNone(server._proc) + + def test_context_manager_after_start(self): + server = slapdtest.SlapdObject() + server.start() + self.assertIsNotNone(server._proc) + with server: + self.assertIsNotNone(server._proc) + self.assertIsNone(server._proc) From ca705aaa5e66c6eeec8b2dc152646b7f7e1636a2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 3 Apr 2018 13:23:35 +0200 Subject: [PATCH 033/206] Add versionchanged entry in slapdtest docs about context manager --- Lib/slapdtest/_slapdtest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 03ad8d89..f1885caf 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -168,6 +168,10 @@ class SlapdObject(object): An instance can be used as a context manager. When exiting the context manager, the slapd server is shut down and the temporary data store is removed. + + .. versionchanged:: 3.1 + + Added context manager functionality """ slapd_conf_template = SLAPD_CONF_TEMPLATE database = 'mdb' From e7ee841412d873c62707843a822bc4a8639c6c23 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Tue, 3 Apr 2018 14:40:27 +0300 Subject: [PATCH 034/206] Doc: Replace Mac OS X -> macOS, this is now the official name https://github.com/python-ldap/python-ldap/pull/202 --- Doc/faq.rst | 2 +- Doc/installing.rst | 4 ++-- Doc/spelling_wordlist.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/faq.rst b/Doc/faq.rst index ef479a87..44754147 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -114,7 +114,7 @@ telling Lib/ldap.py and Lib/ldap/schema.py are not found:: .. _install-macosx: -**Q**: What's the correct way to install on Mac OS X? +**Q**: What's the correct way to install on macOS? **A**:: diff --git a/Doc/installing.rst b/Doc/installing.rst index dc4f5b95..f6986630 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -73,8 +73,8 @@ Unofficial packages for Windows are available on The CVS repository of FreeBSD contains the package `py-ldap `_ -Mac OS X --------- +macOS +----- You can install directly with pip:: diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 925ddc30..3ee0e858 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -83,6 +83,7 @@ LDIFWriter libldap libs Libs +macOS modlist modrdn msgid From c10cd5c347b75ebf80d8a31fffb057aaf39db44d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Apr 2018 13:20:10 -0700 Subject: [PATCH 035/206] Add new Pytest cache directory to gitignore Starting with Pytest 3.4.0 (2018-01-30), Pytest's cache directory was renamed to .pytest_cache. https://docs.pytest.org/en/latest/changelog.html#pytest-3-4-0-2018-01-30 > The default cache directory has been renamed from .cache to > .pytest_cache after community feedback that the name .cache did not > make it clear that it was used by pytest. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0034b3a2..5f6d0425 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ .coverage* !.coveragerc /.cache +/.pytest_cache # shared libs installed by 'setup.py test' /Lib/*.so* From 28f5251fd534fd50d333b522c857a2b0aaa56787 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 30 Mar 2018 06:07:27 -0700 Subject: [PATCH 036/206] Move import statements to top level For import statements that can safely exist at the top level of the file, move them there. In Lib/slapdtest/_slapdtest.py, Adding imports local to functions unnecessarily resulted in duplicates. This style is also more in line with PEP8: https://www.python.org/dev/peps/pep-0008/#imports > Imports are always put at the top of the file, just after any module > comments and docstrings, and before module globals and constants. --- Lib/ldap/asyncsearch.py | 3 ++- Lib/ldap/schema/subentry.py | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib/ldap/asyncsearch.py b/Lib/ldap/asyncsearch.py index 80b5b2a9..6514dd05 100644 --- a/Lib/ldap/asyncsearch.py +++ b/Lib/ldap/asyncsearch.py @@ -8,6 +8,8 @@ from ldap import __version__ +import ldif + SEARCH_RESULT_TYPES = { ldap.RES_SEARCH_ENTRY, ldap.RES_SEARCH_RESULT, @@ -269,7 +271,6 @@ class LDIFWriter(FileWriter): """ def __init__(self,l,writer_obj,headerStr='',footerStr=''): - import ldif if isinstance(writer_obj,ldif.LDIFWriter): self._ldif_writer = writer_obj else: diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index aa094cb5..5ccbce05 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -4,10 +4,17 @@ See https://www.python-ldap.org/ for details. """ +import copy + import ldap.cidict,ldap.schema +from ldap.compat import urlopen from ldap.schema.models import * +import ldapurl +import ldif + + SCHEMA_CLASS_MAPPING = ldap.cidict.cidict() SCHEMA_ATTR_MAPPING = {} @@ -256,7 +263,6 @@ def get_inheritedobj(self,se_class,nameoroid,inherited=None): Get a schema element by name or OID with all class attributes set including inherited class attributes """ - import copy inherited = inherited or [] se = copy.copy(self.sed[se_class].get(self.getoid(se_class,nameoroid))) if se and hasattr(se,'sup'): @@ -452,7 +458,6 @@ def urlfetch(uri,trace_level=0): """ uri = uri.strip() if uri.startswith(('ldap:', 'ldaps:', 'ldapi:')): - import ldapurl ldap_url = ldapurl.LDAPUrl(uri) l=ldap.initialize(ldap_url.initializeUrl(),trace_level) @@ -472,8 +477,6 @@ def urlfetch(uri,trace_level=0): l.unbind_s() del l else: - import ldif - from ldap.compat import urlopen ldif_file = urlopen(uri) ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) ldif_parser.parse() From f23f7901f5343581fcfbc3a5db8a745d8adb4e3f Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 19 Apr 2018 06:32:39 -0700 Subject: [PATCH 037/206] Update all pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- Demo/pyasn1/README | 4 ++-- Doc/contributing.rst | 2 +- Doc/faq.rst | 2 +- Doc/installing.rst | 6 +++--- setup.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Demo/pyasn1/README b/Demo/pyasn1/README index b5fc28f7..2ca95a6a 100644 --- a/Demo/pyasn1/README +++ b/Demo/pyasn1/README @@ -2,5 +2,5 @@ The sample modules/scripts herein require modules pyasn1 and pyasn1-modules. https://github.com/etingof/pyasn1 -https://pypi.python.org/pypi/pyasn1 -https://pypi.python.org/pypi/pyasn1-modules +https://pypi.org/project/pyasn1/ +https://pypi.org/project/pyasn1-modules/ diff --git a/Doc/contributing.rst b/Doc/contributing.rst index badb9315..e6481fec 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -143,7 +143,7 @@ keeps track of all reference increments and decrements. The leak plugin runs each test multiple times and checks if the reference count increases. .. _pytest: https://docs.pytest.org/en/latest/ -.. _pytest-leaks: https://pypi.python.org/pypi/pytest-leaks +.. _pytest-leaks: https://pypi.org/project/pytest-leaks/ Download and compile the *pydebug* build:: diff --git a/Doc/faq.rst b/Doc/faq.rst index 44754147..c2e7e153 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -26,7 +26,7 @@ Usage **A1**. For earlier versions, there's `pyldap`_, an independent fork now merged into python-ldap. -.. _pyldap: https://pypi.python.org/pypi/pyldap +.. _pyldap: https://pypi.org/project/pyldap/ **Q**: Does it work with Python 2.6? (1.5|2.0|2.1|2.2|2.3|2.4|2.5)? diff --git a/Doc/installing.rst b/Doc/installing.rst index f6986630..babbaf63 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -15,7 +15,7 @@ For example:: $ python -m pip install python-ldap -.. _PyPI repository: https://pypi.python.org/pypi/python-ldap/ +.. _PyPI repository: https://pypi.org/project/python-ldap/ .. _pip: https://pip.pypa.io/en/stable/ For installing from PyPI, you will need the same :ref:`build prerequisites` @@ -27,8 +27,8 @@ We do not currently provide pre-built packages (wheels). Furthermore, python-ldap requires the modules `pyasn1`_ and `pyasn1-modules`_. ``pip`` will install these automatically. -.. _pyasn1: https://pypi.python.org/pypi/pyasn1 -.. _pyasn1-modules: https://pypi.python.org/pypi/pyasn1-modules +.. _pyasn1: https://pypi.org/project/pyasn1/ +.. _pyasn1-modules: https://pypi.org/project/pyasn1-modules/ Pre-built Binaries diff --git a/setup.py b/setup.py index 1b3dcdee..e1914aaf 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ class OpenLDAP2: author = 'python-ldap project', author_email = 'python-ldap@python.org', url = 'https://www.python-ldap.org/', - download_url = 'https://pypi.python.org/pypi/python-ldap/', + download_url = 'https://pypi.org/project/python-ldap/', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', From 5faff53839906ac65bdc4cb87e0a009395a3ac0e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 28 Apr 2018 09:47:57 -0700 Subject: [PATCH 038/206] Fix assertTrue(result, _ldap.RES_BIND) to assertEqual() Fix a pattern where assertTrue(result, _ldap.RES_BIND) was mistakenly used in place of self.assertEqual(result, _ldap.RES_BIND). Mistakes introduced in commit 162cedf71ba62b3944d18a27ed206d55e679f966. --- Tests/t_cext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index ff5fb6c2..49ef2696 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -234,7 +234,7 @@ def test_simple_anonymous_bind(self): m = l.simple_bind("", "") self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) - self.assertTrue(result, _ldap.RES_BIND) + self.assertEqual(result, _ldap.RES_BIND) self.assertEqual(msgid, m) self.assertEqual(pmsg, []) self.assertEqual(ctrls, []) @@ -629,7 +629,7 @@ def test_whoami_anonymous(self): # Anonymous bind m = l.simple_bind("", "") result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) - self.assertTrue(result, _ldap.RES_BIND) + self.assertEqual(result, _ldap.RES_BIND) # check with Who Am I? extended operation r = l.whoami_s() self.assertEqual("", r) From 68a6db69f76b8f7be6e26aa0c85f7b8a297c22bc Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 28 Apr 2018 09:06:29 -0700 Subject: [PATCH 039/206] Use builtin unittest methods assertIsNone & assertIsNotNone These methods were introduced in Python 2.7 and 3.1. Available in all supported environments. Can remove the local reimplementation. https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsNone https://docs.python.org/2/library/unittest.html#unittest.TestCase.assertIsNone --- Tests/t_cext.py | 132 +++++++++++++++++++--------------------- Tests/t_cidict.py | 2 +- Tests/t_ldap_options.py | 12 ++-- Tests/t_ldapurl.py | 24 +++----- 4 files changed, 80 insertions(+), 90 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 49ef2696..4de2b992 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -99,12 +99,6 @@ def _open_conn(self, bind=True): self.assertEqual(type(msgid), type(0)) return l - def assertNotNone(self, expr, msg=None): - self.assertFalse(expr is None, msg or repr(expr)) - - def assertNone(self, expr, msg=None): - self.assertFalse(expr is not None, msg or repr(expr)) - # Test for the existence of a whole bunch of constants # that the C module is supposed to export def test_constants(self): @@ -128,88 +122,88 @@ def test_constants(self): self.assertEqual(_ldap.RES_SEARCH_REFERENCE, 0x73) # v3 self.assertEqual(_ldap.RES_EXTENDED, 0x78) # v3 #self.assertEqual(_ldap.RES_INTERMEDIATE, 0x79) # v3 - self.assertNotNone(_ldap.RES_ANY) - self.assertNotNone(_ldap.RES_UNSOLICITED) + self.assertIsNotNone(_ldap.RES_ANY) + self.assertIsNotNone(_ldap.RES_UNSOLICITED) - self.assertNotNone(_ldap.AUTH_NONE) - self.assertNotNone(_ldap.AUTH_SIMPLE) + self.assertIsNotNone(_ldap.AUTH_NONE) + self.assertIsNotNone(_ldap.AUTH_SIMPLE) - self.assertNotNone(_ldap.SCOPE_BASE) - self.assertNotNone(_ldap.SCOPE_ONELEVEL) - self.assertNotNone(_ldap.SCOPE_SUBTREE) + self.assertIsNotNone(_ldap.SCOPE_BASE) + self.assertIsNotNone(_ldap.SCOPE_ONELEVEL) + self.assertIsNotNone(_ldap.SCOPE_SUBTREE) - self.assertNotNone(_ldap.MOD_ADD) - self.assertNotNone(_ldap.MOD_DELETE) - self.assertNotNone(_ldap.MOD_REPLACE) - self.assertNotNone(_ldap.MOD_INCREMENT) - self.assertNotNone(_ldap.MOD_BVALUES) + self.assertIsNotNone(_ldap.MOD_ADD) + self.assertIsNotNone(_ldap.MOD_DELETE) + self.assertIsNotNone(_ldap.MOD_REPLACE) + self.assertIsNotNone(_ldap.MOD_INCREMENT) + self.assertIsNotNone(_ldap.MOD_BVALUES) # for result4() - self.assertNotNone(_ldap.MSG_ONE) - self.assertNotNone(_ldap.MSG_ALL) - self.assertNotNone(_ldap.MSG_RECEIVED) + self.assertIsNotNone(_ldap.MSG_ONE) + self.assertIsNotNone(_ldap.MSG_ALL) + self.assertIsNotNone(_ldap.MSG_RECEIVED) # for OPT_DEFEF - self.assertNotNone(_ldap.DEREF_NEVER) - self.assertNotNone(_ldap.DEREF_SEARCHING) - self.assertNotNone(_ldap.DEREF_FINDING) - self.assertNotNone(_ldap.DEREF_ALWAYS) + self.assertIsNotNone(_ldap.DEREF_NEVER) + self.assertIsNotNone(_ldap.DEREF_SEARCHING) + self.assertIsNotNone(_ldap.DEREF_FINDING) + self.assertIsNotNone(_ldap.DEREF_ALWAYS) # for OPT_SIZELIMIT, OPT_TIMELIMIT - self.assertNotNone(_ldap.NO_LIMIT) + self.assertIsNotNone(_ldap.NO_LIMIT) # standard options - self.assertNotNone(_ldap.OPT_API_INFO) - self.assertNotNone(_ldap.OPT_DEREF) - self.assertNotNone(_ldap.OPT_SIZELIMIT) - self.assertNotNone(_ldap.OPT_TIMELIMIT) - self.assertNotNone(_ldap.OPT_REFERRALS) - self.assertNotNone(_ldap.OPT_RESTART) - self.assertNotNone(_ldap.OPT_PROTOCOL_VERSION) - self.assertNotNone(_ldap.OPT_SERVER_CONTROLS) - self.assertNotNone(_ldap.OPT_CLIENT_CONTROLS) - self.assertNotNone(_ldap.OPT_API_FEATURE_INFO) - self.assertNotNone(_ldap.OPT_HOST_NAME) - self.assertNotNone(_ldap.OPT_ERROR_NUMBER) # = OPT_RESULT_CODE - self.assertNotNone(_ldap.OPT_ERROR_STRING) # = OPT_DIAGNOSITIC_MESSAGE - self.assertNotNone(_ldap.OPT_MATCHED_DN) + self.assertIsNotNone(_ldap.OPT_API_INFO) + self.assertIsNotNone(_ldap.OPT_DEREF) + self.assertIsNotNone(_ldap.OPT_SIZELIMIT) + self.assertIsNotNone(_ldap.OPT_TIMELIMIT) + self.assertIsNotNone(_ldap.OPT_REFERRALS) + self.assertIsNotNone(_ldap.OPT_RESTART) + self.assertIsNotNone(_ldap.OPT_PROTOCOL_VERSION) + self.assertIsNotNone(_ldap.OPT_SERVER_CONTROLS) + self.assertIsNotNone(_ldap.OPT_CLIENT_CONTROLS) + self.assertIsNotNone(_ldap.OPT_API_FEATURE_INFO) + self.assertIsNotNone(_ldap.OPT_HOST_NAME) + self.assertIsNotNone(_ldap.OPT_ERROR_NUMBER) # = OPT_RESULT_CODE + self.assertIsNotNone(_ldap.OPT_ERROR_STRING) # = OPT_DIAGNOSITIC_MESSAGE + self.assertIsNotNone(_ldap.OPT_MATCHED_DN) # OpenLDAP specific - self.assertNotNone(_ldap.OPT_DEBUG_LEVEL) - self.assertNotNone(_ldap.OPT_TIMEOUT) - self.assertNotNone(_ldap.OPT_REFHOPLIMIT) - self.assertNotNone(_ldap.OPT_NETWORK_TIMEOUT) - self.assertNotNone(_ldap.OPT_URI) - #self.assertNotNone(_ldap.OPT_REFERRAL_URLS) - #self.assertNotNone(_ldap.OPT_SOCKBUF) - #self.assertNotNone(_ldap.OPT_DEFBASE) - #self.assertNotNone(_ldap.OPT_CONNECT_ASYNC) + self.assertIsNotNone(_ldap.OPT_DEBUG_LEVEL) + self.assertIsNotNone(_ldap.OPT_TIMEOUT) + self.assertIsNotNone(_ldap.OPT_REFHOPLIMIT) + self.assertIsNotNone(_ldap.OPT_NETWORK_TIMEOUT) + self.assertIsNotNone(_ldap.OPT_URI) + #self.assertIsNotNone(_ldap.OPT_REFERRAL_URLS) + #self.assertIsNotNone(_ldap.OPT_SOCKBUF) + #self.assertIsNotNone(_ldap.OPT_DEFBASE) + #self.assertIsNotNone(_ldap.OPT_CONNECT_ASYNC) # str2dn() - self.assertNotNone(_ldap.DN_FORMAT_LDAP) - self.assertNotNone(_ldap.DN_FORMAT_LDAPV3) - self.assertNotNone(_ldap.DN_FORMAT_LDAPV2) - self.assertNotNone(_ldap.DN_FORMAT_DCE) - self.assertNotNone(_ldap.DN_FORMAT_UFN) - self.assertNotNone(_ldap.DN_FORMAT_AD_CANONICAL) - self.assertNotNone(_ldap.DN_FORMAT_MASK) - self.assertNotNone(_ldap.DN_PRETTY) - self.assertNotNone(_ldap.DN_SKIP) - self.assertNotNone(_ldap.DN_P_NOLEADTRAILSPACES) - self.assertNotNone(_ldap.DN_P_NOSPACEAFTERRDN) - self.assertNotNone(_ldap.DN_PEDANTIC) - self.assertNotNone(_ldap.AVA_NULL) - self.assertNotNone(_ldap.AVA_STRING) - self.assertNotNone(_ldap.AVA_BINARY) - self.assertNotNone(_ldap.AVA_NONPRINTABLE) + self.assertIsNotNone(_ldap.DN_FORMAT_LDAP) + self.assertIsNotNone(_ldap.DN_FORMAT_LDAPV3) + self.assertIsNotNone(_ldap.DN_FORMAT_LDAPV2) + self.assertIsNotNone(_ldap.DN_FORMAT_DCE) + self.assertIsNotNone(_ldap.DN_FORMAT_UFN) + self.assertIsNotNone(_ldap.DN_FORMAT_AD_CANONICAL) + self.assertIsNotNone(_ldap.DN_FORMAT_MASK) + self.assertIsNotNone(_ldap.DN_PRETTY) + self.assertIsNotNone(_ldap.DN_SKIP) + self.assertIsNotNone(_ldap.DN_P_NOLEADTRAILSPACES) + self.assertIsNotNone(_ldap.DN_P_NOSPACEAFTERRDN) + self.assertIsNotNone(_ldap.DN_PEDANTIC) + self.assertIsNotNone(_ldap.AVA_NULL) + self.assertIsNotNone(_ldap.AVA_STRING) + self.assertIsNotNone(_ldap.AVA_BINARY) + self.assertIsNotNone(_ldap.AVA_NONPRINTABLE) # these two constants are pointless? XXX self.assertEqual(_ldap.OPT_ON, 1) self.assertEqual(_ldap.OPT_OFF, 0) # these constants useless after ldap_url_parse() was dropped XXX - self.assertNotNone(_ldap.URL_ERR_BADSCOPE) - self.assertNotNone(_ldap.URL_ERR_MEM) + self.assertIsNotNone(_ldap.URL_ERR_BADSCOPE) + self.assertIsNotNone(_ldap.URL_ERR_MEM) def test_test_flags(self): # test flag, see slapdtest and tox.ini @@ -263,7 +257,7 @@ def test_anon_rootdse_search(self): def test_unbind(self): l = self._open_conn() m = l.unbind_ext() - self.assertNone(m) + self.assertIsNone(m) # Second attempt to unbind should yield an exception try: l.unbind_ext() @@ -301,7 +295,7 @@ def test_abandon(self): l = self._open_conn() m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(objectClass=*)') ret = l.abandon_ext(m) - self.assertNone(ret) + self.assertIsNone(ret) try: r = l.result4(m, _ldap.MSG_ALL, 0.3) # (timeout /could/ be longer) except _ldap.TIMEOUT as e: diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index f8b993f0..fa5a39b8 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -30,7 +30,7 @@ def test_cidict(self): cix = ldap.cidict.cidict(data) self.assertEqual(cix["ABCDEF"], 123) self.assertEqual(cix.get("ABCDEF", None), 123) - self.assertEqual(cix.get("not existent", None), None) + self.assertIsNone(cix.get("not existent", None)) cix["xYZ"] = 987 self.assertEqual(cix["XyZ"], 987) self.assertEqual(cix.get("xyz", None), 987) diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index ffc2451f..684fdf7b 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -71,9 +71,9 @@ def _test_timeout(self, option): old = self.get_option(option) try: self.set_option(option, None) - self.assertEqual(self.get_option(option), None) + self.assertIsNone(self.get_option(option)) self.set_option(option, -1) - self.assertEqual(self.get_option(option), None) + self.assertIsNone(self.get_option(option)) finally: self.set_option(option, old) @@ -168,16 +168,16 @@ def test_network_timeout_attribute(self): self.assertEqual(self.get_option(option), 5) self.conn.network_timeout = -1 - self.assertEqual(self.conn.network_timeout, None) - self.assertEqual(self.get_option(option), None) + self.assertIsNone(self.conn.network_timeout) + self.assertIsNone(self.get_option(option)) self.conn.network_timeout = 10.5 self.assertEqual(self.conn.network_timeout, 10.5) self.assertEqual(self.get_option(option), 10.5) self.conn.network_timeout = None - self.assertEqual(self.conn.network_timeout, None) - self.assertEqual(self.get_option(option), None) + self.assertIsNone(self.conn.network_timeout) + self.assertIsNone(self.get_option(option)) finally: self.set_option(option, old) diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index c4a813dd..1408efcf 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -183,10 +183,6 @@ def test_ldapurl(self): class TestLDAPUrl(unittest.TestCase): - - def assertNone(self, expr, msg=None): - self.assertFalse(expr is not None, msg or ("%r" % expr)) - def test_combo(self): u = MyLDAPUrl( "ldap://127.0.0.1:1234/dc=example,dc=com" @@ -223,15 +219,15 @@ def test_parse_empty_dn(self): def test_parse_default_attrs(self): u = LDAPUrl("ldap://") - self.assertNone(u.attrs) + self.assertIsNone(u.attrs) def test_parse_default_scope(self): u = LDAPUrl("ldap://") - self.assertNone(u.scope) # RFC4516 s3 + self.assertIsNone(u.scope) # RFC4516 s3 def test_parse_default_filter(self): u = LDAPUrl("ldap://") - self.assertNone(u.filterstr) # RFC4516 s3 + self.assertIsNone(u.filterstr) # RFC4516 s3 def test_parse_default_extensions(self): u = LDAPUrl("ldap://") @@ -285,9 +281,9 @@ def test_parse_dn(self): def test_parse_attrs(self): u = LDAPUrl("ldap:///?") - self.assertEqual(u.attrs, None) + self.assertIsNone(u.attrs) u = LDAPUrl("ldap:///??") - self.assertEqual(u.attrs, None) + self.assertIsNone(u.attrs) u = LDAPUrl("ldap:///?*?") self.assertEqual(u.attrs, ['*']) u = LDAPUrl("ldap:///?*,*?") @@ -303,9 +299,9 @@ def test_parse_attrs(self): def test_parse_scope_default(self): u = LDAPUrl("ldap:///??") - self.assertNone(u.scope) # on opposite to RFC4516 s3 for referral chasing + self.assertIsNone(u.scope) # on opposite to RFC4516 s3 for referral chasing u = LDAPUrl("ldap:///???") - self.assertNone(u.scope) # on opposite to RFC4516 s3 for referral chasing + self.assertIsNone(u.scope) # on opposite to RFC4516 s3 for referral chasing def test_parse_scope(self): u = LDAPUrl("ldap:///??sub") @@ -355,8 +351,8 @@ def test_parse_filter(self): def test_parse_extensions(self): u = LDAPUrl("ldap:///????") - self.assertNone(u.extensions) - self.assertNone(u.who) + self.assertIsNone(u.extensions) + self.assertIsNone(u.who) u = LDAPUrl("ldap:///????bindname=cn=root") self.assertEqual(len(u.extensions), 1) self.assertEqual(u.who, "cn=root") @@ -380,7 +376,7 @@ def test_parse_extensions_5questions(self): def test_parse_extensions_novalue(self): u = LDAPUrl("ldap:///????bindname") self.assertEqual(len(u.extensions), 1) - self.assertNone(u.who) + self.assertIsNone(u.who) @unittest.expectedFailure def test_bad_urls(self): From 77e934e88766002e77462d541b6e9c9599c7d410 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 24 May 2018 23:36:57 +0200 Subject: [PATCH 040/206] Fix ref counting bug in LDAPmessage_to_python PyDict_GetItem() returns a borrowed reference. The code later Py_DECREF() the reference, which causes a segfault at a later point in time. Fixes: https://github.com/python-ldap/python-ldap/issues/218 Signed-off-by: Christian Heimes --- Modules/message.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/message.c b/Modules/message.c index cf3ea3b4..2c05488a 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -99,7 +99,15 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, /* Find which list to append to */ if (PyDict_Contains(attrdict, pyattr)) { + /* Multiple attribute entries with same name. This code path + * is rarely used and cannot be exhausted with OpenLDAP + * tests. 389-DS sometimes triggeres it, see + * https://github.com/python-ldap/python-ldap/issues/218 + */ valuelist = PyDict_GetItem(attrdict, pyattr); + /* Turn borrowed reference into owned reference */ + if (valuelist != NULL) + Py_INCREF(valuelist); } else { valuelist = PyList_New(0); From 1eb07ff43bb072ffb04dd0b07bb46e38b2642422 Mon Sep 17 00:00:00 2001 From: SOMA Yuki Date: Fri, 27 Apr 2018 11:55:55 +0900 Subject: [PATCH 041/206] Tests: modlist test cases were obsolete in Python 3 --- Tests/t_ldap_modlist.py | 72 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Tests/t_ldap_modlist.py b/Tests/t_ldap_modlist.py index db248b7b..53c80cc3 100644 --- a/Tests/t_ldap_modlist.py +++ b/Tests/t_ldap_modlist.py @@ -20,19 +20,19 @@ class TestModlist(unittest.TestCase): addModlist_tests = [ ( { - 'objectClass':['person','pilotPerson'], - 'cn':['Michael Str\303\266der','Michael Stroeder'], - 'sn':['Str\303\266der'], + 'objectClass': [b'person',b'pilotPerson'], + 'cn':[b'Michael Str\303\266der',b'Michael Stroeder'], + 'sn':[b'Str\303\266der'], 'dummy1':[], - 'dummy2':['2'], - 'dummy3':[''], + 'dummy2':[b'2'], + 'dummy3':[b''], }, [ - ('objectClass',['person','pilotPerson']), - ('cn',['Michael Str\303\266der','Michael Stroeder']), - ('sn',['Str\303\266der']), - ('dummy2',['2']), - ('dummy3',['']), + ('objectClass',[b'person',b'pilotPerson']), + ('cn',[b'Michael Str\303\266der',b'Michael Stroeder']), + ('sn',[b'Str\303\266der']), + ('dummy2',[b'2']), + ('dummy3',[b'']), ] ), ] @@ -52,42 +52,42 @@ def test_addModlist(self): modifyModlist_tests = [ ( { - 'objectClass':['person','pilotPerson'], - 'cn':['Michael Str\303\266der','Michael Stroeder'], - 'sn':['Str\303\266der'], - 'enum':['a','b','c'], - 'c':['DE'], + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Michael Str\303\266der',b'Michael Stroeder'], + 'sn':[b'Str\303\266der'], + 'enum':[b'a',b'b',b'c'], + 'c':[b'DE'], }, { - 'objectClass':['person','inetOrgPerson'], - 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'objectClass':[b'person',b'inetOrgPerson'], + 'cn':[b'Michael Str\303\266der',b'Michael Stroeder'], 'sn':[], - 'enum':['a','b','d'], - 'mail':['michael@stroeder.com'], + 'enum':[b'a',b'b',b'd'], + 'mail':[b'michael@stroeder.com'], }, [], [ (ldap.MOD_DELETE,'objectClass',None), - (ldap.MOD_ADD,'objectClass',['person','inetOrgPerson']), + (ldap.MOD_ADD,'objectClass',[b'person',b'inetOrgPerson']), (ldap.MOD_DELETE,'c',None), (ldap.MOD_DELETE,'sn',None), - (ldap.MOD_ADD,'mail',['michael@stroeder.com']), + (ldap.MOD_ADD,'mail',[b'michael@stroeder.com']), (ldap.MOD_DELETE,'enum',None), - (ldap.MOD_ADD,'enum',['a','b','d']), + (ldap.MOD_ADD,'enum',[b'a',b'b',b'd']), ] ), ( { - 'c':['DE'], + 'c':[b'DE'], }, { - 'c':['FR'], + 'c':[b'FR'], }, [], [ (ldap.MOD_DELETE,'c',None), - (ldap.MOD_ADD,'c',['FR']), + (ldap.MOD_ADD,'c',[b'FR']), ] ), @@ -95,10 +95,10 @@ def test_addModlist(self): # of removing an attribute with MOD_DELETE,attr_type,None ( { - 'objectClass':['person'], + 'objectClass':[b'person'], 'cn':[None], - 'sn':[''], - 'c':['DE'], + 'sn':[b''], + 'c':[b'DE'], }, { 'objectClass':[], @@ -115,22 +115,22 @@ def test_addModlist(self): ( { - 'objectClass':['person'], - 'cn':['Michael Str\303\266der','Michael Stroeder'], - 'sn':['Str\303\266der'], - 'enum':['a','b','C'], + 'objectClass':[b'person'], + 'cn':[b'Michael Str\303\266der',b'Michael Stroeder'], + 'sn':[b'Str\303\266der'], + 'enum':[b'a',b'b',b'C'], }, { - 'objectClass':['Person'], - 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'objectClass':[b'Person'], + 'cn':[b'Michael Str\303\266der',b'Michael Stroeder'], 'sn':[], - 'enum':['a','b','c'], + 'enum':[b'a',b'b',b'c'], }, ['objectClass'], [ (ldap.MOD_DELETE,'sn',None), (ldap.MOD_DELETE,'enum',None), - (ldap.MOD_ADD,'enum',['a','b','c']), + (ldap.MOD_ADD,'enum',[b'a',b'b',b'c']), ] ), From f3ff4a320a21a3ac42bc7587eaed09c4f9b2f9f5 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 28 Apr 2018 14:27:54 -0700 Subject: [PATCH 042/206] Return a bool in .compare_s() and .compare_ext_s() Previously, LDAPObject.compare_s() and LDAPObject.compare_ext_s() returned 1 for true and 0 for false. As the return value is intended for a bool context, return a bool instead. --- CHANGES | 7 +++++++ Doc/reference/ldap.rst | 13 ++++++------- Lib/ldap/ldapobject.py | 18 +++++++++--------- Tests/t_ldapobject.py | 12 ++++++++++++ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 7a5e102d..8f17c2a4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +---------------------------------------------------------------- +UNRELEASED + +Lib/ +* LDAPObject.compare_s() and LDAPObject.compare_ext_s() now return a bool + instead of 1 or 0. + ---------------------------------------------------------------- Released 3.0.0 2018-03-12 diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index a3ee63cc..601824b8 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -704,17 +704,16 @@ and wait for and return with the server's result, or with .. py:method:: LDAPObject.compare(dn, attr, value) -> int -.. py:method:: LDAPObject.compare_s(dn, attr, value) -> tuple +.. py:method:: LDAPObject.compare_s(dn, attr, value) -> bool .. py:method:: LDAPObject.compare_ext(dn, attr, value [, serverctrls=None [, clientctrls=None]]) -> int -.. py:method:: LDAPObject.compare_ext_s(dn, attr, value [, serverctrls=None [, clientctrls=None]]) -> tuple +.. py:method:: LDAPObject.compare_ext_s(dn, attr, value [, serverctrls=None [, clientctrls=None]]) -> bool - Perform an LDAP comparison between the attribute named *attr* of - entry *dn*, and the value *value*. The synchronous forms - returns :py:const:`0` for false, or :py:const:`1` for true. - The asynchronous forms returns the message ID of the initiated request, - and the result of the asynchronous compare can be obtained using + Perform an LDAP comparison between the attribute named *attr* of entry *dn*, + and the value *value*. The synchronous forms returns ``True`` or ``False``. + The asynchronous forms returns the message ID of the initiated request, and + the result of the asynchronous compare can be obtained using :py:meth:`result()`. Note that the asynchronous technique yields the answer diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 36bf034f..8fa71c3e 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -495,14 +495,14 @@ def sasl_bind_s(self,dn,mechanism,cred,serverctrls=None,clientctrls=None): def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None): """ compare_ext(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int - compare_ext_s(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int + compare_ext_s(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> bool compare(dn, attr, value) -> int - compare_s(dn, attr, value) -> int - Perform an LDAP comparison between the attribute named attr of - entry dn, and the value value. The synchronous form returns 0 - for false, or 1 for true. The asynchronous form returns the - message id of the initiates request, and the result of the - asynchronous compare can be obtained using result(). + compare_s(dn, attr, value) -> bool + Perform an LDAP comparison between the attribute named attr of entry + dn, and the value value. The synchronous form returns True or False. + The asynchronous form returns the message id of the initiates request, + and the result of the asynchronous compare can be obtained using + result(). Note that this latter technique yields the answer by raising the exception objects COMPARE_TRUE or COMPARE_FALSE. @@ -520,9 +520,9 @@ def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): try: ldap_res = self.result3(msgid,all=1,timeout=self.timeout) except ldap.COMPARE_TRUE: - return 1 + return True except ldap.COMPARE_FALSE: - return 0 + return False raise ldap.PROTOCOL_ERROR( 'Compare operation returned wrong result: %r' % (ldap_res) ) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 33c2c5a9..76868269 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -646,6 +646,18 @@ def test_dse_bytes(self): [self.server.suffix.encode('utf-8')] ) + def test_compare_s_true(self): + base = self.server.suffix + l = self._ldap_conn + result = l.compare_s('cn=Foo1,%s' % base, 'cn', 'Foo1') + self.assertIs(result, True) + + def test_compare_s_false(self): + base = self.server.suffix + l = self._ldap_conn + result = l.compare_s('cn=Foo1,%s' % base, 'cn', 'Foo2') + self.assertIs(result, False) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From 8d6da14a94066e3b1e2f8d4940ba7cbb34c6fde6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 25 May 2018 13:50:38 +0200 Subject: [PATCH 043/206] Update CHANGES for 3.1.0 --- CHANGES | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8f17c2a4..2c04f801 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,59 @@ ---------------------------------------------------------------- -UNRELEASED +Released 3.1.0 2018-05-25 + +This release brings two minor API changes: +- Long-deprecated functions `ldap.open()` and `ldap.init()` are removed +- `LDAPObject.compare_s()` and `compare_ext_s` return bool instead of 0 or 1 + +All changes since 3.0.0: Lib/ +* Remove long deprecated functions ldap.open() and ldap.init() * LDAPObject.compare_s() and LDAPObject.compare_ext_s() now return a bool instead of 1 or 0. +* Make iteration over cidict yield same values as keys() +* Fail if pyasn1 is not installed +* Fix parsing of PPolicyControl ASN.1 structure +* Use items() when appropriate in dict iteration +* Add support for tracing LDAP calls. Tracing can now be enabled with + the env var PYTHON_LDAP_TRACE_LEVEL and redirected to a file with + PYTHON_LDAP_TRACE_FILE. + (This is mainly intended for debugging and internal testing; the + configuration or output may change in future versions.) + +Modules/ +* Fix ref counting bug in LDAPmessage_to_python + +Doc/ +* Remove warning about unreleased version +* Doc: Replace Mac OS X -> macOS + +Tests/ +* Add tests and coverage for tracing +* Disable warnings-as-errors for Python 3.4 +* Fix assertTrue to assertEqual +* Mark several test values as bytes + +Lib/slapdtest/ +* Fix error message for missing commands +* Make SlapdObject a context manager +* Disable SASL external when missing SASL support +* Make SlapdObject.root_dn a property +* In SlapdObject, build include directives dynamically +* Move import statements to top level + +Code style: +* Add Makefile rules for automatic formatting of C and Python code +* Reformat and indent all C files +* Trim white space throughout the project + +Infrastructure: +* Add py3-trace tox environment to Travis CI config +* Add new Pytest cache directory to gitignore + +General: +* Update all pypi.python.org URLs to pypi.org + ---------------------------------------------------------------- Released 3.0.0 2018-03-12 From a06254e6b75fb0430fc0eaa475c30bc370d18030 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 25 May 2018 13:54:20 +0200 Subject: [PATCH 044/206] Bump version to 3.1.0 --- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 47199e92..d004c5d8 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.0.0' +__version__ = '3.1.0' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 12eafdcf..7a25ecab 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0' +__version__ = '3.1.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 3fbb2b1c..3f13ec68 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.0.0' +__version__ = '3.1.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 82970243..56ba2c91 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -5,7 +5,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0' +__version__ = '3.1.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From f228e7c4c164e4c0aa86fa3e23a4a21e72521b0d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 31 May 2018 20:22:21 -0700 Subject: [PATCH 045/206] Correct attr value's type to bytes in tests Refs #212 --- Tests/t_ldapobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 76868269..4ab63f53 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -649,13 +649,13 @@ def test_dse_bytes(self): def test_compare_s_true(self): base = self.server.suffix l = self._ldap_conn - result = l.compare_s('cn=Foo1,%s' % base, 'cn', 'Foo1') + result = l.compare_s('cn=Foo1,%s' % base, 'cn', b'Foo1') self.assertIs(result, True) def test_compare_s_false(self): base = self.server.suffix l = self._ldap_conn - result = l.compare_s('cn=Foo1,%s' % base, 'cn', 'Foo2') + result = l.compare_s('cn=Foo1,%s' % base, 'cn', b'Foo2') self.assertIs(result, False) From e8cc39db0990bfb56e95c6ae1bd2f8be10e88683 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 4 Jul 2018 19:15:54 -0700 Subject: [PATCH 046/206] Docs: Fix internal set|get_option func|meth references Previous syntax did not create links and appeared as raw rst syntax. https://www.python-ldap.org/en/python-ldap-3.0.0/reference/ldap.html#options --- Doc/reference/ldap.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 601824b8..0ce2e418 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -121,9 +121,9 @@ Options :manpage:`ldap.conf(5)` and :manpage:`ldap_get_option(3)` -For use with functions :py:func:set_option() and :py:func:get_option() -and methods :py:method:LDAPObject.set_option() and :py:method:LDAPObject.get_option() the -following option identifiers are defined as constants: +For use with functions :py:func:`set_option` and :py:func:`get_option` and +methods :py:meth:`LDAPObject.set_option` and :py:meth:`LDAPObject.get_option` +the following option identifiers are defined as constants: .. py:data:: OPT_API_FEATURE_INFO From 97379aa8a8f8b5eaab472b03b3a94de5c3a91cfe Mon Sep 17 00:00:00 2001 From: "Ivan A. Melnikov" Date: Mon, 9 Jul 2018 16:15:07 +0400 Subject: [PATCH 047/206] tests: Use system-specific ENOTCONN value The integer values for `errno` are not standard and can vary from platform to platform, so the tests should rely on the constants provided in `errno` module. Closes: #228 --- Tests/t_cext.py | 10 ++++++---- Tests/t_ldapobject.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 4de2b992..96c3b2c8 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals +import errno import os import unittest @@ -731,15 +732,16 @@ def test_cancel(self): if not self._require_attr(l, 'cancel'): # FEATURE_CANCEL return - def test_errno107(self): + def test_enotconn(self): l = _ldap.initialize('ldap://127.0.0.1:42') try: m = l.simple_bind("", "") r = l.result4(m, _ldap.MSG_ALL, self.timeout) except _ldap.SERVER_DOWN as ldap_err: - errno = ldap_err.args[0]['errno'] - if errno != 107: - self.fail("expected errno=107, got %d" % errno) + errno_val = ldap_err.args[0]['errno'] + if errno_val != errno.ENOTCONN: + self.fail("expected errno=%d, got %d" + % (errno.ENOTCONN, errno_val)) else: self.fail("expected SERVER_DOWN, got %r" % r) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 4ab63f53..0619d514 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -16,6 +16,7 @@ PY2 = False text_type = str +import errno import contextlib import linecache import os @@ -451,18 +452,20 @@ def test_search_subschema_have_bytes(self): ] ) - def test004_errno107(self): + def test004_enotconn(self): l = self.ldap_object_class('ldap://127.0.0.1:42') try: m = l.simple_bind_s("", "") r = l.result4(m, ldap.MSG_ALL, self.timeout) except ldap.SERVER_DOWN as ldap_err: - errno = ldap_err.args[0]['errno'] - if errno != 107: - self.fail("expected errno=107, got %d" % errno) + errno_val = ldap_err.args[0]['errno'] + if errno_val != errno.ENOTCONN: + self.fail("expected errno=%d, got %d" + % (errno.ENOTCONN, errno_val)) info = ldap_err.args[0]['info'] - if info != os.strerror(107): - self.fail("expected info=%r, got %d" % (os.strerror(107), info)) + expected_info = os.strerror(errno.ENOTCONN) + if info != expected_info: + self.fail("expected info=%r, got %d" % (expected_info, info)) else: self.fail("expected SERVER_DOWN, got %r" % r) From 363e417e37daeb97439bcc34f0a9a50c336ce5f6 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 13 Jul 2018 21:09:29 -0700 Subject: [PATCH 048/206] Add testing and document support for Python 3.7 Python 3.7 was released on June 27, 2018. --- .travis.yml | 7 +++---- setup.py | 1 + tox.ini | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c7e9892..9ce0cdfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,10 +33,12 @@ matrix: - python: pypy env: - TOXENV=pypy - - python: 3.7-dev + - python: 3.7 env: - TOXENV=py37 - WITH_GCOV=1 + dist: xenial + sudo: true - python: 2.7 env: - TOXENV=py2-nosasltls @@ -51,9 +53,6 @@ matrix: - python: 3.6 env: TOXENV=doc allow_failures: - - env: - - TOXENV=py37 - - WITH_GCOV=1 - env: - TOXENV=pypy diff --git a/setup.py b/setup.py index e1914aaf..e66ecbd6 100644 --- a/setup.py +++ b/setup.py @@ -94,6 +94,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', # Note: when updating Python versions, also change .travis.yml and tox.ini 'Topic :: Database', diff --git a/tox.ini b/tox.ini index f7751d0a..7dee491b 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,py3-trace,coverage-report +envlist = py27,py34,py35,py36,py37,{py2,py3}-nosasltls,doc,py3-trace,coverage-report minver = 1.8 [testenv] @@ -19,6 +19,7 @@ passenv = WITH_GCOV commands = {envpython} -bb -Werror \ "-Wignore:the imp module is deprecated:DeprecationWarning" \ "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ + "-Wignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning" \ -m coverage run --parallel setup.py \ clean --all \ test From be8e3c6ca28ca2468aee5828073583fbe92b0c14 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 30 Jul 2018 19:01:48 +0200 Subject: [PATCH 049/206] Lib/ldap: Provide _trace atributes in non-debug mode All use of these attributes *should* be guarded by `if __debug__`. However, that's not always the case. Providing different API based on __debug__ is unnecessarily fragile. This is intended as a quick fix for a maintenance release. Fixes: https://github.com/python-ldap/python-ldap/issues/226 --- Lib/ldap/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 068f9e67..951f9575 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -23,6 +23,13 @@ _trace_file = open(_trace_file, 'a') atexit.register(_trace_file.close) _trace_stack_limit = None +else: + # Any use of the _trace attributes should be guarded by `if __debug__`, + # so they should not be needed here. + # But, providing different API for debug mode is unnecessarily fragile. + _trace_level = 0 + _trace_file = sys.stderr + _trace_stack_limit = None import _ldap assert _ldap.__version__==__version__, \ From ec41070d3fa1d00cb272f86e0c8ca55db51bdf62 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 14 Jul 2018 10:54:23 +0200 Subject: [PATCH 050/206] Add Python 3.8-dev to Tox config --- .travis.yml | 18 ++++++++++++++++-- tox.ini | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ce0cdfa..81466393 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,13 @@ matrix: - WITH_GCOV=1 dist: xenial sudo: true + - python: 3.8-dev + env: + - TOXENV=py38 + - CFLAGS_std="-std=c99" + - WITH_GCOV=1 + dist: xenial + sudo: true - python: 2.7 env: - TOXENV=py2-nosasltls @@ -53,6 +60,10 @@ matrix: - python: 3.6 env: TOXENV=doc allow_failures: + - env: + - TOXENV=py38 + - CFLAGS_std="-std=c99" + - WITH_GCOV=1 - env: - TOXENV=pypy @@ -61,7 +72,10 @@ env: # -Wno-int-in-bool-context: don't complain about PyMem_MALLOC() # -Werror: turn all warnings into fatal errors # -Werror=declaration-after-statement: strict ISO C90 - - CFLAGS="-std=c90 -Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" + - CFLAGS_warnings="-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" + # Keep C90 compatibility where possible. + # (Python 3.8+ headers use C99 features, so this needs to be overridable.) + - CFLAGS_std="-std=c90" # pass CFLAGS, CI (for Travis CI) and WITH_GCOV to tox tasks - TOX_TESTENV_PASSENV="CFLAGS CI WITH_GCOV" @@ -69,7 +83,7 @@ install: - pip install "pip>=7.1.0" - pip install tox-travis tox codecov coverage -script: tox +script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" tox after_success: # gather Python coverage diff --git a/tox.ini b/tox.ini index 7dee491b..1434ba01 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,py37,{py2,py3}-nosasltls,doc,py3-trace,coverage-report +envlist = py27,py34,py35,py36,py37,py38,{py2,py3}-nosasltls,doc,py3-trace,coverage-report minver = 1.8 [testenv] From 7c22697600c3f49b03d393be54d9b2db81cf56c0 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mon, 8 Oct 2018 18:03:59 +0200 Subject: [PATCH 051/206] Add support for X-ORIGIN in schema element class ObjectClass --- Lib/ldap/schema/models.py | 5 ++++- Tests/t_ldap_schema_subentry.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index feb7bff1..22ba6fbe 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -137,7 +137,8 @@ class ObjectClass(SchemaElement): 'AUXILIARY':None, 'ABSTRACT':None, 'MUST':(()), - 'MAY':() + 'MAY':(), + 'X-ORIGIN':(None,) } def _set_attrs(self,l,d): @@ -146,6 +147,7 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.must = d['MUST'] self.may = d['MAY'] + self.x_origin = d['X-ORIGIN'][0] # Default is STRUCTURAL, see RFC2552 or draft-ietf-ldapbis-syntaxes self.kind = 0 if d['ABSTRACT']!=None: @@ -168,6 +170,7 @@ def __str__(self): result.append({0:' STRUCTURAL',1:' ABSTRACT',2:' AUXILIARY'}[self.kind]) result.append(self.key_list('MUST',self.must,sep=' $ ')) result.append(self.key_list('MAY',self.may,sep=' $ ')) + result.append(self.key_attr('X-ORIGIN',self.x_origin,quoted=1)) return '( %s )' % ''.join(result) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 4e1e09b2..5869b9d0 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -61,7 +61,7 @@ def test_urlfetch_file(self): str(obj), "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST cn " "MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o " - "$ description ) )" + "$ description ) X-ORIGIN 'RFC 4519' )" ) From ebc12dacd90da898d35252046d5af96531302ee2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 25 Oct 2018 17:12:32 +0200 Subject: [PATCH 052/206] Add a test for X-ORIGIN --- Tests/t_ldap_schema_subentry.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 5869b9d0..edce9310 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -15,7 +15,7 @@ import ldif from ldap.ldapobject import SimpleLDAPObject import ldap.schema -from ldap.schema.models import ObjectClass +from ldap.schema.models import ObjectClass, AttributeType from slapdtest import SlapdTestCase, requires_ldapi HERE = os.path.abspath(os.path.dirname(__file__)) @@ -65,6 +65,22 @@ def test_urlfetch_file(self): ) +class TestXOrigin(unittest.TestCase): + def get_attribute_type(self, oid): + openldap_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[1]) + dn, schema = ldap.schema.urlfetch(openldap_uri) + return schema.get_obj(AttributeType, oid) + + def test_origin_none(self): + self.assertEqual( + self.get_attribute_type('2.5.4.0').x_origin, None) + + def test_origin_string(self): + self.assertEqual( + self.get_attribute_type('1.3.6.1.4.1.3401.8.2.8').x_origin, + 'Pretty Good Privacy (PGP)') + + class TestSubschemaUrlfetchSlapd(SlapdTestCase): ldap_object_class = SimpleLDAPObject From 7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 10 Jun 2018 11:18:30 -0700 Subject: [PATCH 053/206] In docs, use intersphinx to link to Python documentation The intersphinx extension can generate automatic links to the documentation of objects in other projects. For complete details on intersphinx, see: http://www.sphinx-doc.org/en/master/ext/intersphinx.html --- Doc/bytes_mode.rst | 9 ++++----- Doc/conf.py | 7 ++++++- Doc/installing.rst | 15 ++++++--------- Doc/sample_workflow.rst | 5 ++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index d7c2aa4f..dcd3dcb2 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -93,12 +93,11 @@ to be given as well. Porting recommendations ----------------------- -Since end of life of Python 2 is coming in a few years, -projects are strongly urged to make their code compatible with Python 3. -General instructions for this are provided `in Python documentation`_ and in -the `Conservative porting guide`_. +Since end of life of Python 2 is coming in a few years, projects are strongly +urged to make their code compatible with Python 3. General instructions for +this are provided :ref:`in Python documentation ` and in the +`Conservative porting guide`_. -.. _in Python documentation: https://docs.python.org/3/howto/pyporting.html .. _Conservative porting guide: https://portingguide.readthedocs.io/en/latest/ diff --git a/Doc/conf.py b/Doc/conf.py index eb9f5a01..e1fb9ee2 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -31,7 +31,10 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', +] try: import sphinxcontrib.spelling @@ -148,3 +151,5 @@ # If false, no module index is generated. latex_use_modindex = True + +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} diff --git a/Doc/installing.rst b/Doc/installing.rst index babbaf63..90187a93 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -99,10 +99,8 @@ From a source repository:: If you have more than one Python interpreter installed locally, you should use the same one you plan to use python-ldap with. -Further instructions can be found in `Setuptools documentation`_. - - -.. _Setuptools documentation: https://docs.python.org/3/distributing/index.html +Further instructions can be found in :ref:`Setuptools documentation +`. .. _build prerequisites: @@ -169,11 +167,10 @@ Packages for building and testing:: setup.cfg ========= -The file setup.cfg allows to set some build and installation -parameters for reflecting the local installation of required -software packages. Only section ``[_ldap]`` is described here. -More information about other sections can be found in -`Setuptools documentation`_. +The file setup.cfg allows to set some build and installation parameters for +reflecting the local installation of required software packages. Only section +``[_ldap]`` is described here. More information about other sections can be +found in :ref:`Setuptools documentation `. .. data:: library_dirs diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index c2010c1a..8a43553d 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -26,15 +26,14 @@ Clone the repository:: $ git clone https://github.com/python-ldap/python-ldap $ cd python-ldap -Create a `virtual environment`_ to ensure you in-development python-ldap won't -affect the rest of your system:: +Create a :mod:`virtual environment ` to ensure you in-development +python-ldap won't affect the rest of your system:: $ python3 -m venv __venv__ (For Python 2, install `virtualenv`_ and use it instead of ``python3 -m venv``.) .. _git: https://git-scm.com/ -.. _virtual environment: https://docs.python.org/3/library/venv.html .. _virtualenv: https://virtualenv.pypa.io/en/stable/ Activate the virtual environment:: From 9f5a578eed70368ad8fc089b44ec2118a3c2cefb Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mon, 5 Nov 2018 17:35:38 +0100 Subject: [PATCH 054/206] Add docstrings for X-ORIGIN --- Lib/ldap/schema/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 22ba6fbe..18d24fb7 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -126,6 +126,9 @@ class ObjectClass(SchemaElement): sup This list of strings contains NAMEs or OIDs of object classes this object class is derived from + x-origin + This string contains the X-ORIGIN text which is typically used to indicate + the source of the associated schema element """ schema_attribute = u'objectClasses' token_defaults = { @@ -227,6 +230,9 @@ class AttributeType(SchemaElement): sup This list of strings contains NAMEs or OIDs of attribute types this attribute type is derived from + x-origin + This string contains the X-ORIGIN text which is typically used to indicate + the source of the associated schema element """ schema_attribute = u'attributeTypes' token_defaults = { From 7d1359c9852815a635de9ac01a2767e5b577d512 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mon, 26 Nov 2018 02:19:35 +0100 Subject: [PATCH 055/206] Add multi-value support to X-ORIGIN attribute --- Lib/ldap/schema/models.py | 16 ++++++++-------- Tests/t_ldap_schema_subentry.py | 14 ++++++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 18d24fb7..22affa2a 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -128,7 +128,7 @@ class ObjectClass(SchemaElement): this object class is derived from x-origin This string contains the X-ORIGIN text which is typically used to indicate - the source of the associated schema element + the source of the associated schema element. It can a list of strings """ schema_attribute = u'objectClasses' token_defaults = { @@ -141,7 +141,7 @@ class ObjectClass(SchemaElement): 'ABSTRACT':None, 'MUST':(()), 'MAY':(), - 'X-ORIGIN':(None,) + 'X-ORIGIN':() } def _set_attrs(self,l,d): @@ -150,7 +150,7 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.must = d['MUST'] self.may = d['MAY'] - self.x_origin = d['X-ORIGIN'][0] + self.x_origin = d['X-ORIGIN'] # Default is STRUCTURAL, see RFC2552 or draft-ietf-ldapbis-syntaxes self.kind = 0 if d['ABSTRACT']!=None: @@ -173,7 +173,7 @@ def __str__(self): result.append({0:' STRUCTURAL',1:' ABSTRACT',2:' AUXILIARY'}[self.kind]) result.append(self.key_list('MUST',self.must,sep=' $ ')) result.append(self.key_list('MAY',self.may,sep=' $ ')) - result.append(self.key_attr('X-ORIGIN',self.x_origin,quoted=1)) + result.append(self.key_list('X-ORIGIN',self.x_origin,quoted=1)) return '( %s )' % ''.join(result) @@ -232,7 +232,7 @@ class AttributeType(SchemaElement): this attribute type is derived from x-origin This string contains the X-ORIGIN text which is typically used to indicate - the source of the associated schema element + the source of the associated schema element. It can a list of strings """ schema_attribute = u'attributeTypes' token_defaults = { @@ -248,7 +248,7 @@ class AttributeType(SchemaElement): 'COLLECTIVE':None, 'NO-USER-MODIFICATION':None, 'USAGE':('userApplications',), - 'X-ORIGIN':(None,), + 'X-ORIGIN':(), 'X-ORDERED':(None,), } @@ -260,7 +260,7 @@ def _set_attrs(self,l,d): self.equality = d['EQUALITY'][0] self.ordering = d['ORDERING'][0] self.substr = d['SUBSTR'][0] - self.x_origin = d['X-ORIGIN'][0] + self.x_origin = d['X-ORIGIN'] self.x_ordered = d['X-ORDERED'][0] try: syntax = d['SYNTAX'][0] @@ -311,7 +311,7 @@ def __str__(self): 3:" USAGE dSAOperation", }[self.usage] ) - result.append(self.key_attr('X-ORIGIN',self.x_origin,quoted=1)) + result.append(self.key_list('X-ORIGIN',self.x_origin,quoted=1)) result.append(self.key_attr('X-ORDERED',self.x_ordered,quoted=1)) return '( %s )' % ''.join(result) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index edce9310..3fc394d1 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -67,18 +67,24 @@ def test_urlfetch_file(self): class TestXOrigin(unittest.TestCase): def get_attribute_type(self, oid): - openldap_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[1]) + openldap_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[0]) dn, schema = ldap.schema.urlfetch(openldap_uri) return schema.get_obj(AttributeType, oid) def test_origin_none(self): self.assertEqual( - self.get_attribute_type('2.5.4.0').x_origin, None) + self.get_attribute_type('2.16.840.1.113719.1.301.4.24.1').x_origin, + ()) def test_origin_string(self): self.assertEqual( - self.get_attribute_type('1.3.6.1.4.1.3401.8.2.8').x_origin, - 'Pretty Good Privacy (PGP)') + self.get_attribute_type('2.16.840.1.113730.3.1.2091').x_origin, + ('Netscape',)) + + def test_origin_multi_valued(self): + self.assertEqual( + self.get_attribute_type('1.3.6.1.4.1.11.1.3.1.1.3').x_origin, + ('RFC4876', 'user defined')) class TestSubschemaUrlfetchSlapd(SlapdTestCase): From cd35f1c1a50073ab3a49cac7d6c15685fe8e1e38 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Jan 2019 16:20:41 +0100 Subject: [PATCH 056/206] Test the stringification of X-ORIGIN in schema attributes --- Tests/t_ldap_schema_subentry.py | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 3fc394d1..fd006a78 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -86,6 +86,62 @@ def test_origin_multi_valued(self): self.get_attribute_type('1.3.6.1.4.1.11.1.3.1.1.3').x_origin, ('RFC4876', 'user defined')) + def test_origin_none_str(self): + """Check string representation of an attribute without X-ORIGIN""" + # This should check that the representation: + # - does not contain X-ORIGIN, and + # - is still syntactically valid. + # Checking the full output makes the test simpler, + # though might need to be adjusted in the future. + self.assertEqual( + str(self.get_attribute_type('2.16.840.1.113719.1.301.4.24.1')), + ( + "( 2.16.840.1.113719.1.301.4.24.1 " + + "NAME 'krbHostServer' " + + "EQUALITY caseExactIA5Match " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )" + ), + ) + + def test_origin_string_str(self): + """Check string representation of an attr with single-value X-ORIGIN""" + # This should check that the representation: + # - has the X-ORIGIN entry 'Netscape' with no parentheses, and + # - is still syntactically valid. + # Checking the full output makes the test simpler, + # though might need to be adjusted in the future. + self.assertEqual( + str(self.get_attribute_type('2.16.840.1.113730.3.1.2091')), + ( + "( 2.16.840.1.113730.3.1.2091 " + + "NAME 'nsslapd-suffix' " + + "DESC 'Netscape defined attribute type' " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + + "X-ORIGIN 'Netscape' )" + ), + ) + + def test_origin_multi_valued_str(self): + """Check string representation of an attr with multi-value X-ORIGIN""" + # This should check that the representation: + # - has a parenthesized X-ORIGIN entry, and + # - is still syntactically valid. + # Checking the full output makes the test simpler, + # though might need to be adjusted in the future. + self.assertEqual( + str(self.get_attribute_type('1.3.6.1.4.1.11.1.3.1.1.3')), + ( + "( 1.3.6.1.4.1.11.1.3.1.1.3 NAME 'searchTimeLimit' " + + "DESC 'Maximum time an agent or service allows for a search " + + "to complete' " + + "EQUALITY integerMatch " + + "ORDERING integerOrderingMatch " + + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + + "SINGLE-VALUE " + + "X-ORIGIN ( 'RFC4876' 'user defined' ) )" + ), + ) + class TestSubschemaUrlfetchSlapd(SlapdTestCase): ldap_object_class = SimpleLDAPObject From c186345d6846bcf1738ac752540964ccd37bcd0f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Jan 2019 16:37:16 +0100 Subject: [PATCH 057/206] Test behavior of setting x_origin --- Tests/t_ldap_schema_subentry.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index fd006a78..2baea166 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -142,6 +142,24 @@ def test_origin_multi_valued_str(self): ), ) + def test_set_origin_str(self): + """Check that setting X-ORIGIN to a string makes entry unusable""" + attr = self.get_attribute_type('2.16.840.1.113719.1.301.4.24.1') + attr.x_origin = 'Netscape' + self.assertRaises(AssertionError, str, attr) + + def test_set_origin_list(self): + """Check that setting X-ORIGIN to a list makes entry unusable""" + attr = self.get_attribute_type('2.16.840.1.113719.1.301.4.24.1') + attr.x_origin = [] + self.assertRaises(AssertionError, str, attr) + + def test_set_origin_tuple(self): + """Check that setting X-ORIGIN to a tuple works""" + attr = self.get_attribute_type('2.16.840.1.113719.1.301.4.24.1') + attr.x_origin = ('user defined',) + self.assertIn(" X-ORIGIN 'user defined' ", str(attr)) + class TestSubschemaUrlfetchSlapd(SlapdTestCase): ldap_object_class = SimpleLDAPObject From 2dc4ebd6f698c8aac6f4178bb8ae76bdff73778a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Jan 2019 16:38:17 +0100 Subject: [PATCH 058/206] Fix x_origin (not x-origin) attribute name in docs --- Lib/ldap/schema/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 22affa2a..96246c24 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -126,7 +126,7 @@ class ObjectClass(SchemaElement): sup This list of strings contains NAMEs or OIDs of object classes this object class is derived from - x-origin + x_origin This string contains the X-ORIGIN text which is typically used to indicate the source of the associated schema element. It can a list of strings """ @@ -230,7 +230,7 @@ class AttributeType(SchemaElement): sup This list of strings contains NAMEs or OIDs of attribute types this attribute type is derived from - x-origin + x_origin This string contains the X-ORIGIN text which is typically used to indicate the source of the associated schema element. It can a list of strings """ From 7b76b00af4d31937b3bca9b9498d117f651a9487 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Jan 2019 16:58:21 +0100 Subject: [PATCH 059/206] Fix and reword docs of schema elements The sequences are always tuples, not lists. String attributes are None when missing. --- Lib/ldap/schema/models.py | 126 ++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 96246c24..7520b2b1 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -106,29 +106,32 @@ class ObjectClass(SchemaElement): oid OID assigned to the object class names - This list of strings contains all NAMEs of the object class + All NAMEs of the object class (tuple of strings) desc - This string contains description text (DESC) of the object class + Description text (DESC) of the object class (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the object class is marked as OBSOLETE in the schema must - This list of strings contains NAMEs or OIDs of all attributes - an entry of the object class must have + NAMEs or OIDs of all attributes an entry of the object class must have + (tuple of strings) may - This list of strings contains NAMEs or OIDs of additional attributes - an entry of the object class may have + NAMEs or OIDs of additional attributes an entry of the object class may + have (tuple of strings) kind Kind of an object class: 0 = STRUCTURAL, 1 = ABSTRACT, 2 = AUXILIARY sup - This list of strings contains NAMEs or OIDs of object classes - this object class is derived from + NAMEs or OIDs of object classes this object class is derived from + (tuple of strings) x_origin - This string contains the X-ORIGIN text which is typically used to indicate - the source of the associated schema element. It can a list of strings + Value of the X-ORIGIN extension flag (tuple of strings) + + Although it's not official, X-ORIGIN is used in several LDAP server + implementations to indicate the source of the associated schema + element """ schema_attribute = u'objectClasses' token_defaults = { @@ -196,11 +199,11 @@ class AttributeType(SchemaElement): Class attributes: oid - OID assigned to the attribute type + OID assigned to the attribute type (string) names - This list of strings contains all NAMEs of the attribute type + All NAMEs of the attribute type (tuple of strings) desc - This string contains description text (DESC) of the attribute type + Description text (DESC) of the attribute type (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the attribute type is marked as OBSOLETE in the schema @@ -208,19 +211,19 @@ class AttributeType(SchemaElement): Integer flag (0 or 1) indicating whether the attribute must have only one value syntax - String contains OID of the LDAP syntax assigned to the attribute type + OID of the LDAP syntax assigned to the attribute type no_user_mod Integer flag (0 or 1) indicating whether the attribute is modifiable by a client application equality - String contains NAME or OID of the matching rule used for - checking whether attribute values are equal + NAME or OID of the matching rule used for checking whether attribute values + are equal (string, or None if missing) substr - String contains NAME or OID of the matching rule used for - checking whether an attribute value contains another value + NAME or OID of the matching rule used for checking whether an attribute + value contains another value (string, or None if missing) ordering - String contains NAME or OID of the matching rule used for - checking whether attribute values are lesser-equal than + NAME or OID of the matching rule used for checking whether attribute values + are lesser-equal than (string, or None if missing) usage USAGE of an attribute type: 0 = userApplications @@ -228,11 +231,14 @@ class AttributeType(SchemaElement): 2 = distributedOperation, 3 = dSAOperation sup - This list of strings contains NAMEs or OIDs of attribute types - this attribute type is derived from + NAMEs or OIDs of attribute types this attribute type is derived from + (tuple of strings) x_origin - This string contains the X-ORIGIN text which is typically used to indicate - the source of the associated schema element. It can a list of strings + Value of the X-ORIGIN extension flag (tuple of strings). + + Although it's not official, X-ORIGIN is used in several LDAP server + implementations to indicate the source of the associated schema + element """ schema_attribute = u'attributeTypes' token_defaults = { @@ -323,7 +329,7 @@ class LDAPSyntax(SchemaElement): oid OID assigned to the LDAP syntax desc - This string contains description text (DESC) of the LDAP syntax + Description text (DESC) of the LDAP syntax (string, or None if missing) not_human_readable Integer flag (0 or 1) indicating whether the attribute type is marked as not human-readable (X-NOT-HUMAN-READABLE) @@ -367,14 +373,15 @@ class MatchingRule(SchemaElement): oid OID assigned to the matching rule names - This list of strings contains all NAMEs of the matching rule + All NAMEs of the matching rule (tuple of strings) desc - This string contains description text (DESC) of the matching rule + Description text (DESC) of the matching rule obsolete Integer flag (0 or 1) indicating whether the matching rule is marked as OBSOLETE in the schema syntax - String contains OID of the LDAP syntax this matching rule is usable with + OID of the LDAP syntax this matching rule is usable with + (string, or None if missing) """ schema_attribute = u'matchingRules' token_defaults = { @@ -412,15 +419,15 @@ class MatchingRuleUse(SchemaElement): oid OID of the accompanying matching rule names - This list of strings contains all NAMEs of the matching rule + All NAMEs of the matching rule (tuple of strings) desc - This string contains description text (DESC) of the matching rule + Description text (DESC) of the matching rule (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the matching rule is marked as OBSOLETE in the schema applies - This list of strings contains NAMEs or OIDs of attribute types - for which this matching rule is used + NAMEs or OIDs of attribute types for which this matching rule is used + (tuple of strings) """ schema_attribute = u'matchingRuleUse' token_defaults = { @@ -458,26 +465,29 @@ class DITContentRule(SchemaElement): oid OID of the accompanying structural object class names - This list of strings contains all NAMEs of the DIT content rule + All NAMEs of the DIT content rule (tuple of strings) desc - This string contains description text (DESC) of the DIT content rule + Description text (DESC) of the DIT content rule + (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the DIT content rule is marked as OBSOLETE in the schema aux - This list of strings contains NAMEs or OIDs of all auxiliary - object classes usable in an entry of the object class + NAMEs or OIDs of all auxiliary object classes usable in an entry of the + object class (tuple of strings) must - This list of strings contains NAMEs or OIDs of all attributes - an entry of the object class must have which may extend the - list of required attributes of the object classes of an entry + NAMEs or OIDs of all attributes an entry of the object class must + have, which may extend the list of required attributes of the object + classes of an entry. + (tuple of strings) may - This list of strings contains NAMEs or OIDs of additional attributes - an entry of the object class may have which may extend the - list of optional attributes of the object classes of an entry + NAMEs or OIDs of additional attributes an entry of the object class may + have. which may extend the list of optional attributes of the object + classes of an entry. + (tuple of strings) nots - This list of strings contains NAMEs or OIDs of attributes which - may not be present in an entry of the object class + NAMEs or OIDs of attributes which may not be present in an entry of the + object class. (tuple of strings) """ schema_attribute = u'dITContentRules' token_defaults = { @@ -524,17 +534,18 @@ class DITStructureRule(SchemaElement): ruleid rule ID of the DIT structure rule (only locally unique) names - This list of strings contains all NAMEs of the DIT structure rule + All NAMEs of the DIT structure rule (tuple of strings) desc - This string contains description text (DESC) of the DIT structure rule + Description text (DESC) of the DIT structure rule + (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the DIT content rule is marked as OBSOLETE in the schema form - List of strings with NAMEs or OIDs of associated name forms + NAMEs or OIDs of associated name forms (tuple of strings) sup - List of strings with NAMEs or OIDs of allowed structural object classes - of superior entries in the DIT + NAMEs or OIDs of allowed structural object classes + of superior entries in the DIT (tuple of strings) """ schema_attribute = u'dITStructureRules' @@ -582,23 +593,22 @@ class NameForm(SchemaElement): oid OID of the name form names - This list of strings contains all NAMEs of the name form + All NAMEs of the name form (tuple of strings) desc - This string contains description text (DESC) of the name form + Description text (DESC) of the name form (string, or None if missing) obsolete Integer flag (0 or 1) indicating whether the name form is marked as OBSOLETE in the schema form - List of strings with NAMEs or OIDs of associated name forms + NAMEs or OIDs of associated name forms (tuple of strings) oc - String with NAME or OID of structural object classes this name form - is usable with + NAME or OID of structural object classes this name form + is usable with (string) must - This list of strings contains NAMEs or OIDs of all attributes - an RDN must contain + NAMEs or OIDs of all attributes an RDN must contain (tuple of strings) may - This list of strings contains NAMEs or OIDs of additional attributes - an RDN may contain + NAMEs or OIDs of additional attributes an RDN may contain + (tuple of strings) """ schema_attribute = u'nameForms' token_defaults = { From 9c48d3e348e76fdb11c707e2ac58d5a1bdc1d0ee Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Jan 2019 17:43:03 +0100 Subject: [PATCH 060/206] Test types and values of ObjectClass and AttributeType attributes --- Tests/t_ldap_schema_subentry.py | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 2baea166..e05c957a 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -161,6 +161,82 @@ def test_set_origin_tuple(self): self.assertIn(" X-ORIGIN 'user defined' ", str(attr)) +class TestAttributes(unittest.TestCase): + def get_schema(self): + openldap_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[0]) + dn, schema = ldap.schema.urlfetch(openldap_uri) + return schema + + def test_empty_attributetype_attrs(self): + """Check types and values of attributes of a minimal AttributeType""" + # (OID 2.999 is actually "/Example", for use in documentation) + attr = AttributeType('( 2.999 )') + self.assertEqual(attr.oid, '2.999') + self.assertEqual(attr.names, ()) + self.assertEqual(attr.desc, None) + self.assertEqual(attr.obsolete, False) + self.assertEqual(attr.single_value, False) + self.assertEqual(attr.syntax, None) + self.assertEqual(attr.no_user_mod, False) + self.assertEqual(attr.equality, None) + self.assertEqual(attr.substr, None) + self.assertEqual(attr.ordering, None) + self.assertEqual(attr.usage, 0) + self.assertEqual(attr.sup, ()) + self.assertEqual(attr.x_origin, ()) + + def test_empty_objectclass_attrs(self): + """Check types and values of attributes of a minimal ObjectClass""" + # (OID 2.999 is actually "/Example", for use in documentation) + cls = ObjectClass('( 2.999 )') + self.assertEqual(cls.oid, '2.999') + self.assertEqual(cls.names, ()) + self.assertEqual(cls.desc, None) + self.assertEqual(cls.obsolete, False) + self.assertEqual(cls.must, ()) + self.assertEqual(cls.may, ()) + self.assertEqual(cls.kind, 0) + self.assertEqual(cls.sup, ('top',)) + self.assertEqual(cls.x_origin, ()) + + def test_attributetype_attrs(self): + """Check types and values of an AttributeType object's attributes""" + schema = self.get_schema() + attr = schema.get_obj(AttributeType, '1.3.6.1.4.1.11.1.3.1.1.3') + expected_desc = ( + 'Maximum time an agent or service allows for a search to complete' + ) + self.assertEqual(attr.oid, '1.3.6.1.4.1.11.1.3.1.1.3') + self.assertEqual(attr.names, ('searchTimeLimit',)) + self.assertEqual(attr.desc, expected_desc) + self.assertEqual(attr.obsolete, False) + self.assertEqual(attr.single_value, True) + self.assertEqual(attr.syntax, '1.3.6.1.4.1.1466.115.121.1.27') + self.assertEqual(attr.no_user_mod, False) + self.assertEqual(attr.equality, 'integerMatch') + self.assertEqual(attr.ordering, 'integerOrderingMatch') + self.assertEqual(attr.sup, ()) + self.assertEqual(attr.x_origin, ('RFC4876', 'user defined')) + + def test_objectclass_attrs(self): + """Check types and values of an ObjectClass object's attributes""" + schema = self.get_schema() + cls = schema.get_obj(ObjectClass, '2.5.6.9') + expected_may = ( + 'member', 'businessCategory', 'seeAlso', 'owner', 'ou', 'o', + 'description', + ) + self.assertEqual(cls.oid, '2.5.6.9') + self.assertEqual(cls.names, ('groupOfNames',)) + self.assertEqual(cls.desc, None) + self.assertEqual(cls.obsolete, False) + self.assertEqual(cls.must, ('cn',)) + self.assertEqual(cls.may, expected_may) + self.assertEqual(cls.kind, 0) + self.assertEqual(cls.sup, ('top',)) + self.assertEqual(cls.x_origin, ('RFC 4519',)) + + class TestSubschemaUrlfetchSlapd(SlapdTestCase): ldap_object_class = SimpleLDAPObject From 576d5bf84aa7fc65bb9fdb53b0ad9ee169f4e171 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 7 Jan 2019 17:38:14 +0100 Subject: [PATCH 061/206] Make initialize() pass extra keyword arguments to LDAPObject Make initialize() pass extra keyword arguments to LDAPObject Fixes: https://github.com/python-ldap/python-ldap/issues/249 For docs, use signatures and doc from the code (and docstrings) using Sphinx autodoc. https://github.com/python-ldap/python-ldap/pull/250 --- Doc/reference/ldap.rst | 31 ++++++++----------------------- Lib/ldap/functions.py | 11 +++++++++-- Lib/ldap/ldapobject.py | 27 +++++++++++++++++---------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 0ce2e418..b13aa6f0 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -63,6 +63,8 @@ This module defines the following functions: :py:const:`2` for logging the method calls with arguments and the complete results and :py:const:`9` for also logging the traceback of method calls. + Additional keyword arguments are passed to :class:`LDAPObject`. + .. seealso:: :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator @@ -579,33 +581,16 @@ LDAPObject classes .. py:class:: LDAPObject - Instances of :py:class:`LDAPObject` are returned by :py:func:`initialize()` - and :py:func:`open()` (deprecated). The connection is automatically unbound + Instances of :py:class:`LDAPObject` are returned by :py:func:`initialize()`. + The connection is automatically unbound and closed when the LDAP object is deleted. - Internally :py:class:`LDAPObject` is set to :py:class:`SimpleLDAPObject` - by default. - -.. py:class:: SimpleLDAPObject(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=5]]]) - - This basic class wraps all methods of the underlying C API object. - - The arguments are same like for function :py:func:`initialize()`. - -.. py:class:: ReconnectLDAPObject(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=5] [, retry_max=1 [, retry_delay=60.0]]]]) - - This class is derived from :py:class:`SimpleLDAPObject` and used for automatic - reconnects when using the synchronous request methods (see below). This class - also implements the pickle protocol. - - The first arguments are same like for function :py:func:`initialize()`. - - For automatic reconnects it has additional arguments: + Internally :py:class:`LDAPObject` is set to + :py:class:`~ldap.ldapobject.SimpleLDAPObject` by default. - *retry_max* specifies the number of reconnect attempts before - re-raising the :py:exc:`ldap.SERVER_DOWN` exception. +.. autoclass:: ldap.ldapobject.SimpleLDAPObject - *retry_delay* specifies the time in seconds between reconnect attempts. +.. autoclass:: ldap.ldapobject.ReconnectLDAPObject .. _ldap-controls: diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index ae83d08a..529c4c8f 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -65,7 +65,10 @@ def _ldap_function_call(lock,func,*args,**kwargs): return result -def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, bytes_mode=None): +def initialize( + uri, trace_level=0, trace_file=sys.stdout, trace_stack_limit=None, + bytes_mode=None, **kwargs +): """ Return LDAPObject instance by opening LDAP connection to LDAP host specified by LDAP URL @@ -81,8 +84,12 @@ def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, b Default is to use stdout. bytes_mode Whether to enable :ref:`bytes_mode` for backwards compatibility under Py2. + + Additional keyword arguments (such as ``bytes_strictness``) are + passed to ``LDAPObject``. """ - return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode) + return LDAPObject( + uri, trace_level, trace_file, trace_stack_limit, bytes_mode, **kwargs) def get_option(option): diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 8fa71c3e..e4e6841a 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -76,7 +76,9 @@ class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): class SimpleLDAPObject: """ - Drop-in wrapper class around _ldap.LDAPObject + This basic class wraps all methods of the underlying C API object. + + The arguments are same as for the :func:`~ldap.initialize()` function. """ CLASSATTR_OPTION_MAPPING = { @@ -1057,15 +1059,20 @@ def get_naming_contexts(self): class ReconnectLDAPObject(SimpleLDAPObject): """ - In case of server failure (ldap.SERVER_DOWN) the implementations - of all synchronous operation methods (search_s() etc.) are doing - an automatic reconnect and rebind and will retry the very same - operation. - - This is very handy for broken LDAP server implementations - (e.g. in Lotus Domino) which drop connections very often making - it impossible to have a long-lasting control flow in the - application. + :py:class:`SimpleLDAPObject` subclass whose synchronous request methods + automatically reconnect and re-try in case of server failure + (:exc:`ldap.SERVER_DOWN`). + + The first arguments are same as for the :py:func:`~ldap.initialize()` + function. + For automatic reconnects it has additional arguments: + + * retry_max: specifies the number of reconnect attempts before + re-raising the :py:exc:`ldap.SERVER_DOWN` exception. + + * retry_delay: specifies the time in seconds between reconnect attempts. + + This class also implements the pickle protocol. """ __transient_attrs__ = { From 2059c13bf1adbec16875cf81230ea4f78561136b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 30 Jan 2019 13:47:34 +0000 Subject: [PATCH 062/206] Lib/ldap/controls/sss: use str instead of basestring on Python 3 * Python 3 has no basestring, use str instead * Add a very minimal test to create a SSSRequestControl Fixes: https://github.com/python-ldap/python-ldap/issues/255 --- Lib/ldap/controls/sss.py | 6 ++++++ Tests/t_ldap_controls_sss.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 Tests/t_ldap_controls_sss.py diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index a5312d2f..5cdfbdac 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -12,6 +12,8 @@ ] +import sys + import ldap from ldap.ldapobject import LDAPObject from ldap.controls import (RequestControl, ResponseControl, @@ -20,6 +22,10 @@ from pyasn1.type import univ, namedtype, tag, namedval, constraint from pyasn1.codec.ber import encoder, decoder +PY2 = sys.version_info[0] <= 2 +if not PY2: + basestring = str + # SortKeyList ::= SEQUENCE OF SEQUENCE { # attributeType AttributeDescription, diff --git a/Tests/t_ldap_controls_sss.py b/Tests/t_ldap_controls_sss.py new file mode 100644 index 00000000..b510fcbe --- /dev/null +++ b/Tests/t_ldap_controls_sss.py @@ -0,0 +1,17 @@ +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +from ldap.controls import sss + + +class TestControlsPPolicy(unittest.TestCase): + def test_create_sss_request_control(self): + control = sss.SSSRequestControl(ordering_rules=['-uidNumber']) + self.assertEqual(control.ordering_rules, ['-uidNumber']) + + +if __name__ == '__main__': + unittest.main() From 7fb02c0aed8cb97384515d7681c29880326c1909 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 30 Jan 2019 18:09:21 +0100 Subject: [PATCH 063/206] Add Doc/requirements.txt for Read the Docs build (#260) The documentation build uses docstrings from the code, and so it needs the code to be importable. We already use a fake _ldap module for cases where the C compiler is not available, like on Read the Docs. Another issue on Read the Docs is that the pyasn1 dependency needs to be installed. This can't be done automatically via setup.py install, as that would attempt to build C code. Instead, add a documentation-only "requirements.txt", which we can point Read the Docs to. --- Doc/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Doc/requirements.txt diff --git a/Doc/requirements.txt b/Doc/requirements.txt new file mode 100644 index 00000000..7102362c --- /dev/null +++ b/Doc/requirements.txt @@ -0,0 +1,2 @@ +pyasn1 +pyasn1_modules From adf47613f2df5a9b080d00f9b2632da556a61ea8 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 12 Mar 2019 16:01:16 +0100 Subject: [PATCH 064/206] Bump version to 3.2.0 and update the changelog --- CHANGES | 23 +++++++++++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 2c04f801..6e370160 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,26 @@ +---------------------------------------------------------------- +Released 3.2.0 2019-03-13 + +Lib/ +* Add support for X-ORIGIN in ldap.schema's ObjectClass +* Make initialize() pass extra keyword arguments to LDAPObject +* ldap.controls.sss: use str instead of basestring on Python 3 +* Provide ldap._trace_* atributes in non-debug mode + +Doc/ +* Fix ReST syntax for links to set_option and get_option + +Tests/ +* Use intersphinx to link to Python documentation +* Correct type of some attribute values to bytes +* Use system-specific ENOTCONN value + +Infrastructure: +* Add testing and document support for Python 3.7 +* Add Python 3.8-dev to Tox and CI configuration +* Add Doc/requirements.txt for building on Read the Docs + + ---------------------------------------------------------------- Released 3.1.0 2018-05-25 diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index d004c5d8..df29b60c 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.1.0' +__version__ = '3.2.0' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 7a25ecab..6de06459 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.1.0' +__version__ = '3.2.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 3f13ec68..a26c8ac1 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.1.0' +__version__ = '3.2.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 56ba2c91..6b8c986f 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -5,7 +5,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.1.0' +__version__ = '3.2.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From adf9dc402380572b1d3b5bc6fa1d353bbed35074 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Tue, 9 Apr 2019 07:49:52 -0400 Subject: [PATCH 065/206] Update FAQ to include Samba AD-DC error message (GH-276) See https://github.com/python-ldap/python-ldap/issues/275 --- Doc/faq.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/faq.rst b/Doc/faq.rst index c2e7e153..38a645fe 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -45,10 +45,10 @@ That used to work, but after an upgrade it does not work anymore. Why? providing the full functionality. **Q**: My script bound to MS Active Directory but a a search operation results -in the exception :exc:`ldap.OPERATIONS_ERROR` with the diagnostic messages text -“In order to perform this operation a successful bind must be -completed on the connection.” -What's happening here? +in the exception :exc:`ldap.OPERATIONS_ERROR` with the diagnostic message text +*“In order to perform this operation a successful bind must be completed on the +connection.”* Alternatively, a Samba 4 AD returns the diagnostic message +*"Operation unavailable without authentication"*. What's happening here? **A**: When searching from the domain level, MS AD returns referrals (search continuations) for some objects to indicate to the client where to look for these objects. From 95e1d1a8aa9e6aa036f085af1ace3c295e78412b Mon Sep 17 00:00:00 2001 From: Daniel Li Date: Tue, 21 May 2019 10:40:32 -0400 Subject: [PATCH 066/206] Fix thread support check for Python 3 Fixes https://github.com/python-ldap/python-ldap/issues/289. --- Lib/ldap/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 951f9575..8d675733 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -54,11 +54,10 @@ def release(self): try: # Check if Python installation was build with thread support - import thread + import threading except ImportError: LDAPLockBaseClass = DummyLock else: - import threading LDAPLockBaseClass = threading.Lock From 1d373da3746842b4c09f933896f3b37c4b36cd88 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 24 May 2019 13:12:13 +0200 Subject: [PATCH 067/206] Fix TypeError in compare_s, test and document error behavior Pass ``(ldap_res,)`` tuple to string formatting instead of ``ldap_res``. This changes fixes ``TypeError: not all arguments converted during string formatting``. https://github.com/python-ldap/python-ldap/pull/271 Fixes: https://github.com/python-ldap/python-ldap/issues/270 Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 4 +++- Lib/ldap/ldapobject.py | 2 +- Tests/t_ldapobject.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index b13aa6f0..69e24629 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -699,7 +699,9 @@ and wait for and return with the server's result, or with and the value *value*. The synchronous forms returns ``True`` or ``False``. The asynchronous forms returns the message ID of the initiated request, and the result of the asynchronous compare can be obtained using - :py:meth:`result()`. + :py:meth:`result()`. The operation can fail with an exception, e.g. + :py:exc:`ldap.NO_SUCH_OBJECT` when *dn* does not exist or + :py:exc:`ldap.UNDEFINED_TYPE` for an invalid attribute. Note that the asynchronous technique yields the answer by raising the exception objects :py:exc:`ldap.COMPARE_TRUE` or diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index e4e6841a..f7443fad 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -526,7 +526,7 @@ def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): except ldap.COMPARE_FALSE: return False raise ldap.PROTOCOL_ERROR( - 'Compare operation returned wrong result: %r' % (ldap_res) + 'Compare operation returned wrong result: %r' % (ldap_res,) ) def compare(self,dn,attr,value): diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 0619d514..67adeb25 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -661,6 +661,18 @@ def test_compare_s_false(self): result = l.compare_s('cn=Foo1,%s' % base, 'cn', b'Foo2') self.assertIs(result, False) + def test_compare_s_notfound(self): + base = self.server.suffix + l = self._ldap_conn + with self.assertRaises(ldap.NO_SUCH_OBJECT): + result = l.compare_s('cn=invalid,%s' % base, 'cn', b'Foo2') + + def test_compare_s_invalidattr(self): + base = self.server.suffix + l = self._ldap_conn + with self.assertRaises(ldap.UNDEFINED_TYPE): + result = l.compare_s('cn=Foo1,%s' % base, 'invalidattr', b'invalid') + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From 44a593d1c55a007a43fcf30d2b027ac910ea1b96 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 24 May 2019 13:30:45 +0200 Subject: [PATCH 068/206] Fix escape_dn_chars https://github.com/python-ldap/python-ldap/pull/268 --- Lib/ldap/dn.py | 4 ++-- Tests/t_ldap_dn.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 00c7b064..cabfed84 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -29,10 +29,10 @@ def escape_dn_chars(s): s = s.replace(';' ,'\\;') s = s.replace('=' ,'\\=') s = s.replace('\000' ,'\\\000') - if s[0]=='#' or s[0]==' ': - s = ''.join(('\\',s)) if s[-1]==' ': s = ''.join((s[:-1],'\\ ')) + if s[0]=='#' or s[0]==' ': + s = ''.join(('\\',s)) return s diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 4b4dd319..fd36f866 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -50,6 +50,11 @@ def test_escape_dn_chars(self): self.assertEqual(ldap.dn.escape_dn_chars('#foobar'), '\\#foobar') self.assertEqual(ldap.dn.escape_dn_chars('foo bar'), 'foo bar') self.assertEqual(ldap.dn.escape_dn_chars(' foobar'), '\\ foobar') + self.assertEqual(ldap.dn.escape_dn_chars(' '), '\\ ') + self.assertEqual(ldap.dn.escape_dn_chars(' '), '\\ \\ ') + self.assertEqual(ldap.dn.escape_dn_chars('foobar '), 'foobar\\ ') + self.assertEqual(ldap.dn.escape_dn_chars('f+o>o,bo\\,b\\ Date: Fri, 12 Apr 2019 20:56:30 +0200 Subject: [PATCH 069/206] Make tests pass after 2028 Extend the lifetime of test certs to not expire so soon. Background: As part of my work on reproducible builds for openSUSE, I check that software still gives identical build results in the future. The usual offset is +15 years, because that is how long I expect some software will be used in some places. This showed up failing tests in our package build. See https://reproducible-builds.org/ for why this matters. --- Lib/slapdtest/certs/ca.conf | 4 +- Lib/slapdtest/certs/ca.pem | 110 +++++++++++++++--------------- Lib/slapdtest/certs/client.key | 52 +++++++-------- Lib/slapdtest/certs/client.pem | 112 +++++++++++++++---------------- Lib/slapdtest/certs/gencerts.sh | 2 +- Lib/slapdtest/certs/server.key | 52 +++++++-------- Lib/slapdtest/certs/server.pem | 114 ++++++++++++++++---------------- 7 files changed, 223 insertions(+), 223 deletions(-) diff --git a/Lib/slapdtest/certs/ca.conf b/Lib/slapdtest/certs/ca.conf index 5046b0d6..d1d89e18 100644 --- a/Lib/slapdtest/certs/ca.conf +++ b/Lib/slapdtest/certs/ca.conf @@ -32,7 +32,7 @@ serial = $tmpdir/$ca.crt.srl crlnumber = $tmpdir/$ca.crl.srl database = $tmpdir/$ca.db unique_subject = no -default_days = 3652 +default_days = 365200 default_md = sha256 policy = match_pol email_in_dn = no @@ -40,7 +40,7 @@ preserve = no name_opt = $name_opt cert_opt = ca_default copy_extensions = none -default_crl_days = 3651 +default_crl_days = 365100 [match_pol] countryName = match diff --git a/Lib/slapdtest/certs/ca.pem b/Lib/slapdtest/certs/ca.pem index cf2ff33c..b52ffafb 100644 --- a/Lib/slapdtest/certs/ca.pem +++ b/Lib/slapdtest/certs/ca.pem @@ -5,31 +5,31 @@ Certificate: Signature Algorithm: sha256WithRSAEncryption Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA Validity - Not Before: Dec 2 11:57:47 2017 GMT - Not After : Sep 4 11:57:47 2027 GMT + Not Before: Apr 12 18:52:38 2019 GMT + Not After : Oct 17 18:52:38 2994 GMT Subject: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:af:1f:cf:0f:c5:95:66:2d:eb:85:cc:21:fc:0d: - 0f:44:d8:2f:a8:85:08:ef:60:67:57:fa:0b:c5:e4: - b3:fb:f1:6f:cb:30:7a:47:0d:a7:f1:b5:37:81:5f: - f6:39:28:e2:f9:4d:6c:2e:a6:5c:0e:3c:db:4d:c9: - 2a:64:ce:0d:15:30:c7:75:52:b8:74:c5:0b:00:4c: - 2f:94:1b:dd:fb:83:2c:58:02:73:b0:86:3a:6a:aa: - 55:f2:d5:49:99:17:a5:e2:44:ec:dd:62:5f:8d:ce: - 77:29:0b:8d:87:23:e2:4b:d6:1c:25:f3:06:a9:ee: - 33:6f:ac:ed:22:9e:35:ec:55:e7:1b:38:68:7e:46: - e3:c3:42:ac:06:0b:0a:7a:84:c9:3d:ef:3d:a5:6e: - e9:10:24:c3:28:fe:1f:4a:9a:23:8a:3c:db:0a:66: - 5d:07:f8:c5:17:68:53:e4:0e:37:33:c4:d2:ad:58: - 62:6b:8a:87:ab:73:eb:bc:2b:ac:07:69:84:8d:e3: - c4:a9:78:9b:6c:1e:03:63:df:b4:96:18:bd:3c:2e: - be:7f:2c:d5:a8:f8:12:b9:ab:27:52:b0:de:38:62: - 3c:54:a7:f3:aa:37:a3:11:12:b2:a7:6f:8d:96:10: - ce:01:cb:25:24:a6:51:18:93:69:9b:9e:5c:8a:ff: - fe:89 + 00:d7:30:73:20:44:7d:83:d4:c7:01:b8:ab:1e:7c: + 91:f4:38:ac:9c:41:43:64:0c:31:99:48:70:22:7d: + ae:1b:47:e7:2a:28:4d:f7:46:4e:b4:ba:ae:c0:9d: + d5:1f:4b:7a:79:2f:b9:dc:68:7f:79:84:88:50:51: + 3b:7d:dc:d5:57:17:66:45:c0:2c:20:13:f7:99:d6: + 9d:e2:12:7c:41:76:82:51:19:2c:b6:ff:46:cb:04: + 56:38:22:2a:c3:7a:b5:71:51:49:4e:62:68:a0:99: + 6f:de:f3:a2:0f:a2:aa:1b:72:a5:87:bc:42:5a:a7: + 22:8d:33:b4:88:a8:dc:5d:72:ca:dd:a0:9a:4e:db: + 7d:8b:10:de:c5:41:e9:e9:8d:fa:6c:dd:94:6e:b1: + 31:c2:6d:a1:69:6c:7a:3a:b2:76:65:c9:e5:95:38: + 62:40:81:c6:29:26:26:d1:d1:c1:f4:5e:fa:24:ef: + 13:da:24:13:6f:f5:5c:ba:b1:31:8f:30:94:71:7b: + c6:e5:da:b9:b5:64:39:39:09:c2:4a:80:64:58:1d: + 99:f5:65:3c:a7:26:08:95:26:35:7b:fa:e7:20:08: + ff:72:df:9b:8f:9f:da:8b:c3:a7:8b:fc:8c:c0:a5: + 31:87:1d:4c:14:f6:cf:90:5e:2e:6e:a6:db:27:08: + eb:df Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical @@ -37,44 +37,44 @@ Certificate: X509v3 Key Usage: critical Certificate Sign, CRL Sign X509v3 Subject Key Identifier: - 3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 + BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 X509v3 Authority Key Identifier: - keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 + keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 Signature Algorithm: sha256WithRSAEncryption - 0a:e7:dc:38:ce:03:dd:a8:99:11:d0:24:be:ef:1a:18:9d:7c: - 95:75:4a:4a:29:44:23:28:fc:66:d5:81:ce:05:c2:c0:6b:71: - d6:8d:33:a9:53:a6:1c:f1:4e:50:ae:a3:b1:72:d6:69:53:ad: - a9:62:a9:45:27:68:17:35:41:97:ec:e9:65:91:62:12:ed:eb: - 45:3a:9b:cc:09:bc:e3:ad:22:6b:13:6b:b0:67:ef:ce:01:83: - 5e:6c:95:e2:b3:73:b9:69:9a:33:49:f9:5f:52:4e:39:94:c9: - db:93:6f:d8:ba:10:92:ce:fa:12:6b:bc:31:ff:c1:67:70:63: - 07:dc:53:7a:3a:a3:51:20:15:44:cf:1c:a9:cd:b7:30:1d:8e: - 55:93:8a:56:8c:3d:e9:8b:ae:0c:77:8d:5c:8b:fd:22:d8:4c: - 3e:e4:76:e8:d9:e8:c3:98:f4:98:ff:02:60:95:8e:3e:26:7a: - e2:fe:2c:0a:a4:52:8d:4c:3d:dd:4c:fd:2f:2c:db:83:4c:2b: - 25:24:37:78:9a:07:27:52:f9:1c:c0:65:65:cb:50:77:b4:2d: - fa:f4:af:bb:42:1c:43:65:c6:01:6e:f1:4b:fe:b8:4a:3c:29: - 8b:b6:84:1e:17:99:61:98:65:fe:f2:e9:ce:bb:ac:87:69:cb: - e6:13:42:bf + 06:20:1f:eb:42:6a:42:62:b1:ee:69:c8:cd:47:a6:2e:69:95: + 59:dc:49:09:69:40:93:25:a1:ec:6d:3a:dd:dc:e5:74:ab:33: + 9d:8f:cc:e3:bb:7a:3f:5b:51:58:74:f7:bd:6c:7c:3c:b6:5a: + 05:50:a8:8c:c3:fb:5b:75:2a:c2:6c:06:93:4c:a9:93:71:1c: + 51:e5:be:a1:24:93:e2:79:ca:ea:08:86:90:b9:70:e7:7a:40: + bf:f4:d6:71:f4:4d:c0:0f:e0:31:a0:23:46:77:30:72:a9:62: + 8a:2a:12:c4:dd:3d:86:ae:f7:6b:33:80:26:58:49:53:ff:cd: + 8a:c6:f6:11:2c:b3:ff:a5:8e:1c:f8:22:e2:1b:8e:04:33:fb: + 0d:da:31:86:12:9f:d1:03:86:9c:6a:78:5e:3c:5e:8a:52:aa: + 68:1f:ff:f9:17:75:b0:da:f2:99:3c:80:3c:96:2a:33:07:54: + 59:84:e7:92:34:0f:99:76:e3:d6:4d:4d:9c:fb:21:35:f9:cb: + a5:30:80:8b:9d:61:90:d3:d4:59:3a:2f:f2:f6:20:13:7e:26: + dc:50:b0:49:3e:19:fe:eb:7d:cf:b9:1a:5d:5c:3a:76:30:d9: + 0e:d7:df:de:ce:a9:c4:21:df:63:b9:d0:64:86:0b:28:9a:2e: + ab:51:73:e4 -----BEGIN CERTIFICATE----- -MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MIIDjDCCAnSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ3WhcNMjcwOTA0 -MTE1NzQ3WjBWMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR -BgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNVBAMME1B5dGhvbiBMREFQIFRlc3QgQ0Ew -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvH88PxZVmLeuFzCH8DQ9E -2C+ohQjvYGdX+gvF5LP78W/LMHpHDafxtTeBX/Y5KOL5TWwuplwOPNtNySpkzg0V -MMd1Urh0xQsATC+UG937gyxYAnOwhjpqqlXy1UmZF6XiROzdYl+NzncpC42HI+JL -1hwl8wap7jNvrO0injXsVecbOGh+RuPDQqwGCwp6hMk97z2lbukQJMMo/h9KmiOK -PNsKZl0H+MUXaFPkDjczxNKtWGJrioerc+u8K6wHaYSN48SpeJtsHgNj37SWGL08 -Lr5/LNWo+BK5qydSsN44YjxUp/OqN6MRErKnb42WEM4ByyUkplEYk2mbnlyK//6J -AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud -DgQWBBQ7HzL0/lfRb0mRVfIk8QpmO6Xu1DAfBgNVHSMEGDAWgBQ7HzL0/lfRb0mR -VfIk8QpmO6Xu1DANBgkqhkiG9w0BAQsFAAOCAQEACufcOM4D3aiZEdAkvu8aGJ18 -lXVKSilEIyj8ZtWBzgXCwGtx1o0zqVOmHPFOUK6jsXLWaVOtqWKpRSdoFzVBl+zp -ZZFiEu3rRTqbzAm8460iaxNrsGfvzgGDXmyV4rNzuWmaM0n5X1JOOZTJ25Nv2LoQ -ks76Emu8Mf/BZ3BjB9xTejqjUSAVRM8cqc23MB2OVZOKVow96YuuDHeNXIv9IthM -PuR26Nnow5j0mP8CYJWOPiZ64v4sCqRSjUw93Uz9Lyzbg0wrJSQ3eJoHJ1L5HMBl -ZctQd7Qt+vSvu0IcQ2XGAW7xS/64Sjwpi7aEHheZYZhl/vLpzrush2nL5hNCvw== +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMjk5NDEw +MTcxODUyMzhaMFYxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET +MBEGA1UECwwKc2xhcGQtdGVzdDEcMBoGA1UEAwwTUHl0aG9uIExEQVAgVGVzdCBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANcwcyBEfYPUxwG4qx58 +kfQ4rJxBQ2QMMZlIcCJ9rhtH5yooTfdGTrS6rsCd1R9Lenkvudxof3mEiFBRO33c +1VcXZkXALCAT95nWneISfEF2glEZLLb/RssEVjgiKsN6tXFRSU5iaKCZb97zog+i +qhtypYe8QlqnIo0ztIio3F1yyt2gmk7bfYsQ3sVB6emN+mzdlG6xMcJtoWlsejqy +dmXJ5ZU4YkCBxikmJtHRwfRe+iTvE9okE2/1XLqxMY8wlHF7xuXaubVkOTkJwkqA +ZFgdmfVlPKcmCJUmNXv65yAI/3Lfm4+f2ovDp4v8jMClMYcdTBT2z5BeLm6m2ycI +698CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFL141UrxkJbF6OxmSSNHA18mc4ayMB8GA1UdIwQYMBaAFL141UrxkJbF +6OxmSSNHA18mc4ayMA0GCSqGSIb3DQEBCwUAA4IBAQAGIB/rQmpCYrHuacjNR6Yu +aZVZ3EkJaUCTJaHsbTrd3OV0qzOdj8zju3o/W1FYdPe9bHw8tloFUKiMw/tbdSrC +bAaTTKmTcRxR5b6hJJPiecrqCIaQuXDnekC/9NZx9E3AD+AxoCNGdzByqWKKKhLE +3T2GrvdrM4AmWElT/82KxvYRLLP/pY4c+CLiG44EM/sN2jGGEp/RA4acanhePF6K +UqpoH//5F3Ww2vKZPIA8liozB1RZhOeSNA+ZduPWTU2c+yE1+culMICLnWGQ09RZ +Oi/y9iATfibcULBJPhn+633PuRpdXDp2MNkO19/ezqnEId9judBkhgsomi6rUXPk -----END CERTIFICATE----- diff --git a/Lib/slapdtest/certs/client.key b/Lib/slapdtest/certs/client.key index 70600ba5..7213c0b4 100644 --- a/Lib/slapdtest/certs/client.key +++ b/Lib/slapdtest/certs/client.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGxvbMEbahViK4 -P6aoWUkciIf1dEYTBuU8M1eShREUZ3Ytq/ee425pXRyxxDrAa8ygRjqs7tauwhgA -KNuPRGyw7hyZ1Ku4vQObwX9rzyHQ6Fj606U4HHbYfAVb0AF7OzLbhNH3isCRtNcm -EUSYG1Nkfn9zQkV4pz2KJM4ePt4GyGJV0NhRUdHdwgsWiuRt2EIRPwLXdkf/4svm -2EahAJax+SMaZYe/lg9w9SBxl6DTaF9lFpoPqzeW+KnXCmE8e+qSWea8EjkzIRXa -4KJ/EnUMP2fUL+Je2agGF2YfCId9YvLTQV8YDv7Im1Sp0sbIUDoWX14GDUbZ8cBr -wfOyI0TfAgMBAAECggEATWv1eGp1zcU05Lq1+OA938U1316YZJTM+HOu6jy1+FKL -7yIJ4nMG8Db6FCswDv5txwdTl0O3jn2+x2Eik1y9UPSNY0U4VU4Zd7MYJC+bJjk5 -XwjMU1yS1aMIm0gbK5pVJrdG6Lm8Y4QiQIt9Qhlyk7PJhGUNlf7ds06+kX0/ETiO -vx5SatExeKu5F+JRnGFdAN0106SF5vBum+UbrgOSnJmfwX5VoOXARD21ppxgMzAr -JyGBpgBgy++GpV15gXGuA7DVMIADdHw8hV4OuBLjpkUL+ntArjhpUi7TP7VU3WKR -uUmvLm9CX1l8O/xZMpt9N1+o71a//7asnz8AMtT6cQKBgQD4FgefUkVnXDA1xKDW -1JbArVQeHiLGlRdLakRUY/HdGj72YgAOLt3UsrON4VQXl0C6rks/8HKCFaMexBlF -OecJNWsEVgBEAfsQ+NvrApOQsTszc8Zqna0Kqe2vA0VNa+SAzdHzhBbFcaVkzXJb -JB7M0/OIt5IaqXg6Y5eX2eZF1QKBgQDNHkIoJ/2hYtlSgXpGaniM+0XemQJgJXig -edAQdGKKfqwmjSFjByDM01ZaidMu5fEkeGhMRE73IbwNw0pWsMXylD6bI6+sk7yQ -biM+fslFEEDbgSJe41Jy2eerh5am+dnrMWNhd7QZV1K6tmaqrIzkmIV21/EPXIPp -BNHO8GV14wKBgGOybrO/GzcTXChvcXeEDWU3AqPr1mvZhHgBJ56GX69MGdtnvL/2 -Y51Th0bQM7wbQ58B5im21j2itl/pzIH+Z/NSbURbz1WFOkEy0SYbbfPq1XCy6Rz1 -apHrgiIf/VzErBp7HBFxlrkYF7Bvw7IOzPXhg3AA3Y0rZ66HUWdr4NdVAoGBAJfC -E2Bydgy5feC1OypuC9MC9abDviY0kxLoDTCfa2jcX7IGKPWDiJkCo5lI7557Mfax -vzjuMR5XLzNfkdih4VKgq9FMjeU5SQHy+tB6LZ+Tbuj4md1qgs3GuskGAEh6Auko -GUc7sVwuZ18NJNiR4Ywf7F8JVajv4gi9MB3Tbr3RAoGARSnVu+6rYSQTyEqvbsaB -gIW7Ezea5q06GcQF072nk3tNSXuU/52YMlodAJ1UfFPbBAtaa7wEFN8oRG1IyKON -MGyf6RD8GoInJjaDihkdCsR28RkchwymG1UMPnPzqRxSAb7da5YuMR8PEioVbL68 -dxhsgNi1Wtc2nGqN96qufG0= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDjt5O6nRrnAWPm +T0JvRLBHMclll92IWF/O4GEdcJ5fbBxP3BxK0Dv+6aRcR7b2o0f6fk/bgNepXfv/ +MXDQcFlESbfmUNGshFmZr0sjPrYPD1R06TZs+/7RsMXnx1c79mFGEQ4wqzDOBHKQ +xeDhNJk+BcE0QABsqF8AA2XC2/dK14QCljKLC84k1zTFTnh8duN2eAalaPQFFOoj +4AnonUnswJ45zIx5V2BdG+oqO5dwo/cEukKgAEL8T2IJ9Cqlmh2sPbMqYC8cODq6 +YcugMznxrfHV5LNThfkvwMe26+vv68r65zalPDy0M+cUMTMyBVY4TL3fejrloY2t +YMhPJIclAgMBAAECggEAPXdd/u9NRbGQX6hhTFuEIZOEw1F80MLaCaNzU1kExskN +01icom0W5LX4UZhiAK0OTsUtlRhwHh1qWfXkd777uX0UkKycDC8laGByra7Nwb7n +ky8oK77Rh5RptyiNmXflxd3wsJ5k7BczPXTMQL3L53vyLMJh2vKPwhcorrJlS+Pi +JjINMaR4IrDlpMYlrn9NTjsGr+mj/pdmKfU/KVXeKzFcwKTjUnDJNSbGDIC0AxaJ +dGU0yIX9MPW+p5szcA9o22UWW4LsEFY4YABeCqbm9/UQt3jWVMjCy4AOgr/9HWSR +DvXI/Xtdl3CTCr8+qDnhBaUI27z+UelZfTBFKUb8AQKBgQD6SmtrTBgEfb6tuxJw +AAHRuUcWGjatZ7X+meHRC9B7UPxUrKl9tU5NC7Gz6YMt+vr4bNMwykI6Ndj+4tSJ +KqsAC86v19CH4usMBLZ68MeTRvtQGiPah71syYrxf0uvYOx/KzUUBX240Ls+lEbE +W33psMoNAezUPpJwKx7CMjcBgQKBgQDo6VaT59bKRc3DXJvqFjd7TPIex+ny6JK+ +8oOwyyFFBwkzfymoOxN4lxSrE6yf7uTemRRn+RIH3UGDottIDqzhjvtcV5uODeIN +8WzxTbl759qIxt+z7aF7SkwJLJAAZS3qqCXKtMBo7ln4xKaoRLT2RohqD1YXGrg8 +wmYcUZoPpQKBgQCm2QVSuZ8pH0oFNjfMQbT0wbYJnd/lKMXBu4M1f9Ky4gHT0GYM +Ttirs6f6byfrduvmv2TpmWscsti80SktZywnE7fssMlqTHKzyFB9FBV2sFLHyyUr +gGFeK9xbsKgbeVkuTPdNKXvtv/eSd/XU38jIB/opQadGtY+ZBqWyfxb8AQKBgBLc +SlmBzZ/llSr7xdhn4ihG69hYQfacpL13r/hSCqinUDRuWLY5ynLacR8FYdY1pyzr +Yn6k6bPfU93QA0fLgG5ngK1SntMbBrIwWa0UqS+Cb+zhhd3xIUF1m8CmbibKCrTU +1vKaPnaAzqJZclFv9uN2hLdp9IO8cyzgZRpn9TzNAoGAUfZF1983qknfBgD8Lgm3 +zzKYtc8q2Ukatfo4VCp66CEprbLcBq5mKx6JiBoMGqU8SI5XVG0F0aHH2n8gImcu +bO0vtEldDc1ylZ/H7xhHFWlMzmTlsbHdHVtetFfKLTpjq6duvgLA12lJNHNVu3OU +Z1bRWDeZIP70+jdYrmSoVi8= -----END PRIVATE KEY----- diff --git a/Lib/slapdtest/certs/client.pem b/Lib/slapdtest/certs/client.pem index 33b95a73..ca2989ca 100644 --- a/Lib/slapdtest/certs/client.pem +++ b/Lib/slapdtest/certs/client.pem @@ -5,31 +5,31 @@ Certificate: Signature Algorithm: sha256WithRSAEncryption Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA Validity - Not Before: Dec 2 11:57:48 2017 GMT - Not After : Dec 2 11:57:48 2027 GMT + Not Before: Apr 12 18:52:38 2019 GMT + Not After : Mar 1 18:52:38 3019 GMT Subject: C=DE, O=python-ldap, OU=slapd-test, CN=client Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:c6:c6:f6:cc:11:b6:a1:56:22:b8:3f:a6:a8:59: - 49:1c:88:87:f5:74:46:13:06:e5:3c:33:57:92:85: - 11:14:67:76:2d:ab:f7:9e:e3:6e:69:5d:1c:b1:c4: - 3a:c0:6b:cc:a0:46:3a:ac:ee:d6:ae:c2:18:00:28: - db:8f:44:6c:b0:ee:1c:99:d4:ab:b8:bd:03:9b:c1: - 7f:6b:cf:21:d0:e8:58:fa:d3:a5:38:1c:76:d8:7c: - 05:5b:d0:01:7b:3b:32:db:84:d1:f7:8a:c0:91:b4: - d7:26:11:44:98:1b:53:64:7e:7f:73:42:45:78:a7: - 3d:8a:24:ce:1e:3e:de:06:c8:62:55:d0:d8:51:51: - d1:dd:c2:0b:16:8a:e4:6d:d8:42:11:3f:02:d7:76: - 47:ff:e2:cb:e6:d8:46:a1:00:96:b1:f9:23:1a:65: - 87:bf:96:0f:70:f5:20:71:97:a0:d3:68:5f:65:16: - 9a:0f:ab:37:96:f8:a9:d7:0a:61:3c:7b:ea:92:59: - e6:bc:12:39:33:21:15:da:e0:a2:7f:12:75:0c:3f: - 67:d4:2f:e2:5e:d9:a8:06:17:66:1f:08:87:7d:62: - f2:d3:41:5f:18:0e:fe:c8:9b:54:a9:d2:c6:c8:50: - 3a:16:5f:5e:06:0d:46:d9:f1:c0:6b:c1:f3:b2:23: - 44:df + 00:e3:b7:93:ba:9d:1a:e7:01:63:e6:4f:42:6f:44: + b0:47:31:c9:65:97:dd:88:58:5f:ce:e0:61:1d:70: + 9e:5f:6c:1c:4f:dc:1c:4a:d0:3b:fe:e9:a4:5c:47: + b6:f6:a3:47:fa:7e:4f:db:80:d7:a9:5d:fb:ff:31: + 70:d0:70:59:44:49:b7:e6:50:d1:ac:84:59:99:af: + 4b:23:3e:b6:0f:0f:54:74:e9:36:6c:fb:fe:d1:b0: + c5:e7:c7:57:3b:f6:61:46:11:0e:30:ab:30:ce:04: + 72:90:c5:e0:e1:34:99:3e:05:c1:34:40:00:6c:a8: + 5f:00:03:65:c2:db:f7:4a:d7:84:02:96:32:8b:0b: + ce:24:d7:34:c5:4e:78:7c:76:e3:76:78:06:a5:68: + f4:05:14:ea:23:e0:09:e8:9d:49:ec:c0:9e:39:cc: + 8c:79:57:60:5d:1b:ea:2a:3b:97:70:a3:f7:04:ba: + 42:a0:00:42:fc:4f:62:09:f4:2a:a5:9a:1d:ac:3d: + b3:2a:60:2f:1c:38:3a:ba:61:cb:a0:33:39:f1:ad: + f1:d5:e4:b3:53:85:f9:2f:c0:c7:b6:eb:eb:ef:eb: + ca:fa:e7:36:a5:3c:3c:b4:33:e7:14:31:33:32:05: + 56:38:4c:bd:df:7a:3a:e5:a1:8d:ad:60:c8:4f:24: + 87:25 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical @@ -39,45 +39,45 @@ Certificate: X509v3 Extended Key Usage: critical TLS Web Client Authentication X509v3 Subject Key Identifier: - 67:63:38:F4:B4:BC:F3:6B:BC:74:0E:7C:27:C9:BB:C2:CC:58:AC:16 + 4F:E7:35:C7:C8:C1:01:C3:7C:53:86:B9:BF:AE:8B:D6:45:A2:78:20 X509v3 Authority Key Identifier: - keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 + keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 Signature Algorithm: sha256WithRSAEncryption - 76:24:42:6b:33:4f:d6:59:07:48:5b:04:9c:3c:d3:3f:63:80: - 75:4d:78:d7:d5:85:b1:77:81:31:a3:91:cb:c9:a3:8c:0e:00: - 28:08:74:71:6c:fc:83:8c:80:ec:1c:e8:ee:83:e0:7f:49:3b: - f3:42:33:5a:1f:68:0c:a5:41:42:ce:bf:77:29:07:f2:18:a7: - 81:17:d7:76:47:04:d9:8a:dd:e8:5a:26:26:ea:a4:76:70:e1: - f1:fa:e1:db:bc:f2:24:b2:37:a8:58:2f:e3:66:89:77:02:55: - 87:ef:3c:1f:66:ce:4e:86:b3:4c:57:43:86:7f:4c:ab:5a:33: - dd:ca:e3:2f:3b:af:b4:43:5a:53:8b:e0:12:da:e7:c0:13:76: - b2:68:d5:14:f8:1a:07:ce:8a:87:5c:91:bd:35:d7:83:c6:2a: - a4:e0:92:50:01:b9:c2:fa:69:06:5c:8a:80:ee:9c:24:f9:49: - 64:e3:59:c1:a6:69:29:ce:b7:89:20:a9:7c:d6:9f:df:2a:d1: - a4:98:2a:6d:7b:93:6a:52:e3:ae:de:1a:d8:f3:2e:cf:02:7e: - ba:9a:fa:f4:b3:b5:6e:9a:23:10:70:53:53:30:d5:8a:32:35: - 01:52:58:6d:9d:f5:8e:bb:b9:76:bd:41:16:88:26:f8:d3:ce: - 70:03:c8:59 + 1c:90:5f:cf:18:48:95:4d:9d:d3:8e:6d:d1:69:19:1e:7b:3f: + 1f:48:7c:c8:0d:2f:c4:53:0f:89:23:f4:be:ea:b4:7a:c6:dd: + cc:18:0f:e7:34:ea:2c:d4:07:0d:65:78:e8:20:40:3f:36:ef: + 2c:00:31:69:e6:20:48:65:be:57:03:0e:69:ff:b9:83:59:99: + 7d:4d:86:98:14:5b:8e:39:25:3a:a8:6d:51:dc:45:a5:0f:cd: + f3:7a:fd:55:af:5f:55:75:20:03:f5:4a:75:6a:79:2f:76:84: + f6:4e:3d:1d:59:45:9a:b1:6a:57:6f:16:76:76:f8:df:6e:96: + d5:25:27:34:4b:21:d8:c9:9a:36:55:45:a0:43:16:43:68:93: + 37:af:81:89:06:d1:56:1b:9e:0f:62:40:ad:3c:4c:f5:ef:6c: + a2:a4:7f:f2:fa:78:9c:0d:c0:19:f1:10:e8:d8:cf:03:67:3c: + 2d:4d:f3:5d:67:5c:41:a7:4f:d6:c5:0e:ff:2c:04:dd:23:bb: + 85:44:8e:25:ac:15:a3:82:fa:a4:4f:fa:1d:87:f0:58:dc:ae: + 53:05:b9:81:e8:cb:e5:0c:ac:a5:74:68:03:f9:22:a0:45:b6: + 62:58:e0:98:d9:8c:54:a4:22:03:7a:37:12:eb:7d:b1:ad:45: + 60:8e:7a:df -----BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MIIDlDCCAnygAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy -MTE1NzQ4WjBJMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR -BgNVBAsMCnNsYXBkLXRlc3QxDzANBgNVBAMMBmNsaWVudDCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAMbG9swRtqFWIrg/pqhZSRyIh/V0RhMG5TwzV5KF -ERRndi2r957jbmldHLHEOsBrzKBGOqzu1q7CGAAo249EbLDuHJnUq7i9A5vBf2vP -IdDoWPrTpTgcdth8BVvQAXs7MtuE0feKwJG01yYRRJgbU2R+f3NCRXinPYokzh4+ -3gbIYlXQ2FFR0d3CCxaK5G3YQhE/Atd2R//iy+bYRqEAlrH5Ixplh7+WD3D1IHGX -oNNoX2UWmg+rN5b4qdcKYTx76pJZ5rwSOTMhFdrgon8SdQw/Z9Qv4l7ZqAYXZh8I -h31i8tNBXxgO/sibVKnSxshQOhZfXgYNRtnxwGvB87IjRN8CAwEAAaN4MHYwDAYD -VR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH -AwIwHQYDVR0OBBYEFGdjOPS0vPNrvHQOfCfJu8LMWKwWMB8GA1UdIwQYMBaAFDsf -MvT+V9FvSZFV8iTxCmY7pe7UMA0GCSqGSIb3DQEBCwUAA4IBAQB2JEJrM0/WWQdI -WwScPNM/Y4B1TXjX1YWxd4Exo5HLyaOMDgAoCHRxbPyDjIDsHOjug+B/STvzQjNa -H2gMpUFCzr93KQfyGKeBF9d2RwTZit3oWiYm6qR2cOHx+uHbvPIksjeoWC/jZol3 -AlWH7zwfZs5OhrNMV0OGf0yrWjPdyuMvO6+0Q1pTi+AS2ufAE3ayaNUU+BoHzoqH -XJG9NdeDxiqk4JJQAbnC+mkGXIqA7pwk+Ulk41nBpmkpzreJIKl81p/fKtGkmCpt -e5NqUuOu3hrY8y7PAn66mvr0s7VumiMQcFNTMNWKMjUBUlhtnfWOu7l2vUEWiCb4 -085wA8hZ +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz +MDExODUyMzhaMEkxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET +MBEGA1UECwwKc2xhcGQtdGVzdDEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA47eTup0a5wFj5k9Cb0SwRzHJZZfdiFhfzuBh +HXCeX2wcT9wcStA7/umkXEe29qNH+n5P24DXqV37/zFw0HBZREm35lDRrIRZma9L +Iz62Dw9UdOk2bPv+0bDF58dXO/ZhRhEOMKswzgRykMXg4TSZPgXBNEAAbKhfAANl +wtv3SteEApYyiwvOJNc0xU54fHbjdngGpWj0BRTqI+AJ6J1J7MCeOcyMeVdgXRvq +KjuXcKP3BLpCoABC/E9iCfQqpZodrD2zKmAvHDg6umHLoDM58a3x1eSzU4X5L8DH +tuvr7+vK+uc2pTw8tDPnFDEzMgVWOEy933o65aGNrWDITySHJQIDAQABo3gwdjAM +BgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEF +BQcDAjAdBgNVHQ4EFgQUT+c1x8jBAcN8U4a5v66L1kWieCAwHwYDVR0jBBgwFoAU +vXjVSvGQlsXo7GZJI0cDXyZzhrIwDQYJKoZIhvcNAQELBQADggEBAByQX88YSJVN +ndOObdFpGR57Px9IfMgNL8RTD4kj9L7qtHrG3cwYD+c06izUBw1leOggQD827ywA +MWnmIEhlvlcDDmn/uYNZmX1NhpgUW445JTqobVHcRaUPzfN6/VWvX1V1IAP1SnVq +eS92hPZOPR1ZRZqxaldvFnZ2+N9ultUlJzRLIdjJmjZVRaBDFkNokzevgYkG0VYb +ng9iQK08TPXvbKKkf/L6eJwNwBnxEOjYzwNnPC1N811nXEGnT9bFDv8sBN0ju4VE +jiWsFaOC+qRP+h2H8FjcrlMFuYHoy+UMrKV0aAP5IqBFtmJY4JjZjFSkIgN6NxLr +fbGtRWCOet8= -----END CERTIFICATE----- diff --git a/Lib/slapdtest/certs/gencerts.sh b/Lib/slapdtest/certs/gencerts.sh index 7a971a3a..8a99db58 100755 --- a/Lib/slapdtest/certs/gencerts.sh +++ b/Lib/slapdtest/certs/gencerts.sh @@ -29,7 +29,7 @@ openssl ca -selfsign \ -in $CATMPDIR/ca.csr \ -out $CAOUTDIR/ca.pem \ -extensions ca_ext \ - -days 3563 \ + -days 356300 \ -batch # server cert diff --git a/Lib/slapdtest/certs/server.key b/Lib/slapdtest/certs/server.key index a48ee567..a8916701 100644 --- a/Lib/slapdtest/certs/server.key +++ b/Lib/slapdtest/certs/server.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAgcI7Pj89Aw4r -rb+N8j3t1ynJgXRhQNxbxQcQmUCi8AtpGKNXu+aM9u2HxZ677ALfhsEivtQA5QKz -Ll5G2G2IQa7uzgIco73OL/kMZIJt7sKfnvfACtSoOlD0IyOzVEEu0AVA7hMHb6Ul -I0mCNfdk2FWFbc1nUmIAEBhIODbFoNW+Rc6lz94NsUqArDayvWpAsXkUbubQikpi -KNX1OpOOC9GUbEhwI/G90ZnUg9STk/2oxsvwLlYdOhGhpyfl53tTu7eLMBriMxFl -UTvkY1GUgPj0fA/giWtCerGOyeKu1hFlbS4LjborsfrlyYPwfwTg3YL4hVnt9fF0 -rpjzY1mlAgMBAAECggEAJY6rSEeiqtKXxynEv3rNXkOmIWwiOn8e/sB32mMr2x4d -+8kUxR8hocrjGKQTjfJDtTxjHdZBIlOLrU2UkxnSdMzrxidm/hNsCngNjL9nOu9k -BSRMjakPSCrodFkOtAPyG6H2BG7uQ3siqxYxVzgUJhaWyMtdUZUfDYgWVLCy7udU -5ML/OTOi7virueMmshjXoyrDug9OpiEMKiLu3ndAaDk/26m05ePAXB6TjW8SFw1B -qn7cITSG0G5MZ9pOw0KwT9irY1SdppBHVWIg7dkYWRCni0BPCFewastU+GVKH5PJ -+dYSvafhkEGD1bBu484KN9yX1BcHV41ZKR8pGgMM2QKBgQD3/0R2vZsTxoO1CHNI -IT7nBnuPIOP45iTFm/SNRY7e4dhQBy6HM6JD3Sr6Iksm8jRoboz+tnAso6l6QHRS -842uqBiOHdnka2RslDmrEun1lJv1MWuPM8JN0o8pYjVG/IRtaAFnYSEk72UoNy2h -bHC4OGFNwMbAadVm7DK5OiMfXwKBgQDGuBRxz7jkVZoMbbaeIqmGZAIejWkJweDZ -AK+txM+6Sg+Li14t190N3Xf6tyyidKhUAEWaINzLjZB+luxNaDXtxqWzLYHCwQKA -qfrjWVeZOS1clLya7jwl1jJqBtBiGKHv9eRL21hgX/9gX3odxqFMvX3vm6L7F1q1 -5CNApW0ZewKBgGO8qNcsWBLy8oM7G8n1fOvCwqyEaMrwG/fRSeALCnN+1tUQnljH -nkm2yBMC+cB3Bja9xzylOKXrSDyfcWjvBJsqhX2aacggnKnCTxMLL0aR9sr8jipw -gYN03Bijo5Oh+MxbWL0v5fmJweATmOljyE1+dzui/QvjRGz5L0kpJXj3AoGBAIa4 -3+t1B4WN312TuB4no8Tf4mvyNQcPcS/Nfk0RxD8o3Lcfal8sHMq8ng3Ux6bv7frd -IFLo+qfpts+L5HJqNz2X0ljSfkmZ7udp1hTySigwEmfU0rU61H5WZGFrczU+O/Ni -Qj+HWrgj/Q/KSxEKy+oqAcpDOtB+Odpc6+V1Aa0nAoGBAItWHP9UjTNFqOfyjZhG -qaUiZd1S2KyRR0l/lVcn+rJ46Yg5i+lMGwHMF1xPyWH4ELz+QCUX3doOI4yB2ikg -XXFcc8/bqgaR4AfOvP98T86s7+f33kaAKZsgyAFB2cjo+fz8ArTz+GjPeHbiOPaR -Ra7+BVwl9GE0+bCdirq+99GO +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsBk0ml3ERFJyg +I6ujIJYERVU4doTZZd4r4z/LOef0hyiYiIQAc9wetaoZpM+bl4Eherxy9SBaCBwR +zefbaYQz2f2hdEDb+sISOiTke1eiF2ugYNlS55Wk1KnCnORE9bjcSNLPsscoUSzE +2bnBSoUwdiVK18YOCZR6GTeC8eA3ekvlR+9g+FBOgQ9+StXPDdq+iIAGXZREJIua +munErtTOw85De4YFCnzGw3UeCITDD4wFmI2IWphRFwWPsSDwUJfATA8S+7Rm4vwr +Qj726gUDlicTzPXKhJjXjj6XL7xXHfpQwMPkBCrxesKceHMJ+mrRsuuqHciuixRi +g94mILElAgMBAAECggEADG5oJOHMye8zYl8xiBhSvvxDrFDkSNGTvJgvhAArQwCB +boRvBZlZzt5R7Ih8eEH6kvDLrYMJU3hCjwbSOojlhNm7+m7sQPleDPMmt1wyeQQ4 +Qt681cDmj4LOwcGUvWcEdObOVTQWMFOtaIxTYCSCe34OM9pj9Z+7mxc3a78O9PND +Ib/CwcTA1OyoupzkKirqkdLXwK3x2aT/1TMaPX94taHB51cxXc7AglL9QnuCkuaG +krqrexy3rGimzsP3OwQGEUjWKcZVSSPT8/k1pPE9hRgOqBy05BfkAzlebdvc3GO5 +AbZk0NX2sfVHl4dTEXs/hTBCTQ3XmaltumQ9MdL+AQKBgQDg2I5QxBA2UHb8vCtK +f31kfG6YQc4MkoslrrMrtJjZqDYaLZPS1ARPSfYRqcc+7GDreuLmw39f8ZECd+2W +BYUqzZv9g13R9DY99g0/sINnZGsESwfIdLNNlHvVx2UrD5ybCj4vLhuPsVV7XlWs +cpl+rcuBVpqy8UIXifQ/Z3xLvwKBgQDD3CLjuC0mcTO2sIWqEHqVkc8CY2NJA2Qh +C78fwpaCqJUUdWnS69QbRGWgkFJL+oO8lQVQ1bXhZLHyQmy7Z5d5olCH6AW4GRnf +hBAnKJ+QTm9B6QVWzjUuHuOeCukfiTQbha14pOS9ar3X2QFWjDnzCRrnAxJmoY3H +BJATLHhMGwKBgQDSxAy7xt4Pm+O9y8Gk5tcq771X+i9k96V54EZRzMuPFDAK3/h2 +o4marZD9Q7Hi2P+NHTc+67klvbKZpsPOYkRPOEdmH9M9cPe7oz8OGa9DpwzuDEsy +a7p8GZjvbyb1c3/wkWxzG3x4eNnReD9FFHOwHMfr6LvAy4iRuh57pM0NzwKBgDY3 +1DixnV4M7EHgb7/6O9T3vhRtKujlVWyIcen61etpe4tkTV0kB11c+70M9pstyBYG +MqiD4It6coAbvznJnXcAZcaZhivGVxE237nXVwR9kfLu7JlxD+uqhVwUrSAbvR75 +TGIfU2rUB6We3u30d349wQK+KPPcOQEk1DValBqNAoGBAKfXOXgFBkIVW79fOkup +aIZXdEmU3Up61Oo0KDbxsg4l73NnnvuEnNMBTx3nT3KCVIAcQL9MNpLX/Z0HjOn1 +aiWVtTNq2OFL0V0HueBhbkFiWp551jTS7LjndCYHpUB/B8/wXP0kxHUm8HrQrRvK +DhV3zcxsXts1INidXjzzOkPi -----END PRIVATE KEY----- diff --git a/Lib/slapdtest/certs/server.pem b/Lib/slapdtest/certs/server.pem index 7e750596..25ba06c0 100644 --- a/Lib/slapdtest/certs/server.pem +++ b/Lib/slapdtest/certs/server.pem @@ -5,31 +5,31 @@ Certificate: Signature Algorithm: sha256WithRSAEncryption Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA Validity - Not Before: Dec 2 11:57:48 2017 GMT - Not After : Dec 2 11:57:48 2027 GMT + Not Before: Apr 12 18:52:38 2019 GMT + Not After : Mar 1 18:52:38 3019 GMT Subject: C=DE, O=python-ldap, OU=slapd-test, CN=server cert for localhost Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:c0:81:c2:3b:3e:3f:3d:03:0e:2b:ad:bf:8d:f2: - 3d:ed:d7:29:c9:81:74:61:40:dc:5b:c5:07:10:99: - 40:a2:f0:0b:69:18:a3:57:bb:e6:8c:f6:ed:87:c5: - 9e:bb:ec:02:df:86:c1:22:be:d4:00:e5:02:b3:2e: - 5e:46:d8:6d:88:41:ae:ee:ce:02:1c:a3:bd:ce:2f: - f9:0c:64:82:6d:ee:c2:9f:9e:f7:c0:0a:d4:a8:3a: - 50:f4:23:23:b3:54:41:2e:d0:05:40:ee:13:07:6f: - a5:25:23:49:82:35:f7:64:d8:55:85:6d:cd:67:52: - 62:00:10:18:48:38:36:c5:a0:d5:be:45:ce:a5:cf: - de:0d:b1:4a:80:ac:36:b2:bd:6a:40:b1:79:14:6e: - e6:d0:8a:4a:62:28:d5:f5:3a:93:8e:0b:d1:94:6c: - 48:70:23:f1:bd:d1:99:d4:83:d4:93:93:fd:a8:c6: - cb:f0:2e:56:1d:3a:11:a1:a7:27:e5:e7:7b:53:bb: - b7:8b:30:1a:e2:33:11:65:51:3b:e4:63:51:94:80: - f8:f4:7c:0f:e0:89:6b:42:7a:b1:8e:c9:e2:ae:d6: - 11:65:6d:2e:0b:8d:ba:2b:b1:fa:e5:c9:83:f0:7f: - 04:e0:dd:82:f8:85:59:ed:f5:f1:74:ae:98:f3:63: - 59:a5 + 00:ac:06:4d:26:97:71:11:14:9c:a0:23:ab:a3:20: + 96:04:45:55:38:76:84:d9:65:de:2b:e3:3f:cb:39: + e7:f4:87:28:98:88:84:00:73:dc:1e:b5:aa:19:a4: + cf:9b:97:81:21:7a:bc:72:f5:20:5a:08:1c:11:cd: + e7:db:69:84:33:d9:fd:a1:74:40:db:fa:c2:12:3a: + 24:e4:7b:57:a2:17:6b:a0:60:d9:52:e7:95:a4:d4: + a9:c2:9c:e4:44:f5:b8:dc:48:d2:cf:b2:c7:28:51: + 2c:c4:d9:b9:c1:4a:85:30:76:25:4a:d7:c6:0e:09: + 94:7a:19:37:82:f1:e0:37:7a:4b:e5:47:ef:60:f8: + 50:4e:81:0f:7e:4a:d5:cf:0d:da:be:88:80:06:5d: + 94:44:24:8b:9a:9a:e9:c4:ae:d4:ce:c3:ce:43:7b: + 86:05:0a:7c:c6:c3:75:1e:08:84:c3:0f:8c:05:98: + 8d:88:5a:98:51:17:05:8f:b1:20:f0:50:97:c0:4c: + 0f:12:fb:b4:66:e2:fc:2b:42:3e:f6:ea:05:03:96: + 27:13:cc:f5:ca:84:98:d7:8e:3e:97:2f:bc:57:1d: + fa:50:c0:c3:e4:04:2a:f1:7a:c2:9c:78:73:09:fa: + 6a:d1:b2:eb:aa:1d:c8:ae:8b:14:62:83:de:26:20: + b1:25 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical @@ -39,48 +39,48 @@ Certificate: X509v3 Extended Key Usage: critical TLS Web Server Authentication X509v3 Subject Key Identifier: - 1B:78:45:40:0D:50:8A:8B:3B:C1:0A:F8:3F:7A:48:7B:A6:3C:28:09 + 08:D1:86:1B:82:0A:4F:71:31:E4:F5:31:23:CC:67:3B:FA:84:3B:A0 X509v3 Authority Key Identifier: - keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 + keyid:BD:78:D5:4A:F1:90:96:C5:E8:EC:66:49:23:47:03:5F:26:73:86:B2 X509v3 Subject Alternative Name: DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 Signature Algorithm: sha256WithRSAEncryption - ad:08:3f:7d:b1:09:a1:a5:6c:c3:58:80:1d:e5:33:a5:bb:c0: - 33:39:95:aa:88:ee:c4:8e:38:3b:59:a7:0e:39:74:6c:fe:11: - 33:5e:fa:50:cb:20:4b:67:b7:c9:5e:96:a7:9e:d8:47:46:e1: - ab:fe:5d:8b:9a:2d:1a:1b:43:08:f9:93:0f:2a:e3:ce:83:4a: - 94:cd:02:f0:8e:25:f2:41:0d:55:10:f5:4c:5b:39:8b:77:5e: - ab:78:16:64:a1:48:d5:e1:f6:69:9a:0f:d8:30:a6:cc:92:4d: - 81:df:46:74:ab:cf:1d:b7:d4:01:b9:6d:d5:f4:14:b8:d5:54: - 84:79:11:42:69:55:7f:74:ce:01:96:2f:3f:51:23:b3:11:fb: - 72:dc:4c:b9:a3:89:ef:31:e4:c0:49:06:fa:8d:09:71:e1:c1: - 74:a9:ed:f8:96:87:67:16:b5:5d:16:5d:59:70:ff:1c:b5:a1: - 6c:d2:22:11:3a:0e:6f:76:9b:69:cb:f3:85:a7:79:ad:53:f5: - 34:e8:87:cc:dd:09:51:25:e0:28:ee:79:a0:a3:dc:0a:dd:f0: - 1b:e3:c9:5f:14:d3:95:f5:12:4d:23:95:45:2c:3c:32:94:ad: - ce:1e:a0:5f:e6:e8:28:c6:f9:c7:fb:57:06:ad:0b:eb:86:ca: - 0e:d2:a8:67 + 88:60:af:be:11:c4:aa:dc:9b:f1:e7:14:da:20:aa:6f:2f:06: + ae:38:b2:7c:ac:90:81:22:51:7e:cb:26:15:6e:fe:67:98:c1: + 0d:dc:aa:39:98:2b:d2:cc:3c:ff:1a:92:2f:56:0a:a9:6e:d8: + 9a:3d:c5:4d:6f:cc:91:2e:e3:4e:bf:22:ab:cb:92:1a:a0:8f: + 43:cd:82:bc:48:55:c4:95:cf:10:6b:6a:31:19:92:7d:e0:06: + 05:6f:0b:33:e7:2a:37:42:f9:ec:1b:29:99:e1:58:0c:01:a7: + c3:8b:58:71:21:9f:61:8c:a7:fb:b6:7e:32:8b:a9:4e:c7:1f: + f6:46:e8:dd:ac:a6:4c:53:f8:4d:93:e4:ec:73:ab:0b:be:98: + c5:78:c4:92:c0:4c:78:47:52:2f:93:07:67:20:a4:5a:7f:59: + 7e:4f:48:53:20:0d:37:bb:06:f8:44:42:64:b4:94:15:43:d1: + 4c:51:f3:97:1d:2d:cd:db:b9:bb:1a:69:10:89:7d:ae:1d:0d: + 94:78:45:29:cd:c4:42:67:67:96:05:bf:da:aa:23:65:7b:04: + ff:b7:ac:9d:ee:0b:e7:0f:c1:c5:0b:48:fe:0f:d6:3f:d8:b4: + 77:12:bb:f5:91:4f:43:e6:01:3f:a4:c0:ea:8c:c6:68:99:8e: + 49:e8:c4:8b -----BEGIN CERTIFICATE----- -MIID1TCCAr2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MIID1zCCAr+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy -MTE1NzQ4WjBcMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR -BgNVBAsMCnNsYXBkLXRlc3QxIjAgBgNVBAMMGXNlcnZlciBjZXJ0IGZvciBsb2Nh -bGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAgcI7Pj89Aw4r -rb+N8j3t1ynJgXRhQNxbxQcQmUCi8AtpGKNXu+aM9u2HxZ677ALfhsEivtQA5QKz -Ll5G2G2IQa7uzgIco73OL/kMZIJt7sKfnvfACtSoOlD0IyOzVEEu0AVA7hMHb6Ul -I0mCNfdk2FWFbc1nUmIAEBhIODbFoNW+Rc6lz94NsUqArDayvWpAsXkUbubQikpi -KNX1OpOOC9GUbEhwI/G90ZnUg9STk/2oxsvwLlYdOhGhpyfl53tTu7eLMBriMxFl -UTvkY1GUgPj0fA/giWtCerGOyeKu1hFlbS4LjborsfrlyYPwfwTg3YL4hVnt9fF0 -rpjzY1mlAgMBAAGjgacwgaQwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw -FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFBt4RUANUIqLO8EK+D96 -SHumPCgJMB8GA1UdIwQYMBaAFDsfMvT+V9FvSZFV8iTxCmY7pe7UMCwGA1UdEQQl -MCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B -AQsFAAOCAQEArQg/fbEJoaVsw1iAHeUzpbvAMzmVqojuxI44O1mnDjl0bP4RM176 -UMsgS2e3yV6Wp57YR0bhq/5di5otGhtDCPmTDyrjzoNKlM0C8I4l8kENVRD1TFs5 -i3deq3gWZKFI1eH2aZoP2DCmzJJNgd9GdKvPHbfUAblt1fQUuNVUhHkRQmlVf3TO -AZYvP1EjsxH7ctxMuaOJ7zHkwEkG+o0JceHBdKnt+JaHZxa1XRZdWXD/HLWhbNIi -EToOb3abacvzhad5rVP1NOiHzN0JUSXgKO55oKPcCt3wG+PJXxTTlfUSTSOVRSw8 -MpStzh6gX+boKMb5x/tXBq0L64bKDtKoZw== +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwIBcNMTkwNDEyMTg1MjM4WhgPMzAxOTAz +MDExODUyMzhaMFwxCzAJBgNVBAYTAkRFMRQwEgYDVQQKDAtweXRob24tbGRhcDET +MBEGA1UECwwKc2xhcGQtdGVzdDEiMCAGA1UEAwwZc2VydmVyIGNlcnQgZm9yIGxv +Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKwGTSaXcREU +nKAjq6MglgRFVTh2hNll3ivjP8s55/SHKJiIhABz3B61qhmkz5uXgSF6vHL1IFoI +HBHN59tphDPZ/aF0QNv6whI6JOR7V6IXa6Bg2VLnlaTUqcKc5ET1uNxI0s+yxyhR +LMTZucFKhTB2JUrXxg4JlHoZN4Lx4Dd6S+VH72D4UE6BD35K1c8N2r6IgAZdlEQk +i5qa6cSu1M7DzkN7hgUKfMbDdR4IhMMPjAWYjYhamFEXBY+xIPBQl8BMDxL7tGbi +/CtCPvbqBQOWJxPM9cqEmNeOPpcvvFcd+lDAw+QEKvF6wpx4cwn6atGy66odyK6L +FGKD3iYgsSUCAwEAAaOBpzCBpDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF +oDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUCNGGG4IKT3Ex5PUx +I8xnO/qEO6AwHwYDVR0jBBgwFoAUvXjVSvGQlsXo7GZJI0cDXyZzhrIwLAYDVR0R +BCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3 +DQEBCwUAA4IBAQCIYK++EcSq3Jvx5xTaIKpvLwauOLJ8rJCBIlF+yyYVbv5nmMEN +3Ko5mCvSzDz/GpIvVgqpbtiaPcVNb8yRLuNOvyKry5IaoI9DzYK8SFXElc8Qa2ox +GZJ94AYFbwsz5yo3QvnsGymZ4VgMAafDi1hxIZ9hjKf7tn4yi6lOxx/2RujdrKZM +U/hNk+Tsc6sLvpjFeMSSwEx4R1IvkwdnIKRaf1l+T0hTIA03uwb4REJktJQVQ9FM +UfOXHS3N27m7GmkQiX2uHQ2UeEUpzcRCZ2eWBb/aqiNlewT/t6yd7gvnD8HFC0j+ +D9Y/2LR3Erv1kU9D5gE/pMDqjMZomY5J6MSL -----END CERTIFICATE----- From f4059c4164a8c7c760e0f235471a3282587e75e3 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 30 Jan 2019 13:55:02 +0100 Subject: [PATCH 070/206] Prefer iterating dict instead of calling dict.keys() Calling dict.keys() is unnecessary. iter(dict) is equivalent to dict.keys(). Inspired by Lennart Regebro's talk "Prehistoric Patterns in Python" from PyCon 2017. https://www.youtube.com/watch?v=V5-JH23Vk0I --- Lib/ldap/modlist.py | 2 +- Lib/ldap/schema/subentry.py | 12 ++++++------ Tests/t_ldap_syncrepl.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 4acf4e9f..bf4e4819 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -50,7 +50,7 @@ def modifyModlist( case_ignore_attr_types = {v.lower() for v in case_ignore_attr_types or []} modlist = [] attrtype_lower_map = {} - for a in old_entry.keys(): + for a in old_entry: attrtype_lower_map[a.lower()]=a for attrtype, value in new_entry.items(): attrtype_lower = attrtype.lower() diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 5ccbce05..215f148e 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -23,7 +23,7 @@ SCHEMA_CLASS_MAPPING[o.schema_attribute] = o SCHEMA_ATTR_MAPPING[o] = o.schema_attribute -SCHEMA_ATTRS = SCHEMA_CLASS_MAPPING.keys() +SCHEMA_ATTRS = list(SCHEMA_CLASS_MAPPING) class SubschemaError(ValueError): @@ -122,7 +122,7 @@ def __init__(self,sub_schema_sub_entry,check_uniqueness=1): self.sed[se_class][se_id] = se_instance if hasattr(se_instance,'names'): - for name in ldap.cidict.cidict({}.fromkeys(se_instance.names)).keys(): + for name in ldap.cidict.cidict({}.fromkeys(se_instance.names)): if check_uniqueness and name in self.name2oid[se_class]: self.non_unique_names[se_class][se_id] = None raise NameNotUnique(attr_value) @@ -130,7 +130,7 @@ def __init__(self,sub_schema_sub_entry,check_uniqueness=1): self.name2oid[se_class][name] = se_id # Turn dict into list maybe more handy for applications - self.non_unique_oids = self.non_unique_oids.keys() + self.non_unique_oids = list(self.non_unique_oids) return # subSchema.__init__() @@ -168,7 +168,7 @@ def listall(self,schema_element_class,schema_element_filters=None): except AttributeError: pass else: - result = avail_se.keys() + result = list(avail_se) return result @@ -422,14 +422,14 @@ def attribute_types( # Remove all mandantory attribute types from # optional attribute type list - for a in list(r_may.keys()): + for a in list(r_may): if a in r_must: del r_may[a] # Apply attr_type_filter to results if attr_type_filter: for l in [r_must,r_may]: - for a in list(l.keys()): + for a in list(l): for afk,afv in attr_type_filter: try: schema_attr_type = self.sed[AttributeType][a] diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 73ba1fb1..9398de5b 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -242,7 +242,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): elif (uuids is None) and (refreshDeletes is False): deleted_uuids = [] - for uuid in self.uuid_dn.keys(): + for uuid in self.uuid_dn: if uuid not in self.present: deleted_uuids.append(uuid) From e6b6e8e3ce3a715fbb004f9f7e381a88a1af6ff7 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 5 Apr 2018 09:56:49 +0200 Subject: [PATCH 071/206] Release GIL around global get/set option call ldap.set_option() and ldap.get_option() now properly release the GIL around potentially blocking calls. Also fixes a problem with make indent by replacing Py_BEGIN/END_ALLOW_THREADS with PyEval_SaveThread / PyEval_RestoreThread function calls. python-ldap requires threading support any way. Fixes: https://github.com/python-ldap/python-ldap/issues/85 Signed-off-by: Christian Heimes --- Modules/functions.c | 14 ++++++--- Modules/ldapcontrol.c | 25 +++++++++------ Modules/options.c | 71 ++++++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 48 deletions(-) diff --git a/Modules/functions.c b/Modules/functions.c index 4731efb8..3a43c7c2 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -15,13 +15,18 @@ l_ldap_initialize(PyObject *unused, PyObject *args) char *uri; LDAP *ld = NULL; int ret; + PyThreadState *save; if (!PyArg_ParseTuple(args, "s:initialize", &uri)) return NULL; - Py_BEGIN_ALLOW_THREADS ret = ldap_initialize(&ld, uri); - Py_END_ALLOW_THREADS if (ret != LDAP_SUCCESS) + save = PyEval_SaveThread(); + ret = ldap_initialize(&ld, uri); + PyEval_RestoreThread(save); + + if (ret != LDAP_SUCCESS) return LDAPerror(ld, "ldap_initialize"); + return (PyObject *)newLDAPObject(ld); } @@ -75,9 +80,8 @@ l_ldap_str2dn(PyObject *unused, PyObject *args) tuple = Py_BuildValue("(O&O&i)", LDAPberval_to_unicode_object, &ava->la_attr, LDAPberval_to_unicode_object, &ava->la_value, - ava-> - la_flags & ~(LDAP_AVA_FREE_ATTR | - LDAP_AVA_FREE_VALUE)); + ava->la_flags & ~(LDAP_AVA_FREE_ATTR | + LDAP_AVA_FREE_VALUE)); if (!tuple) { Py_DECREF(rdnlist); goto failed; diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index f53e681a..66155984 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -339,6 +339,7 @@ encode_assertion_control(PyObject *self, PyObject *args) char *assertion_filterstr; struct berval ctrl_val; LDAP *ld = NULL; + PyThreadState *save; if (!PyArg_ParseTuple(args, "s:encode_assertion_control", &assertion_filterstr)) { @@ -348,21 +349,27 @@ encode_assertion_control(PyObject *self, PyObject *args) /* XXX: ldap_create() is a nasty and slow hack. It's creating a full blown * LDAP object just to encode assertion controls. */ - Py_BEGIN_ALLOW_THREADS err = ldap_create(&ld); - Py_END_ALLOW_THREADS if (err != LDAP_SUCCESS) + save = PyEval_SaveThread(); + err = ldap_create(&ld); + PyEval_RestoreThread(save); + if (err != LDAP_SUCCESS) return LDAPerror(ld, "ldap_create"); - err = - ldap_create_assertion_control_value(ld, assertion_filterstr, - &ctrl_val); + err = ldap_create_assertion_control_value(ld, assertion_filterstr, + &ctrl_val); if (err != LDAP_SUCCESS) { LDAPerror(ld, "ldap_create_assertion_control_value"); - Py_BEGIN_ALLOW_THREADS ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS return NULL; + save = PyEval_SaveThread(); + ldap_unbind_ext(ld, NULL, NULL); + PyEval_RestoreThread(save); + return NULL; } - Py_BEGIN_ALLOW_THREADS ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS res = LDAPberval_to_object(&ctrl_val); + save = PyEval_SaveThread(); + ldap_unbind_ext(ld, NULL, NULL); + PyEval_RestoreThread(save); + res = LDAPberval_to_object(&ctrl_val); + if (ctrl_val.bv_val != NULL) { ber_memfree(ctrl_val.bv_val); } diff --git a/Modules/options.c b/Modules/options.c index 85560e62..549a6726 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -185,11 +185,18 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 0; } - if (self) + if (self) { LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_set_option(ld, option, ptr); - if (self) + res = ldap_set_option(ld, option, ptr); LDAP_END_ALLOW_THREADS(self); + } + else { + PyThreadState *save; + + save = PyEval_SaveThread(); + res = ldap_set_option(NULL, option, ptr); + PyEval_RestoreThread(save); + } if ((option == LDAP_OPT_SERVER_CONTROLS) || (option == LDAP_OPT_CLIENT_CONTROLS)) @@ -203,6 +210,26 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 1; } +static int +LDAP_int_get_option(LDAPObject *self, int option, void *value) +{ + int res; + + if (self != NULL) { + LDAP_BEGIN_ALLOW_THREADS(self); + res = ldap_get_option(self->ldap, option, value); + LDAP_END_ALLOW_THREADS(self); + } + else { + PyThreadState *save; + + save = PyEval_SaveThread(); + res = ldap_get_option(NULL, option, value); + PyEval_RestoreThread(save); + } + return res; +} + PyObject * LDAP_get_option(LDAPObject *self, int option) { @@ -214,18 +241,11 @@ LDAP_get_option(LDAPObject *self, int option) char *strval; PyObject *extensions, *v; Py_ssize_t i, num_extensions; - LDAP *ld; - - ld = self ? self->ldap : NULL; switch (option) { case LDAP_OPT_API_INFO: apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION; - if (self) - LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &apiinfo); - if (self) - LDAP_END_ALLOW_THREADS(self); + res = LDAP_int_get_option(self, option, &apiinfo); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); @@ -236,8 +256,8 @@ LDAP_get_option(LDAPObject *self, int option) extensions = PyTuple_New(num_extensions); for (i = 0; i < num_extensions; i++) PyTuple_SET_ITEM(extensions, i, - PyUnicode_FromString(apiinfo. - ldapai_extensions[i])); + PyUnicode_FromString(apiinfo.ldapai_extensions + [i])); /* return api info as a dictionary */ v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}", @@ -299,11 +319,7 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_X_KEEPALIVE_INTERVAL: #endif /* Integer-valued options */ - if (self) - LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &intval); - if (self) - LDAP_END_ALLOW_THREADS(self); + res = LDAP_int_get_option(self, option, &intval); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); return PyInt_FromLong(intval); @@ -347,11 +363,7 @@ LDAP_get_option(LDAPObject *self, int option) #endif #endif /* String-valued options */ - if (self) - LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &strval); - if (self) - LDAP_END_ALLOW_THREADS(self); + res = LDAP_int_get_option(self, option, &strval); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); if (strval == NULL) { @@ -365,11 +377,7 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Double-valued timeval options */ - if (self) - LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &tv); - if (self) - LDAP_END_ALLOW_THREADS(self); + res = LDAP_int_get_option(self, option, &tv); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); if (tv == NULL) { @@ -384,12 +392,7 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_SERVER_CONTROLS: case LDAP_OPT_CLIENT_CONTROLS: - if (self) - LDAP_BEGIN_ALLOW_THREADS(self); - res = ldap_get_option(ld, option, &lcs); - if (self) - LDAP_END_ALLOW_THREADS(self); - + res = LDAP_int_get_option(self, option, &lcs); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); From 0f1d8a84c0f30a4288d720ed8f3f77c486cd8385 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 30 Jan 2019 16:27:04 +0100 Subject: [PATCH 072/206] Doc: Use the full module in ldap.ldapobject.LDAPObject Previously, the class showed up as `ldap.LDAPObject`, which is wrong. Fixes: https://github.com/python-ldap/python-ldap/issues/256 --- Doc/reference/ldap.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 69e24629..1117c078 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -579,7 +579,7 @@ Warnings LDAPObject classes ================== -.. py:class:: LDAPObject +.. py:class:: ldap.ldapobject.LDAPObject Instances of :py:class:`LDAPObject` are returned by :py:func:`initialize()`. The connection is automatically unbound From 43391694378053ea62dad559c27465c0d3373f43 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 30 Jan 2019 17:42:30 +0100 Subject: [PATCH 073/206] Doc: Clarify the relationship between initialize() and LDAPObject() Originally, `ldap.ldapobject.LDAPObject` was meant as a read/write alias of the "default" connection class. However, setting module attributes is bad practice (action at distance): it affects all users of python-ldap, some of which might not be aware of each other. Clarify that: * `initialize()` is a thin wrapper around calling `LDAPObject()`, * it is fine and recommnended to instantiate LDAPObject-like classes directly, and * it is possible, but not recommended, to set `ldap.ldapobject.LDAPObject` to another class. --- Doc/reference/ldap.rst | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1117c078..61f16c7b 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -32,7 +32,7 @@ This module defines the following functions: .. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None]]]]]) -> LDAPObject object Initializes a new connection object for accessing the given LDAP server, - and return an LDAP object (see :ref:`ldap-objects`) used to perform operations + and return an :class:`~ldap.ldapobject.LDAPObject` used to perform operations on that server. The *uri* parameter may be a comma- or whitespace-separated list of URIs @@ -63,7 +63,10 @@ This module defines the following functions: :py:const:`2` for logging the method calls with arguments and the complete results and :py:const:`9` for also logging the traceback of method calls. - Additional keyword arguments are passed to :class:`LDAPObject`. + This function is a thin wrapper around instantiating + :class:`~ldap.ldapobject.LDAPObject`. + Any additional keyword arguments are passed to ``LDAPObject``. + It is also fine to instantiate a ``LDAPObject`` (or a subclass) directly. .. seealso:: @@ -585,8 +588,13 @@ LDAPObject classes The connection is automatically unbound and closed when the LDAP object is deleted. - Internally :py:class:`LDAPObject` is set to - :py:class:`~ldap.ldapobject.SimpleLDAPObject` by default. + :py:class:`LDAPObject` is an alias of + :py:class:`~ldap.ldapobject.SimpleLDAPObject`, the default connection class. + If you wish to use a different class, instantiate it directly instead of + calling :func:`initialize()`. + + (It is also possible, but not recommended, to change the default by setting + ``ldap.ldapobject.LDAPObject`` to a different class.) .. autoclass:: ldap.ldapobject.SimpleLDAPObject From da432d727138c315846fec5f271f065d7b4bc15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 09:40:38 +0100 Subject: [PATCH 074/206] Do not leak serverctrls --- Modules/LDAPObject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index bc26727e..ed4df4c0 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1171,6 +1171,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) } else e = "ldap_parse_result"; + ldap_controls_free(serverctrls); ldap_msgfree(msg); Py_XDECREF(valuestr); return LDAPerror(self->ldap, e); @@ -1182,6 +1183,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) LDAP_BEGIN_ALLOW_THREADS(self); ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &err); LDAP_END_ALLOW_THREADS(self); + ldap_controls_free(serverctrls); ldap_msgfree(msg); Py_XDECREF(valuestr); return LDAPerror(self->ldap, "LDAPControls_to_List"); From 689f7df6146ed2ecf6e7535c1cbc8643fc51ae15 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 10 May 2019 16:24:29 +0200 Subject: [PATCH 075/206] Test if reconnection is done after connection loss --- Tests/t_ldapobject.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 67adeb25..36e2acfc 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -736,6 +736,23 @@ def test104_reconnect_restore(self): l2 = pickle.loads(l1_state) self.assertEqual(l2.whoami_s(), 'dn:'+bind_dn) + def test105_reconnect_restore(self): + l1 = self.ldap_object_class(self.server.ldap_uri, retry_max=2, retry_delay=1) + bind_dn = 'cn=user1,'+self.server.suffix + l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + self.server._proc.terminate() + self.server.wait() + try: + l1.whoami_s() + except ldap.SERVER_DOWN: + pass + else: + self.assertEqual(True, False) + finally: + self.server._start_slapd() + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + if __name__ == '__main__': unittest.main() From daf266a28ec3720d054aafddad3274ad8740cd46 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 10 May 2019 13:48:51 +0200 Subject: [PATCH 076/206] Ensure self._l is not left in an only partially initialized state If the timeout is reached and a reconnection was not successfull in that time, a ldap.SERVER_DOWN exception is raised. If later on, when it's assured that the ldap server is running again, the connection is used again, the reconnection is not performed and the ldap connection in an incosistent unbind state. Traceback (most recent call last): File "reproduce.py", line 23, in _ = lo.search_s('l=school,l=dev', ldap.SCOPE_SUBTREE, '(uid=Administrator)') File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 597, in search_s return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 997, in search_ext_s return self._apply_method_s(SimpleLDAPObject.search_ext_s,*args,**kwargs) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 935, in _apply_method_s return func(self,*args,**kwargs) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 591, in search_ext_s return self.result(msgid,all=1,timeout=timeout)[1] File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 503, in result resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 507, in result2 resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 514, in result3 resp_ctrl_classes=resp_ctrl_classes File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 521, in result4 ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop) File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 106, in _ldap_call result = func(*args,**kwargs) ldap.INSUFFICIENT_ACCESS: {'desc': 'Insufficient access'} --- Lib/ldap/ldapobject.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index f7443fad..a92b0886 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -1166,14 +1166,18 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): counter_text,uri )) try: - # Do the connect - self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) - self._restore_options() - # StartTLS extended operation in case this was called before - if self._start_tls: - SimpleLDAPObject.start_tls_s(self) - # Repeat last simple or SASL bind - self._apply_last_bind() + try: + # Do the connect + self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) + self._restore_options() + # StartTLS extended operation in case this was called before + if self._start_tls: + SimpleLDAPObject.start_tls_s(self) + # Repeat last simple or SASL bind + self._apply_last_bind() + except ldap.LDAPError: + SimpleLDAPObject.unbind_s(self) + raise except (ldap.SERVER_DOWN,ldap.TIMEOUT): if __debug__ and self._trace_level>=1: self._trace_file.write('*** %s reconnect to %s failed\n' % ( @@ -1185,7 +1189,6 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): if __debug__ and self._trace_level>=1: self._trace_file.write('=> delay %s...\n' % (retry_delay)) time.sleep(retry_delay) - SimpleLDAPObject.unbind_s(self) else: if __debug__ and self._trace_level>=1: self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % ( From 532ffc0ae16733f8b384f9e544016e9be6319ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 11:00:22 +0100 Subject: [PATCH 077/206] Use slapd -Tt instead of slaptest If we point SBIN to $openldap_source/servers/slapd, libtool scripts will munge argv[0] and slapd won't recognise it's slaptest we asked for. So just request a slapd tool directly. --- Lib/slapdtest/_slapdtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index f1885caf..2f932bc7 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -252,7 +252,6 @@ def _find_commands(self): self.PATH_SLAPD = os.environ.get('SLAPD', None) if not self.PATH_SLAPD: self.PATH_SLAPD = self._find_command('slapd', in_sbin=True) - self.PATH_SLAPTEST = self._find_command('slaptest', in_sbin=True) def _find_command(self, cmd, in_sbin=False): if in_sbin: @@ -378,7 +377,8 @@ def _write_config(self): def _test_config(self): self._log.debug('testing config %s', self._slapd_conf) popen_list = [ - self.PATH_SLAPTEST, + self.PATH_SLAPD, + '-Ttest', "-f", self._slapd_conf, '-u', ] From 2b132f1436d42454526f26fd23081d1ffa3317ba Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Jan 2020 15:08:00 +0100 Subject: [PATCH 078/206] ldap.dn.is_dn: Fix typo in docstring https://github.com/python-ldap/python-ldap/pull/303 Suggested in https://github.com/python-ldap/python-ldap/pull/272 Co-authored-by: Florian Best --- Lib/ldap/dn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index cabfed84..a066ac19 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -111,7 +111,7 @@ def explode_rdn(rdn, notypes=False, flags=0): def is_dn(s,flags=0): """ - Returns True is `s' can be parsed by ldap.dn.str2dn() like as a + Returns True if `s' can be parsed by ldap.dn.str2dn() as a distinguished host_name (DN), otherwise False is returned. """ try: From 9cd5d9855ae4bd1ef850c0aa3afa9fcbe7b086a7 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Sun, 8 Dec 2019 20:19:15 +0100 Subject: [PATCH 079/206] Fix typo in documentation --- Doc/bytes_mode.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index dcd3dcb2..c9994210 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -55,7 +55,7 @@ argument to :func:`ldap.initialize`: Text values are represented as ``unicode``. If not given explicitly, python-ldap will default to ``bytes_mode=True``, -but if an ``unicode`` value supplied to it, if will warn and use that value. +but if an ``unicode`` value supplied to it, it will warn and use that value. Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. From 860ea96ff966058467ae9970e6e42d2ea3a4ea30 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Jan 2020 16:49:57 +0100 Subject: [PATCH 080/206] Test on Python 3.8 and 3.9-dev --- .travis.yml | 11 +++++++++-- setup.py | 1 + tox.ini | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81466393..8e0e3a28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,13 +39,20 @@ matrix: - WITH_GCOV=1 dist: xenial sudo: true - - python: 3.8-dev + - python: 3.8 env: - TOXENV=py38 - CFLAGS_std="-std=c99" - WITH_GCOV=1 dist: xenial sudo: true + - python: 3.9-dev + env: + - TOXENV=py39 + - CFLAGS_std="-std=c99" + - WITH_GCOV=1 + dist: xenial + sudo: true - python: 2.7 env: - TOXENV=py2-nosasltls @@ -61,7 +68,7 @@ matrix: env: TOXENV=doc allow_failures: - env: - - TOXENV=py38 + - TOXENV=py39 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - env: diff --git a/setup.py b/setup.py index e66ecbd6..69747853 100644 --- a/setup.py +++ b/setup.py @@ -95,6 +95,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', # Note: when updating Python versions, also change .travis.yml and tox.ini 'Topic :: Database', diff --git a/tox.ini b/tox.ini index 1434ba01..be723176 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,py37,py38,{py2,py3}-nosasltls,doc,py3-trace,coverage-report +envlist = py27,py34,py35,py36,py37,py38,py39,{py2,py3}-nosasltls,doc,py3-trace,coverage-report minver = 1.8 [testenv] From 6fb4ea430e29ea5e93251458b6c926c0619ea154 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 18 Feb 2020 09:44:40 +0000 Subject: [PATCH 081/206] fix grammar in docs --- Doc/bytes_mode.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index c9994210..38f9e730 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -55,7 +55,7 @@ argument to :func:`ldap.initialize`: Text values are represented as ``unicode``. If not given explicitly, python-ldap will default to ``bytes_mode=True``, -but if an ``unicode`` value supplied to it, it will warn and use that value. +but if an ``unicode`` value is supplied to it, it will warn and use that value. Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. From 9eb8b95a68168f04a72af5cf8db4c57338cf1836 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 18 Feb 2020 09:51:32 +0000 Subject: [PATCH 082/206] Update Doc/bytes_mode.rst --- Doc/bytes_mode.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index 38f9e730..0d207457 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -55,7 +55,7 @@ argument to :func:`ldap.initialize`: Text values are represented as ``unicode``. If not given explicitly, python-ldap will default to ``bytes_mode=True``, -but if an ``unicode`` value is supplied to it, it will warn and use that value. +but if a ``unicode`` value is supplied to it, it will warn and use that value. Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. From 7196171da7113aa12723db91003cc0424f8d0eaf Mon Sep 17 00:00:00 2001 From: johnthagen Date: Tue, 25 Feb 2020 07:07:55 -0500 Subject: [PATCH 083/206] Add LICENSE file metadata to package distributions Add LICENSE file metadata to package distributions https://github.com/python-ldap/python-ldap/pull/315 Co-authored-by: Petr Viktorin --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index c6546068..01d43a06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,7 @@ +# Package metadata (for Setuptools 30.3 and later) +[metadata] +license_file = LICENCE + # Example for setup.cfg # You have to edit this file to reflect your system configuation From 9005ba96eceed0e5712a0824cc707b98310cc483 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 6 May 2020 14:57:42 +0200 Subject: [PATCH 084/206] Doc: Use bytes for attribute values in example https://github.com/python-ldap/python-ldap/pull/317 Fixes: https://github.com/python-ldap/python-ldap/issues/306 --- Doc/reference/ldif.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldif.rst b/Doc/reference/ldif.rst index c508f7dc..87dcb70b 100644 --- a/Doc/reference/ldif.rst +++ b/Doc/reference/ldif.rst @@ -61,11 +61,11 @@ Example The following example demonstrates how to write LDIF output of an LDAP entry with :mod:`ldif` module. ->>> import sys,ldif ->>> entry={'objectClass':['top','person'],'cn':['Michael Stroeder'],'sn':['Stroeder']} +>>> import sys, ldif +>>> entry={'objectClass': [b'top', b'person'], 'cn': [b'Michael Stroeder'], 'sn': [b'Stroeder']} >>> dn='cn=Michael Stroeder,ou=Test' >>> ldif_writer=ldif.LDIFWriter(sys.stdout) ->>> ldif_writer.unparse(dn,entry) +>>> ldif_writer.unparse(dn, entry) dn: cn=Michael Stroeder,ou=Test cn: Michael Stroeder objectClass: top From 8dd59eaffa696bcf8bc70c259db87f65c6500bce Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 6 May 2020 15:55:18 +0200 Subject: [PATCH 085/206] Doc: Remove duplicate entry for NO_OBJECT_CLASS_MODS This fixes the build with newer versions of Sphinx. https://github.com/python-ldap/python-ldap/pull/330 --- Doc/reference/ldap.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 61f16c7b..32543750 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -458,10 +458,6 @@ The module defines the following exceptions: .. py:exception:: NO_MEMORY -.. py:exception:: NO_OBJECT_CLASS_MODS - - Object class modifications are not allowed. - .. py:exception:: NO_RESULTS_RETURNED .. py:exception:: NO_SUCH_ATTRIBUTE From 7e084aec1ba9ced25b44fd3db77e65242a827806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 6 May 2020 15:48:46 +0100 Subject: [PATCH 086/206] Get rid of expected failures in tokenizer tests https://github.com/python-ldap/python-ldap/pull/283 --- Lib/ldap/schema/tokenizer.py | 10 +++++++--- Tests/t_ldap_schema_tokenizer.py | 6 ++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index 20958c09..69823f2b 100644 --- a/Lib/ldap/schema/tokenizer.py +++ b/Lib/ldap/schema/tokenizer.py @@ -13,12 +13,16 @@ r"|" # or r"([^'$()\s]+)" # string of length >= 1 without '$() or whitespace r"|" # or - r"('.*?'(?!\w))" # any string or empty string surrounded by single quotes - # except if right quote is succeeded by alphanumeric char + r"('(?:[^'\\]|\\\\|\\.)*?'(?!\w))" + # any string or empty string surrounded by unescaped + # single quotes except if right quote is succeeded by + # alphanumeric char r"|" # or r"([^\s]+?)", # residue, all non-whitespace strings ).findall +UNESCAPE_PATTERN = re.compile(r"\\(.)") + def split_tokens(s): """ @@ -30,7 +34,7 @@ def split_tokens(s): if unquoted: parts.append(unquoted) elif quoted: - parts.append(quoted[1:-1]) + parts.append(UNESCAPE_PATTERN.sub(r'\1', quoted[1:-1])) elif opar: parens += 1 parts.append(opar) diff --git a/Tests/t_ldap_schema_tokenizer.py b/Tests/t_ldap_schema_tokenizer.py index c8581771..0890379a 100644 --- a/Tests/t_ldap_schema_tokenizer.py +++ b/Tests/t_ldap_schema_tokenizer.py @@ -44,8 +44,8 @@ # broken schema of Oracle Internet Directory TESTCASES_BROKEN_OID = ( - ("BLUBB DI 'BLU B B ER'MUST 'BLAH' ", ['BLUBB', 'DI', 'BLU B B ER', 'MUST', 'BLAH']), - ("BLUBBER DI 'BLU'BB ER' DA 'BLAH' ", ["BLUBBER", "DI", "BLU'BB ER", "DA", "BLAH"]), + "BLUBB DI 'BLU B B ER'MUST 'BLAH' ", #['BLUBB', 'DI', 'BLU B B ER', 'MUST', 'BLAH'] + "BLUBBER DI 'BLU'BB ER' DA 'BLAH' ", #["BLUBBER", "DI", "BLU'BB ER", "DA", "BLAH"] ) # for quoted single quotes inside string values @@ -104,14 +104,12 @@ def test_utf8(self): """ self._run_split_tokens_tests(TESTCASES_UTF8) - @unittest.expectedFailure def test_broken_oid(self): """ run test cases specified in constant TESTCASES_BROKEN_OID """ self._run_failure_tests(TESTCASES_BROKEN_OID) - @unittest.expectedFailure def test_escaped_quotes(self): """ run test cases specified in constant TESTCASES_ESCAPED_QUOTES From 7d92e0fb76e9d5da63638ecde2c615f8d0084f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 09:33:25 +0100 Subject: [PATCH 087/206] Pass message type as is --- Modules/LDAPObject.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index ed4df4c0..8ce9adb3 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1081,7 +1081,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) struct timeval *tvp; int res_type; LDAPMessage *msg = NULL; - PyObject *result_str, *retval, *pmsg, *pyctrls = 0; + PyObject *retval, *pmsg, *pyctrls = 0; int res_msgid = 0; char *retoid = 0; PyObject *valuestr = NULL; @@ -1193,27 +1193,19 @@ l_ldap_result4(LDAPObject *self, PyObject *args) pmsg = LDAPmessage_to_python(self->ldap, msg, add_ctrls, add_intermediates); - if (res_type == 0) { - result_str = Py_None; - Py_INCREF(Py_None); - } - else { - result_str = PyInt_FromLong(res_type); - } - if (pmsg == NULL) { retval = NULL; } else { /* s handles NULL, but O does not */ if (add_extop) { - retval = Py_BuildValue("(OOiOsO)", result_str, pmsg, res_msgid, + retval = Py_BuildValue("(iOiOsO)", res_type, pmsg, res_msgid, pyctrls, retoid, valuestr ? valuestr : Py_None); } else { retval = - Py_BuildValue("(OOiO)", result_str, pmsg, res_msgid, pyctrls); + Py_BuildValue("(iOiO)", res_type, pmsg, res_msgid, pyctrls); } if (pmsg != Py_None) { @@ -1222,7 +1214,6 @@ l_ldap_result4(LDAPObject *self, PyObject *args) } Py_XDECREF(valuestr); Py_XDECREF(pyctrls); - Py_DECREF(result_str); return retval; } From f754e35dcbc65bea6c879e6a68de3e5889a43460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 10:38:20 +0100 Subject: [PATCH 088/206] Attach msgid/controls/... to exceptions (Closes #177, #208) Closes: https://github.com/python-ldap/python-ldap/issues/177 (msgid) https://github.com/python-ldap/python-ldap/issues/208 (controls) --- Modules/LDAPObject.c | 3 +- Modules/constants.c | 110 ++++++++++++++++++++++++++++++++++--------- Modules/constants.h | 1 + 3 files changed, 91 insertions(+), 23 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 8ce9adb3..73dc0fe1 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1172,9 +1172,8 @@ l_ldap_result4(LDAPObject *self, PyObject *args) else e = "ldap_parse_result"; ldap_controls_free(serverctrls); - ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror(self->ldap, e); + return LDAPraise_for_message(self->ldap, e, msg); } if (!(pyctrls = LDAPControls_to_List(serverctrls))) { diff --git a/Modules/constants.c b/Modules/constants.c index f8da3736..bc7c2dcd 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -3,6 +3,7 @@ #include "common.h" #include "constants.h" +#include "ldapcontrol.h" #include "lber.h" #include "ldap.h" @@ -48,29 +49,48 @@ LDAPerr(int errnum) /* Convert an LDAP error into an informative python exception */ PyObject * -LDAPerror(LDAP *l, char *msg) +LDAPraise_for_message(LDAP *l, char *msg, LDAPMessage *m) { if (l == NULL) { PyErr_SetFromErrno(LDAPexception_class); + ldap_msgfree(m); return NULL; } else { - int myerrno, errnum, opt_errnum; + int myerrno, errnum, opt_errnum, msgid = -1, msgtype = 0; PyObject *errobj; PyObject *info; PyObject *str; PyObject *pyerrno; - char *matched, *error; + PyObject *pyresult; + PyObject *pyctrls = NULL; + char *matched = NULL, + *error = NULL, + **refs = NULL; + LDAPControl **serverctrls = NULL; /* at first save errno for later use before it gets overwritten by another call */ myerrno = errno; - opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); - if (opt_errnum != LDAP_OPT_SUCCESS) - errnum = opt_errnum; + if (m != NULL) { + msgid = ldap_msgid(m); + msgtype = ldap_msgtype(m); + ldap_parse_result(l, m, &errnum, &matched, &error, &refs, + &serverctrls, 1); + } - if (errnum == LDAP_NO_MEMORY) - return PyErr_NoMemory(); + if (msgtype <= 0) { + opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); + if (opt_errnum != LDAP_OPT_SUCCESS) + errnum = opt_errnum; + + if (errnum == LDAP_NO_MEMORY) { + return PyErr_NoMemory(); + } + + ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched); + ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error); + } if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; @@ -78,8 +98,32 @@ LDAPerror(LDAP *l, char *msg) errobj = LDAPexception_class; info = PyDict_New(); - if (info == NULL) + if (info == NULL) { + ldap_memfree(matched); + ldap_memfree(error); + ldap_memvfree((void **)refs); + ldap_controls_free(serverctrls); return NULL; + } + + if (msgtype > 0) { + pyresult = PyInt_FromLong(msgtype); + if (pyresult) + PyDict_SetItemString(info, "msgtype", pyresult); + Py_XDECREF(pyresult); + } + + if (msgid >= 0) { + pyresult = PyInt_FromLong(msgid); + if (pyresult) + PyDict_SetItemString(info, "msgid", pyresult); + Py_XDECREF(pyresult); + } + + pyresult = PyInt_FromLong(errnum); + if (pyresult) + PyDict_SetItemString(info, "result", pyresult); + Py_XDECREF(pyresult); str = PyUnicode_FromString(ldap_err2string(errnum)); if (str) @@ -93,8 +137,21 @@ LDAPerror(LDAP *l, char *msg) Py_XDECREF(pyerrno); } - if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 - && matched != NULL) { + if (!(pyctrls = LDAPControls_to_List(serverctrls))) { + int err = LDAP_NO_MEMORY; + + ldap_set_option(l, LDAP_OPT_ERROR_NUMBER, &err); + ldap_memfree(matched); + ldap_memfree(error); + ldap_memvfree((void **)refs); + ldap_controls_free(serverctrls); + return PyErr_NoMemory(); + } + ldap_controls_free(serverctrls); + PyDict_SetItemString(info, "ctrls", pyctrls); + Py_XDECREF(pyctrls); + + if (matched != NULL) { if (*matched != '\0') { str = PyUnicode_FromString(matched); if (str) @@ -104,27 +161,38 @@ LDAPerror(LDAP *l, char *msg) ldap_memfree(matched); } - if (errnum == LDAP_REFERRAL) { + if (errnum == LDAP_REFERRAL && refs != NULL && refs[0] != NULL) { + /* Keep old behaviour, overshadow error message */ + char err[1024]; + + snprintf(err, sizeof(err), "Referral:\n%s", refs[0]); + str = PyUnicode_FromString(err); + PyDict_SetItemString(info, "info", str); + Py_XDECREF(str); + } + else if (msg != NULL || (error != NULL && *error != '\0')) { + msg = msg ? msg : error; + str = PyUnicode_FromString(msg); if (str) PyDict_SetItemString(info, "info", str); Py_XDECREF(str); } - else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { - if (error != NULL && *error != '\0') { - str = PyUnicode_FromString(error); - if (str) - PyDict_SetItemString(info, "info", str); - Py_XDECREF(str); - } - ldap_memfree(error); - } + PyErr_SetObject(errobj, info); Py_DECREF(info); + ldap_memvfree((void **)refs); + ldap_memfree(error); return NULL; } } +PyObject * +LDAPerror(LDAP *l, char *msg) +{ + return LDAPraise_for_message(l, msg, NULL); +} + /* initialise the module constants */ int diff --git a/Modules/constants.h b/Modules/constants.h index 8a390b5b..f1d84a5d 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -12,6 +12,7 @@ extern PyObject *LDAPconstant(int); extern PyObject *LDAPexception_class; extern PyObject *LDAPerror(LDAP *, char *msg); +extern PyObject *LDAPraise_for_message(LDAP *, char *msg, LDAPMessage *m); PyObject *LDAPerr(int errnum); #ifndef LDAP_CONTROL_PAGE_OID From 07f44122f1f4b4e2b1550a6dc6e13d0d14e664ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 10:43:15 +0100 Subject: [PATCH 089/206] Sample test for previous commit --- Tests/t_cext.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 96c3b2c8..668107ee 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -381,30 +381,36 @@ def test_compare(self): self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_ADD) + # try a false compare m = l.compare_ext(dn, "userPassword", "bad_string") - try: + with self.assertRaises(_ldap.COMPARE_FALSE) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.COMPARE_FALSE: - pass - else: - self.fail("expected COMPARE_FALSE, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 5) + self.assertFalse(e.exception.args[0]['ctrls']) + # try a true compare m = l.compare_ext(dn, "userPassword", "the_password") - try: + with self.assertRaises(_ldap.COMPARE_TRUE) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.COMPARE_TRUE: - pass - else: - self.fail("expected COMPARE_TRUE, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 6) + self.assertFalse(e.exception.args[0]['ctrls']) + # try a compare on bad attribute m = l.compare_ext(dn, "badAttribute", "ignoreme") - try: + with self.assertRaises(_ldap.error) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.error: - pass - else: - self.fail("expected LDAPError, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 17) + self.assertFalse(e.exception.args[0]['ctrls']) def test_delete_no_such_object(self): """ From 972490c9cb916f7954c372a718e0050943d5dffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 2 May 2019 10:48:25 +0100 Subject: [PATCH 090/206] refs is now unused --- Modules/LDAPObject.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 73dc0fe1..9f672732 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1086,7 +1086,6 @@ l_ldap_result4(LDAPObject *self, PyObject *args) char *retoid = 0; PyObject *valuestr = NULL; int result = LDAP_SUCCESS; - char **refs = NULL; LDAPControl **serverctrls = 0; if (!PyArg_ParseTuple @@ -1157,23 +1156,15 @@ l_ldap_result4(LDAPObject *self, PyObject *args) } LDAP_BEGIN_ALLOW_THREADS(self); - rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, &refs, + rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, NULL, &serverctrls, 0); LDAP_END_ALLOW_THREADS(self); } if (result != LDAP_SUCCESS) { /* result error */ - char *e, err[1024]; - - if (result == LDAP_REFERRAL && refs && refs[0]) { - snprintf(err, sizeof(err), "Referral:\n%s", refs[0]); - e = err; - } - else - e = "ldap_parse_result"; ldap_controls_free(serverctrls); Py_XDECREF(valuestr); - return LDAPraise_for_message(self->ldap, e, msg); + return LDAPraise_for_message(self->ldap, "ldap_parse_result", msg); } if (!(pyctrls = LDAPControls_to_List(serverctrls))) { From 785729194f133fd7947c2366a941363a57380429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 30 Apr 2020 12:11:16 +0100 Subject: [PATCH 091/206] Add .errnum to LDAPExceptions --- Doc/reference/ldap.rst | 3 +++ Modules/constants.c | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 32543750..1f093ab9 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -321,6 +321,9 @@ The module defines the following exceptions: is set to a truncated form of the name provided or alias dereferenced for the lowest entry (object or alias) that was matched. + Most exceptions from protocol results also carry the :py:attr:`errnum` + attribute. + .. py:exception:: ADMINLIMIT_EXCEEDED diff --git a/Modules/constants.c b/Modules/constants.c index bc7c2dcd..a1c79d14 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -198,7 +198,7 @@ LDAPerror(LDAP *l, char *msg) int LDAPinit_constants(PyObject *m) { - PyObject *exc; + PyObject *exc, *nobj; /* simple constants */ @@ -228,6 +228,10 @@ LDAPinit_constants(PyObject *m) #define add_err(n) do { \ exc = PyErr_NewException("ldap." #n, LDAPexception_class, NULL); \ if (exc == NULL) return -1; \ + nobj = PyLong_FromLong(LDAP_##n); \ + if (nobj == NULL) return -1; \ + if (PyObject_SetAttrString(exc, "errnum", nobj) != 0) return -1; \ + Py_DECREF(nobj); \ errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = exc; \ if (PyModule_AddObject(m, #n, exc) != 0) return -1; \ Py_INCREF(exc); \ From 18a261effe2498f49560a35b2adbee438c2ebcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 18 May 2020 13:44:50 +0100 Subject: [PATCH 092/206] Drop unused/unusable argument --- Modules/LDAPObject.c | 38 +++++++++++++++++++------------------- Modules/constants.c | 12 +++++------- Modules/constants.h | 4 ++-- Modules/functions.c | 2 +- Modules/ldapcontrol.c | 4 ++-- Modules/message.c | 14 +++++++------- 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 9f672732..0f52d229 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -415,7 +415,7 @@ l_ldap_unbind_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_unbind_ext"); + return LDAPerror(self->ldap); self->valid = 0; Py_INCREF(Py_None); @@ -461,7 +461,7 @@ l_ldap_abandon_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_abandon_ext"); + return LDAPerror(self->ldap); Py_INCREF(Py_None); return Py_None; @@ -517,7 +517,7 @@ l_ldap_add_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_add_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -568,7 +568,7 @@ l_ldap_simple_bind(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_simple_bind"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -727,7 +727,7 @@ l_ldap_sasl_bind_s(LDAPObject *self, PyObject *args) servercred->bv_len); } else if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "l_ldap_sasl_bind_s"); + return LDAPerror(self->ldap); return PyInt_FromLong(ldaperror); } @@ -806,7 +806,7 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (msgid != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_sasl_interactive_bind_s"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } #endif @@ -854,7 +854,7 @@ l_ldap_cancel(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_cancel"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -908,7 +908,7 @@ l_ldap_compare_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_compare_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -954,7 +954,7 @@ l_ldap_delete_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_delete_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1011,7 +1011,7 @@ l_ldap_modify_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_modify_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1061,7 +1061,7 @@ l_ldap_rename(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_rename"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1108,7 +1108,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) LDAP_END_ALLOW_THREADS(self); if (res_type < 0) /* LDAP or system error */ - return LDAPerror(self->ldap, "ldap_result4"); + return LDAPerror(self->ldap); if (res_type == 0) { /* Polls return (None, None, None, None); timeouts raise an exception */ @@ -1164,7 +1164,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) if (result != LDAP_SUCCESS) { /* result error */ ldap_controls_free(serverctrls); Py_XDECREF(valuestr); - return LDAPraise_for_message(self->ldap, "ldap_parse_result", msg); + return LDAPraise_for_message(self->ldap, msg); } if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -1176,7 +1176,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) ldap_controls_free(serverctrls); ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror(self->ldap, "LDAPControls_to_List"); + return LDAPerror(self->ldap); } ldap_controls_free(serverctrls); @@ -1277,7 +1277,7 @@ l_ldap_search_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_search_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1324,7 +1324,7 @@ l_ldap_whoami_s(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) { ber_bvfree(bvalue); - return LDAPerror(self->ldap, "ldap_whoami_s"); + return LDAPerror(self->ldap); } result = LDAPberval_to_unicode_object(bvalue); @@ -1351,7 +1351,7 @@ l_ldap_start_tls_s(LDAPObject *self, PyObject *args) LDAP_END_ALLOW_THREADS(self); if (ldaperror != LDAP_SUCCESS) { ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &ldaperror); - return LDAPerror(self->ldap, "ldap_start_tls_s"); + return LDAPerror(self->ldap); } Py_INCREF(Py_None); @@ -1443,7 +1443,7 @@ l_ldap_passwd(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_passwd"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1494,7 +1494,7 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_extended_operation"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } diff --git a/Modules/constants.c b/Modules/constants.c index a1c79d14..a4473109 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -49,7 +49,7 @@ LDAPerr(int errnum) /* Convert an LDAP error into an informative python exception */ PyObject * -LDAPraise_for_message(LDAP *l, char *msg, LDAPMessage *m) +LDAPraise_for_message(LDAP *l, LDAPMessage *m) { if (l == NULL) { PyErr_SetFromErrno(LDAPexception_class); @@ -170,10 +170,8 @@ LDAPraise_for_message(LDAP *l, char *msg, LDAPMessage *m) PyDict_SetItemString(info, "info", str); Py_XDECREF(str); } - else if (msg != NULL || (error != NULL && *error != '\0')) { - msg = msg ? msg : error; - - str = PyUnicode_FromString(msg); + else if (error != NULL && *error != '\0') { + str = PyUnicode_FromString(error); if (str) PyDict_SetItemString(info, "info", str); Py_XDECREF(str); @@ -188,9 +186,9 @@ LDAPraise_for_message(LDAP *l, char *msg, LDAPMessage *m) } PyObject * -LDAPerror(LDAP *l, char *msg) +LDAPerror(LDAP *l) { - return LDAPraise_for_message(l, msg, NULL); + return LDAPraise_for_message(l, NULL); } /* initialise the module constants */ diff --git a/Modules/constants.h b/Modules/constants.h index f1d84a5d..b8150949 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -11,8 +11,8 @@ extern int LDAPinit_constants(PyObject *m); extern PyObject *LDAPconstant(int); extern PyObject *LDAPexception_class; -extern PyObject *LDAPerror(LDAP *, char *msg); -extern PyObject *LDAPraise_for_message(LDAP *, char *msg, LDAPMessage *m); +extern PyObject *LDAPerror(LDAP *); +extern PyObject *LDAPraise_for_message(LDAP *, LDAPMessage *m); PyObject *LDAPerr(int errnum); #ifndef LDAP_CONTROL_PAGE_OID diff --git a/Modules/functions.c b/Modules/functions.c index 3a43c7c2..2a567fd4 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -25,7 +25,7 @@ l_ldap_initialize(PyObject *unused, PyObject *args) PyEval_RestoreThread(save); if (ret != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_initialize"); + return LDAPerror(ld); return (PyObject *)newLDAPObject(ld); } diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 66155984..17f52cb9 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -353,13 +353,13 @@ encode_assertion_control(PyObject *self, PyObject *args) err = ldap_create(&ld); PyEval_RestoreThread(save); if (err != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_create"); + return LDAPerror(ld); err = ldap_create_assertion_control_value(ld, assertion_filterstr, &ctrl_val); if (err != LDAP_SUCCESS) { - LDAPerror(ld, "ldap_create_assertion_control_value"); + LDAPerror(ld); save = PyEval_SaveThread(); ldap_unbind_ext(ld, NULL, NULL); PyEval_RestoreThread(save); diff --git a/Modules/message.c b/Modules/message.c index 2c05488a..d225a1d6 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -53,7 +53,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, if (dn == NULL) { Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_get_dn"); + return LDAPerror(ld); } attrdict = PyDict_New(); @@ -69,7 +69,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(result); ldap_msgfree(m); ldap_memfree(dn); - return LDAPerror(ld, "ldap_get_entry_controls"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ @@ -81,7 +81,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, ldap_msgfree(m); ldap_memfree(dn); ldap_controls_free(serverctrls); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); @@ -201,7 +201,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(reflist); Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_parse_reference"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -212,7 +212,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(result); ldap_msgfree(m); ldap_controls_free(serverctrls); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); if (refs) { @@ -255,7 +255,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, 0) != LDAP_SUCCESS) { Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_parse_intermediate"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -267,7 +267,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, ldap_controls_free(serverctrls); ldap_memfree(retoid); ber_bvfree(retdata); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); From 5f083fe45c4d0f12f86adadc904a514c95a676de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 18 May 2020 13:45:33 +0100 Subject: [PATCH 093/206] Fix error in test failure reporting --- Tests/t_ldapobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 36e2acfc..a64b5f0c 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -465,7 +465,7 @@ def test004_enotconn(self): info = ldap_err.args[0]['info'] expected_info = os.strerror(errno.ENOTCONN) if info != expected_info: - self.fail("expected info=%r, got %d" % (expected_info, info)) + self.fail("expected info=%r, got %r" % (expected_info, info)) else: self.fail("expected SERVER_DOWN, got %r" % r) From 63058a0b774ee8ebbbcdac5af72344a8b42c052f Mon Sep 17 00:00:00 2001 From: Eyal Cherevatzki Date: Thu, 28 May 2020 09:59:10 +0300 Subject: [PATCH 094/206] docs: python-tox no longer exists in Debian Buster (#307) Co-authored-by: Christian Heimes Co-authored-by: eyal0803 --- Doc/installing.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 90187a93..514cf99e 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -146,8 +146,12 @@ Debian Packages for building and testing:: # apt-get install build-essential python3-dev python2.7-dev \ - libldap2-dev libsasl2-dev slapd ldap-utils python-tox \ + libldap2-dev libsasl2-dev slapd ldap-utils tox \ lcov valgrind + +.. note:: + + On older releases ``tox`` was called ``python-tox``. Fedora ------ From a7671d84bc9aea322c6cb36468a91c02543ebf9d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 16:17:02 +0200 Subject: [PATCH 095/206] Don't overallocate memory in attrs_from_List() Allocate ``(strlen + 1) * sizeof(char)`` instead of ``(strlen + 1) * sizeof(char*)`` for a ``char[]`` in ``attrs_from_List()``. https://github.com/python-ldap/python-ldap/pull/340 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 0f52d229..a2ff626f 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -335,7 +335,7 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) * internal values that must be treated like const char. Python * 3.7 actually returns a const char. */ - attrs[i] = (char *)PyMem_NEW(char *, strlen + 1); + attrs[i] = (char *)PyMem_NEW(char, strlen + 1); if (attrs[i] == NULL) goto nomem; From f8f10a9ac34952e0c2a2c47fc9234c48eb3b2f11 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 16:34:58 +0200 Subject: [PATCH 096/206] Document that TLS options are backend specific https://github.com/python-ldap/python-ldap/pull/338 Fixes: https://github.com/python-ldap/python-ldap/issues/301 --- Doc/reference/ldap.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1f093ab9..f4381212 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -223,6 +223,8 @@ TLS options .. py:data:: OPT_X_TLS +.. py:data:: OPT_X_TLS_NEWCTX + .. py:data:: OPT_X_TLS_ALLOW .. py:data:: OPT_X_TLS_CACERTDIR @@ -249,6 +251,12 @@ TLS options .. py:data:: OPT_X_TLS_TRY +.. note:: + + OpenLDAP supports several TLS/SSL libraries. OpenSSL is the most common + backend. Some options may not be available when libldap uses NSS, GnuTLS, + or Apple's Secure Transport backend. + .. _ldap-keepalive-options: Keepalive options From 269df1793df386687a6eab880c4a6c28cde61533 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 16:56:22 +0200 Subject: [PATCH 097/206] Deprecate cidict's strlist functions The functions are undocumented, untested, unused, and can now be trivialy implemented with set operations. https://github.com/python-ldap/python-ldap/pull/336 Signed-off-by: Christian Heimes --- Lib/ldap/cidict.py | 16 ++++++++++++++++ Tests/t_cidict.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 4f7e0910..875d5f50 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -5,6 +5,7 @@ See https://www.python-ldap.org/ for details. """ +import warnings from ldap import __version__ @@ -62,6 +63,11 @@ def strlist_minus(a,b): Return list of all items in a which are not in b (a - b). a,b are supposed to be lists of case-insensitive strings. """ + warnings.warn( + "strlist functions are deprecated and will be removed in 3.4", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in b: temp[elt] = elt @@ -77,6 +83,11 @@ def strlist_intersection(a,b): """ Return intersection of two lists of case-insensitive strings a,b. """ + warnings.warn( + "strlist functions are deprecated and will be removed in 3.4", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in a: temp[elt] = elt @@ -92,6 +103,11 @@ def strlist_union(a,b): """ Return union of two lists of case-insensitive strings a,b. """ + warnings.warn( + "strlist functions are deprecated and will be removed in 3.4", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in a: temp[elt] = elt diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index fa5a39b8..b96a26e6 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -7,6 +7,7 @@ import os import unittest +import warnings # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -48,6 +49,19 @@ def test_cidict(self): self.assertEqual(cix.has_key("abcdef"), False) self.assertEqual(cix.has_key("AbCDef"), False) + def test_strlist_deprecated(self): + strlist_funcs = [ + ldap.cidict.strlist_intersection, + ldap.cidict.strlist_minus, + ldap.cidict.strlist_union + ] + for strlist_func in strlist_funcs: + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter("always", DeprecationWarning) + strlist_func(["a"], ["b"]) + self.assertEqual(len(w), 1) + if __name__ == '__main__': unittest.main() From ed802af28187fa13d668349ad4ed38327434f3ff Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 27 May 2020 16:27:52 +0200 Subject: [PATCH 098/206] Run make indent on C code Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 2 ++ Modules/ldapcontrol.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index a2ff626f..e06f47c3 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -126,6 +126,7 @@ Tuple_to_LDAPMod(PyObject *tup, int no_op) } lm = PyMem_NEW(LDAPMod, 1); + if (lm == NULL) goto nomem; @@ -236,6 +237,7 @@ List_to_LDAPMods(PyObject *list, int no_op) } lms = PyMem_NEW(LDAPMod *, len + 1); + if (lms == NULL) goto nomem; diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 17f52cb9..5e2d2ff8 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -82,6 +82,7 @@ Tuple_to_LDAPControl(PyObject *tup) return NULL; lc = PyMem_NEW(LDAPControl, 1); + if (lc == NULL) { PyErr_NoMemory(); return NULL; @@ -91,6 +92,7 @@ Tuple_to_LDAPControl(PyObject *tup) len = strlen(oid); lc->ldctl_oid = PyMem_NEW(char, len + 1); + if (lc->ldctl_oid == NULL) { PyErr_NoMemory(); LDAPControl_DEL(lc); @@ -137,6 +139,7 @@ LDAPControls_from_object(PyObject *list, LDAPControl ***controls_ret) len = PySequence_Length(list); ldcs = PyMem_NEW(LDAPControl *, len + 1); + if (ldcs == NULL) { PyErr_NoMemory(); return 0; From a8fd053d84172c73957cd147cac3ddf5f404979a Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 27 May 2020 16:27:36 +0200 Subject: [PATCH 099/206] Fix broken print() migration Signed-off-by: Christian Heimes --- Demo/pyasn1/readentrycontrol.py | 14 +++++++------- Demo/pyasn1/sss_highest_number.py | 6 +++--- Demo/schema_tree.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Demo/pyasn1/readentrycontrol.py b/Demo/pyasn1/readentrycontrol.py index 10faa2b2..a857be22 100644 --- a/Demo/pyasn1/readentrycontrol.py +++ b/Demo/pyasn1/readentrycontrol.py @@ -39,8 +39,8 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].dn:", resp_ctrls[0].dn) +print("resp_ctrls[0].entry:", pprint.pformat(resp_ctrls[0].entry)) print("""#--------------------------------------------------------------------------- # Modify entry @@ -56,7 +56,7 @@ ) _,_,_,resp_ctrls = l.result3(msg_id) print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].entry:",pprint.pformat(resp_ctrls[0].entry)) pr = PostReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN']) @@ -67,7 +67,7 @@ ) _,_,_,resp_ctrls = l.result3(msg_id) print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].entry:",pprint.pformat(resp_ctrls[0].entry)) print("""#--------------------------------------------------------------------------- # Rename entry @@ -83,7 +83,7 @@ ) _,_,_,resp_ctrls = l.result3(msg_id) print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].entry:",pprint.pformat(resp_ctrls[0].entry)) pr = PreReadControl(criticality=True,attrList=['uid']) msg_id = l.rename( @@ -94,7 +94,7 @@ ) _,_,_,resp_ctrls = l.result3(msg_id) print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].entry:",pprint.pformat(resp_ctrls[0].entry)) print("""#--------------------------------------------------------------------------- # Delete entry @@ -108,4 +108,4 @@ ) _,_,_,resp_ctrls = l.result3(msg_id) print("resp_ctrls[0].dn:",resp_ctrls[0].dn) -print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) +print("resp_ctrls[0].entry:",pprint.pformat(resp_ctrls[0].entry)) diff --git a/Demo/pyasn1/sss_highest_number.py b/Demo/pyasn1/sss_highest_number.py index 5f5bdc50..020dcdb3 100644 --- a/Demo/pyasn1/sss_highest_number.py +++ b/Demo/pyasn1/sss_highest_number.py @@ -38,9 +38,9 @@ class MyLDAPObject(LDAPObject,ResultProcessor): except ldap.SIZELIMIT_EXCEEDED: pass # print result - print 'Highest value of %s' % (id_attr) + print('Highest value of %s' % (id_attr)) if ldap_result: dn,entry = ldap_result[0] - print '->',entry[id_attr] + print('->',entry[id_attr]) else: - print 'not found' + print('not found') diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index 648bb86f..bda5f64d 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -15,9 +15,9 @@ def PrintSchemaTree(schema,se_class,se_tree,se_oid,level): """ASCII text output for console""" se_obj = schema.get_obj(se_class,se_oid) if se_obj!=None: - print('| '*(level-1)+'+---'*(level>0), \) - ', '.join(se_obj.names), \ - '(%s)' % se_obj.oid + print('| '*(level-1)+'+---'*(level>0), + ', '.join(se_obj.names), + '(%s)' % se_obj.oid) for sub_se_oid in se_tree[se_oid]: print('| '*(level+1)) PrintSchemaTree(schema,se_class,se_tree,sub_se_oid,level+1) From 9b6aabb9586994c70597a12fff6518023dc65f2d Mon Sep 17 00:00:00 2001 From: KOVACS Krisztian Date: Fri, 5 Jun 2020 18:44:50 +0200 Subject: [PATCH 100/206] Tests and documentation for msgid for exceptions raised from result4() --- Doc/reference/ldap.rst | 8 ++++++++ Tests/t_ldapobject.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index f4381212..ce30ed95 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -329,6 +329,14 @@ The module defines the following exceptions: is set to a truncated form of the name provided or alias dereferenced for the lowest entry (object or alias) that was matched. + For use in asynchronous operations an optional field :py:const:`msg_id` is + also set in the dictionary in cases where the exception can be associated + with a request. This can be used in asynchronous code where + :py:meth:`result()` returns an exception that is effectively the result of a + previously started asynchronous operation. For example, this is the case for + asynchronous (:py:meth:`compare()`), where the boolean result is always + raised as an exception (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`). + Most exceptions from protocol results also carry the :py:attr:`errnum` attribute. diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index a64b5f0c..24711b21 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -673,6 +673,20 @@ def test_compare_s_invalidattr(self): with self.assertRaises(ldap.UNDEFINED_TYPE): result = l.compare_s('cn=Foo1,%s' % base, 'invalidattr', b'invalid') + def test_compare_true_exception_contains_message_id(self): + base = self.server.suffix + l = self._ldap_conn + msgid = l.compare('cn=Foo1,%s' % base, 'cn', b'Foo1') + with self.assertRaises(ldap.COMPARE_TRUE) as cm: + l.result() + self.assertEqual(cm.exception.args[0]["msgid"], msgid) + + def test_async_search_no_such_object_exception_contains_message_id(self): + msgid = self._ldap_conn.search("CN=XXX", ldap.SCOPE_SUBTREE) + with self.assertRaises(ldap.NO_SUCH_OBJECT) as cm: + self._ldap_conn.result() + self.assertEqual(cm.exception.args[0]["msgid"], msgid) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From e2bcd627830f0eb4a2900045b84375d32fa04868 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 18:47:42 +0200 Subject: [PATCH 101/206] Reword the msgid documentation to make it terser --- Doc/reference/ldap.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index ce30ed95..799afe14 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -329,13 +329,12 @@ The module defines the following exceptions: is set to a truncated form of the name provided or alias dereferenced for the lowest entry (object or alias) that was matched. - For use in asynchronous operations an optional field :py:const:`msg_id` is - also set in the dictionary in cases where the exception can be associated - with a request. This can be used in asynchronous code where - :py:meth:`result()` returns an exception that is effectively the result of a - previously started asynchronous operation. For example, this is the case for - asynchronous (:py:meth:`compare()`), where the boolean result is always - raised as an exception (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`). + The field :py:const:`msgid` is set in the dictionary where the + exception can be associated with an asynchronous request. + This can be used in asynchronous code where :py:meth:`result()` raises the + result of an operation as an exception. For example, this is the case for + :py:meth:`compare()`, always raises the boolean result as an exception + (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`). Most exceptions from protocol results also carry the :py:attr:`errnum` attribute. From def231c0673739edf43c9faadf2047313bca3ebe Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 19:15:48 +0200 Subject: [PATCH 102/206] Tox: replace setup.py test with unittest discover setup.py test is deprecated and causes issues with testing our code. There are cases where the wrong shared library is picked up. The ``unittest discover`` approach avoids building the extension twice. python-ldap should move to pytest eventually. https://github.com/python-ldap/python-ldap/pull/335 Fixes: https://github.com/python-ldap/python-ldap/issues/326 Signed-off-by: Christian Heimes --- .coveragerc | 7 ++++--- .travis.yml | 4 ---- tox.ini | 38 ++++++++++++++++++++++---------------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.coveragerc b/.coveragerc index fc432b9e..738d86fa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,9 +1,10 @@ [run] branch = True source = - Lib/ -omit = - Lib/slapdtest.py + ldap + ldif + ldapurl + slapdtest [paths] source = diff --git a/.travis.yml b/.travis.yml index 8e0e3a28..067ee373 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,10 +67,6 @@ matrix: - python: 3.6 env: TOXENV=doc allow_failures: - - env: - - TOXENV=py39 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - env: - TOXENV=pypy diff --git a/tox.ini b/tox.ini index be723176..6d9f85a3 100644 --- a/tox.ini +++ b/tox.ini @@ -16,43 +16,49 @@ passenv = WITH_GCOV # - 'ignore:the imp module is deprecated' is required to ignore import of 'imp' # in distutils. Python < 3.6 use PendingDeprecationWarning; Python >= 3.6 use # DeprecationWarning. +# - 'ignore:lib2to3 package' for Python 3.9 commands = {envpython} -bb -Werror \ "-Wignore:the imp module is deprecated:DeprecationWarning" \ "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ "-Wignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning" \ - -m coverage run --parallel setup.py \ - clean --all \ - test + "-Wignore:lib2to3 package is deprecated and may not be able to parse Python 3.10+:PendingDeprecationWarning" \ + -m coverage run --parallel -m unittest discover -v -s Tests -p 't_*' [testenv:py27] # No warnings with Python 2.7 passenv = {[testenv]passenv} -commands = {envpython} \ - -m coverage run --parallel setup.py test +commands = + {envpython} -m coverage run --parallel \ + -m unittest discover -v -s Tests -p 't_*' [testenv:py34] # No warnings with Python 3.4 passenv = {[testenv]passenv} -commands = {envpython} \ - -m coverage run --parallel setup.py test +commands = {[testenv:py27]commands} [testenv:py2-nosasltls] basepython = python2 -deps = {[testenv]deps} +# don't install, install dependencies manually +skip_install = true +deps = + {[testenv]deps} + pyasn1 + pyasn1_modules passenv = {[testenv]passenv} setenv = CI_DISABLED=LDAPI:SASL:TLS -# rebuild without SASL and TLS, run without LDAPI -commands = {envpython} \ - -m coverage run --parallel setup.py \ - clean --all \ - build_ext -UHAVE_SASL,HAVE_TLS \ - test +# build and install without SASL and TLS, run without LDAPI +commands = + {envpython} setup.py clean --all + {envpython} setup.py build_ext -UHAVE_SASL,HAVE_TLS + {envpython} setup.py install --single-version-externally-managed --root=/ + {[testenv:py27]commands} [testenv:py3-nosasltls] basepython = python3 -deps = {[testenv]deps} -passenv = {[testenv]passenv} +skip_install = {[testenv:py2-nosasltls]skip_install} +deps = {[testenv:py2-nosasltls]deps} +passenv = {[testenv:py2-nosasltls]passenv} setenv = {[testenv:py2-nosasltls]setenv} commands = {[testenv:py2-nosasltls]commands} From c803bfc7479566c0c36f4eb2d9e5596a0b30d221 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 19:28:56 +0200 Subject: [PATCH 103/206] Improve TLS documentation See: https://github.com/python-ldap/python-ldap/issues/55 https://github.com/python-ldap/python-ldap/pull/339 Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 145 ++++++++++++++++++++++++++++++++-- Doc/spelling_wordlist.txt | 2 + Lib/ldap/constants.py | 1 - Makefile | 5 ++ Modules/constants_generated.h | 4 - 5 files changed, 145 insertions(+), 12 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 799afe14..1d1b025e 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -83,6 +83,12 @@ This module defines the following functions: This function sets the value of the global option specified by *option* to *invalue*. + .. note:: + + Most global settings do not affect existing :py:class:`LDAPObject` + connections. Applications should call :py:func:`set_option()` before + they establish connections with :py:func:`initialize`. + .. versionchanged:: 3.1 The deprecated functions ``ldap.init()`` and ``ldap.open()`` were removed. @@ -221,35 +227,158 @@ SASL options TLS options ::::::::::: -.. py:data:: OPT_X_TLS +.. warning:: + + libldap does not materialize all TLS settings immediately. You must use + :py:const:`OPT_X_TLS_NEWCTX` with value ``0`` to instruct libldap to + apply pending TLS settings and create a new internal TLS context:: + + conn = ldap.initialize("ldap://ldap.example") + conn.set_option(ldap.OPT_X_TLS_CACERTFILE, '/path/to/ca.pem') + conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0) + conn.start_tls_s() + conn.simple_bind_s(dn, password) + .. py:data:: OPT_X_TLS_NEWCTX -.. py:data:: OPT_X_TLS_ALLOW + set and apply TLS settings to internal TLS context. Value ``0`` creates + a new client-side context. + +.. py:data:: OPT_X_TLS_PACKAGE + + Get TLS implementation, known values are + + * ``GnuTLS`` + * ``MozNSS`` (Mozilla NSS) + * ``OpenSSL`` + .. py:data:: OPT_X_TLS_CACERTDIR + get/set path to directory with CA certs + .. py:data:: OPT_X_TLS_CACERTFILE + get/set path to PEM file with CA certs + .. py:data:: OPT_X_TLS_CERTFILE -.. py:data:: OPT_X_TLS_CIPHER_SUITE + get/set path to file with PEM encoded cert for client cert authentication, + requires :py:const:`OPT_X_TLS_KEYFILE`. -.. py:data:: OPT_X_TLS_CTX +.. py:data:: OPT_X_TLS_KEYFILE + + get/set path to file with PEM encoded key for client cert authentication, + requires :py:const:`OPT_X_TLS_CERTFILE`. + + +.. py:data:: OPT_X_TLS_CRLCHECK + + get/set certificate revocation list (CRL) check mode. CRL validation + requires :py:const:`OPT_X_TLS_CRLFILE`. + + :py:const:`OPT_X_TLS_CRL_NONE` + Don't perform CRL checks + + :py:const:`OPT_X_TLS_CRL_PEER` + Perform CRL check for peer's end entity cert. + + :py:const:`OPT_X_TLS_CRL_ALL` + Perform CRL checks for the whole cert chain + +.. py:data:: OPT_X_TLS_CRLFILE + + get/set path to CRL file + +.. py:data:: OPT_X_TLS_CRL_ALL + + value for :py:const:`OPT_X_TLS_CRLCHECK` + +.. py:data:: OPT_X_TLS_CRL_NONE + + value for :py:const:`OPT_X_TLS_CRLCHECK` + +.. py:data:: OPT_X_TLS_CRL_PEER + + value for :py:const:`OPT_X_TLS_CRLCHECK` + + +.. py:data:: OPT_X_TLS_REQUIRE_CERT + + get/set validation strategy for server cert. + + :py:const:`OPT_X_TLS_NEVER` + Don't check server cert and host name + + :py:const:`OPT_X_TLS_ALLOW` + Used internally by slapd server. + + :py:const:`OPT_X_TLS_DEMAND` + Validate peer cert chain and host name + + :py:const:`OPT_X_TLS_HARD` + Same as :py:const:`OPT_X_TLS_DEMAND` + +.. py:data:: OPT_X_TLS_ALLOW + + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` .. py:data:: OPT_X_TLS_DEMAND + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. py:data:: OPT_X_TLS_HARD -.. py:data:: OPT_X_TLS_KEYFILE + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` .. py:data:: OPT_X_TLS_NEVER + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + +.. py:data:: OPT_X_TLS_TRY + + .. deprecated:: 3.3.0 + This value is only used by slapd server internally. It will be removed + in the future. + + +.. py:data:: OPT_X_TLS_CIPHER + + get cipher suite name from TLS session + +.. py:data:: OPT_X_TLS_CIPHER_SUITE + + get/set allowed cipher suites + +.. py:data:: OPT_X_TLS_CTX + + get address of internal memory address of TLS context (**DO NOT USE**) + +.. py:data:: OPT_X_TLS_PEERCERT + + Get peer's certificate as binary ASN.1 data structure (not supported) + +.. py:data:: OPT_X_TLS_PROTOCOL_MIN + + get/set minimum protocol version (wire protocol version as int) + + * ``0x303`` for TLS 1.2 + * ``0x304`` for TLS 1.3 + +.. py:data:: OPT_X_TLS_VERSION + + Get negotiated TLS protocol version as string + .. py:data:: OPT_X_TLS_RANDOM_FILE -.. py:data:: OPT_X_TLS_REQUIRE_CERT + get/set path to /dev/urandom (**DO NOT USE**) -.. py:data:: OPT_X_TLS_TRY +.. py:data:: OPT_X_TLS + + .. deprecated:: 3.3.0 + The option is deprecated in OpenLDAP and should no longer be used. It + will be removed in the future. .. note:: @@ -579,6 +708,8 @@ The above exceptions are raised when a result code from an underlying API call does not indicate success. +.. _ldap-warnings: + Warnings ======== diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 3ee0e858..d13c0791 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -39,6 +39,7 @@ defresult dereferenced dereferencing desc +dev directoryOperation distinguished distributedOperation @@ -145,6 +146,7 @@ UDP Umich unparsing unsigend +urandom uri urlPrefix urlscheme diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 7a7982bb..641d49ce 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -281,7 +281,6 @@ class Str(Constant): TLSInt('OPT_X_TLS_DEMAND'), TLSInt('OPT_X_TLS_ALLOW'), TLSInt('OPT_X_TLS_TRY'), - TLSInt('OPT_X_TLS_PEERCERT', optional=True), TLSInt('OPT_X_TLS_VERSION', optional=True), TLSInt('OPT_X_TLS_CIPHER', optional=True), diff --git a/Makefile b/Makefile index 2d3293e6..8ec46a6b 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,11 @@ AUTOPEP8_OPTS=--aggressive .PHONY: all all: +Modules/constants_generated.h: Lib/ldap/constants.py + $(PYTHON) $^ > $@ + indent Modules/constants_generated.h + rm -f Modules/constants_generated.h~ + .PHONY: clean clean: rm -rf build dist *.egg-info .tox MANIFEST diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 455852ed..3231e635 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -213,10 +213,6 @@ add_int(OPT_X_TLS_DEMAND); add_int(OPT_X_TLS_ALLOW); add_int(OPT_X_TLS_TRY); -#if defined(LDAP_OPT_X_TLS_PEERCERT) -add_int(OPT_X_TLS_PEERCERT); -#endif - #if defined(LDAP_OPT_X_TLS_VERSION) add_int(OPT_X_TLS_VERSION); #endif From 9a91bbd8150a9d7b59b0dadd195d8f2c142a401b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Fri, 5 Jun 2020 19:22:53 +0100 Subject: [PATCH 104/206] Extract new password in passwd_s Fixes: https://github.com/python-ldap/python-ldap/issues/246 https://github.com/python-ldap/python-ldap/pull/299 --- Doc/reference/ldap.rst | 10 +++++++++- Lib/ldap/extop/__init__.py | 1 + Lib/ldap/extop/passwd.py | 33 ++++++++++++++++++++++++++++++++ Lib/ldap/ldapobject.py | 15 +++++++++++---- Tests/t_ldapobject.py | 39 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 Lib/ldap/extop/passwd.py diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1d1b025e..06ecb906 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -963,7 +963,7 @@ and wait for and return with the server's result, or with .. py:method:: LDAPObject.passwd(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> int -.. py:method:: LDAPObject.passwd_s(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> None +.. py:method:: LDAPObject.passwd_s(user, oldpw, newpw [, serverctrls=None [, clientctrls=None] [, extract_newpw=False]]]) -> (respoid, respvalue) Perform a ``LDAP Password Modify Extended Operation`` operation on the entry specified by *user*. @@ -974,6 +974,13 @@ and wait for and return with the server's result, or with of the specified *user* which is sometimes used when a user changes his own password. + *respoid* is always :py:const:`None`. *respvalue* is also + :py:const:`None` unless *newpw* was :py:const:`None`. This requests that + the server generate a new random password. If *extract_newpw* is + :py:const:`True`, this password is a bytes object available through + ``respvalue.genPasswd``, otherwise *respvalue* is the raw ASN.1 response + (this is deprecated and only for backwards compatibility). + *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. The asynchronous version returns the initiated message id. @@ -983,6 +990,7 @@ and wait for and return with the server's result, or with .. seealso:: :rfc:`3062` - LDAP Password Modify Extended Operation + :py:mod:`ldap.extop.passwd` diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index 874166d9..39e653a9 100644 --- a/Lib/ldap/extop/__init__.py +++ b/Lib/ldap/extop/__init__.py @@ -65,3 +65,4 @@ def decodeResponseValue(self,value): # Import sub-modules from ldap.extop.dds import * +from ldap.extop.passwd import PasswordModifyResponse diff --git a/Lib/ldap/extop/passwd.py b/Lib/ldap/extop/passwd.py new file mode 100644 index 00000000..0a8346a8 --- /dev/null +++ b/Lib/ldap/extop/passwd.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +ldap.extop.passwd - Classes for Password Modify extended operation +(see RFC 3062) + +See https://www.python-ldap.org/ for details. +""" + +from ldap.extop import ExtendedResponse + +# Imports from pyasn1 +from pyasn1.type import namedtype, univ, tag +from pyasn1.codec.der import decoder + + +class PasswordModifyResponse(ExtendedResponse): + responseName = None + + class PasswordModifyResponseValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType( + 'genPasswd', + univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 0) + ) + ) + ) + + def decodeResponseValue(self, value): + respValue, _ = decoder.decode(value, asn1Spec=self.PasswordModifyResponseValue()) + self.genPasswd = bytes(respValue.getComponentByName('genPasswd')) + return self.genPasswd diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index a92b0886..ab654e17 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -27,7 +27,7 @@ from ldap.schema import SCHEMA_ATTRS from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples -from ldap.extop import ExtendedRequest,ExtendedResponse +from ldap.extop import ExtendedRequest,ExtendedResponse,PasswordModifyResponse from ldap.compat import reraise from ldap import LDAPError @@ -656,9 +656,16 @@ def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None): newpw = self._bytesify_input('newpw', newpw) return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) - def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None): - msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls) - return self.extop_result(msgid,all=1,timeout=self.timeout) + def passwd_s(self, user, oldpw, newpw, serverctrls=None, clientctrls=None, extract_newpw=False): + msgid = self.passwd(user, oldpw, newpw, serverctrls, clientctrls) + respoid, respvalue = self.extop_result(msgid, all=1, timeout=self.timeout) + + if respoid != PasswordModifyResponse.responseName: + raise ldap.PROTOCOL_ERROR("Unexpected OID %s in extended response!" % respoid) + if extract_newpw and respvalue: + respvalue = PasswordModifyResponse(PasswordModifyResponse.responseName, respvalue) + + return respoid, respvalue def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None): """ diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 24711b21..1ec00280 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -687,6 +687,45 @@ def test_async_search_no_such_object_exception_contains_message_id(self): self._ldap_conn.result() self.assertEqual(cm.exception.args[0]["msgid"], msgid) + def test_passwd_s(self): + l = self._ldap_conn + + # first, create a user to change password on + dn = "cn=PasswordTest," + self.server.suffix + result, pmsg, msgid, ctrls = l.add_ext_s( + dn, + [ + ('objectClass', b'person'), + ('sn', b'PasswordTest'), + ('cn', b'PasswordTest'), + ('userPassword', b'initial'), + ] + ) + self.assertEqual(result, ldap.RES_ADD) + self.assertIsInstance(msgid, int) + self.assertEqual(pmsg, []) + self.assertEqual(ctrls, []) + + # try changing password with a wrong old-pw + with self.assertRaises(ldap.UNWILLING_TO_PERFORM): + l.passwd_s(dn, "bogus", "ignored") + + # have the server generate a new random pw + respoid, respvalue = l.passwd_s(dn, "initial", None, extract_newpw=True) + self.assertEqual(respoid, None) + + password = respvalue.genPasswd + self.assertIsInstance(password, bytes) + if PY2: + password = password.decode('utf-8') + + # try changing password back + respoid, respvalue = l.passwd_s(dn, password, "initial") + self.assertEqual(respoid, None) + self.assertEqual(respvalue, None) + + l.delete_s(dn) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From 2a2cef38feb7f259ea6b5735a6ce3e46812e217d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 15:25:04 +0100 Subject: [PATCH 105/206] cidict: Rewrite in terms of MutableMapping The UserDict API is not specified when it comes to extending: it is not clear which methods can be safely overridden. The MutableMapping ABC fixes this problem by providing an explicit set of abstract methods, in terms of which the rest of the API is implemented. Switch to using MutableMapping for cidict. --- Lib/ldap/cidict.py | 71 +++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 875d5f50..db57468a 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -5,57 +5,58 @@ See https://www.python-ldap.org/ for details. """ +from collections import MutableMapping import warnings from ldap import __version__ -from ldap.compat import IterableUserDict +class cidict(MutableMapping): + """ + Case-insensitive but case-respecting dictionary. + """ -class cidict(IterableUserDict): - """ - Case-insensitive but case-respecting dictionary. - """ + def __init__(self, default=None): + self._keys = {} + self._data = {} + if default: + self.update(default) + + # MutableMapping abstract methods - def __init__(self,default=None): - self._keys = {} - IterableUserDict.__init__(self,{}) - self.update(default or {}) + def __getitem__(self, key): + return self._data[key.lower()] - def __getitem__(self,key): - return self.data[key.lower()] + def __setitem__(self, key, value): + lower_key = key.lower() + self._keys[lower_key] = key + self._data[lower_key] = value - def __setitem__(self,key,value): - lower_key = key.lower() - self._keys[lower_key] = key - self.data[lower_key] = value + def __delitem__(self, key): + lower_key = key.lower() + del self._keys[lower_key] + del self._data[lower_key] - def __delitem__(self,key): - lower_key = key.lower() - del self._keys[lower_key] - del self.data[lower_key] + def __iter__(self): + return iter(self._keys.values()) - def update(self,dict): - for key, value in dict.items(): - self[key] = value + def __len__(self): + return len(self._keys) - def has_key(self,key): - return key in self + # Specializations for performance - def __contains__(self,key): - return IterableUserDict.__contains__(self, key.lower()) + def __contains__(self, key): + return key.lower() in self._keys - def __iter__(self): - return iter(self.keys()) + def clear(self): + self._keys.clear() + self._data.clear() - def keys(self): - return self._keys.values() + # Backwards compatibility - def items(self): - result = [] - for k in self._keys.values(): - result.append((k,self[k])) - return result + def has_key(self, key): + """Compatibility with python-ldap 2.x""" + return key in self def strlist_minus(a,b): From dada91afdba682f4d755f972b3d94a6e9e08f385 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 15:47:52 +0100 Subject: [PATCH 106/206] cidict: Re-add the `data` attribute --- Lib/ldap/cidict.py | 11 +++++++++++ Tests/t_cidict.py | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index db57468a..3dcfe486 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -58,6 +58,17 @@ def has_key(self, key): """Compatibility with python-ldap 2.x""" return key in self + @property + def data(self): + """Compatibility with older IterableUserDict-based implementation""" + warnings.warn( + 'ldap.cidict.cidict.data is an internal attribute; it may be ' + + 'removed at any time', + category=DeprecationWarning, + stacklevel=2, + ) + return self._data + def strlist_minus(a,b): """ diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index b96a26e6..6878617e 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -62,6 +62,16 @@ def test_strlist_deprecated(self): strlist_func(["a"], ["b"]) self.assertEqual(len(w), 1) + def test_cidict_data(self): + """test the deprecated data atrtribute""" + d = ldap.cidict.cidict({'A': 1, 'B': 2}) + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', DeprecationWarning) + data = d.data + assert data == {'a': 1, 'b': 2} + self.assertEqual(len(w), 1) + if __name__ == '__main__': unittest.main() From 3e11046243b0b29fa48b84ce74082920796cdd78 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 14 Mar 2018 16:16:05 +0100 Subject: [PATCH 107/206] ldapurl.LDAPUrlExtensions: Rewrite in terms of MutableMapping The UserDict API is not specified when it comes to extending: it is not clear which methods can be safely overridden. The MutableMapping ABC fixes this problem by providing an explicit set of abstract methods, in terms of which the rest of the API is implemented. Switch to using MutableMapping for LDAPUrlExtensions. --- Lib/ldapurl.py | 102 ++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 6de06459..04f78adb 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -16,7 +16,9 @@ 'LDAPUrlExtension','LDAPUrlExtensions','LDAPUrl' ] -from ldap.compat import UserDict, quote, unquote +from collections import MutableMapping + +from ldap.compat import quote, unquote LDAP_SCOPE_BASE = 0 LDAP_SCOPE_ONELEVEL = 1 @@ -130,58 +132,64 @@ def __ne__(self,other): return not self.__eq__(other) -class LDAPUrlExtensions(UserDict): - """ - Models a collection of LDAP URL extensions as - dictionary type - """ - - def __init__(self,default=None): - UserDict.__init__(self) - for k,v in (default or {}).items(): - self[k]=v - - def __setitem__(self,name,value): +class LDAPUrlExtensions(MutableMapping): """ - value - Either LDAPUrlExtension instance, (critical,exvalue) - or string'ed exvalue + Models a collection of LDAP URL extensions as + a mapping type """ - assert isinstance(value,LDAPUrlExtension) - assert name==value.extype - self.data[name] = value - def values(self): - return [ - self[k] - for k in self.keys() - ] - - def __str__(self): - return ','.join(str(v) for v in self.values()) - - def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( - self.__class__.__module__, - self.__class__.__name__, - hex(id(self)), - self.data - ) + def __init__(self, default=None): + self._data = {} + if default is not None: + self.update(default) + + def __setitem__(self, name, value): + """ + value + Either LDAPUrlExtension instance, (critical,exvalue) + or string'ed exvalue + """ + assert isinstance(value, LDAPUrlExtension) + assert name == value.extype + self._data[name] = value + + def __getitem__(self, name): + return self._data[name] + + def __delitem__(self, name): + del self._data[name] + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return len(self._data) + + def __str__(self): + return ','.join(str(v) for v in self.values()) + + def __repr__(self): + return '<%s.%s instance at %s: %s>' % ( + self.__class__.__module__, + self.__class__.__name__, + hex(id(self)), + self._data + ) - def __eq__(self,other): - assert isinstance(other,self.__class__),TypeError( - "other has to be instance of %s" % (self.__class__) - ) - return self.data==other.data + def __eq__(self,other): + assert isinstance(other, self.__class__), TypeError( + "other has to be instance of %s" % (self.__class__) + ) + return self._data == other._data - def parse(self,extListStr): - for extension_str in extListStr.strip().split(','): - if extension_str: - e = LDAPUrlExtension(extension_str) - self[e.extype] = e + def parse(self,extListStr): + for extension_str in extListStr.strip().split(','): + if extension_str: + e = LDAPUrlExtension(extension_str) + self[e.extype] = e - def unparse(self): - return ','.join([ v.unparse() for v in self.values() ]) + def unparse(self): + return ','.join(v.unparse() for v in self.values()) class LDAPUrl(object): From 917f9c34c0860f0fe5b8ce26cbd56fad466d7238 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 Mar 2018 15:35:35 +0200 Subject: [PATCH 108/206] ldapurl: Replace asserts with proper checks --- Lib/ldapurl.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 04f78adb..1edc2fed 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -149,8 +149,13 @@ def __setitem__(self, name, value): Either LDAPUrlExtension instance, (critical,exvalue) or string'ed exvalue """ - assert isinstance(value, LDAPUrlExtension) - assert name == value.extype + if not isinstance(value, LDAPUrlExtension): + raise TypeError("value must be LDAPUrlExtension, not " + + type(value).__name__) + if name != value.extype: + raise ValueError( + "key {!r} does not match extension type {!r}".format( + name, value.extype)) self._data[name] = value def __getitem__(self, name): @@ -177,9 +182,8 @@ def __repr__(self): ) def __eq__(self,other): - assert isinstance(other, self.__class__), TypeError( - "other has to be instance of %s" % (self.__class__) - ) + if not isinstance(other, self.__class__): + return NotImplemented return self._data == other._data def parse(self,extListStr): @@ -374,17 +378,23 @@ def htmlHREF(self,urlPrefix='',hrefText=None,hrefTarget=None): hrefTarget string added as link target attribute """ - assert type(urlPrefix)==StringType, "urlPrefix must be StringType" + if not isinstance(urlPrefix, str): + raise TypeError("urlPrefix must be str, not " + + type(urlPrefix).__name__) if hrefText is None: - hrefText = self.unparse() - assert type(hrefText)==StringType, "hrefText must be StringType" + hrefText = self.unparse() + if not isinstance(hrefText, str): + raise TypeError("hrefText must be str, not " + + type(hrefText).__name__) if hrefTarget is None: - target = '' + target = '' else: - assert type(hrefTarget)==StringType, "hrefTarget must be StringType" - target = ' target="%s"' % hrefTarget + if not isinstance(hrefTarget, str): + raise TypeError("hrefTarget must be str, not " + + type(hrefTarget).__name__) + target = ' target="%s"' % hrefTarget return '%s' % ( - target,urlPrefix,self.unparse(),hrefText + target, urlPrefix, self.unparse(), hrefText ) def __str__(self): From 89f66f8dfa0be86a90dc1684135ab1d2fd04240d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 Mar 2018 15:42:36 +0200 Subject: [PATCH 109/206] ldapurl: Update docstring of LDAPUrlExtensions.__setitem__ --- Lib/ldapurl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 1edc2fed..7ed1d8ae 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -144,10 +144,12 @@ def __init__(self, default=None): self.update(default) def __setitem__(self, name, value): - """ + """Store an extension + + name + string value - Either LDAPUrlExtension instance, (critical,exvalue) - or string'ed exvalue + LDAPUrlExtension instance, whose extype nust match `name` """ if not isinstance(value, LDAPUrlExtension): raise TypeError("value must be LDAPUrlExtension, not " From 6f2a45d77a82a34d20ba1bd21246d0ef0433faea Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 27 Mar 2018 15:43:30 +0200 Subject: [PATCH 110/206] Lib: Add __slots__ to cidict and LDAPUrlExtensions --- Lib/ldap/cidict.py | 1 + Lib/ldapurl.py | 1 + 2 files changed, 2 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 3dcfe486..cf133883 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -15,6 +15,7 @@ class cidict(MutableMapping): """ Case-insensitive but case-respecting dictionary. """ + __slots__ = ('_keys', '_data') def __init__(self, default=None): self._keys = {} diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 7ed1d8ae..ecfc539a 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -137,6 +137,7 @@ class LDAPUrlExtensions(MutableMapping): Models a collection of LDAP URL extensions as a mapping type """ + __slots__ = ('_data', ) def __init__(self, default=None): self._data = {} From b1b3b5a91b252afabf9c3d125ef648907dae5527 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 20:55:07 +0200 Subject: [PATCH 111/206] Import MutableMapping from collections.abc, except on legacy Python --- Lib/ldap/cidict.py | 2 +- Lib/ldap/compat.py | 2 ++ Lib/ldapurl.py | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index cf133883..48aeacb4 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -5,9 +5,9 @@ See https://www.python-ldap.org/ for details. """ -from collections import MutableMapping import warnings +from ldap.compat import MutableMapping from ldap import __version__ diff --git a/Lib/ldap/compat.py b/Lib/ldap/compat.py index cbfeef57..901457b2 100644 --- a/Lib/ldap/compat.py +++ b/Lib/ldap/compat.py @@ -10,6 +10,7 @@ from urllib import unquote as urllib_unquote from urllib import urlopen from urlparse import urlparse + from collections import MutableMapping def unquote(uri): """Specialized unquote that uses UTF-8 for parsing.""" @@ -33,6 +34,7 @@ def unquote(uri): IterableUserDict = UserDict from urllib.parse import quote, quote_plus, unquote, urlparse from urllib.request import urlopen + from collections.abc import MutableMapping def reraise(exc_type, exc_value, exc_traceback): """Re-raise an exception given information from sys.exc_info() diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index ecfc539a..a3dd7ff2 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -16,9 +16,7 @@ 'LDAPUrlExtension','LDAPUrlExtensions','LDAPUrl' ] -from collections import MutableMapping - -from ldap.compat import quote, unquote +from ldap.compat import quote, unquote, MutableMapping LDAP_SCOPE_BASE = 0 LDAP_SCOPE_ONELEVEL = 1 From 72a4707bd4a5265f0419d591fa8166251674178d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 21:08:36 +0200 Subject: [PATCH 112/206] Fix NULL deref and checks in LDAPmessage_to_python The function LDAPmessage_to_python() had some potential NULL pointer derefs and missed a couple of errors checks. It's still not perfect but a bit better now. https://github.com/python-ldap/python-ldap/pull/342 Signed-off-by: Christian Heimes --- Modules/message.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Modules/message.c b/Modules/message.c index d225a1d6..22aa313c 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -273,20 +273,35 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, valuestr = LDAPberval_to_object(retdata); ber_bvfree(retdata); + if (valuestr == NULL) { + ldap_memfree(retoid); + Py_DECREF(result); + ldap_msgfree(m); + return NULL; + } + pyoid = PyUnicode_FromString(retoid); ldap_memfree(retoid); if (pyoid == NULL) { + Py_DECREF(valuestr); + Py_DECREF(result); + ldap_msgfree(m); + return NULL; + } + + valtuple = Py_BuildValue("(NNN)", pyoid, valuestr, pyctrls); + if (valtuple == NULL) { + Py_DECREF(result); + ldap_msgfree(m); + return NULL; + } + + if (PyList_Append(result, valtuple) == -1) { + Py_DECREF(valtuple); Py_DECREF(result); ldap_msgfree(m); return NULL; } - valtuple = Py_BuildValue("(OOO)", pyoid, - valuestr ? valuestr : Py_None, - pyctrls); - Py_DECREF(pyoid); - Py_DECREF(valuestr); - Py_XDECREF(pyctrls); - PyList_Append(result, valtuple); Py_DECREF(valtuple); } } From 0870889b01ef696f32f53bb269312133b465d1f2 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 29 May 2020 12:10:09 +0200 Subject: [PATCH 113/206] Check valid connection in LDAPObject.set/get_option set_option() and get_option() now verify that LDAPObject is valid. This fixes an assertion error and possible segfault after unbind_ext(). Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index e06f47c3..da18d575 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1365,14 +1365,16 @@ l_ldap_start_tls_s(LDAPObject *self, PyObject *args) /* ldap_set_option */ static PyObject * -l_ldap_set_option(PyObject *self, PyObject *args) +l_ldap_set_option(LDAPObject *self, PyObject *args) { PyObject *value; int option; if (!PyArg_ParseTuple(args, "iO:set_option", &option, &value)) return NULL; - if (!LDAP_set_option((LDAPObject *)self, option, value)) + if (not_valid(self)) + return NULL; + if (!LDAP_set_option(self, option, value)) return NULL; Py_INCREF(Py_None); return Py_None; @@ -1381,13 +1383,15 @@ l_ldap_set_option(PyObject *self, PyObject *args) /* ldap_get_option */ static PyObject * -l_ldap_get_option(PyObject *self, PyObject *args) +l_ldap_get_option(LDAPObject *self, PyObject *args) { int option; if (!PyArg_ParseTuple(args, "i:get_option", &option)) return NULL; - return LDAP_get_option((LDAPObject *)self, option); + if (not_valid(self)) + return NULL; + return LDAP_get_option(self, option); } /* ldap_passwd */ From cb4eb7897439683ef7a5f2c43504cf897bfcbc8b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 29 May 2020 12:09:55 +0200 Subject: [PATCH 114/206] Allow LDAP connection from file descriptor ``ldap.initialize()`` now takes an optional fileno argument to create an LDAP connection from a connected socket. See: https://github.com/python-ldap/python-ldap/issues/178 Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 16 ++++++++- Lib/ldap/functions.py | 9 +++-- Lib/ldap/ldapobject.py | 16 ++++++--- Lib/slapdtest/_slapdtest.py | 12 +++++-- Modules/functions.c | 72 +++++++++++++++++++++++++++++++++++++ Tests/t_cext.py | 59 ++++++++++++++++++++++++++---- Tests/t_ldapobject.py | 27 ++++++++++++++ setup.py | 1 + 8 files changed, 197 insertions(+), 15 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 06ecb906..164e2b6a 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -29,7 +29,7 @@ Functions This module defines the following functions: -.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None]]]]]) -> LDAPObject object +.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None, [fileno=None]]]]]]) -> LDAPObject object Initializes a new connection object for accessing the given LDAP server, and return an :class:`~ldap.ldapobject.LDAPObject` used to perform operations @@ -40,6 +40,16 @@ This module defines the following functions: when using multiple URIs you cannot determine to which URI your client gets connected. + If *fileno* parameter is given then the file descriptor will be used to + connect to an LDAP server. The *fileno* must either be a socket file + descriptor as :class:`int` or a file-like object with a *fileno()* method + that returns a socket file descriptor. The socket file descriptor must + already be connected. :class:`~ldap.ldapobject.LDAPObject` does not take + ownership of the file descriptor. It must be kept open during operations + and explicitly closed after the :class:`~ldap.ldapobject.LDAPObject` is + unbound. The internal connection type is determined from the URI, ``TCP`` + for ``ldap://`` / ``ldaps://``, ``IPC`` (``AF_UNIX``) for ``ldapi://``. + Note that internally the OpenLDAP function `ldap_initialize(3) `_ is called which just initializes the LDAP connection struct in the C API @@ -72,6 +82,10 @@ This module defines the following functions: :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator + .. versionadded:: 3.3 + + The *fileno* argument was added. + .. py:function:: get_option(option) -> int|string diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 529c4c8f..31ab00f7 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -67,7 +67,7 @@ def _ldap_function_call(lock,func,*args,**kwargs): def initialize( uri, trace_level=0, trace_file=sys.stdout, trace_stack_limit=None, - bytes_mode=None, **kwargs + bytes_mode=None, fileno=None, **kwargs ): """ Return LDAPObject instance by opening LDAP connection to @@ -84,12 +84,17 @@ def initialize( Default is to use stdout. bytes_mode Whether to enable :ref:`bytes_mode` for backwards compatibility under Py2. + fileno + If not None the socket file descriptor is used to connect to an + LDAP server. Additional keyword arguments (such as ``bytes_strictness``) are passed to ``LDAPObject``. """ return LDAPObject( - uri, trace_level, trace_file, trace_stack_limit, bytes_mode, **kwargs) + uri, trace_level, trace_file, trace_stack_limit, bytes_mode, + fileno=fileno, **kwargs + ) def get_option(option): diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index ab654e17..b44d90cd 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -96,14 +96,21 @@ class SimpleLDAPObject: def __init__( self,uri, trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, - bytes_strictness=None, + bytes_strictness=None, fileno=None ): self._trace_level = trace_level or ldap._trace_level self._trace_file = trace_file or ldap._trace_file self._trace_stack_limit = trace_stack_limit self._uri = uri self._ldap_object_lock = self._ldap_lock('opcall') - self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) + if fileno is not None: + if hasattr(fileno, "fileno"): + fileno = fileno.fileno() + self._l = ldap.functions._ldap_function_call( + ldap._ldap_module_lock, _ldap.initialize_fd, fileno, uri + ) + else: + self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) self.timeout = -1 self.protocol_version = ldap.VERSION3 @@ -1093,7 +1100,7 @@ class ReconnectLDAPObject(SimpleLDAPObject): def __init__( self,uri, trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, - bytes_strictness=None, retry_max=1, retry_delay=60.0 + bytes_strictness=None, retry_max=1, retry_delay=60.0, fileno=None ): """ Parameters like SimpleLDAPObject.__init__() with these @@ -1109,7 +1116,8 @@ def __init__( self._last_bind = None SimpleLDAPObject.__init__(self, uri, trace_level, trace_file, trace_stack_limit, bytes_mode, - bytes_strictness=bytes_strictness) + bytes_strictness=bytes_strictness, + fileno=fileno) self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) self._retry_max = retry_max self._retry_delay = retry_delay diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 2f932bc7..de4c3e53 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -179,7 +179,7 @@ class SlapdObject(object): root_cn = 'Manager' root_pw = 'password' slapd_loglevel = 'stats stats2' - local_host = '127.0.0.1' + local_host = LOCALHOST testrunsubdirs = ( 'schema', ) @@ -214,7 +214,7 @@ def __init__(self): self._schema_prefix = os.path.join(self.testrundir, 'schema') self._slapd_conf = os.path.join(self.testrundir, 'slapd.conf') self._db_directory = os.path.join(self.testrundir, "openldap-data") - self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) + self.ldap_uri = "ldap://%s:%d/" % (self.local_host, self._port) if HAVE_LDAPI: ldapi_path = os.path.join(self.testrundir, 'ldapi') self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) @@ -243,6 +243,14 @@ def __init__(self): def root_dn(self): return 'cn={self.root_cn},{self.suffix}'.format(self=self) + @property + def hostname(self): + return self.local_host + + @property + def port(self): + return self._port + def _find_commands(self): self.PATH_LDAPADD = self._find_command('ldapadd') self.PATH_LDAPDELETE = self._find_command('ldapdelete') diff --git a/Modules/functions.c b/Modules/functions.c index 2a567fd4..74740a98 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -30,6 +30,75 @@ l_ldap_initialize(PyObject *unused, PyObject *args) return (PyObject *)newLDAPObject(ld); } +#ifdef HAVE_LDAP_INIT_FD + +/* initialize_fd(fileno, url) + * + * ldap_init_fd() is not a private API but it's not in a public header either + * SSSD has been using the function for a while, so it's probably OK. + */ + +#ifndef LDAP_PROTO_TCP +#define LDAP_PROTO_TCP 1 +#define LDAP_PROTO_UDP 2 +#define LDAP_PROTO_IPC 3 +#endif + +extern int + ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, LDAP **ldp); + +static PyObject * +l_ldap_initialize_fd(PyObject *unused, PyObject *args) +{ + char *url; + LDAP *ld = NULL; + int ret; + int fd; + int proto = -1; + LDAPURLDesc *lud = NULL; + + PyThreadState *save; + + if (!PyArg_ParseTuple(args, "is:initialize_fd", &fd, &url)) + return NULL; + + /* Get LDAP protocol from scheme */ + ret = ldap_url_parse(url, &lud); + if (ret != LDAP_SUCCESS) + return LDAPerr(ret); + + if (strcmp(lud->lud_scheme, "ldap") == 0) { + proto = LDAP_PROTO_TCP; + } + else if (strcmp(lud->lud_scheme, "ldaps") == 0) { + proto = LDAP_PROTO_TCP; + } + else if (strcmp(lud->lud_scheme, "ldapi") == 0) { + proto = LDAP_PROTO_IPC; + } +#ifdef LDAP_CONNECTIONLESS + else if (strcmp(lud->lud_scheme, "cldap") == 0) { + proto = LDAP_PROTO_UDP; + } +#endif + else { + ldap_free_urldesc(lud); + PyErr_SetString(PyExc_ValueError, "unsupported URL scheme"); + return NULL; + } + ldap_free_urldesc(lud); + + save = PyEval_SaveThread(); + ret = ldap_init_fd((ber_socket_t) fd, proto, url, &ld); + PyEval_RestoreThread(save); + + if (ret != LDAP_SUCCESS) + return LDAPerror(ld); + + return (PyObject *)newLDAPObject(ld); +} +#endif /* HAVE_LDAP_INIT_FD */ + /* ldap_str2dn */ static PyObject * @@ -137,6 +206,9 @@ l_ldap_get_option(PyObject *self, PyObject *args) static PyMethodDef methods[] = { {"initialize", (PyCFunction)l_ldap_initialize, METH_VARARGS}, +#ifdef HAVE_LDAP_INIT_FD + {"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS}, +#endif {"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS}, {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 668107ee..a19d3c33 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -7,8 +7,10 @@ from __future__ import unicode_literals +import contextlib import errno import os +import socket import unittest # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -92,14 +94,35 @@ def _open_conn(self, bind=True): """ l = _ldap.initialize(self.server.ldap_uri) if bind: - # Perform a simple bind - l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) - m = l.simple_bind(self.server.root_dn, self.server.root_pw) - result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ONE, self.timeout) - self.assertEqual(result, _ldap.RES_BIND) - self.assertEqual(type(msgid), type(0)) + self._bind_conn(l) return l + @contextlib.contextmanager + def _open_conn_fd(self, bind=True): + sock = socket.create_connection( + (self.server.hostname, self.server.port) + ) + try: + l = _ldap.initialize_fd(sock.fileno(), self.server.ldap_uri) + if bind: + self._bind_conn(l) + yield sock, l + finally: + try: + sock.close() + except OSError: + # already closed + pass + + def _bind_conn(self, l): + # Perform a simple bind + l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + m = l.simple_bind(self.server.root_dn, self.server.root_pw) + result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ONE, self.timeout) + self.assertEqual(result, _ldap.RES_BIND) + self.assertEqual(type(msgid), type(0)) + + # Test for the existence of a whole bunch of constants # that the C module is supposed to export def test_constants(self): @@ -224,6 +247,30 @@ def test_test_flags(self): def test_simple_bind(self): l = self._open_conn() + def test_simple_bind_fileno(self): + with self._open_conn_fd() as (sock, l): + self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) + + def test_simple_bind_fileno_invalid(self): + with open(os.devnull) as f: + l = _ldap.initialize_fd(f.fileno(), self.server.ldap_uri) + with self.assertRaises(_ldap.SERVER_DOWN): + self._bind_conn(l) + + def test_simple_bind_fileno_closed(self): + with self._open_conn_fd() as (sock, l): + self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) + sock.close() + with self.assertRaises(_ldap.SERVER_DOWN): + l.whoami_s() + + def test_simple_bind_fileno_rebind(self): + with self._open_conn_fd() as (sock, l): + self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) + l.unbind_ext() + with self.assertRaises(_ldap.LDAPError): + self._bind_conn(l) + def test_simple_anonymous_bind(self): l = self._open_conn(bind=False) m = l.simple_bind("", "") diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 1ec00280..459ba768 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -20,6 +20,7 @@ import contextlib import linecache import os +import socket import unittest import warnings import pickle @@ -103,6 +104,9 @@ def setUp(self): # open local LDAP connection self._ldap_conn = self._open_ldap_conn(bytes_mode=False) + def tearDown(self): + del self._ldap_conn + def test_reject_bytes_base(self): base = self.server.suffix l = self._ldap_conn @@ -807,5 +811,28 @@ def test105_reconnect_restore(self): self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) +class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): + def _get_bytes_ldapobject(self, explicit=True, **kwargs): + raise unittest.SkipTest("Test opens two sockets") + + def _search_wrong_type(self, bytes_mode, strictness): + raise unittest.SkipTest("Test opens two sockets") + + def _open_ldap_conn(self, who=None, cred=None, **kwargs): + if hasattr(self, '_sock'): + raise RuntimeError("socket already connected") + self._sock = socket.create_connection( + (self.server.hostname, self.server.port) + ) + return super(Test03_SimpleLDAPObjectWithFileno, self)._open_ldap_conn( + who=who, cred=cred, fileno=self._sock.fileno(), **kwargs + ) + + def tearDown(self): + self._sock.close() + del self._sock + super(Test03_SimpleLDAPObjectWithFileno, self).tearDown() + + if __name__ == '__main__': unittest.main() diff --git a/setup.py b/setup.py index 69747853..4559d840 100644 --- a/setup.py +++ b/setup.py @@ -145,6 +145,7 @@ class OpenLDAP2: ('LDAPMODULE_VERSION', pkginfo.__version__), ('LDAPMODULE_AUTHOR', pkginfo.__author__), ('LDAPMODULE_LICENSE', pkginfo.__license__), + ('HAVE_LDAP_INIT_FD', None), ] ), ], From d5996ec34a4f8bab17acfa358b5e2f7733b40b47 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 21:45:56 +0200 Subject: [PATCH 115/206] Do not ignore warnings in tox config (#343) It looks like all the warnings were caused by setuptools. Since we don't use setuptools in tests any more, we can remove the warnings. --- tox.ini | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tox.ini b/tox.ini index 6d9f85a3..92933b84 100644 --- a/tox.ini +++ b/tox.ini @@ -13,15 +13,7 @@ deps = coverage passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. -# - 'ignore:the imp module is deprecated' is required to ignore import of 'imp' -# in distutils. Python < 3.6 use PendingDeprecationWarning; Python >= 3.6 use -# DeprecationWarning. -# - 'ignore:lib2to3 package' for Python 3.9 commands = {envpython} -bb -Werror \ - "-Wignore:the imp module is deprecated:DeprecationWarning" \ - "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ - "-Wignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning" \ - "-Wignore:lib2to3 package is deprecated and may not be able to parse Python 3.10+:PendingDeprecationWarning" \ -m coverage run --parallel -m unittest discover -v -s Tests -p 't_*' [testenv:py27] From 450b2f370080d288dc6c54c3cd963da1672e7bf2 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jun 2020 22:16:50 +0200 Subject: [PATCH 116/206] Make indent again https://github.com/python-ldap/python-ldap/pull/344 Signed-off-by: Christian Heimes --- Modules/constants.c | 4 +--- Modules/functions.c | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Modules/constants.c b/Modules/constants.c index a4473109..88658c55 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -64,9 +64,7 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) PyObject *pyerrno; PyObject *pyresult; PyObject *pyctrls = NULL; - char *matched = NULL, - *error = NULL, - **refs = NULL; + char *matched = NULL, *error = NULL, **refs = NULL; LDAPControl **serverctrls = NULL; /* at first save errno for later use before it gets overwritten by another call */ diff --git a/Modules/functions.c b/Modules/functions.c index 74740a98..9e0312db 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -84,7 +84,7 @@ l_ldap_initialize_fd(PyObject *unused, PyObject *args) else { ldap_free_urldesc(lud); PyErr_SetString(PyExc_ValueError, "unsupported URL scheme"); - return NULL; + return NULL; } ldap_free_urldesc(lud); From 39ea8e5bc4a7bfba05e0edbfff2afcf04156a072 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 23:51:06 +0200 Subject: [PATCH 117/206] Limit coverage version to <5.0 again to work around CI issues (#346) --- .travis.yml | 5 ++++- tox.ini | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 067ee373..4bd7d724 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,10 @@ env: install: - pip install "pip>=7.1.0" - - pip install tox-travis tox codecov coverage + - pip install tox-travis tox codecov + # Coverage 5.0+ has issues similar to: + # https://github.com/nedbat/coveragepy/issues/915 + - pip install "coverage<5.0" script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" tox diff --git a/tox.ini b/tox.ini index 92933b84..2faa94d0 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,9 @@ envlist = py27,py34,py35,py36,py37,py38,py39,{py2,py3}-nosasltls,doc,py3-trace,c minver = 1.8 [testenv] -deps = coverage +# Coverage 5.0+ has issues similar to: +# https://github.com/nedbat/coveragepy/issues/915 +deps = coverage<5.0 passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. @@ -75,7 +77,9 @@ deps = {[testenv:pypy]deps} commands = {[testenv:pypy]commands} [testenv:coverage-report] -deps = coverage +# Coverage 5.0+ has issues similar to: +# https://github.com/nedbat/coveragepy/issues/915 +deps = coverage<5.0 skip_install = true commands = {envpython} -m coverage combine From c00694ee4b9b6d51bfbf75700adaff40b354e37c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 8 Jun 2020 11:18:59 +0200 Subject: [PATCH 118/206] Remove coverage At the moment coverage is causing more trouble than it's worth. Codecov isn't giving meaningful results and we aren't using coverage to increase test coverage. https://github.com/python-ldap/python-ldap/pull/349 Fixes: https://github.com/python-ldap/python-ldap/issues/348 Signed-off-by: Christian Heimes --- .travis.yml | 8 -------- tox.ini | 21 ++++----------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4bd7d724..22224cab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,14 +85,6 @@ env: install: - pip install "pip>=7.1.0" - pip install tox-travis tox codecov - # Coverage 5.0+ has issues similar to: - # https://github.com/nedbat/coveragepy/issues/915 - - pip install "coverage<5.0" script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" tox -after_success: - # gather Python coverage - - python -m coverage combine - # send Python and GCOV coverage - - codecov diff --git a/tox.ini b/tox.ini index 2faa94d0..81a38bf5 100644 --- a/tox.ini +++ b/tox.ini @@ -5,25 +5,22 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,py37,py38,py39,{py2,py3}-nosasltls,doc,py3-trace,coverage-report +envlist = py27,py34,py35,py36,py37,py38,py39,{py2,py3}-nosasltls,doc,py3-trace minver = 1.8 [testenv] -# Coverage 5.0+ has issues similar to: -# https://github.com/nedbat/coveragepy/issues/915 -deps = coverage<5.0 +deps = passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. commands = {envpython} -bb -Werror \ - -m coverage run --parallel -m unittest discover -v -s Tests -p 't_*' + -m unittest discover -v -s Tests -p 't_*' [testenv:py27] # No warnings with Python 2.7 passenv = {[testenv]passenv} commands = - {envpython} -m coverage run --parallel \ - -m unittest discover -v -s Tests -p 't_*' + {envpython} -m unittest discover -v -s Tests -p 't_*' [testenv:py34] # No warnings with Python 3.4 @@ -76,16 +73,6 @@ basepython = pypy3.5 deps = {[testenv:pypy]deps} commands = {[testenv:pypy]commands} -[testenv:coverage-report] -# Coverage 5.0+ has issues similar to: -# https://github.com/nedbat/coveragepy/issues/915 -deps = coverage<5.0 -skip_install = true -commands = - {envpython} -m coverage combine - {envpython} -m coverage report --show-missing - {envpython} -m coverage html - [testenv:doc] basepython = python3 deps = From 8f79376cfcb6327b2ecbd19df715b4a8da52649b Mon Sep 17 00:00:00 2001 From: Firstyear Date: Thu, 18 Jun 2020 19:03:28 +1000 Subject: [PATCH 119/206] Syncrepl fails to parse SyncInfoMessage when the message is a syncIdSet Previously, the SyncInfoMessage parser used a for loop to test for the presence of each named choice with with the structure. However, because refreshDelete and refreshPresent both are able to be fully resolved as defaults, and due to how pyasn1 accesses named types, it was not checking the choice tag, and would return a fully populated refreshDelete to the caller instead. This fixes the parser to always return the inner component, and retrieves the name based on the current choice of the tagged item. Author: William Brown https://github.com/python-ldap/python-ldap/pull/351 --- Lib/ldap/syncrepl.py | 49 ++++++++++++++++++++-------------------- Tests/t_ldap_syncrepl.py | 44 +++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 0de5cec4..f6ac2d1a 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -314,34 +314,35 @@ def __init__(self, encodedMessage): self.refreshPresent = None self.syncIdSet = None - for attr in ['newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']: - comp = d[0].getComponentByName(attr) - - if comp is not None and comp.hasValue(): - - if attr == 'newcookie': - self.newcookie = str(comp) - return + # Due to the way pyasn1 works, refreshDelete and refreshPresent are both + # valid in the components as they are fully populated defaults. We must + # get the component directly from the message, not by iteration. + attr = d[0].getName() + comp = d[0].getComponent() + + if comp is not None and comp.hasValue(): + if attr == 'newcookie': + self.newcookie = str(comp) + return - val = {} + val = {} - cookie = comp.getComponentByName('cookie') - if cookie.hasValue(): - val['cookie'] = str(cookie) + cookie = comp.getComponentByName('cookie') + if cookie.hasValue(): + val['cookie'] = str(cookie) - if attr.startswith('refresh'): - val['refreshDone'] = bool(comp.getComponentByName('refreshDone')) - elif attr == 'syncIdSet': - uuids = [] - ids = comp.getComponentByName('syncUUIDs') - for i in range(len(ids)): - uuid = UUID(bytes=bytes(ids.getComponentByPosition(i))) - uuids.append(str(uuid)) - val['syncUUIDs'] = uuids - val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) + if attr.startswith('refresh'): + val['refreshDone'] = bool(comp.getComponentByName('refreshDone')) + elif attr == 'syncIdSet': + uuids = [] + ids = comp.getComponentByName('syncUUIDs') + for i in range(len(ids)): + uuid = UUID(bytes=bytes(ids.getComponentByPosition(i))) + uuids.append(str(uuid)) + val['syncUUIDs'] = uuids + val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) - setattr(self, attr, val) - return + setattr(self, attr, val) class SyncreplConsumer: diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 9398de5b..b8a6ab63 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -10,6 +10,7 @@ import shelve import sys import unittest +import binascii if sys.version_info[0] <= 2: PY2 = True @@ -21,7 +22,7 @@ import ldap from ldap.ldapobject import SimpleLDAPObject -from ldap.syncrepl import SyncreplConsumer +from ldap.syncrepl import SyncreplConsumer, SyncInfoMessage from slapdtest import SlapdObject, SlapdTestCase @@ -445,6 +446,47 @@ def setUp(self): ) self.suffix = self.server.suffix.encode('utf-8') +class DecodeSyncreplProtoTests(unittest.TestCase): + """ + Tests of the ASN.1 decoder for tricky cases or past issues to ensure that + syncrepl messages are handled correctly. + """ + + def test_syncidset_message(self): + """ + A syncrepl server may send a sync info message, with a syncIdSet + of uuids to delete. A regression was found in the original + sync info message implementation due to how the choice was + evaluated, because refreshPresent and refreshDelete were both + able to be fully expressed as defaults, causing the parser + to mistakenly catch a syncIdSet as a refreshPresent/refereshDelete. + + This tests that a syncIdSet request is properly decoded. + + reference: https://tools.ietf.org/html/rfc4533#section-2.5 + """ + + # This is a dump of a syncidset message from wireshark + 389-ds + msg = """ + a36b04526c6461706b64632e6578616d706c652e636f6d3a333839303123636e + 3d6469726563746f7279206d616e616765723a64633d6578616d706c652c6463 + 3d636f6d3a286f626a656374436c6173733d2a2923330101ff311204108dc446 + 01a93611ea8aaff248c5fa5780 + """.replace(' ', '').replace('\n', '') + + msgraw = binascii.unhexlify(msg) + sim = SyncInfoMessage(msgraw) + self.assertEqual(sim.refreshDelete, None) + self.assertEqual(sim.refreshPresent, None) + self.assertEqual(sim.newcookie, None) + self.assertEqual(sim.syncIdSet, + { + 'cookie': 'ldapkdc.example.com:38901#cn=directory manager:dc=example,dc=com:(objectClass=*)#3', + 'syncUUIDs': ['8dc44601-a936-11ea-8aaf-f248c5fa5780'], + 'refreshDeletes': True + } + ) + if __name__ == '__main__': unittest.main() From f50b1977baeaa02ac2862a1cee5a40bb2735d53f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 9 Jun 2020 13:18:59 +0200 Subject: [PATCH 120/206] Use openldap.h on OpenLDAP 2.4.48 OpenLDAP 2.4.48 added openldap.h, which defines ldap_init_fd. Use the header file with 2.4.48 and define the function for older versions. The patch also cleans up #include. All OpenLDAP includes are now in common.h and use global includes ``#include `` instead of local includes ``#include "ldap.h"``. Fixes: https://github.com/python-ldap/python-ldap/issues/353 See: https://bugs.openldap.org/show_bug.cgi?id=8671 Signed-off-by: Christian Heimes --- Modules/LDAPObject.h | 6 ------ Modules/berval.h | 1 - Modules/common.h | 27 +++++++++++++++++++++++++++ Modules/constants.c | 2 -- Modules/constants.h | 2 -- Modules/functions.c | 15 +-------------- Modules/ldapcontrol.c | 2 -- Modules/ldapcontrol.h | 1 - Modules/message.h | 2 -- 9 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index a456bce0..1b6066db 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -5,12 +5,6 @@ #include "common.h" -#include "lber.h" -#include "ldap.h" -#if LDAP_API_VERSION < 2040 -#error Current python-ldap requires OpenLDAP 2.4.x -#endif - #if PYTHON_API_VERSION < 1007 typedef PyObject *_threadstate; #else diff --git a/Modules/berval.h b/Modules/berval.h index 2aa9c977..9c427240 100644 --- a/Modules/berval.h +++ b/Modules/berval.h @@ -4,7 +4,6 @@ #define __h_berval #include "common.h" -#include "lber.h" PyObject *LDAPberval_to_object(const struct berval *bv); PyObject *LDAPberval_to_unicode_object(const struct berval *bv); diff --git a/Modules/common.h b/Modules/common.h index affa5f93..bde943cd 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -12,6 +12,33 @@ #include "config.h" #endif +#include +#include +#include + +#if LDAP_API_VERSION < 2040 +#error Current python-ldap requires OpenLDAP 2.4.x +#endif + +#if LDAP_VENDOR_VERSION >= 20448 + /* openldap.h with ldap_init_fd() was introduced in 2.4.48 + * see https://bugs.openldap.org/show_bug.cgi?id=8671 + */ +#include +#ifndef HAVE_LDAP_INIT_FD +#define HAVE_LDAP_INIT_FD +#endif +#else + /* ldap_init_fd() has been around for a very long time + * SSSD has been defining the function for a while, so it's probably OK. + */ +#define LDAP_PROTO_TCP 1 +#define LDAP_PROTO_UDP 2 +#define LDAP_PROTO_IPC 3 +extern int ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, + LDAP **ldp); +#endif + #if defined(MS_WINDOWS) #include #else /* unix */ diff --git a/Modules/constants.c b/Modules/constants.c index 88658c55..8b902e02 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -4,8 +4,6 @@ #include "common.h" #include "constants.h" #include "ldapcontrol.h" -#include "lber.h" -#include "ldap.h" /* the base exception class */ diff --git a/Modules/constants.h b/Modules/constants.h index b8150949..7b9ce53e 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -4,8 +4,6 @@ #define __h_constants_ #include "common.h" -#include "lber.h" -#include "ldap.h" extern int LDAPinit_constants(PyObject *m); extern PyObject *LDAPconstant(int); diff --git a/Modules/functions.c b/Modules/functions.c index 9e0312db..e3920477 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -32,20 +32,7 @@ l_ldap_initialize(PyObject *unused, PyObject *args) #ifdef HAVE_LDAP_INIT_FD -/* initialize_fd(fileno, url) - * - * ldap_init_fd() is not a private API but it's not in a public header either - * SSSD has been using the function for a while, so it's probably OK. - */ - -#ifndef LDAP_PROTO_TCP -#define LDAP_PROTO_TCP 1 -#define LDAP_PROTO_UDP 2 -#define LDAP_PROTO_IPC 3 -#endif - -extern int - ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, LDAP **ldp); +/* initialize_fd(fileno, url) */ static PyObject * l_ldap_initialize_fd(PyObject *unused, PyObject *args) diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 5e2d2ff8..e287e9a3 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -6,8 +6,6 @@ #include "berval.h" #include "constants.h" -#include "lber.h" - /* Prints to stdout the contents of an array of LDAPControl objects */ /* XXX: This is a debugging tool, and the printf generates some warnings diff --git a/Modules/ldapcontrol.h b/Modules/ldapcontrol.h index de694c07..74cae423 100644 --- a/Modules/ldapcontrol.h +++ b/Modules/ldapcontrol.h @@ -4,7 +4,6 @@ #define __h_ldapcontrol #include "common.h" -#include "ldap.h" void LDAPinit_control(PyObject *d); void LDAPControl_List_DEL(LDAPControl **); diff --git a/Modules/message.h b/Modules/message.h index 2978ea56..ed73f32c 100644 --- a/Modules/message.h +++ b/Modules/message.h @@ -4,8 +4,6 @@ #define __h_message #include "common.h" -#include "lber.h" -#include "ldap.h" extern PyObject *LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermediates); From d45ad6afc8453e71df8520934ad42ee109c29bc4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 9 Jun 2020 14:05:36 +0200 Subject: [PATCH 121/206] Drop HAVE_LDAP_INIT_FD python-ldap requires OpenLDAP 2.4.0 that provides ldap_init_fd. Signed-off-by: Christian Heimes --- Modules/common.h | 3 --- Modules/functions.c | 5 ----- setup.py | 1 - 3 files changed, 9 deletions(-) diff --git a/Modules/common.h b/Modules/common.h index bde943cd..1ce2eb83 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -25,9 +25,6 @@ * see https://bugs.openldap.org/show_bug.cgi?id=8671 */ #include -#ifndef HAVE_LDAP_INIT_FD -#define HAVE_LDAP_INIT_FD -#endif #else /* ldap_init_fd() has been around for a very long time * SSSD has been defining the function for a while, so it's probably OK. diff --git a/Modules/functions.c b/Modules/functions.c index e3920477..ce4a924a 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -30,8 +30,6 @@ l_ldap_initialize(PyObject *unused, PyObject *args) return (PyObject *)newLDAPObject(ld); } -#ifdef HAVE_LDAP_INIT_FD - /* initialize_fd(fileno, url) */ static PyObject * @@ -84,7 +82,6 @@ l_ldap_initialize_fd(PyObject *unused, PyObject *args) return (PyObject *)newLDAPObject(ld); } -#endif /* HAVE_LDAP_INIT_FD */ /* ldap_str2dn */ @@ -193,9 +190,7 @@ l_ldap_get_option(PyObject *self, PyObject *args) static PyMethodDef methods[] = { {"initialize", (PyCFunction)l_ldap_initialize, METH_VARARGS}, -#ifdef HAVE_LDAP_INIT_FD {"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS}, -#endif {"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS}, {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, diff --git a/setup.py b/setup.py index 4559d840..69747853 100644 --- a/setup.py +++ b/setup.py @@ -145,7 +145,6 @@ class OpenLDAP2: ('LDAPMODULE_VERSION', pkginfo.__version__), ('LDAPMODULE_AUTHOR', pkginfo.__author__), ('LDAPMODULE_LICENSE', pkginfo.__license__), - ('HAVE_LDAP_INIT_FD', None), ] ), ], From 578b0b7103f87fe0ce46aadf034b3be9225ea290 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 18 Jun 2020 16:05:06 +0200 Subject: [PATCH 122/206] Document the LDAPError fields Also, correct the note on COMPARE_FALSE and COMPARE_TRUE https://github.com/python-ldap/python-ldap/pull/355 --- Doc/reference/ldap.rst | 49 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 164e2b6a..a6d69287 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -462,26 +462,27 @@ The module defines the following exceptions: are instead turned into exceptions, raised as soon an the error condition is detected. - The exceptions are accompanied by a dictionary possibly - containing an string value for the key :py:const:`desc` - (giving an English description of the error class) - and/or a string value for the key :py:const:`info` - (giving a string containing more information that the server may have sent). - - A third possible field of this dictionary is :py:const:`matched` and - is set to a truncated form of the name provided or alias dereferenced - for the lowest entry (object or alias) that was matched. - - The field :py:const:`msgid` is set in the dictionary where the - exception can be associated with an asynchronous request. - This can be used in asynchronous code where :py:meth:`result()` raises the - result of an operation as an exception. For example, this is the case for - :py:meth:`compare()`, always raises the boolean result as an exception - (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`). - - Most exceptions from protocol results also carry the :py:attr:`errnum` - attribute. - + The exceptions are accompanied by a dictionary with additional information. + All fields are optional and more fields may be added in the future. + Currently, ``python-ldap`` may set the following fields: + + * ``'result'``: a numeric code of the error class. + * ``'desc'``: string giving a description of the error class, as provided + by calling OpenLDAP's ``ldap_err2string`` on the ``result``. + * ``'info'``: string containing more information that the server may + have sent. The value is server-specific: for example, the OpenLDAP server + may send different info messages than Active Directory or 389-DS. + * ``'matched'``: truncated form of the name provided or alias. + dereferenced for the lowest entry (object or alias) that was matched. + * ``'msgid'``: ID of the matching asynchronous request. + This can be used in asynchronous code where :py:meth:`result()` raises the + result of an operation as an exception. For example, this is the case for + :py:meth:`~LDAPObject.compare()`, always raises the boolean result as an + exception (:py:exc:`COMPARE_TRUE` or :py:exc:`COMPARE_FALSE`). + * ``'ctrls'``: list of :py:class:`ldap.controls.LDAPControl` instances + attached to the error. + * ``'errno'``: the C ``errno``, usually set by system calls or ``libc`` + rather than the LDAP libraries. .. py:exception:: ADMINLIMIT_EXCEEDED @@ -515,14 +516,14 @@ The module defines the following exceptions: .. py:exception:: COMPARE_FALSE A compare operation returned false. - (This exception should never be seen because :py:meth:`compare()` returns - a boolean result.) + (This exception should only be seen asynchronous operations, because + :py:meth:`~LDAPObject.compare_s()` returns a boolean result.) .. py:exception:: COMPARE_TRUE A compare operation returned true. - (This exception should never be seen because :py:meth:`compare()` returns - a boolean result.) + (This exception should only be seen asynchronous operations, because + :py:meth:`~LDAPObject.compare_s()` returns a boolean result.) .. py:exception:: CONFIDENTIALITY_REQUIRED From c345916ded5c911442f52ca33eb34d96c81b6771 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 22:39:34 +0200 Subject: [PATCH 123/206] Bump version to 3.3.0 --- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index df29b60c..d99d2d00 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.2.0' +__version__ = '3.3.0' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index a3dd7ff2..7a0017c6 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.2.0' +__version__ = '3.3.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index a26c8ac1..f07f42dd 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.2.0' +__version__ = '3.3.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 6b8c986f..02ed317f 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -5,7 +5,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.2.0' +__version__ = '3.3.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From e885b621562a3c987934be3fba3873d21026bf5c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jun 2020 22:36:47 +0200 Subject: [PATCH 124/206] CHANGES: Prepare release entry for 3.3.0 --- CHANGES | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CHANGES b/CHANGES index 6e370160..711b665e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,54 @@ +Released 3.3.0 2020-06-18 + +Highlights: +* ``LDAPError`` now contains additional fields, such as ctrls, result, msgid +* ``passwd_s`` can now extract the newly generated password +* LDAP connections can now be made from a file descriptor + +This release is tested on Python 3.8, and the beta of Python 3.9. + +The following undocumented functions are deprecated and scheduled for removal: +- ``ldap.cidict.strlist_intersection`` +- ``ldap.cidict.strlist_minus`` +- ``ldap.cidict.strlist_union`` + +Modules/ +* Ensure ReconnectLDAPObject is not left in an inconsistent state after + a reconnection timeout +* Syncrepl now correctly parses SyncInfoMessage when the message is a syncIdSet +* Release GIL around global get/set option call +* Do not leak serverctrls in result functions +* Don't overallocate memory in attrs_from_List() +* Fix thread support check for Python 3 +* With OpenLDAP 2.4.48, use the new header openldap.h + +Lib/ +* Fix some edge cases regarding quoting in the schema tokenizer +* Fix escaping a single space in ldap.escape_dn_chars +* Fix string formatting in ldap.compare_ext_s +* Prefer iterating dict instead of calling dict.keys() + +Doc/ +* Clarify the relationship between initialize() and LDAPObject() +* Improve documentation of TLS options +* Update FAQ to include Samba AD-DC error message + "Operation unavailable without authentication" +* Fix several incorrect examples and demos + (but note that these are not yet tested) +* Update Debian installation instructions for Debian Buster +* Typo fixes in docs and docstrings + +Test/ +* Test and document error cases in ldap.compare_s +* Test if reconnection is done after connection loss +* Make test certificates valid for the far future +* Use slapd -Tt instead of slaptest + +Infrastructure: +* Mark the LICENCE file as a license for setuptools +* Use "unittest discover" rather than "setup.py test" to run tests + + ---------------------------------------------------------------- Released 3.2.0 2019-03-13 From 6aacd6f3634223bfa988b0fa679ffc58d5aead3e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 18 Jun 2020 16:42:56 +0200 Subject: [PATCH 125/206] Adjust notes for the release process - Git tags should be signed - Need to check python-ldap.org --- Doc/contributing.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index e6481fec..b11c18b5 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -221,10 +221,13 @@ If you are tasked with releasing python-ldap, remember to: * Merge all that (using pull requests). * Run ``python setup.py sdist``, and smoke-test the resulting package (install in a clean virtual environment, import ``ldap``). -* Create Git tag ``python-ldap-{version}``, and push it to GitHub and Pagure. +* Create GPG-signed Git tag: ``git tag -s python-ldap-{version}``. + Push it to GitHub and Pagure. * Release the ``sdist`` on PyPI. * Announce the release on the mailing list. Mention the Git hash. * Add the release's log from ``CHANGES`` on the `GitHub release page`_. +* Check that python-ldap.org shows the latest version; if not, adjust + things at readthedocs.org .. _GitHub release page: https://github.com/python-ldap/python-ldap/releases From 2be2c6bb8f44792d506cb1759fe06ff4cda3c5cf Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 22 Jun 2020 10:51:42 +0200 Subject: [PATCH 126/206] Remove Python 2 support python-ldap 3.4 will require Python 3.6 or newer. https://github.com/python-ldap/python-ldap/pull/358 Signed-off-by: Christian Heimes --- .travis.yml | 24 +-- Doc/bytes_mode.rst | 138 +++------------- Doc/faq.rst | 2 +- Doc/reference/ldap.rst | 22 ++- Doc/reference/ldapurl.rst | 3 +- Doc/sample_workflow.rst | 2 - Lib/ldap/cidict.py | 2 +- Lib/ldap/compat.py | 136 +++------------- Lib/ldap/controls/sss.py | 6 +- Lib/ldap/dn.py | 4 - Lib/ldap/functions.py | 3 - Lib/ldap/ldapobject.py | 312 ++++-------------------------------- Lib/ldap/schema/models.py | 4 +- Lib/ldap/schema/subentry.py | 3 +- Lib/ldapurl.py | 3 +- Lib/ldif.py | 6 +- Lib/slapdtest/_slapdtest.py | 6 +- Modules/LDAPObject.h | 10 +- Modules/ldapmodule.c | 7 - Tests/t_bind.py | 24 --- Tests/t_cext.py | 3 - Tests/t_edit.py | 11 -- Tests/t_ldap_dn.py | 3 - Tests/t_ldap_syncrepl.py | 20 --- Tests/t_ldapobject.py | 310 +++-------------------------------- Tests/t_ldapurl.py | 6 +- Tests/t_ldif.py | 3 - setup.py | 18 +-- tox.ini | 37 +---- 29 files changed, 134 insertions(+), 994 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22224cab..021fa401 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,25 +14,14 @@ addons: # Note: when updating Python versions, also change setup.py and tox.ini matrix: include: - - python: 2.7 - env: - - TOXENV=py27 - - WITH_GCOV=1 - - python: 3.4 - env: - - TOXENV=py34 - - WITH_GCOV=1 - - python: 3.5 - env: - - TOXENV=py35 - - WITH_GCOV=1 - python: 3.6 env: - TOXENV=py36 - WITH_GCOV=1 - - python: pypy + - python: pypy3 env: - - TOXENV=pypy + - TOXENV=pypy3 + - CFLAGS_std="-std=c99" - python: 3.7 env: - TOXENV=py37 @@ -53,10 +42,6 @@ matrix: - WITH_GCOV=1 dist: xenial sudo: true - - python: 2.7 - env: - - TOXENV=py2-nosasltls - - WITH_GCOV=1 - python: 3.6 env: - TOXENV=py3-nosasltls @@ -68,7 +53,7 @@ matrix: env: TOXENV=doc allow_failures: - env: - - TOXENV=pypy + - TOXENV=pypy3 env: global: @@ -87,4 +72,3 @@ install: - pip install tox-travis tox codecov script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" tox - diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index 0d207457..3a984bf0 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -1,34 +1,12 @@ .. _text-bytes: +.. _bytes_mode: Bytes/text management ===================== -Python 3 introduces a hard distinction between *text* (``str``) – sequences of -characters (formally, *Unicode codepoints*) – and ``bytes`` – sequences of -8-bit values used to encode *any* kind of data for storage or transmission. - -Python 2 has the same distinction between ``str`` (bytes) and -``unicode`` (text). -However, values can be implicitly converted between these types as needed, -e.g. when comparing or writing to disk or the network. -The implicit encoding and decoding can be a source of subtle bugs when not -designed and tested adequately. - -In python-ldap 2.x (for Python 2), bytes were used for all fields, -including those guaranteed to be text. - -From version 3.0, python-ldap uses text where appropriate. -On Python 2, the :ref:`bytes mode ` setting influences how text is -handled. - - -What's text, and what's bytes ------------------------------ - The LDAP protocol states that some fields (distinguished names, relative distinguished names, attribute names, queries) be encoded in UTF-8. -In python-ldap, these are represented as text (``str`` on Python 3, -``unicode`` on Python 2). +In python-ldap, these are represented as text (``str`` on Python 3). Attribute *values*, on the other hand, **MAY** contain any type of data, including text. @@ -38,102 +16,26 @@ Thus, attribute values are *always* treated as ``bytes``. Encoding/decoding to other formats – text, images, etc. – is left to the caller. -.. _bytes_mode: - -The bytes mode --------------- - -In Python 3, text values are represented as ``str``, the Unicode text type. - -In Python 2, the behavior of python-ldap 3.0 is influenced by a ``bytes_mode`` -argument to :func:`ldap.initialize`: - -``bytes_mode=True`` (backwards compatible): - Text values are represented as bytes (``str``) encoded using UTF-8. - -``bytes_mode=False`` (future compatible): - Text values are represented as ``unicode``. - -If not given explicitly, python-ldap will default to ``bytes_mode=True``, -but if a ``unicode`` value is supplied to it, it will warn and use that value. - -Backwards-compatible behavior is not scheduled for removal until Python 2 -itself reaches end of life. - - -Errors, warnings, and automatic encoding ----------------------------------------- - -While the type of values *returned* from python-ldap is always given by -``bytes_mode``, for Python 2 the behavior for “wrong-type” values *passed in* -can be controlled by the ``bytes_strictness`` argument to -:func:`ldap.initialize`: +Historical note +--------------- -``bytes_strictness='error'`` (default if ``bytes_mode`` is specified): - A ``TypeError`` is raised. - -``bytes_strictness='warn'`` (default when ``bytes_mode`` is not given explicitly): - A warning is raised, and the value is encoded/decoded - using the UTF-8 encoding. - - The warnings are of type :class:`~ldap.LDAPBytesWarning`, which - is a subclass of :class:`BytesWarning` designed to be easily - :ref:`filtered out ` if needed. - -``bytes_strictness='silent'``: - The value is automatically encoded/decoded using the UTF-8 encoding. - -On Python 3, ``bytes_strictness`` is ignored and a ``TypeError`` is always -raised. - -When setting ``bytes_strictness``, an explicit value for ``bytes_mode`` needs -to be given as well. - - -Porting recommendations ------------------------ - -Since end of life of Python 2 is coming in a few years, projects are strongly -urged to make their code compatible with Python 3. General instructions for -this are provided :ref:`in Python documentation ` and in the -`Conservative porting guide`_. - -.. _Conservative porting guide: https://portingguide.readthedocs.io/en/latest/ - - -When porting from python-ldap 2.x, users are advised to update their code -to set ``bytes_mode=False``, and fix any resulting failures. - -The typical usage is as follows. -Note that only the result's *values* are of the ``bytes`` type: - -.. code-block:: pycon - - >>> import ldap - >>> con = ldap.initialize('ldap://localhost:389', bytes_mode=False) - >>> con.simple_bind_s(u'login', u'secret_password') - >>> results = con.search_s(u'ou=people,dc=example,dc=org', ldap.SCOPE_SUBTREE, u"(cn=Raphaël)") - >>> results - [ - ("cn=Raphaël,ou=people,dc=example,dc=org", { - 'cn': [b'Rapha\xc3\xabl'], - 'sn': [b'Barrois'], - }), - ] - - -.. _filter-bytes-warning: - -Filtering warnings ------------------- +Python 3 introduced a hard distinction between *text* (``str``) – sequences of +characters (formally, *Unicode codepoints*) – and ``bytes`` – sequences of +8-bit values used to encode *any* kind of data for storage or transmission. -The bytes mode warnings can be filtered out and ignored with a -simple filter. +Python 2 had the same distinction between ``str`` (bytes) and +``unicode`` (text). +However, values could be implicitly converted between these types as needed, +e.g. when comparing or writing to disk or the network. +The implicit encoding and decoding can be a source of subtle bugs when not +designed and tested adequately. -.. code-block:: python +In python-ldap 2.x (for Python 2), bytes were used for all fields, +including those guaranteed to be text. - import warnings - import ldap +From version 3.0 to 3.3, python-ldap uses text where appropriate. +On Python 2, special ``bytes_mode`` and ``bytes_strictness`` settings +influenced how text was handled. - if hasattr(ldap, 'LDAPBytesWarning'): - warnings.simplefilter('ignore', ldap.LDAPBytesWarning) +From version 3.3 on, only Python 3 is supported. The “bytes mode” settings +are deprecated and do nothing. diff --git a/Doc/faq.rst b/Doc/faq.rst index 38a645fe..2152873b 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -29,7 +29,7 @@ Usage .. _pyldap: https://pypi.org/project/pyldap/ -**Q**: Does it work with Python 2.6? (1.5|2.0|2.1|2.2|2.3|2.4|2.5)? +**Q**: Does it work with Python 2.7? (1.5|2.0|2.1|2.2|2.3|2.4|2.5|2.6|2.7)? **A**: No. Old versions of python-ldap are still available from PyPI, though. diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index a6d69287..89c98064 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -29,7 +29,7 @@ Functions This module defines the following functions: -.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=None, [bytes_strictness=None, [fileno=None]]]]]]) -> LDAPObject object +.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [fileno=None]]]]) -> LDAPObject object Initializes a new connection object for accessing the given LDAP server, and return an :class:`~ldap.ldapobject.LDAPObject` used to perform operations @@ -63,10 +63,6 @@ This module defines the following functions: *trace_file* specifies a file-like object as target of the debug log and *trace_stack_limit* specifies the stack limit of tracebacks in debug log. - The *bytes_mode* and *bytes_strictness* arguments specify text/bytes - behavior under Python 2. - See :ref:`text-bytes` for a complete documentation. - Possible values for *trace_level* are :py:const:`0` for no logging, :py:const:`1` for only logging the method calls with arguments, @@ -78,6 +74,10 @@ This module defines the following functions: Any additional keyword arguments are passed to ``LDAPObject``. It is also fine to instantiate a ``LDAPObject`` (or a subclass) directly. + The function additionally takes *bytes_mode* and *bytes_strictness* keyword + arguments, which are deprecated and ignored. See :ref:`bytes_mode` for + details. + .. seealso:: :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator @@ -86,6 +86,10 @@ This module defines the following functions: The *fileno* argument was added. + .. deprecated:: 3.4 + + *bytes_mode* and *bytes_strictness* arguments are deprecated. + .. py:function:: get_option(option) -> int|string @@ -730,12 +734,16 @@ Warnings .. py:class:: LDAPBytesWarning - Raised when bytes/text mismatch in non-strict bytes mode. + This warning is deprecated. python-ldap no longer raises it. - See :ref:`bytes_mode` for details. + It used to be raised under Python 2 when bytes/text mismatch in non-strict + bytes mode. See :ref:`bytes_mode` for details. .. versionadded:: 3.0.0 + .. versionchanged:: 3.4.0 + + Deprecated. .. _ldap-objects: diff --git a/Doc/reference/ldapurl.rst b/Doc/reference/ldapurl.rst index 96b5ed24..de86de7e 100644 --- a/Doc/reference/ldapurl.rst +++ b/Doc/reference/ldapurl.rst @@ -9,8 +9,7 @@ This module parses and generates LDAP URLs. It is implemented in pure Python and does not rely on any non-standard modules. Therefore it can be used stand- -alone without the rest of the python-ldap package. Compatibility note: This -module has been solely tested on Python 2.x and above. +alone without the rest of the python-ldap package. .. seealso:: diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index 8a43553d..60d60cac 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -31,8 +31,6 @@ python-ldap won't affect the rest of your system:: $ python3 -m venv __venv__ -(For Python 2, install `virtualenv`_ and use it instead of ``python3 -m venv``.) - .. _git: https://git-scm.com/ .. _virtualenv: https://virtualenv.pypa.io/en/stable/ diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 48aeacb4..a5ac29e0 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -7,7 +7,7 @@ """ import warnings -from ldap.compat import MutableMapping +from collections.abc import MutableMapping from ldap import __version__ diff --git a/Lib/ldap/compat.py b/Lib/ldap/compat.py index 901457b2..a287ce4e 100644 --- a/Lib/ldap/compat.py +++ b/Lib/ldap/compat.py @@ -1,115 +1,23 @@ """Compatibility wrappers for Py2/Py3.""" - -import sys -import os - -if sys.version_info[0] < 3: - from UserDict import UserDict, IterableUserDict - from urllib import quote - from urllib import quote_plus - from urllib import unquote as urllib_unquote - from urllib import urlopen - from urlparse import urlparse - from collections import MutableMapping - - def unquote(uri): - """Specialized unquote that uses UTF-8 for parsing.""" - uri = uri.encode('ascii') - unquoted = urllib_unquote(uri) - return unquoted.decode('utf-8') - - # Old-style of re-raising an exception is SyntaxError in Python 3, - # so hide behind exec() so the Python 3 parser doesn't see it - exec('''def reraise(exc_type, exc_value, exc_traceback): - """Re-raise an exception given information from sys.exc_info() - - Note that unlike six.reraise, this does not support replacing the - traceback. All arguments must come from a single sys.exc_info() call. - """ - raise exc_type, exc_value, exc_traceback - ''') - -else: - from collections import UserDict - IterableUserDict = UserDict - from urllib.parse import quote, quote_plus, unquote, urlparse - from urllib.request import urlopen - from collections.abc import MutableMapping - - def reraise(exc_type, exc_value, exc_traceback): - """Re-raise an exception given information from sys.exc_info() - - Note that unlike six.reraise, this does not support replacing the - traceback. All arguments must come from a single sys.exc_info() call. - """ - # In Python 3, all exception info is contained in one object. - raise exc_value - -try: - from shutil import which -except ImportError: - # shutil.which() from Python 3.6 - # "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, - # 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; - # All Rights Reserved" - def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - - """ - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) - and not os.path.isdir(fn)) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if not os.curdir in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if not normdir in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None +import warnings + +warnings.warn( + "The ldap.compat module is deprecated and will be removed in the future", + DeprecationWarning, +) + +from collections import UserDict +IterableUserDict = UserDict +from urllib.parse import quote, quote_plus, unquote, urlparse +from urllib.request import urlopen +from collections.abc import MutableMapping +from shutil import which + +def reraise(exc_type, exc_value, exc_traceback): + """Re-raise an exception given information from sys.exc_info() + + Note that unlike six.reraise, this does not support replacing the + traceback. All arguments must come from a single sys.exc_info() call. + """ + # In Python 3, all exception info is contained in one object. + raise exc_value diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 5cdfbdac..5001987b 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -22,10 +22,6 @@ from pyasn1.type import univ, namedtype, tag, namedval, constraint from pyasn1.codec.ber import encoder, decoder -PY2 = sys.version_info[0] <= 2 -if not PY2: - basestring = str - # SortKeyList ::= SEQUENCE OF SEQUENCE { # attributeType AttributeDescription, @@ -63,7 +59,7 @@ def __init__( ): RequestControl.__init__(self,self.controlType,criticality) self.ordering_rules = ordering_rules - if isinstance(ordering_rules, basestring): + if isinstance(ordering_rules, str): ordering_rules = [ordering_rules] for rule in ordering_rules: rule = rule.split(':') diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index a066ac19..886ff877 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -3,8 +3,6 @@ See https://www.python-ldap.org/ for details. """ - -import sys from ldap.pkginfo import __version__ import _ldap @@ -47,8 +45,6 @@ def str2dn(dn,flags=0): """ if not dn: return [] - if sys.version_info[0] < 3 and isinstance(dn, unicode): - dn = dn.encode('utf-8') return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags) diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 31ab00f7..8c9dc626 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -27,9 +27,6 @@ # Tracing is only supported in debugging mode import traceback -# See _raise_byteswarning in ldapobject.py -_LDAP_WARN_SKIP_FRAME = True - def _ldap_function_call(lock,func,*args,**kwargs): """ diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index b44d90cd..e29ee9d7 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -3,9 +3,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - from os import strerror from ldap.pkginfo import __version__, __author__, __license__ @@ -28,43 +25,19 @@ from ldap.schema import SCHEMA_ATTRS from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples from ldap.extop import ExtendedRequest,ExtendedResponse,PasswordModifyResponse -from ldap.compat import reraise from ldap import LDAPError -PY2 = sys.version_info[0] <= 2 -if PY2: - text_type = unicode -else: - text_type = str - - -# See SimpleLDAPObject._bytesify_input -_LDAP_WARN_SKIP_FRAME = True class LDAPBytesWarning(BytesWarning): - """python-ldap bytes mode warning - """ - -def _raise_byteswarning(message): - """Raise LDAPBytesWarning - """ + """Python 2 bytes mode warning""" - # Call stacks that raise the warning tend to be complicated, so - # getting a useful stacklevel is tricky. - # We walk stack frames, ignoring functions in uninteresting files, - # based on the _LDAP_WARN_SKIP_FRAME marker in globals(). - stacklevel = 2 - try: - getframe = sys._getframe - except AttributeError: - pass - else: - frame = sys._getframe(stacklevel) - while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'): - stacklevel += 1 - frame = frame.f_back - warnings.warn(message, LDAPBytesWarning, stacklevel=stacklevel+1) + def __init__(self, *args, **kwargs): + warnings.warn( + "LDAPBytesWarning is deprecated and will be removed in the future", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): @@ -114,185 +87,16 @@ def __init__( self.timeout = -1 self.protocol_version = ldap.VERSION3 - # Bytes mode - # ---------- - - if PY2: - if bytes_mode is None: - bytes_mode = True - if bytes_strictness is None: - _raise_byteswarning( - "Under Python 2, python-ldap uses bytes by default. " - "This will be removed in Python 3 (no bytes for " - "DN/RDN/field names). " - "Please call initialize(..., bytes_mode=False) explicitly.") - bytes_strictness = 'warn' - else: - if bytes_strictness is None: - bytes_strictness = 'error' - else: - if bytes_mode: - raise ValueError("bytes_mode is *not* supported under Python 3.") - bytes_mode = False - bytes_strictness = 'error' - self.bytes_mode = bytes_mode - self.bytes_strictness = bytes_strictness - - def _bytesify_input(self, arg_name, value): - """Adapt a value following bytes_mode in Python 2. - - In Python 3, returns the original value unmodified. - - With bytes_mode ON, takes bytes or None and returns bytes or None. - With bytes_mode OFF, takes unicode or None and returns bytes or None. - - For the wrong argument type (unicode or bytes, respectively), - behavior depends on the bytes_strictness setting. - In all cases, bytes or None are returned (or an exception is raised). - """ - if not PY2: - return value - if value is None: - return value - - elif self.bytes_mode: - if isinstance(value, bytes): - return value - elif self.bytes_strictness == 'silent': - pass - elif self.bytes_strictness == 'warn': - _raise_byteswarning( - "Received non-bytes value for '{}' in bytes mode; " - "please choose an explicit " - "option for bytes_mode on your LDAP connection".format(arg_name)) - else: - raise TypeError( - "All provided fields *must* be bytes when bytes mode is on; " - "got type '{}' for '{}'.".format(type(value).__name__, arg_name) - ) - return value.encode('utf-8') - else: - if isinstance(value, unicode): - return value.encode('utf-8') - elif self.bytes_strictness == 'silent': - pass - elif self.bytes_strictness == 'warn': - _raise_byteswarning( - "Received non-text value for '{}' with bytes_mode off and " - "bytes_strictness='warn'".format(arg_name)) - else: - raise TypeError( - "All provided fields *must* be text when bytes mode is off; " - "got type '{}' for '{}'.".format(type(value).__name__, arg_name) - ) - return value - - def _bytesify_modlist(self, arg_name, modlist, with_opcode): - """Adapt a modlist according to bytes_mode. - - A modlist is a tuple of (op, attr, value), where: - - With bytes_mode ON, attr is checked to be bytes - - With bytes_mode OFF, attr is converted from unicode to bytes - - value is *always* bytes - """ - if not PY2: - return modlist - if with_opcode: - return tuple( - (op, self._bytesify_input(arg_name, attr), val) - for op, attr, val in modlist - ) - else: - return tuple( - (self._bytesify_input(arg_name, attr), val) - for attr, val in modlist - ) - - def _unbytesify_text_value(self, value): - """Adapt a 'known text, UTF-8 encoded' returned value following bytes_mode. - - With bytes_mode ON, takes bytes or None and returns bytes or None. - With bytes_mode OFF, takes bytes or None and returns unicode or None. - - This function should only be applied on field *values*; distinguished names - or field *names* are already natively handled in result4. - """ - if value is None: - return value - - # Preserve logic of assertions only under Python 2 - if PY2: - assert isinstance(value, bytes), "Expected bytes value, got text instead (%r)" % (value,) - - if self.bytes_mode: - return value - else: - return value.decode('utf-8') - - def _maybe_rebytesify_text(self, value): - """Re-encodes text to bytes if needed by bytes_mode. + if bytes_mode: + raise ValueError("bytes_mode is *not* supported under Python 3.") - Takes unicode (and checks for it), and returns: - - bytes under bytes_mode - - unicode otherwise. - """ - if not PY2: - return value + @property + def bytes_mode(self): + return False - if value is None: - return value - - assert isinstance(value, text_type), "Should return text, got bytes instead (%r)" % (value,) - if not self.bytes_mode: - return value - else: - return value.encode('utf-8') - - def _bytesify_result_value(self, result_value): - """Applies bytes_mode to a result value. - - Such a value can either be: - - a dict mapping an attribute name to its list of values - (where attribute names are unicode and values bytes) - - a list of referals (which are unicode) - """ - if not PY2: - return result_value - if hasattr(result_value, 'items'): - # It's a attribute_name: [values] dict - return { - self._maybe_rebytesify_text(key): value - for (key, value) in result_value.items() - } - elif isinstance(result_value, bytes): - return result_value - else: - # It's a list of referals - # Example value: - # [u'ldap://DomainDnsZones.xxxx.root.local/DC=DomainDnsZones,DC=xxxx,DC=root,DC=local'] - return [self._maybe_rebytesify_text(referal) for referal in result_value] - - def _bytesify_results(self, results, with_ctrls=False): - """Converts a "results" object according to bytes_mode. - - Takes: - - a list of (dn, {field: [values]}) if with_ctrls is False - - a list of (dn, {field: [values]}, ctrls) if with_ctrls is True - - And, if bytes_mode is on, converts dn and fields to bytes. - """ - if not PY2: - return results - if with_ctrls: - return [ - (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields), ctrls) - for (dn, fields, ctrls) in results - ] - else: - return [ - (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields)) - for (dn, fields) in results - ] + @property + def bytes_strictness(self): + return 'error' def _ldap_lock(self,desc=''): if ldap.LIBLDAP_R: @@ -326,7 +130,6 @@ def _ldap_call(self,func,*args,**kwargs): finally: self._ldap_object_lock.release() except LDAPError as e: - exc_type,exc_value,exc_traceback = sys.exc_info() try: if 'info' not in e.args[0] and 'errno' in e.args[0]: e.args[0]['info'] = strerror(e.args[0]['errno']) @@ -334,10 +137,7 @@ def _ldap_call(self,func,*args,**kwargs): pass if __debug__ and self._trace_level>=2: self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e))) - try: - reraise(exc_type, exc_value, exc_traceback) - finally: - exc_type = exc_value = exc_traceback = None + raise else: if __debug__ and self._trace_level>=2: if not diagnostic_message_success is None: @@ -413,9 +213,6 @@ def add_ext(self,dn,modlist,serverctrls=None,clientctrls=None): The parameter modlist is similar to the one passed to modify(), except that no operation integer need be included in the tuples. """ - if PY2: - dn = self._bytesify_input('dn', dn) - modlist = self._bytesify_modlist('modlist', modlist, with_opcode=False) return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None): @@ -440,9 +237,6 @@ def simple_bind(self,who=None,cred=None,serverctrls=None,clientctrls=None): """ simple_bind([who='' [,cred='']]) -> int """ - if PY2: - who = self._bytesify_input('who', who) - cred = self._bytesify_input('cred', cred) return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def simple_bind_s(self,who=None,cred=None,serverctrls=None,clientctrls=None): @@ -519,9 +313,6 @@ def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None): A design bug in the library prevents value from containing nul characters. """ - if PY2: - dn = self._bytesify_input('dn', dn) - attr = self._bytesify_input('attr', attr) return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): @@ -552,7 +343,6 @@ def delete_ext(self,dn,serverctrls=None,clientctrls=None): form returns the message id of the initiated request, and the result can be obtained from a subsequent call to result(). """ - dn = self._bytesify_input('dn', dn) return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def delete_ext_s(self,dn,serverctrls=None,clientctrls=None): @@ -601,9 +391,6 @@ def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None): """ modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int """ - if PY2: - dn = self._bytesify_input('dn', dn) - modlist = self._bytesify_modlist('modlist', modlist, with_opcode=True) return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None): @@ -657,10 +444,6 @@ def modrdn_s(self,dn,newrdn,delold=1): return self.rename_s(dn,newrdn,None,delold) def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None): - if PY2: - user = self._bytesify_input('user', user) - oldpw = self._bytesify_input('oldpw', oldpw) - newpw = self._bytesify_input('newpw', newpw) return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def passwd_s(self, user, oldpw, newpw, serverctrls=None, clientctrls=None, extract_newpw=False): @@ -689,10 +472,6 @@ def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls This actually corresponds to the rename* routines in the LDAP-EXT C API library. """ - if PY2: - dn = self._bytesify_input('dn', dn) - newrdn = self._bytesify_input('newrdn', newrdn) - newsuperior = self._bytesify_input('newsuperior', newsuperior) return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None): @@ -781,8 +560,6 @@ def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermedi if add_ctrls: resp_data = [ (t,r,DecodeControlTuples(c,resp_ctrl_classes)) for t,r,c in resp_data ] decoded_resp_ctrls = DecodeControlTuples(resp_ctrls,resp_ctrl_classes) - if resp_data is not None: - resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls) return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): @@ -830,24 +607,8 @@ def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverct The amount of search results retrieved can be limited with the sizelimit parameter if non-zero. """ - - if PY2: - base = self._bytesify_input('base', base) - if filterstr is None: - # workaround for default argument, - # see https://github.com/python-ldap/python-ldap/issues/147 - if self.bytes_mode: - filterstr = b'(objectClass=*)' - else: - filterstr = u'(objectClass=*)' - else: - filterstr = self._bytesify_input('filterstr', filterstr) - if attrlist is not None: - attrlist = tuple(self._bytesify_input('attrlist', a) - for a in attrlist) - else: - if filterstr is None: - filterstr = '(objectClass=*)' + if filterstr is None: + filterstr = '(objectClass=*)' return self._ldap_call( self._l.search_ext, base,scope,filterstr, @@ -944,12 +705,8 @@ def search_subschemasubentry_s(self,dn=None): Returns: None or text/bytes depending on bytes_mode. """ - if self.bytes_mode: - empty_dn = b'' - attrname = b'subschemaSubentry' - else: - empty_dn = u'' - attrname = u'subschemaSubentry' + empty_dn = u'' + attrname = u'subschemaSubentry' if dn is None: dn = empty_dn try: @@ -972,9 +729,8 @@ def search_subschemasubentry_s(self,dn=None): # If dn was already root DSE we can return here return None else: - # With legacy bytes mode, return bytes; otherwise, since this is a DN, - # RFCs impose that the field value *can* be decoded to UTF-8. - return self._unbytesify_text_value(search_subschemasubentry_dn) + if search_subschemasubentry_dn is not None: + return search_subschemasubentry_dn.decode('utf-8') except IndexError: return None @@ -1002,14 +758,9 @@ def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None): """ Returns the sub schema sub entry's data """ - if self.bytes_mode: - filterstr = b'(objectClass=subschema)' - if attrs is None: - attrs = [attr.encode('utf-8') for attr in SCHEMA_ATTRS] - else: - filterstr = u'(objectClass=subschema)' - if attrs is None: - attrs = SCHEMA_ATTRS + filterstr = u'(objectClass=subschema)' + if attrs is None: + attrs = SCHEMA_ATTRS try: subschemasubentry = self.read_s( subschemasubentry_dn, @@ -1044,12 +795,8 @@ def read_rootdse_s(self, filterstr=None, attrlist=None): """ convenience wrapper around read_s() for reading rootDSE """ - if self.bytes_mode: - base = b'' - attrlist = attrlist or [b'*', b'+'] - else: - base = u'' - attrlist = attrlist or [u'*', u'+'] + base = u'' + attrlist = attrlist or [u'*', u'+'] ldap_rootdse = self.read_s( base, filterstr=filterstr, @@ -1062,10 +809,7 @@ def get_naming_contexts(self): returns all attribute values of namingContexts in rootDSE if namingContexts is not present (not readable) then empty list is returned """ - if self.bytes_mode: - name = b'namingContexts' - else: - name = u'namingContexts' + name = u'namingContexts' return self.read_rootdse_s( attrlist=[name] ).get(name, []) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 7520b2b1..0ebd61e7 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -7,7 +7,7 @@ import sys import ldap.cidict -from ldap.compat import IterableUserDict +from collections import UserDict as IterableUserDict from ldap.schema.tokenizer import split_tokens,extract_tokens @@ -47,7 +47,7 @@ class SchemaElement: } def __init__(self,schema_element_str=None): - if sys.version_info >= (3, 0) and isinstance(schema_element_str, bytes): + if isinstance(schema_element_str, bytes): schema_element_str = schema_element_str.decode('utf-8') if schema_element_str: l = split_tokens(schema_element_str) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 215f148e..86e996f0 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -5,10 +5,9 @@ """ import copy +from urllib.request import urlopen import ldap.cidict,ldap.schema - -from ldap.compat import urlopen from ldap.schema.models import * import ldapurl diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 7a0017c6..0e03fcc2 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -16,7 +16,8 @@ 'LDAPUrlExtension','LDAPUrlExtensions','LDAPUrl' ] -from ldap.compat import quote, unquote, MutableMapping +from collections.abc import MutableMapping +from urllib.parse import quote, unquote LDAP_SCOPE_BASE = 0 LDAP_SCOPE_ONELEVEL = 1 diff --git a/Lib/ldif.py b/Lib/ldif.py index f07f42dd..0afebd84 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,9 +3,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - __version__ = '3.3.0' __all__ = [ @@ -25,7 +22,8 @@ from io import StringIO import warnings -from ldap.compat import urlparse, urlopen +from urllib.parse import urlparse +from urllib.request import urlopen attrtype_pattern = r'[\w;.-]+(;[\w_-]+)*' attrvalue_pattern = r'(([^,]|\\,)+|".*?")' diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index de4c3e53..cebd0df1 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -4,9 +4,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - import os import socket import sys @@ -16,12 +13,13 @@ import atexit from logging.handlers import SysLogHandler import unittest +from shutil import which +from urllib.parse import quote_plus # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' import ldap -from ldap.compat import quote_plus, which HERE = os.path.abspath(os.path.dirname(__file__)) diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index 1b6066db..4af0b382 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -5,15 +5,9 @@ #include "common.h" -#if PYTHON_API_VERSION < 1007 -typedef PyObject *_threadstate; -#else -typedef PyThreadState *_threadstate; -#endif - typedef struct { PyObject_HEAD LDAP *ldap; - _threadstate _save; /* for thread saving on referrals */ + PyThreadState *_save; /* for thread saving on referrals */ int valid; } LDAPObject; @@ -36,7 +30,7 @@ extern LDAPObject *newLDAPObject(LDAP *); #define LDAP_END_ALLOW_THREADS( l ) \ { \ LDAPObject *lo = (l); \ - _threadstate _save = lo->_save; \ + PyThreadState *_save = lo->_save; \ lo->_save = NULL; \ PyEval_RestoreThread( _save ); \ } diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 8bd55ab4..34d5a24c 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -72,13 +72,6 @@ init_ldap_module(void) LDAPinit_functions(d); LDAPinit_control(d); - /* Marker for LDAPBytesWarning stack walking - * See _raise_byteswarning in ldapobject.py - */ - if (PyModule_AddIntConstant(m, "_LDAP_WARN_SKIP_FRAME", 1) != 0) { - return NULL; - } - /* Check for errors */ if (PyErr_Occurred()) Py_FatalError("can't initialize module _ldap"); diff --git a/Tests/t_bind.py b/Tests/t_bind.py index 3e2b67f8..ba90c4cd 100644 --- a/Tests/t_bind.py +++ b/Tests/t_bind.py @@ -1,14 +1,3 @@ -from __future__ import unicode_literals - -import sys - -if sys.version_info[0] <= 2: - PY2 = True - text_type = unicode -else: - PY2 = False - text_type = str - import os import unittest @@ -44,19 +33,6 @@ def test_unicode_bind(self): l = self._get_ldapobject(False) l.simple_bind("CN=user", self.unicode_val) - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_unicode_bind_bytesmode(self): - l = self._get_ldapobject(True) - with self.assertRaises(TypeError): - l.simple_bind_s(self.dn_unicode, self.unicode_val_bytes) - - with self.assertRaises(TypeError): - l.simple_bind_s(self.dn_bytes, self.unicode_val) - - # Works when encoded to UTF-8 - with self.assertRaises(ldap.INVALID_CREDENTIALS): - l.simple_bind_s(self.dn_bytes, self.unicode_val_bytes) - def test_unicode_bind_no_bytesmode(self): l = self._get_ldapobject(False) with self.assertRaises(TypeError): diff --git a/Tests/t_cext.py b/Tests/t_cext.py index a19d3c33..2fa4f56c 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -4,9 +4,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - import contextlib import errno import os diff --git a/Tests/t_edit.py b/Tests/t_edit.py index a5b3f657..f79ff18f 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -1,14 +1,3 @@ -from __future__ import unicode_literals - -import sys - -if sys.version_info[0] <= 2: - PY2 = True - text_type = unicode -else: - PY2 = False - text_type = str - import os import unittest diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index fd36f866..d62ec719 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -4,9 +4,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - # from Python's standard lib import os import unittest diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index b8a6ab63..7ec97075 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -4,19 +4,11 @@ See https://www.python-ldap.org/ for details. """ - - import os import shelve -import sys import unittest import binascii -if sys.version_info[0] <= 2: - PY2 = True -else: - PY2 = False - # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -434,18 +426,6 @@ def setUp(self): self.suffix = self.server.suffix -@unittest.skipUnless(PY2, "no bytes_mode under Py3") -class TestSyncreplBytesMode(BaseSyncreplTests, SlapdTestCase): - def setUp(self): - super(TestSyncreplBytesMode, self).setUp() - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn.encode('utf-8'), - self.server.root_pw.encode('utf-8'), - bytes_mode=True - ) - self.suffix = self.server.suffix.encode('utf-8') - class DecodeSyncreplProtoTests(unittest.TestCase): """ Tests of the ASN.1 decoder for tricky cases or past issues to ensure that diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 459ba768..75da0f43 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -4,25 +4,11 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - -import sys - -if sys.version_info[0] <= 2: - PY2 = True - text_type = unicode -else: - PY2 = False - text_type = str - import errno -import contextlib import linecache import os import socket import unittest -import warnings import pickle # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -115,44 +101,29 @@ def test_reject_bytes_base(self): l.search_s( base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', ['*'] ) - if PY2: - self.assertIn( - u"got type 'str' for 'base'", text_type(e.exception) - ) - elif sys.version_info >= (3, 5, 0): - # Python 3.4.x does not include 'search_ext()' in message - self.assertEqual( - "search_ext() argument 1 must be str, not bytes", - text_type(e.exception) - ) + # Python 3.4.x does not include 'search_ext()' in message + self.assertEqual( + "search_ext() argument 1 must be str, not bytes", + str(e.exception) + ) with self.assertRaises(TypeError) as e: l.search_s( base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*'] ) - if PY2: - self.assertIn( - u"got type 'str' for 'filterstr'", text_type(e.exception) - ) - elif sys.version_info >= (3, 5, 0): - self.assertEqual( - "search_ext() argument 3 must be str, not bytes", - text_type(e.exception) - ) + self.assertEqual( + "search_ext() argument 3 must be str, not bytes", + str(e.exception) + ) with self.assertRaises(TypeError) as e: l.search_s( base, ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*'] ) - if PY2: - self.assertIn( - u"got type 'str' for 'attrlist'", text_type(e.exception) - ) - elif sys.version_info >= (3, 5, 0): - self.assertEqual( - ('attrs_from_List(): expected string in list', b'*'), - e.exception.args - ) + self.assertEqual( + ('attrs_from_List(): expected string in list', b'*'), + e.exception.args + ) def test_search_keys_are_text(self): base = self.server.suffix @@ -161,143 +132,12 @@ def test_search_keys_are_text(self): result.sort() dn, fields = result[0] self.assertEqual(dn, 'cn=Foo1,%s' % base) - self.assertEqual(type(dn), text_type) + self.assertEqual(type(dn), str) for key, values in fields.items(): - self.assertEqual(type(key), text_type) + self.assertEqual(type(key), str) for value in values: self.assertEqual(type(value), bytes) - def _get_bytes_ldapobject(self, explicit=True, **kwargs): - if explicit: - kwargs.setdefault('bytes_mode', True) - else: - kwargs = {} - return self._open_ldap_conn( - who=self.server.root_dn.encode('utf-8'), - cred=self.server.root_pw.encode('utf-8'), - **kwargs - ) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_search_requires_bytes(self): - l = self._get_bytes_ldapobject() - base = self.server.suffix - - with self.assertRaises(TypeError): - l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) - with self.assertRaises(TypeError): - l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) - with self.assertRaises(TypeError): - l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_search_results_have_bytes(self): - l = self._get_bytes_ldapobject() - base = self.server.suffix - result = l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) - result.sort() - dn, fields = result[0] - self.assertEqual(dn, b'cn=Foo1,%s' % base) - self.assertEqual(type(dn), bytes) - for key, values in fields.items(): - self.assertEqual(type(key), bytes) - for value in values: - self.assertEqual(type(value), bytes) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_search_defaults(self): - l = self._get_bytes_ldapobject() - base = 'cn=Foo1,' + self.server.suffix - kwargs = dict( - base=base.encode('utf-8'), - scope=ldap.SCOPE_SUBTREE, - # filterstr=b'(objectClass=*)' - ) - expected = [ - ( - base, - {'cn': [b'Foo1'], 'objectClass': [b'organizationalRole']} - ), - ] - - result = l.search_s(**kwargs) - self.assertEqual(result, expected) - result = l.search_st(**kwargs) - self.assertEqual(result, expected) - result = l.search_ext_s(**kwargs) - self.assertEqual(result, expected) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_unset_bytesmode_search_warns_bytes(self): - l = self._get_bytes_ldapobject(explicit=False) - base = self.server.suffix - - l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) - l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) - l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) - - def _search_wrong_type(self, bytes_mode, strictness): - if bytes_mode: - l = self._get_bytes_ldapobject(bytes_strictness=strictness) - else: - l = self._open_ldap_conn(bytes_mode=False, - bytes_strictness=strictness) - base = 'cn=Foo1,' + self.server.suffix - if not bytes_mode: - base = base.encode('utf-8') - result = l.search_s(base, scope=ldap.SCOPE_SUBTREE) - return result[0][-1]['cn'] - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_silent(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - self._search_wrong_type(bytes_mode=True, strictness='silent') - self.assertEqual(w, []) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_warn(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - self._search_wrong_type(bytes_mode=True, strictness='warn') - self.assertEqual(len(w), 1) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_bytesmode_error(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - with self.assertRaises(TypeError): - self._search_wrong_type(bytes_mode=True, strictness='error') - self.assertEqual(w, []) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_textmode_silent(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - self._search_wrong_type(bytes_mode=True, strictness='silent') - self.assertEqual(w, []) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_textmode_warn(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - self._search_wrong_type(bytes_mode=True, strictness='warn') - self.assertEqual(len(w), 1) - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_textmode_error(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - with self.assertRaises(TypeError): - self._search_wrong_type(bytes_mode=True, strictness='error') - self.assertEqual(w, []) - def test_search_accepts_unicode_dn(self): base = self.server.suffix l = self._ldap_conn @@ -319,7 +159,7 @@ def test_attrlist_accepts_unicode(self): result.sort() for dn, attrs in result: - self.assertIsInstance(dn, text_type) + self.assertIsInstance(dn, str) self.assertEqual(attrs, {}) def test001_search_subtree(self): @@ -422,7 +262,7 @@ def test_find_unique_entry(self): def test_search_subschema(self): l = self._ldap_conn dn = l.search_subschemasubentry_s() - self.assertIsInstance(dn, text_type) + self.assertIsInstance(dn, str) self.assertEqual(dn, "cn=Subschema") subschema = l.read_subschemasubentry_s(dn) self.assertIsInstance(subschema, dict) @@ -437,25 +277,6 @@ def test_search_subschema(self): ] ) - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_search_subschema_have_bytes(self): - l = self._get_bytes_ldapobject() - dn = l.search_subschemasubentry_s() - self.assertIsInstance(dn, bytes) - self.assertEqual(dn, b"cn=Subschema") - subschema = l.read_subschemasubentry_s(dn) - self.assertIsInstance(subschema, dict) - self.assertEqual( - sorted(subschema), - [ - b'attributeTypes', - b'ldapSyntaxes', - b'matchingRuleUse', - b'matchingRules', - b'objectClasses' - ] - ) - def test004_enotconn(self): l = self.ldap_object_class('ldap://127.0.0.1:42') try: @@ -516,45 +337,9 @@ def test_simple_bind_noarg(self): l.simple_bind_s(None, None) self.assertEqual(l.whoami_s(), u'') - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_ldapbyteswarning(self): - self.assertIsSubclass(ldap.LDAPBytesWarning, BytesWarning) - self.assertIsSubclass(ldap.LDAPBytesWarning, Warning) - self.assertIsInstance(self.server.suffix, text_type) - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - conn = self._get_bytes_ldapobject(explicit=False) - result = conn.search_s( - self.server.suffix, - ldap.SCOPE_SUBTREE, - b'(cn=Foo*)', - attrlist=[b'*'], - ) - self.assertEqual(len(result), 4) - - # ReconnectLDAP only emits one warning - self.assertGreaterEqual(len(w), 1, w) - msg = w[-1] - self.assertIs(msg.category, ldap.LDAPBytesWarning) - self.assertEqual( - text_type(msg.message), - "Received non-bytes value for 'base' in bytes " - "mode; please choose an explicit option for bytes_mode on your " - "LDAP connection" - ) - - @contextlib.contextmanager - def catch_byteswarnings(self, *args, **kwargs): - with warnings.catch_warnings(record=True) as w: - conn = self._get_bytes_ldapobject(*args, **kwargs) - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - yield conn, w - def _check_byteswarning(self, warning, expected_message): self.assertIs(warning.category, ldap.LDAPBytesWarning) - self.assertIn(expected_message, text_type(warning.message)) + self.assertIn(expected_message, str(warning.message)) def _normalize(filename): # Python 2 likes to report the ".pyc" file in warnings, @@ -571,44 +356,6 @@ def _normalize(filename): linecache.getline(warning.filename, warning.lineno) ) - def _test_byteswarning_level_search(self, methodname): - with self.catch_byteswarnings(explicit=False) as (conn, w): - method = getattr(conn, methodname) - result = method( - self.server.suffix.encode('utf-8'), - ldap.SCOPE_SUBTREE, - '(cn=Foo*)', - attrlist=['*'], # CORRECT LINE - ) - self.assertEqual(len(result), 4) - - self.assertEqual(len(w), 2, w) - - self._check_byteswarning( - w[0], u"Received non-bytes value for 'filterstr'") - - self._check_byteswarning( - w[1], u"Received non-bytes value for 'attrlist'") - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_byteswarning_level_search(self): - self._test_byteswarning_level_search('search_s') - self._test_byteswarning_level_search('search_st') - self._test_byteswarning_level_search('search_ext_s') - - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_byteswarning_initialize(self): - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - warnings.simplefilter('always', ldap.LDAPBytesWarning) - bytes_uri = self.server.ldap_uri.decode('utf-8') - self.ldap_object_class(bytes_uri) # CORRECT LINE - - self.assertEqual(len(w), 1, w) - - self._check_byteswarning( - w[0], u"Under Python 2, python-ldap uses bytes by default.") - @requires_tls() def test_multiple_starttls(self): # Test for openldap does not re-register nss shutdown callbacks @@ -642,17 +389,6 @@ def test_dse(self): [self.server.suffix.encode('utf-8')] ) - @unittest.skipUnless(PY2, "no bytes_mode under Py3") - def test_dse_bytes(self): - l = self._get_bytes_ldapobject() - dse = l.read_rootdse_s() - self.assertIsInstance(dse, dict) - self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) - self.assertEqual( - l.get_naming_contexts(), - [self.server.suffix.encode('utf-8')] - ) - def test_compare_s_true(self): base = self.server.suffix l = self._ldap_conn @@ -720,8 +456,6 @@ def test_passwd_s(self): password = respvalue.genPasswd self.assertIsInstance(password, bytes) - if PY2: - password = password.decode('utf-8') # try changing password back respoid, respvalue = l.passwd_s(dn, password, "initial") @@ -777,8 +511,6 @@ def test103_reconnect_get_state(self): str('_trace_level'): ldap._trace_level, str('_trace_stack_limit'): 5, str('_uri'): self.server.ldap_uri, - str('bytes_mode'): l1.bytes_mode, - str('bytes_strictness'): l1.bytes_strictness, str('timeout'): -1, }, ) @@ -812,12 +544,6 @@ def test105_reconnect_restore(self): class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): - def _get_bytes_ldapobject(self, explicit=True, **kwargs): - raise unittest.SkipTest("Test opens two sockets") - - def _search_wrong_type(self, bytes_mode, strictness): - raise unittest.SkipTest("Test opens two sockets") - def _open_ldap_conn(self, who=None, cred=None, **kwargs): if hasattr(self, '_sock'): raise RuntimeError("socket already connected") diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 1408efcf..398dc892 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -4,17 +4,13 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - import os import unittest +from urllib.parse import quote # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' -from ldap.compat import quote - import ldapurl from ldapurl import LDAPUrl diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 048b3f40..254e68d6 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -4,9 +4,6 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - import os import textwrap import unittest diff --git a/setup.py b/setup.py index 69747853..22c7c741 100644 --- a/setup.py +++ b/setup.py @@ -7,15 +7,10 @@ import sys,os from setuptools import setup, Extension -if sys.version_info[0] == 2 and sys.version_info[1] < 7: - raise RuntimeError('This software requires Python 2.7 or 3.x.') -if sys.version_info[0] >= 3 and sys.version_info < (3, 4): - raise RuntimeError('The C API from Python 3.4+ is required.') +if sys.version_info < (3, 6): + raise RuntimeError('The C API from Python 3.6+ is required.') -if sys.version_info[0] >= 3: - from configparser import ConfigParser -else: - from ConfigParser import ConfigParser +from configparser import ConfigParser sys.path.insert(0, os.path.join(os.getcwd(), 'Lib/ldap')) import pkginfo @@ -88,14 +83,11 @@ class OpenLDAP2: 'Programming Language :: C', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', # Note: when updating Python versions, also change .travis.yml and tox.ini 'Topic :: Database', @@ -169,6 +161,6 @@ class OpenLDAP2: 'pyasn1_modules >= 0.1.5', ], zip_safe=False, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', + python_requires='>=3.6', test_suite = 'Tests', ) diff --git a/tox.ini b/tox.ini index 81a38bf5..13a0e9bd 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,py37,py38,py39,{py2,py3}-nosasltls,doc,py3-trace +envlist = py36,py37,py38,py39,py3-nosasltls,doc,py3-trace minver = 1.8 [testenv] @@ -16,19 +16,8 @@ passenv = WITH_GCOV commands = {envpython} -bb -Werror \ -m unittest discover -v -s Tests -p 't_*' -[testenv:py27] -# No warnings with Python 2.7 -passenv = {[testenv]passenv} -commands = - {envpython} -m unittest discover -v -s Tests -p 't_*' - -[testenv:py34] -# No warnings with Python 3.4 -passenv = {[testenv]passenv} -commands = {[testenv:py27]commands} - -[testenv:py2-nosasltls] -basepython = python2 +[testenv:py3-nosasltls] +basepython = python3 # don't install, install dependencies manually skip_install = true deps = @@ -43,15 +32,7 @@ commands = {envpython} setup.py clean --all {envpython} setup.py build_ext -UHAVE_SASL,HAVE_TLS {envpython} setup.py install --single-version-externally-managed --root=/ - {[testenv:py27]commands} - -[testenv:py3-nosasltls] -basepython = python3 -skip_install = {[testenv:py2-nosasltls]skip_install} -deps = {[testenv:py2-nosasltls]deps} -passenv = {[testenv:py2-nosasltls]passenv} -setenv = {[testenv:py2-nosasltls]setenv} -commands = {[testenv:py2-nosasltls]commands} + {[testenv]commands} [testenv:py3-trace] basepython = python3 @@ -62,17 +43,11 @@ setenv = PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log commands = {[testenv]commands} -[testenv:pypy] -# PyPy doesn't have working setup.py test +[testenv:pypy3] +basepython = pypy3 deps = pytest commands = {envpython} -m pytest -[testenv:pypy35] -# PyPy-5.9.0 -basepython = pypy3.5 -deps = {[testenv:pypy]deps} -commands = {[testenv:pypy]commands} - [testenv:doc] basepython = python3 deps = From 08907384778a5bb08399865b519221380c30b9dc Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 29 Jun 2020 13:41:51 +0200 Subject: [PATCH 127/206] Add words to spellchecker it looks like sphinxcontrib-spelling 5.1.2 does not like GPG and readthedocs. https://github.com/python-ldap/python-ldap/pull/361 Signed-off-by: Christian Heimes --- Doc/spelling_wordlist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index d13c0791..fb4a9903 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -56,6 +56,7 @@ filterstr filterStr formatOID func +GPG Heimdal hostport hrefTarget @@ -105,6 +106,7 @@ processResultsCount Proxied py rdn +readthedocs reentrant refmodule refreshAndPersist From 6f06059b746cdae69715fd1bef1386ef93419a14 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 29 Jun 2020 13:42:31 +0200 Subject: [PATCH 128/206] Force Python 3 tox on Travis CI pip may point to Python 2 but setup.py requires Python 3. Tox sdist may fail when tox is installed for Python 2 on Travis CI. https://github.com/python-ldap/python-ldap/pull/362 Signed-off-by: Christian Heimes --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 021fa401..35497998 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,7 @@ env: - TOX_TESTENV_PASSENV="CFLAGS CI WITH_GCOV" install: - - pip install "pip>=7.1.0" - - pip install tox-travis tox codecov + - python3 -m pip install "pip>=7.1.0" + - python3 -m pip install tox-travis tox codecov -script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" tox +script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" python3 -m tox From 605a34b4a776bba9f79aa4fb399bf468bd497566 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 29 Jun 2020 15:41:22 +0200 Subject: [PATCH 129/206] Fix macOS SDK builds without ldap_init_fd Fix macOS SDK builds without ldap_init_fd macOS system libldap 2.4.28 does not have ldap_init_fd symbol. Disable initialize_fd when Apple libldap 2.4.28 is detected. Also run some macOS tests on Travis CI. Since the SDK does not ship slapd, testing is rather limited. https://github.com/python-ldap/python-ldap/pull/360 Fixes: https://github.com/python-ldap/python-ldap/issues/359 Signed-off-by: Christian Heimes --- .travis.yml | 9 +++++++++ Doc/reference/ldap.rst | 8 ++++++++ Lib/ldap/constants.py | 1 + Lib/ldap/ldapobject.py | 2 ++ Lib/slapdtest/__init__.py | 1 + Lib/slapdtest/_slapdtest.py | 8 ++++++++ Modules/common.h | 5 +++++ Modules/constants_generated.h | 8 ++++++++ Modules/functions.c | 4 ++++ Tests/t_cext.py | 5 ++++- Tests/t_ldapobject.py | 2 ++ setup.py | 4 +++- tox.ini | 20 ++++++++++++++++++++ 13 files changed, 75 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35497998..17d46bf3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +group: travis_latest sudo: false @@ -14,6 +15,13 @@ addons: # Note: when updating Python versions, also change setup.py and tox.ini matrix: include: + - os: osx + osx_image: xcode11.4 + language: minimal + env: + - TOXENV=macos + - CFLAGS_warnings="-Wall -Werror=declaration-after-statement" + - CFLAGS_std="-std=c99" - python: 3.6 env: - TOXENV=py36 @@ -25,6 +33,7 @@ matrix: - python: 3.7 env: - TOXENV=py37 + - CFLAGS_std="-std=c99" - WITH_GCOV=1 dist: xenial sudo: true diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 89c98064..16220f3b 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -49,6 +49,8 @@ This module defines the following functions: and explicitly closed after the :class:`~ldap.ldapobject.LDAPObject` is unbound. The internal connection type is determined from the URI, ``TCP`` for ``ldap://`` / ``ldaps://``, ``IPC`` (``AF_UNIX``) for ``ldapi://``. + The parameter is not available on macOS when python-ldap is compiled with system + libldap, see :py:const:`INIT_FD_AVAIL`. Note that internally the OpenLDAP function `ldap_initialize(3) `_ @@ -139,6 +141,12 @@ General Integer where a non-zero value indicates that python-ldap was built with support for SSL/TLS (OpenSSL or similar libs). +.. py:data:: INIT_FD_AVAIL + + Integer where a non-zero value indicates that python-ldap supports + :py:func:`initialize` from a file descriptor. The feature is generally + available except on macOS when python-ldap is compiled with system libldap. + .. _ldap-options: diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 641d49ce..5e178a17 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -344,6 +344,7 @@ class Str(Constant): Feature('LIBLDAP_R', 'HAVE_LIBLDAP_R'), Feature('SASL_AVAIL', 'HAVE_SASL'), Feature('TLS_AVAIL', 'HAVE_TLS'), + Feature('INIT_FD_AVAIL', 'HAVE_LDAP_INIT_FD'), Str("CONTROL_MANAGEDSAIT"), Str("CONTROL_PROXY_AUTHZ"), diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index e29ee9d7..dcdeea5a 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -77,6 +77,8 @@ def __init__( self._uri = uri self._ldap_object_lock = self._ldap_lock('opcall') if fileno is not None: + if not hasattr(_ldap, "initialize_fd"): + raise ValueError("libldap does not support initialize_fd") if hasattr(fileno, "fileno"): fileno = fileno.fileno() self._l = ldap.functions._ldap_function_call( diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 02ed317f..1371bef2 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -9,4 +9,5 @@ from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls +from slapdtest._slapdtest import requires_init_fd from slapdtest._slapdtest import skip_unless_ci diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index cebd0df1..25b3b22b 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -107,6 +107,14 @@ def requires_ldapi(): else: return identity +def requires_init_fd(): + if not ldap.INIT_FD_AVAIL: + return skip_unless_ci( + "test needs ldap.INIT_FD", feature='INIT_FD') + else: + return identity + + def _add_sbin(path): """Add /sbin and related directories to a command search path""" directories = path.split(os.pathsep) diff --git a/Modules/common.h b/Modules/common.h index 1ce2eb83..886024f2 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -24,11 +24,16 @@ /* openldap.h with ldap_init_fd() was introduced in 2.4.48 * see https://bugs.openldap.org/show_bug.cgi?id=8671 */ +#define HAVE_LDAP_INIT_FD 1 #include +#elif (defined(__APPLE__) && (LDAP_VENDOR_VERSION == 20428)) +/* macOS system libldap 2.4.28 does not have ldap_init_fd symbol */ +#undef HAVE_LDAP_INIT_FD #else /* ldap_init_fd() has been around for a very long time * SSSD has been defining the function for a while, so it's probably OK. */ +#define HAVE_LDAP_INIT_FD 1 #define LDAP_PROTO_TCP 1 #define LDAP_PROTO_UDP 2 #define LDAP_PROTO_IPC 3 diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 3231e635..4a4cdb3e 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -329,6 +329,14 @@ if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) return -1; #endif +#ifdef HAVE_LDAP_INIT_FD +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 1) != 0) + return -1; +#else +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 0) != 0) + return -1; +#endif + add_string(CONTROL_MANAGEDSAIT); add_string(CONTROL_PROXY_AUTHZ); add_string(CONTROL_SUBENTRIES); diff --git a/Modules/functions.c b/Modules/functions.c index ce4a924a..b811708f 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -30,6 +30,7 @@ l_ldap_initialize(PyObject *unused, PyObject *args) return (PyObject *)newLDAPObject(ld); } +#ifdef HAVE_LDAP_INIT_FD /* initialize_fd(fileno, url) */ static PyObject * @@ -82,6 +83,7 @@ l_ldap_initialize_fd(PyObject *unused, PyObject *args) return (PyObject *)newLDAPObject(ld); } +#endif /* ldap_str2dn */ @@ -190,7 +192,9 @@ l_ldap_get_option(PyObject *self, PyObject *args) static PyMethodDef methods[] = { {"initialize", (PyCFunction)l_ldap_initialize, METH_VARARGS}, +#ifdef HAVE_LDAP_INIT_FD {"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS}, +#endif {"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS}, {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 2fa4f56c..c271531a 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -15,7 +15,7 @@ # import the plain C wrapper module import _ldap -from slapdtest import SlapdTestCase, requires_tls +from slapdtest import SlapdTestCase, requires_tls, requires_init_fd class TestLdapCExtension(SlapdTestCase): @@ -248,12 +248,14 @@ def test_simple_bind_fileno(self): with self._open_conn_fd() as (sock, l): self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) + @requires_init_fd() def test_simple_bind_fileno_invalid(self): with open(os.devnull) as f: l = _ldap.initialize_fd(f.fileno(), self.server.ldap_uri) with self.assertRaises(_ldap.SERVER_DOWN): self._bind_conn(l) + @requires_init_fd() def test_simple_bind_fileno_closed(self): with self._open_conn_fd() as (sock, l): self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) @@ -261,6 +263,7 @@ def test_simple_bind_fileno_closed(self): with self.assertRaises(_ldap.SERVER_DOWN): l.whoami_s() + @requires_init_fd() def test_simple_bind_fileno_rebind(self): with self._open_conn_fd() as (sock, l): self.assertEqual(l.whoami_s(), "dn:" + self.server.root_dn) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 75da0f43..da937a30 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -19,6 +19,7 @@ from slapdtest import SlapdTestCase from slapdtest import requires_ldapi, requires_sasl, requires_tls +from slapdtest import requires_init_fd LDIF_TEMPLATE = """dn: %(suffix)s @@ -543,6 +544,7 @@ def test105_reconnect_restore(self): self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) +@requires_init_fd() class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): def _open_ldap_conn(self, who=None, cred=None, **kwargs): if hasattr(self, '_sock'): diff --git a/setup.py b/setup.py index 22c7c741..20c31c5f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,9 @@ from setuptools import setup, Extension if sys.version_info < (3, 6): - raise RuntimeError('The C API from Python 3.6+ is required.') + raise RuntimeError( + 'The C API from Python 3.6+ is required, found %s' % sys.version_info + ) from configparser import ConfigParser diff --git a/tox.ini b/tox.ini index 13a0e9bd..e33de28c 100644 --- a/tox.ini +++ b/tox.ini @@ -43,6 +43,26 @@ setenv = PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log commands = {[testenv]commands} +[testenv:macos] +# Travis CI macOS image does not have slapd +# SDK libldap does not support ldap_init_fd +basepython = python3 +deps = {[testenv]deps} +passenv = {[testenv]passenv} +setenv = + CI_DISABLED=INIT_FD +commands = + {envpython} -m unittest -v \ + Tests/t_cidict.py \ + Tests/t_ldap_dn.py \ + Tests/t_ldap_filter.py \ + Tests/t_ldap_functions.py \ + Tests/t_ldap_modlist.py \ + Tests/t_ldap_schema_tokenizer.py \ + Tests/t_ldapurl.py \ + Tests/t_ldif.py \ + Tests/t_untested_mods.py + [testenv:pypy3] basepython = pypy3 deps = pytest From 2e34d75e64628d02e32dadaa87bfa35447fa503c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 13 Jul 2020 14:53:41 +0200 Subject: [PATCH 130/206] Prepare auto-formatting with black (#364) Also removes autopep8 and prepares configuration for import sorter isort. Signed-off-by: Christian Heimes --- Doc/contributing.rst | 2 +- Makefile | 14 ++++++++------ pyproject.toml | 8 ++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 pyproject.toml diff --git a/Doc/contributing.rst b/Doc/contributing.rst index b11c18b5..1fc1365b 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -108,7 +108,7 @@ Notable targets are: Note that no backups are made – please commit any other changes before using this target. - Requires the ``indent`` program and the ``autopep8`` Python module. + Requires the ``indent`` program and the ``black`` Python module. .. _PEP 7: https://www.python.org/dev/peps/pep-0007/ .. _PEP 8: https://www.python.org/dev/peps/pep-0008/ diff --git a/Makefile b/Makefile index 8ec46a6b..f7360a66 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ LCOV_REPORT_OPTIONS=--show-details -no-branch-coverage \ --title "python-ldap LCOV report" SCAN_REPORT=build/scan_report PYTHON_SUPP=/usr/share/doc/python3-devel/valgrind-python.supp -AUTOPEP8_OPTS=--aggressive + .NOTPARALLEL: @@ -85,13 +85,15 @@ valgrind: build $(PYTHON_SUPP) fi # Code autoformatter -.PHONY: autoformat indent autopep8 -autoformat: indent autopep8 +.PHONY: autoformat indent black black-check +autoformat: indent black indent: indent Modules/*.c Modules/*.h rm -f Modules/*.c~ Modules/*.h~ -autopep8: - $(PYTHON) -m autopep8 -r -i -j0 $(AUTOPEP8_OPTS) \ - Demo Lib Tests setup.py +black: + $(PYTHON) -m black $(CURDIR) + +black-check: + $(PYTHON) -m black $(CURDIR) --check diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..dda8dbc1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.black] +line-length = 88 +target-version = ['py36', 'py37', 'py38'] + +[tool.isort] +line_length=88 +known_first_party=['ldap', '_ldap', 'ldapurl', 'ldif', 'slapdtest'] +sections=['FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER'] From f881387c698d82c71dba4deaf32910302cda918c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 31 Jul 2020 10:01:05 +0200 Subject: [PATCH 131/206] Show stderr of slapd -Ttest Related: #370 Signed-off-by: Christian Heimes --- Lib/slapdtest/_slapdtest.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 25b3b22b..52069571 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -392,17 +392,19 @@ def _test_config(self): self._log.debug('testing config %s', self._slapd_conf) popen_list = [ self.PATH_SLAPD, - '-Ttest', + "-Ttest", "-f", self._slapd_conf, - '-u', + "-u", + "-v", + "-d", "config" ] - if self._log.isEnabledFor(logging.DEBUG): - popen_list.append('-v') - popen_list.extend(['-d', 'config']) - else: - popen_list.append('-Q') - proc = subprocess.Popen(popen_list) - if proc.wait() != 0: + p = subprocess.run( + popen_list, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + if p.returncode != 0: + self._log.error(p.stdout.decode("utf-8")) raise RuntimeError("configuration test failed") self._log.info("config ok: %s", self._slapd_conf) From 5740c5f7bf47f2c4134857e61e8dc0d3b02dd69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Tue, 1 Sep 2020 14:27:28 +0200 Subject: [PATCH 132/206] Tox can pass positional arguments to test runners --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index e33de28c..65773a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. commands = {envpython} -bb -Werror \ - -m unittest discover -v -s Tests -p 't_*' + -m unittest discover -v -s Tests -p 't_*' {posargs} [testenv:py3-nosasltls] basepython = python3 @@ -52,7 +52,7 @@ passenv = {[testenv]passenv} setenv = CI_DISABLED=INIT_FD commands = - {envpython} -m unittest -v \ + {envpython} -m unittest -v {posargs} \ Tests/t_cidict.py \ Tests/t_ldap_dn.py \ Tests/t_ldap_filter.py \ @@ -66,7 +66,7 @@ commands = [testenv:pypy3] basepython = pypy3 deps = pytest -commands = {envpython} -m pytest +commands = {envpython} -m pytest {posargs} [testenv:doc] basepython = python3 From e54b9f64e74883c4cf5816bead3fbc8a0936b1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Tue, 1 Sep 2020 13:14:45 +0200 Subject: [PATCH 133/206] pytest-ldap documentation link --- Doc/reference/slapdtest.rst | 4 ++++ Doc/spelling_wordlist.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/Doc/reference/slapdtest.rst b/Doc/reference/slapdtest.rst index bd54bb69..9274d00a 100644 --- a/Doc/reference/slapdtest.rst +++ b/Doc/reference/slapdtest.rst @@ -14,6 +14,8 @@ This module is pure Python and does not rely on any non-standard modules. Therefore it can be used stand-alone without the rest of the python-ldap package. +For pytest fixtures, check `pytest-ldap`_. + Functions ^^^^^^^^^ @@ -26,3 +28,5 @@ Classes .. autoclass:: slapdtest.SlapdTestCase :members: + +.. _pytest-ldap: https://pypi.org/project/pytest-ldap/ diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index fb4a9903..b0e9fc90 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -105,6 +105,7 @@ previousDN processResultsCount Proxied py +pytest rdn readthedocs reentrant From ca684f882aae7845a128b6d215df5400e58a2cc3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 30 Sep 2020 15:51:16 +0200 Subject: [PATCH 134/206] Update Doc/reference/slapdtest.rst --- Doc/reference/slapdtest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/slapdtest.rst b/Doc/reference/slapdtest.rst index 9274d00a..7517e662 100644 --- a/Doc/reference/slapdtest.rst +++ b/Doc/reference/slapdtest.rst @@ -14,7 +14,7 @@ This module is pure Python and does not rely on any non-standard modules. Therefore it can be used stand-alone without the rest of the python-ldap package. -For pytest fixtures, check `pytest-ldap`_. +Test fixtures for the popular `pytest` framework are developed in an external project, `pytest-ldap`_. Functions ^^^^^^^^^ From 7dc1e62592a0ff179d14633d474607a0eddfe88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 29 Oct 2020 10:54:40 +0100 Subject: [PATCH 135/206] SlapdObject directory based configuration method, and slapadd implementation (#382) https://github.com/python-ldap/python-ldap/pull/382 --- Doc/spelling_wordlist.txt | 2 + Lib/slapdtest/_slapdtest.py | 132 +++++++++++++++++++----------------- Tests/t_ldap_syncrepl.py | 59 ++++++++++------ Tests/t_ldapobject.py | 48 +++++++++++++ 4 files changed, 156 insertions(+), 85 deletions(-) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index b0e9fc90..c24ab486 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -129,8 +129,10 @@ serverctrls sessionSourceIp sessionSourceName sessionTrackingIdentifier +slapadd sizelimit slapd +startup stderr stdout str diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 52069571..6784cf12 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -23,34 +23,33 @@ HERE = os.path.abspath(os.path.dirname(__file__)) -# a template string for generating simple slapd.conf file -SLAPD_CONF_TEMPLATE = r""" -serverID %(serverid)s -moduleload back_%(database)s -%(include_directives)s -loglevel %(loglevel)s -allow bind_v2 - -authz-regexp - "gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" - "%(rootdn)s" - -database %(database)s -directory "%(directory)s" -suffix "%(suffix)s" -rootdn "%(rootdn)s" -rootpw "%(rootpw)s" - -TLSCACertificateFile "%(cafile)s" -TLSCertificateFile "%(servercert)s" -TLSCertificateKeyFile "%(serverkey)s" -# ignore missing client cert but fail with invalid client cert -TLSVerifyClient try - -authz-regexp - "C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" - "ldap://ou=people,dc=local???($1)" - +# a template string for generating simple slapd.d file +SLAPD_CONF_TEMPLATE = r"""dn: cn=config +objectClass: olcGlobal +cn: config +olcServerID: %(serverid)s +olcLogLevel: %(loglevel)s +olcAllows: bind_v2 +olcAuthzRegexp: {0}"gidnumber=%(root_gid)s\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" "%(rootdn)s" +olcAuthzRegexp: {1}"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" "ldap://ou=people,dc=local???($1)" +olcTLSCACertificateFile: %(cafile)s +olcTLSCertificateFile: %(servercert)s +olcTLSCertificateKeyFile: %(serverkey)s +olcTLSVerifyClient: try + +dn: cn=module,cn=config +objectClass: olcModuleList +cn: module +olcModuleLoad: back_%(database)s + +dn: olcDatabase=%(database)s,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: %(database)s +olcSuffix: %(suffix)s +olcRootDN: %(rootdn)s +olcRootPW: %(rootpw)s +olcDbDirectory: %(directory)s """ LOCALHOST = '127.0.0.1' @@ -175,6 +174,9 @@ class SlapdObject(object): manager, the slapd server is shut down and the temporary data store is removed. + :param openldap_schema_files: A list of schema names or schema paths to + load at startup. By default this only contains `core`. + .. versionchanged:: 3.1 Added context manager functionality @@ -187,10 +189,10 @@ class SlapdObject(object): slapd_loglevel = 'stats stats2' local_host = LOCALHOST testrunsubdirs = ( - 'schema', + 'slapd.d', ) openldap_schema_files = ( - 'core.schema', + 'core.ldif', ) TMPDIR = os.environ.get('TMP', os.getcwd()) @@ -217,8 +219,7 @@ def __init__(self): self._port = self._avail_tcp_port() self.server_id = self._port % 4096 self.testrundir = os.path.join(self.TMPDIR, 'python-ldap-test-%d' % self._port) - self._schema_prefix = os.path.join(self.testrundir, 'schema') - self._slapd_conf = os.path.join(self.testrundir, 'slapd.conf') + self._slapd_conf = os.path.join(self.testrundir, 'slapd.d') self._db_directory = os.path.join(self.testrundir, "openldap-data") self.ldap_uri = "ldap://%s:%d/" % (self.local_host, self._port) if HAVE_LDAPI: @@ -262,6 +263,7 @@ def _find_commands(self): self.PATH_LDAPDELETE = self._find_command('ldapdelete') self.PATH_LDAPMODIFY = self._find_command('ldapmodify') self.PATH_LDAPWHOAMI = self._find_command('ldapwhoami') + self.PATH_SLAPADD = self._find_command('slapadd') self.PATH_SLAPD = os.environ.get('SLAPD', None) if not self.PATH_SLAPD: @@ -292,7 +294,6 @@ def setup_rundir(self): os.mkdir(self.testrundir) os.mkdir(self._db_directory) self._create_sub_dirs(self.testrunsubdirs) - self._ln_schema_files(self.openldap_schema_files, self.SCHEMADIR) def _cleanup_rundir(self): """ @@ -337,17 +338,8 @@ def gen_config(self): for generating specific static configuration files you have to override this method """ - include_directives = '\n'.join( - 'include "{schema_prefix}/{schema_file}"'.format( - schema_prefix=self._schema_prefix, - schema_file=schema_file, - ) - for schema_file in self.openldap_schema_files - ) config_dict = { 'serverid': hex(self.server_id), - 'schema_prefix':self._schema_prefix, - 'include_directives': include_directives, 'loglevel': self.slapd_loglevel, 'database': self.database, 'directory': self._db_directory, @@ -371,29 +363,28 @@ def _create_sub_dirs(self, dir_names): self._log.debug('Create directory %s', dir_name) os.mkdir(dir_name) - def _ln_schema_files(self, file_names, source_dir): - """ - write symbolic links to original schema files - """ - for fname in file_names: - ln_source = os.path.join(source_dir, fname) - ln_target = os.path.join(self._schema_prefix, fname) - self._log.debug('Create symlink %s -> %s', ln_source, ln_target) - os.symlink(ln_source, ln_target) - def _write_config(self): - """Writes the slapd.conf file out, and returns the path to it.""" - self._log.debug('Writing config to %s', self._slapd_conf) - with open(self._slapd_conf, 'w') as config_file: - config_file.write(self.gen_config()) - self._log.info('Wrote config to %s', self._slapd_conf) + """Loads the slapd.d configuration.""" + self._log.debug("importing configuration: %s", self._slapd_conf) + + self.slapadd(self.gen_config(), ["-n0"]) + ldif_paths = [ + schema + if os.path.exists(schema) + else os.path.join(self.SCHEMADIR, schema) + for schema in self.openldap_schema_files + ] + for ldif_path in ldif_paths: + self.slapadd(None, ["-n0", "-l", ldif_path]) + + self._log.debug("import ok: %s", self._slapd_conf) def _test_config(self): self._log.debug('testing config %s', self._slapd_conf) popen_list = [ self.PATH_SLAPD, "-Ttest", - "-f", self._slapd_conf, + "-F", self._slapd_conf, "-u", "-v", "-d", "config" @@ -417,8 +408,7 @@ def _start_slapd(self): urls.append(self.ldapi_uri) slapd_args = [ self.PATH_SLAPD, - '-f', self._slapd_conf, - '-F', self.testrundir, + '-F', self._slapd_conf, '-h', ' '.join(urls), ] if self._log.isEnabledFor(logging.DEBUG): @@ -523,10 +513,14 @@ def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None): # pragma: no cover if ldap_uri is None: ldap_uri = self.default_ldap_uri - args = [ - ldapcommand, - '-H', ldap_uri, - ] + self._cli_auth_args() + (extra_args or []) + + if ldapcommand.split("/")[-1].startswith("ldap"): + args = [ldapcommand, '-H', ldap_uri] + self._cli_auth_args() + else: + args = [ldapcommand, '-F', self._slapd_conf] + + args += (extra_args or []) + self._log.debug('Run command: %r', ' '.join(args)) proc = subprocess.Popen( args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -577,6 +571,16 @@ def ldapdelete(self, dn, recursive=False, extra_args=None): extra_args.append(dn) self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) + def slapadd(self, ldif, extra_args=None): + """ + Runs slapadd on this slapd instance, passing it the ldif content + """ + self._cli_popen( + self.PATH_SLAPADD, + stdin_data=ldif.encode("utf-8") if ldif else None, + extra_args=extra_args, + ) + def __enter__(self): self.start() return self diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 7ec97075..51104148 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -19,27 +19,44 @@ from slapdtest import SlapdObject, SlapdTestCase # a template string for generating simple slapd.conf file -SLAPD_CONF_PROVIDER_TEMPLATE = r""" -serverID %(serverid)s -moduleload back_%(database)s -moduleload syncprov -include "%(schema_prefix)s/core.schema" -loglevel %(loglevel)s -allow bind_v2 - -authz-regexp - "gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" - "%(rootdn)s" - -database %(database)s -directory "%(directory)s" -suffix "%(suffix)s" -rootdn "%(rootdn)s" -rootpw "%(rootpw)s" -overlay syncprov -syncprov-checkpoint 100 10 -syncprov-sessionlog 100 -index objectclass,entryCSN,entryUUID eq +SLAPD_CONF_PROVIDER_TEMPLATE = r"""dn: cn=config +objectClass: olcGlobal +cn: config +olcServerID: %(serverid)s +olcLogLevel: %(loglevel)s +olcAllows: bind_v2 +olcAuthzRegexp: {0}"gidnumber=%(root_gid)s\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" "%(rootdn)s" +olcAuthzRegexp: {1}"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)" "ldap://ou=people,dc=local???($1)" +olcTLSCACertificateFile: %(cafile)s +olcTLSCertificateFile: %(servercert)s +olcTLSCertificateKeyFile: %(serverkey)s +olcTLSVerifyClient: try + +dn: cn=module,cn=config +objectClass: olcModuleList +cn: module +olcModuleLoad: back_%(database)s +olcModuleLoad: syncprov + +dn: olcDatabase=%(database)s,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: %(database)s +olcSuffix: %(suffix)s +olcRootDN: %(rootdn)s +olcRootPW: %(rootpw)s +olcDbDirectory: %(directory)s +olcDbIndex: objectclass,entryCSN,entryUUID eq + +dn: olcOverlay=syncprov,olcDatabase={1}%(database)s,cn=config +objectClass: olcOverlayConfig +objectClass: olcSyncProvConfig +olcOverlay: syncprov +olcSpCheckpoint: 100 10 +olcSpSessionlog: 100 +""" + +OTHER_CONF = r""" """ # Define initial data load, both as an LDIF and as a dictionary. diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index da937a30..6d44ff1d 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -62,6 +62,25 @@ """ +SCHEMA_TEMPLATE = """dn: cn=mySchema,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: mySchema +olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.1 NAME 'myAttribute' + DESC 'fobar attribute' + EQUALITY caseExactMatch + ORDERING caseExactOrderingMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + USAGE userApplications + X-ORIGIN 'foobar' ) +olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.2 NAME 'myClass' + DESC 'foobar objectclass' + SUP top + STRUCTURAL + MUST myAttribute + X-ORIGIN 'foobar' )""" + class Test00_SimpleLDAPObject(SlapdTestCase): """ @@ -94,6 +113,14 @@ def setUp(self): def tearDown(self): del self._ldap_conn + def reset_connection(self): + try: + del self._ldap_conn + except AttributeError: + pass + + self._ldap_conn = self._open_ldap_conn(bytes_mode=False) + def test_reject_bytes_base(self): base = self.server.suffix l = self._ldap_conn @@ -465,6 +492,22 @@ def test_passwd_s(self): l.delete_s(dn) + def test_slapadd(self): + with self.assertRaises(ldap.INVALID_DN_SYNTAX): + self._ldap_conn.add_s("myAttribute=foobar,ou=Container,%s" % self.server.suffix, [ + ("objectClass", b'myClass'), + ("myAttribute", b'foobar'), + ]) + + self.server.slapadd(SCHEMA_TEMPLATE, ["-n0"]) + self.server.restart() + self.reset_connection() + + self._ldap_conn.add_s("myAttribute=foobar,ou=Container,%s" % self.server.suffix, [ + ("objectClass", b'myClass'), + ("myAttribute", b'foobar'), + ]) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ @@ -561,6 +604,11 @@ def tearDown(self): del self._sock super(Test03_SimpleLDAPObjectWithFileno, self).tearDown() + def reset_connection(self): + self._sock.close() + del self._sock + super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection() + if __name__ == '__main__': unittest.main() From 3ea12d65d70678a3cca479383dd2f65b01f3b973 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Nov 2020 11:33:14 -0800 Subject: [PATCH 136/206] Fix CI failure on docs Use backticks around setup.cfg as it is a filename. This resolves a failure that occurs in sphinxcontrib.spelling due to trying to import setup.cfg. --- Doc/installing.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 514cf99e..56778220 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -168,10 +168,10 @@ Packages for building and testing:: newer are required. -setup.cfg -========= +``setup.cfg`` +============= -The file setup.cfg allows to set some build and installation parameters for +The file ``setup.cfg`` allows to set some build and installation parameters for reflecting the local installation of required software packages. Only section ``[_ldap]`` is described here. More information about other sections can be found in :ref:`Setuptools documentation `. From 4fc9038290d874cc1da6b611159c634a000aa1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 21:45:03 +0100 Subject: [PATCH 137/206] slapdtest: Rework waiting for SlapdObject startup This gives a chance to SlapdObject to start faster. Notably it does not start by sleeping 1.5s anymore. https://github.com/python-ldap/python-ldap/pull/390 --- Lib/slapdtest/_slapdtest.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 6784cf12..141f459b 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -208,9 +208,6 @@ class SlapdObject(object): BIN_PATH = os.environ.get('BIN', os.environ.get('PATH', os.defpath)) SBIN_PATH = os.environ.get('SBIN', _add_sbin(BIN_PATH)) - # time in secs to wait before trying to access slapd via LDAP (again) - _start_sleep = 1.5 - # create loggers once, multiple calls mess up refleak tests _log = combined_logger('python-ldap-test') @@ -418,20 +415,22 @@ def _start_slapd(self): self._log.info('starting slapd: %r', ' '.join(slapd_args)) self._proc = subprocess.Popen(slapd_args) # Waits until the LDAP server socket is open, or slapd crashed + deadline = time.monotonic() + 10 # no cover to avoid spurious coverage changes, see # https://github.com/python-ldap/python-ldap/issues/127 - for _ in range(10): # pragma: no cover + while True: # pragma: no cover if self._proc.poll() is not None: self._stopped() raise RuntimeError("slapd exited before opening port") - time.sleep(self._start_sleep) try: self._log.debug( "slapd connection check to %s", self.default_ldap_uri ) self.ldapwhoami() except RuntimeError: - pass + if time.monotonic() >= deadline: + break + time.sleep(0.2) else: return raise RuntimeError("slapd did not start properly") From 3484d574d9c9daa6fdda1d4b0e0a949021b48564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 21:54:44 +0100 Subject: [PATCH 138/206] Remove Python 2 syntax (#383) https://github.com/python-ldap/python-ldap/pull/383 --- .gitignore | 3 ++ Demo/Lib/ldap/async/deltree.py | 2 -- Demo/Lib/ldapurl/urlsearch.py | 1 - Demo/initialize.py | 1 - Demo/ldapcontrols.py | 1 - Demo/ldapurl_search.py | 2 -- Demo/matchedvalues.py | 3 +- Demo/options.py | 2 -- Demo/page_control.py | 3 -- Demo/paged_search_ext_s.py | 1 - Demo/passwd_ext_op.py | 1 - Demo/pyasn1/dds.py | 2 -- Demo/pyasn1/derefcontrol.py | 1 - Demo/pyasn1/noopsearch.py | 2 -- Demo/pyasn1/ppolicy.py | 2 -- Demo/pyasn1/psearch.py | 2 -- Demo/pyasn1/readentrycontrol.py | 1 - Demo/pyasn1/sessiontrack.py | 2 -- Demo/pyasn1/syncrepl.py | 2 -- Demo/rename.py | 1 - Demo/resiter.py | 1 - Demo/sasl_bind.py | 1 - Demo/schema.py | 1 - Demo/schema_tree.py | 7 ++-- Demo/simple.py | 3 +- Demo/simplebrowse.py | 1 - Doc/conf.py | 1 - Lib/ldap/__init__.py | 6 ++-- Lib/ldap/asyncsearch.py | 2 +- Lib/ldap/constants.py | 11 +++---- Lib/ldap/controls/__init__.py | 3 +- Lib/ldap/controls/deref.py | 1 - Lib/ldap/controls/libldap.py | 3 +- Lib/ldap/controls/openldap.py | 1 - Lib/ldap/controls/pagedresults.py | 1 - Lib/ldap/controls/ppolicy.py | 1 - Lib/ldap/controls/psearch.py | 1 - Lib/ldap/controls/pwdpolicy.py | 1 - Lib/ldap/controls/readentry.py | 1 - Lib/ldap/controls/sessiontrack.py | 1 - Lib/ldap/controls/simple.py | 1 - Lib/ldap/controls/sss.py | 1 - Lib/ldap/controls/vlv.py | 1 - Lib/ldap/dn.py | 2 +- Lib/ldap/extop/__init__.py | 4 +-- Lib/ldap/extop/dds.py | 1 - Lib/ldap/extop/passwd.py | 1 - Lib/ldap/filter.py | 2 +- Lib/ldap/functions.py | 2 +- Lib/ldap/ldapobject.py | 28 ++++++++-------- Lib/ldap/logger.py | 3 +- Lib/ldap/pkginfo.py | 3 +- Lib/ldap/schema/models.py | 24 +++++++------- Lib/ldap/schema/subentry.py | 8 ++--- Lib/ldap/syncrepl.py | 1 - Lib/ldapurl.py | 22 ++++++------- Lib/slapdtest/__init__.py | 1 - Lib/slapdtest/_slapdtest.py | 3 +- Tests/__init__.py | 2 -- Tests/t_cext.py | 9 +++-- Tests/t_cidict.py | 1 - Tests/t_edit.py | 2 +- Tests/t_ldap_dn.py | 1 - Tests/t_ldap_filter.py | 1 - Tests/t_ldap_functions.py | 1 - Tests/t_ldap_modlist.py | 5 ++- Tests/t_ldap_options.py | 4 +-- Tests/t_ldap_sasl.py | 7 ++-- Tests/t_ldap_schema_subentry.py | 1 - Tests/t_ldap_schema_tokenizer.py | 1 - Tests/t_ldap_syncrepl.py | 11 +++---- Tests/t_ldapobject.py | 55 +++++++++++++++---------------- Tests/t_ldapurl.py | 5 ++- Tests/t_ldif.py | 1 - 74 files changed, 114 insertions(+), 184 deletions(-) diff --git a/.gitignore b/.gitignore index 5f6d0425..bab21878 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ PKG-INFO # generated in the sample workflow /__venv__/ + +# test dirs +python-ldap-test-* diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index d3fc6203..9db52c84 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ldap,ldap.async class DeleteLeafs(ldap.async.AsyncSearchHandler): diff --git a/Demo/Lib/ldapurl/urlsearch.py b/Demo/Lib/ldapurl/urlsearch.py index b293aa20..c58a1954 100644 --- a/Demo/Lib/ldapurl/urlsearch.py +++ b/Demo/Lib/ldapurl/urlsearch.py @@ -3,7 +3,6 @@ No output of LDAP data is produced except trace output. """ -from __future__ import print_function import sys,getpass,ldap,ldapurl try: diff --git a/Demo/initialize.py b/Demo/initialize.py index 952b3f4b..ab78cdb8 100644 --- a/Demo/initialize.py +++ b/Demo/initialize.py @@ -7,7 +7,6 @@ ldaps://localhost:1391 (LDAP over SSL) ldapi://%2ftmp%2fopenldap2 (domain socket /tmp/openldap2) """ -from __future__ import print_function import sys,os,ldap diff --git a/Demo/ldapcontrols.py b/Demo/ldapcontrols.py index a5ba8d34..eec86b4c 100644 --- a/Demo/ldapcontrols.py +++ b/Demo/ldapcontrols.py @@ -1,4 +1,3 @@ -from __future__ import print_function import ldap,ldapurl,pprint from ldap.controls import LDAPControl,BooleanControl diff --git a/Demo/ldapurl_search.py b/Demo/ldapurl_search.py index 07ffbca5..614ba1af 100644 --- a/Demo/ldapurl_search.py +++ b/Demo/ldapurl_search.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys,pprint,ldap from ldap.ldapobject import LDAPObject diff --git a/Demo/matchedvalues.py b/Demo/matchedvalues.py index 7a6e7b6b..bbc2a1bc 100644 --- a/Demo/matchedvalues.py +++ b/Demo/matchedvalues.py @@ -27,7 +27,6 @@ # Matched values control: (mail=*@example.org) # dn: uid=jsmith,ou=People,dc=example,dc=com # mail: jsmith@example.org -from __future__ import print_function import ldap from ldap.controls import MatchedValuesControl @@ -37,7 +36,7 @@ def print_result(search_result): print("dn: %s" % search_result[n][0]) for attr in search_result[n][1].keys(): for i in range(len(search_result[n][1][attr])): - print("%s: %s" % (attr, search_result[n][1][attr][i])) + print("{}: {}".format(attr, search_result[n][1][attr][i])) print diff --git a/Demo/options.py b/Demo/options.py index 28a04374..7a8ee9db 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ldap host="localhost:1390" diff --git a/Demo/page_control.py b/Demo/page_control.py index 8238ede3..b92cbf86 100644 --- a/Demo/page_control.py +++ b/Demo/page_control.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function - url = "ldap://localhost:1390" base = "dc=stroeder,dc=de" search_flt = r'(objectClass=*)' diff --git a/Demo/paged_search_ext_s.py b/Demo/paged_search_ext_s.py index d0f82918..3a1a4acd 100644 --- a/Demo/paged_search_ext_s.py +++ b/Demo/paged_search_ext_s.py @@ -1,4 +1,3 @@ -from __future__ import print_function url = "ldap://localhost:1390/" base = "dc=stroeder,dc=de" search_flt = r'(objectClass=*)' diff --git a/Demo/passwd_ext_op.py b/Demo/passwd_ext_op.py index cc5d22cd..6c695482 100644 --- a/Demo/passwd_ext_op.py +++ b/Demo/passwd_ext_op.py @@ -1,7 +1,6 @@ """ Example showing the use of the password extended operation. """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/dds.py b/Demo/pyasn1/dds.py index c803a1de..19270603 100644 --- a/Demo/pyasn1/dds.py +++ b/Demo/pyasn1/dds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Dynamic Entries (see RFC 2589) @@ -8,7 +7,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function from ldap.extop.dds import RefreshRequest,RefreshResponse diff --git a/Demo/pyasn1/derefcontrol.py b/Demo/pyasn1/derefcontrol.py index 0e7153de..9565a9e8 100644 --- a/Demo/pyasn1/derefcontrol.py +++ b/Demo/pyasn1/derefcontrol.py @@ -3,7 +3,6 @@ This sample script demonstrates the use of the dereference control (see https://tools.ietf.org/html/draft-masarati-ldap-deref) """ -from __future__ import print_function import pprint,ldap,ldap.modlist,ldap.resiter diff --git a/Demo/pyasn1/noopsearch.py b/Demo/pyasn1/noopsearch.py index 2045f50c..a239c0e7 100644 --- a/Demo/pyasn1/noopsearch.py +++ b/Demo/pyasn1/noopsearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for counting searching with OpenLDAP's no-op control @@ -9,7 +8,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/ppolicy.py b/Demo/pyasn1/ppolicy.py index cf6b2ac9..c143bf16 100644 --- a/Demo/pyasn1/ppolicy.py +++ b/Demo/pyasn1/ppolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Password Policy Controls (see https://tools.ietf.org/html/draft-behera-ldap-password-policy) @@ -9,7 +8,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/psearch.py b/Demo/pyasn1/psearch.py index 3bd59e6d..2703a253 100644 --- a/Demo/pyasn1/psearch.py +++ b/Demo/pyasn1/psearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Persistent Search Control (see https://tools.ietf.org/html/draft-ietf-ldapext-psearch) @@ -10,7 +9,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/readentrycontrol.py b/Demo/pyasn1/readentrycontrol.py index a857be22..b3ea6e81 100644 --- a/Demo/pyasn1/readentrycontrol.py +++ b/Demo/pyasn1/readentrycontrol.py @@ -4,7 +4,6 @@ Originally contributed by Andreas Hasenack """ -from __future__ import print_function import pprint,ldap,ldap.modlist diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 58a34e82..491172c0 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ demo_track_ldap_session.py @@ -8,7 +7,6 @@ https://tools.ietf.org/html/draft-wahl-ldap-session-03 """ -from __future__ import print_function __version__ = '0.1' diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 7103bc64..f1f24e19 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ This script implements a syncrepl consumer which syncs data from an OpenLDAP server to a local (shelve) database. @@ -8,7 +7,6 @@ The bound user needs read access to the attributes entryDN and entryCSN. """ -from __future__ import print_function # Import modules from Python standard lib import logging diff --git a/Demo/rename.py b/Demo/rename.py index 91d7528f..edb78a80 100644 --- a/Demo/rename.py +++ b/Demo/rename.py @@ -1,4 +1,3 @@ -from __future__ import print_function import ldap from getpass import getpass diff --git a/Demo/resiter.py b/Demo/resiter.py index 96e9c90d..9fc14e41 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -4,7 +4,6 @@ See https://www.python-ldap.org for details. """ -from __future__ import print_function import ldap,ldap.resiter diff --git a/Demo/sasl_bind.py b/Demo/sasl_bind.py index 667221c1..8453d087 100644 --- a/Demo/sasl_bind.py +++ b/Demo/sasl_bind.py @@ -1,6 +1,5 @@ # For documentation, see comments in Module/LDAPObject.c and the # ldap.sasl module documentation. -from __future__ import print_function import ldap,ldap.sasl diff --git a/Demo/schema.py b/Demo/schema.py index 03f90a83..bdad5e0c 100644 --- a/Demo/schema.py +++ b/Demo/schema.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys,ldap,ldap.schema schema_attrs = ldap.schema.SCHEMA_ATTRS diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index bda5f64d..2b182e98 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -4,7 +4,6 @@ Usage: schema_oc_tree.py [--html] [LDAP URL] """ -from __future__ import print_function import sys,getopt,ldap,ldap.schema @@ -28,10 +27,10 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): se_obj = schema.get_obj(se_class,se_oid) if se_obj!=None: print(""" -
%s (%s)
+
{} ({})
- %s - """ % (', '.join(se_obj.names),se_obj.oid,se_obj.desc)) + {} + """.format(', '.join(se_obj.names),se_obj.oid,se_obj.desc)) if se_tree[se_oid]: print('
') for sub_se_oid in se_tree[se_oid]: diff --git a/Demo/simple.py b/Demo/simple.py index 5fa04250..c82659c3 100644 --- a/Demo/simple.py +++ b/Demo/simple.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys,getpass import ldap @@ -86,7 +85,7 @@ ("labeleduri", ["labeleduri"]), ("manager", ["cn=Jaga Indulska"]), ("reports", ["reports"]), - ("jpegPhoto", [open("/www/leonard/leonard.jpg","r").read()]), + ("jpegPhoto", [open("/www/leonard/leonard.jpg").read()]), ("uid", ["leonard"]), ("userPassword", [""]) diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index 9d138003..fd4563ae 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -3,7 +3,6 @@ # # simple LDAP server browsing example # -from __future__ import print_function import ldap from traceback import print_exc diff --git a/Doc/conf.py b/Doc/conf.py index e1fb9ee2..0fa396b8 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # python-ldap documentation build configuration file, created by # sphinx-quickstart on Sat Mar 29 15:08:17 2008. diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 8d675733..b1797078 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -33,7 +33,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') from _ldap import * # call into libldap to initialize it right now LIBLDAP_API_INFO = _ldap.get_option(_ldap.OPT_API_INFO) @@ -83,14 +83,14 @@ def acquire(self): if __debug__: global _trace_level if _trace_level>=self._min_trace_level: - _trace_file.write('***%s.acquire() %s %s\n' % (self.__class__.__name__,repr(self),self._desc)) + _trace_file.write('***{}.acquire() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc)) return self._lock.acquire() def release(self): if __debug__: global _trace_level if _trace_level>=self._min_trace_level: - _trace_file.write('***%s.release() %s %s\n' % (self.__class__.__name__,repr(self),self._desc)) + _trace_file.write('***{}.release() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc)) return self._lock.release() diff --git a/Lib/ldap/asyncsearch.py b/Lib/ldap/asyncsearch.py index 6514dd05..6c6929dd 100644 --- a/Lib/ldap/asyncsearch.py +++ b/Lib/ldap/asyncsearch.py @@ -30,7 +30,7 @@ def __init__(self,receivedResultType,expectedResultTypes): Exception.__init__(self) def __str__(self): - return 'Received wrong result type %s (expected one of %s).' % ( + return 'Received wrong result type {} (expected one of {}).'.format( self.receivedResultType, ', '.join(self.expectedResultTypes), ) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 5e178a17..b6ec0d33 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -13,16 +13,15 @@ # This module cannot import anything from ldap. # When building documentation, it is used to initialize ldap.__init__. -from __future__ import print_function -class Constant(object): +class Constant: """Base class for a definition of an OpenLDAP constant """ def __init__(self, name, optional=False, requirements=(), doc=None): self.name = name if optional: - self_requirement = 'defined(LDAP_{})'.format(self.name) + self_requirement = f'defined(LDAP_{self.name})' requirements = list(requirements) + [self_requirement] self.requirements = requirements self.doc = self.__doc__ = doc @@ -50,7 +49,7 @@ class TLSInt(Int): def __init__(self, *args, **kwargs): requrements = list(kwargs.get('requirements', ())) kwargs['requirements'] = ['HAVE_TLS'] + requrements - super(TLSInt, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class Feature(Constant): @@ -70,7 +69,7 @@ class Feature(Constant): def __init__(self, name, c_feature, **kwargs): - super(Feature, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.c_feature = c_feature @@ -392,7 +391,7 @@ def pop_requirement(): if requirement not in current_requirements: current_requirements.append(requirement) print() - print('#if {}'.format(requirement)) + print(f'#if {requirement}') print(definition.c_template.format(self=definition)) diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index 811b3be1..73557168 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ controls.py - support classes for LDAP controls @@ -13,7 +12,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index b9994eb6..e5b2a7ec 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.deref - classes for (see https://tools.ietf.org/html/draft-masarati-ldap-deref) diff --git a/Lib/ldap/controls/libldap.py b/Lib/ldap/controls/libldap.py index f6ea42c4..9a102379 100644 --- a/Lib/ldap/controls/libldap.py +++ b/Lib/ldap/controls/libldap.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ controls.libldap - LDAP controls wrapper classes with en-/decoding done by OpenLDAP functions @@ -10,7 +9,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py index 5da2dd3f..24040ed7 100644 --- a/Lib/ldap/controls/openldap.py +++ b/Lib/ldap/controls/openldap.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.openldap - classes for OpenLDAP-specific controls diff --git a/Lib/ldap/controls/pagedresults.py b/Lib/ldap/controls/pagedresults.py index efdd0408..12ca573d 100644 --- a/Lib/ldap/controls/pagedresults.py +++ b/Lib/ldap/controls/pagedresults.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.paged - classes for Simple Paged control (see RFC 2696) diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index 67efe3ad..ebc456ad 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.ppolicy - classes for Password Policy controls (see https://tools.ietf.org/html/draft-behera-ldap-password-policy) diff --git a/Lib/ldap/controls/psearch.py b/Lib/ldap/controls/psearch.py index 002a88e9..32900c8b 100644 --- a/Lib/ldap/controls/psearch.py +++ b/Lib/ldap/controls/psearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.psearch - classes for Persistent Search Control (see https://tools.ietf.org/html/draft-ietf-ldapext-psearch) diff --git a/Lib/ldap/controls/pwdpolicy.py b/Lib/ldap/controls/pwdpolicy.py index cf9c1978..54f1a700 100644 --- a/Lib/ldap/controls/pwdpolicy.py +++ b/Lib/ldap/controls/pwdpolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.pwdpolicy - classes for Password Policy controls (see https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy) diff --git a/Lib/ldap/controls/readentry.py b/Lib/ldap/controls/readentry.py index 57cefef4..c5db4247 100644 --- a/Lib/ldap/controls/readentry.py +++ b/Lib/ldap/controls/readentry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.readentry - classes for the Read Entry controls (see RFC 4527) diff --git a/Lib/ldap/controls/sessiontrack.py b/Lib/ldap/controls/sessiontrack.py index 9c8a057f..a1fb8b34 100644 --- a/Lib/ldap/controls/sessiontrack.py +++ b/Lib/ldap/controls/sessiontrack.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.sessiontrack - class for session tracking control (see draft-wahl-ldap-session) diff --git a/Lib/ldap/controls/simple.py b/Lib/ldap/controls/simple.py index d4130348..05f6760d 100644 --- a/Lib/ldap/controls/simple.py +++ b/Lib/ldap/controls/simple.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.simple - classes for some very simple LDAP controls diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 5001987b..e6ee3686 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.sss - classes for Server Side Sorting (see RFC 2891) diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index 9fea2f03..5fc7ce88 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.vlv - classes for Virtual List View (see draft-ietf-ldapext-ldapv3-vlv) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 886ff877..a9d96846 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -7,7 +7,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap.functions diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index 39e653a9..dc9aea2f 100644 --- a/Lib/ldap/extop/__init__.py +++ b/Lib/ldap/extop/__init__.py @@ -28,7 +28,7 @@ def __init__(self,requestName,requestValue): self.requestValue = requestValue def __repr__(self): - return '%s(%s,%s)' % (self.__class__.__name__,self.requestName,self.requestValue) + return f'{self.__class__.__name__}({self.requestName},{self.requestValue})' def encodedRequestValue(self): """ @@ -53,7 +53,7 @@ def __init__(self,responseName,encodedResponseValue): self.responseValue = self.decodeResponseValue(encodedResponseValue) def __repr__(self): - return '%s(%s,%s)' % (self.__class__.__name__,self.responseName,self.responseValue) + return f'{self.__class__.__name__}({self.responseName},{self.responseValue})' def decodeResponseValue(self,value): """ diff --git a/Lib/ldap/extop/dds.py b/Lib/ldap/extop/dds.py index 4d156e83..7fab0813 100644 --- a/Lib/ldap/extop/dds.py +++ b/Lib/ldap/extop/dds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.extop.dds - Classes for Dynamic Entries extended operations (see RFC 2589) diff --git a/Lib/ldap/extop/passwd.py b/Lib/ldap/extop/passwd.py index 0a8346a8..13e9f252 100644 --- a/Lib/ldap/extop/passwd.py +++ b/Lib/ldap/extop/passwd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.extop.passwd - Classes for Password Modify extended operation (see RFC 3062) diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py index 3dba7f74..782737aa 100644 --- a/Lib/ldap/filter.py +++ b/Lib/ldap/filter.py @@ -71,7 +71,7 @@ def time_span_filter( if from_timestamp < 0: from_timestamp = until_timestamp + from_timestamp if from_timestamp > until_timestamp: - raise ValueError('from_timestamp %r must not be greater than until_timestamp %r' % ( + raise ValueError('from_timestamp {!r} must not be greater than until_timestamp {!r}'.format( from_timestamp, until_timestamp )) return ( diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 8c9dc626..8658db40 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -41,7 +41,7 @@ def _ldap_function_call(lock,func,*args,**kwargs): lock.acquire() if __debug__: if ldap._trace_level>=1: - ldap._trace_file.write('*** %s.%s %s\n' % ( + ldap._trace_file.write('*** {}.{} {}\n'.format( '_ldap',func.__name__, pprint.pformat((args,kwargs)) )) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index dcdeea5a..40091ad7 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -114,7 +114,7 @@ def _ldap_call(self,func,*args,**kwargs): self._ldap_object_lock.acquire() if __debug__: if self._trace_level>=1: - self._trace_file.write('*** %s %s - %s\n%s\n' % ( + self._trace_file.write('*** {} {} - {}\n{}\n'.format( repr(self), self._uri, '.'.join((self.__class__.__name__,func.__name__)), @@ -138,7 +138,7 @@ def _ldap_call(self,func,*args,**kwargs): except IndexError: pass if __debug__ and self._trace_level>=2: - self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e))) + self._trace_file.write('=> LDAPError - {}: {}\n'.format(e.__class__.__name__,str(e))) raise else: if __debug__ and self._trace_level>=2: @@ -159,7 +159,7 @@ def __getattr__(self,name): elif name in self.__dict__: return self.__dict__[name] else: - raise AttributeError('%s has no attribute %s' % ( + raise AttributeError('{} has no attribute {}'.format( self.__class__.__name__,repr(name) )) @@ -326,7 +326,7 @@ def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): except ldap.COMPARE_FALSE: return False raise ldap.PROTOCOL_ERROR( - 'Compare operation returned wrong result: %r' % (ldap_res,) + f'Compare operation returned wrong result: {ldap_res!r}' ) def compare(self,dn,attr,value): @@ -384,7 +384,7 @@ def extop_s(self,extreq,serverctrls=None,clientctrls=None,extop_resp_class=None) if extop_resp_class: respoid,respvalue = res if extop_resp_class.responseName!=respoid: - raise ldap.PROTOCOL_ERROR("Wrong OID in extended response! Expected %s, got %s" % (extop_resp_class.responseName,respoid)) + raise ldap.PROTOCOL_ERROR(f"Wrong OID in extended response! Expected {extop_resp_class.responseName}, got {respoid}") return extop_resp_class(extop_resp_class.responseName,respvalue) else: return res @@ -707,8 +707,8 @@ def search_subschemasubentry_s(self,dn=None): Returns: None or text/bytes depending on bytes_mode. """ - empty_dn = u'' - attrname = u'subschemaSubentry' + empty_dn = '' + attrname = 'subschemaSubentry' if dn is None: dn = empty_dn try: @@ -760,7 +760,7 @@ def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None): """ Returns the sub schema sub entry's data """ - filterstr = u'(objectClass=subschema)' + filterstr = '(objectClass=subschema)' if attrs is None: attrs = SCHEMA_ATTRS try: @@ -797,8 +797,8 @@ def read_rootdse_s(self, filterstr=None, attrlist=None): """ convenience wrapper around read_s() for reading rootDSE """ - base = u'' - attrlist = attrlist or [u'*', u'+'] + base = '' + attrlist = attrlist or ['*', '+'] ldap_rootdse = self.read_s( base, filterstr=filterstr, @@ -811,7 +811,7 @@ def get_naming_contexts(self): returns all attribute values of namingContexts in rootDSE if namingContexts is not present (not readable) then empty list is returned """ - name = u'namingContexts' + name = 'namingContexts' return self.read_rootdse_s( attrlist=[name] ).get(name, []) @@ -923,7 +923,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): while reconnect_counter: counter_text = '%d. (of %d)' % (retry_max-reconnect_counter+1,retry_max) if __debug__ and self._trace_level>=1: - self._trace_file.write('*** Trying %s reconnect to %s...\n' % ( + self._trace_file.write('*** Trying {} reconnect to {}...\n'.format( counter_text,uri )) try: @@ -941,7 +941,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): raise except (ldap.SERVER_DOWN,ldap.TIMEOUT): if __debug__ and self._trace_level>=1: - self._trace_file.write('*** %s reconnect to %s failed\n' % ( + self._trace_file.write('*** {} reconnect to {} failed\n'.format( counter_text,uri )) reconnect_counter = reconnect_counter-1 @@ -952,7 +952,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): time.sleep(retry_delay) else: if __debug__ and self._trace_level>=1: - self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % ( + self._trace_file.write('*** {} reconnect to {} successful => repeat last operation\n'.format( counter_text,uri )) self._reconnects_done = self._reconnects_done + 1 diff --git a/Lib/ldap/logger.py b/Lib/ldap/logger.py index 4db961e3..ae66bd08 100644 --- a/Lib/ldap/logger.py +++ b/Lib/ldap/logger.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- """ Helper class for using logging as trace file object """ import logging -class logging_file_class(object): +class logging_file_class: def __init__(self, logging_level): self._logging_level = logging_level diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index d99d2d00..2d88dc07 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- """ meta attributes for packaging which does not import any dependencies """ __version__ = '3.3.0' -__author__ = u'python-ldap project' +__author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 0ebd61e7..d73420c5 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -69,9 +69,9 @@ def key_attr(self,key,value,quoted=0): assert value is None or type(value)==str,TypeError("value has to be of str, was %r" % value) if value: if quoted: - return " %s '%s'" % (key,value.replace("'","\\'")) + return " {} '{}'".format(key,value.replace("'","\\'")) else: - return " %s %s" % (key,value) + return f" {key} {value}" else: return "" @@ -84,9 +84,9 @@ def key_list(self,key,values,sep=' ',quoted=0): else: quoted_values = values if len(values)==1: - return ' %s %s' % (key,quoted_values[0]) + return ' {} {}'.format(key,quoted_values[0]) else: - return ' %s ( %s )' % (key,sep.join(quoted_values)) + return ' {} ( {} )'.format(key,sep.join(quoted_values)) def __str__(self): result = [str(self.oid)] @@ -133,7 +133,7 @@ class ObjectClass(SchemaElement): implementations to indicate the source of the associated schema element """ - schema_attribute = u'objectClasses' + schema_attribute = 'objectClasses' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -240,7 +240,7 @@ class AttributeType(SchemaElement): implementations to indicate the source of the associated schema element """ - schema_attribute = u'attributeTypes' + schema_attribute = 'attributeTypes' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -334,7 +334,7 @@ class LDAPSyntax(SchemaElement): Integer flag (0 or 1) indicating whether the attribute type is marked as not human-readable (X-NOT-HUMAN-READABLE) """ - schema_attribute = u'ldapSyntaxes' + schema_attribute = 'ldapSyntaxes' token_defaults = { 'DESC':(None,), 'X-NOT-HUMAN-READABLE':(None,), @@ -383,7 +383,7 @@ class MatchingRule(SchemaElement): OID of the LDAP syntax this matching rule is usable with (string, or None if missing) """ - schema_attribute = u'matchingRules' + schema_attribute = 'matchingRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -429,7 +429,7 @@ class MatchingRuleUse(SchemaElement): NAMEs or OIDs of attribute types for which this matching rule is used (tuple of strings) """ - schema_attribute = u'matchingRuleUse' + schema_attribute = 'matchingRuleUse' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -489,7 +489,7 @@ class DITContentRule(SchemaElement): NAMEs or OIDs of attributes which may not be present in an entry of the object class. (tuple of strings) """ - schema_attribute = u'dITContentRules' + schema_attribute = 'dITContentRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -547,7 +547,7 @@ class DITStructureRule(SchemaElement): NAMEs or OIDs of allowed structural object classes of superior entries in the DIT (tuple of strings) """ - schema_attribute = u'dITStructureRules' + schema_attribute = 'dITStructureRules' token_defaults = { 'NAME':(()), @@ -610,7 +610,7 @@ class NameForm(SchemaElement): NAMEs or OIDs of additional attributes an RDN may contain (tuple of strings) """ - schema_attribute = u'nameForms' + schema_attribute = 'nameForms' token_defaults = { 'NAME':(()), 'DESC':(None,), diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 86e996f0..b83d819b 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -191,7 +191,7 @@ def tree(self,schema_element_class,schema_element_filters=None): # This helps with falsely assigned OIDs. continue assert se_obj.__class__==schema_element_class, \ - "Schema element referenced by %s must be of class %s but was %s" % ( + "Schema element referenced by {} must be of class {} but was {}".format( se_oid,schema_element_class.__name__,se_obj.__class__ ) for s in se_obj.sup or ('_',): @@ -216,7 +216,7 @@ def getoid(self,se_class,nameoroid,raise_keyerror=0): result_oid = self.name2oid[se_class][nameoroid_stripped] except KeyError: if raise_keyerror: - raise KeyError('No registered %s-OID for nameoroid %s' % (se_class.__name__,repr(nameoroid_stripped))) + raise KeyError('No registered {}-OID for nameoroid {}'.format(se_class.__name__,repr(nameoroid_stripped))) else: result_oid = nameoroid_stripped return result_oid @@ -249,7 +249,7 @@ def get_obj(self,se_class,nameoroid,default=None,raise_keyerror=0): se_obj = self.sed[se_class][se_oid] except KeyError: if raise_keyerror: - raise KeyError('No ldap.schema.%s instance with nameoroid %s and se_oid %s' % ( + raise KeyError('No ldap.schema.{} instance with nameoroid {} and se_oid {}'.format( se_class.__name__,repr(nameoroid),repr(se_oid)) ) else: @@ -461,7 +461,7 @@ def urlfetch(uri,trace_level=0): l=ldap.initialize(ldap_url.initializeUrl(),trace_level) l.protocol_version = ldap.VERSION3 - l.simple_bind_s(ldap_url.who or u'', ldap_url.cred or u'') + l.simple_bind_s(ldap_url.who or '', ldap_url.cred or '') subschemasubentry_dn = l.search_subschemasubentry_s(ldap_url.dn) if subschemasubentry_dn is None: s_temp = None diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index f6ac2d1a..1708b468 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.syncrepl - for implementing syncrepl consumer (see RFC 4533) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 0e03fcc2..cf3b6a3f 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -62,7 +62,7 @@ def ldapUrlEscape(s): """Returns URL encoding of string s""" return quote(s).replace(',','%2C').replace('/','%2F') -class LDAPUrlExtension(object): +class LDAPUrlExtension: """ Class for parsing and unparsing LDAP URL extensions as described in RFC 4516. @@ -103,9 +103,9 @@ def _parse(self,extension): def unparse(self): if self.exvalue is None: - return '%s%s' % ('!'*(self.critical>0),self.extype) + return '{}{}'.format('!'*(self.critical>0),self.extype) else: - return '%s%s=%s' % ( + return '{}{}={}'.format( '!'*(self.critical>0), self.extype,quote(self.exvalue or '') ) @@ -114,7 +114,7 @@ def __str__(self): return self.unparse() def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -176,7 +176,7 @@ def __str__(self): return ','.join(str(v) for v in self.values()) def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -198,7 +198,7 @@ def unparse(self): return ','.join(v.unparse() for v in self.values()) -class LDAPUrl(object): +class LDAPUrl: """ Class for parsing and unparsing LDAP URLs as described in RFC 4516. @@ -340,7 +340,7 @@ def initializeUrl(self): hostport = ldapUrlEscape(self.hostport) else: hostport = self.hostport - return '%s://%s' % (self.urlscheme,hostport) + return f'{self.urlscheme}://{hostport}' def unparse(self): """ @@ -361,7 +361,7 @@ def unparse(self): hostport = ldapUrlEscape(self.hostport) else: hostport = self.hostport - ldap_url = '%s://%s/%s?%s?%s?%s' % ( + ldap_url = '{}://{}/{}?{}?{}?{}'.format( self.urlscheme, hostport,dn,attrs_str,scope_str,filterstr ) @@ -395,7 +395,7 @@ def htmlHREF(self,urlPrefix='',hrefText=None,hrefTarget=None): raise TypeError("hrefTarget must be str, not " + type(hrefTarget).__name__) target = ' target="%s"' % hrefTarget - return '%s' % ( + return '{}'.format( target, urlPrefix, self.unparse(), hrefText ) @@ -403,7 +403,7 @@ def __str__(self): return self.unparse() def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -420,7 +420,7 @@ def __getattr__(self,name): else: return None else: - raise AttributeError('%s has no attribute %s' % ( + raise AttributeError('{} has no attribute {}'.format( self.__class__.__name__,name )) return result # __getattr__() diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 1371bef2..b57cd44a 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ slapdtest - module for spawning test instances of OpenLDAP's slapd server diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 141f459b..8dcb7133 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ slapdtest - module for spawning test instances of OpenLDAP's slapd server @@ -159,7 +158,7 @@ def combined_logger( return new_logger # end of combined_logger() -class SlapdObject(object): +class SlapdObject: """ Controller class for a slapd instance, OpenLDAP's server. diff --git a/Tests/__init__.py b/Tests/__init__.py index d6545732..ea28d0ce 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap See https://www.python-ldap.org/ for details. """ -from __future__ import absolute_import from . import t_bind from . import t_cext diff --git a/Tests/t_cext.py b/Tests/t_cext.py index c271531a..8333b0f4 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's C wrapper module _ldap @@ -28,7 +27,7 @@ class TestLdapCExtension(SlapdTestCase): @classmethod def setUpClass(cls): - super(TestLdapCExtension, cls).setUpClass() + super().setUpClass() # add two initial objects after server was started and is still empty suffix_dc = cls.server.suffix.split(',')[0][3:] cls.server._log.debug( @@ -52,14 +51,14 @@ def setUpClass(cls): ) def setUp(self): - super(TestLdapCExtension, self).setUp() + super().setUp() self._writesuffix = None def tearDown(self): # cleanup test subtree if self._writesuffix is not None: self.server.ldapdelete(self._writesuffix, recursive=True) - super(TestLdapCExtension, self).tearDown() + super().tearDown() @property def writesuffix(self): @@ -288,7 +287,7 @@ def test_anon_rootdse_search(self): '', _ldap.SCOPE_BASE, '(objectClass=*)', - [str('objectClass'), str('namingContexts')], + ['objectClass', 'namingContexts'], ) self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 6878617e..aa9a77e6 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.cidict diff --git a/Tests/t_edit.py b/Tests/t_edit.py index f79ff18f..5d8b3f06 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -13,7 +13,7 @@ class EditionTests(SlapdTestCase): @classmethod def setUpClass(cls): - super(EditionTests, cls).setUpClass() + super().setUpClass() base = cls.server.suffix suffix_dc = base.split(',')[0][3:] diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index d62ec719..86d36403 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.dn diff --git a/Tests/t_ldap_filter.py b/Tests/t_ldap_filter.py index da96446c..313b3733 100644 --- a/Tests/t_ldap_filter.py +++ b/Tests/t_ldap_filter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.filter diff --git a/Tests/t_ldap_functions.py b/Tests/t_ldap_functions.py index 45931dab..5434a37a 100644 --- a/Tests/t_ldap_functions.py +++ b/Tests/t_ldap_functions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.functions diff --git a/Tests/t_ldap_modlist.py b/Tests/t_ldap_modlist.py index 53c80cc3..62596247 100644 --- a/Tests/t_ldap_modlist.py +++ b/Tests/t_ldap_modlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.modlist @@ -44,7 +43,7 @@ def test_addModlist(self): result_modlist.sort() self.assertEqual( test_modlist, result_modlist, - 'addModlist(%s) returns\n%s\ninstead of\n%s.' % ( + 'addModlist({}) returns\n{}\ninstead of\n{}.'.format( repr(entry),repr(result_modlist),repr(test_modlist) ) ) @@ -146,7 +145,7 @@ def test_modifyModlist(self): self.assertEqual( test_modlist, result_modlist, - 'modifyModlist(%s,%s) returns\n%s\ninstead of\n%s.' % ( + 'modifyModlist({},{}) returns\n{}\ninstead of\n{}.'.format( repr(old_entry), repr(new_entry), repr(result_modlist), diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 684fdf7b..89f21a43 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -28,7 +28,7 @@ ] -class BaseTestOptions(object): +class BaseTestOptions: """Common tests for getting/setting options Used in subclasses below @@ -97,7 +97,7 @@ def _test_controls(self, option): # data must be bytes or None self.set_option( option, - [TEST_CTRL[0][0], TEST_CTRL[0][1], u'data'] + [TEST_CTRL[0][0], TEST_CTRL[0][1], 'data'] ) def test_client_controls(self): diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 9cf675af..40ab27e9 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.sasl @@ -47,7 +46,7 @@ class TestSasl(SlapdTestCase): @classmethod def setUpClass(cls): - super(TestSasl, cls).setUpClass() + super().setUpClass() ldif = LDIF.format( suffix=cls.server.suffix, rootdn=cls.server.root_dn, @@ -72,7 +71,7 @@ def test_external_ldapi(self): ldap_conn.sasl_interactive_bind_s("", auth) self.assertEqual( ldap_conn.whoami_s().lower(), - "dn:{}".format(self.server.root_dn.lower()) + f"dn:{self.server.root_dn.lower()}" ) @requires_tls() @@ -89,7 +88,7 @@ def test_external_tlscert(self): ldap_conn.sasl_interactive_bind_s("", auth) self.assertEqual( ldap_conn.whoami_s().lower(), - "dn:{}".format(self.certsubject) + f"dn:{self.certsubject}" ) if __name__ == '__main__': diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index e05c957a..60a584d1 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's class ldap.schema.SubSchema diff --git a/Tests/t_ldap_schema_tokenizer.py b/Tests/t_ldap_schema_tokenizer.py index 0890379a..20e86ab6 100644 --- a/Tests/t_ldap_schema_tokenizer.py +++ b/Tests/t_ldap_schema_tokenizer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.schema.tokenizer diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 51104148..6acc82c4 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.syncrepl @@ -266,7 +265,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): pass -class BaseSyncreplTests(object): +class BaseSyncreplTests: """ This is a test of all the basic Syncrepl operations. It covers starting a search (both types of search), doing the refresh part of the search, @@ -279,7 +278,7 @@ class BaseSyncreplTests(object): @classmethod def setUpClass(cls): - super(BaseSyncreplTests, cls).setUpClass() + super().setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -292,13 +291,13 @@ def setUpClass(cls): ) def setUp(self): - super(BaseSyncreplTests, self).setUp() + super().setUp() self.tester = None self.suffix = None def tearDown(self): self.tester.unbind_s() - super(BaseSyncreplTests, self).tearDown() + super().tearDown() def create_client(self): raise NotImplementedError @@ -433,7 +432,7 @@ def test_refreshAndPersist_cancelled(self): class TestSyncrepl(BaseSyncreplTests, SlapdTestCase): def setUp(self): - super(TestSyncrepl, self).setUp() + super().setUp() self.tester = SyncreplClient( self.server.ldap_uri, self.server.root_dn, diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 6d44ff1d..e54bbfd4 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.ldapobject @@ -91,7 +90,7 @@ class Test00_SimpleLDAPObject(SlapdTestCase): @classmethod def setUpClass(cls): - super(Test00_SimpleLDAPObject, cls).setUpClass() + super().setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -297,11 +296,11 @@ def test_search_subschema(self): self.assertEqual( sorted(subschema), [ - u'attributeTypes', - u'ldapSyntaxes', - u'matchingRuleUse', - u'matchingRules', - u'objectClasses' + 'attributeTypes', + 'ldapSyntaxes', + 'matchingRuleUse', + 'matchingRules', + 'objectClasses' ] ) @@ -318,7 +317,7 @@ def test004_enotconn(self): info = ldap_err.args[0]['info'] expected_info = os.strerror(errno.ENOTCONN) if info != expected_info: - self.fail("expected info=%r, got %r" % (expected_info, info)) + self.fail(f"expected info={expected_info!r}, got {info!r}") else: self.fail("expected SERVER_DOWN, got %r" % r) @@ -360,10 +359,10 @@ def assertIsSubclass(self, cls, other): def test_simple_bind_noarg(self): l = self.ldap_object_class(self.server.ldap_uri) l.simple_bind_s() - self.assertEqual(l.whoami_s(), u'') + self.assertEqual(l.whoami_s(), '') l = self.ldap_object_class(self.server.ldap_uri) l.simple_bind_s(None, None) - self.assertEqual(l.whoami_s(), u'') + self.assertEqual(l.whoami_s(), '') def _check_byteswarning(self, warning, expected_message): self.assertIs(warning.category, ldap.LDAPBytesWarning) @@ -401,16 +400,16 @@ def test_multiple_starttls(self): def test_dse(self): dse = self._ldap_conn.read_rootdse_s() self.assertIsInstance(dse, dict) - self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) + self.assertEqual(dse['supportedLDAPVersion'], [b'3']) keys = set(dse) # SASL info may be missing in restricted build environments - keys.discard(u'supportedSASLMechanisms') + keys.discard('supportedSASLMechanisms') self.assertEqual( keys, - {u'configContext', u'entryDN', u'namingContexts', u'objectClass', - u'structuralObjectClass', u'subschemaSubentry', - u'supportedControl', u'supportedExtension', u'supportedFeatures', - u'supportedLDAPVersion'} + {'configContext', 'entryDN', 'namingContexts', 'objectClass', + 'structuralObjectClass', 'subschemaSubentry', + 'supportedControl', 'supportedExtension', 'supportedFeatures', + 'supportedLDAPVersion'} ) self.assertEqual( self._ldap_conn.get_naming_contexts(), @@ -542,20 +541,20 @@ def test103_reconnect_get_state(self): self.assertEqual( l1.__getstate__(), { - str('_last_bind'): ( + '_last_bind': ( 'simple_bind_s', (bind_dn, 'user1_pw'), {} ), - str('_options'): [(17, 3)], - str('_reconnects_done'): 0, - str('_retry_delay'): 60.0, - str('_retry_max'): 1, - str('_start_tls'): 0, - str('_trace_level'): ldap._trace_level, - str('_trace_stack_limit'): 5, - str('_uri'): self.server.ldap_uri, - str('timeout'): -1, + '_options': [(17, 3)], + '_reconnects_done': 0, + '_retry_delay': 60.0, + '_retry_max': 1, + '_start_tls': 0, + '_trace_level': ldap._trace_level, + '_trace_stack_limit': 5, + '_uri': self.server.ldap_uri, + 'timeout': -1, }, ) @@ -595,14 +594,14 @@ def _open_ldap_conn(self, who=None, cred=None, **kwargs): self._sock = socket.create_connection( (self.server.hostname, self.server.port) ) - return super(Test03_SimpleLDAPObjectWithFileno, self)._open_ldap_conn( + return super()._open_ldap_conn( who=who, cred=cred, fileno=self._sock.fileno(), **kwargs ) def tearDown(self): self._sock.close() del self._sock - super(Test03_SimpleLDAPObjectWithFileno, self).tearDown() + super().tearDown() def reset_connection(self): self._sock.close() diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 398dc892..4e7744b4 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldapurl @@ -160,7 +159,7 @@ def test_ldapurl(self): ldap_url_obj = LDAPUrl(ldapUrl=ldap_url_str) self.assertEqual( ldap_url_obj, test_ldap_url_obj, - 'Attributes of LDAPUrl(%s) are:\n%s\ninstead of:\n%s' % ( + 'Attributes of LDAPUrl({}) are:\n{}\ninstead of:\n{}'.format( repr(ldap_url_str), repr(ldap_url_obj), repr(test_ldap_url_obj), @@ -170,7 +169,7 @@ def test_ldapurl(self): unparsed_ldap_url_obj = LDAPUrl(ldapUrl=unparsed_ldap_url_str) self.assertEqual( unparsed_ldap_url_obj, test_ldap_url_obj, - 'Attributes of LDAPUrl(%s) are:\n%s\ninstead of:\n%s' % ( + 'Attributes of LDAPUrl({}) are:\n{}\ninstead of:\n{}'.format( repr(unparsed_ldap_url_str), repr(unparsed_ldap_url_obj), repr(test_ldap_url_obj), diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 254e68d6..2f9ed679 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldif From 2647f59dfd25b4bed1cc69ead0b9dce7a59dcf4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 22:19:26 +0100 Subject: [PATCH 139/206] Removed some python2-only code from SlapdObject https://github.com/python-ldap/python-ldap/pull/391 --- Lib/slapdtest/_slapdtest.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 8dcb7133..36841110 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -461,15 +461,7 @@ def stop(self): self._proc.terminate() self.wait() self._cleanup_rundir() - if hasattr(atexit, 'unregister'): - # Python 3 - atexit.unregister(self.stop) - elif hasattr(atexit, '_exithandlers'): - # Python 2, can be None during process shutdown - try: - atexit._exithandlers.remove(self.stop) - except ValueError: - pass + atexit.unregister(self.stop) def restart(self): """ From c2e045de99aa5dd3e626c0dcad62282436a61015 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 6 Nov 2020 17:14:37 +0100 Subject: [PATCH 140/206] Add missing cidict.copy() method The copy() method vanished when cidict was re-implemented on top of MutableMapping abstract base class. See: commit 2a2cef38feb7f259ea6b5735a6ce3e46812e217d Fixes: https://github.com/python-ldap/python-ldap/issues/387 Signed-off-by: Christian Heimes --- Lib/ldap/cidict.py | 8 ++++++++ Tests/t_cidict.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index a5ac29e0..ac19bd7d 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -53,6 +53,14 @@ def clear(self): self._keys.clear() self._data.clear() + def copy(self): + inst = self.__class__.__new__(self.__class__) + inst._data = self._data.copy() + inst._keys = self._keys.copy() + return inst + + __copy__ = copy + # Backwards compatibility def has_key(self, key): diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index aa9a77e6..97146ec8 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -71,6 +71,19 @@ def test_cidict_data(self): assert data == {'a': 1, 'b': 2} self.assertEqual(len(w), 1) + def test_copy(self): + cix1 = ldap.cidict.cidict( + {"a": 1, "B": 2} + ) + cix2 = cix1.copy() + self.assertEqual(cix1, cix2) + cix1["c"] = 3 + self.assertNotIn("c", cix2) + cix2["C"] = 4 + self.assertNotEqual(cix1, cix2) + self.assertEqual(list(cix1.keys()), ["a", "B", "c"]) + self.assertEqual(list(cix2.keys()), ["a", "B", "C"]) + if __name__ == '__main__': unittest.main() From a66dcc36aa28b120bb5341f934be9bf8de7e21d7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Nov 2020 08:08:23 -0800 Subject: [PATCH 141/206] Tidy up Travis configuration - Use the Python 3.9 release rather than '*-dev' - Drop 'sudo' configuration as it is deprecated - Drop 'dist: xenial' configuration, it is now the default https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17d46bf3..95cc1489 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: python group: travis_latest -sudo: false - cache: pip addons: @@ -35,22 +33,16 @@ matrix: - TOXENV=py37 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - python: 3.8 env: - TOXENV=py38 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - - python: 3.9-dev + - python: 3.9 env: - TOXENV=py39 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - python: 3.6 env: - TOXENV=py3-nosasltls From 0ef617548176a89cbb7fa2ee7f748920e10fb5bf Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 14 Nov 2020 13:05:35 +0100 Subject: [PATCH 142/206] LDAPUrl now treats urlscheme cases insensitive https://github.com/python-ldap/python-ldap/pull/347 Fixes: https://github.com/python-ldap/python-ldap/issues/280 Signed-off-by: Christian Heimes Co-authored-by: David Mulder --- Doc/reference/ldapurl.rst | 5 +++++ Lib/ldapurl.py | 15 ++++----------- Tests/t_ldapurl.py | 30 ++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Doc/reference/ldapurl.rst b/Doc/reference/ldapurl.rst index de86de7e..eb2106b3 100644 --- a/Doc/reference/ldapurl.rst +++ b/Doc/reference/ldapurl.rst @@ -65,6 +65,11 @@ A :py:class:`LDAPUrl` object represents a complete LDAP URL. .. autoclass:: ldapurl.LDAPUrl :members: + .. versionchanged:: 3.4.0 + + The urlscheme is now case insensitive and always converted to lower + case. ``LDAP://localhost`` is equivalent to ``ldap://localhost``. + LDAP URL extensions ^^^^^^^^^^^^^^^^^^^ diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index cf3b6a3f..820f3d84 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -48,14 +48,9 @@ def isLDAPUrl(s): + """Returns True if s is a LDAP URL, else False """ - Returns 1 if s is a LDAP URL, 0 else - """ - s_lower = s.lower() - return \ - s_lower.startswith('ldap://') or \ - s_lower.startswith('ldaps://') or \ - s_lower.startswith('ldapi://') + return s.lower().startswith(('ldap://', 'ldaps://', 'ldapi://')) def ldapUrlEscape(s): @@ -235,7 +230,7 @@ def __init__( extensions=None, who=None,cred=None ): - self.urlscheme=urlscheme + self.urlscheme=urlscheme.lower() self.hostport=hostport self.dn=dn self.attrs=attrs @@ -270,9 +265,7 @@ def _parse(self,ldap_url): if not isLDAPUrl(ldap_url): raise ValueError('Value %s for ldap_url does not seem to be a LDAP URL.' % (repr(ldap_url))) scheme,rest = ldap_url.split('://',1) - self.urlscheme = scheme.strip() - if not self.urlscheme in ['ldap','ldaps','ldapi']: - raise ValueError('LDAP URL contains unsupported URL scheme %s.' % (self.urlscheme)) + self.urlscheme = scheme.lower() slash_pos = rest.find('/') qemark_pos = rest.find('?') if (slash_pos==-1) and (qemark_pos==-1): diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 4e7744b4..f9c72098 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -33,18 +33,23 @@ class TestIsLDAPUrl(unittest.TestCase): 'ldap://host.com:6666/o=University%20of%20Michigan,':1, 'ldap://ldap.itd.umich.edu/c=GB?objectClass?one':1, 'ldap://ldap.question.com/o=Question%3f,c=US?mail':1, - 'ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04)':1, + 'ldap://ldap.netscape.com/o=Babsco,c=US???(int=%5c00%5c00%5c00%5c04)':1, 'ldap:///??sub??bindname=cn=Manager%2co=Foo':1, 'ldap:///??sub??!bindname=cn=Manager%2co=Foo':1, # More examples from various sources 'ldap://ldap.nameflow.net:1389/c%3dDE':1, 'ldap://root.openldap.org/dc=openldap,dc=org':1, - 'ldap://root.openldap.org/dc=openldap,dc=org':1, + 'ldaps://root.openldap.org/dc=openldap,dc=org':1, 'ldap://x500.mh.se/o=Mitthogskolan,c=se????1.2.752.58.10.2=T.61':1, 'ldp://root.openldap.org/dc=openldap,dc=org':0, 'ldap://localhost:1389/ou%3DUnstructured%20testing%20tree%2Cdc%3Dstroeder%2Cdc%3Dcom??one':1, 'ldaps://ldap.example.com/c%3dDE':1, 'ldapi:///dc=stroeder,dc=de????x-saslmech=EXTERNAL':1, + 'LDAP://localhost': True, + 'LDAPS://localhost': True, + 'LDAPI://%2Frun%2Fldap.sock': True, + ' ldap://space.example': False, + 'ldap ://space.example': False, } def test_isLDAPUrl(self): @@ -56,6 +61,11 @@ def test_isLDAPUrl(self): ldap_url, result, expected, ) ) + if expected: + LDAPUrl(ldapUrl=ldap_url) + else: + with self.assertRaises(ValueError): + LDAPUrl(ldapUrl=ldap_url) class TestParseLDAPUrl(unittest.TestCase): @@ -144,6 +154,22 @@ class TestParseLDAPUrl(unittest.TestCase): dn='dc=stroeder,dc=com', ), ), + ( + 'LDAPS://localhost:12345/dc=stroeder,dc=com', + LDAPUrl( + urlscheme='ldaps', + hostport='localhost:12345', + dn='dc=stroeder,dc=com', + ), + ), + ( + 'ldaps://localhost:12345/dc=stroeder,dc=com', + LDAPUrl( + urlscheme='LDAPS', + hostport='localhost:12345', + dn='dc=stroeder,dc=com', + ), + ), ( 'ldapi://%2ftmp%2fopenldap2-1389/dc=stroeder,dc=com', LDAPUrl( From 4993d4ade24faceb3321f85f5e69a8b83c4f77f4 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 4 Dec 2020 15:09:00 +0100 Subject: [PATCH 143/206] Do not decode attribute values of post read control Fixes: https://github.com/python-ldap/python-ldap/issues/399 https://github.com/python-ldap/python-ldap/pull/400 --- Doc/reference/ldap-controls.rst | 3 +++ Lib/ldap/controls/readentry.py | 2 +- Tests/t_ldap_controls_readentry.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Tests/t_ldap_controls_readentry.py diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 1520b241..999eaa9a 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -197,6 +197,9 @@ search. :rfc:`4527` - Lightweight Directory Access Protocol (LDAP): Read Entry Controls +.. versionchanged:: 4.0 + The attribute values of the entry now consists of `bytes` instead of ISO8859-1 decoded `str`. + .. autoclass:: ldap.controls.readentry.ReadEntryControl :members: diff --git a/Lib/ldap/controls/readentry.py b/Lib/ldap/controls/readentry.py index c5db4247..7b2a7e89 100644 --- a/Lib/ldap/controls/readentry.py +++ b/Lib/ldap/controls/readentry.py @@ -42,7 +42,7 @@ def decodeControlValue(self,encodedControlValue): self.dn = str(decodedEntry[0]) self.entry = {} for attr in decodedEntry[1]: - self.entry[str(attr[0])] = [ str(attr_value) for attr_value in attr[1] ] + self.entry[str(attr[0])] = [ bytes(attr_value) for attr_value in attr[1] ] class PreReadControl(ReadEntryControl): diff --git a/Tests/t_ldap_controls_readentry.py b/Tests/t_ldap_controls_readentry.py new file mode 100644 index 00000000..313a7905 --- /dev/null +++ b/Tests/t_ldap_controls_readentry.py @@ -0,0 +1,28 @@ +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +from ldap.controls import readentry # noqa: E402 + + +PRC_ENC = b'db\x04)uid=Administrator,cn=users,l=school,l=dev0503\x04\tentryUUID1&\x04$5d96cc2c-8e13-103a-8ca5-2f74868e0e44' +PRC_DEC = b'0\x0b\x04\tentryUUID' + + +class TestLibldapControls(unittest.TestCase): + + def test_pagedresults_encode(self): + pr = readentry.PostReadControl(True, ['entryUUID']) + self.assertEqual(pr.encodeControlValue(), PRC_DEC) + + def test_readentry_decode(self): + pr = readentry.PostReadControl(True, ['entryUUID']) + pr.decodeControlValue(PRC_ENC) + self.assertIsInstance(pr.dn, str) + self.assertEqual(pr.entry, {'entryUUID': [b'5d96cc2c-8e13-103a-8ca5-2f74868e0e44']}) + + +if __name__ == '__main__': + unittest.main() From 7dfa21a1631adb3a7c7c142f7c5d5a152f9403b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 4 Dec 2020 15:20:14 +0100 Subject: [PATCH 144/206] Improve ppolicy documentation https://github.com/python-ldap/python-ldap/pull/397 --- Doc/conf.py | 1 + Doc/reference/ldap-controls.rst | 13 +++++++++++++ Lib/ldap/controls/ppolicy.py | 20 +++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 0fa396b8..30f27306 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -33,6 +33,7 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', ] try: diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 999eaa9a..37d7c1bc 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -209,3 +209,16 @@ search. .. autoclass:: ldap.controls.readentry.PostReadControl :members: + + +:py:mod:`ldap.controls.ppolicy` Password Policy Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. seealso:: + `draft-behera-ldap-password-policy `_ + +.. py:module:: ldap.controls.ppolicy + :synopsis: passworld policies + +.. autoclass:: ldap.controls.ppolicy.PasswordPolicyControl + :members: diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index ebc456ad..da7586f0 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -62,17 +62,31 @@ class PasswordPolicyResponseValue(univ.Sequence): class PasswordPolicyControl(ValueLessRequestControl,ResponseControl): + """ + Indicates the errors and warnings about the password policy. + + Attributes + ---------- + + timeBeforeExpiration : int + The time before the password expires. + + graceAuthNsRemaining : int + The number of grace authentications remaining. + + error: int + The password and authentication errors. + """ controlType = '1.3.6.1.4.1.42.2.27.8.5.1' def __init__(self,criticality=False): self.criticality = criticality - - def decodeControlValue(self,encodedControlValue): - ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) self.timeBeforeExpiration = None self.graceAuthNsRemaining = None self.error = None + def decodeControlValue(self,encodedControlValue): + ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) warning = ppolicyValue.getComponentByName('warning') if warning.hasValue(): if 'timeBeforeExpiration' in warning: From 2fb97af1e025a95d1ea86b9aeac6235358e52278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Tue, 15 Dec 2020 11:09:32 +0100 Subject: [PATCH 145/206] Remove the outdated documentation copyright year https://github.com/python-ldap/python-ldap/pull/398 --- Doc/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index 30f27306..b883736e 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -10,6 +10,7 @@ # All configuration values have a default value; values that are commented out # serve to show the default value. +import datetime import sys import os @@ -54,7 +55,7 @@ # General substitutions. project = 'python-ldap' -copyright = '2008-2017, python-ldap project team' +copyright = 'python-ldap project team' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. From 25d0ca01b26ec213da3670819b5d0df48ec0f1d2 Mon Sep 17 00:00:00 2001 From: Fred Thomsen Date: Tue, 29 Jun 2021 06:35:53 -0400 Subject: [PATCH 146/206] Add note to FAQ regarding search referrals Several posts across the web in regards to this issue, so an additional note in the answer to this question seems warranted. Basing this answer on message this message in the mailing list: https://mail.python.org/pipermail/python-ldap/2014q1/003350.html https://github.com/python-ldap/python-ldap/pull/409 --- Doc/faq.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/faq.rst b/Doc/faq.rst index 2152873b..492b95f2 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -65,6 +65,10 @@ connection.”* Alternatively, a Samba 4 AD returns the diagnostic message l = ldap.initialize('ldap://foobar') l.set_option(ldap.OPT_REFERRALS,0) + Note that setting the above option does NOT prevent search continuations + from being returned, rather only that ``libldap`` won't attempt to resolve + referrals. + **Q**: Why am I seeing a ``ldap.SUCCESS`` traceback as output? **A**: Most likely, you are using one of the non-synchronous calls, and probably From 7d6979073bf12538c2d1d9b218de3f47bbae8a2f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 29 Jun 2021 15:15:24 +0200 Subject: [PATCH 147/206] Test with GHA Drop Travis CI and use Github Actions Signed-off-by: Christian Heimes --- .github/workflows/ci.yml | 35 +++++++++++++++++++ .travis.yml | 75 ---------------------------------------- tox.ini | 8 +++++ 3 files changed, 43 insertions(+), 75 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..0bc2ae0e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +--- +name: CI + +on: [push, pull_request] + +jobs: + distros: + name: "Ubuntu with Python ${{ matrix.python-version }}" + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + steps: + - name: Checkout + uses: "actions/checkout@v2" + - name: Install apt dependencies + run: | + set -ex + sudo apt update + sudo apt install -y ldap-utils slapd enchant libldap2-dev libsasl2-dev apparmor-utils + - name: Disable AppArmor + run: sudo aa-disable /usr/sbin/slapd + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: "Install Python dependencies" + run: | + set -xe + python -V + python -m pip install --upgrade pip setuptools + python -m pip install --upgrade tox tox-gh-actions + - name: "Test tox with Python ${{ matrix.python-version }}" + run: "python -m tox" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 95cc1489..00000000 --- a/.travis.yml +++ /dev/null @@ -1,75 +0,0 @@ -language: python -group: travis_latest - -cache: pip - -addons: - apt: - packages: - - ldap-utils - - slapd - - enchant - -# Note: when updating Python versions, also change setup.py and tox.ini -matrix: - include: - - os: osx - osx_image: xcode11.4 - language: minimal - env: - - TOXENV=macos - - CFLAGS_warnings="-Wall -Werror=declaration-after-statement" - - CFLAGS_std="-std=c99" - - python: 3.6 - env: - - TOXENV=py36 - - WITH_GCOV=1 - - python: pypy3 - env: - - TOXENV=pypy3 - - CFLAGS_std="-std=c99" - - python: 3.7 - env: - - TOXENV=py37 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.8 - env: - - TOXENV=py38 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.9 - env: - - TOXENV=py39 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.6 - env: - - TOXENV=py3-nosasltls - - WITH_GCOV=1 - - python: 3.6 - env: - - TOXENV=py3-trace - - python: 3.6 - env: TOXENV=doc - allow_failures: - - env: - - TOXENV=pypy3 - -env: - global: - # -Wno-int-in-bool-context: don't complain about PyMem_MALLOC() - # -Werror: turn all warnings into fatal errors - # -Werror=declaration-after-statement: strict ISO C90 - - CFLAGS_warnings="-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" - # Keep C90 compatibility where possible. - # (Python 3.8+ headers use C99 features, so this needs to be overridable.) - - CFLAGS_std="-std=c90" - # pass CFLAGS, CI (for Travis CI) and WITH_GCOV to tox tasks - - TOX_TESTENV_PASSENV="CFLAGS CI WITH_GCOV" - -install: - - python3 -m pip install "pip>=7.1.0" - - python3 -m pip install tox-travis tox codecov - -script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" python3 -m tox diff --git a/tox.ini b/tox.ini index 65773a2c..26982d0f 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,14 @@ envlist = py36,py37,py38,py39,py3-nosasltls,doc,py3-trace minver = 1.8 +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38, doc, py3-nosasltls + 3.9: py39, py3-trace + pypy3: pypy3 + [testenv] deps = passenv = WITH_GCOV From 9f9bc4ca3fd21c66dfaa83378992fa7f24e452e9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Jul 2021 15:40:15 +0200 Subject: [PATCH 148/206] [RELICENCE] Adopt the MIT licence for future contributions to python-ldap * Adopt the MIT licence for future contributions - Add LICENCE.MIT for all contributions from July 2021 onward - Explain the licencing situation - Start a list of people who agree to relicence their past contributions to MIT https://github.com/python-ldap/python-ldap/pull/417 --- LICENCE.MIT | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ README | 24 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 LICENCE.MIT diff --git a/LICENCE.MIT b/LICENCE.MIT new file mode 100644 index 00000000..c78bcb9b --- /dev/null +++ b/LICENCE.MIT @@ -0,0 +1,51 @@ +The MIT License applies to contributions committed after July 1st, 2021, and +to all contributions by the following authors: + +* ​A. Karl Kornel +* Alex Willmer +* Aymeric Augustin +* Bernhard M. Wiedemann +* Bradley Baetz +* Éloi Rivard +* Eyal Cherevatzki +* Fred Thomsen +* Ivan A. Melnikov +* johnthagen +* Jonathon Reinhart +* Jon Dufresne +* Martin Basti +* Marti Raudsepp +* Miro Hrončok +* Petr Viktorin +* Pieterjan De Potter +* Raphaël Barrois +* Robert Kuska +* Stanislav Láznička +* Tobias Bräutigam +* Tom van Dijk +* William Brown + + +------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 python-ldap contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README b/README index 81db9bb3..8045555d 100644 --- a/README +++ b/README @@ -127,3 +127,27 @@ their contributions and input into this package. Thanks! We may have missed someone: please mail us if we have omitted your name. + +Licence +======= + +The python-ldap project comes with a LICENCE file. + +We are aware that its text is unclear, but it cannot be changed: +all authors of python-ldap would need to approve the licence change, +but a complete list of all the authors is not available. +(Note that the Git repository of the project is incomplete. +Furthermore, commits imported from CVS lack authorship information; users +"stroeder" or "leonard" are commiters (reviewers), but sometimes not +authors of the committed code.) + +The current maintainers assume that the license is the sentence that refers +to "Python-style license" and assume this means a highly permissive open source +license that only requires preservation of the text of the LICENCE file +(including the disclaimer paragraph). + +------------------------------------------------------------------------------- + +All contributions committed since July 1st, 2021, as well as some past +contributions, are licensed under the MIT license. +The MIT licence and more details are listed in the file LICENCE.MIT. From 69867f5817bc81194fdb45fc478f7ec2b36f27cf Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Jul 2021 15:42:05 +0200 Subject: [PATCH 149/206] Clean up tox environments & run tox on Fedora in CI * Add CFLAGS and -std=c90 runs to tox config * Add Python 3.10 to tox config * List the pypy3 tox environment * Remove mentions of Python 2 from docs & Makefile * Update Travis CI mention in setup.py * Add tox-fedora GH workflow https://github.com/python-ldap/python-ldap/pull/419 --- .github/workflows/tox-fedora.yml | 34 ++++++++++++++++++++++++++++++++ Doc/installing.rst | 10 +++++----- Doc/sample_workflow.rst | 6 +++--- Makefile | 2 +- setup.py | 2 +- tox.ini | 11 +++++++++-- 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/tox-fedora.yml diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml new file mode 100644 index 00000000..c0dbb45c --- /dev/null +++ b/.github/workflows/tox-fedora.yml @@ -0,0 +1,34 @@ +on: [push, pull_request] + +name: Tox on Fedora + +jobs: + tox_test: + name: Tox env "${{matrix.tox_env}}" on Fedora + steps: + - uses: actions/checkout@v2 + - name: Run Tox tests + uses: fedora-python/tox-github-action@master + with: + tox_env: ${{ matrix.tox_env }} + dnf_install: > + @c-development openldap-devel python3-devel + openldap-servers openldap-clients lcov clang-analyzer valgrind + enchant + strategy: + matrix: + tox_env: + - py36 + - py37 + - py38 + - py39 + - py310 + - c90-py36 + - c90-py37 + - py3-nosasltls + - py3-trace + - pypy3 + - doc + + # Use GitHub's Linux Docker host + runs-on: ubuntu-latest diff --git a/Doc/installing.rst b/Doc/installing.rst index 56778220..521910e4 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -130,7 +130,7 @@ Alpine Packages for building:: - # apk add build-base openldap-dev python2-dev python3-dev + # apk add build-base openldap-dev python3-dev CentOS ------ @@ -145,12 +145,12 @@ Debian Packages for building and testing:: - # apt-get install build-essential python3-dev python2.7-dev \ + # apt-get install build-essential python3-dev \ libldap2-dev libsasl2-dev slapd ldap-utils tox \ lcov valgrind - + .. note:: - + On older releases ``tox`` was called ``python-tox``. Fedora @@ -159,7 +159,7 @@ Fedora Packages for building and testing:: # dnf install "@C Development Tools and Libraries" openldap-devel \ - python2-devel python3-devel python3-tox \ + python3-devel python3-tox \ lcov clang-analyzer valgrind .. note:: diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index 60d60cac..76017034 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -61,15 +61,15 @@ This will run tests on all supported versions of Python that you have installed, skipping the ones you don't. To run a subset of test environments, run for example:: - (__venv__)$ tox -e py27,py36 + (__venv__)$ tox -e py36,py39 In addition to ``pyXY`` environments, we have extra environments for checking things independent of the Python version: * ``doc`` checks syntax and spelling of the documentation * ``coverage-report`` generates a test coverage report for Python code. - It must be used last, e.g. ``tox -e py27,py36,coverage-report``. -* ``py2-nosasltls`` and ``py3-nosasltls`` check functionality without + It must be used last, e.g. ``tox -e py36,py39,coverage-report``. +* ``py3-nosasltls`` check functionality without SASL and TLS bindings compiled in. diff --git a/Makefile b/Makefile index f7360a66..577ba883 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ lcov-clean: if [ -d build ]; then find build -name '*.gc??' -delete; fi lcov-coverage: - WITH_GCOV=1 tox -e py27,py36 + WITH_GCOV=1 tox -e py36 $(LCOV_INFO): build lcov --capture --directory build --output-file $(LCOV_INFO) diff --git a/setup.py b/setup.py index 20c31c5f..119b5715 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', - # Note: when updating Python versions, also change .travis.yml and tox.ini + # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', 'Topic :: Internet', diff --git a/tox.ini b/tox.ini index 26982d0f..aaef8b5a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,8 @@ # and then run "tox" from this directory. [tox] -# Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py36,py37,py38,py39,py3-nosasltls,doc,py3-trace +# Note: when updating Python versions, also change setup.py and .github/worlflows/* +envlist = py{36,37,38,39,310},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 minver = 1.8 [gh-actions] @@ -21,6 +21,8 @@ deps = passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement -std=c99 commands = {envpython} -bb -Werror \ -m unittest discover -v -s Tests -p 't_*' {posargs} @@ -51,6 +53,11 @@ setenv = PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log commands = {[testenv]commands} +[testenv:c90] +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement -std=c90 +commands = {envpython} -Werror -c "import ldap" # we just test compilation here + [testenv:macos] # Travis CI macOS image does not have slapd # SDK libldap does not support ldap_init_fd From 2fc51b2c13a76913b439b826e674213b97d08f49 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 3 Aug 2021 10:22:11 +0200 Subject: [PATCH 150/206] Add OPT_X_TLS_REQUIRE_SAN Add bindings for OPT_X_TLS_REQUIRE_SAN option. The flag was introduced in OpenLDAP 2.4.52 to configure subject alternative name verification. Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 23 +++++++++++++++++++++++ Lib/ldap/constants.py | 3 +++ Modules/constants_generated.h | 4 ++++ Modules/options.c | 6 ++++++ Tests/t_cext.py | 23 +++++++++++++++++++++++ 5 files changed, 59 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 16220f3b..57485c7a 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -346,6 +346,29 @@ TLS options :py:const:`OPT_X_TLS_HARD` Same as :py:const:`OPT_X_TLS_DEMAND` +.. py:data:: OPT_X_TLS_REQUIRE_SAN + + get/set how OpenLDAP validates subject alternative name extension, + available in OpenSSL 2.4.52 and newer. + + :py:const:`OPT_X_TLS_NEVER` + Don't check SAN + + :py:const:`OPT_X_TLS_ALLOW` + Check SAN first, always fall back to subject common name (default) + + :py:const:`OPT_X_TLS_TRY` + Check SAN first, only fall back to subject common name, when no SAN + extension is present (:rfc:`6125` conform validation) + + :py:const:`OPT_X_TLS_DEMAND` + Validate peer cert chain and host name + + :py:const:`OPT_X_TLS_HARD` + Require SAN, don't fall back to subject common name + + .. versionadded:: 3.4.0 + .. py:data:: OPT_X_TLS_ALLOW Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index b6ec0d33..1c1d76a7 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -298,6 +298,9 @@ class Str(Constant): TLSInt('OPT_X_TLS_PROTOCOL_MIN', optional=True), TLSInt('OPT_X_TLS_PACKAGE', optional=True), + # Added in OpenLDAP 2.4.52 + TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), + Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), Int('OPT_X_SASL_AUTHCID'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 4a4cdb3e..e357fa23 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -249,6 +249,10 @@ add_int(OPT_X_TLS_PROTOCOL_MIN); add_int(OPT_X_TLS_PACKAGE); #endif +#if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) +add_int(OPT_X_TLS_REQUIRE_SAN); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index 549a6726..5ef0b86e 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -88,6 +88,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_REQUIRE_SAN + case LDAP_OPT_X_TLS_REQUIRE_SAN: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: @@ -298,6 +301,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_REQUIRE_SAN + case LDAP_OPT_X_TLS_REQUIRE_SAN: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 8333b0f4..816af8dd 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -932,6 +932,29 @@ def test_tls_packages(self): package = _ldap.get_option(_ldap.OPT_X_TLS_PACKAGE) self.assertIn(package, {"GnuTLS", "MozNSS", "OpenSSL"}) + @unittest.skipUnless( + hasattr(_ldap, "OPT_X_TLS_REQUIRE_SAN"), + reason="Test requires OPT_X_TLS_REQUIRE_SAN" + ) + def test_require_san(self): + l = self._open_conn(bind=False) + value = l.get_option(_ldap.OPT_X_TLS_REQUIRE_SAN) + self.assertIn( + value, + { + _ldap.OPT_X_TLS_NEVER, + _ldap.OPT_X_TLS_ALLOW, + _ldap.OPT_X_TLS_TRY, + _ldap.OPT_X_TLS_DEMAND, + _ldap.OPT_X_TLS_HARD, + } + ) + l.set_option(_ldap.OPT_X_TLS_REQUIRE_SAN, _ldap.OPT_X_TLS_TRY) + self.assertEqual( + l.get_option(_ldap.OPT_X_TLS_REQUIRE_SAN), + _ldap.OPT_X_TLS_TRY + ) + if __name__ == '__main__': unittest.main() From 84bbf5e59559ebbec22aef3ba2c12ffbf327e36b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 28 Jun 2021 11:03:02 +0200 Subject: [PATCH 151/206] Fix SASL get/set options on big endian platforms The options OPT_X_SASL_SSF_MIN, OPT_X_SASL_SSF_MAX, and OPT_X_SASL_SSF take *ber_len_t as input and output arguments. ber_len_t is defined as unsigned long: ``` /* LBER lengths (32 bits or larger) */ #define LBER_LEN_T long typedef unsigned LBER_LEN_T ber_len_t; ``` Wrong type handling is causing issues on big endian platforms. Signed-off-by: Christian Heimes --- LICENCE.MIT | 1 + Modules/options.c | 41 ++++++++++++++++++++++++++++++----------- Tests/t_ldapobject.py | 23 ++++++++++++++++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/LICENCE.MIT b/LICENCE.MIT index c78bcb9b..d4882df3 100644 --- a/LICENCE.MIT +++ b/LICENCE.MIT @@ -6,6 +6,7 @@ to all contributions by the following authors: * Aymeric Augustin * Bernhard M. Wiedemann * Bradley Baetz +* Christian Heimes * Éloi Rivard * Eyal Cherevatzki * Fred Thomsen diff --git a/Modules/options.c b/Modules/options.c index 5ef0b86e..d4d20724 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -43,6 +43,10 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) double doubleval; char *strval; struct timeval tv; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif void *ptr; LDAP *ld; LDAPControl **controls = NULL; @@ -92,10 +96,6 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif #endif -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF_MIN: - case LDAP_OPT_X_SASL_SSF_MAX: -#endif #ifdef LDAP_OPT_X_KEEPALIVE_IDLE case LDAP_OPT_X_KEEPALIVE_IDLE: #endif @@ -111,6 +111,16 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 0; ptr = &intval; break; + +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF_MIN: + case LDAP_OPT_X_SASL_SSF_MAX: + if (!PyArg_Parse(value, "k:set_option", &blen)) + return 0; + ptr = &blen; + break; +#endif + case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE @@ -138,6 +148,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 0; ptr = strval; break; + case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Float valued timeval options */ @@ -242,6 +253,10 @@ LDAP_get_option(LDAPObject *self, int option) LDAPAPIInfo apiinfo; LDAPControl **lcs; char *strval; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif PyObject *extensions, *v; Py_ssize_t i, num_extensions; @@ -280,9 +295,6 @@ LDAP_get_option(LDAPObject *self, int option) return v; -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF: -#endif case LDAP_OPT_REFERRALS: case LDAP_OPT_RESTART: case LDAP_OPT_DEREF: @@ -305,10 +317,6 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif #endif -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF_MIN: - case LDAP_OPT_X_SASL_SSF_MAX: -#endif #ifdef LDAP_OPT_X_SASL_NOCANON case LDAP_OPT_X_SASL_NOCANON: #endif @@ -330,6 +338,17 @@ LDAP_get_option(LDAPObject *self, int option) return option_error(res, "ldap_get_option"); return PyInt_FromLong(intval); +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF: + case LDAP_OPT_X_SASL_SSF_MIN: + case LDAP_OPT_X_SASL_SSF_MAX: + /* ber_len_t options (unsigned long)*/ + res = LDAP_int_get_option(self, option, &blen); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyLong_FromUnsignedLong(blen); +#endif + case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index e54bbfd4..0a089c91 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -334,7 +334,7 @@ def test005_invalid_credentials(self): @requires_sasl() @requires_ldapi() - def test006_sasl_extenal_bind_s(self): + def test006_sasl_external_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() self.assertEqual(l.whoami_s(), 'dn:'+self.server.root_dn.lower()) @@ -343,6 +343,27 @@ def test006_sasl_extenal_bind_s(self): l.sasl_external_bind_s(authz_id=authz_id) self.assertEqual(l.whoami_s(), authz_id.lower()) + @requires_sasl() + @requires_ldapi() + def test006_sasl_options(self): + l = self.ldap_object_class(self.server.ldapi_uri) + + minssf = l.get_option(ldap.OPT_X_SASL_SSF_MIN) + self.assertGreaterEqual(minssf, 0) + self.assertLessEqual(minssf, 256) + maxssf = l.get_option(ldap.OPT_X_SASL_SSF_MAX) + self.assertGreaterEqual(maxssf, 0) + # libldap sets SSF_MAX to INT_MAX + self.assertLessEqual(maxssf, 2**31 - 1) + + l.set_option(ldap.OPT_X_SASL_SSF_MIN, 56) + l.set_option(ldap.OPT_X_SASL_SSF_MAX, 256) + self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MIN), 56) + self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MAX), 256) + + l.sasl_external_bind_s() + self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn.lower()) + def test007_timeout(self): l = self.ldap_object_class(self.server.ldap_uri) m = l.search_ext(self.server.suffix, ldap.SCOPE_SUBTREE, '(objectClass=*)') From d9ded15a8c69c5891bdb6bc89d71a3ea4d3674a9 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 16 Sep 2021 14:07:57 +0200 Subject: [PATCH 152/206] Handle unknown LDAP result code Prevent ``SystemError: error return without exception set`` when LDAP server returns an unknown LDAP result code. Fixes: https://github.com/python-ldap/python-ldap/issues/240 Signed-off-by: Christian Heimes --- Modules/constants.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/constants.c b/Modules/constants.c index 8b902e02..07d60653 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -31,7 +31,8 @@ static PyObject *errobjects[LDAP_ERROR_MAX - LDAP_ERROR_MIN + 1]; PyObject * LDAPerr(int errnum) { - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) { + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { PyErr_SetNone(errobjects[errnum + LDAP_ERROR_OFFSET]); } else { @@ -88,10 +89,13 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error); } - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; - else + } + else { errobj = LDAPexception_class; + } info = PyDict_New(); if (info == NULL) { From f1c702ebdc231ca6f5ca255f38698ccd8c2743f5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 15 Sep 2021 11:22:43 +0200 Subject: [PATCH 153/206] Implement OPT_X_SASL_SSF_EXTERNAL setter The option flag ``OPT_X_SASL_SSF_EXTERNAL`` never worked because the set_option code didn't handle the flag correctly. Fixes: https://github.com/python-ldap/python-ldap/issues/423 Signed-off-by: Christian Heimes --- Modules/options.c | 7 +++++++ Tests/t_ldapobject.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/Modules/options.c b/Modules/options.c index d4d20724..db5fde3e 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -115,6 +115,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: + case LDAP_OPT_X_SASL_SSF_EXTERNAL: if (!PyArg_Parse(value, "k:set_option", &blen)) return 0; ptr = &blen; @@ -261,6 +262,12 @@ LDAP_get_option(LDAPObject *self, int option) Py_ssize_t i, num_extensions; switch (option) { +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF_EXTERNAL: + /* Write-only options */ + PyErr_SetString(PyExc_ValueError, "write-only option"); + return NULL; +#endif case LDAP_OPT_API_INFO: apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION; res = LDAP_int_get_option(self, option, &apiinfo); diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 0a089c91..3bcc00a2 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -362,6 +362,9 @@ def test006_sasl_options(self): self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MAX), 256) l.sasl_external_bind_s() + with self.assertRaisesRegex(ValueError, "write-only option"): + l.get_option(ldap.OPT_X_SASL_SSF_EXTERNAL) + l.set_option(ldap.OPT_X_SASL_SSF_EXTERNAL, 256) self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn.lower()) def test007_timeout(self): From 478f0b4886ede9d58ff02e499fe85a809fefb518 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 12:17:17 +0200 Subject: [PATCH 154/206] Add another batch of names to LICENCE.MIT (#430) --- LICENCE.MIT | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LICENCE.MIT b/LICENCE.MIT index d4882df3..0c2021f6 100644 --- a/LICENCE.MIT +++ b/LICENCE.MIT @@ -9,6 +9,7 @@ to all contributions by the following authors: * Christian Heimes * Éloi Rivard * Eyal Cherevatzki +* Florian Best * Fred Thomsen * Ivan A. Melnikov * johnthagen @@ -17,6 +18,7 @@ to all contributions by the following authors: * Martin Basti * Marti Raudsepp * Miro Hrončok +* Paul Aurich * Petr Viktorin * Pieterjan De Potter * Raphaël Barrois @@ -24,6 +26,7 @@ to all contributions by the following authors: * Stanislav Láznička * Tobias Bräutigam * Tom van Dijk +* Wentao Han * William Brown From 7206ef8d51c00c32dd34adc1cd967a84ba2d47d1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:39:58 +0200 Subject: [PATCH 155/206] Bump version to 3.4.0 --- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 2d88dc07..ef958a13 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.3.0' +__version__ = '3.4.0' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 820f3d84..cce4e806 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 0afebd84..7e69a594 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index b57cd44a..bb59e7fa 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 4901d051e72bd899b5bdba22c160083c9c4516cc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:42:37 +0200 Subject: [PATCH 156/206] Bump removal of deprecated functions to next release This should be done at the *start* of a release cycle. --- Lib/ldap/cidict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index ac19bd7d..f846fd29 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -85,7 +85,7 @@ def strlist_minus(a,b): a,b are supposed to be lists of case-insensitive strings. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) @@ -105,7 +105,7 @@ def strlist_intersection(a,b): Return intersection of two lists of case-insensitive strings a,b. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) @@ -125,7 +125,7 @@ def strlist_union(a,b): Return union of two lists of case-insensitive strings a,b. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) From 6516eeb3b77c5e8f9ccaacc75617b38eb0640ab6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:37:26 +0200 Subject: [PATCH 157/206] Add a CHANGELOG --- CHANGES | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGES b/CHANGES index 711b665e..78f596de 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,48 @@ +Released 3.4.0 2021-09-17 + +This release requires Python 3.6 or above, +and is tested with Python 3.6 to 3.10. +Python 2 is no longer supported. + +New code in the python-ldap project is available under the MIT licence +(available in ``LICENCE.MIT`` in the source). Several contributors have agreed +to apply this licence their previous contributions as well. +See the ``README`` for details. + +The following undocumented functions are deprecated and scheduled for removal: +- ``ldap.cidict.strlist_intersection`` +- ``ldap.cidict.strlist_minus`` +- ``ldap.cidict.strlist_union`` + +Changes: +* On MacOS, remove option to make LDAP connections from a file descriptor + when built with the system libldap (which lacks the underlying function, + ``ldap_init_fd``) +* Attribute values of the post read control are now ``bytes`` + instead of ISO8859-1 decoded ``str`` +* ``LDAPUrl`` now treats urlscheme as case-insensitive +* Several OpenLDAP options are now supported: + * ``OPT_X_TLS_REQUIRE_SAN`` + * ``OPT_X_SASL_SSF_EXTERNAL`` + * ``OPT_X_TLS_PEERCERT`` + +Fixes: +* The ``copy()`` method of ``cidict`` was added back. It was unintentionally + removed in 3.3.0 +* Fixed getting/setting ``SASL`` options on big endian platforms +* Unknown LDAP result code are now converted to ``LDAPexception``, + rather than raising a ``SystemError``. + +slapdtest: +* Show stderr of slapd -Ttest +* ``SlapdObject`` uses directory-based configuration of ``slapd`` +* ``SlapdObject`` startup is now faster + +Infrastructure: +* CI now runs on GitHub Actions rather than Travis CI. + + +---------------------------------------------------------------- Released 3.3.0 2020-06-18 Highlights: From 4ee61f4e2f81ae7c2e7293bda576630ed5ad3142 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 15 Nov 2021 10:38:03 +0100 Subject: [PATCH 158/206] Adjust version note in documentation --- Doc/installing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 521910e4..e4518c11 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -111,7 +111,7 @@ Build prerequisites The following software packages are required to be installed on the local system when building python-ldap: -- `Python`_ version 2.7, or 3.4 or later including its development files +- `Python`_ including its development files - C compiler corresponding to your Python version (on Linux, it is usually ``gcc``) - `OpenLDAP`_ client libs version 2.4.11 or later; it is not possible and not supported to build with prior versions. From 404c36b702c5b3a7e60729745c8bda16098b1472 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 15:39:04 +0100 Subject: [PATCH 159/206] Set today's release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 78f596de..92d9d414 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Released 3.4.0 2021-09-17 +Released 3.4.0 2021-11-26 This release requires Python 3.6 or above, and is tested with Python 3.6 to 3.10. From faa011b41f7141121546045925d809d54e70f5fd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 15 Oct 2021 15:21:37 +0100 Subject: [PATCH 160/206] Fix ReDoS in regex. --- Lib/ldap/schema/tokenizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index 69823f2b..623b86d5 100644 --- a/Lib/ldap/schema/tokenizer.py +++ b/Lib/ldap/schema/tokenizer.py @@ -13,7 +13,7 @@ r"|" # or r"([^'$()\s]+)" # string of length >= 1 without '$() or whitespace r"|" # or - r"('(?:[^'\\]|\\\\|\\.)*?'(?!\w))" + r"('(?:[^'\\]|\\.)*'(?!\w))" # any string or empty string surrounded by unescaped # single quotes except if right quote is succeeded by # alphanumeric char From bf4c165d1e86846c7204ccb5a42a3facf3f1e4c1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 15:42:22 +0100 Subject: [PATCH 161/206] Add release note --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 92d9d414..c358fa9e 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,11 @@ The following undocumented functions are deprecated and scheduled for removal: - ``ldap.cidict.strlist_minus`` - ``ldap.cidict.strlist_union`` +Security fixes: +* Fix inefficient regular expression which allows denial-of-service attacks + when parsing specially-crafted LDAP schema. + (GHSL-2021-117) + Changes: * On MacOS, remove option to make LDAP connections from a file descriptor when built with the system libldap (which lacks the underlying function, From 47975ee75c84748abfb8bb7456d5cc0a34cfce41 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 16:09:26 +0100 Subject: [PATCH 162/206] Fix inefficient regular expression in the schema tokenizer Fixes: GHSL-2021-117 Thank you to GitHub team members @erik-krogh (Erik Krogh Kristensen) and @yoff (Rasmus Petersen) for discovering the issue. https://github.com/python-ldap/python-ldap/pull/444 Co-authored-by: Kevin Backhouse From 9d7ca92108e8e116f45243f1833c87c9556ea893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 18 Jan 2022 14:34:32 +0000 Subject: [PATCH 163/206] Update BooleanControl encoding/decoding to use pyasn1 --- Lib/ldap/controls/simple.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/controls/simple.py b/Lib/ldap/controls/simple.py index 05f6760d..96837e2a 100644 --- a/Lib/ldap/controls/simple.py +++ b/Lib/ldap/controls/simple.py @@ -7,6 +7,9 @@ import struct,ldap from ldap.controls import RequestControl,ResponseControl,LDAPControl,KNOWN_RESPONSE_CONTROLS +from pyasn1.type import univ +from pyasn1.codec.ber import encoder,decoder + class ValueLessRequestControl(RequestControl): """ @@ -57,8 +60,6 @@ class BooleanControl(LDAPControl): booleanValue Boolean (True/False or 1/0) which is the boolean controlValue. """ - boolean2ber = { 1:'\x01\x01\xFF', 0:'\x01\x01\x00' } - ber2boolean = { '\x01\x01\xFF':1, '\x01\x01\x00':0 } def __init__(self,controlType=None,criticality=False,booleanValue=False): self.controlType = controlType @@ -66,10 +67,11 @@ def __init__(self,controlType=None,criticality=False,booleanValue=False): self.booleanValue = booleanValue def encodeControlValue(self): - return self.boolean2ber[int(self.booleanValue)] + return encoder.encode(self.booleanValue,asn1Spec=univ.Boolean()) def decodeControlValue(self,encodedControlValue): - self.booleanValue = self.ber2boolean[encodedControlValue] + decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=univ.Boolean()) + self.booleanValue = bool(int(decodedValue)) class ManageDSAITControl(ValueLessRequestControl): From 61cb47f0063c7e2a7c36affb6675690f2f90868d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 26 Jan 2022 11:02:25 +0000 Subject: [PATCH 164/206] Mention TLS gotcha in the FAQ --- Doc/faq.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/faq.rst b/Doc/faq.rst index 492b95f2..e96fc030 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -84,6 +84,11 @@ connection.”* Alternatively, a Samba 4 AD returns the diagnostic message `LDAPv2 is considered historic `_ since many years. +**Q**: My TLS settings are ignored/TLS isn't working? + + **A**: Make sure you call `set_option( ldap.OPT_X_TLS_NEWCTX, 0 )` + after changing any of the `OPT_X_TLS_*` options. + Installing From d7b65a737356a6677ee2d76b667a925334b6b699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 26 Jan 2022 12:13:45 +0000 Subject: [PATCH 165/206] Add TLS to wordlist --- Doc/spelling_wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index c24ab486..e6c2aedd 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -144,6 +144,7 @@ subtree syncrepl syntaxes timelimit +TLS tracebacks tuple tuples From e595f43c83e586d37542dd7dd8f497c57825021a Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Tue, 2 Nov 2021 22:08:42 +0100 Subject: [PATCH 166/206] Fix wrong asserts in test_test_flags Introduced in c5ad8025632fb5e5e84cced5a0f8c7ddda1e8dae --- Tests/t_cext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 816af8dd..33fbf29a 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -234,11 +234,11 @@ def test_test_flags(self): if 'TLS' in disabled: self.assertFalse(_ldap.TLS_AVAIL) else: - self.assertFalse(_ldap.TLS_AVAIL) + self.assertTrue(_ldap.TLS_AVAIL) if 'SASL' in disabled: self.assertFalse(_ldap.SASL_AVAIL) else: - self.assertFalse(_ldap.SASL_AVAIL) + self.assertTrue(_ldap.SASL_AVAIL) def test_simple_bind(self): l = self._open_conn() From e75c24dd70dcf10c8315d6f30ecf98f2c30f08e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=A4rdeman?= Date: Mon, 24 Jan 2022 18:02:04 +0100 Subject: [PATCH 167/206] ldap.schema.models.Entry - Fix init() I'm guessing this is partly due to commit 2be2c6bb8f44792d506cb1759fe06ff4cda3c5cf And the update() method can't ever have worked? --- Lib/ldap/schema/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index d73420c5..3d9322c0 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -7,7 +7,7 @@ import sys import ldap.cidict -from collections import UserDict as IterableUserDict +from collections import UserDict from ldap.schema.tokenizer import split_tokens,extract_tokens @@ -640,7 +640,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(IterableUserDict): +class Entry(UserDict): """ Schema-aware implementation of an LDAP entry class. @@ -653,7 +653,7 @@ def __init__(self,schema,dn,entry): self._attrtype2keytuple = {} self._s = schema self.dn = dn - IterableUserDict.IterableUserDict.__init__(self,{}) + super().__init__() self.update(entry) def _at2key(self,nameoroid): @@ -674,7 +674,7 @@ def _at2key(self,nameoroid): return t def update(self,dict): - for key, value in dict.values(): + for key, value in dict.items(): self[key] = value def __contains__(self,nameoroid): From 1e20c2d540a201d035debdbfcd61ab96dc6bd8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 27 Jan 2022 10:35:56 +0000 Subject: [PATCH 168/206] Check whether libldap is threadsafe on startup. Closes #432 --- Lib/ldap/constants.py | 2 -- Modules/constants.c | 10 ++++++++++ setup.cfg | 6 ++++-- setup.py | 1 - 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 1c1d76a7..f76609b9 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -341,9 +341,7 @@ class Str(Constant): # XXX - these should be errors Int('URL_ERR_BADSCOPE'), Int('URL_ERR_MEM'), - # Int('LIBLDAP_R'), - Feature('LIBLDAP_R', 'HAVE_LIBLDAP_R'), Feature('SASL_AVAIL', 'HAVE_SASL'), Feature('TLS_AVAIL', 'HAVE_TLS'), Feature('INIT_FD_AVAIL', 'HAVE_LDAP_INIT_FD'), diff --git a/Modules/constants.c b/Modules/constants.c index 07d60653..8d6f63b0 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -197,6 +197,8 @@ int LDAPinit_constants(PyObject *m) { PyObject *exc, *nobj; + struct ldap_apifeature_info info = { 1, "X_OPENLDAP_THREAD_SAFE", 0 }; + int thread_safe = 0; /* simple constants */ @@ -221,6 +223,14 @@ LDAPinit_constants(PyObject *m) return -1; Py_INCREF(LDAPexception_class); +#ifdef LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE + if (ldap_get_option(NULL, LDAP_OPT_API_FEATURE_INFO, &info) == LDAP_SUCCESS) { + thread_safe = (info.ldapaif_version == 1); + } +#endif + if (PyModule_AddIntConstant(m, "LIBLDAP_R", thread_safe) != 0) + return -1; + /* Generated constants -- see Lib/ldap/constants.py */ #define add_err(n) do { \ diff --git a/setup.cfg b/setup.cfg index 01d43a06..48f36197 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,9 +21,11 @@ defines = HAVE_SASL HAVE_TLS HAVE_LIBLDAP_R extra_compile_args = extra_objects = +# Uncomment this if your libldap is not thread-safe and you need libldap_r +# instead # Example for full-featured build: # Support for StartTLS/LDAPS, SASL bind and reentrant libldap_r. -libs = ldap_r lber +#libs = ldap_r lber # Installation options [install] @@ -33,7 +35,7 @@ optimize = 1 # Linux distributors/packagers should adjust these settings [bdist_rpm] provides = python-ldap -requires = python libldap-2_4 +requires = python libldap-2 vendor = python-ldap project packager = python-ldap team distribution_name = openSUSE 11.x diff --git a/setup.py b/setup.py index 119b5715..b1939571 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,6 @@ class OpenLDAP2: extra_objects = LDAP_CLASS.extra_objects, runtime_library_dirs = (not sys.platform.startswith("win"))*LDAP_CLASS.library_dirs, define_macros = LDAP_CLASS.defines + \ - ('ldap_r' in LDAP_CLASS.libs or 'oldap_r' in LDAP_CLASS.libs)*[('HAVE_LIBLDAP_R',None)] + \ ('sasl' in LDAP_CLASS.libs or 'sasl2' in LDAP_CLASS.libs or 'libsasl' in LDAP_CLASS.libs)*[('HAVE_SASL',None)] + \ ('ssl' in LDAP_CLASS.libs and 'crypto' in LDAP_CLASS.libs)*[('HAVE_TLS',None)] + \ [ From 5f8fcfde7797095fe250d58b591edb66d83ad15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 1 Feb 2022 12:02:53 +0000 Subject: [PATCH 169/206] Regenerate Modules/constants_generated.h --- Modules/constants_generated.h | 43 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index e357fa23..6070c31d 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -76,10 +76,12 @@ add_err(TOO_LATE); add_err(CANNOT_CANCEL); #endif + #if defined(LDAP_ASSERTION_FAILED) add_err(ASSERTION_FAILED); #endif + #if defined(LDAP_PROXIED_AUTHORIZATION_DENIED) add_err(PROXIED_AUTHORIZATION_DENIED); #endif @@ -192,6 +194,7 @@ add_int(OPT_URI); add_int(OPT_DEFBASE); #endif + #if HAVE_TLS #if defined(LDAP_OPT_X_TLS) @@ -217,18 +220,22 @@ add_int(OPT_X_TLS_TRY); add_int(OPT_X_TLS_VERSION); #endif + #if defined(LDAP_OPT_X_TLS_CIPHER) add_int(OPT_X_TLS_CIPHER); #endif + #if defined(LDAP_OPT_X_TLS_PEERCERT) add_int(OPT_X_TLS_PEERCERT); #endif + #if defined(LDAP_OPT_X_TLS_CRLCHECK) add_int(OPT_X_TLS_CRLCHECK); #endif + #if defined(LDAP_OPT_X_TLS_CRLFILE) add_int(OPT_X_TLS_CRLFILE); #endif @@ -241,14 +248,17 @@ add_int(OPT_X_TLS_CRL_ALL); add_int(OPT_X_TLS_NEWCTX); #endif + #if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN) add_int(OPT_X_TLS_PROTOCOL_MIN); #endif + #if defined(LDAP_OPT_X_TLS_PACKAGE) add_int(OPT_X_TLS_PACKAGE); #endif + #if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) add_int(OPT_X_TLS_REQUIRE_SAN); #endif @@ -269,22 +279,27 @@ add_int(OPT_X_SASL_SSF_MAX); add_int(OPT_X_SASL_NOCANON); #endif + #if defined(LDAP_OPT_X_SASL_USERNAME) add_int(OPT_X_SASL_USERNAME); #endif + #if defined(LDAP_OPT_CONNECT_ASYNC) add_int(OPT_CONNECT_ASYNC); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_IDLE) add_int(OPT_X_KEEPALIVE_IDLE); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_PROBES) add_int(OPT_X_KEEPALIVE_PROBES); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_INTERVAL) add_int(OPT_X_KEEPALIVE_INTERVAL); #endif @@ -309,36 +324,24 @@ add_int(OPT_SUCCESS); add_int(URL_ERR_BADSCOPE); add_int(URL_ERR_MEM); -#ifdef HAVE_LIBLDAP_R -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 1) != 0) - return -1; -#else -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 0) != 0) - return -1; -#endif - #ifdef HAVE_SASL -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) return -1; #endif + #ifdef HAVE_TLS -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) return -1; #endif + #ifdef HAVE_LDAP_INIT_FD -if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 0) != 0) return -1; #endif add_string(CONTROL_MANAGEDSAIT); From e712033b0ff88463b6ca39d2b02f1779d4bbea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 1 Feb 2022 12:03:11 +0000 Subject: [PATCH 170/206] Fix LDAP_VENDOR_VERSION check --- Modules/common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/common.h b/Modules/common.h index 886024f2..bc554c85 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -16,7 +16,7 @@ #include #include -#if LDAP_API_VERSION < 2040 +#if LDAP_VENDOR_VERSION < 20400 #error Current python-ldap requires OpenLDAP 2.4.x #endif From 610b2ee59129a2650414370942dff6dec17d4226 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 16 Sep 2021 13:56:34 +0200 Subject: [PATCH 171/206] Implement support for OPT_X_TLS_PEERCERT Co-authored-by: Thomas Grainger Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 7 ++++++- Lib/ldap/constants.py | 3 +++ Modules/berval.c | 2 +- Modules/constants_generated.h | 5 +++++ Modules/options.c | 18 ++++++++++++++++++ Tests/t_ldapobject.py | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 57485c7a..def77c66 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -406,7 +406,12 @@ TLS options .. py:data:: OPT_X_TLS_PEERCERT - Get peer's certificate as binary ASN.1 data structure (not supported) + Get peer's certificate as binary ASN.1 data structure (DER) + + .. versionadded:: 3.4.1 + + .. note:: + The option leaks memory with OpenLDAP < 2.5.8. .. py:data:: OPT_X_TLS_PROTOCOL_MIN diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index f76609b9..19bdd059 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -301,6 +301,9 @@ class Str(Constant): # Added in OpenLDAP 2.4.52 TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), + # Added in OpenLDAP 2.5 + TLSInt('OPT_X_TLS_PEERCERT', optional=True), + Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), Int('OPT_X_SASL_AUTHCID'), diff --git a/Modules/berval.c b/Modules/berval.c index 7435ee0a..6917baef 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -17,7 +17,7 @@ LDAPberval_to_object(const struct berval *bv) { PyObject *ret = NULL; - if (!bv) { + if (!bv || !bv->bv_val) { ret = Py_None; Py_INCREF(ret); } diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 6070c31d..ccb42782 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -263,6 +263,11 @@ add_int(OPT_X_TLS_PACKAGE); add_int(OPT_X_TLS_REQUIRE_SAN); #endif + +#if defined(LDAP_OPT_X_TLS_PEERCERT) +add_int(OPT_X_TLS_PEERCERT); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index db5fde3e..af775766 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -5,6 +5,7 @@ #include "LDAPObject.h" #include "ldapcontrol.h" #include "options.h" +#include "berval.h" void set_timeval_from_double(struct timeval *tv, double d) @@ -58,6 +59,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) case LDAP_OPT_API_FEATURE_INFO: #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: +#endif +#ifdef LDAP_OPT_X_TLS_PEERCERT + case LDAP_OPT_X_TLS_PEERCERT: #endif /* Read-only options */ PyErr_SetString(PyExc_ValueError, "read-only option"); @@ -254,6 +258,7 @@ LDAP_get_option(LDAPObject *self, int option) LDAPAPIInfo apiinfo; LDAPControl **lcs; char *strval; + struct berval berbytes; #if HAVE_SASL /* unsigned long */ ber_len_t blen; @@ -406,6 +411,19 @@ LDAP_get_option(LDAPObject *self, int option) ldap_memfree(strval); return v; +#ifdef HAVE_TLS +#ifdef LDAP_OPT_X_TLS_PEERCERT + case LDAP_OPT_X_TLS_PEERCERT: +#endif +#endif + /* Options dealing with raw data */ + res = LDAP_int_get_option(self, option, &berbytes); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + v = LDAPberval_to_object(&berbytes); + ldap_memfree(berbytes.bv_val); + return v; + case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Double-valued timeval options */ diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 3bcc00a2..07a78595 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -20,6 +20,11 @@ from slapdtest import requires_ldapi, requires_sasl, requires_tls from slapdtest import requires_init_fd +try: + from ssl import PEM_cert_to_DER_cert +except ImportError: + PEM_cert_to_DER_cert = None + LDIF_TEMPLATE = """dn: %(suffix)s objectClass: dcObject @@ -421,6 +426,36 @@ def test_multiple_starttls(self): l.simple_bind_s(self.server.root_dn, self.server.root_pw) self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn) + @requires_tls() + @unittest.skipUnless( + hasattr(ldap, "OPT_X_TLS_PEERCERT"), + reason="Requires OPT_X_TLS_PEERCERT" + ) + def test_get_tls_peercert(self): + l = self.ldap_object_class(self.server.ldap_uri) + peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT) + self.assertEqual(peercert, None) + with self.assertRaises(ValueError): + l.set_option(ldap.OPT_X_TLS_PEERCERT, b"") + + l.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + l.set_option(ldap.OPT_X_TLS_NEWCTX, 0) + l.start_tls_s() + + peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT) + self.assertTrue(peercert) + self.assertIsInstance(peercert, bytes) + + if PEM_cert_to_DER_cert is not None: + with open(self.server.servercert) as f: + server_pem = f.read() + # remove text + begin = server_pem.find("-----BEGIN CERTIFICATE-----") + server_pem = server_pem[begin:-1] + + server_der = PEM_cert_to_DER_cert(server_pem) + self.assertEqual(server_der, peercert) + def test_dse(self): dse = self._ldap_conn.read_rootdse_s() self.assertIsInstance(dse, dict) From c4efbdabb35acab6be89435ed59608ee439153f9 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 18 Sep 2021 17:36:44 +0200 Subject: [PATCH 172/206] Use regex to locate PEM body --- Tests/t_ldapobject.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 07a78595..9e4e3311 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -3,9 +3,11 @@ See https://www.python-ldap.org/ for details. """ +import base64 import errno import linecache import os +import re import socket import unittest import pickle @@ -20,10 +22,10 @@ from slapdtest import requires_ldapi, requires_sasl, requires_tls from slapdtest import requires_init_fd -try: - from ssl import PEM_cert_to_DER_cert -except ImportError: - PEM_cert_to_DER_cert = None +PEM_CERT_RE = re.compile( + b'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', + re.DOTALL +) LDIF_TEMPLATE = """dn: %(suffix)s @@ -446,15 +448,12 @@ def test_get_tls_peercert(self): self.assertTrue(peercert) self.assertIsInstance(peercert, bytes) - if PEM_cert_to_DER_cert is not None: - with open(self.server.servercert) as f: - server_pem = f.read() - # remove text - begin = server_pem.find("-----BEGIN CERTIFICATE-----") - server_pem = server_pem[begin:-1] + with open(self.server.servercert, "rb") as f: + server_cert = f.read() + pem_body = PEM_CERT_RE.search(server_cert).group(1) + server_der = base64.b64decode(pem_body) - server_der = PEM_cert_to_DER_cert(server_pem) - self.assertEqual(server_der, peercert) + self.assertEqual(server_der, peercert) def test_dse(self): dse = self._ldap_conn.read_rootdse_s() From dbd7a3847121e24c3a32b10c6a9016129a08ea0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 14 Feb 2022 13:08:57 +0000 Subject: [PATCH 173/206] Process missing libldap options --- Lib/ldap/constants.py | 3 +++ Modules/constants_generated.h | 11 ++++++++++ Modules/options.c | 40 ++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 19bdd059..71994a8d 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -244,6 +244,7 @@ class Str(Constant): Int('OPT_SIZELIMIT'), Int('OPT_TIMELIMIT'), Int('OPT_REFERRALS', optional=True), + Int('OPT_RESULT_CODE'), Int('OPT_ERROR_NUMBER'), Int('OPT_RESTART'), Int('OPT_PROTOCOL_VERSION'), @@ -261,6 +262,7 @@ class Str(Constant): Int('OPT_TIMEOUT'), Int('OPT_REFHOPLIMIT'), Int('OPT_NETWORK_TIMEOUT'), + Int('OPT_TCP_USER_TIMEOUT', optional=True), Int('OPT_URI'), Int('OPT_DEFBASE', optional=True), @@ -299,6 +301,7 @@ class Str(Constant): TLSInt('OPT_X_TLS_PACKAGE', optional=True), # Added in OpenLDAP 2.4.52 + TLSInt('OPT_X_TLS_ECNAME', optional=True), TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), # Added in OpenLDAP 2.5 diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index ccb42782..9df264ed 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -173,6 +173,7 @@ add_int(OPT_TIMELIMIT); add_int(OPT_REFERRALS); #endif +add_int(OPT_RESULT_CODE); add_int(OPT_ERROR_NUMBER); add_int(OPT_RESTART); add_int(OPT_PROTOCOL_VERSION); @@ -188,6 +189,11 @@ add_int(OPT_DEBUG_LEVEL); add_int(OPT_TIMEOUT); add_int(OPT_REFHOPLIMIT); add_int(OPT_NETWORK_TIMEOUT); + +#if defined(LDAP_OPT_TCP_USER_TIMEOUT) +add_int(OPT_TCP_USER_TIMEOUT); +#endif + add_int(OPT_URI); #if defined(LDAP_OPT_DEFBASE) @@ -259,6 +265,11 @@ add_int(OPT_X_TLS_PACKAGE); #endif +#if defined(LDAP_OPT_X_TLS_ECNAME) +add_int(OPT_X_TLS_ECNAME); +#endif + + #if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) add_int(OPT_X_TLS_REQUIRE_SAN); #endif diff --git a/Modules/options.c b/Modules/options.c index af775766..ef9eddf2 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -41,6 +41,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) { int res; int intval; + unsigned int uintval; double doubleval; char *strval; struct timeval tv; @@ -57,6 +58,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) switch (option) { case LDAP_OPT_API_INFO: case LDAP_OPT_API_FEATURE_INFO: + case LDAP_OPT_DESC: #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: #endif @@ -116,10 +118,19 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) ptr = &intval; break; +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + case LDAP_OPT_TCP_USER_TIMEOUT: +#endif + if (!PyArg_Parse(value, "I:set_option", &uintval)) + return 0; + ptr = &uintval; + break; + #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: case LDAP_OPT_X_SASL_SSF_EXTERNAL: + case LDAP_OPT_X_SASL_MAXBUFSIZE: if (!PyArg_Parse(value, "k:set_option", &blen)) return 0; ptr = &blen; @@ -144,9 +155,15 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_CRLFILE case LDAP_OPT_X_TLS_CRLFILE: #endif +#ifdef LDAP_OPT_X_TLS_ECNAME + case LDAP_OPT_X_TLS_ECNAME: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SECPROPS: +#endif +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: #endif /* String valued options */ if (!PyArg_Parse(value, "s:set_option", &strval)) @@ -187,8 +204,8 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) } else { PyErr_Format(PyExc_ValueError, - "timeout must be >= 0 or -1/None for infinity, got %d", - option); + "timeout must be >= 0 or -1/None for infinity, got %f", + doubleval); return 0; } break; @@ -254,6 +271,7 @@ LDAP_get_option(LDAPObject *self, int option) { int res; int intval; + unsigned int uintval; struct timeval *tv; LDAPAPIInfo apiinfo; LDAPControl **lcs; @@ -268,6 +286,7 @@ LDAP_get_option(LDAPObject *self, int option) switch (option) { #ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SECPROPS: case LDAP_OPT_X_SASL_SSF_EXTERNAL: /* Write-only options */ PyErr_SetString(PyExc_ValueError, "write-only option"); @@ -350,10 +369,20 @@ LDAP_get_option(LDAPObject *self, int option) return option_error(res, "ldap_get_option"); return PyInt_FromLong(intval); +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + case LDAP_OPT_TCP_USER_TIMEOUT: +#endif + /* unsigned int options */ + res = LDAP_int_get_option(self, option, &uintval); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyLong_FromUnsignedLong(uintval); + #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: + case LDAP_OPT_X_SASL_MAXBUFSIZE: /* ber_len_t options (unsigned long)*/ res = LDAP_int_get_option(self, option, &blen); if (res != LDAP_OPT_SUCCESS) @@ -388,9 +417,11 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PACKAGE case LDAP_OPT_X_TLS_PACKAGE: #endif +#ifdef LDAP_OPT_X_TLS_ECNAME + case LDAP_OPT_X_TLS_ECNAME: +#endif #endif #ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SECPROPS: case LDAP_OPT_X_SASL_MECH: case LDAP_OPT_X_SASL_REALM: case LDAP_OPT_X_SASL_AUTHCID: @@ -398,6 +429,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_SASL_USERNAME case LDAP_OPT_X_SASL_USERNAME: #endif +#endif +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: #endif /* String-valued options */ res = LDAP_int_get_option(self, option, &strval); From 6462e58a68bbf674eeace5f6ac035b51966db7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 14 Feb 2022 15:08:08 +0000 Subject: [PATCH 174/206] In liblber {0, ""} corresponds to b'' and {0, NULL} corresponds to None --- Tests/t_ldap_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 89f21a43..e9bef591 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -23,8 +23,8 @@ ]) TEST_CTRL_EXPECTED = [ TEST_CTRL[0], - # get_option returns empty bytes - (TEST_CTRL[1][0], TEST_CTRL[1][1], b''), + # Noop has no value + (TEST_CTRL[1][0], TEST_CTRL[1][1], None), ] From 5cac85a7afeaf117d4b449e9e706a2679accc8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 6 Apr 2022 11:42:43 +0100 Subject: [PATCH 175/206] Add TLS version numbers and remove unsupported TLS options Closes #67 --- Doc/reference/ldap.rst | 64 +++++++++++++++++++++++++---------- Lib/ldap/constants.py | 9 +++-- Modules/constants_generated.h | 36 ++++++++++++++++---- Modules/options.c | 6 ++++ 4 files changed, 90 insertions(+), 25 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index def77c66..9bd6b142 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -372,21 +372,27 @@ TLS options .. py:data:: OPT_X_TLS_ALLOW Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_DEMAND Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_HARD Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_NEVER Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_TRY + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. deprecated:: 3.3.0 This value is only used by slapd server internally. It will be removed in the future. @@ -400,10 +406,6 @@ TLS options get/set allowed cipher suites -.. py:data:: OPT_X_TLS_CTX - - get address of internal memory address of TLS context (**DO NOT USE**) - .. py:data:: OPT_X_TLS_PEERCERT Get peer's certificate as binary ASN.1 data structure (DER) @@ -417,8 +419,47 @@ TLS options get/set minimum protocol version (wire protocol version as int) - * ``0x303`` for TLS 1.2 - * ``0x304`` for TLS 1.3 +.. py:data:: OPT_X_TLS_PROTOCOL_MAX + + get/set maximum protocol version (wire protocol version as int), + available in OpenSSL 2.5 and newer. + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_SSL3 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents SSL 3 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_0 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.0 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_1 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.1 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_2 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.2 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_3 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.3 + + .. versionadded:: 3.4.1 .. py:data:: OPT_X_TLS_VERSION @@ -428,12 +469,6 @@ TLS options get/set path to /dev/urandom (**DO NOT USE**) -.. py:data:: OPT_X_TLS - - .. deprecated:: 3.3.0 - The option is deprecated in OpenLDAP and should no longer be used. It - will be removed in the future. - .. note:: OpenLDAP supports several TLS/SSL libraries. OpenSSL is the most common @@ -923,11 +958,6 @@ and wait for and return with the server's result, or with The *dn* and *attr* arguments are text strings; see :ref:`bytes_mode`. - .. note:: - - A design fault in the LDAP API prevents *value* - from containing *NULL* characters. - .. py:method:: LDAPObject.delete(dn) -> int diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 71994a8d..1807fc55 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -267,8 +267,6 @@ class Str(Constant): Int('OPT_DEFBASE', optional=True), - TLSInt('OPT_X_TLS', optional=True), - TLSInt('OPT_X_TLS_CTX'), TLSInt('OPT_X_TLS_CACERTFILE'), TLSInt('OPT_X_TLS_CACERTDIR'), TLSInt('OPT_X_TLS_CERTFILE'), @@ -306,6 +304,13 @@ class Str(Constant): # Added in OpenLDAP 2.5 TLSInt('OPT_X_TLS_PEERCERT', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_MAX', optional=True), + + TLSInt('OPT_X_TLS_PROTOCOL_SSL3', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_0', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_1', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_2', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_3', optional=True), Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 9df264ed..2d385549 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -202,12 +202,6 @@ add_int(OPT_DEFBASE); #if HAVE_TLS - -#if defined(LDAP_OPT_X_TLS) -add_int(OPT_X_TLS); -#endif - -add_int(OPT_X_TLS_CTX); add_int(OPT_X_TLS_CACERTFILE); add_int(OPT_X_TLS_CACERTDIR); add_int(OPT_X_TLS_CERTFILE); @@ -279,6 +273,36 @@ add_int(OPT_X_TLS_REQUIRE_SAN); add_int(OPT_X_TLS_PEERCERT); #endif + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_MAX) +add_int(OPT_X_TLS_PROTOCOL_MAX); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_SSL3) +add_int(OPT_X_TLS_PROTOCOL_SSL3); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_0) +add_int(OPT_X_TLS_PROTOCOL_TLS1_0); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_1) +add_int(OPT_X_TLS_PROTOCOL_TLS1_1); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_2) +add_int(OPT_X_TLS_PROTOCOL_TLS1_2); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_3) +add_int(OPT_X_TLS_PROTOCOL_TLS1_3); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index ef9eddf2..1a22bed1 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -98,6 +98,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + case LDAP_OPT_X_TLS_PROTOCOL_MAX: +#endif #ifdef LDAP_OPT_X_TLS_REQUIRE_SAN case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif @@ -344,6 +347,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + case LDAP_OPT_X_TLS_PROTOCOL_MAX: +#endif #ifdef LDAP_OPT_X_TLS_REQUIRE_SAN case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif From febaf561b241a0d0cbe8be6ea5f786f4276c30ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 27 Jan 2022 10:35:56 +0000 Subject: [PATCH 176/206] Fix omissions from previous merge. --- Doc/reference/ldap.rst | 4 ++-- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 9bd6b142..5d4fb642 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -349,7 +349,7 @@ TLS options .. py:data:: OPT_X_TLS_REQUIRE_SAN get/set how OpenLDAP validates subject alternative name extension, - available in OpenSSL 2.4.52 and newer. + available in OpenLDAP 2.4.52 and newer. :py:const:`OPT_X_TLS_NEVER` Don't check SAN @@ -422,7 +422,7 @@ TLS options .. py:data:: OPT_X_TLS_PROTOCOL_MAX get/set maximum protocol version (wire protocol version as int), - available in OpenSSL 2.5 and newer. + available in OpenLDAP 2.5 and newer. .. versionadded:: 3.4.1 diff --git a/setup.cfg b/setup.cfg index 48f36197..fdb32fbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ license_file = LICENCE # These defines needs OpenLDAP built with # ./configure --with-cyrus-sasl --with-tls -defines = HAVE_SASL HAVE_TLS HAVE_LIBLDAP_R +defines = HAVE_SASL HAVE_TLS extra_compile_args = extra_objects = From 8666af380ef8dde9973f46fb20e458f7eab6ce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 6 Apr 2022 11:11:08 +0100 Subject: [PATCH 177/206] Update to behera ppolicy draft 11 --- Lib/ldap/controls/ppolicy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index da7586f0..f3a8416d 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -40,9 +40,10 @@ class PasswordPolicyError(univ.Enumerated): ('insufficientPasswordQuality',5), ('passwordTooShort',6), ('passwordTooYoung',7), - ('passwordInHistory',8) + ('passwordInHistory',8), + ('passwordTooLong',9), ) - subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8) + subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8,9) class PasswordPolicyResponseValue(univ.Sequence): From 7c25278259952e4787e86d20934af71fc17c197d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 20 Apr 2022 15:38:51 +0100 Subject: [PATCH 178/206] Make 'method' parameter of ReconnectLDAPObject._store_last_bind private Fixes: https://github.com/python-ldap/python-ldap/issues/448 --- Lib/ldap/ldapobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 40091ad7..9442e39b 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -895,8 +895,8 @@ def __setstate__(self,d): self._trace_file = ldap._trace_file self.reconnect(self._uri) - def _store_last_bind(self,method,*args,**kwargs): - self._last_bind = (method,args,kwargs) + def _store_last_bind(self,_method,*args,**kwargs): + self._last_bind = (_method,args,kwargs) def _apply_last_bind(self): if self._last_bind!=None: From 7f30c4721ea2ca4373ed7860e6467781f0afa758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 20 Apr 2022 14:42:42 +0100 Subject: [PATCH 179/206] Document OPT_X_SASL_* differ from others Fixes: https://github.com/python-ldap/python-ldap/issues/468 --- Doc/reference/ldap.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 5d4fb642..0046f4a1 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -226,6 +226,9 @@ the following option identifiers are defined as constants: SASL options :::::::::::: +Unlike most other options, SASL options must be set on an +:py:class:`LDAPObject` instance. + .. py:data:: OPT_X_SASL_AUTHCID .. py:data:: OPT_X_SASL_AUTHZID @@ -234,7 +237,8 @@ SASL options .. py:data:: OPT_X_SASL_NOCANON - If set to zero SASL host name canonicalization is disabled. + If set to zero, SASL host name canonicalization is disabled. This is the only + SASL option that can be set globally. .. py:data:: OPT_X_SASL_REALM From 81e1f28c15c86ddc48eddca39fdf64273809587d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Jun 2022 09:39:55 +0100 Subject: [PATCH 180/206] Correct SASL option documentation --- Doc/reference/ldap.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 0046f4a1..2d5c4780 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -237,8 +237,7 @@ Unlike most other options, SASL options must be set on an .. py:data:: OPT_X_SASL_NOCANON - If set to zero, SASL host name canonicalization is disabled. This is the only - SASL option that can be set globally. + If set to zero, SASL host name canonicalization is disabled. .. py:data:: OPT_X_SASL_REALM From 59af061d1bc2fad832952f0b18105ab1ae18246d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Jun 2022 10:53:14 +0100 Subject: [PATCH 181/206] Prepare CHANGELOG --- CHANGES | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGES b/CHANGES index c358fa9e..8f9237a8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,43 @@ +Released 3.4.1 2022-07-05 + +This is a minor release to provide out-of-the-box compatibility with the merge +of libldap and libldap_r that happened with OpenLDAP's 2.5 release. + +The following undocumented functions are deprecated and scheduled for removal: +- ``ldap.cidict.strlist_intersection`` +- ``ldap.cidict.strlist_minus`` +- ``ldap.cidict.strlist_union`` + +The following deprecated option has been removed: +- ``OPT_X_TLS`` + +Doc/ +* SASL option usage has been clarified + +Lib/ +* ppolicy control definition has been updated to match Behera draft 11 + +Modules/ +* By default, compile against libldap, checking whether it provides a + threadsafe implementation at runtime +* When decoding controls, the module can now distinguish between no value + (now exposed as ``None``) and an empty value (exposed as ``b''``) +* Several new OpenLDAP options are now supported: + * ``OPT_SOCKET_BIND_ADDRESSES`` + * ``OPT_TCP_USER_TIMEOUT`` + * ``OPT_X_SASL_MAXBUFSIZE`` + * ``OPT_X_SASL_SECPROPS`` + * ``OPT_X_TLS_ECNAME`` + * ``OPT_X_TLS_PEERCERT`` + * ``OPT_X_TLS_PROTOCOL``-related options and constants + +Fixes: +* Encoding/decoding of boolean controls has been corrected +* ldap.schema.models.Entry is now usable +* ``method`` keyword to ReconnectLDAPObject.bind_s is now usable + + +---------------------------------------------------------------- Released 3.4.0 2021-11-26 This release requires Python 3.6 or above, From b80e8135785ef80a40a52eb61033a54dfd70266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 5 Jul 2022 15:29:24 +0100 Subject: [PATCH 182/206] Prepare a new release --- CHANGES | 2 +- Doc/contributing.rst | 2 ++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 8f9237a8..b1ccc990 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Released 3.4.1 2022-07-05 +Released 3.4.2 2022-07-06 This is a minor release to provide out-of-the-box compatibility with the merge of libldap and libldap_r that happened with OpenLDAP's 2.5 release. diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 1fc1365b..bbaab491 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -218,6 +218,8 @@ If you are tasked with releasing python-ldap, remember to: * Go through all changes since last version, and add them to ``CHANGES``. * Run :ref:`additional tests` as appropriate, fix any regressions. * Change the release date in ``CHANGES``. +* Update ``__version__`` tags where appropriate (each module ``ldap``, + ``ldif``, ``ldapurl``, ``slapdtest`` has its own copy). * Merge all that (using pull requests). * Run ``python setup.py sdist``, and smoke-test the resulting package (install in a clean virtual environment, import ``ldap``). diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index ef958a13..4e195264 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.0' +__version__ = '3.4.2' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index cce4e806..e76528a7 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 7e69a594..7561d09a 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index bb59e7fa..a49b13f7 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 00eaedd09ce5a7dabb4bc337443e020771177fa4 Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Fri, 26 Aug 2022 23:03:38 +0200 Subject: [PATCH 183/206] Add ASN.1 replace to psearch control Allow Sphinx to pickup the ASN.1 substitution from pyasn1, so docs can build. --- Doc/reference/ldap-controls.rst | 1 + Doc/reference/ldap-extop.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 37d7c1bc..2206e101 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -171,6 +171,7 @@ search. .. autoclass:: ldap.controls.psearch.EntryChangeNotificationControl :members: +.. |ASN.1| replace:: Asn1Type :py:mod:`ldap.controls.sessiontrack` Session tracking control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/reference/ldap-extop.rst b/Doc/reference/ldap-extop.rst index 8fe49f42..ad70e4e7 100644 --- a/Doc/reference/ldap-extop.rst +++ b/Doc/reference/ldap-extop.rst @@ -38,3 +38,5 @@ This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. .. autoclass:: ldap.extop.dds.RefreshResponse :members: + +.. |ASN.1| replace:: Asn1Type From e5959b38902bbf1d68a3f16b04ddc210884b8d5e Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Sat, 27 Aug 2022 19:40:46 +0200 Subject: [PATCH 184/206] Fix broken reset and tearDown in pypy3 There appear to be some minor differences in the CPython and Pypy3 implementations of del. Use delattr to ensure that hasattr will see the attributes as deleted. --- Tests/t_ldapobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 9e4e3311..ccc7d218 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -658,12 +658,12 @@ def _open_ldap_conn(self, who=None, cred=None, **kwargs): def tearDown(self): self._sock.close() - del self._sock + delattr(self, '_sock') super().tearDown() def reset_connection(self): self._sock.close() - del self._sock + delattr(self, '_sock') super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection() From 4e53fc927945e294d74cfde1f0e66dee4327637d Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Wed, 31 Aug 2022 20:11:13 +0200 Subject: [PATCH 185/206] Add slapdtest.certs to setup.py packages to fix the deprecation warning --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b1939571..33cc2603 100644 --- a/setup.py +++ b/setup.py @@ -153,6 +153,7 @@ class OpenLDAP2: 'ldap.extop', 'ldap.schema', 'slapdtest', + 'slapdtest.certs', ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, From b3d6a8c2a6466dcf5f24cfa42d2bfc3815f9cedd Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 1 Sep 2022 11:32:03 -0700 Subject: [PATCH 186/206] Back out the removal of OPT_X_TLS and OPT_X_TLS_CTX options Closes #480 --- Doc/reference/ldap.rst | 10 ++++++++++ Lib/ldap/constants.py | 2 ++ Modules/constants_generated.h | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 2d5c4780..d059dfa4 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -409,6 +409,10 @@ TLS options get/set allowed cipher suites +.. py:data:: OPT_X_TLS_CTX + + get address of internal memory address of TLS context (**DO NOT USE**) + .. py:data:: OPT_X_TLS_PEERCERT Get peer's certificate as binary ASN.1 data structure (DER) @@ -472,6 +476,12 @@ TLS options get/set path to /dev/urandom (**DO NOT USE**) +.. py:data:: OPT_X_TLS + + .. deprecated:: 3.3.0 + The option is deprecated in OpenLDAP and should no longer be used. It + will be removed in the future. + .. note:: OpenLDAP supports several TLS/SSL libraries. OpenSSL is the most common diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 1807fc55..0e7df6e7 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -267,6 +267,8 @@ class Str(Constant): Int('OPT_DEFBASE', optional=True), + TLSInt('OPT_X_TLS', optional=True), + TLSInt('OPT_X_TLS_CTX'), TLSInt('OPT_X_TLS_CACERTFILE'), TLSInt('OPT_X_TLS_CACERTDIR'), TLSInt('OPT_X_TLS_CERTFILE'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 2d385549..3e59f828 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -202,6 +202,12 @@ add_int(OPT_DEFBASE); #if HAVE_TLS + +#if defined(LDAP_OPT_X_TLS) +add_int(OPT_X_TLS); +#endif + +add_int(OPT_X_TLS_CTX); add_int(OPT_X_TLS_CACERTFILE); add_int(OPT_X_TLS_CACERTDIR); add_int(OPT_X_TLS_CERTFILE); From 9dd59a9cefdf8980beca5d75ac5983fdde7f0d79 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 15 Sep 2022 16:38:25 -0700 Subject: [PATCH 187/206] Prepare a new release --- CHANGES | 15 +++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b1ccc990..500fa1e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,18 @@ +Released 3.4.3 2022-09-15 + +This is a minor release to bring back the removed OPT_X_TLS option. +Please note, it's still a deprecated option and it will be removed in 3.5.0. + +The following deprecated option has been brought back: +- ``OPT_X_TLS`` + +Fixes: +* Sphinx documentation is now successfully built +* pypy3 tests stability was improved +* setup.py deprecation warning is now resolved + + +---------------------------------------------------------------- Released 3.4.2 2022-07-06 This is a minor release to provide out-of-the-box compatibility with the merge diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 4e195264..026e9101 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.2' +__version__ = '3.4.3' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index e76528a7..964076d3 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 7561d09a..ae1d643d 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index a49b13f7..7ab7d2bd 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 88e74b923594693f9d6a7f666f1ce783f2f39762 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Tue, 20 Dec 2022 20:58:16 -0600 Subject: [PATCH 188/206] Add Python 3.10 and 3.11 to CI and tox --- .github/workflows/ci.yml | 2 +- .github/workflows/tox-fedora.yml | 1 + setup.py | 2 ++ tox.ini | 4 +++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bc2ae0e..9700a8ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - name: Checkout uses: "actions/checkout@v2" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index c0dbb45c..cc75db90 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -23,6 +23,7 @@ jobs: - py38 - py39 - py310 + - py311 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/setup.py b/setup.py index 33cc2603..2bba473e 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,8 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index aaef8b5a..3387d094 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 +envlist = py{36,37,38,39,310,311},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 minver = 1.8 [gh-actions] @@ -14,6 +14,8 @@ python = 3.7: py37 3.8: py38, doc, py3-nosasltls 3.9: py39, py3-trace + 3.10: py310 + 3.11: py311 pypy3: pypy3 [testenv] From b8ad0c5ea7d2c4b295ea636c03124c9e5efb5eea Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Wed, 18 Jan 2023 19:17:00 -0800 Subject: [PATCH 189/206] Fix CI images after ubuntu-latest update Keep 20.04 for py3.6 testing. Replace the deprecated enchant package with enchant-2. --- .github/workflows/ci.yml | 4 ++-- .github/workflows/tox-fedora.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9700a8ed..2432e9e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: [push, pull_request] jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" - runs-on: "ubuntu-latest" + runs-on: "ubuntu-20.04" strategy: fail-fast: false matrix: @@ -18,7 +18,7 @@ jobs: run: | set -ex sudo apt update - sudo apt install -y ldap-utils slapd enchant libldap2-dev libsasl2-dev apparmor-utils + sudo apt install -y ldap-utils slapd enchant-2 libldap2-dev libsasl2-dev apparmor-utils - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index cc75db90..381a0b0c 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -32,4 +32,4 @@ jobs: - doc # Use GitHub's Linux Docker host - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 From ec74e5b0a60aa5716119aa06ac48e01344b5fdf2 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 2 Feb 2023 21:34:08 -0800 Subject: [PATCH 190/206] Conscious Language: Rename master branch to main branch Additionally, rename sphinx configuration parameter master_doc to root_doc Fixes: https://github.com/python-ldap/python-ldap/issues/509 --- Doc/conf.py | 4 ++-- Doc/faq.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index b883736e..e79cfb34 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -50,8 +50,8 @@ # The suffix of source filenames. source_suffix = '.rst' -# The master toctree document. -master_doc = 'index' +# The root toctree document. +root_doc = 'index' # General substitutions. project = 'python-ldap' diff --git a/Doc/faq.rst b/Doc/faq.rst index e96fc030..39a6743c 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -13,7 +13,7 @@ Project **A3**: see file CHANGES in source distribution or `repository`_. -.. _repository: https://github.com/python-ldap/python-ldap/blob/master/CHANGES +.. _repository: https://github.com/python-ldap/python-ldap/blob/main/CHANGES Usage From 105925fe6db9cc6c71fafe16bddaa4abcbb8ddce Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Wed, 8 Feb 2023 02:21:53 +0100 Subject: [PATCH 191/206] fix(ReconnectLDAPObject): reconnect race condition Move calling `unbind_s()` inside the locked region so that `self._l` is handled atomically. Add a new parameter `force` to either forcefully close any previous connection or keep re-use the previous connection if it still is supposed to work. --- Lib/ldap/ldapobject.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 9442e39b..7a9c17f6 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -893,7 +893,7 @@ def __setstate__(self,d): self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) # XXX cannot pickle file, use default trace file self._trace_file = ldap._trace_file - self.reconnect(self._uri) + self.reconnect(self._uri,force=True) def _store_last_bind(self,_method,*args,**kwargs): self._last_bind = (_method,args,kwargs) @@ -914,11 +914,16 @@ def _restore_options(self): def passwd_s(self,*args,**kwargs): return self._apply_method_s(SimpleLDAPObject.passwd_s,*args,**kwargs) - def reconnect(self,uri,retry_max=1,retry_delay=60.0): + def reconnect(self,uri,retry_max=1,retry_delay=60.0,force=True): # Drop and clean up old connection completely # Reconnect self._reconnect_lock.acquire() try: + if hasattr(self,'_l'): + if force: + SimpleLDAPObject.unbind_s(self) + else: + return reconnect_counter = retry_max while reconnect_counter: counter_text = '%d. (of %d)' % (retry_max-reconnect_counter+1,retry_max) @@ -962,14 +967,12 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): return # reconnect() def _apply_method_s(self,func,*args,**kwargs): - if not hasattr(self,'_l'): - self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) + self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=False) try: return func(self,*args,**kwargs) except ldap.SERVER_DOWN: - SimpleLDAPObject.unbind_s(self) # Try to reconnect - self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) + self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=True) # Re-try last operation return func(self,*args,**kwargs) From e756eac00a80240b9c708fba145ff664c134b957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lum=C3=ADr=20=27Frenzy=27=20Balhar?= Date: Fri, 14 Apr 2023 06:13:20 +0200 Subject: [PATCH 192/206] Switch tox-github-action from master to main branch --- .github/workflows/tox-fedora.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 381a0b0c..4e88cee4 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run Tox tests - uses: fedora-python/tox-github-action@master + uses: fedora-python/tox-github-action@main with: tox_env: ${{ matrix.tox_env }} dnf_install: > From 91e0918f822e5a28d5b66b0bdf9b99b596f5be3c Mon Sep 17 00:00:00 2001 From: Diogo Teles Sant'Anna Date: Fri, 2 Jun 2023 12:46:09 -0300 Subject: [PATCH 193/206] ci: set minimal permissions on workflows (#525) Signed-off-by: Diogo Teles Sant'Anna --- .github/workflows/ci.yml | 3 +++ .github/workflows/tox-fedora.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2432e9e8..86e8ba51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,9 @@ name: CI on: [push, pull_request] +permissions: + contents: read + jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 4e88cee4..f41024a0 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -2,6 +2,9 @@ on: [push, pull_request] name: Tox on Fedora +permissions: + contents: read + jobs: tox_test: name: Tox env "${{matrix.tox_env}}" on Fedora From 72c1b5e0f37f74b1a68e67b6b5712d395d577bb9 Mon Sep 17 00:00:00 2001 From: Diogo Teles Sant'Anna Date: Thu, 27 Jul 2023 20:55:14 -0300 Subject: [PATCH 194/206] docs: create Security Policy (#530) --- SECURITY.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..752b1394 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at our [security advisory](https://github.com/python-ldap/python-ldap/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base. From 30fe146e6c8e881a2db2a3f6b60fb6201bf6a534 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Tue, 22 Aug 2023 20:02:46 -0400 Subject: [PATCH 195/206] Update article links in resources.rst (#533) Fix broken links. --- Doc/resources.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/resources.rst b/Doc/resources.rst index 56cb1a1a..795f8b63 100644 --- a/Doc/resources.rst +++ b/Doc/resources.rst @@ -8,13 +8,13 @@ members. Therefore some information might be outdated or links might be broken. *Python LDAP Applications* articles by Matt Butcher --------------------------------------------------- -* `Part 1 - Installing and Configuring the Python-LDAP Library and Binding to an LDAP Directory `_ +* `Part 1 - Installing and Configuring the Python-LDAP Library and Binding to an LDAP Directory `_ This also covers SASL. -* `Part 2 - LDAP Operations `_ -* `Part 3 - More LDAP Operations and the LDAP URL Library `_ -* `Part 4 - LDAP Schema `_ +* `Part 2 - LDAP Operations `_ +* `Part 3 - More LDAP Operations and the LDAP URL Library `_ +* `Part 4 - LDAP Schema `_ Gee, someone waded through the badly documented mysteries of module :mod:`ldap.schema`. From 2229d83646895ce041a5582400fa77e82d40c2c7 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 4 Oct 2023 18:42:53 +0200 Subject: [PATCH 196/206] Test with Python 3.12 (#537) --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++----- .github/workflows/tox-fedora.yml | 1 + Modules/options.c | 4 ++-- setup.py | 1 + tox.ini | 10 ++++++++-- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86e8ba51..37843f31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,13 @@ --- name: CI -on: [push, pull_request] +on: + push: + pull_request: + schedule: + # every Monday + - cron: '30 4 * * 1' + workflow_dispatch: permissions: contents: read @@ -9,14 +15,26 @@ permissions: jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" - runs-on: "ubuntu-20.04" + runs-on: "${{ matrix.image }}" strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "pypy3.9" + image: + - "ubuntu-22.04" + include: + - python-version: "3.6" + image: "ubuntu-20.04" steps: - name: Checkout - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Install apt dependencies run: | set -ex @@ -25,9 +43,10 @@ jobs: - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: "Install Python dependencies" run: | set -xe diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index f41024a0..b86303fe 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -27,6 +27,7 @@ jobs: - py39 - py310 - py311 + - py312 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/Modules/options.c b/Modules/options.c index 1a22bed1..a621f81a 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -207,8 +207,8 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) } else { PyErr_Format(PyExc_ValueError, - "timeout must be >= 0 or -1/None for infinity, got %f", - doubleval); + "timeout must be >= 0 or -1/None for infinity, got %S", + value); return 0; } break; diff --git a/setup.py b/setup.py index 2bba473e..6da3f491 100644 --- a/setup.py +++ b/setup.py @@ -92,6 +92,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index 3387d094..beade024 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310,311},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 +envlist = py{36,37,38,39,310,311,312},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3.9 minver = 1.8 [gh-actions] @@ -16,7 +16,8 @@ python = 3.9: py39, py3-trace 3.10: py310 3.11: py311 - pypy3: pypy3 + 3.12: py312 + pypy3.9: pypy3.9 [testenv] deps = @@ -28,6 +29,11 @@ setenv = commands = {envpython} -bb -Werror \ -m unittest discover -v -s Tests -p 't_*' {posargs} +[testenv:py312] +# Python 3.12 headers are incompatible with declaration-after-statement +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -std=c99 + [testenv:py3-nosasltls] basepython = python3 # don't install, install dependencies manually From 1490e999e10960f0fdf974f6da804a2fc55e4b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Nov 2023 17:46:35 +0000 Subject: [PATCH 197/206] Claim ownership of socket once we've passed it to libldap Fixes: https://github.com/python-ldap/python-ldap/issues/460 Closes: https://github.com/python-ldap/python-ldap/pull/543 --- Tests/t_ldapobject.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index ccc7d218..ada5f990 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -647,24 +647,14 @@ def test105_reconnect_restore(self): @requires_init_fd() class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): def _open_ldap_conn(self, who=None, cred=None, **kwargs): - if hasattr(self, '_sock'): - raise RuntimeError("socket already connected") - self._sock = socket.create_connection( + sock = socket.create_connection( (self.server.hostname, self.server.port) ) - return super()._open_ldap_conn( - who=who, cred=cred, fileno=self._sock.fileno(), **kwargs + result = super()._open_ldap_conn( + who=who, cred=cred, fileno=sock.fileno(), **kwargs ) - - def tearDown(self): - self._sock.close() - delattr(self, '_sock') - super().tearDown() - - def reset_connection(self): - self._sock.close() - delattr(self, '_sock') - super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection() + sock.detach() + return result if __name__ == '__main__': From 75a765f82fed2e3f418a1152b3af02f7d1e2629e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 30 Oct 2023 09:19:41 +0100 Subject: [PATCH 198/206] refactor: Merge all header files Merge all header files except `constants_generated.h` into a single header file `pythonldap.h`. A single header file makes it far easier to port python-ldap to heap types and module state for Per-Interpreter GIL. `pythonldap.h` uses new macros `PYLDAP_FUNC` and `PYLDAP_DATA` to declare functions and data, which are used across C files. Remove unused macro `streq`. See: https://github.com/python-ldap/python-ldap/issues/540 Signed-off-by: Christian Heimes --- Makefile | 3 +- Modules/LDAPObject.c | 8 +-- Modules/LDAPObject.h | 38 ------------ Modules/berval.c | 3 +- Modules/berval.h | 11 ---- Modules/common.c | 2 +- Modules/common.h | 68 --------------------- Modules/constants.c | 4 +- Modules/constants.h | 24 -------- Modules/functions.c | 7 +-- Modules/functions.h | 9 --- Modules/ldapcontrol.c | 6 +- Modules/ldapcontrol.h | 13 ---- Modules/ldapmodule.c | 7 +-- Modules/message.c | 6 +- Modules/message.h | 11 ---- Modules/options.c | 7 +-- Modules/options.h | 7 --- Modules/pythonldap.h | 137 ++++++++++++++++++++++++++++++++++++++++++ setup.py | 9 +-- 20 files changed, 149 insertions(+), 231 deletions(-) delete mode 100644 Modules/LDAPObject.h delete mode 100644 Modules/berval.h delete mode 100644 Modules/common.h delete mode 100644 Modules/constants.h delete mode 100644 Modules/functions.h delete mode 100644 Modules/ldapcontrol.h delete mode 100644 Modules/message.h delete mode 100644 Modules/options.h create mode 100644 Modules/pythonldap.h diff --git a/Makefile b/Makefile index 577ba883..2b52ddf5 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,8 @@ valgrind: build $(PYTHON_SUPP) autoformat: indent black indent: - indent Modules/*.c Modules/*.h + indent Modules/*.c + indent -npsl Modules/pythonldap.h rm -f Modules/*.c~ Modules/*.h~ black: diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index da18d575..eaf831bd 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1,16 +1,10 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" +#include "pythonldap.h" #include "patchlevel.h" #include #include -#include "constants.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "message.h" -#include "berval.h" -#include "options.h" #ifdef HAVE_SASL #include diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h deleted file mode 100644 index 4af0b382..00000000 --- a/Modules/LDAPObject.h +++ /dev/null @@ -1,38 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_LDAPObject -#define __h_LDAPObject - -#include "common.h" - -typedef struct { - PyObject_HEAD LDAP *ldap; - PyThreadState *_save; /* for thread saving on referrals */ - int valid; -} LDAPObject; - -extern PyTypeObject LDAP_Type; - -#define LDAPObject_Check(v) (Py_TYPE(v) == &LDAP_Type) - -extern LDAPObject *newLDAPObject(LDAP *); - -/* macros to allow thread saving in the context of an LDAP connection */ - -#define LDAP_BEGIN_ALLOW_THREADS( l ) \ - { \ - LDAPObject *lo = (l); \ - if (lo->_save != NULL) \ - Py_FatalError( "saving thread twice?" ); \ - lo->_save = PyEval_SaveThread(); \ - } - -#define LDAP_END_ALLOW_THREADS( l ) \ - { \ - LDAPObject *lo = (l); \ - PyThreadState *_save = lo->_save; \ - lo->_save = NULL; \ - PyEval_RestoreThread( _save ); \ - } - -#endif /* __h_LDAPObject */ diff --git a/Modules/berval.c b/Modules/berval.c index 6917baef..39cc98a8 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -1,7 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "berval.h" +#include "pythonldap.h" /* * Copies out the data from a berval, and returns it as a new Python object, diff --git a/Modules/berval.h b/Modules/berval.h deleted file mode 100644 index 9c427240..00000000 --- a/Modules/berval.h +++ /dev/null @@ -1,11 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_berval -#define __h_berval - -#include "common.h" - -PyObject *LDAPberval_to_object(const struct berval *bv); -PyObject *LDAPberval_to_unicode_object(const struct berval *bv); - -#endif /* __h_berval_ */ diff --git a/Modules/common.c b/Modules/common.c index 9d7001c0..4cfee744 100644 --- a/Modules/common.c +++ b/Modules/common.c @@ -1,7 +1,7 @@ /* Miscellaneous common routines * See https://www.python-ldap.org/ for details. */ -#include "common.h" +#include "pythonldap.h" /* dynamically add the methods into the module dictionary d */ diff --git a/Modules/common.h b/Modules/common.h deleted file mode 100644 index bc554c85..00000000 --- a/Modules/common.h +++ /dev/null @@ -1,68 +0,0 @@ -/* common utility macros - * See https://www.python-ldap.org/ for details. */ - -#ifndef __h_common -#define __h_common - -#define PY_SSIZE_T_CLEAN - -#include "Python.h" - -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif - -#include -#include -#include - -#if LDAP_VENDOR_VERSION < 20400 -#error Current python-ldap requires OpenLDAP 2.4.x -#endif - -#if LDAP_VENDOR_VERSION >= 20448 - /* openldap.h with ldap_init_fd() was introduced in 2.4.48 - * see https://bugs.openldap.org/show_bug.cgi?id=8671 - */ -#define HAVE_LDAP_INIT_FD 1 -#include -#elif (defined(__APPLE__) && (LDAP_VENDOR_VERSION == 20428)) -/* macOS system libldap 2.4.28 does not have ldap_init_fd symbol */ -#undef HAVE_LDAP_INIT_FD -#else - /* ldap_init_fd() has been around for a very long time - * SSSD has been defining the function for a while, so it's probably OK. - */ -#define HAVE_LDAP_INIT_FD 1 -#define LDAP_PROTO_TCP 1 -#define LDAP_PROTO_UDP 2 -#define LDAP_PROTO_IPC 3 -extern int ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, - LDAP **ldp); -#endif - -#if defined(MS_WINDOWS) -#include -#else /* unix */ -#include -#include -#include -#endif - -#include -#define streq( a, b ) \ - ( (*(a)==*(b)) && 0==strcmp(a,b) ) - -extern PyObject *LDAPerror_TypeError(const char *, PyObject *); - -void LDAPadd_methods(PyObject *d, PyMethodDef *methods); - -#define PyNone_Check(o) ((o) == Py_None) - -/* Py2/3 compatibility */ -#if PY_VERSION_HEX >= 0x03000000 -/* In Python 3, alias PyInt to PyLong */ -#define PyInt_FromLong PyLong_FromLong -#endif - -#endif /* __h_common_ */ diff --git a/Modules/constants.c b/Modules/constants.c index 8d6f63b0..b70db245 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -1,9 +1,7 @@ /* constants defined for LDAP * See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "ldapcontrol.h" +#include "pythonldap.h" /* the base exception class */ diff --git a/Modules/constants.h b/Modules/constants.h deleted file mode 100644 index 7b9ce53e..00000000 --- a/Modules/constants.h +++ /dev/null @@ -1,24 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_constants_ -#define __h_constants_ - -#include "common.h" - -extern int LDAPinit_constants(PyObject *m); -extern PyObject *LDAPconstant(int); - -extern PyObject *LDAPexception_class; -extern PyObject *LDAPerror(LDAP *); -extern PyObject *LDAPraise_for_message(LDAP *, LDAPMessage *m); -PyObject *LDAPerr(int errnum); - -#ifndef LDAP_CONTROL_PAGE_OID -#define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" -#endif /* !LDAP_CONTROL_PAGE_OID */ - -#ifndef LDAP_CONTROL_VALUESRETURNFILTER -#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ -#endif /* !LDAP_CONTROL_VALUESRETURNFILTER */ - -#endif /* __h_constants_ */ diff --git a/Modules/functions.c b/Modules/functions.c index b811708f..f7d9cf37 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "functions.h" -#include "LDAPObject.h" -#include "berval.h" -#include "constants.h" -#include "options.h" +#include "pythonldap.h" /* ldap_initialize */ diff --git a/Modules/functions.h b/Modules/functions.h deleted file mode 100644 index 2aef9740..00000000 --- a/Modules/functions.h +++ /dev/null @@ -1,9 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_functions_ -#define __h_functions_ - -#include "common.h" -extern void LDAPinit_functions(PyObject *); - -#endif /* __h_functions_ */ diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index e287e9a3..4a37b614 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -1,10 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "berval.h" -#include "constants.h" +#include "pythonldap.h" /* Prints to stdout the contents of an array of LDAPControl objects */ diff --git a/Modules/ldapcontrol.h b/Modules/ldapcontrol.h deleted file mode 100644 index 74cae423..00000000 --- a/Modules/ldapcontrol.h +++ /dev/null @@ -1,13 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_ldapcontrol -#define __h_ldapcontrol - -#include "common.h" - -void LDAPinit_control(PyObject *d); -void LDAPControl_List_DEL(LDAPControl **); -int LDAPControls_from_object(PyObject *, LDAPControl ***); -PyObject *LDAPControls_to_List(LDAPControl **ldcs); - -#endif /* __h_ldapcontrol */ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 34d5a24c..8562337b 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "functions.h" -#include "ldapcontrol.h" - -#include "LDAPObject.h" +#include "pythonldap.h" #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit__ldap(void); diff --git a/Modules/message.c b/Modules/message.c index 22aa313c..f1403237 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -1,10 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "message.h" -#include "berval.h" -#include "ldapcontrol.h" -#include "constants.h" +#include "pythonldap.h" /* * Converts an LDAP message into a Python structure. diff --git a/Modules/message.h b/Modules/message.h deleted file mode 100644 index ed73f32c..00000000 --- a/Modules/message.h +++ /dev/null @@ -1,11 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_message -#define __h_message - -#include "common.h" - -extern PyObject *LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, - int add_intermediates); - -#endif /* __h_message_ */ diff --git a/Modules/options.c b/Modules/options.c index a621f81a..df55ed05 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "options.h" -#include "berval.h" +#include "pythonldap.h" void set_timeval_from_double(struct timeval *tv, double d) diff --git a/Modules/options.h b/Modules/options.h deleted file mode 100644 index fd6a5ce2..00000000 --- a/Modules/options.h +++ /dev/null @@ -1,7 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -int LDAP_optionval_by_name(const char *name); -int LDAP_set_option(LDAPObject *self, int option, PyObject *value); -PyObject *LDAP_get_option(LDAPObject *self, int option); - -void set_timeval_from_double(struct timeval *tv, double d); diff --git a/Modules/pythonldap.h b/Modules/pythonldap.h new file mode 100644 index 00000000..ae6a1269 --- /dev/null +++ b/Modules/pythonldap.h @@ -0,0 +1,137 @@ +/* common utility macros + * See https://www.python-ldap.org/ for details. */ + +#ifndef pythonldap_h +#define pythonldap_h + +/* *** common *** */ +#define PY_SSIZE_T_CLEAN + +#include "Python.h" + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include +#include +#include + +/* Py2/3 compatibility */ +#if PY_VERSION_HEX >= 0x03000000 +/* In Python 3, alias PyInt to PyLong */ +#define PyInt_FromLong PyLong_FromLong +#endif + +#if LDAP_VENDOR_VERSION < 20400 +#error Current python-ldap requires OpenLDAP 2.4.x +#endif + +#if LDAP_VENDOR_VERSION >= 20448 + /* openldap.h with ldap_init_fd() was introduced in 2.4.48 + * see https://bugs.openldap.org/show_bug.cgi?id=8671 + */ +#define HAVE_LDAP_INIT_FD 1 +#include +#elif (defined(__APPLE__) && (LDAP_VENDOR_VERSION == 20428)) +/* macOS system libldap 2.4.28 does not have ldap_init_fd symbol */ +#undef HAVE_LDAP_INIT_FD +#else + /* ldap_init_fd() has been around for a very long time + * SSSD has been defining the function for a while, so it's probably OK. + */ +#define HAVE_LDAP_INIT_FD 1 +#define LDAP_PROTO_TCP 1 +#define LDAP_PROTO_UDP 2 +#define LDAP_PROTO_IPC 3 +LDAP_F(int) ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, + LDAP **ldp); +#endif + +#if defined(MS_WINDOWS) +#include +#else /* unix */ +#include +#include +#include +#endif + +#define PYLDAP_FUNC(rtype) rtype +#define PYLDAP_DATA(rtype) extern rtype + +PYLDAP_FUNC(PyObject *) LDAPerror_TypeError(const char *, PyObject *); + +PYLDAP_FUNC(void) LDAPadd_methods(PyObject *d, PyMethodDef *methods); + +#define PyNone_Check(o) ((o) == Py_None) + +/* *** berval *** */ +PYLDAP_FUNC(PyObject *) LDAPberval_to_object(const struct berval *bv); +PYLDAP_FUNC(PyObject *) LDAPberval_to_unicode_object(const struct berval *bv); + +/* *** constants *** */ +PYLDAP_FUNC(int) LDAPinit_constants(PyObject *m); + +PYLDAP_DATA(PyObject *) LDAPexception_class; +PYLDAP_FUNC(PyObject *) LDAPerror(LDAP *); +PYLDAP_FUNC(PyObject *) LDAPraise_for_message(LDAP *, LDAPMessage *m); +PYLDAP_FUNC(PyObject *) LDAPerr(int errnum); + +#ifndef LDAP_CONTROL_PAGE_OID +#define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" +#endif /* !LDAP_CONTROL_PAGE_OID */ + +#ifndef LDAP_CONTROL_VALUESRETURNFILTER +#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ +#endif /* !LDAP_CONTROL_VALUESRETURNFILTER */ + +/* *** functions *** */ +PYLDAP_FUNC(void) LDAPinit_functions(PyObject *); + +/* *** ldapcontrol *** */ +PYLDAP_FUNC(void) LDAPinit_control(PyObject *d); +PYLDAP_FUNC(void) LDAPControl_List_DEL(LDAPControl **); +PYLDAP_FUNC(int) LDAPControls_from_object(PyObject *, LDAPControl ***); +PYLDAP_FUNC(PyObject *) LDAPControls_to_List(LDAPControl **ldcs); + +/* *** ldapobject *** */ +typedef struct { + PyObject_HEAD LDAP *ldap; + PyThreadState *_save; /* for thread saving on referrals */ + int valid; +} LDAPObject; + +PYLDAP_DATA(PyTypeObject) LDAP_Type; +PYLDAP_FUNC(LDAPObject *) newLDAPObject(LDAP *); + +/* macros to allow thread saving in the context of an LDAP connection */ + +#define LDAP_BEGIN_ALLOW_THREADS( l ) \ + { \ + LDAPObject *lo = (l); \ + if (lo->_save != NULL) \ + Py_FatalError( "saving thread twice?" ); \ + lo->_save = PyEval_SaveThread(); \ + } + +#define LDAP_END_ALLOW_THREADS( l ) \ + { \ + LDAPObject *lo = (l); \ + PyThreadState *_save = lo->_save; \ + lo->_save = NULL; \ + PyEval_RestoreThread( _save ); \ + } + +/* *** messages *** */ +PYLDAP_FUNC(PyObject *) +LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, + int add_intermediates); + +/* *** options *** */ +PYLDAP_FUNC(int) LDAP_optionval_by_name(const char *name); +PYLDAP_FUNC(int) LDAP_set_option(LDAPObject *self, int option, + PyObject *value); +PYLDAP_FUNC(PyObject *) LDAP_get_option(LDAPObject *self, int option); +PYLDAP_FUNC(void) set_timeval_from_double(struct timeval *tv, double d); + +#endif /* pythonldap_h */ diff --git a/setup.py b/setup.py index 6da3f491..dbf66a04 100644 --- a/setup.py +++ b/setup.py @@ -117,15 +117,8 @@ class OpenLDAP2: 'Modules/berval.c', ], depends = [ - 'Modules/LDAPObject.h', - 'Modules/berval.h', - 'Modules/common.h', + 'Modules/pythonldap.h', 'Modules/constants_generated.h', - 'Modules/constants.h', - 'Modules/functions.h', - 'Modules/ldapcontrol.h', - 'Modules/message.h', - 'Modules/options.h', ], libraries = LDAP_CLASS.libs, include_dirs = ['Modules'] + LDAP_CLASS.include_dirs, From f48101097eb08f902daed5b8e7836c54cf44b0f4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 30 Oct 2023 09:40:12 +0100 Subject: [PATCH 199/206] refactor: Remove Python 2 vestiges The C code had a few version checks for Python 2. python-ldap requires Python >= 3.6. Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 51 +++++++++++--------------------------------- Modules/constants.c | 8 +++---- Modules/ldapmodule.c | 45 ++++++++++---------------------------- Modules/options.c | 2 +- Modules/pythonldap.h | 6 ------ 5 files changed, 28 insertions(+), 84 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index eaf831bd..71fac73e 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -270,13 +270,8 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) if (attrlist == Py_None) { /* None means a NULL attrlist */ -#if PY_MAJOR_VERSION == 2 - } - else if (PyBytes_Check(attrlist)) { -#else } else if (PyUnicode_Check(attrlist)) { -#endif /* caught by John Benninghoff */ LDAPerror_TypeError ("attrs_from_List(): expected *list* of strings, not a string", @@ -287,11 +282,7 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) PyObject *item = NULL; Py_ssize_t i, len, strlen; -#if PY_MAJOR_VERSION >= 3 const char *str; -#else - char *str; -#endif seq = PySequence_Fast(attrlist, "expected list of strings or None"); if (seq == NULL) @@ -309,24 +300,12 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) item = PySequence_Fast_GET_ITEM(seq, i); if (item == NULL) goto error; -#if PY_MAJOR_VERSION == 2 - /* Encoded in Python to UTF-8 */ - if (!PyBytes_Check(item)) { - LDAPerror_TypeError - ("attrs_from_List(): expected bytes in list", item); - goto error; - } - if (PyBytes_AsStringAndSize(item, &str, &strlen) == -1) { - goto error; - } -#else if (!PyUnicode_Check(item)) { LDAPerror_TypeError ("attrs_from_List(): expected string in list", item); goto error; } str = PyUnicode_AsUTF8AndSize(item, &strlen); -#endif /* Make a copy. PyBytes_AsString* / PyUnicode_AsUTF8* return * internal values that must be treated like const char. Python * 3.7 actually returns a const char. @@ -515,7 +494,7 @@ l_ldap_add_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_simple_bind */ @@ -566,7 +545,7 @@ l_ldap_simple_bind(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #ifdef HAVE_SASL @@ -724,7 +703,7 @@ l_ldap_sasl_bind_s(LDAPObject *self, PyObject *args) } else if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(ldaperror); + return PyLong_FromLong(ldaperror); } static PyObject * @@ -751,15 +730,9 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) * unsigned int, we need to use the "I" flag if we're running Python 2.3+ and a * "i" otherwise. */ -#if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 3) - if (!PyArg_ParseTuple - (args, "sOOOi:sasl_interactive_bind_s", &who, &SASLObject, - &serverctrls, &clientctrls, &sasl_flags)) -#else if (!PyArg_ParseTuple (args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags)) -#endif return NULL; if (not_valid(self)) @@ -803,7 +776,7 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) if (msgid != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #endif @@ -852,7 +825,7 @@ l_ldap_cancel(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #endif @@ -906,7 +879,7 @@ l_ldap_compare_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_delete_ext */ @@ -952,7 +925,7 @@ l_ldap_delete_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_modify_ext */ @@ -1009,7 +982,7 @@ l_ldap_modify_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_rename */ @@ -1059,7 +1032,7 @@ l_ldap_rename(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_result4 */ @@ -1275,7 +1248,7 @@ l_ldap_search_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_whoami_s (available since OpenLDAP 2.1.13) */ @@ -1445,7 +1418,7 @@ l_ldap_passwd(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_extended_operation */ @@ -1496,7 +1469,7 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* methods */ diff --git a/Modules/constants.c b/Modules/constants.c index b70db245..f0a0da94 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -105,20 +105,20 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) } if (msgtype > 0) { - pyresult = PyInt_FromLong(msgtype); + pyresult = PyLong_FromLong(msgtype); if (pyresult) PyDict_SetItemString(info, "msgtype", pyresult); Py_XDECREF(pyresult); } if (msgid >= 0) { - pyresult = PyInt_FromLong(msgid); + pyresult = PyLong_FromLong(msgid); if (pyresult) PyDict_SetItemString(info, "msgid", pyresult); Py_XDECREF(pyresult); } - pyresult = PyInt_FromLong(errnum); + pyresult = PyLong_FromLong(errnum); if (pyresult) PyDict_SetItemString(info, "result", pyresult); Py_XDECREF(pyresult); @@ -129,7 +129,7 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) Py_XDECREF(str); if (myerrno != 0) { - pyerrno = PyInt_FromLong(myerrno); + pyerrno = PyLong_FromLong(myerrno); if (pyerrno) PyDict_SetItemString(info, "errno", pyerrno); Py_XDECREF(pyerrno); diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 8562337b..cb3f58fb 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -2,12 +2,6 @@ #include "pythonldap.h" -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC PyInit__ldap(void); -#else -PyMODINIT_FUNC init_ldap(void); -#endif - #define _STR(x) #x #define STR(x) _STR(x) @@ -28,27 +22,24 @@ static PyMethodDef methods[] = { {NULL, NULL} }; +static struct PyModuleDef ldap_moduledef = { + PyModuleDef_HEAD_INIT, + "_ldap", /* m_name */ + "", /* m_doc */ + -1, /* m_size */ + methods, /* m_methods */ +}; + /* module initialisation */ -/* Common initialization code */ -PyObject * -init_ldap_module(void) +PyMODINIT_FUNC +PyInit__ldap() { PyObject *m, *d; /* Create the module and add the functions */ -#if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef ldap_moduledef = { - PyModuleDef_HEAD_INIT, - "_ldap", /* m_name */ - "", /* m_doc */ - -1, /* m_size */ - methods, /* m_methods */ - }; m = PyModule_Create(&ldap_moduledef); -#else - m = Py_InitModule("_ldap", methods); -#endif + /* Initialize LDAP class */ if (PyType_Ready(&LDAP_Type) < 0) { Py_DECREF(m); @@ -73,17 +64,3 @@ init_ldap_module(void) return m; } - -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC -init_ldap() -{ - init_ldap_module(); -} -#else -PyMODINIT_FUNC -PyInit__ldap() -{ - return init_ldap_module(); -} -#endif diff --git a/Modules/options.c b/Modules/options.c index df55ed05..4577b075 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -368,7 +368,7 @@ LDAP_get_option(LDAPObject *self, int option) res = LDAP_int_get_option(self, option, &intval); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); - return PyInt_FromLong(intval); + return PyLong_FromLong(intval); #ifdef LDAP_OPT_TCP_USER_TIMEOUT case LDAP_OPT_TCP_USER_TIMEOUT: diff --git a/Modules/pythonldap.h b/Modules/pythonldap.h index ae6a1269..7703af5e 100644 --- a/Modules/pythonldap.h +++ b/Modules/pythonldap.h @@ -17,12 +17,6 @@ #include #include -/* Py2/3 compatibility */ -#if PY_VERSION_HEX >= 0x03000000 -/* In Python 3, alias PyInt to PyLong */ -#define PyInt_FromLong PyLong_FromLong -#endif - #if LDAP_VENDOR_VERSION < 20400 #error Current python-ldap requires OpenLDAP 2.4.x #endif From 16a3a3c0175aa51ee22efda118dbaeb3881cec3c Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 17 Nov 2023 12:29:34 -0800 Subject: [PATCH 200/206] Prepare a new release --- CHANGES | 17 +++++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 500fa1e7..0491b6ef 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,20 @@ +Released 3.4.4 2022-11-17 + +Fixes: +* Reconnect race condition in ReconnectLDAPObject is now fixed +* Socket ownership is now claimed once we've passed it to libldap +* LDAP_set_option string formats are now compatible with Python 3.12 + +Doc/ +* Security Policy was created +* Broken article links are fixed now +* Bring Conscious Language improvements + +Infrastructure: +* Add testing and document support for Python 3.10, 3.11, and 3.12 + + +---------------------------------------------------------------- Released 3.4.3 2022-09-15 This is a minor release to bring back the removed OPT_X_TLS option. diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 026e9101..18ead66c 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.3' +__version__ = '3.4.4' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 964076d3..b4dfd890 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index ae1d643d..fa41321c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 7ab7d2bd..7c410180 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From ac5d051ec3bc3dee700ebbe3c1ba68f54fc39bc5 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Mon, 27 Nov 2023 21:52:26 +0200 Subject: [PATCH 201/206] Update link to unofficial Windows binary builds (#524) --- Doc/installing.rst | 4 ++-- Doc/spelling_wordlist.txt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index e4518c11..6627ce5d 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -63,8 +63,8 @@ to get up to date information which versions are available. Windows ------- -Unofficial packages for Windows are available on -`Christoph Gohlke's page `_. +Unofficial binary builds for Windows are provided by Christoph Gohlke, available at +`python-ldap-build `_. `FreeBSD `_ diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index e6c2aedd..8cdd9f16 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -25,6 +25,7 @@ changeNumber changesOnly changeType changeTypes +Christoph cidict clientctrls conf @@ -56,6 +57,7 @@ filterstr filterStr formatOID func +Gohlke GPG Heimdal hostport From 10985e38902d170402acdfe6fabece5db70437cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Bourgault?= Date: Thu, 22 Feb 2024 08:19:11 +0100 Subject: [PATCH 202/206] docs: add missing negation in contributing.rst (#552) Current description contains a sentence that miss a negative form, contradicting previous sentence and leaving the reader with an ambiguity. --- Doc/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index bbaab491..6ef8a5a8 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -19,7 +19,7 @@ Communication Always keep in mind that python-ldap is developed and maintained by volunteers. We're happy to share our work, and to work with you to make the library better, -but (until you pay someone), there's obligation to provide assistance. +but (until you pay someone), there's no obligation to provide assistance. So, keep it friendly, respectful, and supportive! From 06fdd3dc4d3da82afa8d445cb8f1e8842c824236 Mon Sep 17 00:00:00 2001 From: RafaelWO <38643099+RafaelWO@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:58:54 +0100 Subject: [PATCH 203/206] Update docs on installation requirements (#548) Separate building and testing requirements for Debian --- Doc/installing.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 6627ce5d..03e7a295 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -143,10 +143,15 @@ Packages for building:: Debian ------ +Packages for building:: + + # apt-get install build-essential ldap-utils \ + libldap2-dev libsasl2-dev + Packages for building and testing:: - # apt-get install build-essential python3-dev \ - libldap2-dev libsasl2-dev slapd ldap-utils tox \ + # apt-get install build-essential ldap-utils \ + libldap2-dev libsasl2-dev slapd python3-dev tox \ lcov valgrind .. note:: From a58282adbc6b1f5f9755458227e6bb8667b72f6b Mon Sep 17 00:00:00 2001 From: Quanah Gibson-Mount Date: Mon, 22 Apr 2024 22:18:09 +0000 Subject: [PATCH 204/206] Fixes #565 - Use name values instead of raw decimal Use the name values for result types in syncrepl.py rather than the raw decimal values. Signed-off-by: Quanah Gibson-Mount --- Lib/ldap/syncrepl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 1708b468..fd0c1285 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -12,6 +12,7 @@ from ldap.pkginfo import __version__, __author__, __license__ from ldap.controls import RequestControl, ResponseControl, KNOWN_RESPONSE_CONTROLS +from ldap import RES_SEARCH_RESULT, RES_SEARCH_ENTRY, RES_INTERMEDIATE __all__ = [ 'SyncreplConsumer', @@ -407,7 +408,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): all=0, ) - if type == 101: + if type == RES_SEARCH_RESULT: # search result. This marks the end of a refreshOnly session. # look for a SyncDone control, save the cookie, and if necessary # delete non-present entries. @@ -420,7 +421,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): return False - elif type == 100: + elif type == RES_SEARCH_ENTRY: # search entry with associated SyncState control for m in msg: dn, attrs, ctrls = m @@ -439,7 +440,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): self.syncrepl_set_cookie(c.cookie) break - elif type == 121: + elif type == RES_INTERMEDIATE: # Intermediate message. If it is a SyncInfoMessage, parse it for m in msg: rname, resp, ctrls = m From 8b27db3d605974fc308b3b52ec464e354dcbafa0 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 10 Oct 2024 22:55:29 -0700 Subject: [PATCH 205/206] Add support for Python 3.13 (#576) Update GitHub Actions. Explicitly install python3-setuptools for Tox env runs on Fedora. --- .github/workflows/ci.yml | 4 +++- .github/workflows/tox-fedora.yml | 5 +++-- setup.py | 1 + tox.ini | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37843f31..2f835d76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,9 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" - "pypy3.9" + - "pypy3.10" image: - "ubuntu-22.04" include: @@ -43,7 +45,7 @@ jobs: - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index b86303fe..4c4c18f0 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -9,7 +9,7 @@ jobs: tox_test: name: Tox env "${{matrix.tox_env}}" on Fedora steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Run Tox tests uses: fedora-python/tox-github-action@main with: @@ -17,7 +17,7 @@ jobs: dnf_install: > @c-development openldap-devel python3-devel openldap-servers openldap-clients lcov clang-analyzer valgrind - enchant + enchant python3-setuptools strategy: matrix: tox_env: @@ -28,6 +28,7 @@ jobs: - py310 - py311 - py312 + - py313 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/setup.py b/setup.py index dbf66a04..8e7963a1 100644 --- a/setup.py +++ b/setup.py @@ -93,6 +93,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index beade024..22752067 100644 --- a/tox.ini +++ b/tox.ini @@ -17,10 +17,12 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 pypy3.9: pypy3.9 + pypy3.10: pypy3.10 [testenv] -deps = +deps = setuptools passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. @@ -98,6 +100,7 @@ deps = markdown sphinx sphinxcontrib-spelling + setuptools commands = {envpython} setup.py check --restructuredtext --metadata --strict {envpython} -m markdown README -f {envtmpdir}/README.html From 326f8708ca6d6fff36893a38f38b645bed9c5e6f Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 11 Oct 2024 08:16:27 -0700 Subject: [PATCH 206/206] Deprecate Pagure repo (#579) --- Doc/contributing.rst | 7 +------ Doc/spelling_wordlist.txt | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 6ef8a5a8..de63a2e3 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -72,9 +72,6 @@ If you're used to open-source Python development with Git, here's the gist: .. _the bug tracker: https://github.com/python-ldap/python-ldap/issues .. _tox: https://tox.readthedocs.io/en/latest/ -Or, if you prefer to avoid closed-source services: - -* ``git clone https://pagure.io/python-ldap`` * Send bug reports and patches to the mailing list. * Run tests with `tox`_; ignore Python interpreters you don't have locally. * Read the documentation directly at `Read the Docs`_. @@ -203,8 +200,6 @@ remember: * Consider making the summary line suitable for the CHANGES document, and starting it with a prefix like ``Lib:`` or ``Tests:``. -* Push to Pagure as well. - If you have good reason to break the “rules”, go ahead and break them, but mention why. @@ -224,7 +219,7 @@ If you are tasked with releasing python-ldap, remember to: * Run ``python setup.py sdist``, and smoke-test the resulting package (install in a clean virtual environment, import ``ldap``). * Create GPG-signed Git tag: ``git tag -s python-ldap-{version}``. - Push it to GitHub and Pagure. + Push it to GitHub. * Release the ``sdist`` on PyPI. * Announce the release on the mailing list. Mention the Git hash. diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 8cdd9f16..e2150d9a 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -100,7 +100,6 @@ oc oid oids OpenLDAP -Pagure postalAddress pre previousDN