From 875ef440682cb3e5ccc6cc9513a4bf2f1d9b72b4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 27 Nov 2017 14:40:47 +0100 Subject: [PATCH 001/369] Fix syntax of find -delete With -delete, the matches must be grouped. Otherwise -delete is only executed for the last matcher. https://github.com/python-ldap/python-ldap/pull/27 Signed-off-by: Christian Heimes --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 770620a9..47fac901 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,8 @@ all: clean: rm -rf build dist *.egg-info $(VENV) .tox MANIFEST rm -f .coverage .coverage.* - find . -name '*.py[co]' -or -name '*.so*' -or -name '*.dylib' -delete + find . \( -name '*.py[co]' -or -name '*.so*' -or -name '*.dylib' \) \ + -delete find . -depth -name __pycache__ -exec rm -rf {} \; # LCOV report (measuring test coverage for C code) From 4aa0d36a12a7275d4d9a28e1b3894c768789ce7c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 27 Nov 2017 14:49:29 +0100 Subject: [PATCH 002/369] Use `package` to distribute ldap sub-package Instead of listing all Python files, use distutils' and setuptools' `package` direcective to ship the ldap. ldap.controls, ldap.extop, and ldap.schema packages. The modules ldapurl, ldif, and slaptest are not packages and therefore still use `py_module`. The `ldap.controls.pagedresults` module is now shipped and installed. https://github.com/python-ldap/python-ldap/pull/29 Closes: #26 Signed-off-by: Christian Heimes --- setup.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/setup.py b/setup.py index e47d2158..2ac63ecf 100644 --- a/setup.py +++ b/setup.py @@ -165,39 +165,13 @@ class OpenLDAP2: py_modules = [ 'ldapurl', 'ldif', - 'ldap', 'slapdtest', - 'ldap.async', - 'ldap.compat', + ], + packages = [ + 'ldap', 'ldap.controls', - 'ldap.controls.deref', - 'ldap.controls.libldap', - 'ldap.controls.openldap', - 'ldap.controls.ppolicy', - 'ldap.controls.psearch', - 'ldap.controls.pwdpolicy', - 'ldap.controls.readentry', - 'ldap.controls.sessiontrack', - 'ldap.controls.simple', - 'ldap.controls.sss', - 'ldap.controls.vlv', - 'ldap.cidict', - 'ldap.dn', 'ldap.extop', - 'ldap.extop.dds', - 'ldap.filter', - 'ldap.functions', - 'ldap.ldapobject', - 'ldap.logger', - 'ldap.modlist', - 'ldap.pkginfo', - 'ldap.resiter', - 'ldap.sasl', 'ldap.schema', - 'ldap.schema.models', - 'ldap.schema.subentry', - 'ldap.schema.tokenizer', - 'ldap.syncrepl', ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, From a72aaace0c4bd1085d23bb740fbc1183c7a16aba Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 27 Nov 2017 12:14:04 +0100 Subject: [PATCH 003/369] Fix multiple ref leaks when raising TypeError Multiple functions had a reference leak while raising a TypeError with Py_BuildValue(). The CAPI function Py_BuildValue() returns a new reference. PyErr_SetObject() does NOT consume the reference. Instead it increments the reference count of the object again. A new function LDAPerror_TypeError() handles raising TypeError with custom message and object. https://github.com/python-ldap/python-ldap/pull/31/files Closes: #24 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 31 +++++++++++-------------------- Modules/errors.c | 24 ++++++++++++++++++++---- Modules/errors.h | 1 + Modules/ldapcontrol.c | 12 ++++-------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index ce0ff52a..3e084737 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -113,9 +113,7 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) Py_ssize_t i, len, nstrs; if (!PyTuple_Check(tup)) { - PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected a tuple", tup)); - return NULL; + return LDAPerror_TypeError("expected a tuple", tup); } if (no_op) { @@ -167,9 +165,7 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) if (item == NULL) goto error; if (!PyBytes_Check(item)) { - PyErr_SetObject( PyExc_TypeError, Py_BuildValue( "sO", - "expected a byte string in the list", item)); - Py_DECREF(item); + LDAPerror_TypeError("expected a byte string in the list", item); goto error; } lm->mod_bvalues[i]->bv_len = PyBytes_Size(item); @@ -214,17 +210,13 @@ List_to_LDAPMods( PyObject *list, int no_op ) { PyObject *item; if (!PySequence_Check(list)) { - PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO", - "expected list of tuples", list )); - return NULL; + return LDAPerror_TypeError("expected list of tuples", list); } len = PySequence_Length(list); if (len < 0) { - PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO", - "expected list of tuples", list )); - return NULL; + return LDAPerror_TypeError("expected list of tuples", list); } lms = PyMem_NEW(LDAPMod *, len + 1); @@ -274,8 +266,8 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { } else if (PyUnicode_Check(attrlist)) { #endif /* caught by John Benninghoff */ - PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO", - "expected *list* of strings, not a string", attrlist )); + LDAPerror_TypeError( + "expected *list* of strings, not a string", attrlist); goto error; } else { *seq = PySequence_Fast(attrlist, "expected list of strings or None"); @@ -296,16 +288,15 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { #if PY_MAJOR_VERSION == 2 /* Encoded by Python to UTF-8 */ if (!PyBytes_Check(item)) { -#else - if (!PyUnicode_Check(item)) { -#endif - PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected string in list", item)); + LDAPerror_TypeError("expected bytes in list", item); goto error; } -#if PY_MAJOR_VERSION == 2 attrs[i] = PyBytes_AsString(item); #else + if (!PyUnicode_Check(item)) { + LDAPerror_TypeError("expected string in list", item); + goto error; + } attrs[i] = PyUnicode_AsUTF8(item); #endif } diff --git a/Modules/errors.c b/Modules/errors.c index e5cb0ee8..3c4da78a 100644 --- a/Modules/errors.c +++ b/Modules/errors.c @@ -36,11 +36,15 @@ 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) { PyErr_SetNone(errobjects[errnum+LDAP_ERROR_OFFSET]); - else - PyErr_SetObject(LDAPexception_class, - Py_BuildValue("{s:i}", "errnum", errnum)); + } else { + PyObject *args = Py_BuildValue("{s:i}", "errnum", errnum); + if (args == NULL) + return NULL; + PyErr_SetObject(LDAPexception_class, args); + Py_DECREF(args); + } return NULL; } @@ -123,6 +127,18 @@ LDAPerror( LDAP *l, char *msg ) } } +/* Raise TypeError with custom message and object */ +PyObject* +LDAPerror_TypeError(const char *msg, PyObject *obj) { + PyObject *args = Py_BuildValue("sO", msg, obj); + if (args == NULL) { + return NULL; + } + PyErr_SetObject(PyExc_TypeError, args); + Py_DECREF(args); + return NULL; +} + /* initialisation */ diff --git a/Modules/errors.h b/Modules/errors.h index 32aebffe..5bd3576a 100644 --- a/Modules/errors.h +++ b/Modules/errors.h @@ -9,6 +9,7 @@ extern PyObject* LDAPexception_class; extern PyObject* LDAPerror( LDAP*, char*msg ); +extern PyObject* LDAPerror_TypeError(const char *, PyObject *); extern void LDAPinit_errors( PyObject* ); PyObject* LDAPerr(int errnum); diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 6ff27c55..7e31f720 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -72,9 +72,7 @@ Tuple_to_LDAPControl( PyObject* tup ) Py_ssize_t len; if (!PyTuple_Check(tup)) { - PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected a tuple", tup)); - return NULL; + return LDAPerror_TypeError("expected a tuple", tup); } if (!PyArg_ParseTuple( tup, "sbO", &oid, &iscritical, &bytes )) @@ -107,8 +105,7 @@ Tuple_to_LDAPControl( PyObject* tup ) berbytes.bv_val = PyBytes_AsString(bytes); } else { - PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected bytes", bytes)); + LDAPerror_TypeError("expected bytes", bytes); LDAPControl_DEL(lc); return NULL; } @@ -130,9 +127,8 @@ LDAPControls_from_object(PyObject* list, LDAPControl ***controls_ret) PyObject* item; if (!PySequence_Check(list)) { - PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected a list", list)); - return 0; + LDAPerror_TypeError("expected a list", list); + return 0; } len = PySequence_Length(list); From 8807e7554410fb39e44da1320a372a81f2f2acbf Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 22:10:34 +0100 Subject: [PATCH 004/369] Modules: Remove _ldap._forward and _ldap._reverse These were created only as: forward = {} reverse = {0: None} The only use was the function LDAPconstant, equivalent to: def LDAPconstant(i): return reverse.get(i, i) It looks like these were prepared for a better future, but python-ldap has gone for years or decades without needing to actually use them. Remove both dictionaries and the function. --- Modules/LDAPObject.c | 7 ++++++- Modules/constants.c | 29 +---------------------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 3e084737..a4349869 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1059,7 +1059,12 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) pmsg = LDAPmessage_to_python( self->ldap, msg, add_ctrls, add_intermediates ); - result_str = LDAPconstant( res_type ); + if (res_type == 0) { + result_str = Py_None; + Py_INCREF(Py_None); + } else { + result_str = PyInt_FromLong( res_type ); + } if (pmsg == NULL) { retval = NULL; diff --git a/Modules/constants.c b/Modules/constants.c index 7ed9e418..cd3877cd 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -6,35 +6,12 @@ #include "lber.h" #include "ldap.h" -static PyObject* reverse; -static PyObject* forward; - -/* convert an result integer into a Python string */ - -PyObject* -LDAPconstant( int val ) { - PyObject *i = PyInt_FromLong( val ); - PyObject *s = PyObject_GetItem( reverse, i ); - if (s == NULL) { - PyErr_Clear(); - return i; - } - Py_DECREF(i); - return s; -} - /* initialise the module constants */ void LDAPinit_constants( PyObject* d ) { - PyObject *zero, *author,*obj; - - reverse = PyDict_New(); - forward = PyDict_New(); - - PyDict_SetItemString( d, "_reverse", reverse ); - PyDict_SetItemString( d, "_forward", forward ); + PyObject *obj; #define add_int(d, name) \ { \ @@ -91,10 +68,6 @@ LDAPinit_constants( PyObject* d ) /* reversibles */ - zero = PyInt_FromLong( 0 ); - PyDict_SetItem( reverse, zero, Py_None ); - Py_DECREF( zero ); - add_int(d,RES_BIND); add_int(d,RES_SEARCH_ENTRY); add_int(d,RES_SEARCH_RESULT); From 48a924117b3abe53a98c70d6791488b1ebafced0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 27 Nov 2017 16:16:35 +0100 Subject: [PATCH 005/369] Refactor: Keep information about OpenLDAP constants in Python, generate C Modules/errors.{c,h} are merged into Modules/constants.{c,h} The function LDAPerror_TypeError is moved to common.{c,h}, as it's not concerned with LDAPError. Add a new Python module, ldap.constants, to keep information about all OpenLDAP constants that we know about, including those that aren't available in the OpenLDAP used. Generate a C header file, Modules/constants_generated.h, from this information. (Checking generated files into Git is better avoided, but it's much more straightforward than generating the file from setup.py.) Use proper error checking when adding the constants. --- Lib/ldap/constants.py | 402 +++++++++++++++++++++++++++++ Modules/LDAPObject.c | 1 - Modules/common.c | 12 + Modules/common.h | 2 + Modules/constants.c | 470 +++++++++++----------------------- Modules/constants.h | 9 +- Modules/constants_generated.h | 366 ++++++++++++++++++++++++++ Modules/errors.c | 249 ------------------ Modules/errors.h | 16 -- Modules/functions.c | 2 +- Modules/ldapcontrol.c | 1 - Modules/ldapmodule.c | 7 +- Modules/message.c | 1 - Modules/options.c | 2 +- setup.py | 1 - 15 files changed, 950 insertions(+), 591 deletions(-) create mode 100644 Lib/ldap/constants.py create mode 100644 Modules/constants_generated.h delete mode 100644 Modules/errors.c delete mode 100644 Modules/errors.h diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py new file mode 100644 index 00000000..6469a302 --- /dev/null +++ b/Lib/ldap/constants.py @@ -0,0 +1,402 @@ +"""Definitions for constants exported by OpenLDAP + +This file lists all constants we know about, even those that aren't +available in the OpenLDAP version python-ldap is compiled against. + +The information serves two purposes: + +- Generate a C header with the constants +- Provide support for building documentation without compiling python-ldap + +""" + +from __future__ import print_function + +class Constant(object): + """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) + requirements = list(requirements) + [self_requirement] + self.requirements = requirements + self.doc = self.__doc__ = doc + + +class Error(Constant): + """Definition for an OpenLDAP error code + + This is a constant at the C level; in Python errors are provided as + exception classes. + """ + + c_template = 'add_err({self.name});' + + +class Int(Constant): + """Definition for an OpenLDAP integer constant""" + + c_template = 'add_int({self.name});' + + +class TLSInt(Int): + """Definition for a TLS integer constant -- requires HAVE_TLS""" + + def __init__(self, *args, **kwargs): + requrements = list(kwargs.get('requirements', ())) + kwargs['requirements'] = ['HAVE_TLS'] + requrements + super(TLSInt, self).__init__(*args, **kwargs) + + +class Feature(Constant): + """Definition for a feature: 0 or 1 based on a C #ifdef + + """ + + c_template = '\n'.join([ + '', + '#ifdef {self.c_feature}', + 'result = PyModule_AddIntConstant(m, "{self.name}", 1);', + '#else', + 'result = PyModule_AddIntConstant(m, "{self.name}", 0);', + '#endif', + 'check_result();', + '', + ]) + + + def __init__(self, name, c_feature, **kwargs): + super(Feature, self).__init__(name, **kwargs) + self.c_feature = c_feature + + +class Str(Constant): + c_template = 'add_string({self.name});' + + +API_2004 = 'LDAP_API_VERSION >= 2004' + +CONSTANTS = ( + Error('ADMINLIMIT_EXCEEDED'), + Error('AFFECTS_MULTIPLE_DSAS'), + Error('ALIAS_DEREF_PROBLEM'), + Error('ALIAS_PROBLEM'), + Error('ALREADY_EXISTS'), + Error('AUTH_METHOD_NOT_SUPPORTED'), + Error('AUTH_UNKNOWN'), + Error('BUSY'), + Error('CLIENT_LOOP'), + Error('COMPARE_FALSE'), + Error('COMPARE_TRUE'), + Error('CONFIDENTIALITY_REQUIRED'), + Error('CONNECT_ERROR'), + Error('CONSTRAINT_VIOLATION'), + Error('CONTROL_NOT_FOUND'), + Error('DECODING_ERROR'), + Error('ENCODING_ERROR'), + Error('FILTER_ERROR'), + Error('INAPPROPRIATE_AUTH'), + Error('INAPPROPRIATE_MATCHING'), + Error('INSUFFICIENT_ACCESS'), + Error('INVALID_CREDENTIALS'), + Error('INVALID_DN_SYNTAX'), + Error('INVALID_SYNTAX'), + Error('IS_LEAF'), + Error('LOCAL_ERROR'), + Error('LOOP_DETECT'), + Error('MORE_RESULTS_TO_RETURN'), + Error('NAMING_VIOLATION'), + Error('NO_MEMORY'), + Error('NO_OBJECT_CLASS_MODS'), + Error('NO_OBJECT_CLASS_MODS'), + Error('NO_RESULTS_RETURNED'), + Error('NO_SUCH_ATTRIBUTE'), + Error('NO_SUCH_OBJECT'), + Error('NOT_ALLOWED_ON_NONLEAF'), + Error('NOT_ALLOWED_ON_RDN'), + Error('NOT_SUPPORTED'), + Error('OBJECT_CLASS_VIOLATION'), + Error('OPERATIONS_ERROR'), + Error('OTHER'), + Error('PARAM_ERROR'), + Error('PARTIAL_RESULTS'), + Error('PROTOCOL_ERROR'), + Error('REFERRAL'), + Error('REFERRAL_LIMIT_EXCEEDED'), + Error('RESULTS_TOO_LARGE'), + Error('SASL_BIND_IN_PROGRESS'), + Error('SERVER_DOWN'), + Error('SIZELIMIT_EXCEEDED'), + Error('STRONG_AUTH_NOT_SUPPORTED'), + Error('STRONG_AUTH_REQUIRED'), + Error('SUCCESS'), + Error('TIMELIMIT_EXCEEDED'), + Error('TIMEOUT'), + Error('TYPE_OR_VALUE_EXISTS'), + Error('UNAVAILABLE'), + Error('UNAVAILABLE_CRITICAL_EXTENSION'), + Error('UNDEFINED_TYPE'), + Error('UNWILLING_TO_PERFORM'), + Error('USER_CANCELLED'), + Error('VLV_ERROR'), + Error('X_PROXY_AUTHZ_FAILURE'), + + Error('CANCELLED', requirements=['defined(LDAP_API_FEATURE_CANCEL)']), + Error('NO_SUCH_OPERATION', requirements=['defined(LDAP_API_FEATURE_CANCEL)']), + Error('TOO_LATE', requirements=['defined(LDAP_API_FEATURE_CANCEL)']), + Error('CANNOT_CANCEL', requirements=['defined(LDAP_API_FEATURE_CANCEL)']), + + Error('ASSERTION_FAILED', optional=True), + + Error('PROXIED_AUTHORIZATION_DENIED', optional=True), + + # simple constants + + Int('API_VERSION'), + Int('VENDOR_VERSION'), + + Int('PORT'), + Int('VERSION1'), + Int('VERSION2'), + Int('VERSION3'), + Int('VERSION_MIN'), + Int('VERSION'), + Int('VERSION_MAX'), + Int('TAG_MESSAGE'), + Int('TAG_MSGID'), + + Int('REQ_BIND'), + Int('REQ_UNBIND'), + Int('REQ_SEARCH'), + Int('REQ_MODIFY'), + Int('REQ_ADD'), + Int('REQ_DELETE'), + Int('REQ_MODRDN'), + Int('REQ_COMPARE'), + Int('REQ_ABANDON'), + + Int('TAG_LDAPDN'), + Int('TAG_LDAPCRED'), + Int('TAG_CONTROLS'), + Int('TAG_REFERRAL'), + + Int('REQ_EXTENDED'), + Int('TAG_NEWSUPERIOR', requirements=[API_2004]), + Int('TAG_EXOP_REQ_OID', requirements=[API_2004]), + Int('TAG_EXOP_REQ_VALUE', requirements=[API_2004]), + Int('TAG_EXOP_RES_OID', requirements=[API_2004]), + Int('TAG_EXOP_RES_VALUE', requirements=[API_2004]), + Int('TAG_SASL_RES_CREDS', requirements=[API_2004, 'defined(HAVE_SASL)']), + + Int('SASL_AUTOMATIC'), + Int('SASL_INTERACTIVE'), + Int('SASL_QUIET'), + + # reversibles + + Int('RES_BIND'), + Int('RES_SEARCH_ENTRY'), + Int('RES_SEARCH_RESULT'), + Int('RES_MODIFY'), + Int('RES_ADD'), + Int('RES_DELETE'), + Int('RES_MODRDN'), + Int('RES_COMPARE'), + Int('RES_ANY'), + + Int('RES_SEARCH_REFERENCE'), + Int('RES_EXTENDED'), + Int('RES_UNSOLICITED'), + + Int('RES_INTERMEDIATE'), + + # non-reversibles + + Int('AUTH_NONE'), + Int('AUTH_SIMPLE'), + Int('SCOPE_BASE'), + Int('SCOPE_ONELEVEL'), + Int('SCOPE_SUBTREE'), + Int('SCOPE_SUBORDINATE', optional=True), + Int('MOD_ADD'), + Int('MOD_DELETE'), + Int('MOD_REPLACE'), + Int('MOD_INCREMENT'), + Int('MOD_BVALUES'), + + Int('MSG_ONE'), + Int('MSG_ALL'), + Int('MSG_RECEIVED'), + + # (error constants handled above) + + Int('DEREF_NEVER'), + Int('DEREF_SEARCHING'), + Int('DEREF_FINDING'), + Int('DEREF_ALWAYS'), + Int('NO_LIMIT'), + + Int('OPT_API_INFO'), + Int('OPT_DEREF'), + Int('OPT_SIZELIMIT'), + Int('OPT_TIMELIMIT'), + Int('OPT_REFERRALS', optional=True), + Int('OPT_ERROR_NUMBER'), + Int('OPT_RESTART'), + Int('OPT_PROTOCOL_VERSION'), + Int('OPT_SERVER_CONTROLS'), + Int('OPT_CLIENT_CONTROLS'), + Int('OPT_API_FEATURE_INFO'), + Int('OPT_HOST_NAME'), + + Int('OPT_DESC'), + Int('OPT_DIAGNOSTIC_MESSAGE'), + + Int('OPT_ERROR_STRING'), + Int('OPT_MATCHED_DN'), + Int('OPT_DEBUG_LEVEL'), + Int('OPT_TIMEOUT'), + Int('OPT_REFHOPLIMIT'), + Int('OPT_NETWORK_TIMEOUT'), + Int('OPT_URI'), + + 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'), + TLSInt('OPT_X_TLS_KEYFILE'), + TLSInt('OPT_X_TLS_REQUIRE_CERT'), + TLSInt('OPT_X_TLS_CIPHER_SUITE'), + TLSInt('OPT_X_TLS_RANDOM_FILE'), + TLSInt('OPT_X_TLS_DHFILE'), + TLSInt('OPT_X_TLS_NEVER'), + TLSInt('OPT_X_TLS_HARD'), + 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), + TLSInt('OPT_X_TLS_PEERCERT', optional=True), + + # only available if OpenSSL supports it => might cause + # backward compability problems + TLSInt('OPT_X_TLS_CRLCHECK', optional=True), + + TLSInt('OPT_X_TLS_CRLFILE', optional=True), + + TLSInt('OPT_X_TLS_CRL_NONE'), + TLSInt('OPT_X_TLS_CRL_PEER'), + TLSInt('OPT_X_TLS_CRL_ALL'), + TLSInt('OPT_X_TLS_NEWCTX', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_MIN', optional=True), + TLSInt('OPT_X_TLS_PACKAGE', optional=True), + + Int('OPT_X_SASL_MECH'), + Int('OPT_X_SASL_REALM'), + Int('OPT_X_SASL_AUTHCID'), + Int('OPT_X_SASL_AUTHZID'), + Int('OPT_X_SASL_SSF'), + Int('OPT_X_SASL_SSF_EXTERNAL'), + Int('OPT_X_SASL_SECPROPS'), + Int('OPT_X_SASL_SSF_MIN'), + Int('OPT_X_SASL_SSF_MAX'), + Int('OPT_X_SASL_NOCANON', optional=True), + Int('OPT_X_SASL_USERNAME', optional=True), + Int('OPT_CONNECT_ASYNC', optional=True), + Int('OPT_X_KEEPALIVE_IDLE', optional=True), + Int('OPT_X_KEEPALIVE_PROBES', optional=True), + Int('OPT_X_KEEPALIVE_INTERVAL', optional=True), + + Int('DN_FORMAT_LDAP'), + Int('DN_FORMAT_LDAPV3'), + Int('DN_FORMAT_LDAPV2'), + Int('DN_FORMAT_DCE'), + Int('DN_FORMAT_UFN'), + Int('DN_FORMAT_AD_CANONICAL'), + # Int('DN_FORMAT_LBER'), # for testing only + Int('DN_FORMAT_MASK'), + Int('DN_PRETTY'), + Int('DN_SKIP'), + Int('DN_P_NOLEADTRAILSPACES'), + Int('DN_P_NOSPACEAFTERRDN'), + Int('DN_PEDANTIC'), + + Int('AVA_NULL'), + Int('AVA_STRING'), + Int('AVA_BINARY'), + Int('AVA_NONPRINTABLE'), + + Int('OPT_SUCCESS'), + + # 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'), + + Str("CONTROL_MANAGEDSAIT"), + Str("CONTROL_PROXY_AUTHZ"), + Str("CONTROL_SUBENTRIES"), + Str("CONTROL_VALUESRETURNFILTER"), + Str("CONTROL_ASSERT"), + Str("CONTROL_PRE_READ"), + Str("CONTROL_POST_READ"), + Str("CONTROL_SORTREQUEST"), + Str("CONTROL_SORTRESPONSE"), + Str("CONTROL_PAGEDRESULTS"), + Str("CONTROL_SYNC"), + Str("CONTROL_SYNC_STATE"), + Str("CONTROL_SYNC_DONE"), + Str("SYNC_INFO"), + Str("CONTROL_PASSWORDPOLICYREQUEST"), + Str("CONTROL_PASSWORDPOLICYRESPONSE"), + Str("CONTROL_RELAX"), +) + + +def print_header(): + """Print the C header file to standard output""" + + print('/*') + print(' * Generated with:') + print(' * python Lib/ldap/constants.py > Modules/constants_generated.h') + print(' *') + print(' * Please do any modifications there, then re-generate this file') + print(' */') + print('') + + current_requirements = [] + + def pop_requirement(): + popped = current_requirements.pop() + print('#endif') + print() + + for definition in CONSTANTS: + while not set(current_requirements).issubset(definition.requirements): + pop_requirement() + + for requirement in definition.requirements: + if requirement not in current_requirements: + current_requirements.append(requirement) + print() + print('#if {}'.format(requirement)) + + print(definition.c_template.format(self=definition)) + + while current_requirements: + pop_requirement() + + +if __name__ == '__main__': + print_header() diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index a4349869..f1ad0d44 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -5,7 +5,6 @@ #include #include -#include "errors.h" #include "constants.h" #include "LDAPObject.h" #include "ldapcontrol.h" diff --git a/Modules/common.c b/Modules/common.c index 7d3ac8b3..0f0cd36a 100644 --- a/Modules/common.c +++ b/Modules/common.c @@ -16,3 +16,15 @@ LDAPadd_methods( PyObject* d, PyMethodDef* methods ) Py_DECREF(f); } } + +/* Raise TypeError with custom message and object */ +PyObject* +LDAPerror_TypeError(const char *msg, PyObject *obj) { + PyObject *args = Py_BuildValue("sO", msg, obj); + if (args == NULL) { + return NULL; + } + PyErr_SetObject(PyExc_TypeError, args); + Py_DECREF(args); + return NULL; +} diff --git a/Modules/common.h b/Modules/common.h index 0eea1d92..029f234f 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -24,6 +24,8 @@ #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) diff --git a/Modules/constants.c b/Modules/constants.c index cd3877cd..4fd09b85 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -6,346 +6,184 @@ #include "lber.h" #include "ldap.h" -/* initialise the module constants */ - -void -LDAPinit_constants( PyObject* d ) -{ - PyObject *obj; +/* the base exception class */ -#define add_int(d, name) \ - { \ - PyObject *i = PyInt_FromLong(LDAP_##name); \ - PyDict_SetItemString( d, #name, i ); \ - Py_DECREF(i); \ - } +PyObject* +LDAPexception_class; - /* simple constants */ - - add_int(d,API_VERSION); - add_int(d,VENDOR_VERSION); - - add_int(d,PORT); - add_int(d,VERSION1); - add_int(d,VERSION2); - add_int(d,VERSION3); - add_int(d,VERSION_MIN); - add_int(d,VERSION); - add_int(d,VERSION_MAX); - add_int(d,TAG_MESSAGE); - add_int(d,TAG_MSGID); - - add_int(d,REQ_BIND); - add_int(d,REQ_UNBIND); - add_int(d,REQ_SEARCH); - add_int(d,REQ_MODIFY); - add_int(d,REQ_ADD); - add_int(d,REQ_DELETE); - add_int(d,REQ_MODRDN); - add_int(d,REQ_COMPARE); - add_int(d,REQ_ABANDON); - - add_int(d,TAG_LDAPDN); - add_int(d,TAG_LDAPCRED); - add_int(d,TAG_CONTROLS); - add_int(d,TAG_REFERRAL); - - add_int(d,REQ_EXTENDED); -#if LDAP_API_VERSION >= 2004 - add_int(d,TAG_NEWSUPERIOR); - add_int(d,TAG_EXOP_REQ_OID); - add_int(d,TAG_EXOP_REQ_VALUE); - add_int(d,TAG_EXOP_RES_OID); - add_int(d,TAG_EXOP_RES_VALUE); -#ifdef HAVE_SASL - add_int(d,TAG_SASL_RES_CREDS); -#endif -#endif +/* list of exception classes */ - add_int(d,SASL_AUTOMATIC); - add_int(d,SASL_INTERACTIVE); - add_int(d,SASL_QUIET); - - /* reversibles */ - - add_int(d,RES_BIND); - add_int(d,RES_SEARCH_ENTRY); - add_int(d,RES_SEARCH_RESULT); - add_int(d,RES_MODIFY); - add_int(d,RES_ADD); - add_int(d,RES_DELETE); - add_int(d,RES_MODRDN); - add_int(d,RES_COMPARE); - add_int(d,RES_ANY); - - add_int(d,RES_SEARCH_REFERENCE); - add_int(d,RES_EXTENDED); - add_int(d,RES_UNSOLICITED); - - add_int(d,RES_INTERMEDIATE); - - /* non-reversibles */ - - add_int(d,AUTH_NONE); - add_int(d,AUTH_SIMPLE); - add_int(d,SCOPE_BASE); - add_int(d,SCOPE_ONELEVEL); - add_int(d,SCOPE_SUBTREE); -#ifdef LDAP_SCOPE_SUBORDINATE - add_int(d,SCOPE_SUBORDINATE); -#endif - add_int(d,MOD_ADD); - add_int(d,MOD_DELETE); - add_int(d,MOD_REPLACE); - add_int(d,MOD_INCREMENT); - add_int(d,MOD_BVALUES); - - add_int(d,MSG_ONE); - add_int(d,MSG_ALL); - add_int(d,MSG_RECEIVED); - - /* (errors.c contains the error constants) */ - - add_int(d,DEREF_NEVER); - add_int(d,DEREF_SEARCHING); - add_int(d,DEREF_FINDING); - add_int(d,DEREF_ALWAYS); - add_int(d,NO_LIMIT); - - add_int(d,OPT_API_INFO); - add_int(d,OPT_DEREF); - add_int(d,OPT_SIZELIMIT); - add_int(d,OPT_TIMELIMIT); -#ifdef LDAP_OPT_REFERRALS - add_int(d,OPT_REFERRALS); -#endif - add_int(d,OPT_ERROR_NUMBER); - add_int(d,OPT_RESTART); - add_int(d,OPT_PROTOCOL_VERSION); - add_int(d,OPT_SERVER_CONTROLS); - add_int(d,OPT_CLIENT_CONTROLS); - add_int(d,OPT_API_FEATURE_INFO); - add_int(d,OPT_HOST_NAME); - - add_int(d,OPT_DESC); - add_int(d,OPT_DIAGNOSTIC_MESSAGE); - - add_int(d,OPT_ERROR_STRING); - add_int(d,OPT_MATCHED_DN); - add_int(d,OPT_DEBUG_LEVEL); - add_int(d,OPT_TIMEOUT); - add_int(d,OPT_REFHOPLIMIT); - add_int(d,OPT_NETWORK_TIMEOUT); - add_int(d,OPT_URI); -#ifdef LDAP_OPT_DEFBASE - add_int(d,OPT_DEFBASE); -#endif -#ifdef HAVE_TLS - add_int(d,OPT_X_TLS); -#ifdef LDAP_OPT_X_TLS_NEWCTX - add_int(d,OPT_X_TLS_CTX); -#endif - add_int(d,OPT_X_TLS_CACERTFILE); - add_int(d,OPT_X_TLS_CACERTDIR); - add_int(d,OPT_X_TLS_CERTFILE); - add_int(d,OPT_X_TLS_KEYFILE); - add_int(d,OPT_X_TLS_REQUIRE_CERT); - add_int(d,OPT_X_TLS_CIPHER_SUITE); - add_int(d,OPT_X_TLS_RANDOM_FILE); - add_int(d,OPT_X_TLS_DHFILE); - add_int(d,OPT_X_TLS_NEVER); - add_int(d,OPT_X_TLS_HARD); - add_int(d,OPT_X_TLS_DEMAND); - add_int(d,OPT_X_TLS_ALLOW); - add_int(d,OPT_X_TLS_TRY); -#ifdef LDAP_OPT_X_TLS_PEERCERT - add_int(d,OPT_X_TLS_PEERCERT); -#endif -#ifdef LDAP_OPT_X_TLS_VERSION - add_int(d,OPT_X_TLS_VERSION); -#endif -#ifdef LDAP_OPT_X_TLS_CIPHER - add_int(d,OPT_X_TLS_CIPHER); -#endif -#ifdef LDAP_OPT_X_TLS_PEERCERT - add_int(d,OPT_X_TLS_PEERCERT); -#endif -#ifdef LDAP_OPT_X_TLS_CRLCHECK - /* only available if OpenSSL supports it => might cause backward compability problems */ - add_int(d,OPT_X_TLS_CRLCHECK); -#ifdef LDAP_OPT_X_TLS_CRLFILE - add_int(d,OPT_X_TLS_CRLFILE); -#endif - add_int(d,OPT_X_TLS_CRL_NONE); - add_int(d,OPT_X_TLS_CRL_PEER); - add_int(d,OPT_X_TLS_CRL_ALL); -#endif -#ifdef LDAP_OPT_X_TLS_NEWCTX - add_int(d,OPT_X_TLS_NEWCTX); -#endif -#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN - add_int(d,OPT_X_TLS_PROTOCOL_MIN); -#endif -#ifdef LDAP_OPT_X_TLS_PACKAGE - add_int(d,OPT_X_TLS_PACKAGE); -#endif -#endif - add_int(d,OPT_X_SASL_MECH); - add_int(d,OPT_X_SASL_REALM); - add_int(d,OPT_X_SASL_AUTHCID); - add_int(d,OPT_X_SASL_AUTHZID); - add_int(d,OPT_X_SASL_SSF); - add_int(d,OPT_X_SASL_SSF_EXTERNAL); - add_int(d,OPT_X_SASL_SECPROPS); - add_int(d,OPT_X_SASL_SSF_MIN); - add_int(d,OPT_X_SASL_SSF_MAX); -#ifdef LDAP_OPT_X_SASL_NOCANON - add_int(d,OPT_X_SASL_NOCANON); -#endif -#ifdef LDAP_OPT_X_SASL_USERNAME - add_int(d,OPT_X_SASL_USERNAME); -#endif -#ifdef LDAP_OPT_CONNECT_ASYNC - add_int(d,OPT_CONNECT_ASYNC); -#endif -#ifdef LDAP_OPT_X_KEEPALIVE_IDLE - add_int(d,OPT_X_KEEPALIVE_IDLE); -#endif -#ifdef LDAP_OPT_X_KEEPALIVE_PROBES - add_int(d,OPT_X_KEEPALIVE_PROBES); -#endif -#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL - add_int(d,OPT_X_KEEPALIVE_INTERVAL); -#endif +#define LDAP_ERROR_MIN LDAP_REFERRAL_LIMIT_EXCEEDED - add_int(d,DN_FORMAT_LDAP); - add_int(d,DN_FORMAT_LDAPV3); - add_int(d,DN_FORMAT_LDAPV2); - add_int(d,DN_FORMAT_DCE); - add_int(d,DN_FORMAT_UFN); - add_int(d,DN_FORMAT_AD_CANONICAL); - /* add_int(d,DN_FORMAT_LBER); */ /* "for testing only" */ - add_int(d,DN_FORMAT_MASK); - add_int(d,DN_PRETTY); - add_int(d,DN_SKIP); - add_int(d,DN_P_NOLEADTRAILSPACES); - add_int(d,DN_P_NOSPACEAFTERRDN); - add_int(d,DN_PEDANTIC); - - add_int(d,AVA_NULL); - add_int(d,AVA_STRING); - add_int(d,AVA_BINARY); - add_int(d,AVA_NONPRINTABLE); - - /*add_int(d,OPT_ON);*/ - obj = PyInt_FromLong(1); - PyDict_SetItemString( d, "OPT_ON", obj ); - Py_DECREF(obj); - /*add_int(d,OPT_OFF);*/ - obj = PyInt_FromLong(0); - PyDict_SetItemString( d, "OPT_OFF", obj ); - Py_DECREF(obj); - - add_int(d,OPT_SUCCESS); - - /* XXX - these belong in errors.c */ - - add_int(d,URL_ERR_BADSCOPE); - add_int(d,URL_ERR_MEM); - - /* add_int(d,LIBLDAP_R); */ -#ifdef HAVE_LIBLDAP_R - obj = PyInt_FromLong(1); +#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED + #define LDAP_ERROR_MAX LDAP_PROXIED_AUTHORIZATION_DENIED #else - obj = PyInt_FromLong(0); + #ifdef LDAP_ASSERTION_FAILED + #define LDAP_ERROR_MAX LDAP_ASSERTION_FAILED + #else + #define LDAP_ERROR_MAX LDAP_OTHER + #endif #endif - PyDict_SetItemString( d, "LIBLDAP_R", obj ); - Py_DECREF(obj); - /* add_int(d,SASL); */ -#ifdef HAVE_SASL - obj = PyInt_FromLong(1); -#else - obj = PyInt_FromLong(0); -#endif - PyDict_SetItemString( d, "SASL_AVAIL", obj ); - Py_DECREF(obj); +#define LDAP_ERROR_OFFSET -LDAP_ERROR_MIN + +static PyObject* errobjects[ LDAP_ERROR_MAX-LDAP_ERROR_MIN+1 ]; - /* add_int(d,TLS); */ -#ifdef HAVE_TLS - obj = PyInt_FromLong(1); -#else - obj = PyInt_FromLong(0); -#endif - PyDict_SetItemString( d, "TLS_AVAIL", obj ); - Py_DECREF(obj); - obj = PyUnicode_FromString(LDAP_CONTROL_MANAGEDSAIT); - PyDict_SetItemString( d, "CONTROL_MANAGEDSAIT", obj ); - Py_DECREF(obj); +/* Convert a bare LDAP error number into an exception */ +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; +} - obj = PyUnicode_FromString(LDAP_CONTROL_PROXY_AUTHZ); - PyDict_SetItemString( d, "CONTROL_PROXY_AUTHZ", obj ); - Py_DECREF(obj); +/* Convert an LDAP error into an informative python exception */ +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; + + /* at first save errno for later use before it gets overwritten by another call */ + myerrno = errno; + + char *matched, *error; + + 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 (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 ); + 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); + return NULL; + } +} - obj = PyUnicode_FromString(LDAP_CONTROL_SUBENTRIES); - PyDict_SetItemString( d, "CONTROL_SUBENTRIES", obj ); - Py_DECREF(obj); +/* initialise the module constants */ - obj = PyUnicode_FromString(LDAP_CONTROL_VALUESRETURNFILTER); - PyDict_SetItemString( d, "CONTROL_VALUESRETURNFILTER", obj ); - Py_DECREF(obj); +int +LDAPinit_constants( PyObject* m ) +{ + PyObject *exc; + int result; - obj = PyUnicode_FromString(LDAP_CONTROL_ASSERT); - PyDict_SetItemString( d, "CONTROL_ASSERT", obj ); - Py_DECREF(obj); +#define check_result() { \ + if (result != 0) return -1; \ +} - obj = PyUnicode_FromString(LDAP_CONTROL_PRE_READ); - PyDict_SetItemString( d, "CONTROL_PRE_READ", obj ); - Py_DECREF(obj); + /* simple constants */ - obj = PyUnicode_FromString(LDAP_CONTROL_POST_READ); - PyDict_SetItemString( d, "CONTROL_POST_READ", obj ); - Py_DECREF(obj); + result = PyModule_AddIntConstant(m, "OPT_ON", 1); + check_result(); + result = PyModule_AddIntConstant(m, "OPT_OFF", 0); + check_result(); - obj = PyUnicode_FromString(LDAP_CONTROL_SORTREQUEST); - PyDict_SetItemString( d, "CONTROL_SORTREQUEST", obj ); - Py_DECREF(obj); + /* exceptions */ - obj = PyUnicode_FromString(LDAP_CONTROL_SORTRESPONSE); - PyDict_SetItemString( d, "CONTROL_SORTRESPONSE", obj ); - Py_DECREF(obj); + LDAPexception_class = PyErr_NewException("ldap.LDAPError", NULL, NULL); + if (LDAPexception_class == NULL) { + return -1; + } - obj = PyUnicode_FromString(LDAP_CONTROL_PAGEDRESULTS); - PyDict_SetItemString( d, "CONTROL_PAGEDRESULTS", obj ); - Py_DECREF(obj); + result = PyModule_AddObject(m, "LDAPError", LDAPexception_class); + check_result(); + Py_INCREF(LDAPexception_class); - obj = PyUnicode_FromString(LDAP_CONTROL_SYNC); - PyDict_SetItemString( d, "CONTROL_SYNC", obj ); - Py_DECREF(obj); + /* XXX - backward compatibility with pre-1.8 */ + result = PyModule_AddObject(m, "error", LDAPexception_class); + check_result(); + Py_INCREF(LDAPexception_class); - obj = PyUnicode_FromString(LDAP_CONTROL_SYNC_STATE); - PyDict_SetItemString( d, "CONTROL_SYNC_STATE", obj ); - Py_DECREF(obj); + /* Generated constants -- see Lib/ldap/constants.py */ - obj = PyUnicode_FromString(LDAP_CONTROL_SYNC_DONE); - PyDict_SetItemString( d, "CONTROL_SYNC_DONE", obj ); - Py_DECREF(obj); +#define seterrobj2(n, o) \ + PyModule_AddObject(m, #n, (errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = o)) - obj = PyUnicode_FromString(LDAP_SYNC_INFO); - PyDict_SetItemString( d, "SYNC_INFO", obj ); - Py_DECREF(obj); +#define add_err(n) { \ + exc = PyErr_NewException("ldap." #n, LDAPexception_class, NULL); \ + if (exc == NULL) return -1; \ + result = seterrobj2(n, exc); \ + check_result(); \ + Py_INCREF(exc); \ +} - obj = PyUnicode_FromString(LDAP_CONTROL_PASSWORDPOLICYREQUEST); - PyDict_SetItemString( d, "CONTROL_PASSWORDPOLICYREQUEST", obj ); - Py_DECREF(obj); +#define add_int(n) { \ + result = PyModule_AddIntConstant(m, #n, LDAP_##n); \ + check_result(); \ +} - obj = PyUnicode_FromString(LDAP_CONTROL_PASSWORDPOLICYRESPONSE); - PyDict_SetItemString( d, "CONTROL_PASSWORDPOLICYRESPONSE", obj ); - Py_DECREF(obj); +#define add_string(n) { \ + result = PyModule_AddStringConstant(m, #n, LDAP_##n); \ + check_result(); \ +} - obj = PyUnicode_FromString(LDAP_CONTROL_RELAX); - PyDict_SetItemString( d, "CONTROL_RELAX", obj ); - Py_DECREF(obj); +#include "constants_generated.h" + return 0; } diff --git a/Modules/constants.h b/Modules/constants.h index eb766124..4056f907 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -4,9 +4,16 @@ #define __h_constants_ #include "common.h" -extern void LDAPinit_constants( PyObject* d ); +#include "lber.h" +#include "ldap.h" + +extern int LDAPinit_constants( PyObject* m ); extern PyObject* LDAPconstant( int ); +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 */ diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h new file mode 100644 index 00000000..27addc91 --- /dev/null +++ b/Modules/constants_generated.h @@ -0,0 +1,366 @@ +/* + * Generated with: + * python Lib/ldap/constants.py > Modules/constants_generated.h + * + * Please do any modifications there, then re-generate this file + */ + +add_err(ADMINLIMIT_EXCEEDED); +add_err(AFFECTS_MULTIPLE_DSAS); +add_err(ALIAS_DEREF_PROBLEM); +add_err(ALIAS_PROBLEM); +add_err(ALREADY_EXISTS); +add_err(AUTH_METHOD_NOT_SUPPORTED); +add_err(AUTH_UNKNOWN); +add_err(BUSY); +add_err(CLIENT_LOOP); +add_err(COMPARE_FALSE); +add_err(COMPARE_TRUE); +add_err(CONFIDENTIALITY_REQUIRED); +add_err(CONNECT_ERROR); +add_err(CONSTRAINT_VIOLATION); +add_err(CONTROL_NOT_FOUND); +add_err(DECODING_ERROR); +add_err(ENCODING_ERROR); +add_err(FILTER_ERROR); +add_err(INAPPROPRIATE_AUTH); +add_err(INAPPROPRIATE_MATCHING); +add_err(INSUFFICIENT_ACCESS); +add_err(INVALID_CREDENTIALS); +add_err(INVALID_DN_SYNTAX); +add_err(INVALID_SYNTAX); +add_err(IS_LEAF); +add_err(LOCAL_ERROR); +add_err(LOOP_DETECT); +add_err(MORE_RESULTS_TO_RETURN); +add_err(NAMING_VIOLATION); +add_err(NO_MEMORY); +add_err(NO_OBJECT_CLASS_MODS); +add_err(NO_OBJECT_CLASS_MODS); +add_err(NO_RESULTS_RETURNED); +add_err(NO_SUCH_ATTRIBUTE); +add_err(NO_SUCH_OBJECT); +add_err(NOT_ALLOWED_ON_NONLEAF); +add_err(NOT_ALLOWED_ON_RDN); +add_err(NOT_SUPPORTED); +add_err(OBJECT_CLASS_VIOLATION); +add_err(OPERATIONS_ERROR); +add_err(OTHER); +add_err(PARAM_ERROR); +add_err(PARTIAL_RESULTS); +add_err(PROTOCOL_ERROR); +add_err(REFERRAL); +add_err(REFERRAL_LIMIT_EXCEEDED); +add_err(RESULTS_TOO_LARGE); +add_err(SASL_BIND_IN_PROGRESS); +add_err(SERVER_DOWN); +add_err(SIZELIMIT_EXCEEDED); +add_err(STRONG_AUTH_NOT_SUPPORTED); +add_err(STRONG_AUTH_REQUIRED); +add_err(SUCCESS); +add_err(TIMELIMIT_EXCEEDED); +add_err(TIMEOUT); +add_err(TYPE_OR_VALUE_EXISTS); +add_err(UNAVAILABLE); +add_err(UNAVAILABLE_CRITICAL_EXTENSION); +add_err(UNDEFINED_TYPE); +add_err(UNWILLING_TO_PERFORM); +add_err(USER_CANCELLED); +add_err(VLV_ERROR); +add_err(X_PROXY_AUTHZ_FAILURE); + +#if defined(LDAP_API_FEATURE_CANCEL) +add_err(CANCELLED); +add_err(NO_SUCH_OPERATION); +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 + +add_int(API_VERSION); +add_int(VENDOR_VERSION); +add_int(PORT); +add_int(VERSION1); +add_int(VERSION2); +add_int(VERSION3); +add_int(VERSION_MIN); +add_int(VERSION); +add_int(VERSION_MAX); +add_int(TAG_MESSAGE); +add_int(TAG_MSGID); +add_int(REQ_BIND); +add_int(REQ_UNBIND); +add_int(REQ_SEARCH); +add_int(REQ_MODIFY); +add_int(REQ_ADD); +add_int(REQ_DELETE); +add_int(REQ_MODRDN); +add_int(REQ_COMPARE); +add_int(REQ_ABANDON); +add_int(TAG_LDAPDN); +add_int(TAG_LDAPCRED); +add_int(TAG_CONTROLS); +add_int(TAG_REFERRAL); +add_int(REQ_EXTENDED); + +#if LDAP_API_VERSION >= 2004 +add_int(TAG_NEWSUPERIOR); +add_int(TAG_EXOP_REQ_OID); +add_int(TAG_EXOP_REQ_VALUE); +add_int(TAG_EXOP_RES_OID); +add_int(TAG_EXOP_RES_VALUE); + +#if defined(HAVE_SASL) +add_int(TAG_SASL_RES_CREDS); +#endif + +#endif + +add_int(SASL_AUTOMATIC); +add_int(SASL_INTERACTIVE); +add_int(SASL_QUIET); +add_int(RES_BIND); +add_int(RES_SEARCH_ENTRY); +add_int(RES_SEARCH_RESULT); +add_int(RES_MODIFY); +add_int(RES_ADD); +add_int(RES_DELETE); +add_int(RES_MODRDN); +add_int(RES_COMPARE); +add_int(RES_ANY); +add_int(RES_SEARCH_REFERENCE); +add_int(RES_EXTENDED); +add_int(RES_UNSOLICITED); +add_int(RES_INTERMEDIATE); +add_int(AUTH_NONE); +add_int(AUTH_SIMPLE); +add_int(SCOPE_BASE); +add_int(SCOPE_ONELEVEL); +add_int(SCOPE_SUBTREE); + +#if defined(LDAP_SCOPE_SUBORDINATE) +add_int(SCOPE_SUBORDINATE); +#endif + +add_int(MOD_ADD); +add_int(MOD_DELETE); +add_int(MOD_REPLACE); +add_int(MOD_INCREMENT); +add_int(MOD_BVALUES); +add_int(MSG_ONE); +add_int(MSG_ALL); +add_int(MSG_RECEIVED); +add_int(DEREF_NEVER); +add_int(DEREF_SEARCHING); +add_int(DEREF_FINDING); +add_int(DEREF_ALWAYS); +add_int(NO_LIMIT); +add_int(OPT_API_INFO); +add_int(OPT_DEREF); +add_int(OPT_SIZELIMIT); +add_int(OPT_TIMELIMIT); + +#if defined(LDAP_OPT_REFERRALS) +add_int(OPT_REFERRALS); +#endif + +add_int(OPT_ERROR_NUMBER); +add_int(OPT_RESTART); +add_int(OPT_PROTOCOL_VERSION); +add_int(OPT_SERVER_CONTROLS); +add_int(OPT_CLIENT_CONTROLS); +add_int(OPT_API_FEATURE_INFO); +add_int(OPT_HOST_NAME); +add_int(OPT_DESC); +add_int(OPT_DIAGNOSTIC_MESSAGE); +add_int(OPT_ERROR_STRING); +add_int(OPT_MATCHED_DN); +add_int(OPT_DEBUG_LEVEL); +add_int(OPT_TIMEOUT); +add_int(OPT_REFHOPLIMIT); +add_int(OPT_NETWORK_TIMEOUT); +add_int(OPT_URI); + +#if defined(LDAP_OPT_DEFBASE) +add_int(OPT_DEFBASE); +#endif + + +#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); +add_int(OPT_X_TLS_KEYFILE); +add_int(OPT_X_TLS_REQUIRE_CERT); +add_int(OPT_X_TLS_CIPHER_SUITE); +add_int(OPT_X_TLS_RANDOM_FILE); +add_int(OPT_X_TLS_DHFILE); +add_int(OPT_X_TLS_NEVER); +add_int(OPT_X_TLS_HARD); +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 + + +#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 + +add_int(OPT_X_TLS_CRL_NONE); +add_int(OPT_X_TLS_CRL_PEER); +add_int(OPT_X_TLS_CRL_ALL); + +#if defined(LDAP_OPT_X_TLS_NEWCTX) +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 + +#endif + +add_int(OPT_X_SASL_MECH); +add_int(OPT_X_SASL_REALM); +add_int(OPT_X_SASL_AUTHCID); +add_int(OPT_X_SASL_AUTHZID); +add_int(OPT_X_SASL_SSF); +add_int(OPT_X_SASL_SSF_EXTERNAL); +add_int(OPT_X_SASL_SECPROPS); +add_int(OPT_X_SASL_SSF_MIN); +add_int(OPT_X_SASL_SSF_MAX); + +#if defined(LDAP_OPT_X_SASL_NOCANON) +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 + +add_int(DN_FORMAT_LDAP); +add_int(DN_FORMAT_LDAPV3); +add_int(DN_FORMAT_LDAPV2); +add_int(DN_FORMAT_DCE); +add_int(DN_FORMAT_UFN); +add_int(DN_FORMAT_AD_CANONICAL); +add_int(DN_FORMAT_MASK); +add_int(DN_PRETTY); +add_int(DN_SKIP); +add_int(DN_P_NOLEADTRAILSPACES); +add_int(DN_P_NOSPACEAFTERRDN); +add_int(DN_PEDANTIC); +add_int(AVA_NULL); +add_int(AVA_STRING); +add_int(AVA_BINARY); +add_int(AVA_NONPRINTABLE); +add_int(OPT_SUCCESS); +add_int(URL_ERR_BADSCOPE); +add_int(URL_ERR_MEM); + +#ifdef HAVE_LIBLDAP_R +result = PyModule_AddIntConstant(m, "LIBLDAP_R", 1); +#else +result = PyModule_AddIntConstant(m, "LIBLDAP_R", 0); +#endif +check_result(); + + +#ifdef HAVE_SASL +result = PyModule_AddIntConstant(m, "SASL_AVAIL", 1); +#else +result = PyModule_AddIntConstant(m, "SASL_AVAIL", 0); +#endif +check_result(); + + +#ifdef HAVE_TLS +result = PyModule_AddIntConstant(m, "TLS_AVAIL", 1); +#else +result = PyModule_AddIntConstant(m, "TLS_AVAIL", 0); +#endif +check_result(); + +add_string(CONTROL_MANAGEDSAIT); +add_string(CONTROL_PROXY_AUTHZ); +add_string(CONTROL_SUBENTRIES); +add_string(CONTROL_VALUESRETURNFILTER); +add_string(CONTROL_ASSERT); +add_string(CONTROL_PRE_READ); +add_string(CONTROL_POST_READ); +add_string(CONTROL_SORTREQUEST); +add_string(CONTROL_SORTRESPONSE); +add_string(CONTROL_PAGEDRESULTS); +add_string(CONTROL_SYNC); +add_string(CONTROL_SYNC_STATE); +add_string(CONTROL_SYNC_DONE); +add_string(SYNC_INFO); +add_string(CONTROL_PASSWORDPOLICYREQUEST); +add_string(CONTROL_PASSWORDPOLICYRESPONSE); +add_string(CONTROL_RELAX); diff --git a/Modules/errors.c b/Modules/errors.c deleted file mode 100644 index 3c4da78a..00000000 --- a/Modules/errors.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * errors that arise from ldap use - * Most errors become their own exception - * See https://www.python-ldap.org/ for details. */ - -#include "common.h" -#include "errors.h" -#include -#include - -/* the base exception class */ - -PyObject* -LDAPexception_class; - -/* list of error objects */ - -#define LDAP_ERROR_MIN LDAP_REFERRAL_LIMIT_EXCEEDED - -#ifdef 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 -#endif - -#define LDAP_ERROR_OFFSET -LDAP_ERROR_MIN - -static PyObject* errobjects[ LDAP_ERROR_MAX-LDAP_ERROR_MIN+1 ]; - - -/* Convert a bare LDAP error number into an exception */ -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; -} - -/* Convert an LDAP error into an informative python exception */ -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; - - /* at first save errno for later use before it gets overwritten by another call */ - myerrno = errno; - - char *matched, *error; - - 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 (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 ); - 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); - return NULL; - } -} - -/* Raise TypeError with custom message and object */ -PyObject* -LDAPerror_TypeError(const char *msg, PyObject *obj) { - PyObject *args = Py_BuildValue("sO", msg, obj); - if (args == NULL) { - return NULL; - } - PyErr_SetObject(PyExc_TypeError, args); - Py_DECREF(args); - return NULL; -} - - -/* initialisation */ - -void -LDAPinit_errors( PyObject*d ) { - - /* create the base exception class */ - LDAPexception_class = PyErr_NewException("ldap.LDAPError", - NULL, - NULL); - PyDict_SetItemString( d, "LDAPError", LDAPexception_class ); - - /* XXX - backward compatibility with pre-1.8 */ - PyDict_SetItemString( d, "error", LDAPexception_class ); - - /* create each LDAP error object */ - -# define seterrobj2(n,o) \ - PyDict_SetItemString( d, #n, (errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = o) ) - - -# define seterrobj(n) { \ - PyObject *e = PyErr_NewException("ldap." #n, \ - LDAPexception_class, NULL); \ - seterrobj2(n, e); \ - Py_INCREF(e); \ - } - - seterrobj(ADMINLIMIT_EXCEEDED); - seterrobj(AFFECTS_MULTIPLE_DSAS); - seterrobj(ALIAS_DEREF_PROBLEM); - seterrobj(ALIAS_PROBLEM); - seterrobj(ALREADY_EXISTS); - seterrobj(AUTH_METHOD_NOT_SUPPORTED); - seterrobj(AUTH_UNKNOWN); - seterrobj(BUSY); - seterrobj(CLIENT_LOOP); - seterrobj(COMPARE_FALSE); - seterrobj(COMPARE_TRUE); - seterrobj(CONFIDENTIALITY_REQUIRED); - seterrobj(CONNECT_ERROR); - seterrobj(CONSTRAINT_VIOLATION); - seterrobj(CONTROL_NOT_FOUND); - seterrobj(DECODING_ERROR); - seterrobj(ENCODING_ERROR); - seterrobj(FILTER_ERROR); - seterrobj(INAPPROPRIATE_AUTH); - seterrobj(INAPPROPRIATE_MATCHING); - seterrobj(INSUFFICIENT_ACCESS); - seterrobj(INVALID_CREDENTIALS); - seterrobj(INVALID_DN_SYNTAX); - seterrobj(INVALID_SYNTAX); - seterrobj(IS_LEAF); - seterrobj(LOCAL_ERROR); - seterrobj(LOOP_DETECT); - seterrobj(MORE_RESULTS_TO_RETURN); - seterrobj(NAMING_VIOLATION); - seterrobj(NO_MEMORY); - seterrobj(NO_OBJECT_CLASS_MODS); - seterrobj(NO_OBJECT_CLASS_MODS); - seterrobj(NO_RESULTS_RETURNED); - seterrobj(NO_SUCH_ATTRIBUTE); - seterrobj(NO_SUCH_OBJECT); - seterrobj(NOT_ALLOWED_ON_NONLEAF); - seterrobj(NOT_ALLOWED_ON_RDN); - seterrobj(NOT_SUPPORTED); - seterrobj(OBJECT_CLASS_VIOLATION); - seterrobj(OPERATIONS_ERROR); - seterrobj(OTHER); - seterrobj(PARAM_ERROR); - seterrobj(PARTIAL_RESULTS); - seterrobj(PROTOCOL_ERROR); - seterrobj(REFERRAL); - seterrobj(REFERRAL_LIMIT_EXCEEDED); - seterrobj(RESULTS_TOO_LARGE); - seterrobj(SASL_BIND_IN_PROGRESS); - seterrobj(SERVER_DOWN); - seterrobj(SIZELIMIT_EXCEEDED); - seterrobj(STRONG_AUTH_NOT_SUPPORTED); - seterrobj(STRONG_AUTH_REQUIRED); - seterrobj(SUCCESS); - seterrobj(TIMELIMIT_EXCEEDED); - seterrobj(TIMEOUT); - seterrobj(TYPE_OR_VALUE_EXISTS); - seterrobj(UNAVAILABLE); - seterrobj(UNAVAILABLE_CRITICAL_EXTENSION); - seterrobj(UNDEFINED_TYPE); - seterrobj(UNWILLING_TO_PERFORM); - seterrobj(USER_CANCELLED); - seterrobj(VLV_ERROR); - seterrobj(X_PROXY_AUTHZ_FAILURE); - -#ifdef LDAP_API_FEATURE_CANCEL - seterrobj(CANCELLED); - seterrobj(NO_SUCH_OPERATION); - seterrobj(TOO_LATE); - seterrobj(CANNOT_CANCEL); -#endif - -#ifdef LDAP_ASSERTION_FAILED - seterrobj(ASSERTION_FAILED); -#endif - -#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED - seterrobj(PROXIED_AUTHORIZATION_DENIED); -#endif - -} diff --git a/Modules/errors.h b/Modules/errors.h deleted file mode 100644 index 5bd3576a..00000000 --- a/Modules/errors.h +++ /dev/null @@ -1,16 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_errors_ -#define __h_errors_ - -#include "common.h" -#include "lber.h" -#include "ldap.h" - -extern PyObject* LDAPexception_class; -extern PyObject* LDAPerror( LDAP*, char*msg ); -extern PyObject* LDAPerror_TypeError(const char *, PyObject *); -extern void LDAPinit_errors( PyObject* ); -PyObject* LDAPerr(int errnum); - -#endif /* __h_errors */ diff --git a/Modules/functions.c b/Modules/functions.c index ffb6765c..a31be05e 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -4,7 +4,7 @@ #include "functions.h" #include "LDAPObject.h" #include "berval.h" -#include "errors.h" +#include "constants.h" #include "options.h" /* ldap_initialize */ diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 7e31f720..3f5b2c4c 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -4,7 +4,6 @@ #include "LDAPObject.h" #include "ldapcontrol.h" #include "berval.h" -#include "errors.h" #include "lber.h" diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 46c9c8a9..ec3a264e 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -2,7 +2,6 @@ #include "common.h" #include "constants.h" -#include "errors.h" #include "functions.h" #include "ldapcontrol.h" @@ -67,8 +66,10 @@ PyObject* init_ldap_module() init_pkginfo(m); - LDAPinit_constants(d); - LDAPinit_errors(d); + if (LDAPinit_constants(m) == -1) { + return NULL; + } + LDAPinit_functions(d); LDAPinit_control(d); diff --git a/Modules/message.c b/Modules/message.c index 1a289dbd..babbd256 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -3,7 +3,6 @@ #include "common.h" #include "message.h" #include "berval.h" -#include "errors.h" #include "ldapcontrol.h" #include "constants.h" diff --git a/Modules/options.c b/Modules/options.c index 7cf996bf..ac1eab60 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -1,7 +1,7 @@ /* See https://www.python-ldap.org/ for details. */ #include "common.h" -#include "errors.h" +#include "constants.h" #include "LDAPObject.h" #include "ldapcontrol.h" #include "options.h" diff --git a/setup.py b/setup.py index 2ac63ecf..9694fa53 100644 --- a/setup.py +++ b/setup.py @@ -136,7 +136,6 @@ class OpenLDAP2: 'Modules/ldapcontrol.c', 'Modules/common.c', 'Modules/constants.c', - 'Modules/errors.c', 'Modules/functions.c', 'Modules/ldapmodule.c', 'Modules/message.c', From 4587df60a331b99d695efd2470edf1853b28d8b2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 22:34:39 +0100 Subject: [PATCH 006/369] Doc: Convert conf.py to Unix line endings --- Doc/conf.py | 266 ++++++++++++++++++++++++++-------------------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 9c322de3..d40e8220 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -1,133 +1,133 @@ -# -*- coding: utf-8 -*- -# -# python-ldap documentation build configuration file, created by -# sphinx-quickstart on Sat Mar 29 15:08:17 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default value; values that are commented out -# serve to show the default value. - -import sys - -# If your extensions are in another directory, add it here. -#sys.path.append('some/directory') - -# General configuration -# --------------------- - -# 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'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['.templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General substitutions. -project = 'python-ldap' -copyright = '2008-2017, python-ldap project team' - -# The default replacements for |version| and |release|, also used in various -# other places throughout the built documents. -# -# The short X.Y version. -version = '2.5' -# The full version, including alpha/beta/rc tags. -release = '2.5.2.0' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - - -# Options for HTML output -# ----------------------- - -# The style sheet to use for HTML and HTML Help pages. A file of that name -# must exist either in Sphinx' static/ path, or in one of the custom paths -# given in html_static_path. -html_style = 'pyramid.css' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['/usr/lib/python2.7/site-packages/sphinx/themes/pyramid/static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Content template for the index page. -#html_index = '' - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -html_use_modindex = True - -# If true, the reST sources are included in the HTML build as _sources/. -#html_copy_source = True - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-ldap-doc' - - -# Options for LaTeX output -# ------------------------ - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). -latex_documents = [('index', 'python-ldap.tex', 'python-ldap Documentation', - 'python-ldap project', 'manual')] - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -latex_use_modindex = True +# -*- coding: utf-8 -*- +# +# python-ldap documentation build configuration file, created by +# sphinx-quickstart on Sat Mar 29 15:08:17 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default value; values that are commented out +# serve to show the default value. + +import sys + +# If your extensions are in another directory, add it here. +#sys.path.append('some/directory') + +# General configuration +# --------------------- + +# 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'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = 'python-ldap' +copyright = '2008-2017, python-ldap project team' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = '2.5' +# The full version, including alpha/beta/rc tags. +release = '2.5.2.0' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'pyramid.css' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['/usr/lib/python2.7/site-packages/sphinx/themes/pyramid/static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Content template for the index page. +#html_index = '' + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = True + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-ldap-doc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [('index', 'python-ldap.tex', 'python-ldap Documentation', + 'python-ldap project', 'manual')] + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +latex_use_modindex = True From 1bb84f641c16ab41a158c414df7d78fb88b1edcd Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 22:39:20 +0100 Subject: [PATCH 007/369] Doc: Add .gitignore --- Doc/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 Doc/.gitignore diff --git a/Doc/.gitignore b/Doc/.gitignore new file mode 100644 index 00000000..a485625d --- /dev/null +++ b/Doc/.gitignore @@ -0,0 +1 @@ +/_build From e34bd6b1ee50372918b1201c2e40f7df65064e72 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 23:07:51 +0100 Subject: [PATCH 008/369] Doc: Allow building documentation even without a compiled extension This involves a fake _ldap module and carefully bootstrapping ldap.__init__ (which imports * from _ldap). --- Doc/conf.py | 9 ++++++- Doc/fake_ldap_module_for_documentation.py | 30 +++++++++++++++++++++++ Lib/ldap/constants.py | 3 +++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Doc/fake_ldap_module_for_documentation.py diff --git a/Doc/conf.py b/Doc/conf.py index d40e8220..d81bf290 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -12,9 +12,16 @@ # serve to show the default value. import sys +import os # If your extensions are in another directory, add it here. -#sys.path.append('some/directory') +_doc_dir = os.path.dirname(__file__) +sys.path.append(_doc_dir) +sys.path.append(os.path.join(_doc_dir, '../Lib/')) +sys.path.insert(0, os.path.join(_doc_dir, '../Lib/ldap')) + +# Import fake `_ldap` module +import fake_ldap_module_for_documentation # General configuration # --------------------- diff --git a/Doc/fake_ldap_module_for_documentation.py b/Doc/fake_ldap_module_for_documentation.py new file mode 100644 index 00000000..30807819 --- /dev/null +++ b/Doc/fake_ldap_module_for_documentation.py @@ -0,0 +1,30 @@ +""" +A module that mocks `_ldap` for the purposes of generating documentation + +This module provides placeholders for the contents of `_ldap`, making it +possible to generate documentation even _ldap is not compiled. +It should also make the documentation independent of which features are +available in the system OpenLDAP library. + +The overly long module name will show up in AttributeError messages, +hinting that this is not the actual _ldap. + +See https://www.python-ldap.org/ for details. +""" + +import sys + +# Cause `import _ldap` to import this module instead of the actual `_ldap`. +sys.modules['_ldap'] = sys.modules[__name__] + +from constants import CONSTANTS +from pkginfo import __version__ + +for constant in CONSTANTS: + globals()[constant.name] = constant + +def get_option(num): + pass + +class LDAPError: + pass diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 6469a302..aafa2651 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -10,6 +10,9 @@ """ +# 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): From 849f36652ddbc1b336aa0b299297d11143c00a98 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 23:28:12 +0100 Subject: [PATCH 009/369] Doc: Get the version from the Python module --- Doc/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index d81bf290..43de80a4 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -23,6 +23,9 @@ # Import fake `_ldap` module import fake_ldap_module_for_documentation +# Now ldap can be used normally +from ldap import __version__ + # General configuration # --------------------- @@ -47,9 +50,9 @@ # other places throughout the built documents. # # The short X.Y version. -version = '2.5' +version = '.'.join(__version__.split('.')[:2]) # The full version, including alpha/beta/rc tags. -release = '2.5.2.0' +release = __version__ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From b096d31f1556435d6f6ab615cd597ec5d0ae4e74 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 23:29:08 +0100 Subject: [PATCH 010/369] Doc: Use the default style The default should be pretty enough. --- Doc/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 43de80a4..63fc24e7 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -84,12 +84,12 @@ # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. -html_style = 'pyramid.css' +#html_style = 'pyramid.css' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['/usr/lib/python2.7/site-packages/sphinx/themes/pyramid/static'] +#html_static_path = ['/usr/lib/python2.7/site-packages/sphinx/themes/pyramid/static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. From a3675965499f3a939f7fd6848b2506d2e4ffdd46 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 27 Nov 2017 15:33:45 +0100 Subject: [PATCH 011/369] Modules: Write out error checking in constants.c --- Lib/ldap/constants.py | 5 ++--- Modules/constants.c | 30 ++++++++---------------------- Modules/constants_generated.h | 15 ++++++--------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index aafa2651..c5ac6c1b 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -61,11 +61,10 @@ class Feature(Constant): c_template = '\n'.join([ '', '#ifdef {self.c_feature}', - 'result = PyModule_AddIntConstant(m, "{self.name}", 1);', + 'if (PyModule_AddIntConstant(m, "{self.name}", 1) != 0) return -1;', '#else', - 'result = PyModule_AddIntConstant(m, "{self.name}", 0);', + 'if (PyModule_AddIntConstant(m, "{self.name}", 0) != 0) return -1;', '#endif', - 'check_result();', '', ]) diff --git a/Modules/constants.c b/Modules/constants.c index 4fd09b85..89065815 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -131,18 +131,11 @@ int LDAPinit_constants( PyObject* m ) { PyObject *exc; - int result; - -#define check_result() { \ - if (result != 0) return -1; \ -} /* simple constants */ - result = PyModule_AddIntConstant(m, "OPT_ON", 1); - check_result(); - result = PyModule_AddIntConstant(m, "OPT_OFF", 0); - check_result(); + if (PyModule_AddIntConstant(m, "OPT_ON", 1) != 0) return -1; + if (PyModule_AddIntConstant(m, "OPT_OFF", 0) != 0) return -1; /* exceptions */ @@ -151,36 +144,29 @@ LDAPinit_constants( PyObject* m ) return -1; } - result = PyModule_AddObject(m, "LDAPError", LDAPexception_class); - check_result(); + if (PyModule_AddObject(m, "LDAPError", LDAPexception_class) != 0) return -1; Py_INCREF(LDAPexception_class); /* XXX - backward compatibility with pre-1.8 */ - result = PyModule_AddObject(m, "error", LDAPexception_class); - check_result(); + if (PyModule_AddObject(m, "error", LDAPexception_class) != 0) return -1; Py_INCREF(LDAPexception_class); /* Generated constants -- see Lib/ldap/constants.py */ -#define seterrobj2(n, o) \ - PyModule_AddObject(m, #n, (errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = o)) - #define add_err(n) { \ exc = PyErr_NewException("ldap." #n, LDAPexception_class, NULL); \ if (exc == NULL) return -1; \ - result = seterrobj2(n, exc); \ - check_result(); \ + errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = exc; \ + if (PyModule_AddObject(m, #n, exc) != 0) return -1; \ Py_INCREF(exc); \ } #define add_int(n) { \ - result = PyModule_AddIntConstant(m, #n, LDAP_##n); \ - check_result(); \ + if (PyModule_AddIntConstant(m, #n, LDAP_##n) != 0) return -1; \ } #define add_string(n) { \ - result = PyModule_AddStringConstant(m, #n, LDAP_##n); \ - check_result(); \ + if (PyModule_AddStringConstant(m, #n, LDAP_##n) != 0) return -1; \ } #include "constants_generated.h" diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 27addc91..083ba161 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -325,27 +325,24 @@ add_int(URL_ERR_BADSCOPE); add_int(URL_ERR_MEM); #ifdef HAVE_LIBLDAP_R -result = PyModule_AddIntConstant(m, "LIBLDAP_R", 1); +if (PyModule_AddIntConstant(m, "LIBLDAP_R", 1) != 0) return -1; #else -result = PyModule_AddIntConstant(m, "LIBLDAP_R", 0); +if (PyModule_AddIntConstant(m, "LIBLDAP_R", 0) != 0) return -1; #endif -check_result(); #ifdef HAVE_SASL -result = PyModule_AddIntConstant(m, "SASL_AVAIL", 1); +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) return -1; #else -result = PyModule_AddIntConstant(m, "SASL_AVAIL", 0); +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) return -1; #endif -check_result(); #ifdef HAVE_TLS -result = PyModule_AddIntConstant(m, "TLS_AVAIL", 1); +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) return -1; #else -result = PyModule_AddIntConstant(m, "TLS_AVAIL", 0); +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) return -1; #endif -check_result(); add_string(CONTROL_MANAGEDSAIT); add_string(CONTROL_PROXY_AUTHZ); From 5cbbc49898aa26045a6bd7c2c12b3585fc6517cf Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 27 Nov 2017 15:38:15 +0100 Subject: [PATCH 012/369] Modules: Wrap macros in constants.c in `do {...} while (0)` --- Modules/constants.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/constants.c b/Modules/constants.c index 89065815..f0028a09 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -153,21 +153,21 @@ LDAPinit_constants( PyObject* m ) /* Generated constants -- see Lib/ldap/constants.py */ -#define add_err(n) { \ +#define add_err(n) do { \ exc = PyErr_NewException("ldap." #n, LDAPexception_class, NULL); \ if (exc == NULL) return -1; \ errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = exc; \ if (PyModule_AddObject(m, #n, exc) != 0) return -1; \ Py_INCREF(exc); \ -} +} while (0) -#define add_int(n) { \ +#define add_int(n) do { \ if (PyModule_AddIntConstant(m, #n, LDAP_##n) != 0) return -1; \ -} +} while (0) -#define add_string(n) { \ +#define add_string(n) do { \ if (PyModule_AddStringConstant(m, #n, LDAP_##n) != 0) return -1; \ -} +} while (0) #include "constants_generated.h" From a06267f2cc9a8fc57c2031d2023ba858dbebcac8 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 27 Nov 2017 16:59:14 +0100 Subject: [PATCH 013/369] Lib: Avoid eval() for getting module-level variables This is for compatibility with other libraries that inject extra variables, like unit testing with pytest. Based on a suggestion by AlanCoding: https://github.com/python-ldap/python-ldap/pull/25 --- Lib/ldap/schema/subentry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 2a42b4c0..1c10d21f 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -11,8 +11,7 @@ SCHEMA_CLASS_MAPPING = ldap.cidict.cidict() SCHEMA_ATTR_MAPPING = {} -for _name in dir(): - o = eval(_name) +for o in list(vars().values()): if hasattr(o,'schema_attribute'): SCHEMA_CLASS_MAPPING[o.schema_attribute] = o SCHEMA_ATTR_MAPPING[o] = o.schema_attribute From 03d09f0ba20774db9369a2006e705060bd690cea Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sun, 26 Nov 2017 22:16:56 +0100 Subject: [PATCH 014/369] Fix resource leaks in error cases On several occasions LDAPObject methods did not clean up LDAPControls and other resources when LDAPControls_from_object() fails. This bug would lead to memory leaks in case a server control or client control could not be handled correctly. Closes: #18 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 75 +++++++++++++++++++++++++++++++++----------- Tests/t_cext.py | 47 +++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 18 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index f1ad0d44..b066cdf3 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -351,8 +351,10 @@ l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -392,8 +394,10 @@ l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -434,13 +438,18 @@ l_ldap_add_ext( LDAPObject* self, PyObject *args ) return NULL; if (!PyNone_Check(serverctrls)) { - if (!LDAPControls_from_object(serverctrls, &server_ldcs)) + if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { + LDAPMods_DEL( mods ); return NULL; + } } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPMods_DEL( mods ); + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -482,8 +491,10 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -631,8 +642,10 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) return NULL; } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -695,8 +708,10 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } /* now we extract the sasl mechanism from the SASL Object */ @@ -755,8 +770,10 @@ l_ldap_cancel( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -801,8 +818,10 @@ l_ldap_compare_ext( LDAPObject* self, PyObject *args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -842,8 +861,10 @@ l_ldap_delete_ext( LDAPObject* self, PyObject *args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -884,13 +905,18 @@ l_ldap_modify_ext( LDAPObject* self, PyObject *args ) return NULL; if (!PyNone_Check(serverctrls)) { - if (!LDAPControls_from_object(serverctrls, &server_ldcs)) + if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { + LDAPMods_DEL( mods ); return NULL; + } } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPMods_DEL( mods ); + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -934,8 +960,10 @@ l_ldap_rename( LDAPObject* self, PyObject *args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -1132,13 +1160,18 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(serverctrls)) { - if (!LDAPControls_from_object(serverctrls, &server_ldcs)) + if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { + free_attrs( &attrs, attrs_seq); return NULL; + } } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + free_attrs( &attrs, attrs_seq); + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -1182,8 +1215,10 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -1290,8 +1325,10 @@ l_ldap_passwd( LDAPObject* self, PyObject *args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); @@ -1340,8 +1377,10 @@ l_ldap_extended_operation( LDAPObject* self, PyObject *args ) } if (!PyNone_Check(clientctrls)) { - if (!LDAPControls_from_object(clientctrls, &client_ldcs)) + if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { + LDAPControl_List_DEL( server_ldcs ); return NULL; + } } LDAP_BEGIN_ALLOW_THREADS( self ); diff --git a/Tests/t_cext.py b/Tests/t_cext.py index d4740d0c..5aeb03d8 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -272,6 +272,13 @@ def test_search_ext_all(self): self.assertEqual(msgid, m) self.assertEqual(ctrls, []) + def test_invalid_search_filter(self): + l = self._open_conn() + with self.assertRaises(_ldap.FILTER_ERROR): + l.search_ext( + self.server.suffix, _ldap.SCOPE_SUBTREE, 'bogus filter expr' + ) + def test_add(self): """ test add operation @@ -726,6 +733,46 @@ def test_invalid_credentials(self): else: self.fail("expected INVALID_CREDENTIALS, got %r" % r) + # TODO: test_extop + + def assertInvalidControls(self, func, *args, **kwargs): + post = kwargs.pop('post', ()) + self.assertFalse(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)) + with self.assertRaises(TypeError) as e: + func(*(args + (None, object) + post)) + self.assertEqual(e.exception.args, ('expected a list', object)) + + def test_invalid_controls(self): + l = self._open_conn() + self.assertInvalidControls(l.simple_bind, "", "") + self.assertInvalidControls(l.whoami_s) + self.assertInvalidControls(l.passwd, 'dn', 'initial', 'changed') + self.assertInvalidControls(l.add_ext, 'dn', [('cn', b'cn')]) + self.assertInvalidControls( + l.modify_ext, 'dn', [(_ldap.MOD_ADD, 'attr', [b'value'])]) + self.assertInvalidControls(l.compare_ext, 'dn', 'val1', 'val2') + self.assertInvalidControls( + l.rename, 'dn', 'newdn', 'container', False) + self.assertInvalidControls( + l.search_ext, 'dn', _ldap.SCOPE_SUBTREE, '(objectClass=*)', + None, 1) + self.assertInvalidControls(l.delete_ext, 'dn') + m = l.search_ext( + self.server.suffix, _ldap.SCOPE_SUBTREE, '(objectClass=*)') + self.assertInvalidControls(l.abandon_ext, m) + self.assertInvalidControls(l.cancel, 0) + self.assertInvalidControls(l.extop, 'oid', 'value') + if hasattr(l, 'sasl_bind_s'): + self.assertInvalidControls(l.sasl_bind_s, 'dn', 'MECH', 'CRED') + if hasattr(l, 'sasl_interactive_bind_s'): + self.assertInvalidControls( + l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,)) + self.assertInvalidControls(l.unbind_ext) + if __name__ == '__main__': unittest.main() From 54a286b41744fbb1bc5296611d72a33d889a681b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 11:00:23 +0100 Subject: [PATCH 015/369] C extension reference leak testing Add documentation how to run refleak tests with pydebug build of CPython and pytest-refleak. Refleak testing runs each unit test multiple times. Some small changes are required to clean up after each test cycle and to avoid extra references. * Only create logging structure for SlapdObject once. The logging framework creates reference cycles that cause false positives. * Clean up and remove atexit handlers in SlapdObject.stop() * Remove new entries at the end of each test, either with an explicit delete_s() or an write area, which is removed in tearDown(). https://github.com/python-ldap/python-ldap/pull/41 Signed-off-by: Christian Heimes --- INSTALL | 34 ++++++++++++++++++++++++ Lib/slapdtest.py | 44 ++++++++++++++++++++++++++----- Tests/t_cext.py | 68 ++++++++++++++++++++++++++++++++++++------------ Tests/t_edit.py | 6 +++++ 4 files changed, 129 insertions(+), 23 deletions(-) diff --git a/INSTALL b/INSTALL index 98dd40cc..57b00b7c 100644 --- a/INSTALL +++ b/INSTALL @@ -24,3 +24,37 @@ Quick build instructions: edit setup.cfg (see Build/ for platform-specific examples) python setup.py build python setup.py install + +-------------------- +Reference leak tests +-------------------- + +Reference leak tests require a pydebug build of CPython and pytest with +pytest-leaks plugin. A pydebug build has a global reference counter, which +keeps track of all reference increments and decrements. The leak plugin runs +each tests multiple times and checks if the reference count increases. + +Download and compile pydebug build +---------------------------------- + +- curl -O https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz +- tar xJf Python-3.6.3.tar.xz +- cd Python-3.6.3 +- ./configure --with-pydebug +- make + +Create virtual env +------------------ + +- ./python -m venv /tmp/refleak +- /tmp/refleak/bin/pip install pytest pytest-leaks + +Run refleak tests +----------------- + +- cd path/to/python-ldap +- /tmp/refleak/bin/pip install --upgrade . +- /tmp/refleak/bin/pytest -v -R: Tests/t_*.py + +Run ``/tmp/refleak/bin/pip install --upgrade .`` every time a file outside +of ``Tests/`` is modified. \ No newline at end of file diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 4955420a..0bef1c15 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -117,6 +117,7 @@ class SlapdObject(object): else: SCHEMADIR = None PATH_LDAPADD = os.path.join(BINDIR, 'ldapadd') + PATH_LDAPDELETE = os.path.join(BINDIR, 'ldapdelete') PATH_LDAPMODIFY = os.path.join(BINDIR, 'ldapmodify') PATH_LDAPWHOAMI = os.path.join(BINDIR, 'ldapwhoami') PATH_SLAPD = os.environ.get('SLAPD', os.path.join(SBINDIR, 'slapd')) @@ -125,8 +126,10 @@ class SlapdObject(object): # 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') + def __init__(self): - self._log = combined_logger('python-ldap-test') self._proc = None self._port = self._avail_tcp_port() self.server_id = self._port % 4096 @@ -189,9 +192,11 @@ def _avail_tcp_port(self): find an available port for TCP connection """ sock = socket.socket() - sock.bind((self.local_host, 0)) - port = sock.getsockname()[1] - sock.close() + try: + sock.bind((self.local_host, 0)) + port = sock.getsockname()[1] + finally: + sock.close() self._log.info('Found available port %d', port) return port @@ -316,6 +321,15 @@ 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 def restart(self): """ @@ -358,7 +372,10 @@ def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=Non '-H', ldap_uri or self.ldapi_uri, ] + self._cli_auth_args() + (extra_args or []) self._log.debug('Run command: %r', ' '.join(args)) - proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + proc = subprocess.Popen( + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) self._log.debug('stdin_data=%r', stdin_data) stdout_data, stderr_data = proc.communicate(stdin_data) if stdout_data is not None: @@ -366,7 +383,11 @@ def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=Non if stderr_data is not None: self._log.debug('stderr_data=%r', stderr_data) if proc.wait() != 0: - raise RuntimeError('ldapadd process failed') + raise RuntimeError( + '{!r} process failed:\n{!r}\n{!r}'.format( + args, stdout_data, stderr_data + ) + ) return stdout_data, stderr_data def ldapwhoami(self, extra_args=None): @@ -389,6 +410,17 @@ def ldapmodify(self, ldif, extra_args=None): self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args, stdin_data=ldif.encode('utf-8')) + def ldapdelete(self, dn, recursive=False, extra_args=None): + """ + Runs ldapdelete on this slapd instance, deleting 'dn' + """ + if extra_args is None: + extra_args = [] + if recursive: + extra_args.append('-r') + extra_args.append(dn) + self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) + class SlapdTestCase(unittest.TestCase): """ diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 5aeb03d8..6998e731 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -51,6 +51,40 @@ def setUpClass(cls): ]) ) + def setUp(self): + super(TestLdapCExtension, self).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() + + @property + def writesuffix(self): + """Initialize writesuffix on demand + + Creates a clean subtree for tests that write to slapd. ldapdelete + is not able to delete a Root DSE, therefore we need a temporary + work space. + + :return: DN + """ + if self._writesuffix is not None: + return self._writesuffix + self._writesuffix = 'ou=write tests,%s' % self.server.suffix + # Add writeable subtree + self.server.ldapadd( + "\n".join([ + 'dn: ' + self._writesuffix, + 'objectClass: organizationalUnit', + 'ou:' + self._writesuffix.split(',')[0][3:], + '' + ]) + ) + return self._writesuffix + def _open_conn(self, bind=True): """ Starts a server, and returns a LDAPObject bound to it @@ -285,7 +319,7 @@ def test_add(self): """ l = self._open_conn() m = l.add_ext( - "cn=Foo," + self.server.suffix, + "cn=Foo," + self.writesuffix, [ ('objectClass', b'organizationalRole'), ('cn', b'Foo'), @@ -299,7 +333,7 @@ def test_add(self): self.assertEqual(msgid, m) self.assertEqual(ctrls, []) # search for it back - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=Foo)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=Foo)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) # Expect to get the objects self.assertEqual(result, _ldap.RES_SEARCH_RESULT) @@ -309,7 +343,7 @@ def test_add(self): self.assertEqual( pmsg[0], ( - 'cn=Foo,'+self.server.suffix, + 'cn=Foo,'+self.writesuffix, { 'objectClass': [b'organizationalRole'], 'cn': [b'Foo'], @@ -324,7 +358,7 @@ def test_compare(self): """ l = self._open_conn() # first, add an object with a field we can compare on - dn = "cn=CompareTest," + self.server.suffix + dn = "cn=CompareTest," + self.writesuffix m = l.add_ext( dn, [ @@ -378,7 +412,7 @@ def test_delete_no_such_object(self): def test_delete(self): l = self._open_conn() # first, add an object we will delete - dn = "cn=Deleteme,"+self.server.suffix + dn = "cn=Deleteme,"+self.writesuffix m = l.add_ext( dn, [ @@ -402,7 +436,7 @@ def test_modify_no_such_object(self): # try deleting an object that doesn't exist m = l.modify_ext( - "cn=DoesNotExist,"+self.server.suffix, + "cn=DoesNotExist,"+self.writesuffix, [ (_ldap.MOD_ADD, 'description', [b'blah']), ] @@ -439,7 +473,7 @@ def test_modify(self): """ l = self._open_conn() # first, add an object we will delete - dn = "cn=AddToMe,"+self.server.suffix + dn = "cn=AddToMe,"+self.writesuffix m = l.add_ext( dn, [ @@ -465,7 +499,7 @@ def test_modify(self): self.assertEqual(msgid, m) self.assertEqual(ctrls, []) # search for it back - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=AddToMe)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=AddToMe)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) # Expect to get the objects self.assertEqual(result, _ldap.RES_SEARCH_RESULT) @@ -479,7 +513,7 @@ def test_modify(self): def test_rename(self): l = self._open_conn() - dn = "cn=RenameMe,"+self.server.suffix + dn = "cn=RenameMe,"+self.writesuffix m = l.add_ext( dn, [ @@ -500,7 +534,7 @@ def test_rename(self): self.assertEqual(ctrls, []) # make sure the old one is gone - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=RenameMe)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=RenameMe)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_SEARCH_RESULT) self.assertEqual(len(pmsg), 0) # expect no results @@ -508,8 +542,8 @@ def test_rename(self): self.assertEqual(ctrls, []) # check that the new one looks right - dn2 = "cn=IAmRenamed,"+self.server.suffix - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamed)') + dn2 = "cn=IAmRenamed,"+self.writesuffix + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamed)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_SEARCH_RESULT) self.assertEqual(msgid, m) @@ -519,7 +553,7 @@ def test_rename(self): self.assertEqual(pmsg[0][1]['cn'], [b'IAmRenamed']) # create the container - containerDn = "ou=RenameContainer,"+self.server.suffix + containerDn = "ou=RenameContainer,"+self.writesuffix m = l.add_ext( containerDn, [ @@ -542,18 +576,18 @@ def test_rename(self): self.assertEqual(ctrls, []) # make sure dn2 is gone - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamed)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamed)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_SEARCH_RESULT) self.assertEqual(len(pmsg), 0) # expect no results self.assertEqual(msgid, m) self.assertEqual(ctrls, []) - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(objectClass=*)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(objectClass=*)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) # make sure dn3 is there - m = l.search_ext(self.server.suffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamedAgain)') + m = l.search_ext(self.writesuffix, _ldap.SCOPE_SUBTREE, '(cn=IAmRenamedAgain)') result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_SEARCH_RESULT) self.assertEqual(msgid, m) @@ -595,7 +629,7 @@ def test_whoami_after_unbind(self): def test_passwd(self): l = self._open_conn() # first, create a user to change password on - dn = "cn=PasswordTest," + self.server.suffix + dn = "cn=PasswordTest," + self.writesuffix m = l.add_ext( dn, [ diff --git a/Tests/t_edit.py b/Tests/t_edit.py index 9aee43e9..df4529ad 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -83,6 +83,12 @@ def test_add_object(self): ("cn=Added,ou=Container," + base, {'cn': [b'Added'], 'objectClass': [b'organizationalRole']}), ]) + # Delete object + self.ldap.delete_s(dn) + result = self.ldap.search_s( + base, ldap.SCOPE_SUBTREE, '(cn=Added)', ['*'] + ) + self.assertEqual(result, []) if __name__ == '__main__': From 5dcf6e8dda168db39192d5f23637b01e60f25af2 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 11:07:35 +0100 Subject: [PATCH 016/369] Add make target for scan-build Scan-build is a frontend to clang's static code analyzer. It checks C code for bugs. https://github.com/python-ldap/python-ldap/pull/39 Signed-off-by: Christian Heimes --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 47fac901..1ee3eda1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ +PYTHON=python3 LCOV_INFO=build/lcov.info LCOV_REPORT=build/lcov_report LCOV_REPORT_OPTIONS=--show-details -no-branch-coverage \ --title "python-ldap LCOV report" +SCAN_REPORT=build/scan_report .NOTPARALLEL: @@ -40,3 +42,10 @@ lcov-open: $(LCOV_REPORT) lcov: lcov-clean $(MAKE) lcov-coverage $(MAKE) lcov-report + +# clang-analyzer for static C code analysis +.PHONY: scan-build +scan-build: + scan-build -o $(SCAN_REPORT) --html-title="python-ldap scan report" \ + -analyze-headers --view \ + $(PYTHON) setup.py clean --all build From 0778a20f8b9aa4d5ade759236b167cf0b234b1d4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 11:10:13 +0100 Subject: [PATCH 017/369] Fix reference leak in result4 Two error cases are missing a Py_XDECREF(valuestr). A password change with an invalid old password triggers a reference leak. Closes: https://github.com/python-ldap/python-ldap/issues/37 https://github.com/python-ldap/python-ldap/pull/38 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index b066cdf3..1c0591cc 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -998,7 +998,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) PyObject *result_str, *retval, *pmsg, *pyctrls = 0; int res_msgid = 0; char *retoid = 0; - PyObject *valuestr = 0; + PyObject *valuestr = NULL; if (!PyArg_ParseTuple( args, "|iidiii", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) return NULL; @@ -1071,6 +1071,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) } else e = "ldap_parse_result"; ldap_msgfree(msg); + Py_XDECREF(valuestr); return LDAPerror( self->ldap, e ); } @@ -1080,6 +1081,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &err); LDAP_END_ALLOW_THREADS( self ); ldap_msgfree(msg); + Py_XDECREF(valuestr); return LDAPerror(self->ldap, "LDAPControls_to_List"); } ldap_controls_free(serverctrls); @@ -1108,9 +1110,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) Py_DECREF(pmsg); } } - if (valuestr) { - Py_DECREF(valuestr); - } + Py_XDECREF(valuestr); Py_XDECREF(pyctrls); Py_DECREF(result_str); return retval; From 9f6f1428fed9976f1215ea80a49004ddad7e96f4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 11:15:32 +0100 Subject: [PATCH 018/369] Add depends=[headers] to setup.py Any modification of a header file now triggers a rebuild of the C extension module. Closes: https://github.com/python-ldap/python-ldap/pull/33 Signed-off-by: Christian Heimes --- setup.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setup.py b/setup.py index 9694fa53..f09d9c3c 100644 --- a/setup.py +++ b/setup.py @@ -142,6 +142,17 @@ class OpenLDAP2: 'Modules/options.c', 'Modules/berval.c', ], + depends = [ + 'Modules/LDAPObject.h', + 'Modules/berval.h', + 'Modules/common.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, library_dirs = LDAP_CLASS.library_dirs, From 4b976714e054b52b52a254529dfcc1ad42dd99da Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 11:19:41 +0100 Subject: [PATCH 019/369] Run tests with warnings and bytes warnings All warnings except expected warnings are turned into exceptions. Fixed a resource warning in schema test case. Rename Tests/ldif to Tests/data to prevent an ImportWarning with Python 2: Traceback (most recent call last): ... ImportWarning: Not importing directory 'Tests/ldif': missing __init__.py https://github.com/python-ldap/python-ldap/pull/15 Signed-off-by: Christian Heimes --- .../subschema-ipa.demo1.freeipa.org.ldif | 0 Tests/{ldif => data}/subschema-openldap-all.ldif | 0 Tests/t_ldap_schema_subentry.py | 13 ++++++++----- tox.ini | 14 +++++++++++++- 4 files changed, 21 insertions(+), 6 deletions(-) rename Tests/{ldif => data}/subschema-ipa.demo1.freeipa.org.ldif (100%) rename Tests/{ldif => data}/subschema-openldap-all.ldif (100%) diff --git a/Tests/ldif/subschema-ipa.demo1.freeipa.org.ldif b/Tests/data/subschema-ipa.demo1.freeipa.org.ldif similarity index 100% rename from Tests/ldif/subschema-ipa.demo1.freeipa.org.ldif rename to Tests/data/subschema-ipa.demo1.freeipa.org.ldif diff --git a/Tests/ldif/subschema-openldap-all.ldif b/Tests/data/subschema-openldap-all.ldif similarity index 100% rename from Tests/ldif/subschema-openldap-all.ldif rename to Tests/data/subschema-openldap-all.ldif diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 83ed0061..ee79072b 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -5,6 +5,7 @@ See https://www.python-ldap.org/ for details. """ +import os import unittest import time @@ -12,9 +13,11 @@ import ldap.schema from ldap.schema.models import ObjectClass +HERE = os.path.abspath(os.path.dirname(__file__)) + TEST_SUBSCHEMA_FILES = ( - 'Tests/ldif/subschema-ipa.demo1.freeipa.org.ldif', - 'Tests/ldif/subschema-openldap-all.ldif', + os.path.join(HERE, 'data', 'subschema-ipa.demo1.freeipa.org.ldif'), + os.path.join(HERE, 'data', 'subschema-openldap-all.ldif'), ) class TestSubschemaLDIF(unittest.TestCase): @@ -25,9 +28,9 @@ class TestSubschemaLDIF(unittest.TestCase): def test_subschema_file(self): for test_file in TEST_SUBSCHEMA_FILES: # Read and parse LDIF file - ldif_file = open(test_file, 'rb') - ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) - ldif_parser.parse() + with open(test_file, 'rb') as ldif_file: + ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) + ldif_parser.parse() _, subschema_subentry = ldif_parser.all_records[0] sub_schema = ldap.schema.SubSchema(subschema_subentry) diff --git a/tox.ini b/tox.ini index b2d435c6..22ca4a8e 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,20 @@ envlist = py27,py33,py34,py35,py36,coverage-report [testenv] deps = coverage -commands = {envpython} -m coverage run --parallel setup.py test 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.3 and 3.4 use PendingDeprecationWarning. +commands = {envpython} -bb -Werror \ + "-Wignore:the imp module is deprecated:DeprecationWarning" \ + "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ + -m coverage run --parallel setup.py test + +[testenv:py27] +# No warnings with Python 2.7 +commands = {envpython} \ + -m coverage run --parallel setup.py test [testenv:coverage-report] deps = coverage From f7012e7bfd717beba2d0e5b96240533c989d4146 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 17:01:08 +0100 Subject: [PATCH 020/369] Doc: Convert Makefile and index.rst to Unix line endings --- Doc/Makefile | 136 +++++++++++++++++++++--------------------- Doc/index.rst | 160 +++++++++++++++++++++++++------------------------- 2 files changed, 148 insertions(+), 148 deletions(-) diff --git a/Doc/Makefile b/Doc/Makefile index 5c8c6f12..9ce697a5 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -1,68 +1,68 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html web htmlhelp latex changes linkcheck - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " web to make files usable by Sphinx.web" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview over all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - -clean: - -rm -rf .build/* - -html: - mkdir -p .build/html .build/doctrees - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html - @echo - @echo "Build finished. The HTML pages are in .build/html." - -web: - mkdir -p .build/web .build/doctrees - $(SPHINXBUILD) -b web $(ALLSPHINXOPTS) .build/web - @echo - @echo "Build finished; now you can run" - @echo " python -m sphinx.web .build/web" - @echo "to start the server." - -htmlhelp: - mkdir -p .build/htmlhelp .build/doctrees - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in .build/htmlhelp." - -latex: - mkdir -p .build/latex .build/doctrees - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex - @echo - @echo "Build finished; the LaTeX files are in .build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - mkdir -p .build/changes .build/doctrees - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes - @echo - @echo "The overview file is in .build/changes." - -linkcheck: - mkdir -p .build/linkcheck .build/doctrees - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in .build/linkcheck/output.txt." +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web htmlhelp latex changes linkcheck + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " web to make files usable by Sphinx.web" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf .build/* + +html: + mkdir -p .build/html .build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html + @echo + @echo "Build finished. The HTML pages are in .build/html." + +web: + mkdir -p .build/web .build/doctrees + $(SPHINXBUILD) -b web $(ALLSPHINXOPTS) .build/web + @echo + @echo "Build finished; now you can run" + @echo " python -m sphinx.web .build/web" + @echo "to start the server." + +htmlhelp: + mkdir -p .build/htmlhelp .build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in .build/htmlhelp." + +latex: + mkdir -p .build/latex .build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex + @echo + @echo "Build finished; the LaTeX files are in .build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p .build/changes .build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes + @echo + @echo "The overview file is in .build/changes." + +linkcheck: + mkdir -p .build/linkcheck .build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in .build/linkcheck/output.txt." diff --git a/Doc/index.rst b/Doc/index.rst index 1f9bf6aa..8a7974e3 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -1,80 +1,80 @@ -########################## -python-ldap Documentation -########################## - -.. topic:: Abstract - - This document describes the package python-ldap with its various modules. - - Depending on what you want to do this manual assumes basic to expert - knowledge about the Python language and the LDAP standard (LDAPv3). - - -******** -Contents -******** - -.. toctree:: - :maxdepth: 3 - - installing.rst - ldap.rst - ldap-async.rst - ldap-controls.rst - ldap-dn.rst - ldap-extop.rst - ldap-filter.rst - ldap-modlist.rst - ldap-resiter.rst - ldap-schema.rst - ldap-syncrepl.rst - ldap-sasl.rst - ldif.rst - ldapurl.rst - slapdtest.rst - - - -********************* -Bytes/text management -********************* - -The LDAP protocol states that some fields (distinguised names, relative distinguished names, -attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** -contain any type of data, and thus be treated as bytes. - -In Python 2, ``python-ldap`` used bytes for all fields, including those guaranteed to be text. -In order to support Python 3, this distinction is made explicit. This is done -through the ``bytes_mode`` flag to ``ldap.initialize()``. - -When porting from ``python-ldap`` 2.x, users are advised to update their code to set ``bytes_mode=False`` -on calls to these methods. -Under Python 2, ``python-pyldap`` aggressively checks the type of provided arguments, and will raise a ``TypeError`` -for any invalid parameter. -However, if the ``bytes_mode`` kwarg isn't provided, ``pyldap`` will only -raise warnings. - -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('login', 'secret_password') - >>> results = con.search_s('ou=people,dc=example,dc=org', ldap.SCOPE_SUBTREE, "(cn=Raphaël)") - >>> results - [ - ("cn=Raphaël,ou=people,dc=example,dc=org", { - 'cn': [b'Rapha\xc3\xabl'], - 'sn': [b'Barrois'], - }), - ] - - -****************** -Indices and tables -****************** - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +########################## +python-ldap Documentation +########################## + +.. topic:: Abstract + + This document describes the package python-ldap with its various modules. + + Depending on what you want to do this manual assumes basic to expert + knowledge about the Python language and the LDAP standard (LDAPv3). + + +******** +Contents +******** + +.. toctree:: + :maxdepth: 3 + + installing.rst + ldap.rst + ldap-async.rst + ldap-controls.rst + ldap-dn.rst + ldap-extop.rst + ldap-filter.rst + ldap-modlist.rst + ldap-resiter.rst + ldap-schema.rst + ldap-syncrepl.rst + ldap-sasl.rst + ldif.rst + ldapurl.rst + slapdtest.rst + + + +********************* +Bytes/text management +********************* + +The LDAP protocol states that some fields (distinguised names, relative distinguished names, +attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** +contain any type of data, and thus be treated as bytes. + +In Python 2, ``python-ldap`` used bytes for all fields, including those guaranteed to be text. +In order to support Python 3, this distinction is made explicit. This is done +through the ``bytes_mode`` flag to ``ldap.initialize()``. + +When porting from ``python-ldap`` 2.x, users are advised to update their code to set ``bytes_mode=False`` +on calls to these methods. +Under Python 2, ``python-pyldap`` aggressively checks the type of provided arguments, and will raise a ``TypeError`` +for any invalid parameter. +However, if the ``bytes_mode`` kwarg isn't provided, ``pyldap`` will only +raise warnings. + +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('login', 'secret_password') + >>> results = con.search_s('ou=people,dc=example,dc=org', ldap.SCOPE_SUBTREE, "(cn=Raphaël)") + >>> results + [ + ("cn=Raphaël,ou=people,dc=example,dc=org", { + 'cn': [b'Rapha\xc3\xabl'], + 'sn': [b'Barrois'], + }), + ] + + +****************** +Indices and tables +****************** + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From fa090f9838fb583a2888d77c3099882c863875f9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 17:02:03 +0100 Subject: [PATCH 021/369] Doc: Move module reference to a subdirectory As more non-reference documentation is added, it becomes convenient to create a new namespace for the API docs, so it's not lost between others. Not included: redirects from old to new names on the hosted documentation (this will be handled in Read the Docs configuration). --- Doc/index.rst | 25 ++----------------------- Doc/reference/index.rst | 26 ++++++++++++++++++++++++++ Doc/{ => reference}/ldap-async.rst | 0 Doc/{ => reference}/ldap-controls.rst | 0 Doc/{ => reference}/ldap-dn.rst | 0 Doc/{ => reference}/ldap-extop.rst | 0 Doc/{ => reference}/ldap-filter.rst | 0 Doc/{ => reference}/ldap-modlist.rst | 0 Doc/{ => reference}/ldap-resiter.rst | 0 Doc/{ => reference}/ldap-sasl.rst | 0 Doc/{ => reference}/ldap-schema.rst | 0 Doc/{ => reference}/ldap-syncrepl.rst | 0 Doc/{ => reference}/ldap.rst | 0 Doc/{ => reference}/ldapurl.rst | 0 Doc/{ => reference}/ldif.rst | 0 Doc/{ => reference}/slapdtest.rst | 0 16 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 Doc/reference/index.rst rename Doc/{ => reference}/ldap-async.rst (100%) rename Doc/{ => reference}/ldap-controls.rst (100%) rename Doc/{ => reference}/ldap-dn.rst (100%) rename Doc/{ => reference}/ldap-extop.rst (100%) rename Doc/{ => reference}/ldap-filter.rst (100%) rename Doc/{ => reference}/ldap-modlist.rst (100%) rename Doc/{ => reference}/ldap-resiter.rst (100%) rename Doc/{ => reference}/ldap-sasl.rst (100%) rename Doc/{ => reference}/ldap-schema.rst (100%) rename Doc/{ => reference}/ldap-syncrepl.rst (100%) rename Doc/{ => reference}/ldap.rst (100%) rename Doc/{ => reference}/ldapurl.rst (100%) rename Doc/{ => reference}/ldif.rst (100%) rename Doc/{ => reference}/slapdtest.rst (100%) diff --git a/Doc/index.rst b/Doc/index.rst index 8a7974e3..476e9cc1 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -2,36 +2,15 @@ python-ldap Documentation ########################## -.. topic:: Abstract - - This document describes the package python-ldap with its various modules. - - Depending on what you want to do this manual assumes basic to expert - knowledge about the Python language and the LDAP standard (LDAPv3). - - ******** Contents ******** .. toctree:: - :maxdepth: 3 + :maxdepth: 2 installing.rst - ldap.rst - ldap-async.rst - ldap-controls.rst - ldap-dn.rst - ldap-extop.rst - ldap-filter.rst - ldap-modlist.rst - ldap-resiter.rst - ldap-schema.rst - ldap-syncrepl.rst - ldap-sasl.rst - ldif.rst - ldapurl.rst - slapdtest.rst + reference/index.rst diff --git a/Doc/reference/index.rst b/Doc/reference/index.rst new file mode 100644 index 00000000..fa45b84d --- /dev/null +++ b/Doc/reference/index.rst @@ -0,0 +1,26 @@ +python-ldap Reference Documentation +=================================== + +This document describes the package python-ldap with its various modules. + +Depending on what you want to do this manual assumes basic to expert +knowledge about the Python language and the LDAP standard (LDAPv3). + + +.. toctree:: + :maxdepth: 3 + + ldap.rst + ldap-async.rst + ldap-controls.rst + ldap-dn.rst + ldap-extop.rst + ldap-filter.rst + ldap-modlist.rst + ldap-resiter.rst + ldap-schema.rst + ldap-syncrepl.rst + ldap-sasl.rst + ldif.rst + ldapurl.rst + slapdtest.rst diff --git a/Doc/ldap-async.rst b/Doc/reference/ldap-async.rst similarity index 100% rename from Doc/ldap-async.rst rename to Doc/reference/ldap-async.rst diff --git a/Doc/ldap-controls.rst b/Doc/reference/ldap-controls.rst similarity index 100% rename from Doc/ldap-controls.rst rename to Doc/reference/ldap-controls.rst diff --git a/Doc/ldap-dn.rst b/Doc/reference/ldap-dn.rst similarity index 100% rename from Doc/ldap-dn.rst rename to Doc/reference/ldap-dn.rst diff --git a/Doc/ldap-extop.rst b/Doc/reference/ldap-extop.rst similarity index 100% rename from Doc/ldap-extop.rst rename to Doc/reference/ldap-extop.rst diff --git a/Doc/ldap-filter.rst b/Doc/reference/ldap-filter.rst similarity index 100% rename from Doc/ldap-filter.rst rename to Doc/reference/ldap-filter.rst diff --git a/Doc/ldap-modlist.rst b/Doc/reference/ldap-modlist.rst similarity index 100% rename from Doc/ldap-modlist.rst rename to Doc/reference/ldap-modlist.rst diff --git a/Doc/ldap-resiter.rst b/Doc/reference/ldap-resiter.rst similarity index 100% rename from Doc/ldap-resiter.rst rename to Doc/reference/ldap-resiter.rst diff --git a/Doc/ldap-sasl.rst b/Doc/reference/ldap-sasl.rst similarity index 100% rename from Doc/ldap-sasl.rst rename to Doc/reference/ldap-sasl.rst diff --git a/Doc/ldap-schema.rst b/Doc/reference/ldap-schema.rst similarity index 100% rename from Doc/ldap-schema.rst rename to Doc/reference/ldap-schema.rst diff --git a/Doc/ldap-syncrepl.rst b/Doc/reference/ldap-syncrepl.rst similarity index 100% rename from Doc/ldap-syncrepl.rst rename to Doc/reference/ldap-syncrepl.rst diff --git a/Doc/ldap.rst b/Doc/reference/ldap.rst similarity index 100% rename from Doc/ldap.rst rename to Doc/reference/ldap.rst diff --git a/Doc/ldapurl.rst b/Doc/reference/ldapurl.rst similarity index 100% rename from Doc/ldapurl.rst rename to Doc/reference/ldapurl.rst diff --git a/Doc/ldif.rst b/Doc/reference/ldif.rst similarity index 100% rename from Doc/ldif.rst rename to Doc/reference/ldif.rst diff --git a/Doc/slapdtest.rst b/Doc/reference/slapdtest.rst similarity index 100% rename from Doc/slapdtest.rst rename to Doc/reference/slapdtest.rst From 4ee76616fe1b791d154fdcf7742f28f5bea99c4a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 19:42:50 +0100 Subject: [PATCH 022/369] Doc: Fix underline in ldap.resiter documentation --- Doc/reference/ldap-resiter.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/ldap-resiter.rst b/Doc/reference/ldap-resiter.rst index 0e72e922..66ce6cab 100644 --- a/Doc/reference/ldap-resiter.rst +++ b/Doc/reference/ldap-resiter.rst @@ -20,7 +20,7 @@ derived classes which has these methods: Examples -======== +-------- .. _ldap.resiter.ResultProcessor-example: From eab9b59a41f91006c81b5e8c6e16d5719e5d7f37 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 17:12:59 +0100 Subject: [PATCH 023/369] Doc: Include information from python-ldap.org main page in the docs --- Doc/index.rst | 51 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/Doc/index.rst b/Doc/index.rst index 476e9cc1..5142baad 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -1,10 +1,44 @@ -########################## -python-ldap Documentation -########################## +python-ldap +=========== + + +What is python-ldap? +-------------------- + +* python-ldap provides an object-oriented API to access LDAP + directory servers from `Python`_ programs. +* For LDAP operations the module wraps `OpenLDAP`_'s + client library *libldap* for that purpose. +* Additionally the package contains modules for other LDAP-related stuff: + + * LDIF + * LDAP URLs + * LDAPv3 subschema + +.. _Python: https://www.python.org/ +.. _OpenLDAP: https://www.openldap.org/ + + +Get it! +------- + +:ref:`Download information` is available for several platforms. + + +Mailing list +------------ + +Discussion about the use and future of Python-LDAP occurs in +the ``python-ldap@python.org`` mailing list. + +You can `subscribe or unsubscribe`_ to this list or browse the `list archive`_. + +.. _subscribe or unsubscribe: https://mail.python.org/mailman/listinfo/python-ldap +.. _list archive: https://mail.python.org/pipermail/python-ldap/ + -******** Contents -******** +-------- .. toctree:: :maxdepth: 2 @@ -13,10 +47,8 @@ Contents reference/index.rst - -********************* Bytes/text management -********************* +--------------------- The LDAP protocol states that some fields (distinguised names, relative distinguished names, attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** @@ -50,9 +82,8 @@ The typical usage is as follows; note that only the result's *values* are of the ] -****************** Indices and tables -****************** +------------------ * :ref:`genindex` * :ref:`modindex` From a7d66f9f055744229715aea0ddb42ac7f342a945 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 17:40:20 +0100 Subject: [PATCH 024/369] Doc: Include information from python-ldap.org/docs.html --- Doc/index.rst | 12 +++++++++++ Doc/resources.rst | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 Doc/resources.rst diff --git a/Doc/index.rst b/Doc/index.rst index 5142baad..43f57c02 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -37,6 +37,17 @@ You can `subscribe or unsubscribe`_ to this list or browse the `list archive`_. .. _list archive: https://mail.python.org/pipermail/python-ldap/ +Documentation +------------- + +The documentation for python-ldap 3.x is hosted at `Read the Docs`_. + +You can switch between versions of the library, or download PDF or HTML +versions for offline use, using the right sidebar. + +.. _Read the Docs: http://python-ldap.readthedocs.io/en/latest/ + + Contents -------- @@ -45,6 +56,7 @@ Contents installing.rst reference/index.rst + resources.rst Bytes/text management diff --git a/Doc/resources.rst b/Doc/resources.rst new file mode 100644 index 00000000..56cb1a1a --- /dev/null +++ b/Doc/resources.rst @@ -0,0 +1,55 @@ +Third-party documentation +========================= + +The following documents referenced are not written by python-ldap project +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 `_ + + This also covers SASL. + +* `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`. + + +`LDAP Programming in Python `_ +------------------------------------------------------------------------- + +Another article for getting started with python-ldap. + + + +`RFC 1823 `_ +------------------------------------------------- + +The LDAP Application Program Interface, mainly for LDAPv2. + + + +`LDAPEXT draft `_ +---------------------------------------------------------------------------- + +The Internet draft of the discontinued IETF working group LDAPEXT is of +interest here since the OpenLDAP 2 libs implement this (expired) draft. + + +`OpenLDAP `_ +--------------------------------------- + +It's worth to have a look at the +`manual pages `_ and the +`Developer's FAQ `_. + + +`VSLDAP `_ +---------------------------------------------------------------------------------------- + +VSLDAP Interoperability Test Suite. From f0daeb6d017274ce63495305a01129d4e4a520f8 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 18:32:58 +0100 Subject: [PATCH 025/369] Doc: Include information from python-ldap.org/download.html --- Doc/installing.rst | 157 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 122 insertions(+), 35 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index bcc41be3..cc0ee013 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -1,19 +1,106 @@ -*********************** -Building and installing -*********************** +.. _Download information: -python-ldap is built and installed using the Python DistUtils installed -along with your Python installation: +Installing python-ldap +###################### -:: +Installing from PyPI +==================== + +The preferred point for downloading the “official” source distribution +is now the `PyPI repository`_ which supports installing via `pip`_. +For example:: + + python -m pip install python-ldap + +.. _PyPI repository: https://pypi.python.org/pypi/python-ldap/ +.. _pip: https://pip.pypa.io/en/stable/ + +For installing from PyPI, you will need the :ref:`build prerequisites` +as when installing from source. + +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 + + +Pre-built Binaries +================== + +Because distributions seem to be all over the place, this page +tries to list all the current ones we know of. + +Note that the python-ldap team is not responsible for the binary packages +except the sources you can grab from the PyPI page. Also note that binary +packages are most times not up to date. If you experience troubles +with a binary package, it would be nice if you try to build a recent version +of python-ldap before submitting a bug report to make sure you did not +hit a problem already fixed in recent releases. + +`openSUSE Linux `_ +--------------------------------------------- + +ships with python-ldap and there's an additional +`download repository `_ +which contains builds of latest releases +(see also `OBS package `_ + +`Debian Linux `_ +---------------------------------------- + +Have a look into the +`Debian Package Tracker `_ +to get up to date information which versions are available. + + +Windows +------- - python setup.py build - python setup.py install +Unoficial packages for Windows are available on +`Christoph Gohlke's page `_. -If you have more than one Python interpreter installed locally you should + +`FreeBSD `_ + +Mac OS X +-------- + +You can install directly with pip:: + + xcode-select --install + pip install python-ldap \ + --global-option=build_ext \ + --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" + + +Installing from Source +====================== + + +python-ldap is built and installed using the Python setuptools. +From a source repository:: + + python -m pip install setuptools + python setup.py install + +If you have more than one Python interpreter installed locally, you should use the same one you plan to use python-ldap with. -See further instructions for using DistUtils here: https://docs.python.org/2.7/install/index.html +See further instructions can be found in `Setuptools documentation`_. + + +.. _Setuptools documentation: https://docs.python.org/3/distributing/index.html + + +.. _build prerequisites: Build prerequisites =================== @@ -21,25 +108,18 @@ Build prerequisites The following software packages are required to be installed on the local system when building python-ldap: -- Python version 2.3 or later including its development files: https://www.python.org/ -- OpenLDAP client libs version 2.4.11 or later: https://www.openldap.org/ +- `Python`_ version 2.7, or 3.3 or later including its development files +- `OpenLDAP`_ client libs version 2.4.11 or later It is not possible and not supported to build with prior versions. -- OpenSSL (optional): https://www.openssl.org/ -- cyrus-sasl (optional): https://www.cyrusimap.org/sasl/ -- Kerberos libs, MIT or heimdal (optional) +- `OpenSSL`_ (optional) +- `cyrus-sasl`_ (optional) +- Kerberos libraries, MIT or heimdal (optional) -Installation prerequisites -========================== +.. _Python: https://www.python.org/ +.. _OpenLDAP: https://www.openldap.org/ +.. _OpenSSL: https://www.openssl.org/ +.. _cyrus-sasl: https://www.cyrusimap.org/sasl/ -Furthermore it is required that modules -:py:mod:`pyasn1` and :py:mod:`pyasn1_modules` -are installed. - - https://github.com/etingof/pyasn1 - - https://pypi.python.org/pypi/pyasn1 - - https://pypi.python.org/pypi/pyasn1-modules setup.cfg ========= @@ -70,33 +150,40 @@ documentation of Python's DistUtils. .. _libs-used-label: -Libs used ---------- -.. data:noindex: ldap -.. data:noindex: ldap_r +Libraries used +--------------- + +.. data:: ldap + :noindex: +.. data:: ldap_r + :noindex: The LDAP protocol library of OpenLDAP. ldap_r is the reentrant version and should be preferred. -.. data:noindex: lber +.. data:: lber + :noindex: The BER encoder/decoder library of OpenLDAP. -.. data:noindex: sasl2 +.. data:: sasl2 + :noindex: The Cyrus-SASL library if needed and present during build -.. data:noindex: ssl +.. data:: ssl + :noindex: The SSL/TLS library of OpenSSL if needed and present during build -.. data:noindex: crypto +.. data:: crypto + :noindex: The basic cryptographic library of OpenSSL if needed and present during build Example -============= +------- The following example is for a full-featured build (including SSL and SASL support) of python-ldap with OpenLDAP installed in a different prefix directory From 823760a2438df9994339e0cabf58a76d01bf69e6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 25 Nov 2017 22:56:03 +0100 Subject: [PATCH 026/369] Doc: Include information from python-ldap.org/faq.html This updates some info that was clearly outdated, but it's not a comprehensive refresh. --- Doc/faq.rst | 184 +++++++++++++++++++++++++++++++++++++++++ Doc/index.rst | 1 + Doc/reference/ldap.rst | 2 + 3 files changed, 187 insertions(+) create mode 100644 Doc/faq.rst diff --git a/Doc/faq.rst b/Doc/faq.rst new file mode 100644 index 00000000..511735a5 --- /dev/null +++ b/Doc/faq.rst @@ -0,0 +1,184 @@ +python-ldap FAQ +=============== + +Project +------- + +**Q**: Is python-ldap yet another abandon-ware project? + + **A1**: “Jump on in.” + + **A2**: “Jump into the C ;-)” + + **A3**: see file CHANGES in source distribution + or `repository`_. + +.. _repository: https://github.com/python-ldap/python-ldap/blob/master/CHANGES + + +Usage +----- + +**Q**: Does it work with Python 3? + + **A0**: Yes, from 3.0 on. + + **A1**. For earlier versions, there's `pyldap`_, an independent fork + now merged into pyhon-ldap. + +.. _pyldap: https://pypi.python.org/pypi/pyldap + + +**Q**: Does it work with Python 2.6? (1.5|2.0|2.1|2.2|2.3|2.4|2.5)? + + **A**: No. Old versions of python-ldap are still available from PyPI, though. + + +**Q**: My code imports module ``_ldap``. +That used to work but from version 2.0.0pre that does not work anymore? + + **A**: Despite some outdated programming examples the extension module + ``_ldap`` **MUST NOT** be imported directly unless + you really know what you're doing (e.g. for internal regression testing). + + Import ``ldap`` instead which is a Python wrapper around ``_ldap`` + providing the full functionality. + +**Q**: My script bound to MS Active Directory but a a search operation results +in an 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? + + **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. + Client-chasing of referrals is a broken concept since LDAPv3 does not specify + which credentials to use when chasing the referral. Windows clients are supposed + to simply use their Windows credentials but this does not work in general when + chasing referrals received from and pointing to arbitrary LDAP servers. + + Therefore per default ``libldap`` automatically chases the referrals + internally with an *anonymous* access which fails with MS AD. + + So best thing is to switch this behaviour off:: + + l = ldap.initialize('ldap://foobar') + l.set_option(ldap.OPT_REFERRALS,0) + +**Q**: Why am I seeing ``ldap.SUCCESS`` traceback as output? + + **A**: Most likely you are using one of the non-synchronous calls, and probably + mean to be using a synchronous call + (see detailed explanation in :ref:`sending-ldap-requests`. + +**Q**: Can I use LDAPv2 via python-ldap? + + **A**: Yes, by explicitly setting the class attribute + :attr:`~ldap.LDAPObject.protocol_version`. + + You should not do that nowadays since + `LDAPv2 is considered historic `_ + since many years. + + + +Installing +---------- + +**Q**: Does it work with Windows 32? + + **A**: You can find links to pre-compiled packages for Win32 on the + :ref:`Download information` page. + + +**Q**: Can python-ldap be built against OpenLDAP 2.3 libs or older? + + **A**: No, for recent python-ldap 2.4.x the OpenLDAP 2.4 client libs or newer are required. + Patched builds of python-ldap linked to older libs are not supported by the + python-ldap project. + + +**Q**: During build there are warning messages displayed +telling Lib/ldap.py and Lib/ldap/schema.py are not found:: + + warning: build_py: file Lib/ldap.py (for module ldap) not found + warning: build_py: file Lib/ldap/schema.py (for module ldap.schema) not found + +.. + + **A**: ``ldap`` and ``ldap.schema`` are both module packages + (directories containing various sub-modules). + The messages above are falsely produced by DistUtils. + Don't worry about it. + +.. _install-macosx: + +**Q**: What's the correct way to install on Mac OS X? + + **A**:: + + xcode-select --install + pip install python-ldap \ + --global-option=build_ext \ + --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" + + +**Q**: While importing module ldap some shared lib files are not found. +Error message looks similar to this:: + + ImportError: ld.so.1: /usr/local/bin/python: fatal: liblber.so.2: open failed: No such file or directory + +.. + + **A1**: You need to make sure that the path to ``liblber.so.2`` and + ``libldap.so.2`` is in your ``LD_LIBRARY_PATH`` environment variable. + + **A2**: Alternatively if you're on Linux you can add the path to + ``liblber.so.2`` and ``libldap.so.2`` to ``/etc/ld.so.conf`` + and invoke command ``ldconfig`` afterwards. + + + +Historic +-------- + +**Q**: Can python-ldap 2.x be built against Netscape, Mozilla or Novell libs? + + **A**: Nope. + + +**Q**: My binary version of python-ldap was build with LDAP libs 3.3. +But the python-ldap docs say LDAP libs 2.x are needed. I'm confused! + + Short answer: + See answer above and :ref:`download information` for + a more recent version. + + Long answer: + E.g. some Win32 DLLs floating around for download are based on + the old Umich LDAP code which is not maintained anymore for + ``many`` years! Last Umich 3.3 release was 1997 if I remember correctly. + + The OpenLDAP project took over the Umich code and started releasing + OpenLDAP 1.x series mainly fixing bugs and doing some improvements + to the database backend. Still only LDAPv2 was supported at server + and client side. (Many commercial vendors also derived their products + from the Umich code.) + + OpenLDAP 2.x is a full-fledged LDAPv3 implementation. Still it has + its roots in Umich code but has many more features/improvements. + + +**Q**: While importing module ldap there are undefined references reported. +Error message looks similar to this:: + + ImportError: /usr/local/lib/libldap.so.2: undefined symbol: res_query + +.. + + **A**: Especially on older Linux systems you might have to explicitly link + against ``libresolv``. + + Tweak ``setup.cfg`` to contain this line:: + + libs = lber ldap resolv diff --git a/Doc/index.rst b/Doc/index.rst index 43f57c02..aa930928 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -57,6 +57,7 @@ Contents installing.rst reference/index.rst resources.rst + faq.rst Bytes/text management diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index d526370f..c80e3869 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -614,6 +614,8 @@ with names ending in :py:const:`_ext` or :py:const:`_ext_s`: request. +.. _sending-ldap-requests: + Sending LDAP requests --------------------- From 0fc0d0cfd6b9285bb5c65b2fb1abd3aa890936a5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 14:08:56 +0100 Subject: [PATCH 027/369] Doc: Prevent doc build from picking up system-wide python-ldap --- Doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index 63fc24e7..9314231b 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -17,7 +17,7 @@ # If your extensions are in another directory, add it here. _doc_dir = os.path.dirname(__file__) sys.path.append(_doc_dir) -sys.path.append(os.path.join(_doc_dir, '../Lib/')) +sys.path.insert(0, os.path.join(_doc_dir, '../Lib/')) sys.path.insert(0, os.path.join(_doc_dir, '../Lib/ldap')) # Import fake `_ldap` module From 574c902401aa233bf0896e3dcfccbaa0017b3030 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 14:09:49 +0100 Subject: [PATCH 028/369] Doc: Fix link-line syntax Words ending with underlines are ReST syntax for links. --- Lib/ldap/sasl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index fa6f4f5c..b54bf2ae 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -54,7 +54,7 @@ def callback(self, cb_id, challenge, prompt, defresult): """ The callback method will be called by the sasl_bind_s() method several times. Each time it will provide the id, which - tells us what kind of information is requested (the CB_ ... + tells us what kind of information is requested (the CB_* constants above). The challenge might be a short (english) text or some binary string, from which the return value is calculated. The prompt argument is always a human-readable description string; From 680c3421e041a56bbdaa87fe4698beaada9c0580 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 14:35:48 +0100 Subject: [PATCH 029/369] Omit SysLogHandler when /dev/log does not exist slaptests use SysLogHandler() to write logs to syslog. Don't create a SysLogHandler when /dev/log does not exist. This fixes a bug when building and testing python-ldap in containers or restricted build environments. https://github.com/python-ldap/python-ldap/pull/28 See: https://github.com/python-ldap/python-ldap/issues/43 Signed-off-by: Christian Heimes --- Lib/slapdtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 0bef1c15..182097a6 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -59,7 +59,7 @@ def combined_logger( pass # for writing to syslog new_logger = logging.getLogger(log_name) - if sys_log_format: + if sys_log_format and os.path.exists('/dev/log'): my_syslog_formatter = logging.Formatter( fmt=' '.join((log_name, sys_log_format))) my_syslog_handler = logging.handlers.SysLogHandler( From 92bd2e74804bdddb6600940886782e7e7c6b4ead Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sun, 26 Nov 2017 17:44:37 +0100 Subject: [PATCH 030/369] Add doc tests and spelling tests Signed-off-by: Christian Heimes --- .travis.yml | 36 ++++++--- Doc/conf.py | 7 ++ Doc/spelling_wordlist.txt | 151 ++++++++++++++++++++++++++++++++++++++ tox.ini | 17 ++++- 4 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 Doc/spelling_wordlist.txt diff --git a/.travis.yml b/.travis.yml index 14c0965d..581013cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,5 @@ language: python -python: -- '2.7' -- '3.3' -- '3.4' -- '3.5' -- '3.6' -# Note: when updating Python versions, also change setup.py and tox.ini - sudo: false cache: pip @@ -17,9 +9,33 @@ addons: packages: - ldap-utils - slapd + - enchant -env: - - WITH_GCOV=1 +# 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.3 + env: + - TOXENV=py33 + - 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: 3.6 + env: TOXENV=doc install: - pip install "pip>=7.1.0" diff --git a/Doc/conf.py b/Doc/conf.py index 9314231b..eb9f5a01 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -33,6 +33,13 @@ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] +try: + import sphinxcontrib.spelling +except ImportError: + pass +else: + extensions.append('sphinxcontrib.spelling') + # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt new file mode 100644 index 00000000..fcc227f3 --- /dev/null +++ b/Doc/spelling_wordlist.txt @@ -0,0 +1,151 @@ +args +async +attr +attrlist +attrList +attrs +attrsonly +attrsOnly +attrtype +authzId +automagically +backend +behaviour +BER +bindname +boolean +booleanValue +Bytestrings +cancelled +canonicalization +cb +cfg +changeNumber +changesOnly +changeType +changeTypes +cidict +clientctrls +conf +Continously +controlType +controlValue +criticality +cryptographic +cyrus +defresult +dereferenced +dereferencing +desc +directoryOperation +distinguished +distributedOperation +dit +dn +DN +dSAOperation +encodedControlValue +encodedResponseValue +extype +exvalue +favour +filterstr +filterStr +formatOID +func +heimdal +hostport +hrefTarget +hrefText +ignoreResultsNumber +integerValue +Interoperability +isn +Keepalive +Kerberos +keyerror +knownLDAPControls +kwarg +ldap +ldapadd +ldapControls +ldapControlTuples +ldapdelete +ldapi +LDAPObject +ldaps +ldapurl +ldapwhoami +ldif +LDIFWriter +libldap +libs +Libs +modlist +modrdn +msgid +multi +nameoroid +nots +Novell +objectClass +oc +oid +oids +openldap +postalAddress +pre +previousDN +processResultsCount +Proxied +py +rdn +reentrant +refmodule +refreshAndPersist +refreshDeletes +refreshOnly +requestName +requestValue +resiter +respvalue +ResultProcessor +returnECs +ruleid +rundir +sasl +searchRoot +searchScope +sed +serverctrls +sessionSourceIp +sessionSourceName +sessionTrackingIdentifier +sizelimit +slapd +stderr +stdout +str +Subclasses +subentry +subschema +substr +subtree +syncrepl +syntaxes +timelimit +tracebacks +tuple +tuples +UDP +Umich +unparsing +unsigend +uri +urlPrefix +urlscheme +userApplications +userPassword +usr +uuids +whitespace diff --git a/tox.ini b/tox.ini index 22ca4a8e..51f8981d 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,py33,py34,py35,py36,coverage-report +envlist = py27,py33,py34,py35,py36,doc,coverage-report [testenv] deps = coverage @@ -31,3 +31,18 @@ commands = {envpython} -m coverage combine {envpython} -m coverage report --show-missing {envpython} -m coverage html + +[testenv:doc] +basepython = python3 +deps = + docutils + markdown + sphinx + sphinxcontrib-spelling +commands = + {envpython} setup.py check --restructuredtext --metadata --strict + {envpython} -m markdown README -f {envtmpdir}/README.html + {envpython} -m sphinx -v -W -b html -d {envtmpdir}/doctrees \ + {toxinidir}/Doc {envtmpdir}/html + {envpython} -m sphinx -v -W -b spelling -d {envtmpdir}/doctrees \ + {toxinidir}/Doc {envtmpdir}/spelling From 16e8529a5cb50417ed41a89311c48b891c9e78d8 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 15:23:15 +0100 Subject: [PATCH 031/369] Fix spelling errors Signed-off-by: Christian Heimes --- Doc/faq.rst | 2 +- Doc/index.rst | 2 +- Doc/installing.rst | 2 +- Doc/reference/ldap.rst | 10 +++++----- Doc/reference/ldapurl.rst | 4 ++-- Lib/ldap/__init__.py | 2 +- Lib/ldap/async.py | 2 +- Lib/ldap/constants.py | 2 +- Lib/ldap/controls/__init__.py | 2 +- Lib/ldap/controls/deref.py | 2 +- Lib/ldap/controls/psearch.py | 4 ++-- Lib/ldap/controls/sss.py | 2 +- Lib/ldap/controls/vlv.py | 2 +- Lib/ldap/filter.py | 2 +- Lib/ldap/ldapobject.py | 2 +- Lib/ldap/sasl.py | 2 +- Lib/ldap/syncrepl.py | 2 +- Lib/ldif.py | 6 +++--- Lib/slapdtest.py | 2 +- README | 2 +- 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Doc/faq.rst b/Doc/faq.rst index 511735a5..58bb81e3 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -24,7 +24,7 @@ Usage **A0**: Yes, from 3.0 on. **A1**. For earlier versions, there's `pyldap`_, an independent fork - now merged into pyhon-ldap. + now merged into python-ldap. .. _pyldap: https://pypi.python.org/pypi/pyldap diff --git a/Doc/index.rst b/Doc/index.rst index aa930928..f478d10c 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -63,7 +63,7 @@ Contents Bytes/text management --------------------- -The LDAP protocol states that some fields (distinguised names, relative distinguished names, +The LDAP protocol states that some fields (distinguished names, relative distinguished names, attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** contain any type of data, and thus be treated as bytes. diff --git a/Doc/installing.rst b/Doc/installing.rst index cc0ee013..c78bd7f2 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -60,7 +60,7 @@ to get up to date information which versions are available. Windows ------- -Unoficial packages for Windows are available on +Unofficial packages for Windows are available on `Christoph Gohlke's page `_. diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index c80e3869..8844e594 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -40,7 +40,7 @@ This module defines the following functions: when using multiple URIs you cannot determine to which URI your client gets connected. - Note that internally the OpenLDAP funtion + Note that internally the OpenLDAP function `ldap_initialize(3) `_ is called which just initializes the LDAP connection struct in the C API - nothing else. Therefore the first call to an operation method (bind, @@ -141,7 +141,7 @@ following option identifiers are defined as constants: .. py:data:: OPT_DEREF - Specifies how alias derefencing is done within the underlying LDAP C lib. + Specifies how alias dereferencing is done within the underlying LDAP C lib. .. py:data:: OPT_ERROR_STRING @@ -296,7 +296,7 @@ The module defines the following exceptions: .. py:exception:: LDAPError - This is the base class of all execeptions raised by the module :py:mod:`ldap`. + This is the base class of all exceptions raised by the module :py:mod:`ldap`. Unlike the C interface, errors are not returned as result codes, but are instead turned into exceptions, raised as soon an the error condition is detected. @@ -413,7 +413,7 @@ The module defines the following exceptions: .. py:exception:: IS_LEAF - The object specified is a leaf of the diretcory tree. + The object specified is a leaf of the directory tree. Sets the :py:const:`matched` field of the exception dictionary value. .. py:exception:: LOCAL_ERROR @@ -836,7 +836,7 @@ and wait for and return with the server's result, or with This method is used to wait for and return the result of an operation previously initiated by one of the LDAP *asynchronous* operations - (eg :py:meth:`search()`, :py:meth:`modify()`, etc.) + (e.g. :py:meth:`search()`, :py:meth:`modify()`, etc.) The *msgid* parameter is the integer identifier returned by that method. The identifier is guaranteed to be unique across an LDAP session, diff --git a/Doc/reference/ldapurl.rst b/Doc/reference/ldapurl.rst index c13061cd..86d0817b 100644 --- a/Doc/reference/ldapurl.rst +++ b/Doc/reference/ldapurl.rst @@ -9,7 +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. Compability note: This +alone without the rest of the python-ldap package. Compatibility note: This module has been solely tested on Python 2.x and above. .. seealso:: @@ -91,7 +91,7 @@ Example ^^^^^^^ Important security advice: -For security reasons you shouldn't specify passwords in LDAP URLs +For security reasons you should not specify passwords in LDAP URLs unless you really know what you are doing. The following example demonstrates how to parse a LDAP URL diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index b22a6f43..7cb16b0e 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -94,5 +94,5 @@ def release(self): # More constants -# For compability of 2.3 and 2.4 OpenLDAP API +# For compatibility of 2.3 and 2.4 OpenLDAP API OPT_DIAGNOSTIC_MESSAGE = OPT_ERROR_STRING diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 0dd4940c..23c56605 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -36,7 +36,7 @@ def __str__(self): class AsyncSearchHandler: """ - Class for stream-processsing LDAP search results + Class for stream-processing LDAP search results Arguments: diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index c5ac6c1b..4c109ac4 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -288,7 +288,7 @@ class Str(Constant): TLSInt('OPT_X_TLS_PEERCERT', optional=True), # only available if OpenSSL supports it => might cause - # backward compability problems + # backward compatibility problems TLSInt('OPT_X_TLS_CRLCHECK', optional=True), TLSInt('OPT_X_TLS_CRLFILE', optional=True), diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index 932fa536..43b938d8 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -96,7 +96,7 @@ class attributes class LDAPControl(RequestControl,ResponseControl): """ Base class for combined request/response controls mainly - for backward-compability to python-ldap 2.3.x + for backward-compatibility to python-ldap 2.3.x """ def __init__(self,controlType=None,criticality=False,controlValue=None,encodedControlValue=None): diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index d1b5ce9e..762ab855 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -26,7 +26,7 @@ # Request types #--------------------------------------------------------------------------- -# For compability with ASN.1 declaration in I-D +# For compatibility with ASN.1 declaration in I-D AttributeList = AttributeDescriptionList class DerefSpec(univ.Sequence): diff --git a/Lib/ldap/controls/psearch.py b/Lib/ldap/controls/psearch.py index 91a5c241..74e40a33 100644 --- a/Lib/ldap/controls/psearch.py +++ b/Lib/ldap/controls/psearch.py @@ -40,13 +40,13 @@ class PersistentSearchControl(RequestControl): Implements the request control for persistent search. changeTypes - List of strings specifiying the types of changes returned by the server. + List of strings specifying the types of changes returned by the server. Setting to None requests all changes. changesOnly Boolean which indicates whether only changes are returned by the server. returnECs Boolean which indicates whether the server should return an - Entry Change Notication response control + Entry Change Notification response control """ class PersistentSearchControlValue(univ.Sequence): diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 5d4955d1..a5312d2f 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -126,7 +126,7 @@ def decodeControlValue(self, encoded): self.attributeType = attribute_type else: self.attributeType = None - # backward compability class attributes + # backward compatibility class attributes self.result = self.sortResult self.attribute_type_error = self.attributeType diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index 74d107b3..9fea2f03 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -134,7 +134,7 @@ def decodeControlValue(self,encoded): self.contextID = str(context_id) else: self.contextID = None - # backward compability class attributes + # backward compatibility class attributes self.target_position = self.targetPosition self.content_count = self.contentCount self.result = self.virtualListViewResult diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py index f5d92787..56665dc4 100644 --- a/Lib/ldap/filter.py +++ b/Lib/ldap/filter.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. -Compability: +Compatibility: - Tested with Python 2.0+ """ diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 36f98cfb..c5e62bd0 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -647,7 +647,7 @@ def result(self,msgid=ldap.RES_ANY,all=1,timeout=None): This method is used to wait for and return the result of an operation previously initiated by one of the LDAP asynchronous - operation routines (eg search(), modify(), etc.) They all + operation routines (e.g. search(), modify(), etc.) They all returned an invocation identifier (a message id) upon successful initiation of their operation. This id is guaranteed to be unique across an LDAP session, and can be used to request the diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index b54bf2ae..cc0a2ead 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -55,7 +55,7 @@ def callback(self, cb_id, challenge, prompt, defresult): The callback method will be called by the sasl_bind_s() method several times. Each time it will provide the id, which tells us what kind of information is requested (the CB_* - constants above). The challenge might be a short (english) text + constants above). The challenge might be a short (English) text or some binary string, from which the return value is calculated. The prompt argument is always a human-readable description string; The defresult is a default value provided by the sasl library diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 8442726a..35d4c452 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -484,7 +484,7 @@ def syncrepl_set_cookie(self, cookie): def syncrepl_get_cookie(self): """ - Called by syncrepl_search() to retreive the cookie stored by syncrepl_set_cookie() + Called by syncrepl_search() to retrieve the cookie stored by syncrepl_set_cookie() """ pass diff --git a/Lib/ldif.py b/Lib/ldif.py index 82c4e3f8..aead2ef3 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -209,7 +209,7 @@ def unparse(self,dn,record): def CreateLDIF(dn,record,base64_attrs=None,cols=76): """ Create LDIF single formatted record including trailing empty line. - This is a compability function. Use is deprecated! + This is a compatibility function. Use is deprecated! dn string-representation of distinguished name @@ -449,7 +449,7 @@ def parse_entry_records(self): def parse(self): """ - Invokes LDIFParser.parse_entry_records() for backward compability + Invokes LDIFParser.parse_entry_records() for backward compatibility """ return self.parse_entry_records() # parse() @@ -633,7 +633,7 @@ def handle(self,dn,entry): def ParseLDIF(f,ignore_attrs=None,maxentries=0): """ Parse LDIF records read from file. - This is a compability function. Use is deprecated! + This is a compatibility function. Use is deprecated! """ ldif_parser = LDIFRecordList( f,ignored_attr_types=ignore_attrs,max_entries=maxentries,process_url_schemes=0 diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 182097a6..948ec099 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -83,7 +83,7 @@ class SlapdObject(object): This class creates a temporary data store for slapd, runs it listening on a private Unix domain socket and TCP port, - and initialises it with a top-level entry and the root user. + and initializes it with a top-level entry and the root user. When a reference to an instance of this class is lost, the slapd server is shut down. diff --git a/README b/README index 2a88bf27..81db9bb3 100644 --- a/README +++ b/README @@ -14,7 +14,7 @@ stuff (e.g. processing LDIF, LDAPURLs, LDAPv3 sub-schema, etc.). Not included: Direct BER support -See INSTALL for version compability +See INSTALL for version compatibility See TODO for planned features. Contributors welcome. From 5f8c419d373f84be91d27e98718658907a764ceb Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 15:34:44 +0100 Subject: [PATCH 032/369] Add tests for start_tls https://github.com/python-ldap/python-ldap/pull/42 Signed-off-by: Christian Heimes --- Lib/slapdtest.py | 18 +++++++++ Tests/certs/README | 24 ++++++++++++ Tests/certs/ca.conf | 77 ++++++++++++++++++++++++++++++++++++ Tests/certs/ca.pem | 80 ++++++++++++++++++++++++++++++++++++++ Tests/certs/client.conf | 16 ++++++++ Tests/certs/client.key | 28 ++++++++++++++ Tests/certs/client.pem | 83 +++++++++++++++++++++++++++++++++++++++ Tests/certs/gencerts.sh | 67 ++++++++++++++++++++++++++++++++ Tests/certs/gennssdb.sh | 28 ++++++++++++++ Tests/certs/server.conf | 16 ++++++++ Tests/certs/server.key | 28 ++++++++++++++ Tests/certs/server.pem | 86 +++++++++++++++++++++++++++++++++++++++++ Tests/t_cext.py | 29 ++++++++++++++ 13 files changed, 580 insertions(+) create mode 100644 Tests/certs/README create mode 100644 Tests/certs/ca.conf create mode 100644 Tests/certs/ca.pem create mode 100644 Tests/certs/client.conf create mode 100644 Tests/certs/client.key create mode 100644 Tests/certs/client.pem create mode 100755 Tests/certs/gencerts.sh create mode 100755 Tests/certs/gennssdb.sh create mode 100644 Tests/certs/server.conf create mode 100644 Tests/certs/server.key create mode 100644 Tests/certs/server.pem diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 182097a6..1df142ba 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -37,6 +37,12 @@ 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 """ LOCALHOST = '127.0.0.1' @@ -140,6 +146,15 @@ def __init__(self): self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) ldapi_path = os.path.join(self.testrundir, 'ldapi') self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) + # TLS certs + capath = os.path.abspath(os.path.join( + os.getcwd(), 'Tests/certs' + )) + self.cafile = os.path.join(capath, 'ca.pem') + self.servercert = os.path.join(capath, 'server.pem') + self.serverkey = os.path.join(capath, 'server.key') + self.clientcert = os.path.join(capath, 'client.pem') + self.clientkey = os.path.join(capath, 'client.key') def _check_requirements(self): binaries = [ @@ -218,6 +233,9 @@ def gen_config(self): 'rootpw': self.root_pw, 'root_uid': os.getuid(), 'root_gid': os.getgid(), + 'cafile': self.cafile, + 'servercert': self.servercert, + 'serverkey': self.serverkey, } return self.slapd_conf_template % config_dict diff --git a/Tests/certs/README b/Tests/certs/README new file mode 100644 index 00000000..4be616ae --- /dev/null +++ b/Tests/certs/README @@ -0,0 +1,24 @@ +python-ldap test certificates +============================= + +Certificates and keys +--------------------- + +* ``ca.pem``: internal root CA certificate +* ``server.pem``: TLS server certificate for slapd, signed by root CA. The + server cert is valid for DNS Name ``localhost`` and IPs ``127.0.0.1`` and + ``:1``. +* ``server.key``: private key for ``server.pem``, no password protection +* ``client.pem``: certificate for TLS client cert authentication, signed by + root CA. +* ``client.key``: private key for ``client.pem``, no password protection + +Configuration and scripts +------------------------- + +* ``ca.conf`` contains the CA definition as well as extensions for the + client and server certificates. +* ``client.conf`` and ``server.conf`` hold the subject and base configuration + for server and client certs. +* ``gencerts.sh`` creates new CA, client and server certificates. +* ``gennssdb.sh`` can be used to create a NSSDB for all certs and keys. diff --git a/Tests/certs/ca.conf b/Tests/certs/ca.conf new file mode 100644 index 00000000..4fb2e2ba --- /dev/null +++ b/Tests/certs/ca.conf @@ -0,0 +1,77 @@ +# Written by Christian Heimes + +[default] +ca = "ca" +tmpdir = $ENV::CATMPDIR +outdir = $ENV::CAOUTDIR +name_opt = multiline,-esc_msb,utf8 + +[req] +default_bits = 2048 +encrypt_key = no +default_md = sha256 +utf8 = yes +string_mask = utf8only +prompt = no +distinguished_name = ca_dn + +[ca_dn] +countryName = "DE" +organizationName = "python-ldap" +organizationalUnitName = "slapd-test" +commonName = "Python LDAP Test CA" + +[ca] +default_ca = python_ldap_ca + +[python_ldap_ca] +certificate = $outdir/$ca.pem +private_key = $outdir/$ca.key +new_certs_dir = $tmpdir +serial = $tmpdir/$ca.crt.srl +crlnumber = $tmpdir/$ca.crl.srl +database = $tmpdir/$ca.db +unique_subject = no +default_days = 1461 +default_md = sha256 +policy = match_pol +email_in_dn = no +preserve = no +name_opt = $name_opt +cert_opt = ca_default +copy_extensions = none +default_crl_days = 365 + +[match_pol] +countryName = match +stateOrProvinceName = optional +localityName = optional +organizationName = match +organizationalUnitName = match +commonName = supplied + +[ca_ext] +basicConstraints = critical,CA:true +keyUsage = critical,keyCertSign,cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always + +[server_san] +DNS.1 = localhost +IP.1 = 127.0.0.1 +IP.2 = ::1 + +[server_ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature,keyEncipherment +extendedKeyUsage = critical,serverAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +subjectAltName = @server_san + +[client_ext] +basicConstraints = critical,CA:false +keyUsage = critical,digitalSignature +extendedKeyUsage = critical,clientAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always diff --git a/Tests/certs/ca.pem b/Tests/certs/ca.pem new file mode 100644 index 00000000..ffd6a5e1 --- /dev/null +++ b/Tests/certs/ca.pem @@ -0,0 +1,80 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA + Validity + Not Before: Nov 28 13:49:28 2017 GMT + Not After : Nov 28 13:49:28 2021 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:b4:bd:47:78:d9:62:4e:8e:ae:18:53:b8:8e:9f: + 0b:13:ea:f2:59:c0:1d:7f:0b:5c:5a:ed:c8:b8:cd: + ff:66:94:c4:40:d5:cd:4c:da:06:d2:35:1c:6a:c5: + 76:70:6d:f0:81:8a:25:e4:35:c8:d5:79:17:3e:35: + ec:2b:97:73:77:6c:04:7f:1f:0b:b5:f3:fd:1a:cd: + 76:2c:5c:17:45:6a:6d:c4:d1:6c:f8:09:ba:a0:f8: + 57:44:d7:b4:1c:a4:be:a4:4b:e4:76:34:1c:b1:0b: + 12:42:7e:79:e4:01:e0:5f:8f:6c:03:2d:2a:b6:4e: + 72:85:f1:b9:ac:d5:22:a7:0a:fc:5a:0c:e5:75:8f: + 9f:20:c2:14:ab:53:6a:b0:e6:8a:0d:f4:97:53:7f: + f7:79:29:dd:ea:df:ae:39:6f:59:d8:b3:8e:da:bf: + 08:cd:03:ea:2a:91:33:c9:6f:21:a6:56:06:10:c1: + 40:4b:31:95:b0:47:2a:1e:2f:7d:eb:9b:f1:d4:27: + af:1a:5d:bf:0f:b4:f5:d5:b8:1d:04:61:fa:06:af: + 49:6f:ff:99:88:f3:44:16:ed:3b:f1:c8:ea:31:0a: + f3:f5:97:7a:17:08:e5:e8:8f:dd:1d:c8:8f:55:bf: + 47:93:9a:be:84:99:6f:f3:99:61:5d:b7:13:56:34: + 04:91 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Subject Key Identifier: + 3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + X509v3 Authority Key Identifier: + keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + + Signature Algorithm: sha256WithRSAEncryption + 03:d2:e6:f5:e4:1d:99:2e:df:02:b9:5b:f7:e0:82:ca:39:76: + 5c:8f:3d:f1:5f:d4:bd:1b:4b:4f:35:4b:5d:f6:21:8b:c0:87: + 23:de:7e:9d:93:1b:67:60:6d:df:7b:95:f0:6a:87:94:5c:f9: + a3:62:50:7b:68:02:b0:5c:d2:4b:f9:b1:97:ba:24:84:47:6e: + e1:e9:d6:e0:fa:2f:f7:9b:c5:67:3a:eb:8e:bc:b7:8f:31:7d: + 5c:ba:91:ce:9d:2c:91:85:32:74:e7:e9:ba:07:61:7c:8b:55: + 69:db:f9:7b:1d:ef:55:fc:bf:58:fc:99:66:9c:9e:92:35:75: + 53:0a:31:e1:34:8a:cf:bc:79:b0:0e:ac:bf:aa:f0:e3:74:88: + f2:a6:5c:38:ad:21:31:ea:cd:e2:57:01:6b:9e:0b:24:62:78: + ac:6a:88:ba:ae:be:20:27:63:ab:3b:e9:2c:c0:5c:81:c5:e7: + e6:fe:a7:09:30:b4:28:65:51:6f:1b:1d:22:f8:30:7d:87:ae: + da:28:99:5c:8b:15:f0:b7:45:d0:1d:3b:ad:c5:29:4a:ed:11: + 68:c4:af:28:a3:0b:9f:1b:c8:86:56:80:3d:6b:d3:1f:c6:b0: + 3d:4a:39:1c:10:57:2d:22:df:d7:7b:ad:7d:f9:12:47:bb:33: + 29:61:14:c4 +-----BEGIN CERTIFICATE----- +MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 +MTM0OTI4WjBWMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNVBAMME1B5dGhvbiBMREFQIFRlc3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0vUd42WJOjq4YU7iOnwsT +6vJZwB1/C1xa7ci4zf9mlMRA1c1M2gbSNRxqxXZwbfCBiiXkNcjVeRc+Newrl3N3 +bAR/Hwu18/0azXYsXBdFam3E0Wz4Cbqg+FdE17QcpL6kS+R2NByxCxJCfnnkAeBf +j2wDLSq2TnKF8bms1SKnCvxaDOV1j58gwhSrU2qw5ooN9JdTf/d5Kd3q3645b1nY +s47avwjNA+oqkTPJbyGmVgYQwUBLMZWwRyoeL33rm/HUJ68aXb8PtPXVuB0EYfoG +r0lv/5mI80QW7TvxyOoxCvP1l3oXCOXoj90dyI9Vv0eTmr6EmW/zmWFdtxNWNASR +AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBQ9gOO7lJfb3d/IGiOKyyvBPRrmhTAfBgNVHSMEGDAWgBQ9gOO7lJfb3d/I +GiOKyyvBPRrmhTANBgkqhkiG9w0BAQsFAAOCAQEAA9Lm9eQdmS7fArlb9+CCyjl2 +XI898V/UvRtLTzVLXfYhi8CHI95+nZMbZ2Bt33uV8GqHlFz5o2JQe2gCsFzSS/mx +l7okhEdu4enW4Pov95vFZzrrjry3jzF9XLqRzp0skYUydOfpugdhfItVadv5ex3v +Vfy/WPyZZpyekjV1Uwox4TSKz7x5sA6sv6rw43SI8qZcOK0hMerN4lcBa54LJGJ4 +rGqIuq6+ICdjqzvpLMBcgcXn5v6nCTC0KGVRbxsdIvgwfYeu2iiZXIsV8LdF0B07 +rcUpSu0RaMSvKKMLnxvIhlaAPWvTH8awPUo5HBBXLSLf13utffkSR7szKWEUxA== +-----END CERTIFICATE----- diff --git a/Tests/certs/client.conf b/Tests/certs/client.conf new file mode 100644 index 00000000..774dc3a4 --- /dev/null +++ b/Tests/certs/client.conf @@ -0,0 +1,16 @@ +# Written by Christian Heimes + +[req] +default_bits = 2048 +encrypt_key = no +default_md = sha256 +utf8 = yes +string_mask = utf8only +prompt = no +distinguished_name = client_dn + +[client_dn] +countryName = "DE" +organizationName = "python-ldap" +organizationalUnitName = "slapd-test" +commonName = "client" diff --git a/Tests/certs/client.key b/Tests/certs/client.key new file mode 100644 index 00000000..81c3e766 --- /dev/null +++ b/Tests/certs/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4aZb1WfASk2JR +VAwFMMQCJNkbBDj7d8QdqSMa32jgTG8m/0N5RYGhdIi6zVeO2GmrSDuRd9Lv5hqu +6ZgFI/rRCYSZHrIe6MXSOs6ZZKHIeQYkZcKa7Eg1FWQPBS51FJjYcotoHj5uxyKZ +pOBMC63qODgq77G4zcqIKVswKS34KAIwvBAAUmTMtyMOOEh8oZm4V4ILpFFNBfs9 +CcyKMcAtCiFXOYZQ0G16ExzT560FMxMmCHd8HkevhfgFlKZb9/bsHNb+tvBwTQNm +eUl4gnPDJk6YLBGIscBImvSRJu8o07aZBvAZvRhPa5iX/mtnlg0pDQ9jiGFK24qB +VPQKtwHjAgMBAAECggEBAIYO7Fir6uP2FHD+4kYbr0HHu7PyG+JirETLoeN1KW50 +4hC9XDWam6PdbVAu2knTdO248uTK8KLI6fjhg0upXjn761LMh7wEh1pOucW07A8q +O5bWCuRIhC1iwXxRzfX65SnkCwfDhKtPRA3hV9SwYNt1xw8fdFjd8S+OsOWP6gUN +A33rBN6L44zfabZmcIO4DvSjDP4zoOL640uFg9dyf2yHzFI+q4mZgMK2iW5TZILl +pNfdQF/hZsqrme+kzUyEtQHzLxGk1twYlFubePS0K1wzXRw6OqFRA0+pYNYP+JmJ +bQs1k/e/y5qRiD9+UzR3MOTASl9BWDu9KCK1LF5pqYECgYEA7SD43REkVmQkFinf +Re1kfOTkqCub1t4veTZBqVBNCF10fnQwPk1UQ2PShum1iJun6S3P0NzJP/svGc0N +m44XJaD8QHnXdsDkHGx36/Cj15+VYpGvSpkPUgVQk4Q82n29Y3v8/xR6xqhXiB2N +kuWHig8WIrrc690mUlL1NLQvH+ECgYEAxxaggQXlBTdDSMj2x3Y/0TJlkese2Oet +JxWu6xGfjrsjMxZj1c5C3ESeKws3Z1eH9nk3AVzJ/q/+2SyCM3lM+eFz6dAtKVDB +Olly4YhxoEZDgW4JXMVLP8d7xNDxWk9y7UitpvfDJ8J0hf//y9KR2jnokkfSKScN +AsqrMe7+6kMCgYEAgndztV3rGkU6vZ8II1c7xKPDUuu7cHsKr6w0cE2oNIQGxlRy +/rRZOkK/4E7R/Hl35wm3n3j6mWNARPfXFtEU1zU91NO0wrfaSfE8AeqCmu5IqNTz +Fx4jmcMm1CMbwDMScpwTVN0VuBuDHXb1H+99pW4rhaw+RN+GaCEQnJDOpMECgYBv +UTOFcOpRNEkm1VdGx9N/ARLRuAmTdlbW18TqIvx4LiLMWeSQk7fGuYdGwgrEeajI +I5ah6GP5SCbS/5P9fAGSZoENZx0ZUNH58jHN8SC3YRI1uHT7rkUY8E1ACyQoPuwf +yNdv2HECNjQ5CJ7aNG7g+igUQpw77l3UBcYbMWrPSQKBgG+R+nFGbjJ7oHyvJyJI +C3rlD6vay9BoXmjA05dCO36zDcq5xZqwLnQmYkFgBw+RwvwV8gbBirG7GEpy0rdt +5YbHwiI6fMfLsXocU9f86o2dhhpA2o42ig3mfe8X3LteWtmRfJ3BtHbW2ZWS8Kvx +MnZGm64AmjWV5oJb7e5jpgu6 +-----END PRIVATE KEY----- diff --git a/Tests/certs/client.pem b/Tests/certs/client.pem new file mode 100644 index 00000000..8133c6dd --- /dev/null +++ b/Tests/certs/client.pem @@ -0,0 +1,83 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA + Validity + Not Before: Nov 28 13:49:28 2017 GMT + Not After : Nov 28 13:49:28 2021 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:b8:69:96:f5:59:f0:12:93:62:51:54:0c:05:30: + c4:02:24:d9:1b:04:38:fb:77:c4:1d:a9:23:1a:df: + 68:e0:4c:6f:26:ff:43:79:45:81:a1:74:88:ba:cd: + 57:8e:d8:69:ab:48:3b:91:77:d2:ef:e6:1a:ae:e9: + 98:05:23:fa:d1:09:84:99:1e:b2:1e:e8:c5:d2:3a: + ce:99:64:a1:c8:79:06:24:65:c2:9a:ec:48:35:15: + 64:0f:05:2e:75:14:98:d8:72:8b:68:1e:3e:6e:c7: + 22:99:a4:e0:4c:0b:ad:ea:38:38:2a:ef:b1:b8:cd: + ca:88:29:5b:30:29:2d:f8:28:02:30:bc:10:00:52: + 64:cc:b7:23:0e:38:48:7c:a1:99:b8:57:82:0b:a4: + 51:4d:05:fb:3d:09:cc:8a:31:c0:2d:0a:21:57:39: + 86:50:d0:6d:7a:13:1c:d3:e7:ad:05:33:13:26:08: + 77:7c:1e:47:af:85:f8:05:94:a6:5b:f7:f6:ec:1c: + d6:fe:b6:f0:70:4d:03:66:79:49:78:82:73:c3:26: + 4e:98:2c:11:88:b1:c0:48:9a:f4:91:26:ef:28:d3: + b6:99:06:f0:19:bd:18:4f:6b:98:97:fe:6b:67:96: + 0d:29:0d:0f:63:88:61:4a:db:8a:81:54:f4:0a:b7: + 01:e3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature + X509v3 Extended Key Usage: critical + TLS Web Client Authentication + X509v3 Subject Key Identifier: + C5:E2:E2:72:A6:55:C1:FB:D3:4B:11:01:D0:A0:AE:99:B6:8F:F1:39 + X509v3 Authority Key Identifier: + keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + + Signature Algorithm: sha256WithRSAEncryption + 11:76:03:a3:a6:9d:4e:3e:74:93:50:3b:31:d5:c7:d0:a8:34: + 84:01:6b:a2:08:cd:89:11:57:97:a2:e9:83:f0:a6:ef:03:8c: + b2:8d:31:8f:ee:b3:c9:8c:49:1f:e4:fc:21:58:8d:22:c2:17: + 96:2e:b1:d1:64:03:d6:48:70:41:65:22:38:bf:9f:00:dd:a8: + 82:a0:df:22:e0:9a:1b:27:16:4b:db:7f:b0:fe:9f:a8:80:90: + 69:ad:35:be:e6:95:fe:f9:64:96:0a:d8:d7:8e:aa:63:fb:9a: + d5:d0:e9:cb:8c:f3:08:c0:0c:26:72:c9:c6:29:5c:cf:3c:9e: + e2:38:37:5a:16:2f:70:82:0b:e6:80:27:90:bd:81:b4:58:52: + 6c:4e:b7:77:fc:49:76:7a:4f:6c:ee:ff:02:1f:f4:49:6e:78: + c3:fa:cd:ee:d3:e7:e5:05:a4:16:97:48:16:44:9f:81:1c:ac: + a9:30:0e:1d:ea:15:32:d7:2b:1e:68:92:0a:9c:5b:b2:27:57: + d3:21:3e:76:71:94:2d:da:76:61:aa:64:99:b7:54:1c:ba:e2: + a5:b3:21:a4:a8:36:fe:5d:a3:73:6a:5e:77:af:ab:9a:d9:62: + 00:f0:fd:90:1e:b6:cc:57:5d:92:0d:16:16:97:78:48:27:fc: + d9:2d:55:4d +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 +MTM0OTI4WjBJMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BgNVBAsMCnNsYXBkLXRlc3QxDzANBgNVBAMMBmNsaWVudDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALhplvVZ8BKTYlFUDAUwxAIk2RsEOPt3xB2pIxrf +aOBMbyb/Q3lFgaF0iLrNV47YaatIO5F30u/mGq7pmAUj+tEJhJkesh7oxdI6zplk +och5BiRlwprsSDUVZA8FLnUUmNhyi2gePm7HIpmk4EwLreo4OCrvsbjNyogpWzAp +LfgoAjC8EABSZMy3Iw44SHyhmbhXggukUU0F+z0JzIoxwC0KIVc5hlDQbXoTHNPn +rQUzEyYId3weR6+F+AWUplv39uwc1v628HBNA2Z5SXiCc8MmTpgsEYixwEia9JEm +7yjTtpkG8Bm9GE9rmJf+a2eWDSkND2OIYUrbioFU9Aq3AeMCAwEAAaN4MHYwDAYD +VR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH +AwIwHQYDVR0OBBYEFMXi4nKmVcH700sRAdCgrpm2j/E5MB8GA1UdIwQYMBaAFD2A +47uUl9vd38gaI4rLK8E9GuaFMA0GCSqGSIb3DQEBCwUAA4IBAQARdgOjpp1OPnST +UDsx1cfQqDSEAWuiCM2JEVeXoumD8KbvA4yyjTGP7rPJjEkf5PwhWI0iwheWLrHR +ZAPWSHBBZSI4v58A3aiCoN8i4JobJxZL23+w/p+ogJBprTW+5pX++WSWCtjXjqpj ++5rV0OnLjPMIwAwmcsnGKVzPPJ7iODdaFi9wggvmgCeQvYG0WFJsTrd3/El2ek9s +7v8CH/RJbnjD+s3u0+flBaQWl0gWRJ+BHKypMA4d6hUy1yseaJIKnFuyJ1fTIT52 +cZQt2nZhqmSZt1QcuuKlsyGkqDb+XaNzal53r6ua2WIA8P2QHrbMV12SDRYWl3hI +J/zZLVVN +-----END CERTIFICATE----- diff --git a/Tests/certs/gencerts.sh b/Tests/certs/gencerts.sh new file mode 100755 index 00000000..6edafa01 --- /dev/null +++ b/Tests/certs/gencerts.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# Written by Christian Heimes +set -e + +export CAOUTDIR=. +export CATMPDIR=tmp + +rm -rf $CATMPDIR +rm -rf ca.pem ca.key server.pem server.key client.pem client.key +rm -rf cert9.db key4.db pkcs11.tx + +mkdir -p $CAOUTDIR +mkdir -p $CATMPDIR + +touch $CATMPDIR/ca.db +touch $CATMPDIR/ca.db.attr +echo '01' > $CATMPDIR/ca.crt.srl +echo '01' > $CATMPDIR/ca.crl.srl + +# root CA +openssl req -new \ + -config ca.conf \ + -out $CATMPDIR/ca.csr \ + -keyout $CAOUTDIR/ca.key \ + -batch + +openssl ca -selfsign \ + -config ca.conf \ + -in $CATMPDIR/ca.csr \ + -out $CAOUTDIR/ca.pem \ + -extensions ca_ext \ + -batch + +# server cert +openssl req -new \ + -config server.conf \ + -out $CATMPDIR/server.csr \ + -keyout $CAOUTDIR/server.key \ + -batch + +openssl ca \ + -config ca.conf \ + -in $CATMPDIR/server.csr \ + -out $CAOUTDIR/server.pem \ + -policy match_pol \ + -extensions server_ext \ + -batch + +# client cert +openssl req -new \ + -config client.conf \ + -out $CATMPDIR/client.csr \ + -keyout $CAOUTDIR/client.key \ + -batch + +openssl ca \ + -config ca.conf \ + -in $CATMPDIR/client.csr \ + -out $CAOUTDIR/client.pem \ + -policy match_pol \ + -extensions client_ext \ + -batch + +# cleanup +rm -rf $CATMPDIR ca.key + +echo DONE diff --git a/Tests/certs/gennssdb.sh b/Tests/certs/gennssdb.sh new file mode 100755 index 00000000..aeeb3331 --- /dev/null +++ b/Tests/certs/gennssdb.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Written by Christian Heimes +set -e + +CATMPDIR=tmp +PASSFILE=${CATMPDIR}/passwd.txt +NSSDB=sql:${CAOUTDIR} + +mkdir -p $CATMPDIR + +# Create PKCS#12 files for NSSDB import +echo "dummy" > $PASSFILE +openssl pkcs12 -name "servercert" -in server.pem -inkey server.key \ + -caname "testca" -CAfile ca.pem \ + -password "file:${PASSFILE}" -export -out server.p12 +openssl pkcs12 -name "clientcert" -in client.pem -inkey client.key \ + -caname "testca" -CAfile ca.pem \ + -password "file:${PASSFILE}" -export -out client.p12 + +# Create NSS DB +certutil -d $NSSDB -N --empty-password +certutil -d $NSSDB -A -n "testca" -t CT,, -a -i ca.pem +pk12util -d $NSSDB -i server.p12 -w ${PASSFILE} +pk12util -d $NSSDB -i client.p12 -w ${PASSFILE} +certutil -d $NSSDB -L + +# cleanup +rm -rf $CATMPDIR server.p12 client.p12 \ No newline at end of file diff --git a/Tests/certs/server.conf b/Tests/certs/server.conf new file mode 100644 index 00000000..94f4427a --- /dev/null +++ b/Tests/certs/server.conf @@ -0,0 +1,16 @@ +# Written by Christian Heimes + +[req] +default_bits = 2048 +encrypt_key = no +default_md = sha256 +utf8 = yes +string_mask = utf8only +prompt = no +distinguished_name = server_dn + +[server_dn] +countryName = "DE" +organizationName = "python-ldap" +organizationalUnitName = "slapd-test" +commonName = "server cert for localhost" diff --git a/Tests/certs/server.key b/Tests/certs/server.key new file mode 100644 index 00000000..081dc2f7 --- /dev/null +++ b/Tests/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTJOAnZdPnCTmy +gkI5XUHsuk1xL4eePxMcV+8HYMfQh+b354CygQnRcjj38uQ6S70mOIyCrTWKNrec +VrnV0xu4o16AkSRrkN0AVpfWk0ae2GaccDV+o82x+NBStfYNXW2xZ1aZIyFplGTU +yxQCpsU4yCX7RM7Ywq3fM6b8eoR2jRNGC/uMdNN/7WJkHfhJFOYFpPJ6vKOlR3PP +qzkj8WRu/ill63+phHA3UEUweKG4DoaP85M0WqHW4Kvp0A4sI5uPYI2lPrDqUf6L +Wc22gUpcFFNDReWYcxLobCsAeT4SqvEC83Dv53NKQS6wt4zLewVG1+8cjyctwv4m +PAatL8uDAgMBAAECggEACB8+BCX1nciMEKLUG1LMC2grPHRgmiTA/nEff8AoT4w6 +xUSBfdxa3VSwflE4mEl7kDHSreAt1BBAxeHMKj6BrXuTUgzDQuQCrFWoZ5eousmG +QPRMCoAQlI0GrnfTbDYw1wcrnJ5uVZpgupLJRUTXB1UjqOO/tTTf7VsWTFYGLG00 +8OeIenTGX0E1mVW9ERYCSYEtMfhQhSS3dhPmMm5VV6muIxv4ym6r2uodCY7ocxGA +JmI9F22xovH8CUtzoRbh0o4Y9VUVkKcMDl35+hya8c0BH4Vh22hnYgiyUkS1pBgY +GiX/rmU4FxkzLDrLLMB8cf+rzh+aOKjNupZ2dbNgsQKBgQDu3DLRHhUJEUcoK6Gb +ltVK179WsT1Me1Y/qiZ2SgGsXmSZFK+8cYLvXUI7Epz2Du2DwAb1J+sItie5QQ9g +o6eymTS7DuQ6MFS1UmC8PfSNjT/dFd/sVIEFJRcNkVC4CpJCnE4MQOUb6OIT8YnR ++HNDNMWc5+ab1OeFxMWRSrP52wKBgQDiS4oFkhfPtaxVyUHuiR0+0icjCGehWSM0 +eNoiX561HtBrYCZYp6kg1JMSLBSJPRaHi78td3z+6iDvE6HQtwrLq/LNd2CRJ80U +rkGjsItpZYfnJVMmK6Y4KksGlvYqWshWb8Zp0o+LPRb6g0TxZrbOGv7yD8FjaZwq +wlGxleIJeQKBgDDR+uT5BA2lZWjVeiOF8bRpYmdCtKe0Mc2zZkcZGzxy0pbjPoQC +o2NvKUFPrZsxM+SQ1Bs5fHV8XaQkoxL6gCUl4Tw2b/lgtX+WBcWT2C8yZpI2jV0N +bI1zpkGUqO3k4z4QGnewr+NDdyniXWv4Hv7mg1ltoJnLK0MRE9x9a0mvAoGBAIi4 +8w8ikVhhf1nlWvxvw0etWRAFh/coD+koC0MxWoY7s9jsIr71rW20gZc9Irs4OWBz +wnIJ+29YrcVEq0ObE96yaORS9/k7fuC719S6WcC4I0A4gOBTBv5wLxwwIVK9vsTa +i5psKWYK5tM8dG8Vi+VC0j4V3tXdfQkolosg74yhAoGAOLj21q7nNk+ExGAOesmR +qykxwIXxbHxDIfmEJJVE6OJMRvubr8WgaiSG0ptWBeiHDnldUS436difn7JXlsdo +K51jVzzgUqZXK75xvnSD7e5AL3fFRCU5usoU699fxWJTMs8H2z4alRng06MloeXJ +xrLwQyuLH8BWYFuPj4QxwwQ= +-----END PRIVATE KEY----- diff --git a/Tests/certs/server.pem b/Tests/certs/server.pem new file mode 100644 index 00000000..45ae694d --- /dev/null +++ b/Tests/certs/server.pem @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA + Validity + Not Before: Nov 28 13:49:28 2017 GMT + Not After : Nov 28 13:49:28 2021 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:d3:24:e0:27:65:d3:e7:09:39:b2:82:42:39:5d: + 41:ec:ba:4d:71:2f:87:9e:3f:13:1c:57:ef:07:60: + c7:d0:87:e6:f7:e7:80:b2:81:09:d1:72:38:f7:f2: + e4:3a:4b:bd:26:38:8c:82:ad:35:8a:36:b7:9c:56: + b9:d5:d3:1b:b8:a3:5e:80:91:24:6b:90:dd:00:56: + 97:d6:93:46:9e:d8:66:9c:70:35:7e:a3:cd:b1:f8: + d0:52:b5:f6:0d:5d:6d:b1:67:56:99:23:21:69:94: + 64:d4:cb:14:02:a6:c5:38:c8:25:fb:44:ce:d8:c2: + ad:df:33:a6:fc:7a:84:76:8d:13:46:0b:fb:8c:74: + d3:7f:ed:62:64:1d:f8:49:14:e6:05:a4:f2:7a:bc: + a3:a5:47:73:cf:ab:39:23:f1:64:6e:fe:29:65:eb: + 7f:a9:84:70:37:50:45:30:78:a1:b8:0e:86:8f:f3: + 93:34:5a:a1:d6:e0:ab:e9:d0:0e:2c:23:9b:8f:60: + 8d:a5:3e:b0:ea:51:fe:8b:59:cd:b6:81:4a:5c:14: + 53:43:45:e5:98:73:12:e8:6c:2b:00:79:3e:12:aa: + f1:02:f3:70:ef:e7:73:4a:41:2e:b0:b7:8c:cb:7b: + 05:46:d7:ef:1c:8f:27:2d:c2:fe:26:3c:06:ad:2f: + cb:83 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: critical + TLS Web Server Authentication + X509v3 Subject Key Identifier: + 98:03:0F:65:1A:34:A9:E1:09:1B:EC:D0:ED:A8:11:E6:AE:85:1F:2C + X509v3 Authority Key Identifier: + keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + + 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 + a6:01:8e:6f:0f:db:35:1f:a2:03:f5:39:ca:16:5f:e5:5a:0f: + 7e:f1:24:8a:b9:6f:78:c1:2c:f7:3a:07:f1:27:46:47:e0:98: + a1:ec:23:38:30:b9:bc:66:ac:17:06:36:48:ad:5d:1d:b4:a2: + c1:2c:08:55:ff:00:01:04:26:60:38:a6:13:52:7b:d3:88:8a: + b1:1f:a4:cd:14:9b:c8:04:77:01:72:6d:fe:b5:8c:3a:25:e2: + 2b:83:73:4b:2e:c6:6f:20:8b:00:c7:3b:2e:de:62:e9:74:81: + c6:a5:f5:b1:f9:db:b9:58:f9:7c:a2:3f:4e:46:93:ae:af:75: + 01:77:ea:34:4e:82:dc:d3:53:39:c0:b0:f9:8a:86:8d:f5:f2: + c3:a0:fa:e4:a9:b3:2c:33:ba:78:d5:ef:12:05:33:4f:29:9b: + 88:41:52:73:5a:17:7e:bc:66:d0:dc:c7:14:6d:a5:99:78:67: + 81:36:76:29:63:de:76:d4:eb:ca:5c:82:1b:ae:7d:80:ed:32: + 69:b0:b7:7a:1d:e1:00:a8:b1:95:d3:4d:d5:e2:8c:f4:30:a7: + da:79:bd:f5:bb:7a:8d:c5:ec:1c:16:32:58:92:ad:fc:1c:83: + 49:ca:8c:f9:6d:d4:f7:2c:98:1a:25:47:36:16:4f:15:46:ea: + 71:78:f1:51 +-----BEGIN CERTIFICATE----- +MIID1TCCAr2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU +MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 +MTM0OTI4WjBcMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BgNVBAsMCnNsYXBkLXRlc3QxIjAgBgNVBAMMGXNlcnZlciBjZXJ0IGZvciBsb2Nh +bGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTJOAnZdPnCTmy +gkI5XUHsuk1xL4eePxMcV+8HYMfQh+b354CygQnRcjj38uQ6S70mOIyCrTWKNrec +VrnV0xu4o16AkSRrkN0AVpfWk0ae2GaccDV+o82x+NBStfYNXW2xZ1aZIyFplGTU +yxQCpsU4yCX7RM7Ywq3fM6b8eoR2jRNGC/uMdNN/7WJkHfhJFOYFpPJ6vKOlR3PP +qzkj8WRu/ill63+phHA3UEUweKG4DoaP85M0WqHW4Kvp0A4sI5uPYI2lPrDqUf6L +Wc22gUpcFFNDReWYcxLobCsAeT4SqvEC83Dv53NKQS6wt4zLewVG1+8cjyctwv4m +PAatL8uDAgMBAAGjgacwgaQwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw +FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFJgDD2UaNKnhCRvs0O2o +EeauhR8sMB8GA1UdIwQYMBaAFD2A47uUl9vd38gaI4rLK8E9GuaFMCwGA1UdEQQl +MCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B +AQsFAAOCAQEApgGObw/bNR+iA/U5yhZf5VoPfvEkirlveMEs9zoH8SdGR+CYoewj +ODC5vGasFwY2SK1dHbSiwSwIVf8AAQQmYDimE1J704iKsR+kzRSbyAR3AXJt/rWM +OiXiK4NzSy7GbyCLAMc7Lt5i6XSBxqX1sfnbuVj5fKI/TkaTrq91AXfqNE6C3NNT +OcCw+YqGjfXyw6D65KmzLDO6eNXvEgUzTymbiEFSc1oXfrxm0NzHFG2lmXhngTZ2 +KWPedtTrylyCG659gO0yabC3eh3hAKixldNN1eKM9DCn2nm99bt6jcXsHBYyWJKt +/ByDScqM+W3U9yyYGiVHNhZPFUbqcXjxUQ== +-----END CERTIFICATE----- diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 6998e731..3a0f7df3 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -807,6 +807,35 @@ def test_invalid_controls(self): l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,)) self.assertInvalidControls(l.unbind_ext) + @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + def test_tls_ext(self): + l = self._open_conn(bind=False) + # StartTLS needs LDAPv3 + l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + l.set_option(_ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + # re-create TLS context + l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) + l.start_tls_s() + + @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + def test_tls_ext_noca(self): + l = self._open_conn(bind=False) + l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) + with self.assertRaises(_ldap.CONNECT_ERROR): + l.start_tls_s() + + @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + def test_tls_ext_clientcert(self): + l = self._open_conn(bind=False) + l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + l.set_option(_ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + l.set_option(_ldap.OPT_X_TLS_CERTFILE, self.server.clientcert) + l.set_option(_ldap.OPT_X_TLS_KEYFILE, self.server.clientkey) + l.set_option(_ldap.OPT_X_TLS_REQUIRE_CERT, _ldap.OPT_X_TLS_HARD) + l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) + l.start_tls_s() + # TODO verify that client auth actually works if __name__ == '__main__': unittest.main() From 7405d120c6d7f6e3b5720e6b58cd02901fc75bd2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 28 Nov 2017 15:49:57 +0100 Subject: [PATCH 033/369] Doc: Fixes for the spelling wordlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - continously → continUously - correct capitalization for Heimdal Kerberos --- Doc/installing.rst | 2 +- Doc/spelling_wordlist.txt | 3 +-- Lib/ldif.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index c78bd7f2..2ea55948 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -113,7 +113,7 @@ on the local system when building python-ldap: It is not possible and not supported to build with prior versions. - `OpenSSL`_ (optional) - `cyrus-sasl`_ (optional) -- Kerberos libraries, MIT or heimdal (optional) +- Kerberos libraries, MIT or Heimdal (optional) .. _Python: https://www.python.org/ .. _OpenLDAP: https://www.openldap.org/ diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index fcc227f3..39df00fd 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -27,7 +27,6 @@ changeTypes cidict clientctrls conf -Continously controlType controlValue criticality @@ -53,7 +52,7 @@ filterstr filterStr formatOID func -heimdal +Heimdal hostport hrefTarget hrefText diff --git a/Lib/ldif.py b/Lib/ldif.py index aead2ef3..4735bfb5 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -395,7 +395,7 @@ def _consume_empty_lines(self): def parse_entry_records(self): """ - Continously read and parse LDIF entry records + Continuously read and parse LDIF entry records """ # Local symbol for better performance next_key_and_value = self._next_key_and_value From 703d32209ba49227edb67d7ca1c3107da6ca8ca5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 16:07:16 +0100 Subject: [PATCH 034/369] Don't define search path for includes and libs Compilers' and linkers' default search paths are fine on modern Linux. The custom search path was wrong on 32bit platforms anyway. When library_dirs is defined, distutils also adds rpath to the shared library. An rpath should not be added by default. I only commented out the configuration stanzes to keep them as example. Closes: https://github.com/python-ldap/python-ldap/issues/30 Signed-off-by: Christian Heimes --- setup.cfg | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 59816b72..7fab8ae4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,9 +5,10 @@ # for wrapping OpenLDAP 2 libs [_ldap] -# Define extra include and library dirs if needed -library_dirs = /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 -include_dirs = /usr/include /usr/include/sasl /usr/local/include /usr/local/include/sasl +# Define extra include and library dirs if needed. distutils adds non +# standard library_dirs as rpath. +# library_dirs = /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 +# include_dirs = /usr/include /usr/include/sasl /usr/local/include /usr/local/include/sasl # These defines needs OpenLDAP built with # ./configure --with-cyrus-sasl --with-tls From 5729d06ff5eda1e30858ffaf19e566e9b5283487 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 16:24:07 +0100 Subject: [PATCH 035/369] Include sasl.h from standard path is the standard include path for sasl.h. ``pkg-config --cflags libsasl2`` agrees with me. Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 4 ---- setup.cfg | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 1c0591cc..0aafe499 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -13,11 +13,7 @@ #include "options.h" #ifdef HAVE_SASL -#ifdef __APPLE__ #include -#else -#include -#endif #endif static void free_attrs(char***, PyObject*); diff --git a/setup.cfg b/setup.cfg index 7fab8ae4..34699da5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ # Define extra include and library dirs if needed. distutils adds non # standard library_dirs as rpath. # library_dirs = /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 -# include_dirs = /usr/include /usr/include/sasl /usr/local/include /usr/local/include/sasl +# include_dirs = /usr/include /usr/local/include # These defines needs OpenLDAP built with # ./configure --with-cyrus-sasl --with-tls From 5a63a08a45e3f2861dbcaf9dbd6bfb714f5163b5 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 28 Nov 2017 15:12:38 +0100 Subject: [PATCH 036/369] Doc: Link to documentation for older versions --- Doc/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/index.rst b/Doc/index.rst index f478d10c..6d2f1acf 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -45,7 +45,11 @@ The documentation for python-ldap 3.x is hosted at `Read the Docs`_. You can switch between versions of the library, or download PDF or HTML versions for offline use, using the right sidebar. +Documentation for some older versions is available for download at the +`GitHub release page`_. + .. _Read the Docs: http://python-ldap.readthedocs.io/en/latest/ +.. _GitHub release page: https://github.com/python-ldap/python-ldap/releases Contents From 644847f818cbc802b8a4b5663e08810427b86c16 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 28 Nov 2017 21:25:34 +0100 Subject: [PATCH 037/369] Doc: rewordings, syntax fixes, typo fixes --- Doc/faq.rst | 61 ++++++++++++++++++++------------------- Doc/index.rst | 23 +++++++++------ Doc/installing.rst | 33 +++++++++++---------- Doc/spelling_wordlist.txt | 4 +-- 4 files changed, 64 insertions(+), 57 deletions(-) diff --git a/Doc/faq.rst b/Doc/faq.rst index 58bb81e3..ef479a87 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -35,41 +35,41 @@ Usage **Q**: My code imports module ``_ldap``. -That used to work but from version 2.0.0pre that does not work anymore? +That used to work, but after an upgrade it does not work anymore. Why? - **A**: Despite some outdated programming examples the extension module - ``_ldap`` **MUST NOT** be imported directly unless - you really know what you're doing (e.g. for internal regression testing). + **A**: Despite some outdated programming examples, the extension module + ``_ldap`` **MUST NOT** be imported directly, unless you really know what + you're doing (e.g. for internal regression testing). - Import ``ldap`` instead which is a Python wrapper around ``_ldap`` + Import ``ldap`` instead, which is a Python wrapper around ``_ldap`` providing the full functionality. **Q**: My script bound to MS Active Directory but a a search operation results -in an 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." +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? - **A**: When searching from the domain level MS AD returns referrals (search continuations) + **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. - Client-chasing of referrals is a broken concept since LDAPv3 does not specify + Client-chasing of referrals is a broken concept, since LDAPv3 does not specify which credentials to use when chasing the referral. Windows clients are supposed - to simply use their Windows credentials but this does not work in general when + to simply use their Windows credentials, but this does not work in general when chasing referrals received from and pointing to arbitrary LDAP servers. - Therefore per default ``libldap`` automatically chases the referrals + Therefore, per default, ``libldap`` automatically chases the referrals internally with an *anonymous* access which fails with MS AD. - So best thing is to switch this behaviour off:: + So, the best thing to do is to switch this behaviour off:: l = ldap.initialize('ldap://foobar') l.set_option(ldap.OPT_REFERRALS,0) -**Q**: Why am I seeing ``ldap.SUCCESS`` traceback as output? +**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 + **A**: Most likely, you are using one of the non-synchronous calls, and probably mean to be using a synchronous call - (see detailed explanation in :ref:`sending-ldap-requests`. + (see detailed explanation in :ref:`sending-ldap-requests`). **Q**: Can I use LDAPv2 via python-ldap? @@ -87,13 +87,14 @@ Installing **Q**: Does it work with Windows 32? - **A**: You can find links to pre-compiled packages for Win32 on the - :ref:`Download information` page. + **A**: Yes. You can find links to unofficial pre-compiled packages + for Windows on the :ref:`installing` page. **Q**: Can python-ldap be built against OpenLDAP 2.3 libs or older? - **A**: No, for recent python-ldap 2.4.x the OpenLDAP 2.4 client libs or newer are required. + **A**: No. + The needed minimal version of OpenLDAP is documented in :ref:`build prerequisites`. Patched builds of python-ldap linked to older libs are not supported by the python-ldap project. @@ -123,8 +124,8 @@ telling Lib/ldap.py and Lib/ldap/schema.py are not found:: --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" -**Q**: While importing module ldap some shared lib files are not found. -Error message looks similar to this:: +**Q**: While importing module ``ldap``, some shared lib files are not found. +The error message looks similar to this:: ImportError: ld.so.1: /usr/local/bin/python: fatal: liblber.so.2: open failed: No such file or directory @@ -133,9 +134,9 @@ Error message looks similar to this:: **A1**: You need to make sure that the path to ``liblber.so.2`` and ``libldap.so.2`` is in your ``LD_LIBRARY_PATH`` environment variable. - **A2**: Alternatively if you're on Linux you can add the path to + **A2**: Alternatively, if you're on Linux, you can add the path to ``liblber.so.2`` and ``libldap.so.2`` to ``/etc/ld.so.conf`` - and invoke command ``ldconfig`` afterwards. + and invoke the command ``ldconfig`` afterwards. @@ -151,32 +152,32 @@ Historic But the python-ldap docs say LDAP libs 2.x are needed. I'm confused! Short answer: - See answer above and :ref:`download information` for + See answer above and the :ref:`installing` page for a more recent version. Long answer: E.g. some Win32 DLLs floating around for download are based on the old Umich LDAP code which is not maintained anymore for - ``many`` years! Last Umich 3.3 release was 1997 if I remember correctly. + *many* years! Last Umich 3.3 release was 1997 if I remember correctly. The OpenLDAP project took over the Umich code and started releasing OpenLDAP 1.x series mainly fixing bugs and doing some improvements - to the database backend. Still only LDAPv2 was supported at server + to the database backend. Still, only LDAPv2 was supported at server and client side. (Many commercial vendors also derived their products from the Umich code.) - OpenLDAP 2.x is a full-fledged LDAPv3 implementation. Still it has + OpenLDAP 2.x is a full-fledged LDAPv3 implementation. It has its roots in Umich code but has many more features/improvements. -**Q**: While importing module ldap there are undefined references reported. -Error message looks similar to this:: +**Q**: While importing module ``ldap``, there are undefined references reported. +The error message looks similar to this:: ImportError: /usr/local/lib/libldap.so.2: undefined symbol: res_query .. - **A**: Especially on older Linux systems you might have to explicitly link + **A**: Especially on older Linux systems, you might have to explicitly link against ``libresolv``. Tweak ``setup.cfg`` to contain this line:: diff --git a/Doc/index.rst b/Doc/index.rst index 6d2f1acf..b3461452 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -5,30 +5,35 @@ python-ldap What is python-ldap? -------------------- -* python-ldap provides an object-oriented API to access LDAP - directory servers from `Python`_ programs. -* For LDAP operations the module wraps `OpenLDAP`_'s - client library *libldap* for that purpose. -* Additionally the package contains modules for other LDAP-related stuff: +python-ldap provides an object-oriented API to access `LDAP`_ +directory servers from `Python`_ programs. - * LDIF +For LDAP operations the module wraps `OpenLDAP`_'s +client library, *libldap*. + +Additionally, the package contains modules for other LDAP-related stuff: + + * `LDIF`_ parsing and generation * LDAP URLs * LDAPv3 subschema +.. _LDAP: https://en.wikipedia.org/wiki/Ldap .. _Python: https://www.python.org/ .. _OpenLDAP: https://www.openldap.org/ +.. _LDIF: https://en.wikipedia.org/wiki/LDIF Get it! ------- -:ref:`Download information` is available for several platforms. +:ref:`Installation instructions ` are available for +several platforms. Mailing list ------------ -Discussion about the use and future of Python-LDAP occurs in +Discussion about the use and future of python-ldap occurs in the ``python-ldap@python.org`` mailing list. You can `subscribe or unsubscribe`_ to this list or browse the `list archive`_. @@ -43,7 +48,7 @@ Documentation The documentation for python-ldap 3.x is hosted at `Read the Docs`_. You can switch between versions of the library, or download PDF or HTML -versions for offline use, using the right sidebar. +versions for offline use, using the sidebar on the right. Documentation for some older versions is available for download at the `GitHub release page`_. diff --git a/Doc/installing.rst b/Doc/installing.rst index 2ea55948..f2dec810 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -1,4 +1,4 @@ -.. _Download information: +.. _installing: Installing python-ldap ###################### @@ -7,7 +7,7 @@ Installing from PyPI ==================== The preferred point for downloading the “official” source distribution -is now the `PyPI repository`_ which supports installing via `pip`_. +is the `PyPI repository`_ which supports installing via `pip`_. For example:: python -m pip install python-ldap @@ -15,7 +15,7 @@ For example:: .. _PyPI repository: https://pypi.python.org/pypi/python-ldap/ .. _pip: https://pip.pypa.io/en/stable/ -For installing from PyPI, you will need the :ref:`build prerequisites` +For installing from PyPI, you will need the same :ref:`build prerequisites` as when installing from source. We do not currently provide pre-built packages (wheels). @@ -94,7 +94,7 @@ 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. -See further instructions can be found in `Setuptools documentation`_. +Further instructions can be found in `Setuptools documentation`_. .. _Setuptools documentation: https://docs.python.org/3/distributing/index.html @@ -112,13 +112,13 @@ on the local system when building python-ldap: - `OpenLDAP`_ client libs version 2.4.11 or later It is not possible and not supported to build with prior versions. - `OpenSSL`_ (optional) -- `cyrus-sasl`_ (optional) +- `Cyrus SASL`_ (optional) - Kerberos libraries, MIT or Heimdal (optional) .. _Python: https://www.python.org/ .. _OpenLDAP: https://www.openldap.org/ .. _OpenSSL: https://www.openssl.org/ -.. _cyrus-sasl: https://www.cyrusimap.org/sasl/ +.. _Cyrus SASL: https://www.cyrusimap.org/sasl/ setup.cfg @@ -126,9 +126,9 @@ 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 the -documentation of Python's DistUtils. +software packages. Only section ``[_ldap]`` is described here. +More information about other sections can be found in +`Setuptools documentation`_. .. data:: library_dirs @@ -148,9 +148,10 @@ documentation of Python's DistUtils. .. data:: extra_objects -.. _libs-used-label: +.. _libs-used-label: + Libraries used --------------- @@ -159,7 +160,7 @@ Libraries used .. data:: ldap_r :noindex: - The LDAP protocol library of OpenLDAP. ldap_r is the reentrant version + The LDAP protocol library of OpenLDAP. ``ldap_r`` is the reentrant version and should be preferred. .. data:: lber @@ -170,25 +171,25 @@ Libraries used .. data:: sasl2 :noindex: - The Cyrus-SASL library if needed and present during build + The Cyrus-SASL library (optional) .. data:: ssl :noindex: - The SSL/TLS library of OpenSSL if needed and present during build + The SSL/TLS library of OpenSSL (optional) .. data:: crypto :noindex: - The basic cryptographic library of OpenSSL if needed and present during build + The basic cryptographic library of OpenSSL (optional) Example ------- The following example is for a full-featured build (including SSL and SASL support) of python-ldap with OpenLDAP installed in a different prefix directory -(here /opt/openldap-2.4) and SASL header files found in /usr/include/sasl. -Debugging symbols are preserved with compile option -g. +(here ``/opt/openldap-2.4``) and SASL header files found in /usr/include/sasl. +Debugging symbols are preserved with compile option ``-g``. :: diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 39df00fd..a8b7e16d 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -31,7 +31,7 @@ controlType controlValue criticality cryptographic -cyrus +Cyrus defresult dereferenced dereferencing @@ -91,7 +91,7 @@ objectClass oc oid oids -openldap +OpenLDAP postalAddress pre previousDN From 53281640a22acb580338345db476be5c59ee3e74 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 29 Nov 2017 14:37:28 +0100 Subject: [PATCH 038/369] Add test case for SASL EXTERNAL auth Test cases for EXTERNAL with LDAPI (Unix socket) and TLS client cert auth. https://github.com/python-ldap/python-ldap/pull/56 Signed-off-by: Christian Heimes --- Lib/slapdtest.py | 6 +++ Tests/t_cext.py | 1 - Tests/t_ldap_sasl.py | 96 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 Tests/t_ldap_sasl.py diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index b5e1507e..e0e57c75 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -43,6 +43,11 @@ 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)" + """ LOCALHOST = '127.0.0.1' @@ -289,6 +294,7 @@ def _start_slapd(self): slapd_args = [ self.PATH_SLAPD, '-f', self._slapd_conf, + '-F', self.testrundir, '-h', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)), ] if self._log.isEnabledFor(logging.DEBUG): diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 3a0f7df3..22eb895f 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -835,7 +835,6 @@ def test_tls_ext_clientcert(self): l.set_option(_ldap.OPT_X_TLS_REQUIRE_CERT, _ldap.OPT_X_TLS_HARD) l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() - # TODO verify that client auth actually works if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py new file mode 100644 index 00000000..e689bb69 --- /dev/null +++ b/Tests/t_ldap_sasl.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +""" +Automatic tests for python-ldap's module ldap.sasl + +See http://www.python-ldap.org/ for details. +""" +import os +import pwd +import socket +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +from ldap.ldapobject import SimpleLDAPObject +import ldap.sasl +from slapdtest import SlapdTestCase + + +LDIF = """ +dn: {suffix} +objectClass: dcObject +objectClass: organization +dc: {dc} +o: {dc} + +dn: {rootdn} +objectClass: applicationProcess +objectClass: simpleSecurityObject +objectClass: uidObject +cn: {rootcn} +userPassword: {rootpw} +uid: {uid} + +dn: cn={certuser},{suffix} +objectClass: applicationProcess +cn: {certuser} + +""" + + +class TestSasl(SlapdTestCase): + ldap_object_class = SimpleLDAPObject + # from Tests/certs/client.pem + certuser = 'client' + certsubject = "cn=client,ou=slapd-test,o=python-ldap,c=de" + + @classmethod + def setUpClass(cls): + super(TestSasl, cls).setUpClass() + ldif = LDIF.format( + suffix=cls.server.suffix, + rootdn=cls.server.root_dn, + rootcn=cls.server.root_cn, + rootpw=cls.server.root_pw, + dc=cls.server.suffix.split(',')[0][3:], + certuser=cls.certuser, + uid=os.geteuid(), + ) + cls.server.ldapadd(ldif) + + @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), "needs Unix socket") + def test_external_ldapi(self): + # EXTERNAL authentication with LDAPI (AF_UNIX) + ldap_conn = self.ldap_object_class(self.server.ldapi_uri) + + auth = ldap.sasl.external("some invalid user") + with self.assertRaises(ldap.INSUFFICIENT_ACCESS): + ldap_conn.sasl_interactive_bind_s("", auth) + + auth = ldap.sasl.external("") + ldap_conn.sasl_interactive_bind_s("", auth) + self.assertEqual( + ldap_conn.whoami_s().lower(), + "dn:{}".format(self.server.root_dn.lower()) + ) + + def test_external_tlscert(self): + ldap_conn = self.ldap_object_class(self.server.ldap_uri) + ldap_conn.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + ldap_conn.set_option(ldap.OPT_X_TLS_CERTFILE, self.server.clientcert) + ldap_conn.set_option(ldap.OPT_X_TLS_KEYFILE, self.server.clientkey) + ldap_conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) + ldap_conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0) + ldap_conn.start_tls_s() + + auth = ldap.sasl.external() + ldap_conn.sasl_interactive_bind_s("", auth) + self.assertEqual( + ldap_conn.whoami_s().lower(), + "dn:{}".format(self.certsubject) + ) + +if __name__ == '__main__': + unittest.main() + From ece47386bc0284d89825ec003e8bfd7a37a797fd Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 29 Nov 2017 14:52:18 +0100 Subject: [PATCH 039/369] Use standard PyVarObject_HEAD_INIT() call PyType_Ready() takes care of the base type for us. Also check PyType_Ready() result for error. https://github.com/python-ldap/python-ldap/pull/47 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 5 ----- Modules/ldapmodule.c | 8 +++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 1c0591cc..48007246 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1434,12 +1434,7 @@ static PyMethodDef methods[] = { /* type entry */ PyTypeObject LDAP_Type = { -#if defined(MS_WINDOWS) || defined(__CYGWIN__) - /* see http://www.python.org/doc/FAQ.html#3.24 */ PyVarObject_HEAD_INIT(NULL, 0) -#else /* ! MS_WINDOWS */ - PyVarObject_HEAD_INIT(&PyType_Type, 0) -#endif /* MS_WINDOWS */ "LDAP", /*tp_name*/ sizeof(LDAPObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index ec3a264e..bd54313d 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -41,9 +41,9 @@ PyObject* init_ldap_module() { PyObject *m, *d; -#if defined(MS_WINDOWS) || defined(__CYGWIN__) - LDAP_Type.ob_type = &PyType_Type; -#endif + /* Initialize LDAP class */ + if (PyType_Ready(&LDAP_Type) < 0) + return NULL; /* Create the module and add the functions */ #if PY_MAJOR_VERSION >= 3 @@ -59,8 +59,6 @@ PyObject* init_ldap_module() m = Py_InitModule("_ldap", methods); #endif - PyType_Ready(&LDAP_Type); - /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); From d4cfa1f208803cb95a4e2261bd2c3fc132f4b347 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Nov 2017 13:59:55 +0100 Subject: [PATCH 040/369] Doc: Add a contributing guide All you wanted to know about contributing to python-ldap! The refleak instructions from INSTALL are merged into contribution instructions. The file itself is made to link to installation instructions. Also add a CONTRIBUTING file that references the new text. --- .gitignore | 3 + CONTRIBUTING.rst | 10 ++ Doc/contributing.rst | 266 ++++++++++++++++++++++++++++++++++++++ Doc/index.rst | 5 + Doc/installing.rst | 3 + Doc/spelling_wordlist.txt | 4 + INSTALL | 57 +------- 7 files changed, 294 insertions(+), 54 deletions(-) create mode 100644 CONTRIBUTING.rst create mode 100644 Doc/contributing.rst diff --git a/.gitignore b/.gitignore index 03b366de..4e261ca4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ __pycache__/ build/ dist/ PKG-INFO + +# generated in the sample workflow +/__venv__/ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..0866c8ee --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,10 @@ +Thank you for your interest in python-ldap! + +If you wish to help, detailed instructions are in `Doc/contributing.rst`_, +and in `online documentation`_. + +.. _Doc/contributing.rst: Doc/contributing.rst +.. _online documentation: http://python-ldap.readthedocs.io/en/latest/contributing.html + + +Open-source veretans should find no surprises there. diff --git a/Doc/contributing.rst b/Doc/contributing.rst new file mode 100644 index 00000000..3a904d34 --- /dev/null +++ b/Doc/contributing.rst @@ -0,0 +1,266 @@ +.. highlight:: console + +Contributing to python-ldap +*************************** + +Thank you for your interest in python-ldap! +If you'd like to contribute (be it code, documentation, maintenance effort, +or anything else), this guide is for you. + + +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. + +So, keep it friendly, respectful, and supportive! + + +Mailing list +------------ + +Discussion about the use and future of python-ldap occurs in +the ``python-ldap@python.org`` mailing list. + +It's also the channel to use if documentation (including this guide) is not +clear to you. +Do try searching around before you ask on the list, though! + +You can `subscribe or unsubscribe`_ to this list or browse the `list archive`_. + +.. _subscribe or unsubscribe: https://mail.python.org/mailman/listinfo/python-ldap +.. _list archive: https://mail.python.org/pipermail/python-ldap/ + + +Issues +------ + +Please report bugs, missing features and other issues to `the bug tracker`_ +at GitHub. You will need a GitHub account for that. + +If you prefer not to open a GitHub account, you're always welcome to use the +mailing list. + + +Security Contact +---------------- + +If you found a security issue that should not be discussed publicly, +please e-mail the maintainer at ``pviktori@redhat.com``. +If required, write to coordinate a more secure channel. + +All other communication should be public. + + +Process for Code contributions +============================== + +If you're used to open-source Python development with Git, here's the gist: + +* ``git clone https://github.com/python-ldap/python-ldap`` +* Use GitHub for `the bug tracker`_ and pull requests. +* Run tests with `tox`_; ignore Python interpreters you don't have locally. + +.. _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`_. + +.. _Read the Docs: http://python-ldap.readthedocs.io/ + +If you're new to some aspect of the project, you're welcome to use (or adapt) +the workflow below. + + +Sample workflow +--------------- + +We assume that, as a user of python-ldap you're not new to software +development in general, so these instructions are terse. +If you need additional detail, please do ask on the mailing list. + +.. note:: + + The following instructions are for Linux. + If you can translate them to another system, please contribute your + translation! + + +Install `Git`_ and `tox`_. + +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:: + + $ 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:: + + $ source __venv__/bin/activate + +Install python-ldap to it in `editable mode`_:: + + (__venv__)$ python -m pip install -e . + +This way, importing a Python module from python-ldap will directly +use the code from your source tree. +If you change C code, you will still need to recompile +(using the ``pip install`` command again). + +.. _editable mode: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs + +Change the code as desired. + + +To run tests, install and run `tox`_:: + + (__venv__)$ python -m pip install tox + (__venv__)$ tox --skip-missing-interpreters + +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 + +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``. + + +When your change is ready, commit to Git, and submit a pull request on GitHub. +You can take a look at the `committer instructions`_ to see what we are looking +for in a pull request. + +If you don't want to open a GitHub account, please send patches as attachments +to the python-ldap mailing list. + + +.. _additional tests: + +Additional tests and scripts +============================ + +We use several specialized tools for debugging and maintenance. + +Make targets +------------ + +``make lcov-open`` + Generate and view test coverage for C code. + Requires ``make`` and ``lcov``. + +``make scan-build`` + Run static analysis. Requires ``clang``. + + +Reference leak tests +-------------------- + +Reference leak tests require a *pydebug* build of CPython and `pytest`_ with +`pytest-leaks`_ plugin. A *pydebug* build has a global reference counter, which +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 + +Download and compile the *pydebug* build:: + + $ curl -O https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz + $ tar xJf Python-3.6.3.tar.xz + $ cd Python-3.6.3 + $ ./configure --with-pydebug + $ make + +Create a virtual environment with the *pydebug* build:: + + $ ./python -m venv /tmp/refleak + $ /tmp/refleak/bin/pip install pytest pytest-leaks + +Run reference leak tests:: + + $ cd path/to/python-ldap + $ /tmp/refleak/bin/pip install --upgrade . + $ /tmp/refleak/bin/pytest -v -R: Tests/t_*.py + +Run ``/tmp/refleak/bin/pip install --upgrade .`` every time a file outside +of ``Tests/`` is modified. + + +.. _committer instructions: + +Instructions for core committers +================================ + +If you have the authority (and responsibility) of merging changes from others, +remember: + +* All code changes need to be reviewed by someone other than the author. + +* Tests must always pass. New features without tests shall *not* pass review. + +* Make sure commit messages don't use GitHub-specific link syntax. + Use the full URL, e.g. ``https://github.com/python-ldap/python-ldap/issues/50`` + instead of ``#20``. + + * Exception: it's fine to use the short form in the summary line of a merge + commit, if the full URL appears later. + * It's OK to use shortcuts in GitHub *discussions*, where they are not + hashed into immutable history. + +* Make a merge commit if the contribution contains several well-isolated + separate commits with good descriptions. Use *squash-and-merge* (or + *fast-forward* from a command line) for all other cases. + +* It's OK to push small changes into a pull request. If you do this, document + what you have done (so the contributor can learn for the future), and get + their :abbr:`ACK (confirmation)` before merging. + +* When squashing, do edit commit messages to add references to the pull request + and relevant discussions/issues, and to conform to Git best practices. + + * 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. + + +Instructions for release managers +================================= + +If you are tasked with releasing python-ldap, remember to: + +* Bump all instances of the version number. +* Go through all changes since last version, and add them to ``CHANGES``. +* Run :ref:`additional tests` as appropriate, fix any regressions. +* 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. +* Release the ``sdist`` on PyPI. +* Announce the release on the mailing list. + Mention the Git hash. diff --git a/Doc/index.rst b/Doc/index.rst index b3461452..2c5b24eb 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -29,6 +29,10 @@ Get it! :ref:`Installation instructions ` are available for several platforms. +Source code can be obtained using Git:: + + git clone https://github.com/python-ldap/python-ldap + Mailing list ------------ @@ -66,6 +70,7 @@ Contents installing.rst reference/index.rst resources.rst + contributing.rst faq.rst diff --git a/Doc/installing.rst b/Doc/installing.rst index f2dec810..52558044 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -81,6 +81,8 @@ You can install directly with pip:: --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" +.. _install-source: + Installing from Source ====================== @@ -109,6 +111,7 @@ The following software packages are required to be installed on the local system when building python-ldap: - `Python`_ version 2.7, or 3.3 or later 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. - `OpenSSL`_ (optional) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index a8b7e16d..95a5df65 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -29,6 +29,8 @@ clientctrls conf controlType controlValue +committers +CPython criticality cryptographic Cyrus @@ -92,6 +94,7 @@ oc oid oids OpenLDAP +Pagure postalAddress pre previousDN @@ -148,3 +151,4 @@ userPassword usr uuids whitespace +workflow diff --git a/INSTALL b/INSTALL index 57b00b7c..0475a2fc 100644 --- a/INSTALL +++ b/INSTALL @@ -1,60 +1,9 @@ ------------------------------- -Installing python-ldap ------------------------------- - -Prerequisites: - - Required: - - - Python 2.3 or newer (see http://www.python.org) - - - OpenLDAP 2.4.11+ client libs (see http://www.openldap.org) - It is not possible and not supported - by the python-ldap project to build with prior versions. - - Optional dependencies of OpenLDAP libs: - - - Cyrus SASL 2.1.x or newer (see http://asg.web.cmu.edu/sasl/sasl-library.html) - - - OpenSSL 0.9.7 or newer (see http://www.openssl.org) - - - MIT Kerberos or heimdal libs - Quick build instructions: + edit setup.cfg (see Build/ for platform-specific examples) python setup.py build python setup.py install --------------------- -Reference leak tests --------------------- - -Reference leak tests require a pydebug build of CPython and pytest with -pytest-leaks plugin. A pydebug build has a global reference counter, which -keeps track of all reference increments and decrements. The leak plugin runs -each tests multiple times and checks if the reference count increases. - -Download and compile pydebug build ----------------------------------- - -- curl -O https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz -- tar xJf Python-3.6.3.tar.xz -- cd Python-3.6.3 -- ./configure --with-pydebug -- make - -Create virtual env ------------------- - -- ./python -m venv /tmp/refleak -- /tmp/refleak/bin/pip install pytest pytest-leaks - -Run refleak tests ------------------ - -- cd path/to/python-ldap -- /tmp/refleak/bin/pip install --upgrade . -- /tmp/refleak/bin/pytest -v -R: Tests/t_*.py +Detailed instructions are in Doc/installing.rst, or online at: -Run ``/tmp/refleak/bin/pip install --upgrade .`` every time a file outside -of ``Tests/`` is modified. \ No newline at end of file + http://python-ldap.readthedocs.io/en/latest/installing.html From 52ce56b67654c2559112d62d9ea2e9e91024a6b7 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Nov 2017 14:05:28 +0100 Subject: [PATCH 041/369] Doc: Add .build to .gitignore This directory is created using `make html`. --- Doc/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/.gitignore b/Doc/.gitignore index a485625d..5e134217 100644 --- a/Doc/.gitignore +++ b/Doc/.gitignore @@ -1 +1,2 @@ -/_build +/_build/ +/.build/ From a52be29da3740b77d945c2837d9fb75f9c6f5b43 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Nov 2017 14:13:45 +0100 Subject: [PATCH 042/369] Infra: Add a GitHub issue template --- .github/ISSUE_TEMPLATE.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..41306bec --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,23 @@ +If you found a bug in python-ldap, or would request a new feature, +this is the place to let us know. + +Please describe the issue and your environment here. + +--- + +Issue description: + + + + + + +Steps to reproduce: + + + +Operating system: + +Python version: + +python-ldap version: From 909c26b879ee96876b21f5e295ce4ffe521f2803 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 29 Nov 2017 14:20:31 +0100 Subject: [PATCH 043/369] Doc: Fix link to FreeBSD --- Doc/installing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 52558044..39075390 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -64,8 +64,8 @@ Unofficial packages for Windows are available on `Christoph Gohlke's page `_. -`FreeBSD `_ +------------------------------------- The CVS repository of FreeBSD contains the package `py-ldap `_ From 7f6b5faf77626ec1de9e48d86a6fc24f6f10591f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 17:52:30 +0100 Subject: [PATCH 044/369] Correct return LDAPerror_TypeError() LDAPerror_TypeError() returns (PyObject *)NULL. Some functions don't return a PyObject* so technically it's wrong to return (PyObject *)NULL. Return NULL instead. Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 9 ++++++--- Modules/ldapcontrol.c | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 48007246..cff9337f 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -112,7 +112,8 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) Py_ssize_t i, len, nstrs; if (!PyTuple_Check(tup)) { - return LDAPerror_TypeError("expected a tuple", tup); + LDAPerror_TypeError("expected a tuple", tup); + return NULL; } if (no_op) { @@ -209,13 +210,15 @@ List_to_LDAPMods( PyObject *list, int no_op ) { PyObject *item; if (!PySequence_Check(list)) { - return LDAPerror_TypeError("expected list of tuples", list); + LDAPerror_TypeError("expected list of tuples", list); + return NULL; } len = PySequence_Length(list); if (len < 0) { - return LDAPerror_TypeError("expected list of tuples", list); + LDAPerror_TypeError("expected list of tuples", list); + return NULL; } lms = PyMem_NEW(LDAPMod *, len + 1); diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 3f5b2c4c..b76d33c3 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -71,7 +71,8 @@ Tuple_to_LDAPControl( PyObject* tup ) Py_ssize_t len; if (!PyTuple_Check(tup)) { - return LDAPerror_TypeError("expected a tuple", tup); + LDAPerror_TypeError("expected a tuple", tup); + return NULL; } if (!PyArg_ParseTuple( tup, "sbO", &oid, &iscritical, &bytes )) From 67c69e72a347c69f599f504b18e47248109bca4f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 17:56:10 +0100 Subject: [PATCH 045/369] Fix implicit declaration of error functions ldapcontrol.c was missing include of constants.h that defines LDAPerr() and LDAPerror(). Signed-off-by: Christian Heimes --- Modules/ldapcontrol.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index b76d33c3..7d8fbe38 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -4,6 +4,7 @@ #include "LDAPObject.h" #include "ldapcontrol.h" #include "berval.h" +#include "constants.h" #include "lber.h" From bdbe614a12fed05166b05d055b0e8d49d42c37b8 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 17:57:02 +0100 Subject: [PATCH 046/369] Travis: Turn compiler warnings into fatal errors Signed-off-by: Christian Heimes --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 581013cf..5dda5e08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,14 @@ matrix: - python: 3.6 env: TOXENV=doc +env: + global: + # -Wno-int-in-bool-context: don't complain about PyMem_MALLOC() + # -Werror: turn all warnings into fatal errors + - CFLAGS="-Wno-int-in-bool-context -Werror" + # pass CFLAGS and WITH_GCOV to tox tasks + - TOX_TESTENV_PASSENV="CFLAGS WITH_GCOV" + install: - pip install "pip>=7.1.0" - pip install tox-travis tox codecov coverage From 5b2d2f9de64c28cb7b73ca95f45648405bc5a97b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 28 Nov 2017 18:22:03 +0100 Subject: [PATCH 047/369] Make init_ldap_module definition a strict prototype Signed-off-by: Christian Heimes --- Modules/ldapmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index bd54313d..4c2c4ec9 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -37,7 +37,7 @@ static PyMethodDef methods[] = { /* Common initialization code */ -PyObject* init_ldap_module() +PyObject* init_ldap_module(void) { PyObject *m, *d; From de6da53a642dc42c2b893003a3c0ce4e3f5d2ab6 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 29 Nov 2017 15:38:54 +0100 Subject: [PATCH 048/369] Fix declaration after statement for C90 In strict ISO C90 mode and on some platforms (e.g. older MSVC), it is not allowed to declare new variables after a statement. I also find it good practice to declare first. Fixes error: ISO C90 forbids mixed declarations and code [-Werror=declaration-after-statement] https://github.com/python-ldap/python-ldap/pull/53 Signed-off-by: Christian Heimes --- .travis.yml | 3 ++- Modules/LDAPObject.c | 6 +++--- Modules/constants.c | 3 +-- Modules/ldapmodule.c | 9 +++++---- Modules/message.c | 5 +++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5dda5e08..8154a771 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,8 @@ env: global: # -Wno-int-in-bool-context: don't complain about PyMem_MALLOC() # -Werror: turn all warnings into fatal errors - - CFLAGS="-Wno-int-in-bool-context -Werror" + # -Werror=declaration-after-statement: strict ISO C90 + - CFLAGS="-std=c90 -Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" # pass CFLAGS and WITH_GCOV to tox tasks - TOX_TESTENV_PASSENV="CFLAGS WITH_GCOV" diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 14387002..8c90a65c 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -998,6 +998,9 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) int res_msgid = 0; char *retoid = 0; PyObject *valuestr = NULL; + int result = LDAP_SUCCESS; + char **refs = NULL; + LDAPControl **serverctrls = 0; if (!PyArg_ParseTuple( args, "|iidiii", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) return NULL; @@ -1033,9 +1036,6 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) if (msg) res_msgid = ldap_msgid(msg); - int result = LDAP_SUCCESS; - char **refs = NULL; - LDAPControl **serverctrls = 0; 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) { diff --git a/Modules/constants.c b/Modules/constants.c index f0028a09..c2b595c1 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -60,12 +60,11 @@ LDAPerror( LDAP *l, char *msg ) 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; - char *matched, *error; - opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); if (opt_errnum != LDAP_OPT_SUCCESS) errnum = opt_errnum; diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 4c2c4ec9..18e0696d 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -41,10 +41,6 @@ PyObject* init_ldap_module(void) { PyObject *m, *d; - /* Initialize LDAP class */ - if (PyType_Ready(&LDAP_Type) < 0) - return NULL; - /* Create the module and add the functions */ #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef ldap_moduledef = { @@ -58,6 +54,11 @@ PyObject* init_ldap_module(void) #else m = Py_InitModule("_ldap", methods); #endif + /* 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); diff --git a/Modules/message.c b/Modules/message.c index babbd256..b7f3ae79 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -92,10 +92,11 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi ) { PyObject* valuelist; PyObject* pyattr; + struct berval **bvals; + pyattr = PyUnicode_FromString(attr); - struct berval ** bvals = - ldap_get_values_len( ld, entry, attr ); + bvals = ldap_get_values_len( ld, entry, attr ); /* Find which list to append to */ if ( PyDict_Contains( attrdict, pyattr ) ) { From 0d4814533f347469bbd0790b216d1c519c1d011f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 29 Nov 2017 18:01:20 +0100 Subject: [PATCH 049/369] Skip cert auth test when start_tls fails On Fedora 27, test_external_tlscert fails with CONNECT_ERROR when the test case is executed with the rest of the suit. It works fine when executed on its own. Skip the test on error for now until I have time to investigate. Signed-off-by: Christian Heimes --- Tests/t_ldap_sasl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index e689bb69..24c7b207 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -82,7 +82,12 @@ def test_external_tlscert(self): ldap_conn.set_option(ldap.OPT_X_TLS_KEYFILE, self.server.clientkey) ldap_conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) ldap_conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0) - ldap_conn.start_tls_s() + try: + ldap_conn.start_tls_s() + except ldap.CONNECT_ERROR as e: + # TODO: On Fedora 27 OpenLDAP server refuses STARTTLS when test + # is executed with other tests, + raise unittest.SkipTest("buggy start_tls_s: {}".format(e)) auth = ldap.sasl.external() ldap_conn.sasl_interactive_bind_s("", auth) From 725c6238b2b06e1310cec8b7afbe6b9f9db652a1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 29 Nov 2017 10:31:46 +0100 Subject: [PATCH 050/369] Add tests for schema urlfetch Signed-off-by: Christian Heimes --- Lib/ldap/schema/subentry.py | 2 +- Tests/t_ldap_schema_subentry.py | 50 ++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 1c10d21f..4e95151c 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -452,7 +452,7 @@ def urlfetch(uri,trace_level=0): is loaded with urllib. """ uri = uri.strip() - if uri.startswith('ldap:') or uri.startswith('ldaps:') or uri.startswith('ldapi:'): + if uri.startswith(('ldap:', 'ldaps:', 'ldapi:')): import ldapurl ldap_url = ldapurl.LDAPUrl(uri) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index ee79072b..d406b46a 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -7,11 +7,16 @@ import os import unittest -import time + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' +import ldap import ldif +from ldap.ldapobject import SimpleLDAPObject import ldap.schema from ldap.schema.models import ObjectClass +from slapdtest import SlapdTestCase HERE = os.path.abspath(os.path.dirname(__file__)) @@ -44,5 +49,48 @@ def test_subschema_file(self): self.assertEqual(attributetype.oid, oid) +class TestSubschemaUrlfetch(unittest.TestSuite): + def test_urlfetch_file(self): + freeipa_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[0]) + dn, schema = ldap.schema.urlfetch(freeipa_uri) + self.assertEqual(dn, 'cn=schema') + self.assertIsInstance(schema, ldap.schema.subentry.SubSchema) + obj = schema.get_obj(ObjectClass, '2.5.6.9') + self.assertEqual( + str(obj), + "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST cn " + "MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o " + "$ description ) )" + ) + + +class TestSubschemaUrlfetchSlapd(SlapdTestCase): + ldap_object_class = SimpleLDAPObject + + def assertSlapdSchema(self, dn, schema): + self.assertEqual(dn, 'cn=Subschema') + self.assertIsInstance(schema, ldap.schema.subentry.SubSchema) + obj = schema.get_obj(ObjectClass, '1.3.6.1.1.3.1') + self.assertEqual( + str(obj), + "( 1.3.6.1.1.3.1 NAME 'uidObject' DESC 'RFC2377: uid object' " + "SUP top AUXILIARY MUST uid )" + ) + entries = schema.ldap_entry() + self.assertIsInstance(entries, dict) + self.assertEqual(sorted(entries), [ + 'attributeTypes', 'ldapSyntaxes', 'matchingRuleUse', + 'matchingRules', 'objectClasses', + ]) + + def test_urlfetch_ldap(self): + dn, schema = ldap.schema.urlfetch(self.server.ldap_uri) + self.assertSlapdSchema(dn, schema) + + def test_urlfetch_ldapi(self): + dn, schema = ldap.schema.urlfetch(self.server.ldapi_uri) + self.assertSlapdSchema(dn, schema) + + if __name__ == '__main__': unittest.main() From 094624a0f6743869ef0e15e73fbabde5c8d3157f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 30 Nov 2017 08:01:22 +0100 Subject: [PATCH 051/369] Fix urlfetch on Python 2.7 Signed-off-by: Christian Heimes --- Lib/ldap/schema/subentry.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 4e95151c..6091a752 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -456,10 +456,9 @@ def urlfetch(uri,trace_level=0): import ldapurl ldap_url = ldapurl.LDAPUrl(uri) - # This is an internal function; don't enable bytes_mode. - l=ldap.initialize(ldap_url.initializeUrl(),trace_level,bytes_mode=False) + l=ldap.initialize(ldap_url.initializeUrl(),trace_level) l.protocol_version = ldap.VERSION3 - l.simple_bind_s(ldap_url.who or '', ldap_url.cred or '') + l.simple_bind_s(ldap_url.who or u'', ldap_url.cred or u'') subschemasubentry_dn = l.search_subschemasubentry_s(ldap_url.dn) if subschemasubentry_dn is None: s_temp = None From 53fa5b7e65efc0ad42f4f1891e517a3565c3c08e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 30 Nov 2017 13:54:41 +0100 Subject: [PATCH 052/369] Use SlapdTestCase in t_bind and t_edit Both test modules used to set up their own server instance. t_edit didn't clean up properly. Let's use SlapdTestCase test class everywhere. https://github.com/python-ldap/python-ldap/pull/58 Signed-off-by: Christian Heimes --- Tests/t_bind.py | 22 +++-------- Tests/t_edit.py | 100 ++++++++++++++++++++++++------------------------ 2 files changed, 56 insertions(+), 66 deletions(-) diff --git a/Tests/t_bind.py b/Tests/t_bind.py index 290deb6d..6e9a61ca 100644 --- a/Tests/t_bind.py +++ b/Tests/t_bind.py @@ -10,25 +10,15 @@ text_type = str import ldap, unittest -from slapdtest import SlapdObject +from slapdtest import SlapdTestCase from ldap.ldapobject import LDAPObject -class TestBinds(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.server = SlapdObject() - cls.server.start() - - cls.unicode_val = "abc\U0001f498def" - cls.unicode_val_bytes = cls.unicode_val.encode('utf-8') - - cls.dn_unicode = "CN=" + cls.unicode_val - cls.dn_bytes = cls.dn_unicode.encode('utf-8') - - @classmethod - def tearDownClass(cls): - cls.server.stop() +class TestBinds(SlapdTestCase): + unicode_val = "abc\U0001f498def" + unicode_val_bytes = unicode_val.encode('utf-8') + dn_unicode = "CN=" + unicode_val + dn_bytes = dn_unicode.encode('utf-8') def _get_ldapobject(self, bytes_mode=None): l = LDAPObject(self.server.ldap_uri, bytes_mode=bytes_mode) diff --git a/Tests/t_edit.py b/Tests/t_edit.py index df4529ad..0012c9cc 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -10,64 +10,64 @@ text_type = str import ldap, unittest -from slapdtest import SlapdObject +from slapdtest import SlapdTestCase from ldap.ldapobject import LDAPObject -server = None +class EditionTests(SlapdTestCase): -class EditionTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + super(EditionTests, cls).setUpClass() + base = cls.server.suffix + suffix_dc = base.split(',')[0][3:] - def setUp(self): - global server - if server is None: - server = SlapdObject() - server.start() - base = server.suffix - suffix_dc = base.split(',')[0][3:] + # insert some Foo* objects via ldapadd + cls.server.ldapadd("\n".join([ + 'dn: '+cls.server.suffix, + 'objectClass: dcObject', + 'objectClass: organization', + 'dc: '+suffix_dc, + 'o: '+suffix_dc, + '', + 'dn: '+cls.server.root_dn, + 'objectClass: applicationProcess', + 'cn: '+cls.server.root_cn, + '', + "dn: cn=Foo1,"+base, + "objectClass: organizationalRole", + "cn: Foo1", + "", + "dn: cn=Foo2,"+base, + "objectClass: organizationalRole", + "cn: Foo2", + "", + "dn: cn=Foo3,"+base, + "objectClass: organizationalRole", + "cn: Foo3", + "", + "dn: ou=Container,"+base, + "objectClass: organizationalUnit", + "ou: Container", + "", + "dn: cn=Foo4,ou=Container,"+base, + "objectClass: organizationalRole", + "cn: Foo4", + "", + ])+"\n") - # insert some Foo* objects via ldapadd - server.ldapadd("\n".join([ - 'dn: '+server.suffix, - 'objectClass: dcObject', - 'objectClass: organization', - 'dc: '+suffix_dc, - 'o: '+suffix_dc, - '', - 'dn: '+server.root_dn, - 'objectClass: applicationProcess', - 'cn: '+server.root_cn, - '', - "dn: cn=Foo1,"+base, - "objectClass: organizationalRole", - "cn: Foo1", - "", - "dn: cn=Foo2,"+base, - "objectClass: organizationalRole", - "cn: Foo2", - "", - "dn: cn=Foo3,"+base, - "objectClass: organizationalRole", - "cn: Foo3", - "", - "dn: ou=Container,"+base, - "objectClass: organizationalUnit", - "ou: Container", - "", - "dn: cn=Foo4,ou=Container,"+base, - "objectClass: organizationalRole", - "cn: Foo4", - "", - ])+"\n") + def setUp(self): + self.ldap = LDAPObject(self.server.ldap_uri, bytes_mode=False) + self.ldap.protocol_version = 3 + self.ldap.set_option(ldap.OPT_REFERRALS, 0) + self.ldap.simple_bind_s( + self.server.root_dn, + self.server.root_pw + ) - l = LDAPObject(server.ldap_uri, bytes_mode=False) - l.protocol_version = 3 - l.set_option(ldap.OPT_REFERRALS,0) - l.simple_bind_s(server.root_dn, - server.root_pw) - self.ldap = l - self.server = server + def tearDown(self): + self.ldap.unbind() def test_add_object(self): base = self.server.suffix From e716349e810aeb38195b0a22a630489cc19f7672 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 30 Nov 2017 14:04:28 +0100 Subject: [PATCH 053/369] Doc: Add dedicated page for bytes_mode --- CHANGES | 13 +++-- Doc/bytes_mode.rst | 105 +++++++++++++++++++++++++++++++++++++++++ Doc/index.rst | 36 +------------- Doc/reference/ldap.rst | 7 ++- Lib/ldap/functions.py | 2 +- 5 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 Doc/bytes_mode.rst diff --git a/CHANGES b/CHANGES index 113eb275..28f6f324 100644 --- a/CHANGES +++ b/CHANGES @@ -3,11 +3,16 @@ Released 3.0.0 xxxx-xx-xx Changes since 2.4.45: -Mandatory prerequisites: -- Python 2.7.x or 3.3+ -- pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ +New dependencies (automatically installed when using pip): +- pyasn1 0.3.7+ +- pyasn1_modules 0.1.5+ -Python 3 support is merged from the pyldap fork (https://github.com/pyldap) +Removed support for Python 2.6. + +Python 3 support and bytes_mode: +- merged from the pyldap fork (https://github.com/pyldap) +- please see documentation on bytes_mode and text/bytes handling: + https://python-ldap.readthedocs.io/en/latest/bytes_mode.html Infrastructure: - Add .gitignore diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst new file mode 100644 index 00000000..ba31f0c8 --- /dev/null +++ b/Doc/bytes_mode.rst @@ -0,0 +1,105 @@ +.. _text-bytes: + +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 `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). + +Attribute *values*, on the other hand, **MAY** +contain any type of data, including text. +To know what type of data is represented, python-ldap would need access to the +schema, which is not always available (nor always correct). +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 +-------------- + +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: + +``bytes_mode=True``: backwards-compatible + + 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. + + Not available in Python 3. + +``bytes_mode=False``: strictly future-compatible + + Text values must be represented as ``unicode``. + An error is raised if python-ldap receives a text value as bytes (``str``). + +Unspecified: relaxed mode with warnings + + Causes a warning on Python 2. + + 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. + +Backwards-compatible behavior is not scheduled for removal until Python 2 +itself reaches end of life. + + +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`_. + +.. _in Python documentation: https://docs.python.org/3/howto/pyporting.html +.. _Conservative porting guide: http://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'], + }), + ] diff --git a/Doc/index.rst b/Doc/index.rst index 2c5b24eb..ef652d96 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -68,47 +68,13 @@ Contents :maxdepth: 2 installing.rst + bytes_mode.rst reference/index.rst resources.rst contributing.rst faq.rst -Bytes/text management ---------------------- - -The LDAP protocol states that some fields (distinguished names, relative distinguished names, -attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** -contain any type of data, and thus be treated as bytes. - -In Python 2, ``python-ldap`` used bytes for all fields, including those guaranteed to be text. -In order to support Python 3, this distinction is made explicit. This is done -through the ``bytes_mode`` flag to ``ldap.initialize()``. - -When porting from ``python-ldap`` 2.x, users are advised to update their code to set ``bytes_mode=False`` -on calls to these methods. -Under Python 2, ``python-pyldap`` aggressively checks the type of provided arguments, and will raise a ``TypeError`` -for any invalid parameter. -However, if the ``bytes_mode`` kwarg isn't provided, ``pyldap`` will only -raise warnings. - -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('login', 'secret_password') - >>> results = con.search_s('ou=people,dc=example,dc=org', ldap.SCOPE_SUBTREE, "(cn=Raphaël)") - >>> results - [ - ("cn=Raphaël,ou=people,dc=example,dc=org", { - 'cn': [b'Rapha\xc3\xabl'], - 'sn': [b'Barrois'], - }), - ] - - Indices and tables ------------------ diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 8844e594..c28cdec5 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]]]) -> LDAPObject object +.. py:function:: initialize(uri [, trace_level=0 [, trace_file=sys.stdout [, trace_stack_limit=None, [bytes_mode=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 @@ -48,11 +48,14 @@ This module defines the following functions: that nothing is sent on the wire. The error handling in the calling application has to correctly handle this behaviour. - The optional arguments are for generating debug log information: + Three optional arguments are for generating debug log information: *trace_level* specifies the amount of information being logged, *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. + 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, diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index b8870378..1588221f 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -77,7 +77,7 @@ def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, b 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. + Whether to enable :ref:`bytes_mode` for backwards compatibility under Py2. """ return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode) From 47ce0dfb412e0874970412aa1758ed5d7bbf4aed Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 30 Nov 2017 15:39:55 +0100 Subject: [PATCH 054/369] Skip some TLS tests when libldap used NSS Some TLS tests are broken or flaky when libldap is compiled with NSS as TLS provider. It currently affects Fedora 27 and older releases. Fedora issue: https://bugzilla.redhat.com/show_bug.cgi?id=1519167 https://github.com/python-ldap/python-ldap/issues/60 Signed-off-by: Christian Heimes --- .travis.yml | 4 ++-- Lib/slapdtest.py | 39 +++++++++++++++++++++++++++++++++++++++ Tests/t_cext.py | 28 ++++++++++++++++------------ Tests/t_ldap_sasl.py | 10 +++------- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8154a771..ad9cd967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,8 +43,8 @@ env: # -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" - # pass CFLAGS and WITH_GCOV to tox tasks - - TOX_TESTENV_PASSENV="CFLAGS WITH_GCOV" + # pass CFLAGS, CI (for Travis CI) and WITH_GCOV to tox tasks + - TOX_TESTENV_PASSENV="CFLAGS CI WITH_GCOV" install: - pip install "pip>=7.1.0" diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index e0e57c75..ee1fbb29 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -18,6 +18,10 @@ from logging.handlers import SysLogHandler import unittest +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap from ldap.compat import quote_plus # a template string for generating simple slapd.conf file @@ -52,6 +56,41 @@ LOCALHOST = '127.0.0.1' + +def identity(test_item): + """Identity decorator + + """ + return test_item + + +def skip_unless_travis(reason): + """Skip test unless test case is executed on CI like Travis CI + """ + if os.environ.get('CI', False): + return identity + else: + return unittest.skip(reason) + + +def requires_tls(skip_nss=False): + """Decorator for TLS tests + + Tests are not skipped on CI (e.g. Travis CI) + + :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib + """ + if not ldap.TLS_AVAIL: + return skip_unless_travis("test needs ldap.TLS_AVAIL") + elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': + return skip_unless_travis( + "Test doesn't work correctly with Mozilla NSS, see " + "https://bugzilla.redhat.com/show_bug.cgi?id=1519167" + ) + else: + return identity + + def combined_logger( log_name, log_level=logging.WARN, diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 22eb895f..be858e95 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -9,7 +9,8 @@ import os import unittest -from slapdtest import SlapdTestCase + +from slapdtest import SlapdTestCase, requires_tls # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -717,12 +718,6 @@ def test_sasl(self): return # TODO - def test_tls(self): - l = self._open_conn() - if not self._require_attr(l, 'start_tls_s'): # HAVE_TLS - return - # TODO - def test_cancel(self): l = self._open_conn() if not self._require_attr(l, 'cancel'): # FEATURE_CANCEL @@ -807,7 +802,7 @@ def test_invalid_controls(self): l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,)) self.assertInvalidControls(l.unbind_ext) - @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + @requires_tls(skip_nss=True) def test_tls_ext(self): l = self._open_conn(bind=False) # StartTLS needs LDAPv3 @@ -817,15 +812,17 @@ def test_tls_ext(self): l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() - @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + @requires_tls(skip_nss=False) def test_tls_ext_noca(self): l = self._open_conn(bind=False) l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) - l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) - with self.assertRaises(_ldap.CONNECT_ERROR): + with self.assertRaises(_ldap.CONNECT_ERROR) as e: l.start_tls_s() + # some platforms return '(unknown error code)' as reason + if '(unknown error code)' not in str(e.exception): + self.assertIn('not trusted', str(e.exception)) - @unittest.skipUnless(_ldap.TLS_AVAIL, "needs tls") + @requires_tls(skip_nss=True) def test_tls_ext_clientcert(self): l = self._open_conn(bind=False) l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) @@ -836,5 +833,12 @@ def test_tls_ext_clientcert(self): l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() + @requires_tls(skip_nss=False) + def test_tls_packages(self): + # libldap has tls_g.c, tls_m.c, and tls_o.c with ldap_int_tls_impl + package = _ldap.get_option(_ldap.OPT_X_TLS_PACKAGE) + self.assertIn(package, {"GnuTLS", "MozNSS", "OpenSSL"}) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 24c7b207..9acd051e 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -14,7 +14,7 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.sasl -from slapdtest import SlapdTestCase +from slapdtest import SlapdTestCase, requires_tls LDIF = """ @@ -75,6 +75,7 @@ def test_external_ldapi(self): "dn:{}".format(self.server.root_dn.lower()) ) + @requires_tls(skip_nss=True) def test_external_tlscert(self): ldap_conn = self.ldap_object_class(self.server.ldap_uri) ldap_conn.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) @@ -82,12 +83,7 @@ def test_external_tlscert(self): ldap_conn.set_option(ldap.OPT_X_TLS_KEYFILE, self.server.clientkey) ldap_conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) ldap_conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0) - try: - ldap_conn.start_tls_s() - except ldap.CONNECT_ERROR as e: - # TODO: On Fedora 27 OpenLDAP server refuses STARTTLS when test - # is executed with other tests, - raise unittest.SkipTest("buggy start_tls_s: {}".format(e)) + ldap_conn.start_tls_s() auth = ldap.sasl.external() ldap_conn.sasl_interactive_bind_s("", auth) From db4a6ff6707f836b44e1d31688f14b9249e20055 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 30 Nov 2017 16:42:40 +0100 Subject: [PATCH 055/369] Infra: Remove nonexistent files from MANIFEST.in PKG-INFO is generated by setup.py. There's only one licence file, in the root. https://github.com/python-ldap/python-ldap/pull/66 --- MANIFEST.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 469ebdf0..85493352 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ -include MANIFEST.in Makefile CHANGES INSTALL LICENCE README TODO PKG-INFO +include MANIFEST.in Makefile CHANGES INSTALL LICENCE README TODO include tox.ini .coveragerc -include Modules/*.c Modules/*.h Modules/LICENSE +include Modules/*.c Modules/*.h recursive-include Build *.cfg* recursive-include Lib *.py recursive-include Demo *.py From 5618ce50389260e2f54fa4d8f6d03e456fd27a43 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 1 Dec 2017 15:26:09 +0100 Subject: [PATCH 056/369] Doc: Correct syntax for link to bytes mode https://github.com/python-ldap/python-ldap/pull/70 --- 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 ba31f0c8..b18c0aed 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -18,7 +18,7 @@ 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 `bytes mode `_ setting influences how text is +On Python 2, the :ref:`bytes mode ` setting influences how text is handled. From dcd064f26ef786fef55a6c76d260ce151cbe48b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 1 Dec 2017 15:32:10 +0100 Subject: [PATCH 057/369] Doc: Add a separator between sentences This rendered as follows: > OpenLDAP client libs version 2.4.11 or later It is not possible and not supported to build with prior versions. https://github.com/python-ldap/python-ldap/pull/71 --- Doc/installing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 39075390..5d82b7b7 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -112,8 +112,8 @@ on the local system when building python-ldap: - `Python`_ version 2.7, or 3.3 or later 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. +- `OpenLDAP`_ client libs version 2.4.11 or later; + it is not possible and not supported to build with prior versions. - `OpenSSL`_ (optional) - `Cyrus SASL`_ (optional) - Kerberos libraries, MIT or Heimdal (optional) From ba765af6fd0bbbc382566f524a888c85ef756f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 1 Dec 2017 15:38:42 +0100 Subject: [PATCH 058/369] Doc: openSUSE typos https://github.com/python-ldap/python-ldap/pull/72 --- Doc/installing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 5d82b7b7..b7c57deb 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -44,10 +44,10 @@ hit a problem already fixed in recent releases. `openSUSE Linux `_ --------------------------------------------- -ships with python-ldap and there's an additional +Ships with python-ldap and there's an additional `download repository `_ which contains builds of latest releases -(see also `OBS package `_ +(see also `OBS package `_). `Debian Linux `_ ---------------------------------------- From 610f875b8bd9c1d481a6f078effe0004824f026c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 30 Nov 2017 16:59:58 +0100 Subject: [PATCH 059/369] Update version to 3.0.0b1 --- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index da891b3d..378700de 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__ = '2.5.2' +__version__ = '3.0.0b1' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 366e177d..9c051a19 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '2.5.2' +__version__ = '3.0.0b1' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 4735bfb5..fe148950 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '2.5.2' +__version__ = '3.0.0b1' __all__ = [ # constants diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index ee1fbb29..cd142f63 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals -__version__ = '2.5.2' +__version__ = '3.0.0b1' import os import socket From 86ccb81922748437e8672d150b3a154a1250a3b1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 1 Dec 2017 13:06:55 +0100 Subject: [PATCH 060/369] Update Changes for 3.0.0b1 --- CHANGES | 67 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 28f6f324..b64c2637 100644 --- a/CHANGES +++ b/CHANGES @@ -1,26 +1,39 @@ ---------------------------------------------------------------- -Released 3.0.0 xxxx-xx-xx +Released 3.0.0b1 xxxx-xx-xx Changes since 2.4.45: +(this list includes changes from 2.5.x) New dependencies (automatically installed when using pip): -- pyasn1 0.3.7+ -- pyasn1_modules 0.1.5+ - -Removed support for Python 2.6. +* pyasn1 0.3.7+ +* pyasn1_modules 0.1.5+ Python 3 support and bytes_mode: -- merged from the pyldap fork (https://github.com/pyldap) -- please see documentation on bytes_mode and text/bytes handling: +* merged from the pyldap fork (https://github.com/pyldap) +* please see documentation on bytes_mode and text/bytes handling: https://python-ldap.readthedocs.io/en/latest/bytes_mode.html +Removed support for Python 2.6. + Infrastructure: -- Add .gitignore -- Re-format README to ReStructured Text -- Setup for automatic testing using Travis CI +* Move to Git +* Don't define search path for includes and libs in the default setup.cfg +* Include sasl/sasl.h from the standard path +* Re-format README to ReStructured Text +* Setup for automatic testing using Travis CI +* Add coverage reporting for Python and C +* Add install requires into setup.py +* Remove distclean.sh in favor of make clean +* Use `package`, `depends`, `install_requires` in setup.py +* Add make target for scan-build (static analysis using clang) Modules/ -(thanks to Michael Ströder) +* Remove unused LDAPberval helper functions +* Fix type conversion in page control +* Fix multiple ref leaks in error-handling code +* Fix reference leak in result4 +* Fix several compiler warnings +and, thanks to Michael Ströder: * removed unused code schema.c * moved code from version.c to ldapmodule.c * removed obsolete back-ward compability constants from common.h @@ -29,7 +42,9 @@ Modules/ * assume C extension API for Python 2.7+ Lib/ -(thanks to Michael Ströder) +* Avoid eval() for getting module-level variables to fix running under pytest +* Compability changes for pyasn1 0.3 or newer +and, thanks to Michael Ströder: * ldap.__version__, ldap.__author__ and ldap.__license__ now imported from new sub-module ldap.pkginfo also to setup.py * Added safety assertion when importing _ldap: @@ -37,8 +52,6 @@ Lib/ * removed stand-alone module dsml * slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data -* Compability changes for pyasn1 0.3.x or newer - (thanks to Ilya Etingof and Christian Heimes) * The methods SSSResponseControl.decodeControlValue() and VLVResponseControl.decodeControlValue() now follow the coding convention to use camel-cased ASN.1 name as class attribute name. @@ -60,21 +73,31 @@ Lib/ Lib/slapdtest.py * Automatically try some common locations for SCHEMADIR * Ensure server is stopped when the process exits +* Check for LDAP schema and slapd binaries Tests/ -(thanks to Michael Ströder) +* Expand cidict membership test +* Add test suite for binds +* Add test suite for edits +* Add a smoke-check for listall() and attribute_types() +* Add test case for SASL EXTERNAL auth +* Add tests for start_tls +* In CI, treat compiler warnings as fatal errors +* Added tests for ldap.syncrepl +and, thanks to Michael Ströder: * added explicit reconnect tests for ReconnectLDAPObject * scripts do not directly call SlapdTestCase.setUpClass() anymore * added LDIF test with folded, base64-encoded attribute * added more tests for sub-module ldap.dn -* added tests for ldap.syncrepl (thanks to Karl Kornel) -Tests/ -(thanks to pyldap contributors): -* Expand cidict membership test -* Add test suite for binds -* Add test suite for edits -* Add a smoke-check for listall() and attribute_types() +Doc/ +* Build documentation without the compiled C extension +* Merge contents from python-ldap.org +* Move reference documentation in its own section +* Document return value of {modify,add,delete}_ext_s() as a tuple +* Add tests for documentation (build & spelling) +* Link to documentation of old versions +* Add a contributing guide ---------------------------------------------------------------- Released 2.4.45 2017-10-09 From c84b4faf429431cb4d4ef42a156b62d1105f1758 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 1 Dec 2017 15:23:09 +0100 Subject: [PATCH 061/369] Doc: Add warning that 3.0 does not yet exist --- Doc/installing.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/installing.rst b/Doc/installing.rst index b7c57deb..d5cbf956 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -3,6 +3,14 @@ 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 cd6f62063db58339dc86b523507c8907181413a2 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 10:59:20 +0100 Subject: [PATCH 062/369] Ship documentation in source distribution Source distributions (sdist) of python-ldap now contain documentation. This allows users to build and read docs locally. Also fixes tox test run in sdist. https://github.com/python-ldap/python-ldap/pull/77 --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 85493352..f7398fab 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,3 +5,5 @@ recursive-include Build *.cfg* recursive-include Lib *.py recursive-include Demo *.py recursive-include Tests *.py *.ldif +recursive-include Doc *.rst *.py spelling_wordlist.txt Makefile +prune Doc/.build From 81ca90a77a667e26446d26223fdcf7e12ba45f15 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 2 Dec 2017 12:51:35 +0100 Subject: [PATCH 063/369] Convert slapdtest to package with data The slapdtest module has been converted to a package to ship the test certs as package data. Closes: https://github.com/python-ldap/python-ldap/issues/76 Signed-off-by: Christian Heimes --- Lib/{slapdtest.py => slapdtest/__init__.py} | 15 +++++++-------- {Tests => Lib/slapdtest}/certs/README | 0 {Tests => Lib/slapdtest}/certs/ca.conf | 0 {Tests => Lib/slapdtest}/certs/ca.pem | 0 {Tests => Lib/slapdtest}/certs/client.conf | 0 {Tests => Lib/slapdtest}/certs/client.key | 0 {Tests => Lib/slapdtest}/certs/client.pem | 0 {Tests => Lib/slapdtest}/certs/gencerts.sh | 0 {Tests => Lib/slapdtest}/certs/gennssdb.sh | 0 {Tests => Lib/slapdtest}/certs/server.conf | 0 {Tests => Lib/slapdtest}/certs/server.key | 0 {Tests => Lib/slapdtest}/certs/server.pem | 0 MANIFEST.in | 1 + setup.py | 3 ++- 14 files changed, 10 insertions(+), 9 deletions(-) rename Lib/{slapdtest.py => slapdtest/__init__.py} (97%) rename {Tests => Lib/slapdtest}/certs/README (100%) rename {Tests => Lib/slapdtest}/certs/ca.conf (100%) rename {Tests => Lib/slapdtest}/certs/ca.pem (100%) rename {Tests => Lib/slapdtest}/certs/client.conf (100%) rename {Tests => Lib/slapdtest}/certs/client.key (100%) rename {Tests => Lib/slapdtest}/certs/client.pem (100%) rename {Tests => Lib/slapdtest}/certs/gencerts.sh (100%) rename {Tests => Lib/slapdtest}/certs/gennssdb.sh (100%) rename {Tests => Lib/slapdtest}/certs/server.conf (100%) rename {Tests => Lib/slapdtest}/certs/server.key (100%) rename {Tests => Lib/slapdtest}/certs/server.pem (100%) diff --git a/Lib/slapdtest.py b/Lib/slapdtest/__init__.py similarity index 97% rename from Lib/slapdtest.py rename to Lib/slapdtest/__init__.py index cd142f63..297bda72 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest/__init__.py @@ -24,6 +24,8 @@ import ldap from ldap.compat import quote_plus +HERE = os.path.abspath(os.path.dirname(__file__)) + # a template string for generating simple slapd.conf file SLAPD_CONF_TEMPLATE = r""" serverID %(serverid)s @@ -191,14 +193,11 @@ def __init__(self): ldapi_path = os.path.join(self.testrundir, 'ldapi') self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) # TLS certs - capath = os.path.abspath(os.path.join( - os.getcwd(), 'Tests/certs' - )) - self.cafile = os.path.join(capath, 'ca.pem') - self.servercert = os.path.join(capath, 'server.pem') - self.serverkey = os.path.join(capath, 'server.key') - self.clientcert = os.path.join(capath, 'client.pem') - self.clientkey = os.path.join(capath, 'client.key') + self.cafile = os.path.join(HERE, 'certs/ca.pem') + self.servercert = os.path.join(HERE, 'certs/server.pem') + self.serverkey = os.path.join(HERE, 'certs/server.key') + self.clientcert = os.path.join(HERE, 'certs/client.pem') + self.clientkey = os.path.join(HERE, 'certs/client.key') def _check_requirements(self): binaries = [ diff --git a/Tests/certs/README b/Lib/slapdtest/certs/README similarity index 100% rename from Tests/certs/README rename to Lib/slapdtest/certs/README diff --git a/Tests/certs/ca.conf b/Lib/slapdtest/certs/ca.conf similarity index 100% rename from Tests/certs/ca.conf rename to Lib/slapdtest/certs/ca.conf diff --git a/Tests/certs/ca.pem b/Lib/slapdtest/certs/ca.pem similarity index 100% rename from Tests/certs/ca.pem rename to Lib/slapdtest/certs/ca.pem diff --git a/Tests/certs/client.conf b/Lib/slapdtest/certs/client.conf similarity index 100% rename from Tests/certs/client.conf rename to Lib/slapdtest/certs/client.conf diff --git a/Tests/certs/client.key b/Lib/slapdtest/certs/client.key similarity index 100% rename from Tests/certs/client.key rename to Lib/slapdtest/certs/client.key diff --git a/Tests/certs/client.pem b/Lib/slapdtest/certs/client.pem similarity index 100% rename from Tests/certs/client.pem rename to Lib/slapdtest/certs/client.pem diff --git a/Tests/certs/gencerts.sh b/Lib/slapdtest/certs/gencerts.sh similarity index 100% rename from Tests/certs/gencerts.sh rename to Lib/slapdtest/certs/gencerts.sh diff --git a/Tests/certs/gennssdb.sh b/Lib/slapdtest/certs/gennssdb.sh similarity index 100% rename from Tests/certs/gennssdb.sh rename to Lib/slapdtest/certs/gennssdb.sh diff --git a/Tests/certs/server.conf b/Lib/slapdtest/certs/server.conf similarity index 100% rename from Tests/certs/server.conf rename to Lib/slapdtest/certs/server.conf diff --git a/Tests/certs/server.key b/Lib/slapdtest/certs/server.key similarity index 100% rename from Tests/certs/server.key rename to Lib/slapdtest/certs/server.key diff --git a/Tests/certs/server.pem b/Lib/slapdtest/certs/server.pem similarity index 100% rename from Tests/certs/server.pem rename to Lib/slapdtest/certs/server.pem diff --git a/MANIFEST.in b/MANIFEST.in index f7398fab..687d2b0c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,5 +5,6 @@ recursive-include Build *.cfg* recursive-include Lib *.py recursive-include Demo *.py recursive-include Tests *.py *.ldif +recursive-include Lib/slapdtest *.pem *.key *.conf *.sh README recursive-include Doc *.rst *.py spelling_wordlist.txt Makefile prune Doc/.build diff --git a/setup.py b/setup.py index f09d9c3c..3d7d338b 100644 --- a/setup.py +++ b/setup.py @@ -175,13 +175,14 @@ class OpenLDAP2: py_modules = [ 'ldapurl', 'ldif', - 'slapdtest', + ], packages = [ 'ldap', 'ldap.controls', 'ldap.extop', 'ldap.schema', + 'slapdtest', ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, From e89b035a2127f75b4aab0059d1e0ce806403f6f5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 2 Dec 2017 13:03:08 +0100 Subject: [PATCH 064/369] Prolong certs to be valid for 10 years Now that we ship the certs as part of the package, make them valid for a longer time. CA validity is one day longer than certs, which are one day longer valid than CRL. Signed-off-by: Christian Heimes --- Lib/slapdtest/certs/ca.conf | 4 +- Lib/slapdtest/certs/ca.pem | 104 +++++++++++++++--------------- Lib/slapdtest/certs/client.key | 52 +++++++-------- Lib/slapdtest/certs/client.pem | 106 +++++++++++++++---------------- Lib/slapdtest/certs/gencerts.sh | 1 + Lib/slapdtest/certs/server.key | 52 +++++++-------- Lib/slapdtest/certs/server.pem | 108 ++++++++++++++++---------------- 7 files changed, 214 insertions(+), 213 deletions(-) diff --git a/Lib/slapdtest/certs/ca.conf b/Lib/slapdtest/certs/ca.conf index 4fb2e2ba..5046b0d6 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 = 1461 +default_days = 3652 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 = 365 +default_crl_days = 3651 [match_pol] countryName = match diff --git a/Lib/slapdtest/certs/ca.pem b/Lib/slapdtest/certs/ca.pem index ffd6a5e1..cf2ff33c 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: Nov 28 13:49:28 2017 GMT - Not After : Nov 28 13:49:28 2021 GMT + Not Before: Dec 2 11:57:47 2017 GMT + Not After : Sep 4 11:57:47 2027 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:b4:bd:47:78:d9:62:4e:8e:ae:18:53:b8:8e:9f: - 0b:13:ea:f2:59:c0:1d:7f:0b:5c:5a:ed:c8:b8:cd: - ff:66:94:c4:40:d5:cd:4c:da:06:d2:35:1c:6a:c5: - 76:70:6d:f0:81:8a:25:e4:35:c8:d5:79:17:3e:35: - ec:2b:97:73:77:6c:04:7f:1f:0b:b5:f3:fd:1a:cd: - 76:2c:5c:17:45:6a:6d:c4:d1:6c:f8:09:ba:a0:f8: - 57:44:d7:b4:1c:a4:be:a4:4b:e4:76:34:1c:b1:0b: - 12:42:7e:79:e4:01:e0:5f:8f:6c:03:2d:2a:b6:4e: - 72:85:f1:b9:ac:d5:22:a7:0a:fc:5a:0c:e5:75:8f: - 9f:20:c2:14:ab:53:6a:b0:e6:8a:0d:f4:97:53:7f: - f7:79:29:dd:ea:df:ae:39:6f:59:d8:b3:8e:da:bf: - 08:cd:03:ea:2a:91:33:c9:6f:21:a6:56:06:10:c1: - 40:4b:31:95:b0:47:2a:1e:2f:7d:eb:9b:f1:d4:27: - af:1a:5d:bf:0f:b4:f5:d5:b8:1d:04:61:fa:06:af: - 49:6f:ff:99:88:f3:44:16:ed:3b:f1:c8:ea:31:0a: - f3:f5:97:7a:17:08:e5:e8:8f:dd:1d:c8:8f:55:bf: - 47:93:9a:be:84:99:6f:f3:99:61:5d:b7:13:56:34: - 04:91 + 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 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: - 3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + 3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 X509v3 Authority Key Identifier: - keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 Signature Algorithm: sha256WithRSAEncryption - 03:d2:e6:f5:e4:1d:99:2e:df:02:b9:5b:f7:e0:82:ca:39:76: - 5c:8f:3d:f1:5f:d4:bd:1b:4b:4f:35:4b:5d:f6:21:8b:c0:87: - 23:de:7e:9d:93:1b:67:60:6d:df:7b:95:f0:6a:87:94:5c:f9: - a3:62:50:7b:68:02:b0:5c:d2:4b:f9:b1:97:ba:24:84:47:6e: - e1:e9:d6:e0:fa:2f:f7:9b:c5:67:3a:eb:8e:bc:b7:8f:31:7d: - 5c:ba:91:ce:9d:2c:91:85:32:74:e7:e9:ba:07:61:7c:8b:55: - 69:db:f9:7b:1d:ef:55:fc:bf:58:fc:99:66:9c:9e:92:35:75: - 53:0a:31:e1:34:8a:cf:bc:79:b0:0e:ac:bf:aa:f0:e3:74:88: - f2:a6:5c:38:ad:21:31:ea:cd:e2:57:01:6b:9e:0b:24:62:78: - ac:6a:88:ba:ae:be:20:27:63:ab:3b:e9:2c:c0:5c:81:c5:e7: - e6:fe:a7:09:30:b4:28:65:51:6f:1b:1d:22:f8:30:7d:87:ae: - da:28:99:5c:8b:15:f0:b7:45:d0:1d:3b:ad:c5:29:4a:ed:11: - 68:c4:af:28:a3:0b:9f:1b:c8:86:56:80:3d:6b:d3:1f:c6:b0: - 3d:4a:39:1c:10:57:2d:22:df:d7:7b:ad:7d:f9:12:47:bb:33: - 29:61:14:c4 + 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 -----BEGIN CERTIFICATE----- MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 -MTM0OTI4WjBWMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ3WhcNMjcwOTA0 +MTE1NzQ3WjBWMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR BgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNVBAMME1B5dGhvbiBMREFQIFRlc3QgQ0Ew -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0vUd42WJOjq4YU7iOnwsT -6vJZwB1/C1xa7ci4zf9mlMRA1c1M2gbSNRxqxXZwbfCBiiXkNcjVeRc+Newrl3N3 -bAR/Hwu18/0azXYsXBdFam3E0Wz4Cbqg+FdE17QcpL6kS+R2NByxCxJCfnnkAeBf -j2wDLSq2TnKF8bms1SKnCvxaDOV1j58gwhSrU2qw5ooN9JdTf/d5Kd3q3645b1nY -s47avwjNA+oqkTPJbyGmVgYQwUBLMZWwRyoeL33rm/HUJ68aXb8PtPXVuB0EYfoG -r0lv/5mI80QW7TvxyOoxCvP1l3oXCOXoj90dyI9Vv0eTmr6EmW/zmWFdtxNWNASR +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvH88PxZVmLeuFzCH8DQ9E +2C+ohQjvYGdX+gvF5LP78W/LMHpHDafxtTeBX/Y5KOL5TWwuplwOPNtNySpkzg0V +MMd1Urh0xQsATC+UG937gyxYAnOwhjpqqlXy1UmZF6XiROzdYl+NzncpC42HI+JL +1hwl8wap7jNvrO0injXsVecbOGh+RuPDQqwGCwp6hMk97z2lbukQJMMo/h9KmiOK +PNsKZl0H+MUXaFPkDjczxNKtWGJrioerc+u8K6wHaYSN48SpeJtsHgNj37SWGL08 +Lr5/LNWo+BK5qydSsN44YjxUp/OqN6MRErKnb42WEM4ByyUkplEYk2mbnlyK//6J AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud -DgQWBBQ9gOO7lJfb3d/IGiOKyyvBPRrmhTAfBgNVHSMEGDAWgBQ9gOO7lJfb3d/I -GiOKyyvBPRrmhTANBgkqhkiG9w0BAQsFAAOCAQEAA9Lm9eQdmS7fArlb9+CCyjl2 -XI898V/UvRtLTzVLXfYhi8CHI95+nZMbZ2Bt33uV8GqHlFz5o2JQe2gCsFzSS/mx -l7okhEdu4enW4Pov95vFZzrrjry3jzF9XLqRzp0skYUydOfpugdhfItVadv5ex3v -Vfy/WPyZZpyekjV1Uwox4TSKz7x5sA6sv6rw43SI8qZcOK0hMerN4lcBa54LJGJ4 -rGqIuq6+ICdjqzvpLMBcgcXn5v6nCTC0KGVRbxsdIvgwfYeu2iiZXIsV8LdF0B07 -rcUpSu0RaMSvKKMLnxvIhlaAPWvTH8awPUo5HBBXLSLf13utffkSR7szKWEUxA== +DgQWBBQ7HzL0/lfRb0mRVfIk8QpmO6Xu1DAfBgNVHSMEGDAWgBQ7HzL0/lfRb0mR +VfIk8QpmO6Xu1DANBgkqhkiG9w0BAQsFAAOCAQEACufcOM4D3aiZEdAkvu8aGJ18 +lXVKSilEIyj8ZtWBzgXCwGtx1o0zqVOmHPFOUK6jsXLWaVOtqWKpRSdoFzVBl+zp +ZZFiEu3rRTqbzAm8460iaxNrsGfvzgGDXmyV4rNzuWmaM0n5X1JOOZTJ25Nv2LoQ +ks76Emu8Mf/BZ3BjB9xTejqjUSAVRM8cqc23MB2OVZOKVow96YuuDHeNXIv9IthM +PuR26Nnow5j0mP8CYJWOPiZ64v4sCqRSjUw93Uz9Lyzbg0wrJSQ3eJoHJ1L5HMBl +ZctQd7Qt+vSvu0IcQ2XGAW7xS/64Sjwpi7aEHheZYZhl/vLpzrush2nL5hNCvw== -----END CERTIFICATE----- diff --git a/Lib/slapdtest/certs/client.key b/Lib/slapdtest/certs/client.key index 81c3e766..70600ba5 100644 --- a/Lib/slapdtest/certs/client.key +++ b/Lib/slapdtest/certs/client.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4aZb1WfASk2JR -VAwFMMQCJNkbBDj7d8QdqSMa32jgTG8m/0N5RYGhdIi6zVeO2GmrSDuRd9Lv5hqu -6ZgFI/rRCYSZHrIe6MXSOs6ZZKHIeQYkZcKa7Eg1FWQPBS51FJjYcotoHj5uxyKZ -pOBMC63qODgq77G4zcqIKVswKS34KAIwvBAAUmTMtyMOOEh8oZm4V4ILpFFNBfs9 -CcyKMcAtCiFXOYZQ0G16ExzT560FMxMmCHd8HkevhfgFlKZb9/bsHNb+tvBwTQNm -eUl4gnPDJk6YLBGIscBImvSRJu8o07aZBvAZvRhPa5iX/mtnlg0pDQ9jiGFK24qB -VPQKtwHjAgMBAAECggEBAIYO7Fir6uP2FHD+4kYbr0HHu7PyG+JirETLoeN1KW50 -4hC9XDWam6PdbVAu2knTdO248uTK8KLI6fjhg0upXjn761LMh7wEh1pOucW07A8q -O5bWCuRIhC1iwXxRzfX65SnkCwfDhKtPRA3hV9SwYNt1xw8fdFjd8S+OsOWP6gUN -A33rBN6L44zfabZmcIO4DvSjDP4zoOL640uFg9dyf2yHzFI+q4mZgMK2iW5TZILl -pNfdQF/hZsqrme+kzUyEtQHzLxGk1twYlFubePS0K1wzXRw6OqFRA0+pYNYP+JmJ -bQs1k/e/y5qRiD9+UzR3MOTASl9BWDu9KCK1LF5pqYECgYEA7SD43REkVmQkFinf -Re1kfOTkqCub1t4veTZBqVBNCF10fnQwPk1UQ2PShum1iJun6S3P0NzJP/svGc0N -m44XJaD8QHnXdsDkHGx36/Cj15+VYpGvSpkPUgVQk4Q82n29Y3v8/xR6xqhXiB2N -kuWHig8WIrrc690mUlL1NLQvH+ECgYEAxxaggQXlBTdDSMj2x3Y/0TJlkese2Oet -JxWu6xGfjrsjMxZj1c5C3ESeKws3Z1eH9nk3AVzJ/q/+2SyCM3lM+eFz6dAtKVDB -Olly4YhxoEZDgW4JXMVLP8d7xNDxWk9y7UitpvfDJ8J0hf//y9KR2jnokkfSKScN -AsqrMe7+6kMCgYEAgndztV3rGkU6vZ8II1c7xKPDUuu7cHsKr6w0cE2oNIQGxlRy -/rRZOkK/4E7R/Hl35wm3n3j6mWNARPfXFtEU1zU91NO0wrfaSfE8AeqCmu5IqNTz -Fx4jmcMm1CMbwDMScpwTVN0VuBuDHXb1H+99pW4rhaw+RN+GaCEQnJDOpMECgYBv -UTOFcOpRNEkm1VdGx9N/ARLRuAmTdlbW18TqIvx4LiLMWeSQk7fGuYdGwgrEeajI -I5ah6GP5SCbS/5P9fAGSZoENZx0ZUNH58jHN8SC3YRI1uHT7rkUY8E1ACyQoPuwf -yNdv2HECNjQ5CJ7aNG7g+igUQpw77l3UBcYbMWrPSQKBgG+R+nFGbjJ7oHyvJyJI -C3rlD6vay9BoXmjA05dCO36zDcq5xZqwLnQmYkFgBw+RwvwV8gbBirG7GEpy0rdt -5YbHwiI6fMfLsXocU9f86o2dhhpA2o42ig3mfe8X3LteWtmRfJ3BtHbW2ZWS8Kvx -MnZGm64AmjWV5oJb7e5jpgu6 +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= -----END PRIVATE KEY----- diff --git a/Lib/slapdtest/certs/client.pem b/Lib/slapdtest/certs/client.pem index 8133c6dd..33b95a73 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: Nov 28 13:49:28 2017 GMT - Not After : Nov 28 13:49:28 2021 GMT + Not Before: Dec 2 11:57:48 2017 GMT + Not After : Dec 2 11:57:48 2027 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:b8:69:96:f5:59:f0:12:93:62:51:54:0c:05:30: - c4:02:24:d9:1b:04:38:fb:77:c4:1d:a9:23:1a:df: - 68:e0:4c:6f:26:ff:43:79:45:81:a1:74:88:ba:cd: - 57:8e:d8:69:ab:48:3b:91:77:d2:ef:e6:1a:ae:e9: - 98:05:23:fa:d1:09:84:99:1e:b2:1e:e8:c5:d2:3a: - ce:99:64:a1:c8:79:06:24:65:c2:9a:ec:48:35:15: - 64:0f:05:2e:75:14:98:d8:72:8b:68:1e:3e:6e:c7: - 22:99:a4:e0:4c:0b:ad:ea:38:38:2a:ef:b1:b8:cd: - ca:88:29:5b:30:29:2d:f8:28:02:30:bc:10:00:52: - 64:cc:b7:23:0e:38:48:7c:a1:99:b8:57:82:0b:a4: - 51:4d:05:fb:3d:09:cc:8a:31:c0:2d:0a:21:57:39: - 86:50:d0:6d:7a:13:1c:d3:e7:ad:05:33:13:26:08: - 77:7c:1e:47:af:85:f8:05:94:a6:5b:f7:f6:ec:1c: - d6:fe:b6:f0:70:4d:03:66:79:49:78:82:73:c3:26: - 4e:98:2c:11:88:b1:c0:48:9a:f4:91:26:ef:28:d3: - b6:99:06:f0:19:bd:18:4f:6b:98:97:fe:6b:67:96: - 0d:29:0d:0f:63:88:61:4a:db:8a:81:54:f4:0a:b7: - 01:e3 + 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 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: - C5:E2:E2:72:A6:55:C1:FB:D3:4B:11:01:D0:A0:AE:99:B6:8F:F1:39 + 67:63:38:F4:B4:BC:F3:6B:BC:74:0E:7C:27:C9:BB:C2:CC:58:AC:16 X509v3 Authority Key Identifier: - keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 Signature Algorithm: sha256WithRSAEncryption - 11:76:03:a3:a6:9d:4e:3e:74:93:50:3b:31:d5:c7:d0:a8:34: - 84:01:6b:a2:08:cd:89:11:57:97:a2:e9:83:f0:a6:ef:03:8c: - b2:8d:31:8f:ee:b3:c9:8c:49:1f:e4:fc:21:58:8d:22:c2:17: - 96:2e:b1:d1:64:03:d6:48:70:41:65:22:38:bf:9f:00:dd:a8: - 82:a0:df:22:e0:9a:1b:27:16:4b:db:7f:b0:fe:9f:a8:80:90: - 69:ad:35:be:e6:95:fe:f9:64:96:0a:d8:d7:8e:aa:63:fb:9a: - d5:d0:e9:cb:8c:f3:08:c0:0c:26:72:c9:c6:29:5c:cf:3c:9e: - e2:38:37:5a:16:2f:70:82:0b:e6:80:27:90:bd:81:b4:58:52: - 6c:4e:b7:77:fc:49:76:7a:4f:6c:ee:ff:02:1f:f4:49:6e:78: - c3:fa:cd:ee:d3:e7:e5:05:a4:16:97:48:16:44:9f:81:1c:ac: - a9:30:0e:1d:ea:15:32:d7:2b:1e:68:92:0a:9c:5b:b2:27:57: - d3:21:3e:76:71:94:2d:da:76:61:aa:64:99:b7:54:1c:ba:e2: - a5:b3:21:a4:a8:36:fe:5d:a3:73:6a:5e:77:af:ab:9a:d9:62: - 00:f0:fd:90:1e:b6:cc:57:5d:92:0d:16:16:97:78:48:27:fc: - d9:2d:55:4d + 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 -----BEGIN CERTIFICATE----- MIIDkjCCAnqgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 -MTM0OTI4WjBJMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy +MTE1NzQ4WjBJMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR BgNVBAsMCnNsYXBkLXRlc3QxDzANBgNVBAMMBmNsaWVudDCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBALhplvVZ8BKTYlFUDAUwxAIk2RsEOPt3xB2pIxrf -aOBMbyb/Q3lFgaF0iLrNV47YaatIO5F30u/mGq7pmAUj+tEJhJkesh7oxdI6zplk -och5BiRlwprsSDUVZA8FLnUUmNhyi2gePm7HIpmk4EwLreo4OCrvsbjNyogpWzAp -LfgoAjC8EABSZMy3Iw44SHyhmbhXggukUU0F+z0JzIoxwC0KIVc5hlDQbXoTHNPn -rQUzEyYId3weR6+F+AWUplv39uwc1v628HBNA2Z5SXiCc8MmTpgsEYixwEia9JEm -7yjTtpkG8Bm9GE9rmJf+a2eWDSkND2OIYUrbioFU9Aq3AeMCAwEAAaN4MHYwDAYD +AQEBBQADggEPADCCAQoCggEBAMbG9swRtqFWIrg/pqhZSRyIh/V0RhMG5TwzV5KF +ERRndi2r957jbmldHLHEOsBrzKBGOqzu1q7CGAAo249EbLDuHJnUq7i9A5vBf2vP +IdDoWPrTpTgcdth8BVvQAXs7MtuE0feKwJG01yYRRJgbU2R+f3NCRXinPYokzh4+ +3gbIYlXQ2FFR0d3CCxaK5G3YQhE/Atd2R//iy+bYRqEAlrH5Ixplh7+WD3D1IHGX +oNNoX2UWmg+rN5b4qdcKYTx76pJZ5rwSOTMhFdrgon8SdQw/Z9Qv4l7ZqAYXZh8I +h31i8tNBXxgO/sibVKnSxshQOhZfXgYNRtnxwGvB87IjRN8CAwEAAaN4MHYwDAYD VR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH -AwIwHQYDVR0OBBYEFMXi4nKmVcH700sRAdCgrpm2j/E5MB8GA1UdIwQYMBaAFD2A -47uUl9vd38gaI4rLK8E9GuaFMA0GCSqGSIb3DQEBCwUAA4IBAQARdgOjpp1OPnST -UDsx1cfQqDSEAWuiCM2JEVeXoumD8KbvA4yyjTGP7rPJjEkf5PwhWI0iwheWLrHR -ZAPWSHBBZSI4v58A3aiCoN8i4JobJxZL23+w/p+ogJBprTW+5pX++WSWCtjXjqpj -+5rV0OnLjPMIwAwmcsnGKVzPPJ7iODdaFi9wggvmgCeQvYG0WFJsTrd3/El2ek9s -7v8CH/RJbnjD+s3u0+flBaQWl0gWRJ+BHKypMA4d6hUy1yseaJIKnFuyJ1fTIT52 -cZQt2nZhqmSZt1QcuuKlsyGkqDb+XaNzal53r6ua2WIA8P2QHrbMV12SDRYWl3hI -J/zZLVVN +AwIwHQYDVR0OBBYEFGdjOPS0vPNrvHQOfCfJu8LMWKwWMB8GA1UdIwQYMBaAFDsf +MvT+V9FvSZFV8iTxCmY7pe7UMA0GCSqGSIb3DQEBCwUAA4IBAQB2JEJrM0/WWQdI +WwScPNM/Y4B1TXjX1YWxd4Exo5HLyaOMDgAoCHRxbPyDjIDsHOjug+B/STvzQjNa +H2gMpUFCzr93KQfyGKeBF9d2RwTZit3oWiYm6qR2cOHx+uHbvPIksjeoWC/jZol3 +AlWH7zwfZs5OhrNMV0OGf0yrWjPdyuMvO6+0Q1pTi+AS2ufAE3ayaNUU+BoHzoqH +XJG9NdeDxiqk4JJQAbnC+mkGXIqA7pwk+Ulk41nBpmkpzreJIKl81p/fKtGkmCpt +e5NqUuOu3hrY8y7PAn66mvr0s7VumiMQcFNTMNWKMjUBUlhtnfWOu7l2vUEWiCb4 +085wA8hZ -----END CERTIFICATE----- diff --git a/Lib/slapdtest/certs/gencerts.sh b/Lib/slapdtest/certs/gencerts.sh index 6edafa01..7a971a3a 100755 --- a/Lib/slapdtest/certs/gencerts.sh +++ b/Lib/slapdtest/certs/gencerts.sh @@ -29,6 +29,7 @@ openssl ca -selfsign \ -in $CATMPDIR/ca.csr \ -out $CAOUTDIR/ca.pem \ -extensions ca_ext \ + -days 3563 \ -batch # server cert diff --git a/Lib/slapdtest/certs/server.key b/Lib/slapdtest/certs/server.key index 081dc2f7..a48ee567 100644 --- a/Lib/slapdtest/certs/server.key +++ b/Lib/slapdtest/certs/server.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTJOAnZdPnCTmy -gkI5XUHsuk1xL4eePxMcV+8HYMfQh+b354CygQnRcjj38uQ6S70mOIyCrTWKNrec -VrnV0xu4o16AkSRrkN0AVpfWk0ae2GaccDV+o82x+NBStfYNXW2xZ1aZIyFplGTU -yxQCpsU4yCX7RM7Ywq3fM6b8eoR2jRNGC/uMdNN/7WJkHfhJFOYFpPJ6vKOlR3PP -qzkj8WRu/ill63+phHA3UEUweKG4DoaP85M0WqHW4Kvp0A4sI5uPYI2lPrDqUf6L -Wc22gUpcFFNDReWYcxLobCsAeT4SqvEC83Dv53NKQS6wt4zLewVG1+8cjyctwv4m -PAatL8uDAgMBAAECggEACB8+BCX1nciMEKLUG1LMC2grPHRgmiTA/nEff8AoT4w6 -xUSBfdxa3VSwflE4mEl7kDHSreAt1BBAxeHMKj6BrXuTUgzDQuQCrFWoZ5eousmG -QPRMCoAQlI0GrnfTbDYw1wcrnJ5uVZpgupLJRUTXB1UjqOO/tTTf7VsWTFYGLG00 -8OeIenTGX0E1mVW9ERYCSYEtMfhQhSS3dhPmMm5VV6muIxv4ym6r2uodCY7ocxGA -JmI9F22xovH8CUtzoRbh0o4Y9VUVkKcMDl35+hya8c0BH4Vh22hnYgiyUkS1pBgY -GiX/rmU4FxkzLDrLLMB8cf+rzh+aOKjNupZ2dbNgsQKBgQDu3DLRHhUJEUcoK6Gb -ltVK179WsT1Me1Y/qiZ2SgGsXmSZFK+8cYLvXUI7Epz2Du2DwAb1J+sItie5QQ9g -o6eymTS7DuQ6MFS1UmC8PfSNjT/dFd/sVIEFJRcNkVC4CpJCnE4MQOUb6OIT8YnR -+HNDNMWc5+ab1OeFxMWRSrP52wKBgQDiS4oFkhfPtaxVyUHuiR0+0icjCGehWSM0 -eNoiX561HtBrYCZYp6kg1JMSLBSJPRaHi78td3z+6iDvE6HQtwrLq/LNd2CRJ80U -rkGjsItpZYfnJVMmK6Y4KksGlvYqWshWb8Zp0o+LPRb6g0TxZrbOGv7yD8FjaZwq -wlGxleIJeQKBgDDR+uT5BA2lZWjVeiOF8bRpYmdCtKe0Mc2zZkcZGzxy0pbjPoQC -o2NvKUFPrZsxM+SQ1Bs5fHV8XaQkoxL6gCUl4Tw2b/lgtX+WBcWT2C8yZpI2jV0N -bI1zpkGUqO3k4z4QGnewr+NDdyniXWv4Hv7mg1ltoJnLK0MRE9x9a0mvAoGBAIi4 -8w8ikVhhf1nlWvxvw0etWRAFh/coD+koC0MxWoY7s9jsIr71rW20gZc9Irs4OWBz -wnIJ+29YrcVEq0ObE96yaORS9/k7fuC719S6WcC4I0A4gOBTBv5wLxwwIVK9vsTa -i5psKWYK5tM8dG8Vi+VC0j4V3tXdfQkolosg74yhAoGAOLj21q7nNk+ExGAOesmR -qykxwIXxbHxDIfmEJJVE6OJMRvubr8WgaiSG0ptWBeiHDnldUS436difn7JXlsdo -K51jVzzgUqZXK75xvnSD7e5AL3fFRCU5usoU699fxWJTMs8H2z4alRng06MloeXJ -xrLwQyuLH8BWYFuPj4QxwwQ= +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 -----END PRIVATE KEY----- diff --git a/Lib/slapdtest/certs/server.pem b/Lib/slapdtest/certs/server.pem index 45ae694d..7e750596 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: Nov 28 13:49:28 2017 GMT - Not After : Nov 28 13:49:28 2021 GMT + Not Before: Dec 2 11:57:48 2017 GMT + Not After : Dec 2 11:57:48 2027 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:d3:24:e0:27:65:d3:e7:09:39:b2:82:42:39:5d: - 41:ec:ba:4d:71:2f:87:9e:3f:13:1c:57:ef:07:60: - c7:d0:87:e6:f7:e7:80:b2:81:09:d1:72:38:f7:f2: - e4:3a:4b:bd:26:38:8c:82:ad:35:8a:36:b7:9c:56: - b9:d5:d3:1b:b8:a3:5e:80:91:24:6b:90:dd:00:56: - 97:d6:93:46:9e:d8:66:9c:70:35:7e:a3:cd:b1:f8: - d0:52:b5:f6:0d:5d:6d:b1:67:56:99:23:21:69:94: - 64:d4:cb:14:02:a6:c5:38:c8:25:fb:44:ce:d8:c2: - ad:df:33:a6:fc:7a:84:76:8d:13:46:0b:fb:8c:74: - d3:7f:ed:62:64:1d:f8:49:14:e6:05:a4:f2:7a:bc: - a3:a5:47:73:cf:ab:39:23:f1:64:6e:fe:29:65:eb: - 7f:a9:84:70:37:50:45:30:78:a1:b8:0e:86:8f:f3: - 93:34:5a:a1:d6:e0:ab:e9:d0:0e:2c:23:9b:8f:60: - 8d:a5:3e:b0:ea:51:fe:8b:59:cd:b6:81:4a:5c:14: - 53:43:45:e5:98:73:12:e8:6c:2b:00:79:3e:12:aa: - f1:02:f3:70:ef:e7:73:4a:41:2e:b0:b7:8c:cb:7b: - 05:46:d7:ef:1c:8f:27:2d:c2:fe:26:3c:06:ad:2f: - cb:83 + 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 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: - 98:03:0F:65:1A:34:A9:E1:09:1B:EC:D0:ED:A8:11:E6:AE:85:1F:2C + 1B:78:45:40:0D:50:8A:8B:3B:C1:0A:F8:3F:7A:48:7B:A6:3C:28:09 X509v3 Authority Key Identifier: - keyid:3D:80:E3:BB:94:97:DB:DD:DF:C8:1A:23:8A:CB:2B:C1:3D:1A:E6:85 + keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4 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 - a6:01:8e:6f:0f:db:35:1f:a2:03:f5:39:ca:16:5f:e5:5a:0f: - 7e:f1:24:8a:b9:6f:78:c1:2c:f7:3a:07:f1:27:46:47:e0:98: - a1:ec:23:38:30:b9:bc:66:ac:17:06:36:48:ad:5d:1d:b4:a2: - c1:2c:08:55:ff:00:01:04:26:60:38:a6:13:52:7b:d3:88:8a: - b1:1f:a4:cd:14:9b:c8:04:77:01:72:6d:fe:b5:8c:3a:25:e2: - 2b:83:73:4b:2e:c6:6f:20:8b:00:c7:3b:2e:de:62:e9:74:81: - c6:a5:f5:b1:f9:db:b9:58:f9:7c:a2:3f:4e:46:93:ae:af:75: - 01:77:ea:34:4e:82:dc:d3:53:39:c0:b0:f9:8a:86:8d:f5:f2: - c3:a0:fa:e4:a9:b3:2c:33:ba:78:d5:ef:12:05:33:4f:29:9b: - 88:41:52:73:5a:17:7e:bc:66:d0:dc:c7:14:6d:a5:99:78:67: - 81:36:76:29:63:de:76:d4:eb:ca:5c:82:1b:ae:7d:80:ed:32: - 69:b0:b7:7a:1d:e1:00:a8:b1:95:d3:4d:d5:e2:8c:f4:30:a7: - da:79:bd:f5:bb:7a:8d:c5:ec:1c:16:32:58:92:ad:fc:1c:83: - 49:ca:8c:f9:6d:d4:f7:2c:98:1a:25:47:36:16:4f:15:46:ea: - 71:78:f1:51 + 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 -----BEGIN CERTIFICATE----- MIID1TCCAr2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV -BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMTI4MTM0OTI4WhcNMjExMTI4 -MTM0OTI4WjBcMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR +BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy +MTE1NzQ4WjBcMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR BgNVBAsMCnNsYXBkLXRlc3QxIjAgBgNVBAMMGXNlcnZlciBjZXJ0IGZvciBsb2Nh -bGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTJOAnZdPnCTmy -gkI5XUHsuk1xL4eePxMcV+8HYMfQh+b354CygQnRcjj38uQ6S70mOIyCrTWKNrec -VrnV0xu4o16AkSRrkN0AVpfWk0ae2GaccDV+o82x+NBStfYNXW2xZ1aZIyFplGTU -yxQCpsU4yCX7RM7Ywq3fM6b8eoR2jRNGC/uMdNN/7WJkHfhJFOYFpPJ6vKOlR3PP -qzkj8WRu/ill63+phHA3UEUweKG4DoaP85M0WqHW4Kvp0A4sI5uPYI2lPrDqUf6L -Wc22gUpcFFNDReWYcxLobCsAeT4SqvEC83Dv53NKQS6wt4zLewVG1+8cjyctwv4m -PAatL8uDAgMBAAGjgacwgaQwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw -FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFJgDD2UaNKnhCRvs0O2o -EeauhR8sMB8GA1UdIwQYMBaAFD2A47uUl9vd38gaI4rLK8E9GuaFMCwGA1UdEQQl +bGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAgcI7Pj89Aw4r +rb+N8j3t1ynJgXRhQNxbxQcQmUCi8AtpGKNXu+aM9u2HxZ677ALfhsEivtQA5QKz +Ll5G2G2IQa7uzgIco73OL/kMZIJt7sKfnvfACtSoOlD0IyOzVEEu0AVA7hMHb6Ul +I0mCNfdk2FWFbc1nUmIAEBhIODbFoNW+Rc6lz94NsUqArDayvWpAsXkUbubQikpi +KNX1OpOOC9GUbEhwI/G90ZnUg9STk/2oxsvwLlYdOhGhpyfl53tTu7eLMBriMxFl +UTvkY1GUgPj0fA/giWtCerGOyeKu1hFlbS4LjborsfrlyYPwfwTg3YL4hVnt9fF0 +rpjzY1mlAgMBAAGjgacwgaQwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw +FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFBt4RUANUIqLO8EK+D96 +SHumPCgJMB8GA1UdIwQYMBaAFDsfMvT+V9FvSZFV8iTxCmY7pe7UMCwGA1UdEQQl MCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B -AQsFAAOCAQEApgGObw/bNR+iA/U5yhZf5VoPfvEkirlveMEs9zoH8SdGR+CYoewj -ODC5vGasFwY2SK1dHbSiwSwIVf8AAQQmYDimE1J704iKsR+kzRSbyAR3AXJt/rWM -OiXiK4NzSy7GbyCLAMc7Lt5i6XSBxqX1sfnbuVj5fKI/TkaTrq91AXfqNE6C3NNT -OcCw+YqGjfXyw6D65KmzLDO6eNXvEgUzTymbiEFSc1oXfrxm0NzHFG2lmXhngTZ2 -KWPedtTrylyCG659gO0yabC3eh3hAKixldNN1eKM9DCn2nm99bt6jcXsHBYyWJKt -/ByDScqM+W3U9yyYGiVHNhZPFUbqcXjxUQ== +AQsFAAOCAQEArQg/fbEJoaVsw1iAHeUzpbvAMzmVqojuxI44O1mnDjl0bP4RM176 +UMsgS2e3yV6Wp57YR0bhq/5di5otGhtDCPmTDyrjzoNKlM0C8I4l8kENVRD1TFs5 +i3deq3gWZKFI1eH2aZoP2DCmzJJNgd9GdKvPHbfUAblt1fQUuNVUhHkRQmlVf3TO +AZYvP1EjsxH7ctxMuaOJ7zHkwEkG+o0JceHBdKnt+JaHZxa1XRZdWXD/HLWhbNIi +EToOb3abacvzhad5rVP1NOiHzN0JUSXgKO55oKPcCt3wG+PJXxTTlfUSTSOVRSw8 +MpStzh6gX+boKMb5x/tXBq0L64bKDtKoZw== -----END CERTIFICATE----- From 289ba45ebcd6d6041f87354bb784b6a7e1c358d0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 1 Dec 2017 18:03:21 +0100 Subject: [PATCH 065/369] slapdtest: Don't have code in __init__ --- Lib/slapdtest/__init__.py | 507 +---------------------------------- Lib/slapdtest/_slapdtest.py | 512 ++++++++++++++++++++++++++++++++++++ 2 files changed, 514 insertions(+), 505 deletions(-) create mode 100644 Lib/slapdtest/_slapdtest.py diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 297bda72..a1acd2b7 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -5,510 +5,7 @@ See https://www.python-ldap.org/ for details. """ -from __future__ import unicode_literals - __version__ = '3.0.0b1' -import os -import socket -import time -import subprocess -import logging -import atexit -from logging.handlers import SysLogHandler -import unittest - -# Switch off processing .ldaprc or ldap.conf before importing _ldap -os.environ['LDAPNOINIT'] = '1' - -import ldap -from ldap.compat import quote_plus - -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 "%(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" - -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)" - -""" - -LOCALHOST = '127.0.0.1' - - -def identity(test_item): - """Identity decorator - - """ - return test_item - - -def skip_unless_travis(reason): - """Skip test unless test case is executed on CI like Travis CI - """ - if os.environ.get('CI', False): - return identity - else: - return unittest.skip(reason) - - -def requires_tls(skip_nss=False): - """Decorator for TLS tests - - Tests are not skipped on CI (e.g. Travis CI) - - :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib - """ - if not ldap.TLS_AVAIL: - return skip_unless_travis("test needs ldap.TLS_AVAIL") - elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': - return skip_unless_travis( - "Test doesn't work correctly with Mozilla NSS, see " - "https://bugzilla.redhat.com/show_bug.cgi?id=1519167" - ) - else: - return identity - - -def combined_logger( - log_name, - log_level=logging.WARN, - sys_log_format='%(levelname)s %(message)s', - console_log_format='%(asctime)s %(levelname)s %(message)s', - ): - """ - Returns a combined SysLogHandler/StreamHandler logging instance - with formatters - """ - if 'LOGLEVEL' in os.environ: - log_level = os.environ['LOGLEVEL'] - try: - log_level = int(log_level) - except ValueError: - pass - # for writing to syslog - new_logger = logging.getLogger(log_name) - if sys_log_format and os.path.exists('/dev/log'): - my_syslog_formatter = logging.Formatter( - fmt=' '.join((log_name, sys_log_format))) - my_syslog_handler = logging.handlers.SysLogHandler( - address='/dev/log', - facility=SysLogHandler.LOG_DAEMON, - ) - my_syslog_handler.setFormatter(my_syslog_formatter) - new_logger.addHandler(my_syslog_handler) - if console_log_format: - my_stream_formatter = logging.Formatter(fmt=console_log_format) - my_stream_handler = logging.StreamHandler() - my_stream_handler.setFormatter(my_stream_formatter) - new_logger.addHandler(my_stream_handler) - new_logger.setLevel(log_level) - return new_logger # end of combined_logger() - - -class SlapdObject(object): - """ - Controller class for a slapd instance, OpenLDAP's server. - - This class creates a temporary data store for slapd, runs it - listening on a private Unix domain socket and TCP port, - and initializes it with a top-level entry and the root user. - - When a reference to an instance of this class is lost, the slapd - server is shut down. - """ - slapd_conf_template = SLAPD_CONF_TEMPLATE - 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' - # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools - cli_sasl_external = True - local_host = '127.0.0.1' - testrunsubdirs = ( - 'schema', - ) - openldap_schema_files = ( - 'core.schema', - ) - - TMPDIR = os.environ.get('TMP', os.getcwd()) - SBINDIR = os.environ.get('SBIN', '/usr/sbin') - BINDIR = os.environ.get('BIN', '/usr/bin') - if 'SCHEMA' in os.environ: - SCHEMADIR = os.environ['SCHEMA'] - elif os.path.isdir("/etc/openldap/schema"): - SCHEMADIR = "/etc/openldap/schema" - elif os.path.isdir("/etc/ldap/schema"): - SCHEMADIR = "/etc/ldap/schema" - else: - SCHEMADIR = None - PATH_LDAPADD = os.path.join(BINDIR, 'ldapadd') - PATH_LDAPDELETE = os.path.join(BINDIR, 'ldapdelete') - PATH_LDAPMODIFY = os.path.join(BINDIR, 'ldapmodify') - PATH_LDAPWHOAMI = os.path.join(BINDIR, 'ldapwhoami') - PATH_SLAPD = os.environ.get('SLAPD', os.path.join(SBINDIR, 'slapd')) - PATH_SLAPTEST = os.path.join(SBINDIR, 'slaptest') - - # 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') - - def __init__(self): - self._proc = None - 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._db_directory = os.path.join(self.testrundir, "openldap-data") - self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) - ldapi_path = os.path.join(self.testrundir, 'ldapi') - self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) - # TLS certs - self.cafile = os.path.join(HERE, 'certs/ca.pem') - self.servercert = os.path.join(HERE, 'certs/server.pem') - self.serverkey = os.path.join(HERE, 'certs/server.key') - self.clientcert = os.path.join(HERE, 'certs/client.pem') - self.clientkey = os.path.join(HERE, 'certs/client.key') - - def _check_requirements(self): - binaries = [ - self.PATH_LDAPADD, self.PATH_LDAPMODIFY, self.PATH_LDAPWHOAMI, - self.PATH_SLAPD, self.PATH_SLAPTEST - ] - for binary in binaries: - if not os.path.isfile(binary): - raise ValueError('Binary {} is missing.'.format(binary)) - if self.SCHEMADIR is None: - raise ValueError('SCHEMADIR is None, ldap schemas are missing.') - - def setup_rundir(self): - """ - creates rundir structure - - for setting up a custom directory structure you have to override - this method - """ - 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): - """ - Recursively delete whole directory specified by `path' - """ - # cleanup_rundir() is called in atexit handler. Until Python 3.4, - # the rest of the world is already destroyed. - import os, os.path - if not os.path.exists(self.testrundir): - return - self._log.debug('clean-up %s', self.testrundir) - for dirpath, dirnames, filenames in os.walk( - self.testrundir, - topdown=False - ): - for filename in filenames: - self._log.debug('remove %s', os.path.join(dirpath, filename)) - os.remove(os.path.join(dirpath, filename)) - for dirname in dirnames: - self._log.debug('rmdir %s', os.path.join(dirpath, dirname)) - os.rmdir(os.path.join(dirpath, dirname)) - os.rmdir(self.testrundir) - self._log.info('cleaned-up %s', self.testrundir) - - def _avail_tcp_port(self): - """ - find an available port for TCP connection - """ - sock = socket.socket() - try: - sock.bind((self.local_host, 0)) - port = sock.getsockname()[1] - finally: - sock.close() - self._log.info('Found available port %d', port) - return port - - def gen_config(self): - """ - generates a slapd.conf and returns it as one string - - for generating specific static configuration files you have to - override this method - """ - config_dict = { - 'serverid': hex(self.server_id), - 'schema_prefix':self._schema_prefix, - 'loglevel': self.slapd_loglevel, - 'database': self.database, - 'directory': self._db_directory, - 'suffix': self.suffix, - 'rootdn': self.root_dn, - 'rootpw': self.root_pw, - 'root_uid': os.getuid(), - 'root_gid': os.getgid(), - 'cafile': self.cafile, - 'servercert': self.servercert, - 'serverkey': self.serverkey, - } - return self.slapd_conf_template % config_dict - - def _create_sub_dirs(self, dir_names): - """ - create sub-directories beneath self.testrundir - """ - for dname in dir_names: - dir_name = os.path.join(self.testrundir, dname) - 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) - - def _test_config(self): - self._log.debug('testing config %s', self._slapd_conf) - popen_list = [ - self.PATH_SLAPTEST, - "-f", self._slapd_conf, - '-u', - ] - 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: - raise RuntimeError("configuration test failed") - self._log.info("config ok: %s", self._slapd_conf) - - def _start_slapd(self): - """ - Spawns/forks the slapd process - """ - slapd_args = [ - self.PATH_SLAPD, - '-f', self._slapd_conf, - '-F', self.testrundir, - '-h', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)), - ] - if self._log.isEnabledFor(logging.DEBUG): - slapd_args.extend(['-d', '-1']) - else: - slapd_args.extend(['-d', '0']) - 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 - while 1: - 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.ldapi_uri) - self.ldapwhoami() - except RuntimeError: - pass - else: - return - - def start(self): - """ - Starts the slapd server process running, and waits for it to come up. - """ - - if self._proc is None: - self._check_requirements() - # prepare directory structure - atexit.register(self.stop) - self._cleanup_rundir() - self.setup_rundir() - self._write_config() - self._test_config() - self._start_slapd() - self._log.debug( - 'slapd with pid=%d listening on %s and %s', - self._proc.pid, self.ldap_uri, self.ldapi_uri - ) - - def stop(self): - """ - Stops the slapd server, and waits for it to terminate and cleans up - """ - if self._proc is not None: - self._log.debug('stopping slapd with pid %d', self._proc.pid) - 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 - - def restart(self): - """ - Restarts the slapd server with same data - """ - self._proc.terminate() - self.wait() - self._start_slapd() - - def wait(self): - """Waits for the slapd process to terminate by itself.""" - if self._proc: - self._proc.wait() - self._stopped() - - def _stopped(self): - """Called when the slapd server is known to have terminated""" - if self._proc is not None: - self._log.info('slapd[%d] terminated', self._proc.pid) - self._proc = None - - def _cli_auth_args(self): - if self.cli_sasl_external: - authc_args = [ - '-Y', 'EXTERNAL', - ] - if not self._log.isEnabledFor(logging.DEBUG): - authc_args.append('-Q') - else: - authc_args = [ - '-x', - '-D', self.root_dn, - '-w', self.root_pw, - ] - return authc_args - - def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None): - args = [ - ldapcommand, - '-H', ldap_uri or self.ldapi_uri, - ] + self._cli_auth_args() + (extra_args or []) - self._log.debug('Run command: %r', ' '.join(args)) - proc = subprocess.Popen( - args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - self._log.debug('stdin_data=%r', stdin_data) - stdout_data, stderr_data = proc.communicate(stdin_data) - if stdout_data is not None: - self._log.debug('stdout_data=%r', stdout_data) - if stderr_data is not None: - self._log.debug('stderr_data=%r', stderr_data) - if proc.wait() != 0: - raise RuntimeError( - '{!r} process failed:\n{!r}\n{!r}'.format( - args, stdout_data, stderr_data - ) - ) - return stdout_data, stderr_data - - def ldapwhoami(self, extra_args=None): - """ - Runs ldapwhoami on this slapd instance - """ - self._cli_popen(self.PATH_LDAPWHOAMI, extra_args=extra_args) - - def ldapadd(self, ldif, extra_args=None): - """ - Runs ldapadd on this slapd instance, passing it the ldif content - """ - self._cli_popen(self.PATH_LDAPADD, extra_args=extra_args, - stdin_data=ldif.encode('utf-8')) - - def ldapmodify(self, ldif, extra_args=None): - """ - Runs ldapadd on this slapd instance, passing it the ldif content - """ - self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args, - stdin_data=ldif.encode('utf-8')) - - def ldapdelete(self, dn, recursive=False, extra_args=None): - """ - Runs ldapdelete on this slapd instance, deleting 'dn' - """ - if extra_args is None: - extra_args = [] - if recursive: - extra_args.append('-r') - extra_args.append(dn) - self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) - - -class SlapdTestCase(unittest.TestCase): - """ - test class which also clones or initializes a running slapd - """ - - server_class = SlapdObject - server = None - ldap_object_class = None - - def _open_ldap_conn(self, who=None, cred=None, **kwargs): - """ - return a LDAPObject instance after simple bind - """ - ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs) - ldap_conn.protocol_version = 3 - #ldap_conn.set_option(ldap.OPT_REFERRALS, 0) - ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw) - return ldap_conn - - @classmethod - def setUpClass(cls): - cls.server = cls.server_class() - cls.server.start() - cls.server = cls.server - - @classmethod - def tearDownClass(cls): - cls.server.stop() +from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler +from slapdtest._slapdtest import skip_unless_ci, requires_tls diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py new file mode 100644 index 00000000..884a1ee5 --- /dev/null +++ b/Lib/slapdtest/_slapdtest.py @@ -0,0 +1,512 @@ +# -*- coding: utf-8 -*- +""" +slapdtest - module for spawning test instances of OpenLDAP's slapd server + +See https://www.python-ldap.org/ for details. +""" + +from __future__ import unicode_literals + +import os +import socket +import time +import subprocess +import logging +import atexit +from logging.handlers import SysLogHandler +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap +from ldap.compat import quote_plus + +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 "%(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" + +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)" + +""" + +LOCALHOST = '127.0.0.1' + + +def identity(test_item): + """Identity decorator + + """ + return test_item + + +def skip_unless_travis(reason): + """Skip test unless test case is executed on CI like Travis CI + """ + if os.environ.get('CI', False): + return identity + else: + return unittest.skip(reason) + + +def requires_tls(skip_nss=False): + """Decorator for TLS tests + + Tests are not skipped on CI (e.g. Travis CI) + + :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib + """ + if not ldap.TLS_AVAIL: + return skip_unless_travis("test needs ldap.TLS_AVAIL") + elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': + return skip_unless_travis( + "Test doesn't work correctly with Mozilla NSS, see " + "https://bugzilla.redhat.com/show_bug.cgi?id=1519167" + ) + else: + return identity + + +def combined_logger( + log_name, + log_level=logging.WARN, + sys_log_format='%(levelname)s %(message)s', + console_log_format='%(asctime)s %(levelname)s %(message)s', + ): + """ + Returns a combined SysLogHandler/StreamHandler logging instance + with formatters + """ + if 'LOGLEVEL' in os.environ: + log_level = os.environ['LOGLEVEL'] + try: + log_level = int(log_level) + except ValueError: + pass + # for writing to syslog + new_logger = logging.getLogger(log_name) + if sys_log_format and os.path.exists('/dev/log'): + my_syslog_formatter = logging.Formatter( + fmt=' '.join((log_name, sys_log_format))) + my_syslog_handler = logging.handlers.SysLogHandler( + address='/dev/log', + facility=SysLogHandler.LOG_DAEMON, + ) + my_syslog_handler.setFormatter(my_syslog_formatter) + new_logger.addHandler(my_syslog_handler) + if console_log_format: + my_stream_formatter = logging.Formatter(fmt=console_log_format) + my_stream_handler = logging.StreamHandler() + my_stream_handler.setFormatter(my_stream_formatter) + new_logger.addHandler(my_stream_handler) + new_logger.setLevel(log_level) + return new_logger # end of combined_logger() + + +class SlapdObject(object): + """ + Controller class for a slapd instance, OpenLDAP's server. + + This class creates a temporary data store for slapd, runs it + listening on a private Unix domain socket and TCP port, + and initializes it with a top-level entry and the root user. + + When a reference to an instance of this class is lost, the slapd + server is shut down. + """ + slapd_conf_template = SLAPD_CONF_TEMPLATE + 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' + # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools + cli_sasl_external = True + local_host = '127.0.0.1' + testrunsubdirs = ( + 'schema', + ) + openldap_schema_files = ( + 'core.schema', + ) + + TMPDIR = os.environ.get('TMP', os.getcwd()) + SBINDIR = os.environ.get('SBIN', '/usr/sbin') + BINDIR = os.environ.get('BIN', '/usr/bin') + if 'SCHEMA' in os.environ: + SCHEMADIR = os.environ['SCHEMA'] + elif os.path.isdir("/etc/openldap/schema"): + SCHEMADIR = "/etc/openldap/schema" + elif os.path.isdir("/etc/ldap/schema"): + SCHEMADIR = "/etc/ldap/schema" + else: + SCHEMADIR = None + PATH_LDAPADD = os.path.join(BINDIR, 'ldapadd') + PATH_LDAPDELETE = os.path.join(BINDIR, 'ldapdelete') + PATH_LDAPMODIFY = os.path.join(BINDIR, 'ldapmodify') + PATH_LDAPWHOAMI = os.path.join(BINDIR, 'ldapwhoami') + PATH_SLAPD = os.environ.get('SLAPD', os.path.join(SBINDIR, 'slapd')) + PATH_SLAPTEST = os.path.join(SBINDIR, 'slaptest') + + # 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') + + def __init__(self): + self._proc = None + 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._db_directory = os.path.join(self.testrundir, "openldap-data") + self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) + ldapi_path = os.path.join(self.testrundir, 'ldapi') + self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) + # TLS certs + self.cafile = os.path.join(HERE, 'certs/ca.pem') + self.servercert = os.path.join(HERE, 'certs/server.pem') + self.serverkey = os.path.join(HERE, 'certs/server.key') + self.clientcert = os.path.join(HERE, 'certs/client.pem') + self.clientkey = os.path.join(HERE, 'certs/client.key') + + def _check_requirements(self): + binaries = [ + self.PATH_LDAPADD, self.PATH_LDAPMODIFY, self.PATH_LDAPWHOAMI, + self.PATH_SLAPD, self.PATH_SLAPTEST + ] + for binary in binaries: + if not os.path.isfile(binary): + raise ValueError('Binary {} is missing.'.format(binary)) + if self.SCHEMADIR is None: + raise ValueError('SCHEMADIR is None, ldap schemas are missing.') + + def setup_rundir(self): + """ + creates rundir structure + + for setting up a custom directory structure you have to override + this method + """ + 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): + """ + Recursively delete whole directory specified by `path' + """ + # cleanup_rundir() is called in atexit handler. Until Python 3.4, + # the rest of the world is already destroyed. + import os, os.path + if not os.path.exists(self.testrundir): + return + self._log.debug('clean-up %s', self.testrundir) + for dirpath, dirnames, filenames in os.walk( + self.testrundir, + topdown=False + ): + for filename in filenames: + self._log.debug('remove %s', os.path.join(dirpath, filename)) + os.remove(os.path.join(dirpath, filename)) + for dirname in dirnames: + self._log.debug('rmdir %s', os.path.join(dirpath, dirname)) + os.rmdir(os.path.join(dirpath, dirname)) + os.rmdir(self.testrundir) + self._log.info('cleaned-up %s', self.testrundir) + + def _avail_tcp_port(self): + """ + find an available port for TCP connection + """ + sock = socket.socket() + try: + sock.bind((self.local_host, 0)) + port = sock.getsockname()[1] + finally: + sock.close() + self._log.info('Found available port %d', port) + return port + + def gen_config(self): + """ + generates a slapd.conf and returns it as one string + + for generating specific static configuration files you have to + override this method + """ + config_dict = { + 'serverid': hex(self.server_id), + 'schema_prefix':self._schema_prefix, + 'loglevel': self.slapd_loglevel, + 'database': self.database, + 'directory': self._db_directory, + 'suffix': self.suffix, + 'rootdn': self.root_dn, + 'rootpw': self.root_pw, + 'root_uid': os.getuid(), + 'root_gid': os.getgid(), + 'cafile': self.cafile, + 'servercert': self.servercert, + 'serverkey': self.serverkey, + } + return self.slapd_conf_template % config_dict + + def _create_sub_dirs(self, dir_names): + """ + create sub-directories beneath self.testrundir + """ + for dname in dir_names: + dir_name = os.path.join(self.testrundir, dname) + 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) + + def _test_config(self): + self._log.debug('testing config %s', self._slapd_conf) + popen_list = [ + self.PATH_SLAPTEST, + "-f", self._slapd_conf, + '-u', + ] + 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: + raise RuntimeError("configuration test failed") + self._log.info("config ok: %s", self._slapd_conf) + + def _start_slapd(self): + """ + Spawns/forks the slapd process + """ + slapd_args = [ + self.PATH_SLAPD, + '-f', self._slapd_conf, + '-F', self.testrundir, + '-h', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)), + ] + if self._log.isEnabledFor(logging.DEBUG): + slapd_args.extend(['-d', '-1']) + else: + slapd_args.extend(['-d', '0']) + 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 + while 1: + 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.ldapi_uri) + self.ldapwhoami() + except RuntimeError: + pass + else: + return + + def start(self): + """ + Starts the slapd server process running, and waits for it to come up. + """ + + if self._proc is None: + self._check_requirements() + # prepare directory structure + atexit.register(self.stop) + self._cleanup_rundir() + self.setup_rundir() + self._write_config() + self._test_config() + self._start_slapd() + self._log.debug( + 'slapd with pid=%d listening on %s and %s', + self._proc.pid, self.ldap_uri, self.ldapi_uri + ) + + def stop(self): + """ + Stops the slapd server, and waits for it to terminate and cleans up + """ + if self._proc is not None: + self._log.debug('stopping slapd with pid %d', self._proc.pid) + 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 + + def restart(self): + """ + Restarts the slapd server with same data + """ + self._proc.terminate() + self.wait() + self._start_slapd() + + def wait(self): + """Waits for the slapd process to terminate by itself.""" + if self._proc: + self._proc.wait() + self._stopped() + + def _stopped(self): + """Called when the slapd server is known to have terminated""" + if self._proc is not None: + self._log.info('slapd[%d] terminated', self._proc.pid) + self._proc = None + + def _cli_auth_args(self): + if self.cli_sasl_external: + authc_args = [ + '-Y', 'EXTERNAL', + ] + if not self._log.isEnabledFor(logging.DEBUG): + authc_args.append('-Q') + else: + authc_args = [ + '-x', + '-D', self.root_dn, + '-w', self.root_pw, + ] + return authc_args + + def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None): + args = [ + ldapcommand, + '-H', ldap_uri or self.ldapi_uri, + ] + self._cli_auth_args() + (extra_args or []) + self._log.debug('Run command: %r', ' '.join(args)) + proc = subprocess.Popen( + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + self._log.debug('stdin_data=%r', stdin_data) + stdout_data, stderr_data = proc.communicate(stdin_data) + if stdout_data is not None: + self._log.debug('stdout_data=%r', stdout_data) + if stderr_data is not None: + self._log.debug('stderr_data=%r', stderr_data) + if proc.wait() != 0: + raise RuntimeError( + '{!r} process failed:\n{!r}\n{!r}'.format( + args, stdout_data, stderr_data + ) + ) + return stdout_data, stderr_data + + def ldapwhoami(self, extra_args=None): + """ + Runs ldapwhoami on this slapd instance + """ + self._cli_popen(self.PATH_LDAPWHOAMI, extra_args=extra_args) + + def ldapadd(self, ldif, extra_args=None): + """ + Runs ldapadd on this slapd instance, passing it the ldif content + """ + self._cli_popen(self.PATH_LDAPADD, extra_args=extra_args, + stdin_data=ldif.encode('utf-8')) + + def ldapmodify(self, ldif, extra_args=None): + """ + Runs ldapadd on this slapd instance, passing it the ldif content + """ + self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args, + stdin_data=ldif.encode('utf-8')) + + def ldapdelete(self, dn, recursive=False, extra_args=None): + """ + Runs ldapdelete on this slapd instance, deleting 'dn' + """ + if extra_args is None: + extra_args = [] + if recursive: + extra_args.append('-r') + extra_args.append(dn) + self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args) + + +class SlapdTestCase(unittest.TestCase): + """ + test class which also clones or initializes a running slapd + """ + + server_class = SlapdObject + server = None + ldap_object_class = None + + def _open_ldap_conn(self, who=None, cred=None, **kwargs): + """ + return a LDAPObject instance after simple bind + """ + ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs) + ldap_conn.protocol_version = 3 + #ldap_conn.set_option(ldap.OPT_REFERRALS, 0) + ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw) + return ldap_conn + + @classmethod + def setUpClass(cls): + cls.server = cls.server_class() + cls.server.start() + cls.server = cls.server + + @classmethod + def tearDownClass(cls): + cls.server.stop() From 53fd80a2da59520391e738634f7191e3973a427e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 1 Dec 2017 18:03:40 +0100 Subject: [PATCH 066/369] slapdtest: Use "CI" instead of "Travis" in function name We want this to work on any kind of CI, not just the one we use now. --- Lib/slapdtest/_slapdtest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 884a1ee5..29005811 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -64,7 +64,7 @@ def identity(test_item): return test_item -def skip_unless_travis(reason): +def skip_unless_ci(reason): """Skip test unless test case is executed on CI like Travis CI """ if os.environ.get('CI', False): @@ -81,9 +81,9 @@ def requires_tls(skip_nss=False): :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib """ if not ldap.TLS_AVAIL: - return skip_unless_travis("test needs ldap.TLS_AVAIL") + return skip_unless_ci("test needs ldap.TLS_AVAIL") elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': - return skip_unless_travis( + return skip_unless_ci( "Test doesn't work correctly with Mozilla NSS, see " "https://bugzilla.redhat.com/show_bug.cgi?id=1519167" ) From 04e6f18eed7e2453dc5639bcac09c5182d2626e1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 4 Dec 2017 11:10:55 +0100 Subject: [PATCH 067/369] Announce that slapdtest is now a package --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b64c2637..05283bf9 100644 --- a/CHANGES +++ b/CHANGES @@ -70,10 +70,11 @@ and, thanks to Michael Ströder: * module ldif now uses functions b64encode() and b64decode() * fixed pickling and restoring of ReconnectLDAPObject -Lib/slapdtest.py +Lib/slapdtest * Automatically try some common locations for SCHEMADIR * Ensure server is stopped when the process exits * Check for LDAP schema and slapd binaries +* slapdtest is now a package and includes testing certificates Tests/ * Expand cidict membership test From 9524dd362fb70d9d5bfdd75694c0b6a8c17fa4f1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 11:32:11 +0100 Subject: [PATCH 068/369] Fix memory leak in whoami_s l_ldap_whoami_s() did not free the berval struct from ldap_whoami_s(). https://github.com/python-ldap/python-ldap/pull/84 Closes: https://github.com/python-ldap/python-ldap/issues/80 Signed-off-by: Christian Heimes --- CHANGES | 1 + Modules/LDAPObject.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b64c2637..dfe4e4d6 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Modules/ * Fix multiple ref leaks in error-handling code * Fix reference leak in result4 * Fix several compiler warnings +* Fix memory leak in whoami and, thanks to Michael Ströder: * removed unused code schema.c * moved code from version.c to ldapmodule.c diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 8c90a65c..815fe426 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1227,10 +1227,13 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) 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" ); + } result = LDAPberval_to_unicode_object(bvalue); + ber_bvfree(bvalue); return result; } From c86b8e9419db8d7b7752fc0a6d8bb79c9da6ba41 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 11:51:03 +0100 Subject: [PATCH 069/369] Fix error handling of LDAPControl_to_List() (#89) LDAPControls_to_List() may have returned a partly filled list in case Py_BuildValue() fails. In case of an error, DECREF the list and return NULL. https://github.com/python-ldap/python-ldap/pull/89 Closes: https://github.com/python-ldap/python-ldap/issues/22 Signed-off-by: Christian Heimes --- CHANGES | 1 + Modules/ldapcontrol.c | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 58dbde99..6cbb8451 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,7 @@ Modules/ * Fix reference leak in result4 * Fix several compiler warnings * Fix memory leak in whoami +* Fix internal error handling of LDAPControl_to_List() and, thanks to Michael Ströder: * removed unused code schema.c * moved code from version.c to ldapmodule.c diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 7d8fbe38..5cd30f91 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -172,22 +172,21 @@ LDAPControls_to_List(LDAPControl **ldcs) if (tmp) while (*tmp++) num_ctrls++; - if (!(res = PyList_New(num_ctrls))) - goto endlbl; + if ((res = PyList_New(num_ctrls)) == NULL) { + return NULL; + } for (i = 0; i < num_ctrls; i++) { - if (!(pyctrl = Py_BuildValue("sbO&", ldcs[i]->ldctl_oid, - ldcs[i]->ldctl_iscritical, - LDAPberval_to_object, - &ldcs[i]->ldctl_value))) { - goto endlbl; + pyctrl = Py_BuildValue("sbO&", + ldcs[i]->ldctl_oid, + ldcs[i]->ldctl_iscritical, + LDAPberval_to_object, &ldcs[i]->ldctl_value); + if (pyctrl == NULL) { + Py_DECREF(res); + return NULL; } PyList_SET_ITEM(res, i, pyctrl); } - Py_INCREF(res); - - endlbl: - Py_XDECREF(res); return res; } From 9f86c0653fe3944ce7aea14a2f34afc91c84521d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 11:51:03 +0100 Subject: [PATCH 070/369] Fix two memory leaks in encode_assertion_control encode_assertion_control was leaking a LDAP* struct and BER data values on every call. Both data structures are now properly freed and the GIL is released around ldap_create() and ldap_unbind_s(). Closes: https://github.com/python-ldap/python-ldap/issues/79 Signed-off-by: Christian Heimes --- CHANGES | 1 + Modules/ldapcontrol.c | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 6cbb8451..884c1d0c 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Modules/ * Fix several compiler warnings * Fix memory leak in whoami * Fix internal error handling of LDAPControl_to_List() +* Fix two memory leaks and release GIL in encode_assertion_control and, thanks to Michael Ströder: * removed unused code schema.c * moved code from version.c to ldapmodule.c diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 5cd30f91..9522d572 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -343,16 +343,33 @@ encode_assertion_control(PyObject *self, PyObject *args) goto endlbl; } + /* 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"); err = ldap_create_assertion_control_value(ld,assertion_filterstr,&ctrl_val); - if (err != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_create_assertion_control_value"); - res = LDAPberval_to_object(&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 + res = LDAPberval_to_object(&ctrl_val); + if (ctrl_val.bv_val != NULL) { + ber_memfree(ctrl_val.bv_val); + } endlbl: return res; @@ -371,5 +388,3 @@ LDAPinit_control(PyObject *d) { LDAPadd_methods(d, methods); } - - From c5ad8025632fb5e5e84cced5a0f8c7ddda1e8dae Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 13:33:27 +0100 Subject: [PATCH 071/369] Run tests without SASL and TLS bindings Also fixes tests when _ldap extension is compiled without SASL support. https://github.com/python-ldap/python-ldap/pull/91 Signed-off-by: Christian Heimes --- .travis.yml | 8 ++++++++ Doc/contributing.rst | 2 ++ Lib/slapdtest/__init__.py | 2 +- Lib/slapdtest/_slapdtest.py | 24 ++++++++++++++++++------ Tests/t_cext.py | 15 +++++++++++++++ Tests/t_ldap_sasl.py | 3 ++- Tests/t_ldapobject.py | 4 +++- tox.ini | 24 +++++++++++++++++++++++- 8 files changed, 72 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad9cd967..960f6db2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,14 @@ matrix: env: - TOXENV=py36 - WITH_GCOV=1 + - python: 2.7 + env: + - TOXENV=py2-nosasltls + - WITH_GCOV=1 + - python: 3.6 + env: + - TOXENV=py3-nosasltls + - WITH_GCOV=1 - python: 3.6 env: TOXENV=doc diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 3a904d34..aa6097d0 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -146,6 +146,8 @@ 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 + SASL and TLS bindings compiled in. When your change is ready, commit to Git, and submit a pull request on GitHub. diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index a1acd2b7..d7d19fdc 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -8,4 +8,4 @@ __version__ = '3.0.0b1' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler -from slapdtest._slapdtest import skip_unless_ci, requires_tls +from slapdtest._slapdtest import skip_unless_ci, requires_sasl, requires_tls diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 29005811..5ba64642 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -64,13 +64,16 @@ def identity(test_item): return test_item -def skip_unless_ci(reason): +def skip_unless_ci(reason, feature=None): """Skip test unless test case is executed on CI like Travis CI """ - if os.environ.get('CI', False): - return identity - else: + if not os.environ.get('CI', False): + return unittest.skip(reason) + elif feature in os.environ.get('CI_DISABLED', '').split(':'): return unittest.skip(reason) + else: + # Don't skip on Travis + return identity def requires_tls(skip_nss=False): @@ -81,16 +84,25 @@ def requires_tls(skip_nss=False): :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib """ if not ldap.TLS_AVAIL: - return skip_unless_ci("test needs ldap.TLS_AVAIL") + return skip_unless_ci("test needs ldap.TLS_AVAIL", feature='TLS') elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': return skip_unless_ci( "Test doesn't work correctly with Mozilla NSS, see " - "https://bugzilla.redhat.com/show_bug.cgi?id=1519167" + "https://bugzilla.redhat.com/show_bug.cgi?id=1519167", + feature="NSS" ) else: return identity +def requires_sasl(): + if not ldap.SASL_AVAIL: + return skip_unless_ci( + "test needs ldap.SASL_AVAIL", feature='SASL') + else: + return identity + + def combined_logger( log_name, log_level=logging.WARN, diff --git a/Tests/t_cext.py b/Tests/t_cext.py index be858e95..4ef5d0eb 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -212,6 +212,21 @@ def test_constants(self): self.assertNotNone(_ldap.URL_ERR_BADSCOPE) self.assertNotNone(_ldap.URL_ERR_MEM) + def test_test_flags(self): + # test flag, see slapdtest and tox.ini + disabled = os.environ.get('CI_DISABLED') + if not disabled: + self.skipTest("No CI_DISABLED env var") + disabled = set(disabled.split(':')) + if 'TLS' in disabled: + self.assertFalse(_ldap.TLS_AVAIL) + else: + self.assertFalse(_ldap.TLS_AVAIL) + if 'SASL' in disabled: + self.assertFalse(_ldap.SASL_AVAIL) + else: + self.assertFalse(_ldap.SASL_AVAIL) + def test_simple_bind(self): l = self._open_conn() diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 9acd051e..2fa4dbcc 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -14,7 +14,7 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.sasl -from slapdtest import SlapdTestCase, requires_tls +from slapdtest import SlapdTestCase, requires_sasl, requires_tls LDIF = """ @@ -39,6 +39,7 @@ """ +@requires_sasl() class TestSasl(SlapdTestCase): ldap_object_class = SimpleLDAPObject # from Tests/certs/client.pem diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index d55d0181..9b6a090e 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -19,7 +19,7 @@ import os import unittest import pickle -from slapdtest import SlapdTestCase +from slapdtest import SlapdTestCase, requires_sasl # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -298,6 +298,7 @@ def test005_invalid_credentials(self): else: self.fail("expected INVALID_CREDENTIALS, got %r" % r) + @requires_sasl() def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() @@ -322,6 +323,7 @@ class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): ldap_object_class = ReconnectLDAPObject + @requires_sasl() def test101_reconnect_sasl_external(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() diff --git a/tox.ini b/tox.ini index 51f8981d..6d984c0f 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,8 @@ [tox] # Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py33,py34,py35,py36,doc,coverage-report +envlist = py27,py33,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report +minver = 1.8 [testenv] deps = coverage @@ -21,9 +22,30 @@ commands = {envpython} -bb -Werror \ [testenv:py27] # No warnings with Python 2.7 +passenv = {[testenv]passenv} commands = {envpython} \ -m coverage run --parallel setup.py test +[testenv:py2-nosasltls] +basepython = python2 +deps = {[testenv]deps} +passenv = {[testenv]passenv} +setenv = + CI_DISABLED=TLS:SASL +# rebuild without SASL and TLS +commands = {envpython} \ + -m coverage run --parallel setup.py \ + clean --all \ + build_ext -UHAVE_SASL,HAVE_TLS \ + test + +[testenv:py3-nosasltls] +basepython = python3 +deps = {[testenv]deps} +passenv = {[testenv]passenv} +setenv = {[testenv:py2-nosasltls]setenv} +commands = {[testenv:py2-nosasltls]commands} + [testenv:coverage-report] deps = coverage skip_install = true From 6f5320597a3043e0cd6c6f4f4ce492a59aca8917 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 13:28:29 +0100 Subject: [PATCH 072/369] Modules: Use LDAPControls_to_List in LDAP_get_option instead of inlining it --- Modules/options.c | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/Modules/options.c b/Modules/options.c index ac1eab60..f5bae422 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -180,10 +180,9 @@ LDAP_get_option(LDAPObject *self, int option) struct timeval *tv; LDAPAPIInfo apiinfo; LDAPControl **lcs; - LDAPControl *lc; char *strval; - PyObject *extensions, *v, *tup; - Py_ssize_t i, num_extensions, num_controls; + PyObject *extensions, *v; + Py_ssize_t i, num_extensions; LDAP *ld; ld = self ? self->ldap : NULL; @@ -352,27 +351,8 @@ LDAP_get_option(LDAPObject *self, int option) if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); - if (lcs == NULL) - return PyList_New(0); - - /* Get the number of controls */ - num_controls = 0; - while (lcs[num_controls]) - num_controls++; - - /* We'll build a list of controls, with each control a tuple */ - v = PyList_New(num_controls); - for (i = 0; i < num_controls; i++) { - lc = lcs[i]; - tup = Py_BuildValue("(sbs)", - lc->ldctl_oid, - lc->ldctl_iscritical, - lc->ldctl_value.bv_val); - PyList_SET_ITEM(v, i, tup); - } - + v = LDAPControls_to_List(lcs); ldap_controls_free(lcs); - return v; default: From dcb5c0052b5f83508c1c406d8ad0b23e4f5a3e6a Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sun, 3 Dec 2017 14:18:57 +0100 Subject: [PATCH 073/369] Allow set_option() to set timeout to infinity OPT_TIMEOUT and OPT_NETWORK_TIMEOUT now support -1 to set default back to infinity. Signed-off-by: Christian Heimes --- CHANGES | 1 + Doc/reference/ldap.rst | 6 ++++++ Modules/options.c | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 884c1d0c..df351798 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,7 @@ Modules/ * Fix memory leak in whoami * Fix internal error handling of LDAPControl_to_List() * Fix two memory leaks and release GIL in encode_assertion_control +* Allow set_option() to set timeouts to infinity and, thanks to Michael Ströder: * removed unused code schema.c * moved code from version.c to ldapmodule.c diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index c28cdec5..1f7ae5b5 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -156,6 +156,9 @@ following option identifiers are defined as constants: .. py:data:: OPT_NETWORK_TIMEOUT + .. versionchanged:: 3.0 + A timeout of ``-1`` resets timeout to infinity. + .. py:data:: OPT_PROTOCOL_VERSION Sets the LDAP protocol version used for a connection. This is mapped to @@ -180,6 +183,9 @@ following option identifiers are defined as constants: .. py:data:: OPT_TIMEOUT + .. versionchanged:: 3.0 + A timeout of ``-1`` resets timeout to infinity. + .. py:data:: OPT_URI .. _ldap-sasl-options: diff --git a/Modules/options.c b/Modules/options.c index f5bae422..647f859d 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -140,10 +140,20 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) if (!PyArg_Parse(value, "d:set_option", &doubleval)) return 0; if (doubleval >= 0) { - set_timeval_from_double( &tv, doubleval ); + set_timeval_from_double( &tv, doubleval ); + ptr = &tv; + } else if (doubleval == -1) { + /* -1 is infinity timeout */ + tv.tv_sec = -1; + tv.tv_usec = 0; ptr = &tv; } else { - ptr = NULL; + PyErr_Format( + PyExc_ValueError, + "timeout must be >= 0 or -1 for infinity, got %d", + option + ); + return 0; } break; case LDAP_OPT_SERVER_CONTROLS: From 2aa8bca4aadc6a22a7ae0075a2b3b4d56cdf24df Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 11:17:17 +0100 Subject: [PATCH 074/369] Add tests for get/set options Signed-off-by: Christian Heimes --- Tests/__init__.py | 1 + Tests/t_ldap_options.py | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 Tests/t_ldap_options.py diff --git a/Tests/__init__.py b/Tests/__init__.py index 8ceb63b6..d6545732 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -22,3 +22,4 @@ from . import t_ldap_schema_subentry from . import t_untested_mods from . import t_ldap_controls_libldap +from . import t_ldap_options diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py new file mode 100644 index 00000000..798ae463 --- /dev/null +++ b/Tests/t_ldap_options.py @@ -0,0 +1,106 @@ +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap +from ldap.controls import RequestControlTuples +from ldap.controls.pagedresults import SimplePagedResultsControl +from ldap.controls.openldap import SearchNoOpControl +from slapdtest import requires_tls + + +SENTINEL = object() + +TEST_CTRL = RequestControlTuples([ + # with BER data + SimplePagedResultsControl(criticality=0, size=5, cookie=b'cookie'), + # value-less + SearchNoOpControl(criticality=1), +]) +TEST_CTRL_EXPECTED = [ + TEST_CTRL[0], + # get_option returns empty bytes + (TEST_CTRL[1][0], TEST_CTRL[1][1], b''), +] + + +class TestGlobalOptions(unittest.TestCase): + def _check_option(self, option, value, expected=SENTINEL, + nonevalue=None): + old = ldap.get_option(option) + try: + ldap.set_option(option, value) + new = ldap.get_option(option) + if expected is SENTINEL: + self.assertEqual(new, value) + else: + self.assertEqual(new, expected) + finally: + ldap.set_option(option, old if old is not None else nonevalue) + self.assertEqual(ldap.get_option(option), old) + + def test_invalid(self): + with self.assertRaises(ValueError): + ldap.get_option(-1) + with self.assertRaises(ValueError): + ldap.set_option(-1, '') + + def test_timeout(self): + self._check_option(ldap.OPT_TIMEOUT, 0, nonevalue=-1) + self._check_option(ldap.OPT_TIMEOUT, 10.5, nonevalue=-1) + with self.assertRaises(ValueError): + self._check_option(ldap.OPT_TIMEOUT, -5, nonevalue=-1) + with self.assertRaises(TypeError): + ldap.set_option(ldap.OPT_TIMEOUT, object) + + def test_network_timeout(self): + self._check_option(ldap.OPT_NETWORK_TIMEOUT, 0, nonevalue=-1) + self._check_option(ldap.OPT_NETWORK_TIMEOUT, 10.5, nonevalue=-1) + with self.assertRaises(ValueError): + self._check_option(ldap.OPT_NETWORK_TIMEOUT, -5, nonevalue=-1) + + def _test_controls(self, option): + self._check_option(option, []) + self._check_option(option, TEST_CTRL, TEST_CTRL_EXPECTED) + self._check_option(option, tuple(TEST_CTRL), TEST_CTRL_EXPECTED) + with self.assertRaises(TypeError): + ldap.set_option(option, object) + + with self.assertRaises(TypeError): + # must contain a tuple + ldap.set_option(option, [list(TEST_CTRL[0])]) + with self.assertRaises(TypeError): + # data must be bytes or None + ldap.set_option( + option, + [TEST_CTRL[0][0], TEST_CTRL[0][1], u'data'] + ) + + def test_client_controls(self): + self._test_controls(ldap.OPT_CLIENT_CONTROLS) + + def test_server_controls(self): + self._test_controls(ldap.OPT_SERVER_CONTROLS) + + def test_uri(self): + self._check_option(ldap.OPT_URI, "ldapi:///path/to/socket") + with self.assertRaises(TypeError): + ldap.set_option(ldap.OPT_URI, object) + + @requires_tls() + def test_cafile(self): + # None or a distribution or OS-specific path + ldap.get_option(ldap.OPT_X_TLS_CACERTFILE) + + def test_readonly(self): + value = ldap.get_option(ldap.OPT_API_INFO) + self.assertIsInstance(value, dict) + with self.assertRaises(ValueError) as e: + ldap.set_option(ldap.OPT_API_INFO, value) + self.assertIn('read-only', str(e.exception)) + + +if __name__ == '__main__': + unittest.main() From ed0f6edf0406dbb6824e680c3d8a5a8381f5f0bb Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 13:51:47 +0100 Subject: [PATCH 075/369] Accept more error messages in test_tls_ext_noca OpenSSL 1.0, 1.1, and NSS return different error messages for untrusted certificate and missing CA. https://github.com/python-ldap/python-ldap/pull/92 Closes: https://github.com/python-ldap/python-ldap/issues/87 Signed-off-by: Christian Heimes --- Tests/t_cext.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 4ef5d0eb..c48a9c37 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -833,9 +833,16 @@ def test_tls_ext_noca(self): l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) with self.assertRaises(_ldap.CONNECT_ERROR) as e: l.start_tls_s() - # some platforms return '(unknown error code)' as reason - if '(unknown error code)' not in str(e.exception): - self.assertIn('not trusted', str(e.exception)) + # known resaons: + # Ubuntu on Travis: '(unknown error code)' + # OpenSSL 1.1: error:1416F086:SSL routines:\ + # tls_process_server_certificate:certificate verify failed + # NSS: TLS error -8172:Peer's certificate issuer has \ + # been marked as not trusted by the user. + msg = str(e.exception) + candidates = ('certificate', 'tls', '(unknown error code)') + if not any(s in msg.lower() for s in candidates): + self.fail(msg) @requires_tls(skip_nss=True) def test_tls_ext_clientcert(self): From edbb3784070284cffc6ad6024b4245a757bef085 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 4 Dec 2017 10:04:57 +0100 Subject: [PATCH 076/369] Add valgrind target to check for memory leaks Signed-off-by: Christian Heimes --- CHANGES | 1 + Doc/contributing.rst | 4 +++- Doc/spelling_wordlist.txt | 1 + Makefile | 16 ++++++++++++++- Misc/python-ldap.supp | 41 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 Misc/python-ldap.supp diff --git a/CHANGES b/CHANGES index df351798..f75659dd 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Infrastructure: * Remove distclean.sh in favor of make clean * Use `package`, `depends`, `install_requires` in setup.py * Add make target for scan-build (static analysis using clang) +* Add make target and suppression file for Valgrind (memory checker) Modules/ * Remove unused LDAPberval helper functions diff --git a/Doc/contributing.rst b/Doc/contributing.rst index aa6097d0..114541f5 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -168,13 +168,15 @@ We use several specialized tools for debugging and maintenance. Make targets ------------ -``make lcov-open`` +``make lcov lcov-open`` Generate and view test coverage for C code. Requires ``make`` and ``lcov``. ``make scan-build`` Run static analysis. Requires ``clang``. +``make valgrind`` + Run Valgrind to check for memory leaks. Requires ``valgrind`` Reference leak tests -------------------- diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 95a5df65..70c07e68 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -150,5 +150,6 @@ userApplications userPassword usr uuids +Valgrind whitespace workflow diff --git a/Makefile b/Makefile index 1ee3eda1..f648d952 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ LCOV_REPORT=build/lcov_report 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 .NOTPARALLEL: @@ -18,6 +19,9 @@ clean: -delete find . -depth -name __pycache__ -exec rm -rf {} \; +build: + mkdir -p build + # LCOV report (measuring test coverage for C code) .PHONY: lcov-clean lcov-coverage lcov-report lcov-open lcov lcov-clean: @@ -27,7 +31,7 @@ lcov-clean: lcov-coverage: WITH_GCOV=1 tox -e py27,py36 -$(LCOV_INFO): +$(LCOV_INFO): build lcov --capture --directory build --output-file $(LCOV_INFO) $(LCOV_REPORT): $(LCOV_INFO) @@ -49,3 +53,13 @@ scan-build: scan-build -o $(SCAN_REPORT) --html-title="python-ldap scan report" \ -analyze-headers --view \ $(PYTHON) setup.py clean --all build + +# valgrind memory checker +.PHONY: valgrind +valgrind: build + valgrind --leak-check=full \ + --suppressions=$(PYTHON_SUPP) \ + --suppressions=Misc/python-ldap.supp \ + --gen-suppressions=all \ + --log-file=build/valgrind.log \ + $(PYTHON) setup.py test diff --git a/Misc/python-ldap.supp b/Misc/python-ldap.supp new file mode 100644 index 00000000..de704466 --- /dev/null +++ b/Misc/python-ldap.supp @@ -0,0 +1,41 @@ +# Valgrind suppression file for Python 3.6. + +{ + Ignore libldap memory leak, https://github.com/python-ldap/python-ldap/issues/82 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ber_memalloc_x + fun:ber_flatten + fun:ldap_cancel + fun:l_ldap_cancel + ... +} + +{ + Known leak in SASL interaction, https://github.com/python-ldap/python-ldap/issues/81 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:strdup + fun:interaction + fun:py_ldap_sasl_interaction + fun:ldap_int_sasl_bind + fun:ldap_sasl_interactive_bind + fun:ldap_sasl_interactive_bind_s + fun:l_ldap_sasl_interactive_bind_s + ... +} + +{ + Ignore possible leaks in exception initialization + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:PyObject_Malloc + ... + fun:PyErr_NewException + fun:LDAPinit_constants + fun:init_ldap_module + ... +} From 4657710b445dcc54112034b1907acea64f2e9b98 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 5 Dec 2017 13:40:48 +0100 Subject: [PATCH 077/369] Use uniform shebang in all demos Demo scripts used different style of shebangs. The shebang "#!/usr/bin/env python" is preferred because it works nicely in virtual envs. https://github.com/python-ldap/python-ldap/pull/97 Signed-off-by: Christian Heimes --- Demo/pyasn1/sessiontrack.py | 2 +- Demo/pyasn1/syncrepl.py | 2 +- Demo/simplebrowse.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 91909a3a..33ddddab 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- """ demo_track_ldap_session.py diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index e4c62e8b..36147920 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- """ This script implements a syncrepl consumer which syncs data from an OpenLDAP diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index aa88f67e..d13c3680 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -1,4 +1,4 @@ -#! python +#!/usr/bin/env python # # simple LDAP server browsing example From c4bcf1d88784f23d070a863cdaab1b59b25d91f8 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 5 Dec 2017 13:41:25 +0100 Subject: [PATCH 078/369] Add the 3.0.0b1 release date to CHANGES Also, put this in the release checklist --- CHANGES | 2 +- Doc/contributing.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index df351798..5c465054 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ ---------------------------------------------------------------- -Released 3.0.0b1 xxxx-xx-xx +Released 3.0.0b1 2017-12-04 Changes since 2.4.45: (this list includes changes from 2.5.x) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index aa6097d0..c87ccf35 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -259,6 +259,7 @@ If you are tasked with releasing python-ldap, remember to: * Bump all instances of the version number. * 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``. * 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``). From c85f4326eea13bdf6af13039c15447a416b3abb9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 5 Dec 2017 14:03:24 +0100 Subject: [PATCH 079/369] Check suppression file in `make valgrind`; improve make target docs This should make things easier to use for people with different configurations. --- Doc/contributing.rst | 22 ++++++++++++++++++++-- Makefile | 8 +++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 114541f5..0cfb51bb 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -168,15 +168,33 @@ We use several specialized tools for debugging and maintenance. Make targets ------------ +Make targets currently use the ``python3`` executable. +Specify a different one using, for example:: + + make PYTHON=/usr/local/bin/python + +Notable targets are: + ``make lcov lcov-open`` Generate and view test coverage for C code. - Requires ``make`` and ``lcov``. + Requires LCOV_. ``make scan-build`` Run static analysis. Requires ``clang``. ``make valgrind`` - Run Valgrind to check for memory leaks. Requires ``valgrind`` + Run Valgrind_ to check for memory leaks. Requires ``valgrind`` and + a Python suppression file, which you can specify as ``PYTHON_SUPP``, e.g.:: + + make valgrind PYTHON_SUPP=/your/path/to/valgrind-python.supp + + The suppression file is ``Misc/valgrind-python.supp`` in the Python + source distribution, and it's frequently packaged together with + Python development headers. + +.. _LCOV: https://github.com/linux-test-project/lcov +.. _Valgrind: http://valgrind.org/ + Reference leak tests -------------------- diff --git a/Makefile b/Makefile index f648d952..150981c3 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,13 @@ scan-build: # valgrind memory checker .PHONY: valgrind -valgrind: build +$(PYTHON_SUPP): + @ >&2 echo "valgrind-python.supp not found" + @ >&2 echo "install Python development files and run:" + @ >&2 echo " $(MAKE) valgrind PYTHON_SUPP=/your/path/to/valgrind-python.supp" + exit 1; + +valgrind: build $(PYTHON_SUPP) valgrind --leak-check=full \ --suppressions=$(PYTHON_SUPP) \ --suppressions=Misc/python-ldap.supp \ From eb9dbc1bb956aac4767cff2a52f39f9de29f31b0 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 5 Dec 2017 15:32:41 +0100 Subject: [PATCH 080/369] Fix error reporting of LDAPObject.set_option() Fix error reporting of LDAPObject.set_option() The method LDAPObject.set_option() did not signal an exception in case the set option failed. The bug was caused by checking for the wrong error value. The internal C function LDAP_set_option() returns ``1`` for sucess and ``0`` for error, but LDAPObject.set_option() was checking for ``-1``. Add some tests for LDAPObject.set_option() and LDAPObject.get_option(). https://github.com/python-ldap/python-ldap/pull/101 Closes: https://github.com/python-ldap/python-ldap/issues/100 Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 2 +- Tests/t_ldap_options.py | 110 ++++++++++++++++++++++++++++++---------- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 815fe426..cd816553 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1273,7 +1273,7 @@ l_ldap_set_option(PyObject* self, PyObject *args) if (!PyArg_ParseTuple(args, "iO:set_option", &option, &value)) return NULL; - if (LDAP_set_option((LDAPObject *)self, option, value) == -1) + if (!LDAP_set_option((LDAPObject *)self, option, value)) return NULL; Py_INCREF(Py_None); return Py_None; diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 798ae463..f0bcc210 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -8,8 +8,8 @@ from ldap.controls import RequestControlTuples from ldap.controls.pagedresults import SimplePagedResultsControl from ldap.controls.openldap import SearchNoOpControl -from slapdtest import requires_tls - +from ldap.ldapobject import SimpleLDAPObject +from slapdtest import SlapdTestCase, requires_tls SENTINEL = object() @@ -26,54 +26,68 @@ ] -class TestGlobalOptions(unittest.TestCase): +class BaseTestOptions(object): + """Common tests for getting/setting options + + Used in subclasses below + """ + + def get_option(self, option): + raise NotImplementedError() + + def set_option(self, option, value): + raise NotImplementedError() + def _check_option(self, option, value, expected=SENTINEL, nonevalue=None): - old = ldap.get_option(option) + old = self.get_option(option) try: - ldap.set_option(option, value) - new = ldap.get_option(option) + self.set_option(option, value) + new = self.get_option(option) if expected is SENTINEL: self.assertEqual(new, value) else: self.assertEqual(new, expected) finally: - ldap.set_option(option, old if old is not None else nonevalue) - self.assertEqual(ldap.get_option(option), old) + self.set_option( + option, + old if old is not None else nonevalue + ) + self.assertEqual(self.get_option(option), old) def test_invalid(self): with self.assertRaises(ValueError): - ldap.get_option(-1) + self.get_option(-1) with self.assertRaises(ValueError): - ldap.set_option(-1, '') + self.set_option(-1, '') - def test_timeout(self): - self._check_option(ldap.OPT_TIMEOUT, 0, nonevalue=-1) - self._check_option(ldap.OPT_TIMEOUT, 10.5, nonevalue=-1) + def _test_timeout(self, option): + self._check_option(option, 10.5, nonevalue=-1) + self._check_option(option, 0, nonevalue=-1) with self.assertRaises(ValueError): - self._check_option(ldap.OPT_TIMEOUT, -5, nonevalue=-1) + self._check_option(option, -5, nonevalue=-1) with self.assertRaises(TypeError): - ldap.set_option(ldap.OPT_TIMEOUT, object) + self.set_option(option, object) + + def test_timeout(self): + self._test_timeout(ldap.OPT_TIMEOUT) def test_network_timeout(self): - self._check_option(ldap.OPT_NETWORK_TIMEOUT, 0, nonevalue=-1) - self._check_option(ldap.OPT_NETWORK_TIMEOUT, 10.5, nonevalue=-1) - with self.assertRaises(ValueError): - self._check_option(ldap.OPT_NETWORK_TIMEOUT, -5, nonevalue=-1) + self._test_timeout(ldap.OPT_NETWORK_TIMEOUT) def _test_controls(self, option): self._check_option(option, []) self._check_option(option, TEST_CTRL, TEST_CTRL_EXPECTED) self._check_option(option, tuple(TEST_CTRL), TEST_CTRL_EXPECTED) with self.assertRaises(TypeError): - ldap.set_option(option, object) + self.set_option(option, object) with self.assertRaises(TypeError): # must contain a tuple - ldap.set_option(option, [list(TEST_CTRL[0])]) + self.set_option(option, [list(TEST_CTRL[0])]) with self.assertRaises(TypeError): # data must be bytes or None - ldap.set_option( + self.set_option( option, [TEST_CTRL[0][0], TEST_CTRL[0][1], u'data'] ) @@ -87,20 +101,64 @@ def test_server_controls(self): def test_uri(self): self._check_option(ldap.OPT_URI, "ldapi:///path/to/socket") with self.assertRaises(TypeError): - ldap.set_option(ldap.OPT_URI, object) + self.set_option(ldap.OPT_URI, object) @requires_tls() def test_cafile(self): # None or a distribution or OS-specific path - ldap.get_option(ldap.OPT_X_TLS_CACERTFILE) + self.get_option(ldap.OPT_X_TLS_CACERTFILE) def test_readonly(self): - value = ldap.get_option(ldap.OPT_API_INFO) + value = self.get_option(ldap.OPT_API_INFO) self.assertIsInstance(value, dict) with self.assertRaises(ValueError) as e: - ldap.set_option(ldap.OPT_API_INFO, value) + self.set_option(ldap.OPT_API_INFO, value) self.assertIn('read-only', str(e.exception)) +class TestGlobalOptions(BaseTestOptions, unittest.TestCase): + """Test setting/getting options globally + """ + + def get_option(self, option): + return ldap.get_option(option) + + def set_option(self, option, value): + return ldap.set_option(option, value) + + +class TestLDAPObjectOptions(BaseTestOptions, SlapdTestCase): + """Test setting/getting connection-specific options + """ + + ldap_object_class = SimpleLDAPObject + + def setUp(self): + self.conn = self._open_ldap_conn( + who=self.server.root_dn, + cred=self.server.root_pw + ) + + def tearDown(self): + self.conn.unbind_s() + self.conn = None + + def get_option(self, option): + return self.conn.get_option(option) + + def set_option(self, option, value): + return self.conn.set_option(option, value) + + # test is failing with: + # pyasn1.error.SubstrateUnderrunError: Short octet stream on tag decoding + @unittest.expectedFailure + def test_client_controls(self): + self._test_controls(ldap.OPT_CLIENT_CONTROLS) + + @unittest.expectedFailure + def test_server_controls(self): + self._test_controls(ldap.OPT_SERVER_CONTROLS) + + if __name__ == '__main__': unittest.main() From a8b2748dfdb6e74d708667255adb6f4bfccadf78 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 5 Dec 2017 13:45:38 +0100 Subject: [PATCH 081/369] Support None for set_option(OPT_NETWORK_TIMEOUT) The setter for network timeout and timeout options now support None as value. None is mapped to infinity. Closes: https://github.com/python-ldap/python-ldap/issues/95 Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 6 ++++-- Modules/options.c | 48 +++++++++++++++++++++++++++-------------- Tests/t_ldap_options.py | 46 +++++++++++++++++++++++++++++++-------- 3 files changed, 73 insertions(+), 27 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1f7ae5b5..82bc31c5 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -157,7 +157,7 @@ following option identifiers are defined as constants: .. py:data:: OPT_NETWORK_TIMEOUT .. versionchanged:: 3.0 - A timeout of ``-1`` resets timeout to infinity. + A timeout of ``-1`` or ``None`` resets timeout to infinity. .. py:data:: OPT_PROTOCOL_VERSION @@ -184,7 +184,7 @@ following option identifiers are defined as constants: .. py:data:: OPT_TIMEOUT .. versionchanged:: 3.0 - A timeout of ``-1`` resets timeout to infinity. + A timeout of ``-1`` or ``None`` resets timeout to infinity. .. py:data:: OPT_URI @@ -1125,6 +1125,8 @@ These attributes are mutable unless described as read-only. This option is mapped to option constant :py:const:`OPT_NETWORK_TIMEOUT` and used in the underlying OpenLDAP client lib. + .. versionchanged:: 3.0.0 + A timeout of ``-1`` or ``None`` resets timeout to infinity. .. py:attribute:: LDAPObject.protocol_version -> int diff --git a/Modules/options.c b/Modules/options.c index 647f859d..ea841a38 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -136,26 +136,42 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) break; case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: - /* Float valued timeval options */ - if (!PyArg_Parse(value, "d:set_option", &doubleval)) - return 0; - if (doubleval >= 0) { - set_timeval_from_double( &tv, doubleval ); - ptr = &tv; - } else if (doubleval == -1) { - /* -1 is infinity timeout */ - tv.tv_sec = -1; - tv.tv_usec = 0; - ptr = &tv; - } else { + /* Float valued timeval options, 'd' also handles int/long */ + if (!PyArg_Parse(value, "d:set_option", &doubleval)) { + /* clear error and try again with None */ + PyErr_Clear(); + if (PyNone_Check(value)) { + /* None is mapped to infinity timeout */ + doubleval = -1; + } + else { PyErr_Format( - PyExc_ValueError, - "timeout must be >= 0 or -1 for infinity, got %d", - option + PyExc_TypeError, + "A float or None is expected for timeout, got %.100s", + Py_TYPE(value)->tp_name ); return 0; } - break; + } + + if (doubleval >= 0) { + set_timeval_from_double( &tv, doubleval ); + ptr = &tv; + } 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 + ); + return 0; + } + break; + case LDAP_OPT_SERVER_CONTROLS: case LDAP_OPT_CLIENT_CONTROLS: if (!LDAPControls_from_object(value, &controls)) diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index f0bcc210..1e682f27 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -38,8 +38,7 @@ def get_option(self, option): def set_option(self, option, value): raise NotImplementedError() - def _check_option(self, option, value, expected=SENTINEL, - nonevalue=None): + def _check_option(self, option, value, expected=SENTINEL): old = self.get_option(option) try: self.set_option(option, value) @@ -49,10 +48,7 @@ def _check_option(self, option, value, expected=SENTINEL, else: self.assertEqual(new, expected) finally: - self.set_option( - option, - old if old is not None else nonevalue - ) + self.set_option(option, old) self.assertEqual(self.get_option(option), old) def test_invalid(self): @@ -62,12 +58,20 @@ def test_invalid(self): self.set_option(-1, '') def _test_timeout(self, option): - self._check_option(option, 10.5, nonevalue=-1) - self._check_option(option, 0, nonevalue=-1) + self._check_option(option, 10.5) + self._check_option(option, 0) with self.assertRaises(ValueError): - self._check_option(option, -5, nonevalue=-1) + self._check_option(option, -5) with self.assertRaises(TypeError): self.set_option(option, object) + old = self.get_option(option) + try: + self.set_option(option, None) + self.assertEqual(self.get_option(option), None) + self.set_option(option, -1) + self.assertEqual(self.get_option(option), None) + finally: + self.set_option(option, old) def test_timeout(self): self._test_timeout(ldap.OPT_TIMEOUT) @@ -149,6 +153,30 @@ def get_option(self, option): def set_option(self, option, value): return self.conn.set_option(option, value) + def test_network_timeout_attribute(self): + option = ldap.OPT_NETWORK_TIMEOUT + old = self.get_option(option) + try: + self.assertEqual(self.conn.network_timeout, old) + + self.conn.network_timeout = 5 + self.assertEqual(self.conn.network_timeout, 5) + 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.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) + finally: + self.set_option(option, old) + # test is failing with: # pyasn1.error.SubstrateUnderrunError: Short octet stream on tag decoding @unittest.expectedFailure From 4389d9b1787bada7de9ebabc27fbcdc2dd4701df Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 5 Dec 2017 14:53:43 +0100 Subject: [PATCH 082/369] Use custom ldap.LDAPBytesWarning class Under Python 2, python-ldap now uses custom ldap.LDAPBytesWarning instead of BytesWarning to emit warnings in default bytes mode. Closes: https://github.com/python-ldap/python-ldap/issues/99 Signed-off-by: Christian Heimes --- Doc/bytes_mode.rst | 19 ++++++++++++++++++- Lib/ldap/__init__.py | 2 +- Lib/ldap/ldapobject.py | 11 +++++++++-- Tests/t_ldapobject.py | 37 ++++++++++++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index b18c0aed..62ab4962 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -66,7 +66,9 @@ Unspecified: relaxed mode with warnings 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. + warnings of type :class:`ldap.LDAPBytesWarning` are emitted when they + are not. :class:`ldap.LDAPBytesWarning` is a subclass of + :class:`BytesWarning`. Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. @@ -103,3 +105,18 @@ Note that only the result's *values* are of the ``bytes`` type: 'sn': [b'Barrois'], }), ] + + +Filter warning +-------------- + +The bytes mode warnings can be filtered out and ignored with a +simple filter. + +.. code-block:: python + + import warnings + import ldap + + if hasattr(ldap, 'LDAPBytesWarning'): + warnings.simplefilter('ignore', ldap.LDAPBytesWarning) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 7cb16b0e..3a86095f 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -86,7 +86,7 @@ def release(self): from ldap.functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs -from ldap.ldapobject import NO_UNIQUE_ENTRY +from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str del str2dn diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index c5e62bd0..f37ef24a 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -14,6 +14,7 @@ 'LDAPObject', 'SimpleLDAPObject', 'ReconnectLDAPObject', + 'LDAPBytesWarning' ] @@ -37,6 +38,12 @@ else: text_type = str + +class LDAPBytesWarning(BytesWarning): + """python-ldap bytes mode warning + """ + + class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): """ Exception raised if a LDAP search returned more than entry entry @@ -84,7 +91,7 @@ def __init__( "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.", - BytesWarning, + LDAPBytesWarning, stacklevel=2, ) bytes_mode = True @@ -122,7 +129,7 @@ def _bytesify_input(self, value): warnings.warn( "Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit " "option for bytes_mode on your LDAP connection" % (value,), - BytesWarning, + LDAPBytesWarning, stacklevel=6, ) return value.encode('utf-8') diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 9b6a090e..835512b0 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -19,6 +19,7 @@ import os import unittest import pickle +import warnings from slapdtest import SlapdTestCase, requires_sasl # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -314,7 +315,41 @@ def test007_timeout(self): l.abandon(m) with self.assertRaises(ldap.TIMEOUT): result = l.result(m, timeout=0.001) - + + def assertIsSubclass(self, cls, other): + self.assertTrue( + issubclass(cls, other), + cls.__mro__ + ) + + @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('default') + 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 u'%s' with default (disabled) bytes " + "mode; please choose an explicit option for bytes_mode on your " + "LDAP connection" % self.server.suffix + ) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From a28d2be28f45303a65c60d4f458cc5370455c634 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 7 Dec 2017 11:24:41 +0100 Subject: [PATCH 083/369] Remove workaround for OpenLDAP NSS issue The NSS issue has been fixed in Fedora update openldap-2.4.45-2.fc26 and openldap-2.4.45-4.fc27. Fedora users can now execute all tests. Includes documentation for build requirements and minimum versions on Fedora. See: https://bugzilla.redhat.com/show_bug.cgi?id=1520990 Closes: https://github.com/python-ldap/python-ldap/issues/60 Closes: https://github.com/python-ldap/python-ldap/issues/51 Signed-off-by: Christian Heimes --- Doc/installing.rst | 23 +++++++++++++++++++++++ Lib/slapdtest/_slapdtest.py | 10 +--------- Tests/t_cext.py | 8 ++++---- Tests/t_ldap_sasl.py | 2 +- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index d5cbf956..a5e08920 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -132,6 +132,29 @@ on the local system when building python-ldap: .. _Cyrus SASL: https://www.cyrusimap.org/sasl/ +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 valgrind + + +Fedora +------ + +Packages for building and testing:: + + dnf install "@C Development Tools and Libraries" openldap-devel \ + python2-devel python3-devel python3-tox valgrind clang-analyzer + +.. note:: + + ``openldap-2.4.45-2`` (Fedora 26), ``openldap-2.4.45-4`` (Fedora 27) or + newer are required. + + setup.cfg ========= diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 5ba64642..f3f096c3 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -76,21 +76,13 @@ def skip_unless_ci(reason, feature=None): return identity -def requires_tls(skip_nss=False): +def requires_tls(): """Decorator for TLS tests Tests are not skipped on CI (e.g. Travis CI) - - :param skip_nss: Skip test when libldap is compiled with NSS as TLS lib """ if not ldap.TLS_AVAIL: return skip_unless_ci("test needs ldap.TLS_AVAIL", feature='TLS') - elif skip_nss and ldap.get_option(ldap.OPT_X_TLS_PACKAGE) == 'MozNSS': - return skip_unless_ci( - "Test doesn't work correctly with Mozilla NSS, see " - "https://bugzilla.redhat.com/show_bug.cgi?id=1519167", - feature="NSS" - ) else: return identity diff --git a/Tests/t_cext.py b/Tests/t_cext.py index c48a9c37..d8233dce 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -817,7 +817,7 @@ def test_invalid_controls(self): l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,)) self.assertInvalidControls(l.unbind_ext) - @requires_tls(skip_nss=True) + @requires_tls() def test_tls_ext(self): l = self._open_conn(bind=False) # StartTLS needs LDAPv3 @@ -827,7 +827,7 @@ def test_tls_ext(self): l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() - @requires_tls(skip_nss=False) + @requires_tls() def test_tls_ext_noca(self): l = self._open_conn(bind=False) l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) @@ -844,7 +844,7 @@ def test_tls_ext_noca(self): if not any(s in msg.lower() for s in candidates): self.fail(msg) - @requires_tls(skip_nss=True) + @requires_tls() def test_tls_ext_clientcert(self): l = self._open_conn(bind=False) l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) @@ -855,7 +855,7 @@ def test_tls_ext_clientcert(self): l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) l.start_tls_s() - @requires_tls(skip_nss=False) + @requires_tls() def test_tls_packages(self): # libldap has tls_g.c, tls_m.c, and tls_o.c with ldap_int_tls_impl package = _ldap.get_option(_ldap.OPT_X_TLS_PACKAGE) diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 2fa4dbcc..7f472cc6 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -76,7 +76,7 @@ def test_external_ldapi(self): "dn:{}".format(self.server.root_dn.lower()) ) - @requires_tls(skip_nss=True) + @requires_tls() def test_external_tlscert(self): ldap_conn = self.ldap_object_class(self.server.ldap_uri) ldap_conn.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) From 829027081d8cb1891732b8d7d631bfcc827fe213 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 5 Dec 2017 16:07:08 +0100 Subject: [PATCH 084/369] Doc: LDAPBytesWarning doc improvements - Make filtering note apply to both causes of the warning - Add formal class documentation - Add cross-references --- Doc/bytes_mode.rst | 14 +++++++++----- Doc/reference/ldap.rst | 12 ++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index 62ab4962..c775b273 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -66,9 +66,11 @@ Unspecified: relaxed mode with warnings Text values returned from python-ldap are always ``unicode``. Text values supplied to python-ldap should be ``unicode``; - warnings of type :class:`ldap.LDAPBytesWarning` are emitted when they - are not. :class:`ldap.LDAPBytesWarning` is a subclass of - :class:`BytesWarning`. + warnings are emitted when they are not. + + The warnings are of type :class:`~ldap.LDAPBytesWarning`, which + is a subclass of :class:`BytesWarning` designed to be easily + :ref:`filtered out ` if needed. Backwards-compatible behavior is not scheduled for removal until Python 2 itself reaches end of life. @@ -107,8 +109,10 @@ Note that only the result's *values* are of the ``bytes`` type: ] -Filter warning --------------- +.. _filter-bytes-warning: + +Filtering warnings +------------------ The bytes mode warnings can be filtered out and ignored with a simple filter. diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1f7ae5b5..4b163da1 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -564,6 +564,18 @@ The above exceptions are raised when a result code from an underlying API call does not indicate success. +Warnings +======== + +.. py:class:: LDAPBytesWarning + + Raised when bytes/text mismatch in non-strict bytes mode. + + See :ref:`bytes_mode` for details. + + .. versionadded:: 3.0.0 + + .. _ldap-objects: LDAPObject classes From 12f193efaeae7c82a62f4b723a5e221dac26adef Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 7 Dec 2017 21:02:29 +0100 Subject: [PATCH 085/369] In timeout options, reword the message only of TypeError --- Modules/options.c | 30 ++++++++++++++++-------------- Tests/t_ldap_options.py | 2 ++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Modules/options.c b/Modules/options.c index ea841a38..ee606d46 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -136,20 +136,22 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) break; case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: - /* Float valued timeval options, 'd' also handles int/long */ - if (!PyArg_Parse(value, "d:set_option", &doubleval)) { - /* clear error and try again with None */ - PyErr_Clear(); - if (PyNone_Check(value)) { - /* None is mapped to infinity timeout */ - doubleval = -1; - } - else { - PyErr_Format( - PyExc_TypeError, - "A float or None is expected for timeout, got %.100s", - Py_TYPE(value)->tp_name - ); + /* Float valued timeval options */ + if (value == Py_None) { + /* None is mapped to infinity timeout */ + doubleval = -1; + } 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 + ); + } return 0; } } diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 1e682f27..a681a9b4 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -64,6 +64,8 @@ def _test_timeout(self, option): self._check_option(option, -5) with self.assertRaises(TypeError): self.set_option(option, object) + with self.assertRaises(OverflowError): + self._check_option(option, 10**1000) old = self.get_option(option) try: self.set_option(option, None) From baa091f0ae1a9cd36d60caea1b58ccdaa957267f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 11 Dec 2017 11:53:36 +0100 Subject: [PATCH 086/369] Change memory handling in attrs_from_List() attrs_from_List() no longer uses internal buffers of bytes/str PyObject* to maintain memory. Instead it creates its own copies, which are then passed to ldap_search_ext() and then cleaned up. The modification is required for Python 3.7. PyUnicode_AsUTF8() returns const char* but ldap_search_ext() does not use const. https://github.com/python-ldap/python-ldap/pull/106 Closes: https://github.com/python-ldap/python-ldap/issues/105 Signed-off-by: Christian Heimes --- .travis.yml | 8 ++++++ Modules/LDAPObject.c | 67 ++++++++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 960f6db2..00eb6292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,10 @@ matrix: env: - TOXENV=py36 - WITH_GCOV=1 + - python: 3.7-dev + env: + - TOXENV=py37 + - WITH_GCOV=1 - python: 2.7 env: - TOXENV=py2-nosasltls @@ -44,6 +48,10 @@ matrix: - WITH_GCOV=1 - python: 3.6 env: TOXENV=doc + allow_failures: + - env: + - TOXENV=py37 + - WITH_GCOV=1 env: global: diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index cd816553..addbe961 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -16,7 +16,7 @@ #include #endif -static void free_attrs(char***, PyObject*); +static void free_attrs(char***); /* constructor */ @@ -248,13 +248,10 @@ List_to_LDAPMods( PyObject *list, int no_op ) { */ int -attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { +attrs_from_List( PyObject *attrlist, char***attrsp) { char **attrs = NULL; - Py_ssize_t i, len; - PyObject *item; - - *seq = NULL; + PyObject *seq = NULL; if (attrlist == Py_None) { /* None means a NULL attrlist */ @@ -268,8 +265,16 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { "expected *list* of strings, not a string", attrlist); goto error; } else { - *seq = PySequence_Fast(attrlist, "expected list of strings or None"); - if (*seq == NULL) + 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) goto error; len = PySequence_Length(attrlist); @@ -280,25 +285,36 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { for (i = 0; i < len; i++) { attrs[i] = NULL; - item = PySequence_Fast_GET_ITEM(*seq, i); + item = PySequence_Fast_GET_ITEM(seq, i); if (item == NULL) goto error; #if PY_MAJOR_VERSION == 2 - /* Encoded by Python to UTF-8 */ + /* Encoded in Python to UTF-8 */ if (!PyBytes_Check(item)) { LDAPerror_TypeError("expected bytes in list", item); goto error; } - attrs[i] = PyBytes_AsString(item); + if (PyBytes_AsStringAndSize(item, &str, &strlen) == -1) { + goto error; + } #else if (!PyUnicode_Check(item)) { LDAPerror_TypeError("expected string in list", item); goto error; } - attrs[i] = PyUnicode_AsUTF8(item); + 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. + */ + attrs[i] = (char *)PyMem_NEW(char *, strlen + 1); + if (attrs[i] == NULL) + goto nomem; + memcpy(attrs[i], str, strlen + 1); } attrs[len] = NULL; + Py_DECREF(seq); } *attrsp = attrs; @@ -307,22 +323,26 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { nomem: PyErr_NoMemory(); error: - free_attrs(&attrs, *seq); + Py_XDECREF(seq); + free_attrs(&attrs); return 0; } /* free memory allocated from above routine */ static void -free_attrs( char*** attrsp, PyObject* seq ) { +free_attrs( char*** attrsp) { char **attrs = *attrsp; + char **p; - if (attrs != NULL) { - PyMem_DEL(attrs); - *attrsp = NULL; - } + if (attrs == NULL) + return; - Py_XDECREF(seq); + *attrsp = NULL; + for (p = attrs; *p != NULL; p++) { + PyMem_DEL(*p); + } + PyMem_DEL(attrs); } /*------------------------------------------------------------ @@ -1130,7 +1150,6 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) PyObject *serverctrls = Py_None; PyObject *clientctrls = Py_None; - PyObject *attrs_seq = NULL; LDAPControl** server_ldcs = NULL; LDAPControl** client_ldcs = NULL; @@ -1148,7 +1167,7 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) &serverctrls, &clientctrls, &timeout, &sizelimit )) return NULL; if (not_valid(self)) return NULL; - if (!attrs_from_List( attrlist, &attrs, &attrs_seq )) + if (!attrs_from_List( attrlist, &attrs )) return NULL; if (timeout >= 0) { @@ -1160,14 +1179,14 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) { - free_attrs( &attrs, attrs_seq); + free_attrs( &attrs ); return NULL; } } if (!PyNone_Check(clientctrls)) { if (!LDAPControls_from_object(clientctrls, &client_ldcs)) { - free_attrs( &attrs, attrs_seq); + free_attrs( &attrs ); LDAPControl_List_DEL( server_ldcs ); return NULL; } @@ -1178,7 +1197,7 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) server_ldcs, client_ldcs, tvp, sizelimit, &msgid ); LDAP_END_ALLOW_THREADS( self ); - free_attrs( &attrs, attrs_seq); + free_attrs( &attrs ); LDAPControl_List_DEL( server_ldcs ); LDAPControl_List_DEL( client_ldcs ); From dc7a3fe05a3ac7491fbaa5ec70437b4dfb2c84b2 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 11 Dec 2017 11:53:52 +0100 Subject: [PATCH 087/369] Rename ldap.async to ldap.asyncsearch Python 3.7 introduces `async` as a keyword. The old import path is still available for backwards compatibility. https://github.com/python-ldap/python-ldap/pull/107 Signed-off-by: Christian Heimes --- Doc/reference/ldap-async.rst | 55 ++++--- Doc/spelling_wordlist.txt | 1 + Lib/ldap/async.py | 286 ++--------------------------------- Lib/ldap/asyncsearch.py | 283 ++++++++++++++++++++++++++++++++++ Tests/t_ldap_asyncsearch.py | 25 +++ Tests/t_untested_mods.py | 3 +- 6 files changed, 353 insertions(+), 300 deletions(-) create mode 100644 Lib/ldap/asyncsearch.py create mode 100644 Tests/t_ldap_asyncsearch.py diff --git a/Doc/reference/ldap-async.rst b/Doc/reference/ldap-async.rst index 59a34f43..76245f60 100644 --- a/Doc/reference/ldap-async.rst +++ b/Doc/reference/ldap-async.rst @@ -1,49 +1,60 @@ -************************************************************** -:py:mod:`ldap.async` Stream-processing of large search results -************************************************************** +******************************************************************** +:py:mod:`ldap.asyncsearch` Stream-processing of large search results +******************************************************************** -.. py:module:: ldap.async +.. py:module:: ldap.asyncsearch :synopsis: Framework for stream-processing of large search results. With newer Python versions one might want to consider using :py:mod:`ldap.resiter` instead. +.. versionchanged:: 3.0 + In Python 3.7 ``async`` is a reserved keyword. The module + :py:mod:`ldap.async` has been renamed to :py:mod:`ldap.asyncsearch`. The + old name :py:mod:`ldap.async` is still available for backwards + compatibility. + +.. deprecated:: 3.0 + The old name :py:mod:`ldap.async` is deprecated, but will not be removed + until Python 3.6 reaches end-of-life. + + Classes ======= -.. autoclass:: ldap.async.AsyncSearchHandler +.. autoclass:: ldap.asyncsearch.AsyncSearchHandler :members: -.. autoclass:: ldap.async.List +.. autoclass:: ldap.asyncsearch.List :members: -.. autoclass:: ldap.async.Dict +.. autoclass:: ldap.asyncsearch.Dict :members: -.. autoclass:: ldap.async.IndexedDict +.. autoclass:: ldap.asyncsearch.IndexedDict :members: -.. autoclass:: ldap.async.LDIFWriter +.. autoclass:: ldap.asyncsearch.LDIFWriter :members: -.. _ldap.async-example: +.. _ldap.asyncsearch-example: Examples ======== -.. _ldap.async-example.List: +.. _ldap.asyncsearch-example.List: -Using ldap.async.List -^^^^^^^^^^^^^^^^^^^^^ +Using ldap.asyncsearch.List +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This example demonstrates how to use class ldap.async.List for +This example demonstrates how to use class ldap.asyncsearch.List for retrieving partial search results even though the exception :exc:`ldap.SIZELIMIT_EXCEEDED` was raised because a server side limit was hit. :: - import sys,ldap,ldap.async + import sys,ldap,ldap.asyncsearch - s = ldap.async.List( + s = ldap.asyncsearch.List( ldap.initialize('ldap://localhost'), ) @@ -67,17 +78,17 @@ retrieving partial search results even though the exception ) ) -.. _ldap.async-example.LDIFWriter: +.. _ldap.asyncsearch-example.LDIFWriter: -Using ldap.async.LDIFWriter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using ldap.asyncsearch.LDIFWriter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This example demonstrates how to use class ldap.async.LDIFWriter +This example demonstrates how to use class ldap.asyncsearch.LDIFWriter for writing search results as LDIF to stdout. :: - import sys,ldap,ldap.async + import sys,ldap,ldap.asyncsearch - s = ldap.async.LDIFWriter( + s = ldap.asyncsearch.LDIFWriter( ldap.initialize('ldap://localhost:1390'), sys.stdout ) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 70c07e68..925ddc30 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -1,5 +1,6 @@ args async +asyncsearch attr attrlist attrList diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 23c56605..1d4505bc 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -1,283 +1,15 @@ """ -ldap.async - handle async LDAP operations +ldap.asyncsearch - handle async LDAP search operations See https://www.python-ldap.org/ for details. """ +import warnings -import ldap +from ldap.asyncsearch import * +from ldap.asyncsearch import __version__ -from ldap import __version__ - -SEARCH_RESULT_TYPES = set([ - ldap.RES_SEARCH_ENTRY, - ldap.RES_SEARCH_RESULT, - ldap.RES_SEARCH_REFERENCE, -]) - -ENTRY_RESULT_TYPES = set([ - ldap.RES_SEARCH_ENTRY, - ldap.RES_SEARCH_RESULT, -]) - - -class WrongResultType(Exception): - - def __init__(self,receivedResultType,expectedResultTypes): - self.receivedResultType = receivedResultType - self.expectedResultTypes = expectedResultTypes - Exception.__init__(self) - - def __str__(self): - return 'Received wrong result type %s (expected one of %s).' % ( - self.receivedResultType, - ', '.join(self.expectedResultTypes), - ) - - -class AsyncSearchHandler: - """ - Class for stream-processing LDAP search results - - Arguments: - - l - LDAPObject instance - """ - - def __init__(self,l): - self._l = l - self._msgId = None - self._afterFirstResult = 1 - - def startSearch( - self, - searchRoot, - searchScope, - filterStr, - attrList=None, - attrsOnly=0, - timeout=-1, - sizelimit=0, - serverctrls=None, - clientctrls=None - ): - """ - searchRoot - See parameter base of method LDAPObject.search() - searchScope - See parameter scope of method LDAPObject.search() - filterStr - See parameter filter of method LDAPObject.search() - attrList=None - See parameter attrlist of method LDAPObject.search() - attrsOnly - See parameter attrsonly of method LDAPObject.search() - timeout - Maximum time the server shall use for search operation - sizelimit - Maximum number of entries a server should return - (request client-side limit) - serverctrls - list of server-side LDAP controls - clientctrls - list of client-side LDAP controls - """ - self._msgId = self._l.search_ext( - searchRoot,searchScope,filterStr, - attrList,attrsOnly,serverctrls,clientctrls,timeout,sizelimit - ) - self._afterFirstResult = 1 - return # startSearch() - - def preProcessing(self): - """ - Do anything you want after starting search but - before receiving and processing results - """ - - def afterFirstResult(self): - """ - Do anything you want right after successfully receiving but before - processing first result - """ - - def postProcessing(self): - """ - Do anything you want after receiving and processing all results - """ - - def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1): - """ - ignoreResultsNumber - Don't process the first ignoreResultsNumber results. - processResultsCount - If non-zero this parameters indicates the number of results - processed is limited to processResultsCount. - timeout - See parameter timeout of ldap.LDAPObject.result() - """ - self.preProcessing() - result_counter = 0 - end_result_counter = ignoreResultsNumber+processResultsCount - go_ahead = 1 - partial = 0 - self.beginResultsDropped = 0 - self.endResultBreak = result_counter - try: - result_type,result_list = None,None - while go_ahead: - while result_type is None and not result_list: - result_type,result_list,result_msgid,result_serverctrls = self._l.result3(self._msgId,0,timeout) - if self._afterFirstResult: - self.afterFirstResult() - self._afterFirstResult = 0 - if not result_list: - break - if result_type not in SEARCH_RESULT_TYPES: - raise WrongResultType(result_type,SEARCH_RESULT_TYPES) - # Loop over list of search results - for result_item in result_list: - if result_counter Date: Mon, 11 Dec 2017 13:29:19 +0100 Subject: [PATCH 088/369] Provide build deps for Alpine and CentOS Provide build deps for Alpine and CentOS Also updates build deps for Fedora and Debian, and format the shell commands nicely. See: https://github.com/pyldap/pyldap/issues/91#issuecomment-350098897 https://github.com/python-ldap/python-ldap/pull/113 Some changes by Petr Viktorin Signed-off-by: Christian Heimes --- Doc/installing.rst | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index a5e08920..01d39c7f 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -1,3 +1,5 @@ +.. highlight:: console + .. _installing: Installing python-ldap @@ -18,7 +20,7 @@ The preferred point for downloading the “official” source distribution is the `PyPI repository`_ which supports installing via `pip`_. For example:: - python -m pip install python-ldap + $ python -m pip install python-ldap .. _PyPI repository: https://pypi.python.org/pypi/python-ldap/ .. _pip: https://pip.pypa.io/en/stable/ @@ -83,8 +85,8 @@ Mac OS X You can install directly with pip:: - xcode-select --install - pip install python-ldap \ + $ xcode-select --install + $ pip install python-ldap \ --global-option=build_ext \ --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" @@ -98,8 +100,8 @@ Installing from Source python-ldap is built and installed using the Python setuptools. From a source repository:: - python -m pip install setuptools - python setup.py install + $ python -m pip install setuptools + $ python setup.py install If you have more than one Python interpreter installed locally, you should use the same one you plan to use python-ldap with. @@ -132,22 +134,38 @@ on the local system when building python-ldap: .. _Cyrus SASL: https://www.cyrusimap.org/sasl/ +Alpine +------ + +Packages for building:: + + # apk add build-base openldap-dev python2-dev python3-dev + +CentOS +------ + +Packages for building:: + + # yum groupinstall "Development tools" + # yum install openldap-devel python-devel + 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 valgrind - + # apt-get install build-essential python3-dev python2.7-dev \ + libldap2-dev libsasl2-dev slapd ldap-utils python-tox \ + lcov valgrind Fedora ------ Packages for building and testing:: - dnf install "@C Development Tools and Libraries" openldap-devel \ - python2-devel python3-devel python3-tox valgrind clang-analyzer + # dnf install "@C Development Tools and Libraries" openldap-devel \ + python2-devel python3-devel python3-tox \ + lcov clang-analyzer valgrind .. note:: From 44f7ba266a5ae2cf12dfe60d37c1764de3fade9f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 11 Dec 2017 14:10:07 +0100 Subject: [PATCH 089/369] Make valgrind check more useful * make valgrind now fails when valgrind detects a definitive memory leak * suppress a known memory leak in OpenLDAP's NSS module https://github.com/python-ldap/python-ldap/pull/111 Signed-off-by: Christian Heimes --- Makefile | 10 +++++++++- Misc/python-ldap.supp | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 150981c3..e4ff75ac 100644 --- a/Makefile +++ b/Makefile @@ -63,9 +63,17 @@ $(PYTHON_SUPP): exit 1; valgrind: build $(PYTHON_SUPP) - valgrind --leak-check=full \ + valgrind \ + --leak-check=full \ + --track-fds=yes \ --suppressions=$(PYTHON_SUPP) \ --suppressions=Misc/python-ldap.supp \ --gen-suppressions=all \ --log-file=build/valgrind.log \ $(PYTHON) setup.py test + + @grep -A7 "blocks are definitely lost" build/valgrind.log; \ + if [ $$? == 0 ]; then \ + echo "Found definitive leak, see build/valgrind.log"; \ + exit 1; \ + fi diff --git a/Misc/python-ldap.supp b/Misc/python-ldap.supp index de704466..3267fd05 100644 --- a/Misc/python-ldap.supp +++ b/Misc/python-ldap.supp @@ -27,6 +27,16 @@ ... } +{ + NSS backend leaks one string during first initialization + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:PL_strdup + fun:tlsm_init + ... +} + { Ignore possible leaks in exception initialization Memcheck:Leak From 2ad72f26e809aeb6e014663f545384f8ee50ae61 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 11 Dec 2017 14:20:57 +0100 Subject: [PATCH 090/369] Minimal configuration for pytest Configure setup.cfg for pytest python-ldap uses a non-standard configuration for tests. Also, fix test class: TestSubschemaUrlfetch must be a subclass of TestCase, not TestSuite. https://github.com/python-ldap/python-ldap/pull/114 Signed-off-by: Christian Heimes --- .gitignore | 1 + Doc/contributing.rst | 2 +- Tests/t_ldap_schema_subentry.py | 3 ++- setup.cfg | 8 ++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4e261ca4..962248fe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ .tox .coverage* !.coveragerc +/.cache # shared libs installed by 'setup.py test' /Lib/*.so* diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 45c40f34..abefc4ad 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -224,7 +224,7 @@ Run reference leak tests:: $ cd path/to/python-ldap $ /tmp/refleak/bin/pip install --upgrade . - $ /tmp/refleak/bin/pytest -v -R: Tests/t_*.py + $ /tmp/refleak/bin/pytest -v -R: Run ``/tmp/refleak/bin/pip install --upgrade .`` every time a file outside of ``Tests/`` is modified. diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index d406b46a..3c07d35b 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -25,6 +25,7 @@ os.path.join(HERE, 'data', 'subschema-openldap-all.ldif'), ) + class TestSubschemaLDIF(unittest.TestCase): """ test ldap.schema.SubSchema with subschema subentries read from LDIF files @@ -49,7 +50,7 @@ def test_subschema_file(self): self.assertEqual(attributetype.oid, oid) -class TestSubschemaUrlfetch(unittest.TestSuite): +class TestSubschemaUrlfetch(unittest.TestCase): def test_urlfetch_file(self): freeipa_uri = 'file://{}'.format(TEST_SUBSCHEMA_FILES[0]) dn, schema = ldap.schema.urlfetch(freeipa_uri) diff --git a/setup.cfg b/setup.cfg index 34699da5..374dd42e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,3 +35,11 @@ packager = python-ldap team distribution_name = openSUSE 11.x release = 1 doc_files = CHANGES README INSTALL TODO Demo/ + +# pytest, https://docs.pytest.org/en/latest/customize.html +[tool:pytest] +testpaths = Tests +python_files = t_*.py +filterwarnings = + error + ignore::ldap.LDAPBytesWarning From e5e34fcca7482921897e4c757b8658945fc455ba Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 11 Dec 2017 16:08:59 +0100 Subject: [PATCH 091/369] Doc: Move sample workflow out of the main document The Contributing docs were too long to read. Move the longness out, which should emphasize you'll be fine with the usual Git workflows. https://github.com/python-ldap/python-ldap/pull/115 --- Doc/contributing.rst | 91 ++++------------------------------------- Doc/sample_workflow.rst | 85 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 82 deletions(-) create mode 100644 Doc/sample_workflow.rst diff --git a/Doc/contributing.rst b/Doc/contributing.rst index abefc4ad..70856c48 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -8,6 +8,12 @@ If you'd like to contribute (be it code, documentation, maintenance effort, or anything else), this guide is for you. +.. toctree:: + :hidden: + + sample_workflow.rst + + Communication ============= @@ -54,8 +60,8 @@ If required, write to coordinate a more secure channel. All other communication should be public. -Process for Code contributions -============================== +Contributing code +================= If you're used to open-source Python development with Git, here's the gist: @@ -76,86 +82,7 @@ Or, if you prefer to avoid closed-source services: .. _Read the Docs: http://python-ldap.readthedocs.io/ If you're new to some aspect of the project, you're welcome to use (or adapt) -the workflow below. - - -Sample workflow ---------------- - -We assume that, as a user of python-ldap you're not new to software -development in general, so these instructions are terse. -If you need additional detail, please do ask on the mailing list. - -.. note:: - - The following instructions are for Linux. - If you can translate them to another system, please contribute your - translation! - - -Install `Git`_ and `tox`_. - -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:: - - $ 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:: - - $ source __venv__/bin/activate - -Install python-ldap to it in `editable mode`_:: - - (__venv__)$ python -m pip install -e . - -This way, importing a Python module from python-ldap will directly -use the code from your source tree. -If you change C code, you will still need to recompile -(using the ``pip install`` command again). - -.. _editable mode: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs - -Change the code as desired. - - -To run tests, install and run `tox`_:: - - (__venv__)$ python -m pip install tox - (__venv__)$ tox --skip-missing-interpreters - -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 - -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 - SASL and TLS bindings compiled in. - - -When your change is ready, commit to Git, and submit a pull request on GitHub. -You can take a look at the `committer instructions`_ to see what we are looking -for in a pull request. - -If you don't want to open a GitHub account, please send patches as attachments -to the python-ldap mailing list. +our :ref:`sample workflow `. .. _additional tests: diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst new file mode 100644 index 00000000..97e51c67 --- /dev/null +++ b/Doc/sample_workflow.rst @@ -0,0 +1,85 @@ + +.. _sample workflow: + +Sample workflow for python-ldap development +------------------------------------------- + +This document will guide you through the process of contributing a change +to python-ldap. + +We assume that, as a user of python-ldap, you're not new to software +development in general, so these instructions are terse. +If you need additional detail, please do ask on the mailing list. + +.. note:: + + The following instructions are for Linux. + If you can translate them to another system, please contribute your + translation! + + +Install `Git`_, `tox`_ and the :ref:`build prerequisites`. + +.. _tox: https://tox.readthedocs.io/en/latest/ + +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:: + + $ 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:: + + $ source __venv__/bin/activate + +Install python-ldap to it in `editable mode`_:: + + (__venv__)$ python -m pip install -e . + +This way, importing a Python module from python-ldap will directly +use the code from your source tree. +If you change C code, you will still need to recompile +(using the ``pip install`` command again). + +.. _editable mode: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs + +Change the code as desired. + + +To run tests, install and run `tox`_:: + + (__venv__)$ python -m pip install tox + (__venv__)$ tox --skip-missing-interpreters + +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 + +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 + SASL and TLS bindings compiled in. + + +When your change is ready, commit to Git, and submit a pull request on GitHub. +You can take a look at the :ref:`committer instructions` to see what we are looking +for in a pull request. + +If you don't want to open a GitHub account, please send patches as attachments +to the python-ldap mailing list. From c09142a4afe00645f6ebde7c680d3e2d682374ca Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 11 Dec 2017 14:33:35 +0100 Subject: [PATCH 092/369] Bump version to 3.0.0b2 --- 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 378700de..4325aac3 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.0b1' +__version__ = '3.0.0b2' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 9c051a19..262b6b02 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0b1' +__version__ = '3.0.0b2' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index fe148950..adb57594 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.0.0b1' +__version__ = '3.0.0b2' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index d7d19fdc..a2a875f9 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.0b1' +__version__ = '3.0.0b2' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import skip_unless_ci, requires_sasl, requires_tls From 673957dfcbf3769778c184db296ad601e669c70d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 11 Dec 2017 14:32:19 +0100 Subject: [PATCH 093/369] Update CHANGES for 3.0.0b2 --- CHANGES | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGES b/CHANGES index dc9aab25..3241046b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,37 @@ +---------------------------------------------------------------- +Released 3.0.0b2 2017-12-11 + +Changes since 3.0.0b1: + +The module `ldap.async` is renamed to `ldap.asyncsearch`, due to +`async` becoming a keyword in Python 3.7. +The old module name is deprecated, but will be available as long +as Python 3.6 is supported. + +Lib/ +* Use custom ldap.LDAPBytesWarning class +* Rename ldap.async to ldap.asyncsearch + +Modules/ +* Support None for set_option(OPT_TIMEOUT) and OPT_NETWORK_TIMEOUT +* Fix error reporting of LDAPObject.set_option() +* Change memory handling in attrs_from_List() + +Test/ +* Remove workaround for OpenLDAP NSS issue + +Demo/ +* Use uniform shebang in all demos + +Doc/ +* Provide build deps for Alpine and CentOS +* Move sample workflow out of the main Contributing guide + +Infrastructure: +* Add valgrind target to check for memory leaks +* Minimal configuration for pytest + + ---------------------------------------------------------------- Released 3.0.0b1 2017-12-04 From bb802b60d93c988b570541a980bf66b7e6b342d4 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 11 Dec 2017 21:38:55 -0800 Subject: [PATCH 094/369] Prefer literals throughout project - Use a dict literal instead of calling dict(). - Use a list literal instead of calling list(). - Use a set literal instead of calling set(). - Use a tuple literal instead of calling tuple(). - Use dict comprehension These syntax expressions are available by all supported Python versions. Literals are always faster than calling the constructor. More idiomatic with wider Python community. --- Demo/pyasn1/syncrepl.py | 6 +++--- Lib/ldap/asyncsearch.py | 10 +++++----- Lib/ldap/controls/deref.py | 6 +++--- Lib/ldap/controls/psearch.py | 2 +- Lib/ldap/ldapobject.py | 16 ++++++++-------- Lib/ldap/schema/models.py | 4 ++-- Lib/ldap/syncrepl.py | 2 +- Lib/ldif.py | 2 +- Tests/t_ldap_syncrepl.py | 2 +- setup.py | 14 +++++++------- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 36147920..61d63ad9 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -45,9 +45,9 @@ def __init__(self, db_path, *args, **kwargs): if db_path: self.__data = shelve.open(db_path, 'c') else: - self.__data = dict() + self.__data = {} # We need this for later internal use - self.__presentUUIDs = dict() + self.__presentUUIDs = {} def close_db(self): # Close the data store properly to avoid corruption @@ -64,7 +64,7 @@ def syncrepl_entry(self, dn, attributes, uuid): logger.debug('dn=%r attributes=%r uuid=%r', dn, attributes, uuid) # First we determine the type of change we have here # (and store away the previous data for later if needed) - previous_attributes = dict() + previous_attributes = {} if uuid in self.__data: change_type = 'modify' previous_attributes = self.__data[uuid] diff --git a/Lib/ldap/asyncsearch.py b/Lib/ldap/asyncsearch.py index 97b3fff8..80b5b2a9 100644 --- a/Lib/ldap/asyncsearch.py +++ b/Lib/ldap/asyncsearch.py @@ -8,16 +8,16 @@ from ldap import __version__ -SEARCH_RESULT_TYPES = set([ +SEARCH_RESULT_TYPES = { ldap.RES_SEARCH_ENTRY, ldap.RES_SEARCH_RESULT, ldap.RES_SEARCH_REFERENCE, -]) +} -ENTRY_RESULT_TYPES = set([ +ENTRY_RESULT_TYPES = { ldap.RES_SEARCH_ENTRY, ldap.RES_SEARCH_RESULT, -]) +} class WrongResultType(Exception): @@ -207,7 +207,7 @@ class IndexedDict(Dict): def __init__(self,l,indexed_attrs=None): Dict.__init__(self,l) - self.indexed_attrs = indexed_attrs or tuple() + self.indexed_attrs = indexed_attrs or () self.index = {}.fromkeys(self.indexed_attrs,{}) def _processSingleResult(self,resultType,resultItem): diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index 762ab855..b927f1a5 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -107,10 +107,10 @@ def decodeControlValue(self,encodedControlValue): self.derefRes = {} for deref_res in decodedValue: deref_attr,deref_val,deref_vals = deref_res[0],deref_res[1],deref_res[2] - partial_attrs_dict = dict([ - (str(tv[0]),map(str,tv[1])) + partial_attrs_dict = { + str(tv[0]): map(str,tv[1]) for tv in deref_vals or [] - ]) + } try: self.derefRes[str(deref_attr)].append((str(deref_val),partial_attrs_dict)) except KeyError: diff --git a/Lib/ldap/controls/psearch.py b/Lib/ldap/controls/psearch.py index 74e40a33..002a88e9 100644 --- a/Lib/ldap/controls/psearch.py +++ b/Lib/ldap/controls/psearch.py @@ -32,7 +32,7 @@ 'modify':4, 'modDN':8, } -CHANGE_TYPES_STR = dict([(v,k) for k,v in CHANGE_TYPES_INT.items()]) +CHANGE_TYPES_STR = {v: k for k,v in CHANGE_TYPES_INT.items()} class PersistentSearchControl(RequestControl): diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index f37ef24a..daa4fba8 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -227,10 +227,10 @@ def _bytesify_result_value(self, result_value): return result_value if hasattr(result_value, 'items'): # It's a attribute_name: [values] dict - return dict( - (self._maybe_rebytesify_text(key), value) + return { + self._maybe_rebytesify_text(key): value for (key, value) in result_value.items() - ) + } elif isinstance(result_value, bytes): return result_value else: @@ -991,13 +991,13 @@ class ReconnectLDAPObject(SimpleLDAPObject): application. """ - __transient_attrs__ = set([ + __transient_attrs__ = { '_l', '_ldap_object_lock', '_trace_file', '_reconnect_lock', '_last_bind', - ]) + } def __init__( self,uri, @@ -1025,11 +1025,11 @@ def __init__( def __getstate__(self): """return data representation for pickled object""" - state = dict([ - (k,v) + state = { + k: v for k,v in self.__dict__.items() if k not in self.__transient_attrs__ - ]) + } state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2] return state diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index c0391b4c..84054f87 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -11,7 +11,7 @@ from ldap.schema.tokenizer import split_tokens,extract_tokens -NOT_HUMAN_READABLE_LDAP_SYNTAXES = set([ +NOT_HUMAN_READABLE_LDAP_SYNTAXES = { '1.3.6.1.4.1.1466.115.121.1.4', # Audio '1.3.6.1.4.1.1466.115.121.1.5', # Binary '1.3.6.1.4.1.1466.115.121.1.8', # Certificate @@ -21,7 +21,7 @@ '1.3.6.1.4.1.1466.115.121.1.28', # JPEG '1.3.6.1.4.1.1466.115.121.1.40', # Octet String '1.3.6.1.4.1.1466.115.121.1.49', # Supported Algorithm -]) +} class SchemaElement: diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 35d4c452..0de5cec4 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -323,7 +323,7 @@ def __init__(self, encodedMessage): self.newcookie = str(comp) return - val = dict() + val = {} cookie = comp.getComponentByName('cookie') if cookie.hasValue(): diff --git a/Lib/ldif.py b/Lib/ldif.py index adb57594..0e78fb1e 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -69,7 +69,7 @@ def list_dict(l): """ return a dictionary with all items of l being the keys of the dictionary """ - return dict([(i,None) for i in l]) + return {i: None for i in l} class LDIFWriter: diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 831c0631..faa6856c 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -247,7 +247,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): self.present.extend(uuids) elif (uuids is None) and (refreshDeletes is False): - deleted_uuids = list() + deleted_uuids = [] for uuid in self.uuid_dn.keys(): if uuid not in self.present: deleted_uuids.append(uuid) diff --git a/setup.py b/setup.py index 3d7d338b..0afa5559 100644 --- a/setup.py +++ b/setup.py @@ -71,18 +71,18 @@ class OpenLDAP2: # setup() in a fashion that doesn't break compatibility to # distutils. This still allows 'normal' builds where either # Python > 2.3.5 or setuptools (or both ;o) are not available. -kwargs = dict() +kwargs = {} if has_setuptools: - kwargs = dict( - include_package_data = True, - install_requires = [ + kwargs = { + 'include_package_data': True, + 'install_requires': [ 'setuptools', 'pyasn1 >= 0.3.7', 'pyasn1_modules >= 0.1.5', ], - zip_safe = False, - python_requires = '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', - ) + 'zip_safe': False, + 'python_requires': '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', + } setup( #-- Package description From 4ad46bb0c666d1b04222c1c5f55e1566c8763d60 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 12 Dec 2017 01:29:47 -0800 Subject: [PATCH 095/369] Always use https with all readthedocs links https://github.com/python-ldap/python-ldap/pull/119 --- CONTRIBUTING.rst | 2 +- Doc/bytes_mode.rst | 2 +- Doc/contributing.rst | 2 +- Doc/index.rst | 2 +- INSTALL | 2 +- Tests/t_ldap_syncrepl.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0866c8ee..7fd68fc9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -4,7 +4,7 @@ If you wish to help, detailed instructions are in `Doc/contributing.rst`_, and in `online documentation`_. .. _Doc/contributing.rst: Doc/contributing.rst -.. _online documentation: http://python-ldap.readthedocs.io/en/latest/contributing.html +.. _online documentation: https://python-ldap.readthedocs.io/en/latest/contributing.html Open-source veretans should find no surprises there. diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index c775b273..bbd83db0 100644 --- a/Doc/bytes_mode.rst +++ b/Doc/bytes_mode.rst @@ -85,7 +85,7 @@ General instructions for this are provided `in Python documentation`_ and in the `Conservative porting guide`_. .. _in Python documentation: https://docs.python.org/3/howto/pyporting.html -.. _Conservative porting guide: http://portingguide.readthedocs.io/en/latest/ +.. _Conservative porting guide: https://portingguide.readthedocs.io/en/latest/ When porting from python-ldap 2.x, users are advised to update their code diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 70856c48..c45e9c94 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -79,7 +79,7 @@ Or, if you prefer to avoid closed-source services: * Run tests with `tox`_; ignore Python interpreters you don't have locally. * Read the documentation directly at `Read the Docs`_. -.. _Read the Docs: http://python-ldap.readthedocs.io/ +.. _Read the Docs: https://python-ldap.readthedocs.io/ If you're new to some aspect of the project, you're welcome to use (or adapt) our :ref:`sample workflow `. diff --git a/Doc/index.rst b/Doc/index.rst index ef652d96..fcf90059 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -57,7 +57,7 @@ versions for offline use, using the sidebar on the right. Documentation for some older versions is available for download at the `GitHub release page`_. -.. _Read the Docs: http://python-ldap.readthedocs.io/en/latest/ +.. _Read the Docs: https://python-ldap.readthedocs.io/en/latest/ .. _GitHub release page: https://github.com/python-ldap/python-ldap/releases diff --git a/INSTALL b/INSTALL index 0475a2fc..b9b13d2d 100644 --- a/INSTALL +++ b/INSTALL @@ -6,4 +6,4 @@ Quick build instructions: Detailed instructions are in Doc/installing.rst, or online at: - http://python-ldap.readthedocs.io/en/latest/installing.html + https://python-ldap.readthedocs.io/en/latest/installing.html diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index faa6856c..539ff3a0 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -241,7 +241,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): """ The 'present' message from the LDAP server is the most complicated part of the refresh phase. Suggest looking here for more info: - http://syncrepl-client.readthedocs.io/en/latest/client.html + https://syncrepl-client.readthedocs.io/en/latest/client.html """ if (uuids is not None) and (refreshDeletes is False): self.present.extend(uuids) From e80cc61139183951ed2b59317bebf14a7ec736e7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 12 Dec 2017 19:25:18 -0800 Subject: [PATCH 096/369] Fix remaining links that can be https --- Demo/resiter.py | 2 +- Doc/installing.rst | 2 +- Lib/ldap/controls/openldap.py | 2 +- Tests/t_ldap_sasl.py | 2 +- Tests/t_ldap_syncrepl.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Demo/resiter.py b/Demo/resiter.py index d6796baf..96e9c90d 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -2,7 +2,7 @@ Demo for using ldap.resiter.ResultProcessor written by Michael Stroeder -See http://www.python-ldap.org for details. +See https://www.python-ldap.org for details. """ from __future__ import print_function diff --git a/Doc/installing.rst b/Doc/installing.rst index 01d39c7f..efbab704 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -55,7 +55,7 @@ hit a problem already fixed in recent releases. --------------------------------------------- Ships with python-ldap and there's an additional -`download repository `_ +`download repository `_ which contains builds of latest releases (see also `OBS package `_). diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py index 7108c632..5da2dd3f 100644 --- a/Lib/ldap/controls/openldap.py +++ b/Lib/ldap/controls/openldap.py @@ -23,7 +23,7 @@ class SearchNoOpControl(ValueLessRequestControl,ResponseControl): No-op control attached to search operations implementing sort of a count operation - see http://www.openldap.org/its/index.cgi?findid=6598 + see https://www.openldap.org/its/index.cgi?findid=6598 """ controlType = '1.3.6.1.4.1.4203.666.5.18' diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 7f472cc6..af6ed51a 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -2,7 +2,7 @@ """ Automatic tests for python-ldap's module ldap.sasl -See http://www.python-ldap.org/ for details. +See https://www.python-ldap.org/ for details. """ import os import pwd diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 539ff3a0..0581e502 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -2,7 +2,7 @@ """ Automatic tests for python-ldap's module ldap.syncrepl -See http://www.python-ldap.org/ for details. +See https://www.python-ldap.org/ for details. """ From f94619f75856bc8b6a1cf7f48adee47ec82246a9 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 13 Dec 2017 02:50:19 -0800 Subject: [PATCH 097/369] Drop distutils workaround and simplify setup.py Modern Python installations contain setuptools. It is reasonable to expect it to exist. Simplifies setup.py and removes the large comment explaining the workaround. https://github.com/python-ldap/python-ldap/pull/123 --- setup.py | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/setup.py b/setup.py index 0afa5559..2f6bd895 100644 --- a/setup.py +++ b/setup.py @@ -5,13 +5,7 @@ """ import sys,os - -has_setuptools = False -try: - from setuptools import setup, Extension - has_setuptools = True -except ImportError: - from distutils.core import setup, Extension +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.') @@ -66,24 +60,6 @@ class OpenLDAP2: #-- Let distutils/setuptools do the rest name = 'python-ldap' -# Python 2.3.6+ and setuptools are needed to build eggs, so -# let's handle setuptools' additional keyword arguments to -# setup() in a fashion that doesn't break compatibility to -# distutils. This still allows 'normal' builds where either -# Python > 2.3.5 or setuptools (or both ;o) are not available. -kwargs = {} -if has_setuptools: - kwargs = { - 'include_package_data': True, - 'install_requires': [ - 'setuptools', - 'pyasn1 >= 0.3.7', - 'pyasn1_modules >= 0.1.5', - ], - 'zip_safe': False, - 'python_requires': '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', - } - setup( #-- Package description name = name, @@ -186,6 +162,12 @@ class OpenLDAP2: ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, + include_package_data=True, + install_requires=[ + 'pyasn1 >= 0.3.7', + 'pyasn1_modules >= 0.1.5', + ], + zip_safe=False, + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', test_suite = 'Tests', - **kwargs ) From 5b8b265f9bcf9fbe1003190cdf19090a14bc7e64 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 13 Dec 2017 11:53:00 +0100 Subject: [PATCH 098/369] Run Travis tests with PyPy To my surprose, python-ldap works just fine with PyPy2. For Python 3, PyPy-5.9 (Python 3.5) is required. Fedora's and Ubuntu's PyPy 5.5 is too old (Python 3.3). https://github.com/python-ldap/python-ldap/pull/122 Signed-off-by: Christian Heimes --- .travis.yml | 5 +++++ tox.ini | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/.travis.yml b/.travis.yml index 00eb6292..22559b7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,9 @@ matrix: env: - TOXENV=py36 - WITH_GCOV=1 + - python: pypy + env: + - TOXENV=pypy - python: 3.7-dev env: - TOXENV=py37 @@ -52,6 +55,8 @@ matrix: - env: - TOXENV=py37 - WITH_GCOV=1 + - env: + - TOXENV=pypy env: global: diff --git a/tox.ini b/tox.ini index 6d984c0f..95fee06f 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,17 @@ passenv = {[testenv]passenv} setenv = {[testenv:py2-nosasltls]setenv} commands = {[testenv:py2-nosasltls]commands} +[testenv:pypy] +# PyPy doesn't have working setup.py test +deps = pytest +commands = {envpython} -m pytest + +[testenv:pypy35] +# PyPy-5.9.0 +basepython = pypy3.5 +deps = {[testenv:pypy]deps} +commands = {[testenv:pypy]commands} + [testenv:coverage-report] deps = coverage skip_install = true From 8f2cded7ffd3ef68f3f96d8432757b60c62b1607 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 13 Dec 2017 11:53:52 +0100 Subject: [PATCH 099/369] Run clean --all in all tox targets (#121) The second full tox run fails because 'py27' target is picking up C object files from 'py2-nosasltls'. Each target needs to clean up and start from scratch. https://github.com/python-ldap/python-ldap/pull/121 Signed-off-by: Christian Heimes --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 95fee06f..fcfc628b 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,9 @@ passenv = WITH_GCOV commands = {envpython} -bb -Werror \ "-Wignore:the imp module is deprecated:DeprecationWarning" \ "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ - -m coverage run --parallel setup.py test + -m coverage run --parallel setup.py \ + clean --all \ + test [testenv:py27] # No warnings with Python 2.7 From 9dff16169ffa82414588e0b64b3bdd059ce59abc Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 13 Dec 2017 04:41:24 -0800 Subject: [PATCH 100/369] Doc deprecated functions for removal in 3.1 - ldap.open() - ldap.init() - ldif.CreateLDIF() - ldif.ParseLDIF() --- Doc/reference/ldap.rst | 15 ++++++++++++++- Doc/reference/ldif.rst | 12 ++++++++++++ Lib/ldap/functions.py | 7 ++++++- Lib/ldif.py | 17 +++++++++++++++-- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 277520e8..20994143 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -73,8 +73,21 @@ This module defines the following functions: string containing solely the host name. *port* is an integer specifying the port where the LDAP server is listening (default is 389). - Note: Using this function is deprecated. + .. 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 diff --git a/Doc/reference/ldif.rst b/Doc/reference/ldif.rst index 1a9eabc3..64afbb02 100644 --- a/Doc/reference/ldif.rst +++ b/Doc/reference/ldif.rst @@ -22,8 +22,20 @@ Functions .. autofunction:: ldif.CreateLDIF + .. deprecated:: 3.0 + + ``ldif.CreateLDIF()`` is deprecated. It will be removed in version 3.1. + Use :meth:`ldif.LDIFWriter.unparse` with a file or ``io.StringIO`` + instead. + .. autofunction:: ldif.ParseLDIF + .. deprecated:: 3.0 + + ``ldif.ParseLDIF()`` is deprecated. It will be removed in version 3.1. + Use the ``all_records`` attribute of the returned value of + ``ldif.LDIFRecordList.parse()`` instead. + Classes ^^^^^^^ diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 1588221f..3dfeee48 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -101,7 +101,12 @@ def open(host,port=389,trace_level=0,trace_file=sys.stdout,trace_stack_limit=Non Whether to enable "bytes_mode" for backwards compatibility under Py2. """ import warnings - warnings.warn('ldap.open() is deprecated! Use ldap.initialize() instead.', DeprecationWarning,2) + 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 diff --git a/Lib/ldif.py b/Lib/ldif.py index 0e78fb1e..16fb8c37 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -23,6 +23,7 @@ import re from base64 import b64encode, b64decode from io import StringIO +import warnings from ldap.compat import urlparse, urlopen @@ -209,7 +210,7 @@ def unparse(self,dn,record): def CreateLDIF(dn,record,base64_attrs=None,cols=76): """ Create LDIF single formatted record including trailing empty line. - This is a compatibility function. Use is deprecated! + This is a compatibility function. dn string-representation of distinguished name @@ -222,6 +223,12 @@ def CreateLDIF(dn,record,base64_attrs=None,cols=76): Specifies how many columns a line may have before it's folded into many lines. """ + warnings.warn( + 'ldif.CreateLDIF() is deprecated. Use LDIFWriter.unparse() instead. It ' + 'will be removed in python-ldap 3.1', + category=DeprecationWarning, + stacklevel=2, + ) f = StringIO() ldif_writer = LDIFWriter(f,base64_attrs,cols,'\n') ldif_writer.unparse(dn,record) @@ -633,8 +640,14 @@ def handle(self,dn,entry): def ParseLDIF(f,ignore_attrs=None,maxentries=0): """ Parse LDIF records read from file. - This is a compatibility function. Use is deprecated! + This is a compatibility function. """ + warnings.warn( + 'ldif.ParseLDIF() is deprecated. Use LDIFRecordList.parse() instead. It ' + 'will be removed in python-ldap 3.1', + category=DeprecationWarning, + stacklevel=2, + ) ldif_parser = LDIFRecordList( f,ignored_attr_types=ignore_attrs,max_entries=maxentries,process_url_schemes=0 ) From cf24a545de3e70740db76cde0c94384ddb6348c7 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 13 Dec 2017 16:05:46 +0100 Subject: [PATCH 101/369] Add pragma: no cover to avoid spurious cov changes "pragma: no cover" was added to two helper functions in slapdtest to avoid spurious changes in coverage whenever slapd has a hick up. https://github.com/python-ldap/python-ldap/pull/129 Closes: https://github.com/python-ldap/python-ldap/issues/127 Signed-off-by: Christian Heimes --- Lib/slapdtest/_slapdtest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index f3f096c3..ef627ef6 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -344,7 +344,9 @@ 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 - while 1: + # no cover to avoid spurious coverage changes, see + # https://github.com/python-ldap/python-ldap/issues/127 + while 1: # pragma: no cover if self._proc.poll() is not None: self._stopped() raise RuntimeError("slapd exited before opening port") @@ -430,7 +432,9 @@ def _cli_auth_args(self): ] return authc_args - def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None): + # no cover to avoid spurious coverage changes + def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, + stdin_data=None): # pragma: no-cover args = [ ldapcommand, '-H', ldap_uri or self.ldapi_uri, From a3723bc588efe808cb9b9b233499a5c151c490ef Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 6 Dec 2017 14:49:08 +0100 Subject: [PATCH 102/369] When raising LDAPBytesWarning, walk the stack to determine stacklevel Closes: https://github.com/python-ldap/python-ldap/issues/108 Signed-off-by: Christian Heimes Some simplification by: Petr Viktorin --- Lib/ldap/ldapobject.py | 21 +++++++++++++- Modules/ldapmodule.c | 7 +++++ Tests/t_ldapobject.py | 63 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index daa4fba8..1929a92f 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -39,6 +39,9 @@ text_type = str +# See SimpleLDAPObject._bytesify_input +_LDAP_WARN_SKIP_FRAME = True + class LDAPBytesWarning(BytesWarning): """python-ldap bytes mode warning """ @@ -126,11 +129,27 @@ def _bytesify_input(self, value): if self.bytes_mode_hardfail: raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,)) else: + # Raise LDAPBytesWarning. + # Call stacks with _bytesify_input tend to be complicated, so + # getting a useful stacklevel is tricky. + # We walk stack frames, ignoring all functions in this file + # and in the _ldap extension, based on a marker in globals(). + stacklevel = 0 + try: + getframe = sys._getframe + except AttributeError: + pass + else: + frame = sys._getframe(stacklevel) + # walk up the stacks until we leave the file + while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'): + stacklevel += 1 + frame = frame.f_back warnings.warn( "Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit " "option for bytes_mode on your LDAP connection" % (value,), LDAPBytesWarning, - stacklevel=6, + stacklevel=stacklevel+1, ) return value.encode('utf-8') else: diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 18e0696d..d5edd821 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -72,6 +72,13 @@ PyObject* init_ldap_module(void) LDAPinit_functions(d); LDAPinit_control(d); + /* Marker for LDAPBytesWarning stack walking + * see SimpleLDAPObject._bytesify_input 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_ldapobject.py b/Tests/t_ldapobject.py index 835512b0..f833fecf 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -16,8 +16,11 @@ PY2 = False text_type = str +import contextlib +import linecache import os import unittest +import warnings import pickle import warnings from slapdtest import SlapdTestCase, requires_sasl @@ -329,7 +332,7 @@ def test_ldapbyteswarning(self): self.assertIsInstance(self.server.suffix, text_type) with warnings.catch_warnings(record=True) as w: warnings.resetwarnings() - warnings.simplefilter('default') + warnings.simplefilter('always', ldap.LDAPBytesWarning) conn = self._get_bytes_ldapobject(explicit=False) result = conn.search_s( self.server.suffix, @@ -350,6 +353,64 @@ def test_ldapbyteswarning(self): "LDAP connection" % self.server.suffix ) + @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 _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) + + def _normalize(filename): + # Python 2 likes to report the ".pyc" file in warnings, + # tracebacks or __file__. + # Use the corresponding ".py" in that case. + if filename.endswith('.pyc'): + return filename[:-1] + return filename + + self.assertIs(w[0].category, ldap.LDAPBytesWarning) + self.assertIn( + u"Received non-bytes value u'(cn=Foo*)'", + text_type(w[0].message) + ) + self.assertEqual(_normalize(w[1].filename), _normalize(__file__)) + self.assertEqual(_normalize(w[0].filename), _normalize(__file__)) + self.assertIn( + 'CORRECT LINE', + linecache.getline(w[0].filename, w[0].lineno) + ) + + self.assertIs(w[1].category, ldap.LDAPBytesWarning) + self.assertIn( + u"Received non-bytes value u'*'", + text_type(w[1].message) + ) + self.assertIn(_normalize(w[1].filename), _normalize(__file__)) + self.assertIn( + 'CORRECT LINE', + linecache.getline(w[1].filename, w[1].lineno) + ) + + @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') + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From db98910a3a3c3ece9a9f1e351ad1e4c957841b7e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 13 Dec 2017 15:51:02 +0100 Subject: [PATCH 103/369] Use stack walking for LDAPBytesWarning also in initialize() --- Lib/ldap/functions.py | 3 ++ Lib/ldap/ldapobject.py | 53 +++++++++++++++++----------------- Modules/ldapmodule.c | 2 +- Tests/t_ldapobject.py | 65 +++++++++++++++++++++++------------------- 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 3dfeee48..8c7580e8 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -27,6 +27,9 @@ # 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 1929a92f..4aa61dbb 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -46,6 +46,26 @@ class LDAPBytesWarning(BytesWarning): """python-ldap bytes mode warning """ +def _raise_byteswarning(message): + """Raise LDAPBytesWarning + """ + + # 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) + class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): """ @@ -90,13 +110,10 @@ def __init__( # By default, raise a TypeError when receiving invalid args self.bytes_mode_hardfail = True if bytes_mode is None and PY2: - warnings.warn( + _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.", - LDAPBytesWarning, - stacklevel=2, - ) + "Please call initialize(..., bytes_mode=False) explicitly.") bytes_mode = True # Disable hard failure when running in backwards compatibility mode. self.bytes_mode_hardfail = False @@ -129,28 +146,10 @@ def _bytesify_input(self, value): if self.bytes_mode_hardfail: raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,)) else: - # Raise LDAPBytesWarning. - # Call stacks with _bytesify_input tend to be complicated, so - # getting a useful stacklevel is tricky. - # We walk stack frames, ignoring all functions in this file - # and in the _ldap extension, based on a marker in globals(). - stacklevel = 0 - try: - getframe = sys._getframe - except AttributeError: - pass - else: - frame = sys._getframe(stacklevel) - # walk up the stacks until we leave the file - while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'): - stacklevel += 1 - frame = frame.f_back - warnings.warn( - "Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit " - "option for bytes_mode on your LDAP connection" % (value,), - LDAPBytesWarning, - stacklevel=stacklevel+1, - ) + _raise_byteswarning( + "Received non-bytes value %r with default (disabled) bytes mode; " + "please choose an explicit " + "option for bytes_mode on your LDAP connection" % (value,)) return value.encode('utf-8') else: if not isinstance(value, text_type): diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index d5edd821..f37c1b8d 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -73,7 +73,7 @@ PyObject* init_ldap_module(void) LDAPinit_control(d); /* Marker for LDAPBytesWarning stack walking - * see SimpleLDAPObject._bytesify_input in ldapobject.py + * See _raise_byteswarning in ldapobject.py */ if (PyModule_AddIntConstant(m, "_LDAP_WARN_SKIP_FRAME", 1) != 0) { return NULL; diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index f833fecf..e417fc1c 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -361,6 +361,25 @@ def catch_byteswarnings(self, *args, **kwargs): 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)) + + def _normalize(filename): + # Python 2 likes to report the ".pyc" file in warnings, + # tracebacks or __file__. + # Use the corresponding ".py" in that case. + if filename.endswith('.pyc'): + return filename[:-1] + return filename + + # Assert warning points to a line marked CORRECT LINE in this file + self.assertEquals(_normalize(warning.filename), _normalize(__file__)) + self.assertIn( + 'CORRECT LINE', + 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) @@ -374,36 +393,11 @@ def _test_byteswarning_level_search(self, methodname): self.assertEqual(len(w), 2, w) - def _normalize(filename): - # Python 2 likes to report the ".pyc" file in warnings, - # tracebacks or __file__. - # Use the corresponding ".py" in that case. - if filename.endswith('.pyc'): - return filename[:-1] - return filename - - self.assertIs(w[0].category, ldap.LDAPBytesWarning) - self.assertIn( - u"Received non-bytes value u'(cn=Foo*)'", - text_type(w[0].message) - ) - self.assertEqual(_normalize(w[1].filename), _normalize(__file__)) - self.assertEqual(_normalize(w[0].filename), _normalize(__file__)) - self.assertIn( - 'CORRECT LINE', - linecache.getline(w[0].filename, w[0].lineno) - ) + self._check_byteswarning( + w[0], u"Received non-bytes value u'(cn=Foo*)'") - self.assertIs(w[1].category, ldap.LDAPBytesWarning) - self.assertIn( - u"Received non-bytes value u'*'", - text_type(w[1].message) - ) - self.assertIn(_normalize(w[1].filename), _normalize(__file__)) - self.assertIn( - 'CORRECT LINE', - linecache.getline(w[1].filename, w[1].lineno) - ) + self._check_byteswarning( + w[1], u"Received non-bytes value u'*'") @unittest.skipUnless(PY2, "no bytes_mode under Py3") def test_byteswarning_level_search(self): @@ -411,6 +405,19 @@ def test_byteswarning_level_search(self): 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.") + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From c1007fa93caadc5f354a21b5c089c781ea0fe47b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 13 Dec 2017 18:09:39 +0100 Subject: [PATCH 104/369] More / fixed pragma: no cover Signed-off-by: Christian Heimes --- Lib/ldap/constants.py | 2 +- Lib/slapdtest/_slapdtest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 4c109ac4..9eb5ceb9 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -366,7 +366,7 @@ class Str(Constant): ) -def print_header(): +def print_header(): # pragma: no cover """Print the C header file to standard output""" print('/*') diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index ef627ef6..4c7a9e45 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -434,7 +434,7 @@ def _cli_auth_args(self): # no cover to avoid spurious coverage changes def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, - stdin_data=None): # pragma: no-cover + stdin_data=None): # pragma: no cover args = [ ldapcommand, '-H', ldap_uri or self.ldapi_uri, From 5197e586541726ccddbfff3d653d4c64e295e25e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 15 Dec 2017 13:06:26 +0100 Subject: [PATCH 105/369] Add reproducer for NSS callback issue The new test case simply creates 10 connections and calls start_tls_s() after OPT_X_TLS_NEWCTX. https://github.com/python-ldap/python-ldap/pull/134 See: https://github.com/python-ldap/python-ldap/issues/60 Signed-off-by: Christian Heimes --- Tests/t_ldapobject.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index e417fc1c..62591d77 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -23,7 +23,7 @@ import warnings import pickle import warnings -from slapdtest import SlapdTestCase, requires_sasl +from slapdtest import SlapdTestCase, requires_sasl, requires_tls # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -418,6 +418,20 @@ def test_byteswarning_initialize(self): 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 + # after nss_Shutdown is called + # https://github.com/python-ldap/python-ldap/issues/60 + # https://bugzilla.redhat.com/show_bug.cgi?id=1520990 + for _ in range(10): + l = self.ldap_object_class(self.server.ldap_uri) + l.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + l.set_option(ldap.OPT_X_TLS_NEWCTX, 0) + l.start_tls_s() + l.simple_bind_s(self.server.root_dn, self.server.root_pw) + self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From be5a3445ac8bf8460be858160e27ea9ca11b641e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 18 Dec 2017 04:18:41 -0800 Subject: [PATCH 106/369] Remove unnecessary bool() call Inequalities already return return a boolean. Coercing to a bool again is a noop. https://github.com/python-ldap/python-ldap/pull/135 --- Lib/ldap/ldapobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 4aa61dbb..bc78fb7f 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -32,7 +32,7 @@ from ldap import LDAPError -PY2 = bool(sys.version_info[0] <= 2) +PY2 = sys.version_info[0] <= 2 if PY2: text_type = unicode else: From 0625e19f16a434604f9bb50b76baed2ca8889ffc Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 18 Dec 2017 04:23:11 -0800 Subject: [PATCH 107/369] Call str.lower() as a method More idiomatic Python. https://github.com/python-ldap/python-ldap/pull/139 --- Lib/ldap/modlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index a853500d..e1dfe446 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -51,9 +51,9 @@ def modifyModlist( modlist = [] attrtype_lower_map = {} for a in old_entry.keys(): - attrtype_lower_map[str.lower(a)]=a + attrtype_lower_map[a.lower()]=a for attrtype in new_entry.keys(): - attrtype_lower = str.lower(attrtype) + attrtype_lower = attrtype.lower() if attrtype_lower in ignore_attr_types: # This attribute type is ignored continue From ab930631760d61da34d0df7ba8eb5c79ebaaffe4 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 18 Dec 2017 05:14:44 -0800 Subject: [PATCH 108/369] Remove override of UserDict.get() to avoid reimplementation Reimplements the parent class implementation. Can simplify classes by removing it. https://github.com/python-ldap/python-ldap/pull/136 --- Lib/ldap/cidict.py | 6 ------ Lib/ldap/schema/models.py | 6 ------ 2 files changed, 12 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index fdfba4b5..f7e8d398 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -44,12 +44,6 @@ def has_key(self,key): def __contains__(self,key): return IterableUserDict.__contains__(self, key.lower()) - def get(self,key,failobj=None): - try: - return self[key] - except KeyError: - return failobj - def keys(self): return self._keys.values() diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 84054f87..c1362a95 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -679,12 +679,6 @@ def has_key(self,nameoroid): k = self._at2key(nameoroid) return k in self.data - def get(self,nameoroid,failobj): - try: - return self[nameoroid] - except KeyError: - return failobj - def keys(self): return self._keytuple2attrtype.values() From aa0a07bf443f673c3a3d3d57e8e9df7f9af30e51 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 18 Dec 2017 08:50:35 +0100 Subject: [PATCH 109/369] Make LDAPI optional LDAPI is LDAP over Unix Domain Socket, also referred as LDAP IPC. AF_UNIX is only supported on POSIX compatible operating systems. Don't bind to LDAPI socket and skip LDAPI specific tests, e.g. SASL EXTERNAL with SO_PEERCRED. Signed-off-by: Christian Heimes --- Lib/slapdtest/__init__.py | 3 ++- Lib/slapdtest/_slapdtest.py | 47 ++++++++++++++++++++++++++------- Tests/t_ldap_sasl.py | 5 ++-- Tests/t_ldap_schema_subentry.py | 3 ++- Tests/t_ldapobject.py | 14 +++++----- tox.ini | 4 +-- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index a2a875f9..306eaf7f 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -8,4 +8,5 @@ __version__ = '3.0.0b2' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler -from slapdtest._slapdtest import skip_unless_ci, requires_sasl, requires_tls +from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls +from slapdtest._slapdtest import skip_unless_ci diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 4c7a9e45..09ec1546 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -56,6 +56,12 @@ LOCALHOST = '127.0.0.1' +CI_DISABLED = set(os.environ.get('CI_DISABLED', '').split(':')) +if 'LDAPI' in CI_DISABLED: + HAVE_LDAPI = False +else: + HAVE_LDAPI = hasattr(socket, 'AF_UNIX') + def identity(test_item): """Identity decorator @@ -69,7 +75,7 @@ def skip_unless_ci(reason, feature=None): """ if not os.environ.get('CI', False): return unittest.skip(reason) - elif feature in os.environ.get('CI_DISABLED', '').split(':'): + elif feature in CI_DISABLED: return unittest.skip(reason) else: # Don't skip on Travis @@ -95,6 +101,14 @@ def requires_sasl(): return identity +def requires_ldapi(): + if not HAVE_LDAPI: + return skip_unless_ci( + "test needs ldapi support (AF_UNIX)", feature='LDAPI') + else: + return identity + + def combined_logger( log_name, log_level=logging.WARN, @@ -149,8 +163,6 @@ class SlapdObject(object): root_dn = 'cn=%s,%s' % (root_cn, suffix) root_pw = 'password' slapd_loglevel = 'stats stats2' - # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools - cli_sasl_external = True local_host = '127.0.0.1' testrunsubdirs = ( 'schema', @@ -192,8 +204,17 @@ def __init__(self): 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) - ldapi_path = os.path.join(self.testrundir, 'ldapi') - self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) + if HAVE_LDAPI: + ldapi_path = os.path.join(self.testrundir, 'ldapi') + 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 + else: + self.ldapi_uri = None + self.default_ldap_uri = self.ldap_uri + # Use simple bind via LDAP uri + self.cli_sasl_external = False # TLS certs self.cafile = os.path.join(HERE, 'certs/ca.pem') self.servercert = os.path.join(HERE, 'certs/server.pem') @@ -331,11 +352,14 @@ def _start_slapd(self): """ Spawns/forks the slapd process """ + urls = [self.ldap_uri] + if self.ldapi_uri: + urls.append(self.ldapi_uri) slapd_args = [ self.PATH_SLAPD, '-f', self._slapd_conf, '-F', self.testrundir, - '-h', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)), + '-h', '%s' % ' '.join(urls), ] if self._log.isEnabledFor(logging.DEBUG): slapd_args.extend(['-d', '-1']) @@ -346,18 +370,21 @@ def _start_slapd(self): # Waits until the LDAP server socket is open, or slapd crashed # no cover to avoid spurious coverage changes, see # https://github.com/python-ldap/python-ldap/issues/127 - while 1: # pragma: no cover + for _ in range(10): # 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.ldapi_uri) + self._log.debug( + "slapd connection check to %s", self.default_ldap_uri + ) self.ldapwhoami() except RuntimeError: pass else: return + raise RuntimeError("slapd did not start properly") def start(self): """ @@ -435,9 +462,11 @@ def _cli_auth_args(self): # no cover to avoid spurious coverage changes 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 or self.ldapi_uri, + '-H', ldap_uri, ] + self._cli_auth_args() + (extra_args or []) self._log.debug('Run command: %r', ' '.join(args)) proc = subprocess.Popen( diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index af6ed51a..828fe776 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -14,7 +14,8 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.sasl -from slapdtest import SlapdTestCase, requires_sasl, requires_tls +from slapdtest import SlapdTestCase +from slapdtest import requires_ldapi, requires_sasl, requires_tls LDIF = """ @@ -60,7 +61,7 @@ def setUpClass(cls): ) cls.server.ldapadd(ldif) - @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), "needs Unix socket") + @requires_ldapi() def test_external_ldapi(self): # EXTERNAL authentication with LDAPI (AF_UNIX) ldap_conn = self.ldap_object_class(self.server.ldapi_uri) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 3c07d35b..4e1e09b2 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -16,7 +16,7 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.schema from ldap.schema.models import ObjectClass -from slapdtest import SlapdTestCase +from slapdtest import SlapdTestCase, requires_ldapi HERE = os.path.abspath(os.path.dirname(__file__)) @@ -88,6 +88,7 @@ def test_urlfetch_ldap(self): dn, schema = ldap.schema.urlfetch(self.server.ldap_uri) self.assertSlapdSchema(dn, schema) + @requires_ldapi() def test_urlfetch_ldapi(self): dn, schema = ldap.schema.urlfetch(self.server.ldapi_uri) self.assertSlapdSchema(dn, schema) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 62591d77..50754687 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -22,8 +22,8 @@ import unittest import warnings import pickle -import warnings -from slapdtest import SlapdTestCase, requires_sasl, requires_tls +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' @@ -303,6 +303,7 @@ def test005_invalid_credentials(self): self.fail("expected INVALID_CREDENTIALS, got %r" % r) @requires_sasl() + @requires_ldapi() def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() @@ -441,6 +442,7 @@ class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): ldap_object_class = ReconnectLDAPObject @requires_sasl() + @requires_ldapi() def test101_reconnect_sasl_external(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() @@ -450,7 +452,7 @@ def test101_reconnect_sasl_external(self): self.assertEqual(l.whoami_s(), authz_id) def test102_reconnect_simple_bind(self): - l = self.ldap_object_class(self.server.ldapi_uri) + l = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) @@ -458,7 +460,7 @@ def test102_reconnect_simple_bind(self): self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) def test103_reconnect_get_state(self): - l1 = self.ldap_object_class(self.server.ldapi_uri) + l1 = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) @@ -477,7 +479,7 @@ def test103_reconnect_get_state(self): str('_start_tls'): 0, str('_trace_level'): 0, str('_trace_stack_limit'): 5, - str('_uri'): self.server.ldapi_uri, + str('_uri'): self.server.ldap_uri, str('bytes_mode'): l1.bytes_mode, str('bytes_mode_hardfail'): l1.bytes_mode_hardfail, str('timeout'): -1, @@ -485,7 +487,7 @@ def test103_reconnect_get_state(self): ) def test104_reconnect_restore(self): - l1 = self.ldap_object_class(self.server.ldapi_uri) + l1 = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) diff --git a/tox.ini b/tox.ini index fcfc628b..58e3cf68 100644 --- a/tox.ini +++ b/tox.ini @@ -33,8 +33,8 @@ basepython = python2 deps = {[testenv]deps} passenv = {[testenv]passenv} setenv = - CI_DISABLED=TLS:SASL -# rebuild without SASL and TLS + CI_DISABLED=LDAPI:SASL:TLS +# rebuild without SASL and TLS, run without LDAPI commands = {envpython} \ -m coverage run --parallel setup.py \ clean --all \ From 50e9c2e320c61d39202c8e23a14bc0b4eed13346 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 18 Dec 2017 08:51:06 +0100 Subject: [PATCH 110/369] Remove unused import of pwd module Signed-off-by: Christian Heimes --- Tests/t_ldap_sasl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 828fe776..d1044681 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 pwd import socket import unittest From f01b66e5ed26b2a80c6c1010f249241100354d50 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 18 Dec 2017 08:57:59 +0100 Subject: [PATCH 111/369] Use PATH to lookup test binaries Instead of hard-coded paths /usr/bin and /usr/sbin, the SlapdObject test helper now uses PATH env var to find test binaries. For slapd server binary, sbin paths are automatically added to lookup PATH in case they are missing. Signed-off-by: Christian Heimes --- Lib/slapdtest/_slapdtest.py | 77 +++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 09ec1546..ebc54412 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -9,6 +9,7 @@ import os import socket +import sys import time import subprocess import logging @@ -109,6 +110,46 @@ def requires_ldapi(): return identity +def _which(cmd): + """Specialized which command based on shutil.which() from Python 3.6. + + * simplified + * always adds /sbin directories to path + """ + + def _access_check(fn): + return (os.path.exists(fn) and os.access(fn, os.F_OK | os.X_OK) + and not os.path.isdir(fn)) + + # Path with directory part skips PATH lookup. + if os.path.dirname(cmd): + if _access_check(cmd): + return cmd + return None + + path = os.environ.get("PATH", os.defpath).split(os.pathsep) + + if sys.platform == 'win32': + if os.curdir not in path: + path.insert(0, os.curdir) + # include path extension (.exe) + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + files = [cmd + ext for ext in pathext] + else: + # always include sbin for slapd binary + for sbin in ['/usr/local/sbin', '/sbin', '/usr/sbin']: + if sbin not in path: + path.append(sbin) + files = [cmd] + + for directory in path: + for name in files: + name = os.path.join(directory, name) + if _access_check(name): + return name + return None + + def combined_logger( log_name, log_level=logging.WARN, @@ -172,8 +213,6 @@ class SlapdObject(object): ) TMPDIR = os.environ.get('TMP', os.getcwd()) - SBINDIR = os.environ.get('SBIN', '/usr/sbin') - BINDIR = os.environ.get('BIN', '/usr/bin') if 'SCHEMA' in os.environ: SCHEMADIR = os.environ['SCHEMA'] elif os.path.isdir("/etc/openldap/schema"): @@ -182,12 +221,14 @@ class SlapdObject(object): SCHEMADIR = "/etc/ldap/schema" else: SCHEMADIR = None - PATH_LDAPADD = os.path.join(BINDIR, 'ldapadd') - PATH_LDAPDELETE = os.path.join(BINDIR, 'ldapdelete') - PATH_LDAPMODIFY = os.path.join(BINDIR, 'ldapmodify') - PATH_LDAPWHOAMI = os.path.join(BINDIR, 'ldapwhoami') - PATH_SLAPD = os.environ.get('SLAPD', os.path.join(SBINDIR, 'slapd')) - PATH_SLAPTEST = os.path.join(SBINDIR, 'slaptest') + # _check_requirements turns paths into absolute paths + PATH_LDAPADD = 'ldapadd' + PATH_LDAPDELETE = 'ldapdelete' + PATH_LDAPMODIFY = 'ldapmodify' + PATH_LDAPWHOAMI = 'ldapwhoami' + # The following two binaries are usually in /usr/sbin. + PATH_SLAPD = os.environ.get('SLAPD', 'slapd') + PATH_SLAPTEST = 'slaptest' # time in secs to wait before trying to access slapd via LDAP (again) _start_sleep = 1.5 @@ -223,13 +264,19 @@ def __init__(self): self.clientkey = os.path.join(HERE, 'certs/client.key') def _check_requirements(self): - binaries = [ - self.PATH_LDAPADD, self.PATH_LDAPMODIFY, self.PATH_LDAPWHOAMI, - self.PATH_SLAPD, self.PATH_SLAPTEST + names = [ + "PATH_LDAPADD", "PATH_LDAPMODIFY", "PATH_LDAPDELETE", + "PATH_LDAPWHOAMI", "PATH_SLAPD", "PATH_SLAPTEST", ] - for binary in binaries: - if not os.path.isfile(binary): - raise ValueError('Binary {} is missing.'.format(binary)) + for name in names: + value = getattr(self, name) + binary = _which(value) + if binary is None: + raise ValueError( + "Command '{}' not found in PATH".format(value) + ) + else: + setattr(self, name, binary) if self.SCHEMADIR is None: raise ValueError('SCHEMADIR is None, ldap schemas are missing.') @@ -359,7 +406,7 @@ def _start_slapd(self): self.PATH_SLAPD, '-f', self._slapd_conf, '-F', self.testrundir, - '-h', '%s' % ' '.join(urls), + '-h', ' '.join(urls), ] if self._log.isEnabledFor(logging.DEBUG): slapd_args.extend(['-d', '-1']) From 02915b3f5bfe0f93d92cdeb26778d8c3efa56d25 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 19 Dec 2017 14:26:39 +0100 Subject: [PATCH 112/369] slapdtest: Set and check command paths in __init__ Make SlapdObject.PATH_* instance attributes, rather than class ones. Set them in __init__. Move checking them from start() to __init__(), so the check becomes simply error handling. Put a straight-up copy of Python's shutil.which() in ldap.compat -- it is a temporary backport, not a modified fork. --- Lib/ldap/compat.py | 70 +++++++++++++++++++++++ Lib/slapdtest/_slapdtest.py | 109 ++++++++++++++---------------------- 2 files changed, 113 insertions(+), 66 deletions(-) diff --git a/Lib/ldap/compat.py b/Lib/ldap/compat.py index de0e110c..cbfeef57 100644 --- a/Lib/ldap/compat.py +++ b/Lib/ldap/compat.py @@ -1,6 +1,7 @@ """Compatibility wrappers for Py2/Py3.""" import sys +import os if sys.version_info[0] < 3: from UserDict import UserDict, IterableUserDict @@ -41,3 +42,72 @@ def reraise(exc_type, exc_value, exc_traceback): """ # 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 diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index ebc54412..484eb54b 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -21,7 +21,7 @@ os.environ['LDAPNOINIT'] = '1' import ldap -from ldap.compat import quote_plus +from ldap.compat import quote_plus, which HERE = os.path.abspath(os.path.dirname(__file__)) @@ -109,46 +109,14 @@ def requires_ldapi(): else: return identity - -def _which(cmd): - """Specialized which command based on shutil.which() from Python 3.6. - - * simplified - * always adds /sbin directories to path - """ - - def _access_check(fn): - return (os.path.exists(fn) and os.access(fn, os.F_OK | os.X_OK) - and not os.path.isdir(fn)) - - # Path with directory part skips PATH lookup. - if os.path.dirname(cmd): - if _access_check(cmd): - return cmd - return None - - path = os.environ.get("PATH", os.defpath).split(os.pathsep) - - if sys.platform == 'win32': - if os.curdir not in path: - path.insert(0, os.curdir) - # include path extension (.exe) - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - files = [cmd + ext for ext in pathext] - else: - # always include sbin for slapd binary - for sbin in ['/usr/local/sbin', '/sbin', '/usr/sbin']: - if sbin not in path: - path.append(sbin) - files = [cmd] - - for directory in path: - for name in files: - name = os.path.join(directory, name) - if _access_check(name): - return name - return None - +def _add_sbin(path): + """Add /sbin and related directories to a command search path""" + directories = path.split(os.pathsep) + if sys.platform != 'win32': + for sbin in '/usr/local/sbin', '/sbin', '/usr/sbin': + if sbin not in directories: + directories.append(sbin) + return os.pathsep.join(directories) def combined_logger( log_name, @@ -221,14 +189,9 @@ class SlapdObject(object): SCHEMADIR = "/etc/ldap/schema" else: SCHEMADIR = None - # _check_requirements turns paths into absolute paths - PATH_LDAPADD = 'ldapadd' - PATH_LDAPDELETE = 'ldapdelete' - PATH_LDAPMODIFY = 'ldapmodify' - PATH_LDAPWHOAMI = 'ldapwhoami' - # The following two binaries are usually in /usr/sbin. - PATH_SLAPD = os.environ.get('SLAPD', 'slapd') - PATH_SLAPTEST = 'slaptest' + + 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 @@ -256,6 +219,12 @@ def __init__(self): self.default_ldap_uri = self.ldap_uri # Use simple bind via LDAP uri self.cli_sasl_external = False + + self._find_commands() + + if self.SCHEMADIR is None: + raise ValueError('SCHEMADIR is None, ldap schemas are missing.') + # TLS certs self.cafile = os.path.join(HERE, 'certs/ca.pem') self.servercert = os.path.join(HERE, 'certs/server.pem') @@ -263,22 +232,31 @@ def __init__(self): self.clientcert = os.path.join(HERE, 'certs/client.pem') self.clientkey = os.path.join(HERE, 'certs/client.key') - def _check_requirements(self): - names = [ - "PATH_LDAPADD", "PATH_LDAPMODIFY", "PATH_LDAPDELETE", - "PATH_LDAPWHOAMI", "PATH_SLAPD", "PATH_SLAPTEST", - ] - for name in names: - value = getattr(self, name) - binary = _which(value) - if binary is None: - raise ValueError( - "Command '{}' not found in PATH".format(value) - ) - else: - setattr(self, name, binary) - if self.SCHEMADIR is None: - raise ValueError('SCHEMADIR is None, ldap schemas are missing.') + def _find_commands(self): + self.PATH_LDAPADD = self._find_command('ldapadd') + self.PATH_LDAPDELETE = self._find_command('ldapdelete') + self.PATH_LDAPMODIFY = self._find_command('ldapmodify') + self.PATH_LDAPWHOAMI = self._find_command('ldapwhoami') + + 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: + path = self.SBIN_PATH + var_name = 'SBIN' + else: + path = self.BIN_PATH + var_name = 'BIN' + command = which(cmd, path=path) + if command is None: + raise ValueError( + "Command '{}' not found. Set the {} environment variable to " + "override slapdtest's search path.".format(value, var_name) + ) + return command def setup_rundir(self): """ @@ -439,7 +417,6 @@ def start(self): """ if self._proc is None: - self._check_requirements() # prepare directory structure atexit.register(self.stop) self._cleanup_rundir() From 9fb933872d27365a30dc07fc55e3233c38c88420 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 20 Dec 2017 12:38:25 +0100 Subject: [PATCH 113/369] Show argument name instead of content in LDAPBytesWarning Based on earlier patch by Christian Heimes https://github.com/python-ldap/python-ldap/pull/145 Fixes: https://github.com/python-ldap/python-ldap/issues/132 Fixes: https://github.com/python-ldap/python-ldap/issues/144 --- Lib/ldap/ldapobject.py | 66 +++++++++++++++++++++--------------------- Tests/t_ldapobject.py | 8 ++--- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index bc78fb7f..f65e09a0 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -122,7 +122,7 @@ def __init__( # On by default on Py2, off on Py3. self.bytes_mode = bytes_mode - def _bytesify_input(self, value): + def _bytesify_input(self, arg_name, value): """Adapt a value following bytes_mode in Python 2. In Python 3, returns the original value unmodified. @@ -147,9 +147,9 @@ def _bytesify_input(self, value): raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,)) else: _raise_byteswarning( - "Received non-bytes value %r with default (disabled) bytes mode; " + "Received non-bytes value for '{}' with default (disabled) bytes mode; " "please choose an explicit " - "option for bytes_mode on your LDAP connection" % (value,)) + "option for bytes_mode on your LDAP connection".format(arg_name)) return value.encode('utf-8') else: if not isinstance(value, text_type): @@ -157,22 +157,7 @@ def _bytesify_input(self, value): assert not isinstance(value, bytes) return value.encode('utf-8') - def _bytesify_inputs(self, *values): - """Adapt values following bytes_mode. - - Applies _bytesify_input on each arg. - - Usage: - >>> a, b, c = self._bytesify_inputs(a, b, c) - """ - if not PY2: - return values - return ( - self._bytesify_input(value) - for value in values - ) - - def _bytesify_modlist(self, modlist, with_opcode): + 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: @@ -184,12 +169,12 @@ def _bytesify_modlist(self, modlist, with_opcode): return modlist if with_opcode: return tuple( - (op, self._bytesify_input(attr), val) + (op, self._bytesify_input(arg_name, attr), val) for op, attr, val in modlist ) else: return tuple( - (self._bytesify_input(attr), val) + (self._bytesify_input(arg_name, attr), val) for attr, val in modlist ) @@ -398,8 +383,9 @@ 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. """ - dn = self._bytesify_input(dn) - modlist = self._bytesify_modlist(modlist, with_opcode=False) + 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): @@ -424,7 +410,9 @@ def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None): """ simple_bind([who='' [,cred='']]) -> int """ - who, cred = self._bytesify_inputs(who, cred) + 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='',cred='',serverctrls=None,clientctrls=None): @@ -501,7 +489,9 @@ def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None): A design bug in the library prevents value from containing nul characters. """ - dn, attr = self._bytesify_inputs(dn, attr) + 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): @@ -532,7 +522,7 @@ 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 = 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): @@ -581,8 +571,9 @@ def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None): """ modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int """ - dn = self._bytesify_input(dn) - modlist = self._bytesify_modlist(modlist, with_opcode=True) + 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): @@ -636,7 +627,10 @@ 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): - user, oldpw, newpw = self._bytesify_inputs(user, oldpw, newpw) + 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): @@ -658,7 +652,10 @@ 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. """ - dn, newrdn, newsuperior = self._bytesify_inputs(dn, newrdn, newsuperior) + 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): @@ -796,9 +793,12 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson The amount of search results retrieved can be limited with the sizelimit parameter if non-zero. """ - base, filterstr = self._bytesify_inputs(base, filterstr) - if attrlist is not None: - attrlist = tuple(self._bytesify_inputs(*attrlist)) + if PY2: + base = self._bytesify_input('base', base) + filterstr = self._bytesify_input('filterstr', filterstr) + if attrlist is not None: + attrlist = tuple(self._bytesify_input('attrlist', a) + for a in attrlist) return self._ldap_call( self._l.search_ext, base,scope,filterstr, diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 62591d77..b51b4cce 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -348,9 +348,9 @@ def test_ldapbyteswarning(self): self.assertIs(msg.category, ldap.LDAPBytesWarning) self.assertEqual( text_type(msg.message), - "Received non-bytes value u'%s' with default (disabled) bytes " + "Received non-bytes value for 'base' with default (disabled) bytes " "mode; please choose an explicit option for bytes_mode on your " - "LDAP connection" % self.server.suffix + "LDAP connection" ) @contextlib.contextmanager @@ -394,10 +394,10 @@ def _test_byteswarning_level_search(self, methodname): self.assertEqual(len(w), 2, w) self._check_byteswarning( - w[0], u"Received non-bytes value u'(cn=Foo*)'") + w[0], u"Received non-bytes value for 'filterstr'") self._check_byteswarning( - w[1], u"Received non-bytes value u'*'") + w[1], u"Received non-bytes value for 'attrlist'") @unittest.skipUnless(PY2, "no bytes_mode under Py3") def test_byteswarning_level_search(self): From a484bc60274b826bbf45d4fe78f14e553c4c384e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 19 Dec 2017 11:56:17 +0100 Subject: [PATCH 114/369] Add recent changes to release notes --- CHANGES | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGES b/CHANGES index 3241046b..93848192 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,29 @@ +---------------------------------------------------------------- +Released 3.0.0b3 2017-12-20 + +Changes since 3.0.0b2: + +The functions `ldap.open()`, `ldap.init()`, `ldif.CreateLDIF()` +and `ldif.ParseLDIF()`, which were deprecated for over a decade, +are scheduled for removal in python-ldap 3.1. + +Infrastructure: +* Require setuptools to build +* Start running automatic tests on PyPy + +Lib/ +* When raising LDAPBytesWarning, give helpful code locations +* Use modern Python idioms in several places +* Avoid reimplementing UserDict.get() in cidict and models.Entry + +Doc/ +* Use https links + +Test/ +* Add reproducer for openldap's NSS shutdown/restart issue +* Make testing on non-Linux platforms easier + + ---------------------------------------------------------------- Released 3.0.0b2 2017-12-11 From 7846ba138e89c7cc6b4a8cee57fd1409273022e3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 19 Dec 2017 11:57:09 +0100 Subject: [PATCH 115/369] Bump version to 3.0.0b3 --- 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 4325aac3..dec5a9ce 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.0b2' +__version__ = '3.0.0b3' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 262b6b02..374336d6 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0b2' +__version__ = '3.0.0b3' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 16fb8c37..a9ece640 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.0.0b2' +__version__ = '3.0.0b3' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 306eaf7f..d2688741 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.0b2' +__version__ = '3.0.0b3' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From b32bcafe5007192e7f1c0d1259dda3b11b9eb1d7 Mon Sep 17 00:00:00 2001 From: Alexandre Figura Date: Fri, 29 Dec 2017 17:28:58 +0100 Subject: [PATCH 116/369] Document all_records attribute of class LDIFRecordList The current documentation of this attribute was not displayed on the online doc. --- Lib/ldif.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Lib/ldif.py b/Lib/ldif.py index a9ece640..96b1651f 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -577,8 +577,10 @@ def parse_change_records(self): class LDIFRecordList(LDIFParser): """ - Collect all records of LDIF input into a single list. - of 2-tuples (dn,entry). It can be a memory hog! + Collect all records of a LDIF file. It can be a memory hog! + + Records are stored in :attr:`.all_records` as a single list + of 2-tuples (dn, entry), after calling :meth:`.parse`. """ def __init__( @@ -586,20 +588,15 @@ def __init__( input_file, ignored_attr_types=None,max_entries=0,process_url_schemes=None ): - """ - See LDIFParser.__init__() - - Additional Parameters: - all_records - List instance for storing parsed records - """ LDIFParser.__init__(self,input_file,ignored_attr_types,max_entries,process_url_schemes) + + #: List storing parsed records. self.all_records = [] self.all_modify_changes = [] def handle(self,dn,entry): """ - Append single record to dictionary of all records. + Append a single record to the list of all records (:attr:`.all_records`). """ self.all_records.append((dn,entry)) From cf9494362b7a4c11d1e669e5d95011cfb6eb4cc0 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jan 2018 16:56:09 +0100 Subject: [PATCH 117/369] Refactor syncrepl tests to run with bytes_mode https://github.com/python-ldap/python-ldap/pull/150 Signed-off-by: Christian Heimes --- Tests/t_ldap_syncrepl.py | 118 ++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 65 deletions(-) diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 0581e502..8c4eb344 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -7,8 +7,14 @@ import os -import unittest import shelve +import sys +import unittest + +if sys.version_info[0] <= 2: + PY2 = True +else: + PY2 = False from slapdtest import SlapdObject, SlapdTestCase @@ -125,9 +131,9 @@ class SyncreplClient(SimpleLDAPObject, SyncreplConsumer): Needs to be separate, because once an LDAP client starts a syncrepl search, it can't be used for anything else. """ - server_class = SyncreplProvider - def __init__(self, uri, dn, password, storage=None): + def __init__(self, uri, dn, password, storage=None, filterstr=None, + **kwargs): """ Set up our object by creating a search client, connecting, and binding. """ @@ -146,11 +152,11 @@ def __init__(self, uri, dn, password, storage=None): self.data['cookie'] = None self.present = [] self.refresh_done = False + self.filterstr = filterstr - SimpleLDAPObject.__init__(self, uri) + SimpleLDAPObject.__init__(self, uri, **kwargs) self.simple_bind_s(dn, password) - def unbind_s(self): """ In addition to unbinding from LDAP, we need to close the shelf. @@ -161,7 +167,6 @@ def unbind_s(self): self.dn_attrs.close() SimpleLDAPObject.unbind_s(self) - def search(self, search_base, search_mode): """ Start a syncrepl search operation, given a base DN and search mode. @@ -170,17 +175,15 @@ def search(self, search_base, search_mode): search_base, ldap.SCOPE_SUBTREE, mode=search_mode, - filterstr='(objectClass=*)' + filterstr=self.filterstr ) - def cancel(self): """ A simple wrapper to call parent class with syncrepl search ID. """ SimpleLDAPObject.cancel(self, self.search_id) - def poll(self, timeout=None, all=0): """ Take the params, add the syncrepl search ID, and call the proper poll. @@ -191,28 +194,24 @@ def poll(self, timeout=None, all=0): all=all ) - def syncrepl_get_cookie(self): """ Pull cookie from storage, if one exists. """ return self.data['cookie'] - def syncrepl_set_cookie(self, cookie): """ Update stored cookie. """ self.data['cookie'] = cookie - def syncrepl_refreshdone(self): """ Just update a variable. """ self.refresh_done = True - def syncrepl_delete(self, uuids): """ Delete the given items from both maps. @@ -221,7 +220,6 @@ def syncrepl_delete(self, uuids): del self.dn_attrs[self.uuid_dn[uuid]] del self.uuid_dn[uuid] - def syncrepl_entry(self, dn, attrs, uuid): """ Handles adds and changes (including DN changes). @@ -236,7 +234,6 @@ def syncrepl_entry(self, dn, attrs, uuid): self.uuid_dn[uuid] = dn self.dn_attrs[dn] = attrs - def syncrepl_present(self, uuids, refreshDeletes=False): """ The 'present' message from the LDAP server is the most complicated @@ -262,7 +259,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): pass -class Test00_Syncrepl(SlapdTestCase): +class BaseSyncreplTests(object): """ 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, @@ -275,7 +272,7 @@ class Test00_Syncrepl(SlapdTestCase): @classmethod def setUpClass(cls): - super(Test00_Syncrepl, cls).setUpClass() + super(BaseSyncreplTests, cls).setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -287,57 +284,39 @@ def setUpClass(cls): } ) - def setUp(self): - try: - self._ldap_conn - except AttributeError: - # open local LDAP connection - self._ldap_conn = self._open_ldap_conn() - + super(BaseSyncreplTests, self).setUp() + self.tester = None + self.suffix = None def tearDown(self): self.tester.unbind_s() + super(BaseSyncreplTests, self).tearDown() + def create_client(self): + raise NotImplementedError def test_refreshOnly_search(self): ''' Test to see if we can initialize a syncrepl search. ''' - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshOnly' ) - def test_refreshAndPersist_search(self): - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshAndPersist' ) - def test_refreshOnly_poll_full(self): """ Test doing a full refresh cycle, and check what we got. """ - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshOnly' ) poll_result = self.tester.poll( @@ -347,18 +326,12 @@ def test_refreshOnly_poll_full(self): self.assertFalse(poll_result) self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) - def test_refreshAndPersist_poll_only(self): """ Test the refresh part of refresh-and-persist, and check what we got. """ - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshAndPersist' ) @@ -372,18 +345,12 @@ def test_refreshAndPersist_poll_only(self): self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) - def test_refreshAndPersist_timeout(self): """ Make sure refreshAndPersist can handle a search with timeouts. """ - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshAndPersist' ) @@ -407,18 +374,12 @@ def test_refreshAndPersist_timeout(self): timeout=1 ) - def test_refreshAndPersist_cancelled(self): """ Make sure refreshAndPersist can handle cancelling a syncrepl search. """ - self.tester = SyncreplClient( - self.server.ldap_uri, - self.server.root_dn, - self.server.root_pw - ) self.tester.search( - self.server.suffix, + self.suffix, 'refreshAndPersist' ) @@ -463,5 +424,32 @@ def test_refreshAndPersist_cancelled(self): # should pick it up during the persist phase. +class TestSyncrepl(BaseSyncreplTests, SlapdTestCase): + def setUp(self): + super(TestSyncrepl, self).setUp() + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw, + filterstr=u'(objectClass=*)', + bytes_mode=False + ) + 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'), + filterstr=b'(objectClass=*)', + bytes_mode=True + ) + self.suffix = self.server.suffix.encode('utf-8') + + if __name__ == '__main__': unittest.main() From 51229fd1277f513fab7b7b2f1f1094c1b427e67e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 5 Jan 2018 16:58:11 +0100 Subject: [PATCH 118/369] Doc: Add updating the GitHub release page to the release process https://github.com/python-ldap/python-ldap/pull/146 --- Doc/contributing.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index c45e9c94..9061332d 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -214,3 +214,6 @@ If you are tasked with releasing python-ldap, remember to: * 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`_. + +.. _GitHub release page: https://github.com/python-ldap/python-ldap/releases From 98c535b9fc3e89bef5deb479590a9509440b494a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 5 Jan 2018 07:59:53 -0800 Subject: [PATCH 119/369] Remove unused local variable, norm_func https://github.com/python-ldap/python-ldap/pull/140 --- Lib/ldap/modlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index e1dfe446..ecd7a42f 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -73,7 +73,6 @@ def modifyModlist( replace_attr_value = len(old_value)!=len(new_value) if not replace_attr_value: if attrtype_lower in case_ignore_attr_types: - norm_func = str.lower old_value_set = set(map(str.lower,old_value)) new_value_set = set(map(str.lower,new_value)) else: From 3ae51ed14c1139ee026d411577d5b349a215a075 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 5 Jan 2018 08:39:20 -0800 Subject: [PATCH 120/369] Update uses of map() to use list/set comprehensions instead Updates style to modern Python idioms. --- Lib/ldap/controls/deref.py | 2 +- Lib/ldap/filter.py | 2 +- Lib/ldap/functions.py | 3 +-- Lib/ldap/modlist.py | 10 +++++----- Lib/ldapurl.py | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index b927f1a5..0d43ab1f 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -108,7 +108,7 @@ def decodeControlValue(self,encodedControlValue): for deref_res in decodedValue: deref_attr,deref_val,deref_vals = deref_res[0],deref_res[1],deref_res[2] partial_attrs_dict = { - str(tv[0]): map(str,tv[1]) + str(tv[0]): [str(v) for v in tv[1]] for tv in deref_vals or [] } try: diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py index 56665dc4..3dba7f74 100644 --- a/Lib/ldap/filter.py +++ b/Lib/ldap/filter.py @@ -54,7 +54,7 @@ def filter_format(filter_template,assertion_values): List or tuple of assertion values. Length must match count of %s in filter_template. """ - return filter_template % (tuple(map(escape_filter_chars,assertion_values))) + return filter_template % tuple(escape_filter_chars(v) for v in assertion_values) def time_span_filter( diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 8c7580e8..6c351eff 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -138,8 +138,7 @@ def escape_str(escape_func,s,*args): Applies escape_func() to all items of `args' and returns a string based on format string `s'. """ - escape_args = map(escape_func,args) - return s % tuple(escape_args) + return s % tuple(escape_func(v) for v in args) def strf_secs(secs): diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index ecd7a42f..6f513127 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -11,7 +11,7 @@ def addModlist(entry,ignore_attr_types=None): """Build modify list for call of method LDAPObject.add()""" - ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) + ignore_attr_types = {v.lower() for v in ignore_attr_types or []} modlist = [] for attrtype in entry.keys(): if attrtype.lower() in ignore_attr_types: @@ -46,8 +46,8 @@ def modifyModlist( List of attribute type names for which comparison will be made case-insensitive """ - ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) - case_ignore_attr_types = set(map(str.lower,case_ignore_attr_types or [])) + ignore_attr_types = {v.lower() for v in ignore_attr_types or []} + case_ignore_attr_types = {v.lower() for v in case_ignore_attr_types or []} modlist = [] attrtype_lower_map = {} for a in old_entry.keys(): @@ -73,8 +73,8 @@ def modifyModlist( replace_attr_value = len(old_value)!=len(new_value) if not replace_attr_value: if attrtype_lower in case_ignore_attr_types: - old_value_set = set(map(str.lower,old_value)) - new_value_set = set(map(str.lower,new_value)) + old_value_set = {v.lower() for v in old_value} + new_value_set = {v.lower() for v in new_value} else: old_value_set = set(old_value) new_value_set = set(new_value) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 374336d6..698889a3 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -158,7 +158,7 @@ def values(self): ] def __str__(self): - return ','.join(map(str,self.values())) + return ','.join(str(v) for v in self.values()) def __repr__(self): return '<%s.%s instance at %s: %s>' % ( From 98999bd617aa837f3f57a3add9802b499f3dbba8 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jan 2018 17:30:54 +0100 Subject: [PATCH 121/369] Make schema attribute names text Schema attribute names must be text in Python 2, too. Otherwise read_subschemasubentry_s() fails with bytes error. Signed-off-by: Christian Heimes --- Lib/ldap/schema/models.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index c1362a95..9a8cb5b2 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -127,7 +127,7 @@ class ObjectClass(SchemaElement): This list of strings contains NAMEs or OIDs of object classes this object class is derived from """ - schema_attribute = 'objectClasses' + schema_attribute = u'objectClasses' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -225,7 +225,7 @@ class AttributeType(SchemaElement): This list of strings contains NAMEs or OIDs of attribute types this attribute type is derived from """ - schema_attribute = 'attributeTypes' + schema_attribute = u'attributeTypes' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -319,7 +319,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 = 'ldapSyntaxes' + schema_attribute = u'ldapSyntaxes' token_defaults = { 'DESC':(None,), 'X-NOT-HUMAN-READABLE':(None,), @@ -367,7 +367,7 @@ class MatchingRule(SchemaElement): syntax String contains OID of the LDAP syntax this matching rule is usable with """ - schema_attribute = 'matchingRules' + schema_attribute = u'matchingRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -413,7 +413,7 @@ class MatchingRuleUse(SchemaElement): This list of strings contains NAMEs or OIDs of attribute types for which this matching rule is used """ - schema_attribute = 'matchingRuleUse' + schema_attribute = u'matchingRuleUse' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -470,7 +470,7 @@ class DITContentRule(SchemaElement): This list of strings contains NAMEs or OIDs of attributes which may not be present in an entry of the object class """ - schema_attribute = 'dITContentRules' + schema_attribute = u'dITContentRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -527,7 +527,7 @@ class DITStructureRule(SchemaElement): List of strings with NAMEs or OIDs of allowed structural object classes of superior entries in the DIT """ - schema_attribute = 'dITStructureRules' + schema_attribute = u'dITStructureRules' token_defaults = { 'NAME':(()), @@ -591,7 +591,7 @@ class NameForm(SchemaElement): This list of strings contains NAMEs or OIDs of additional attributes an RDN may contain """ - schema_attribute = 'nameForms' + schema_attribute = u'nameForms' token_defaults = { 'NAME':(()), 'DESC':(None,), From 2f2f64c546898cec8cdd076b3114ecd290e2a87e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jan 2018 17:32:42 +0100 Subject: [PATCH 122/369] Add more tests for bytes mode affected functions Tests marked with expected failure are currently affected by bug #147. See: https://github.com/python-ldap/python-ldap/issues/147 Signed-off-by: Christian Heimes --- Tests/t_ldapobject.py | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 2e1d3380..fb480117 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -161,6 +161,30 @@ def test_bytesmode_search_results_have_bytes(self): for value in values: self.assertEqual(type(value), bytes) + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + @unittest.expectedFailure + 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) @@ -263,11 +287,51 @@ def test003_search_oneattr(self): [('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': [b'Foo4']})] ) + def test_find_unique_entry(self): + result = self._ldap_conn.find_unique_entry( + self.server.suffix, + ldap.SCOPE_SUBTREE, + '(cn=Foo4)', + ['cn'], + ) + self.assertEqual( + result, + ('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': [b'Foo4']}) + ) + with self.assertRaises(ldap.SIZELIMIT_EXCEEDED): + # > 2 entries returned + self._ldap_conn.find_unique_entry( + self.server.suffix, + ldap.SCOPE_ONELEVEL, + '(cn=Foo*)', + ['*'], + ) + with self.assertRaises(ldap.NO_UNIQUE_ENTRY): + # 0 entries returned + self._ldap_conn.find_unique_entry( + self.server.suffix, + ldap.SCOPE_ONELEVEL, + '(cn=Bar*)', + ['*'], + ) + def test_search_subschema(self): l = self._ldap_conn dn = l.search_subschemasubentry_s() self.assertIsInstance(dn, text_type) self.assertEqual(dn, "cn=Subschema") + subschema = l.read_subschemasubentry_s(dn) + self.assertIsInstance(subschema, dict) + self.assertEqual( + sorted(subschema), + [ + u'attributeTypes', + u'ldapSyntaxes', + u'matchingRuleUse', + u'matchingRules', + u'objectClasses' + ] + ) @unittest.skipUnless(PY2, "no bytes_mode under Py3") def test_search_subschema_have_bytes(self): @@ -275,6 +339,18 @@ def test_search_subschema_have_bytes(self): 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_errno107(self): l = self.ldap_object_class('ldap://127.0.0.1:42') @@ -433,6 +509,34 @@ 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) + def test_dse(self): + dse = self._ldap_conn.read_rootdse_s() + self.assertIsInstance(dse, dict) + self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) + self.assertEqual( + sorted(dse), + [u'configContext', u'entryDN', u'namingContexts', u'objectClass', + u'structuralObjectClass', u'subschemaSubentry', + u'supportedControl', u'supportedExtension', u'supportedFeatures', + u'supportedLDAPVersion', u'supportedSASLMechanisms'] + ) + self.assertEqual( + self._ldap_conn.get_naming_contexts(), + [self.server.suffix.encode('utf-8')] + ) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + @unittest.expectedFailure + 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')] + ) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From e56f61753e6b58b32f571ba9510f4e14ea666786 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jan 2018 17:57:13 +0100 Subject: [PATCH 123/369] Add workarounds for default argument types Several default arguments are not compatible with bytes mode. Default to bytes in bytes mode. See: https://github.com/python-ldap/python-ldap/issues/147 Signed-off-by: Christian Heimes --- Lib/ldap/ldapobject.py | 55 +++++++++++++++++++++++++++++++++--------- Tests/t_ldapobject.py | 4 +-- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index f65e09a0..71d298a6 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -795,7 +795,12 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson """ if PY2: base = self._bytesify_input('base', base) - filterstr = self._bytesify_input('filterstr', filterstr) + # workaround for default argument, + # see https://github.com/python-ldap/python-ldap/issues/147 + if self.bytes_mode and filterstr == '(objectClass=*)': + filterstr = b'(objectClass=*)' + else: + filterstr = self._bytesify_input('filterstr', filterstr) if attrlist is not None: attrlist = tuple(self._bytesify_input('attrlist', a) for a in attrlist) @@ -885,7 +890,7 @@ def set_option(self,option,invalue): invalue = RequestControlTuples(invalue) return self._ldap_call(self._l.set_option,option,invalue) - def search_subschemasubentry_s(self,dn=''): + def search_subschemasubentry_s(self,dn=None): """ Returns the distinguished name of the sub schema sub entry for a part of a DIT specified by dn. @@ -895,9 +900,17 @@ def search_subschemasubentry_s(self,dn=''): 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' + if dn is None: + dn = empty_dn try: r = self.search_s( - dn,ldap.SCOPE_BASE,'(objectClass=*)',['subschemaSubentry'] + dn,ldap.SCOPE_BASE,'(objectClass=*)',[attrname] ) except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS): r = [] @@ -906,11 +919,11 @@ def search_subschemasubentry_s(self,dn=''): try: if r: e = ldap.cidict.cidict(r[0][1]) - search_subschemasubentry_dn = e.get('subschemaSubentry',[None])[0] + search_subschemasubentry_dn = e.get(attrname,[None])[0] if search_subschemasubentry_dn is None: if dn: # Try to find sub schema sub entry in root DSE - return self.search_subschemasubentry_s(dn='') + return self.search_subschemasubentry_s(dn=empty_dn) else: # If dn was already root DSE we can return here return None @@ -945,11 +958,19 @@ 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 try: subschemasubentry = self.read_s( subschemasubentry_dn, - filterstr='(objectClass=subschema)', - attrlist=attrs or SCHEMA_ATTRS + filterstr=filterstr, + attrlist=attrs ) except ldap.NO_SUCH_OBJECT: return None @@ -964,7 +985,7 @@ def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass base, scope, filterstr, - attrlist=attrlist or ['*'], + attrlist=attrlist, attrsonly=attrsonly, serverctrls=serverctrls, clientctrls=clientctrls, @@ -979,10 +1000,16 @@ def read_rootdse_s(self, filterstr='(objectClass=*)', 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'+'] ldap_rootdse = self.read_s( - '', + base, filterstr=filterstr, - attrlist=attrlist or ['*', '+'], + attrlist=attrlist, ) return ldap_rootdse # read_rootdse_s() @@ -991,9 +1018,13 @@ 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' return self.read_rootdse_s( - attrlist=['namingContexts'] - ).get('namingContexts', []) + attrlist=[name] + ).get(name, []) class ReconnectLDAPObject(SimpleLDAPObject): diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index fb480117..243cd86d 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -162,7 +162,6 @@ def test_bytesmode_search_results_have_bytes(self): self.assertEqual(type(value), bytes) @unittest.skipUnless(PY2, "no bytes_mode under Py3") - @unittest.expectedFailure def test_bytesmode_search_defaults(self): l = self._get_bytes_ldapobject() base = 'cn=Foo1,' + self.server.suffix @@ -335,7 +334,7 @@ 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(explicit=False) + l = self._get_bytes_ldapobject() dn = l.search_subschemasubentry_s() self.assertIsInstance(dn, bytes) self.assertEqual(dn, b"cn=Subschema") @@ -526,7 +525,6 @@ def test_dse(self): ) @unittest.skipUnless(PY2, "no bytes_mode under Py3") - @unittest.expectedFailure def test_dse_bytes(self): l = self._get_bytes_ldapobject() dse = l.read_rootdse_s() From 274d826f660ef7cd49c0662126d3c41758800cf1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 5 Jan 2018 18:07:23 +0100 Subject: [PATCH 124/369] Better hack: filterstr=None Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 3 +++ Lib/ldap/ldapobject.py | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 20994143..467f1d78 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1068,7 +1068,10 @@ 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. + .. versionchanged:: 3.0 + ``filterstr=None`` is equal to ``filterstr='(objectClass=*)'``. + .. py:method:: LDAPObject.start_tls_s() -> None diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 71d298a6..69431eff 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -748,7 +748,7 @@ def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermedi 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='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): + def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): """ search(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) -> int search_s(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) @@ -793,17 +793,24 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson The amount of search results retrieved can be limited with the sizelimit parameter if non-zero. """ + if PY2: base = self._bytesify_input('base', base) - # workaround for default argument, - # see https://github.com/python-ldap/python-ldap/issues/147 - if self.bytes_mode and filterstr == '(objectClass=*)': - filterstr = b'(objectClass=*)' + 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=*)' return self._ldap_call( self._l.search_ext, base,scope,filterstr, @@ -813,17 +820,17 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson timeout,sizelimit, ) - def search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): + def search_ext_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit) return self.result(msgid,all=1,timeout=timeout)[1] - def search(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0): + def search(self,base,scope,filterstr=None,attrlist=None,attrsonly=0): return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None) - def search_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0): + def search_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0): return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout) - def search_st(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,timeout=-1): + def search_st(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,timeout=-1): return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout) def start_tls_s(self): @@ -910,7 +917,7 @@ def search_subschemasubentry_s(self,dn=None): dn = empty_dn try: r = self.search_s( - dn,ldap.SCOPE_BASE,'(objectClass=*)',[attrname] + dn,ldap.SCOPE_BASE,None,[attrname] ) except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS): r = [] @@ -943,7 +950,7 @@ def read_s(self,dn,filterstr=None,attrlist=None,serverctrls=None,clientctrls=Non r = self.search_ext_s( dn, ldap.SCOPE_BASE, - filterstr or '(objectClass=*)', + filterstr, attrlist=attrlist, serverctrls=serverctrls, clientctrls=clientctrls, @@ -977,7 +984,7 @@ def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None): else: return subschemasubentry - def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1): + def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1): """ Returns a unique entry, raises exception if not unique """ @@ -996,7 +1003,7 @@ def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass raise NO_UNIQUE_ENTRY('No or non-unique search result for %s' % (repr(filterstr))) return r[0] - def read_rootdse_s(self, filterstr='(objectClass=*)', attrlist=None): + def read_rootdse_s(self, filterstr=None, attrlist=None): """ convenience wrapper around read_s() for reading rootDSE """ From f8f8c9c024fe1b9809c173a1f51e9f055cb6fec3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2018 10:59:28 +0100 Subject: [PATCH 125/369] Wording change in search* docs --- 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 467f1d78..fdd5d356 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1070,7 +1070,7 @@ and wait for and return with the server's result, or with .. versionchanged:: 3.0 - ``filterstr=None`` is equal to ``filterstr='(objectClass=*)'``. + ``filterstr=None`` is equivalent to ``filterstr='(objectClass=*)'``. .. py:method:: LDAPObject.start_tls_s() -> None From 6e75fc4b0f1d7becf0b6958c9da14552a6d6a5e6 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 10 Jan 2018 02:47:25 -0800 Subject: [PATCH 126/369] Remove support for EOL Python 3.3 Python 3.3 is end of life. It is no longer receiving bug fixes including for security issues. It has been EOL since 2017-09-29. For a library focused on authentication, supporting environments that are not receiving security updates sends the wrong message. For additional details, see: https://devguide.python.org/#status-of-python-branches Reduces testing and maintenance resources. https://github.com/python-ldap/python-ldap/pull/154 --- .travis.yml | 4 ---- Doc/installing.rst | 2 +- setup.py | 7 +++---- tox.ini | 7 ++++--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22559b7d..5479573f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,6 @@ matrix: env: - TOXENV=py27 - WITH_GCOV=1 - - python: 3.3 - env: - - TOXENV=py33 - - WITH_GCOV=1 - python: 3.4 env: - TOXENV=py34 diff --git a/Doc/installing.rst b/Doc/installing.rst index efbab704..bb06c212 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -120,7 +120,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.3 or later including its development files +- `Python`_ version 2.7, or 3.4 or later 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. diff --git a/setup.py b/setup.py index 2f6bd895..034c4dd3 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,8 @@ 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, 3): - raise RuntimeError('The C API from Python 3.3+ is required.') +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[0] >= 3: from configparser import ConfigParser @@ -91,7 +91,6 @@ class OpenLDAP2: 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -168,6 +167,6 @@ class OpenLDAP2: 'pyasn1_modules >= 0.1.5', ], zip_safe=False, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', test_suite = 'Tests', ) diff --git a/tox.ini b/tox.ini index 58e3cf68..abba0b01 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,py33,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report +envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report minver = 1.8 [testenv] @@ -13,8 +13,9 @@ 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.3 and 3.4 use PendingDeprecationWarning. +# - '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. commands = {envpython} -bb -Werror \ "-Wignore:the imp module is deprecated:DeprecationWarning" \ "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ From 9369294db7a8d39f6416b74de61e1596cd309b88 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 10 Jan 2018 12:09:05 +0100 Subject: [PATCH 127/369] simple_bind() accept None for who and cred sasl_bind_s() has accepted None for who and cred for a long time. Now simple_bind() and simple_bind_s() default to and accept None, too. See: https://github.com/python-ldap/python-ldap/issues/147 Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 9 +++++++-- Lib/ldap/ldapobject.py | 6 +++--- Modules/LDAPObject.c | 2 +- Tests/t_ldapobject.py | 8 ++++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index fdd5d356..66434b54 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1000,9 +1000,9 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. -.. py:method:: LDAPObject.simple_bind([who='' [, cred='' [, serverctrls=None [, clientctrls=None]]]]) -> int +.. py:method:: LDAPObject.simple_bind([who=None [, cred=None [, serverctrls=None [, clientctrls=None]]]]) -> int -.. py:method:: LDAPObject.simple_bind_s([who='' [, cred='' [, serverctrls=None [, clientctrls=None]]]]) -> None +.. py:method:: LDAPObject.simple_bind_s([who=None [, cred=None [, serverctrls=None [, clientctrls=None]]]]) -> None After an LDAP object is created, and before any other operations can be attempted over the connection, a bind operation must be performed. @@ -1015,6 +1015,11 @@ and wait for and return with the server's result, or with *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. + .. 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 diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 69431eff..5a80bff7 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -406,7 +406,7 @@ def add(self,dn,modlist): def add_s(self,dn,modlist): return self.add_ext_s(dn,modlist,None,None) - def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None): + def simple_bind(self,who=None,cred=None,serverctrls=None,clientctrls=None): """ simple_bind([who='' [,cred='']]) -> int """ @@ -415,7 +415,7 @@ def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None): 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='',cred='',serverctrls=None,clientctrls=None): + def simple_bind_s(self,who=None,cred=None,serverctrls=None,clientctrls=None): """ simple_bind_s([who='' [,cred='']]) -> 4-tuple """ @@ -1107,7 +1107,7 @@ def _apply_last_bind(self): func(self,*args,**kwargs) else: # Send explicit anon simple bind request to provoke ldap.SERVER_DOWN in method reconnect() - SimpleLDAPObject.simple_bind_s(self,'','') + SimpleLDAPObject.simple_bind_s(self, None, None) def _restore_options(self): """Restore all recorded options""" diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index addbe961..f8720fa4 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -499,7 +499,7 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) LDAPControl** client_ldcs = NULL; struct berval cred; - if (!PyArg_ParseTuple( args, "ss#|OO", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; + if (!PyArg_ParseTuple( args, "zz#|OO", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; cred.bv_len = (ber_len_t) cred_len; if (not_valid(self)) return NULL; diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 243cd86d..27deadcd 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -401,6 +401,14 @@ def assertIsSubclass(self, cls, other): cls.__mro__ ) + 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'') + l = self.ldap_object_class(self.server.ldap_uri) + 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) From a144e3d0b51a5622a228b682d9b9109005c9ecfc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2018 12:21:59 +0100 Subject: [PATCH 128/369] ReST syntax fix --- 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 66434b54..9cb1d520 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1018,7 +1018,7 @@ and wait for and return with the server's result, or with .. versionchanged:: 3.0 :meth:`~LDAPObject.simple_bind` and :meth:`~LDAPObject.simple_bind_s` - now accept `None` for who and cred, too. + now accept ``None`` for *who* and *cred*, too. .. py:method:: LDAPObject.search(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->int From abcd2e9937336b82f08dc52557f2319a998cdca1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 10 Jan 2018 12:31:37 +0100 Subject: [PATCH 129/369] Improve valgrind rule for PR_strdup libldap's NSS engine has one known memory leak. One block of memory is leaked one time only. The existing rule only matches when all debug symbols are installed. The new rule should work without debug symbols. https://github.com/python-ldap/python-ldap/pull/157 Closes: https://github.com/python-ldap/python-ldap/issues/156 Signed-off-by: Christian Heimes --- Misc/python-ldap.supp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/python-ldap.supp b/Misc/python-ldap.supp index 3267fd05..b9954a9a 100644 --- a/Misc/python-ldap.supp +++ b/Misc/python-ldap.supp @@ -33,7 +33,8 @@ match-leak-kinds: definite fun:malloc fun:PL_strdup - fun:tlsm_init + ... + fun:ldap_set_option ... } From ad46c1104ea40117f8fb0fd08653517743f94723 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2018 12:35:58 +0100 Subject: [PATCH 130/369] Bump version to 3.0.0b4 and update CHANGES https://github.com/python-ldap/python-ldap/pull/158 --- CHANGES | 18 ++++++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 93848192..03d892f5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,21 @@ +---------------------------------------------------------------- +Released 3.0.0b4 2018-01-10 + +Changes since 3.0.0b3: + +Removed support for Python 3.3, which reached its end-of-life 2017-09-29. + +Lib/ +* Make default argument values work under bytes_mode +* Update use of map() to use list/set comprehensions instead + +Test/ +* Refactor syncrepl tests to run with bytes_mode + +Doc/ +* Document all_records attribute of LDIFRecordList + + ---------------------------------------------------------------- Released 3.0.0b3 2017-12-20 diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index dec5a9ce..24e2017c 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.0b3' +__version__ = '3.0.0b4' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 698889a3..ca28de3d 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0b3' +__version__ = '3.0.0b4' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 96b1651f..29c118c3 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals -__version__ = '3.0.0b3' +__version__ = '3.0.0b4' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index d2688741..727aa528 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.0b3' +__version__ = '3.0.0b4' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 2387fe6df83bd438536b175938d8b6323070f387 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 15 Jan 2018 23:17:58 +0100 Subject: [PATCH 131/369] 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 132/369] 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 133/369] 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 134/369] 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 135/369] 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 136/369] 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 137/369] 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 138/369] 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 139/369] 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 140/369] 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 141/369] 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 142/369] 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 143/369] 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 144/369] 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 145/369] 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 146/369] 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 147/369] 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 148/369] 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 149/369] 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 150/369] 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 151/369] 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 152/369] 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 153/369] 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 154/369] 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 155/369] 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 156/369] 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 157/369] 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 158/369] 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 159/369] 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 160/369] 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 161/369] 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 162/369] 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 163/369] 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 164/369] 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 165/369] 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 166/369] 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 167/369] 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 168/369] 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 169/369] 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 170/369] 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 171/369] 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 172/369] 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 173/369] 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 174/369] 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 175/369] 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 176/369] 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 177/369] 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 178/369] 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 179/369] 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 180/369] 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 181/369] 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 182/369] 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 183/369] 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 184/369] 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 185/369] 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 186/369] 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 187/369] 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 188/369] 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 189/369] 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 190/369] 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 191/369] 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 192/369] 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 193/369] 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 194/369] 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 195/369] 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 196/369] 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 197/369] 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 198/369] 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 199/369] 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 200/369] 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 201/369] 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 202/369] 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 203/369] 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 204/369] 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 205/369] 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 206/369] 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 207/369] 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 208/369] 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 209/369] 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 210/369] 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 211/369] 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 212/369] 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 213/369] 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 214/369] 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 215/369] 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 216/369] 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 217/369] 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 218/369] 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 219/369] 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 220/369] 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 221/369] 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 222/369] 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 223/369] 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 224/369] 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 225/369] 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 226/369] 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 227/369] 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 228/369] 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 229/369] 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 230/369] 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 231/369] 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 232/369] 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 233/369] 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 234/369] 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 235/369] 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 236/369] 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 237/369] 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 238/369] 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 239/369] 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 240/369] 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 241/369] 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 242/369] 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 243/369] 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 244/369] 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 245/369] 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 246/369] 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 247/369] 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 248/369] 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 249/369] 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 250/369] 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 251/369] 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 252/369] 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 253/369] 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 254/369] 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 255/369] 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 256/369] 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 257/369] 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 258/369] 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 259/369] 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 260/369] 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 261/369] 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 262/369] 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 263/369] 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 264/369] 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 265/369] 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 266/369] 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 267/369] 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 268/369] 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 269/369] 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 270/369] 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 271/369] 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 272/369] 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 273/369] 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 274/369] 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 275/369] 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 276/369] 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 277/369] 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 278/369] [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 279/369] 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 280/369] 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 281/369] 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 282/369] 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 283/369] 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 284/369] 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 285/369] 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 286/369] 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 287/369] 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 288/369] 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 289/369] 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 290/369] 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 291/369] 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 292/369] 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 293/369] 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 294/369] 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 295/369] 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 296/369] 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 297/369] 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 298/369] 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 299/369] 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 300/369] 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 301/369] 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 302/369] 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 303/369] 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 304/369] 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 305/369] 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 306/369] 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 307/369] 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 308/369] 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 309/369] 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 310/369] 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 311/369] 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 312/369] 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 313/369] 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 314/369] 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 315/369] 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 316/369] 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 317/369] 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 318/369] 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 319/369] 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 320/369] 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 321/369] 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 322/369] 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 323/369] 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 324/369] 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 325/369] 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 326/369] 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 327/369] 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 328/369] 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 329/369] 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 330/369] 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 331/369] 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 332/369] 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 333/369] 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 334/369] 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 335/369] 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 336/369] 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 From e628f1582b46269ba6c31e1e3ee8a952cb32bb7d Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Tue, 28 Jan 2025 14:26:58 -0800 Subject: [PATCH 337/369] Deprecate EOL Python 3.6, 3.7, 3.8 versions --- .github/workflows/ci.yml | 9 ++------- .github/workflows/tox-fedora.yml | 7 +------ pyproject.toml | 4 ---- setup.py | 5 +---- tox.ini | 7 ++----- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f835d76..06d0b2ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ --- name: CI -on: +on: push: pull_request: schedule: @@ -20,8 +20,6 @@ jobs: fail-fast: false matrix: python-version: - - "3.7" - - "3.8" - "3.9" - "3.10" - "3.11" @@ -29,11 +27,8 @@ jobs: - "3.13" - "pypy3.9" - "pypy3.10" - image: + image: - "ubuntu-22.04" - include: - - python-version: "3.6" - image: "ubuntu-20.04" steps: - name: Checkout uses: "actions/checkout@v4" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 4c4c18f0..bc6f45c5 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -21,20 +21,15 @@ jobs: strategy: matrix: tox_env: - - py36 - - py37 - - py38 - py39 - py310 - py311 - py312 - py313 - - c90-py36 - - c90-py37 - py3-nosasltls - py3-trace - pypy3 - doc # Use GitHub's Linux Docker host - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 diff --git a/pyproject.toml b/pyproject.toml index dda8dbc1..75f7c06a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,3 @@ -[tool.black] -line-length = 88 -target-version = ['py36', 'py37', 'py38'] - [tool.isort] line_length=88 known_first_party=['ldap', '_ldap', 'ldapurl', 'ldif', 'slapdtest'] diff --git a/setup.py b/setup.py index 8e7963a1..ea7364cd 100644 --- a/setup.py +++ b/setup.py @@ -86,9 +86,6 @@ class OpenLDAP2: 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - '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', @@ -160,6 +157,6 @@ class OpenLDAP2: 'pyasn1_modules >= 0.1.5', ], zip_safe=False, - python_requires='>=3.6', + python_requires='>=3.9', test_suite = 'Tests', ) diff --git a/tox.ini b/tox.ini index 22752067..0b284a4e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,15 +5,12 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310,311,312},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3.9 +envlist = py{39,310,311,312},py3-nosasltls,doc,py3-trace,pypy3.9 minver = 1.8 [gh-actions] python = - 3.6: py36 - 3.7: py37 - 3.8: py38, doc, py3-nosasltls - 3.9: py39, py3-trace + 3.9: py39, py3-trace, doc, py3-nosasltls 3.10: py310 3.11: py311 3.12: py312 From 30b24d5673095fecc73f652cfa41efc6208f9d72 Mon Sep 17 00:00:00 2001 From: Jiayu Hu <86949267+JennyHo5@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:25:03 -0400 Subject: [PATCH 338/369] Fix typo (#584) The cookie is saved with key `cookie` intead of `ldap_cookie` in the `self.__data` dict --- Demo/pyasn1/syncrepl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index f1f24e19..754b237a 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -76,7 +76,7 @@ def syncrepl_entry(self, dn, attributes, uuid): logger.debug('Detected %s of entry %r', change_type, dn) # If we have a cookie then this is not our first time being run, # so it must be a change - if 'ldap_cookie' in self.__data: + if 'cookie' in self.__data: self.perform_application_sync(dn, attributes, previous_attributes) def syncrepl_delete(self,uuids): @@ -98,7 +98,7 @@ def syncrepl_present(self,uuids,refreshDeletes=False): deletedEntries = [ uuid for uuid in self.__data.keys() - if uuid not in self.__presentUUIDs and uuid != 'ldap_cookie' + if uuid not in self.__presentUUIDs and uuid != 'cookie' ] self.syncrepl_delete( deletedEntries ) # Phase is now completed, reset the list From 2880183370f99ead12d25ac4683c958555aa1f8b Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 1 Aug 2025 01:57:17 +0200 Subject: [PATCH 339/369] docs(ldapobject): fix typos in docstring (#590) --- Doc/reference/ldap.rst | 2 +- Lib/ldap/ldapobject.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index d059dfa4..4911b7c7 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1364,7 +1364,7 @@ and wait for and return with the server's result, or with This synchronous method implements the LDAP "Who Am I?" extended operation. - It is useful for finding out to find out which identity + It is useful for finding out which identity is assumed by the LDAP server after a SASL bind. .. seealso:: diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 7a9c17f6..290d92b3 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -521,7 +521,7 @@ def result(self,msgid=ldap.RES_ANY,all=1,timeout=None): The method returns a tuple of the form (result_type, result_data). The result_type is one of the constants RES_*. - See search() for a description of the search result's + See search_ext() for a description of the search result's result_data, otherwise the result_data is normally meaningless. The result() method will block for timeout seconds, or @@ -588,7 +588,7 @@ def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverct values are stored in a list as dictionary value. The DN in dn is extracted using the underlying ldap_get_dn(), - which may raise an exception of the DN is malformed. + which may raise an exception if the DN is malformed. If attrsonly is non-zero, the values of attrs will be meaningless (they are not transmitted in the result). From 7af31254dcb22a58686cb140ac320af9a6f967fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Kie=C3=9F?= Date: Wed, 6 Aug 2025 21:05:38 +0200 Subject: [PATCH 340/369] Allow passing None as uri argument to ldap.initialize() (#465) OpenLDAP allows passing NULL to ldap_initialize(). In this case the default URI from ldap.conf will be used. Allow passing None as uri argument to ldap.initialize(). --- Doc/reference/ldap.rst | 3 ++- Modules/functions.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 4911b7c7..a642f579 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -38,7 +38,8 @@ This module defines the following functions: The *uri* parameter may be a comma- or whitespace-separated list of URIs containing only the schema, the host, and the port fields. Note that when using multiple URIs you cannot determine to which URI your client - gets connected. + gets connected. If *uri* is :py:const:`None`, the default URIs from + ``ldap.conf`` or :py:const:`OPT_URI` global option will be used. 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 diff --git a/Modules/functions.c b/Modules/functions.c index f7d9cf37..9a977ff7 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -12,7 +12,7 @@ l_ldap_initialize(PyObject *unused, PyObject *args) int ret; PyThreadState *save; - if (!PyArg_ParseTuple(args, "s:initialize", &uri)) + if (!PyArg_ParseTuple(args, "z:initialize", &uri)) return NULL; save = PyEval_SaveThread(); From 6e9f305ab1ac5d1d83968a7c67f2663df681e7ab Mon Sep 17 00:00:00 2001 From: Florian Best Date: Wed, 6 Aug 2025 22:21:59 +0200 Subject: [PATCH 341/369] Allow to pickle a unbound connection (#588) --- Lib/ldap/ldapobject.py | 8 ++++++-- Tests/t_ldapobject.py | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 290d92b3..e8afb726 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -877,7 +877,10 @@ def __getstate__(self): for k,v in self.__dict__.items() if k not in self.__transient_attrs__ } - state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2] + if self._last_bind is None: + state['_last_bind'] = None + else: + state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2] return state def __setstate__(self,d): @@ -888,7 +891,8 @@ def __setstate__(self,d): 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] + if self._last_bind is not None: + 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))) # XXX cannot pickle file, use default trace file diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index ada5f990..ecf163b7 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -617,12 +617,20 @@ def test103_reconnect_get_state(self): ) def test104_reconnect_restore(self): - l1 = self.ldap_object_class(self.server.ldap_uri) + l0 = self.ldap_object_class(self.server.ldap_uri) + + l0_state = pickle.dumps(l0) + del l0 + l1 = pickle.loads(l0_state) + self.assertEqual(l1.whoami_s(), '') + bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) l1_state = pickle.dumps(l1) del l1 + l2 = pickle.loads(l1_state) self.assertEqual(l2.whoami_s(), 'dn:'+bind_dn) From e4bc42c962a047ba4281ccacdad8254cd89b6f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 20 Nov 2023 10:42:38 +0000 Subject: [PATCH 342/369] Add readthedocs configuration file Running without one has apparently been deprecated since September 2023. --- .readthedocs.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..91fb6028 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: Doc/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: Doc/requirements.txt From d466f394d0c1effc86d76640141d4a99ab98dea7 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 18 Mar 2022 13:13:26 +0100 Subject: [PATCH 343/369] test(ldap.dn): Add test cases for ldap.dn.dn2str() with different format flags --- Tests/t_ldap_dn.py | 203 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 4 deletions(-) diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 86d36403..3d6a950d 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -57,6 +57,7 @@ def test_str2dn(self): test function str2dn() """ self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual(ldap.dn.str2dn(None), []) self.assertEqual( ldap.dn.str2dn('uid=test42,ou=Testing,dc=example,dc=com'), [ @@ -105,7 +106,7 @@ def test_str2dn(self): self.assertEqual( ldap.dn.str2dn('cn=äöüÄÖÜß,dc=example,dc=com', flags=0), [ - [('cn', 'äöüÄÖÜß', 4)], + [('cn', 'äöüÄÖÜß', ldap.AVA_NONPRINTABLE)], [('dc', 'example', 1)], [('dc', 'com', 1)] ] @@ -113,7 +114,16 @@ def test_str2dn(self): self.assertEqual( ldap.dn.str2dn('cn=\\c3\\a4\\c3\\b6\\c3\\bc\\c3\\84\\c3\\96\\c3\\9c\\c3\\9f,dc=example,dc=com', flags=0), [ - [('cn', 'äöüÄÖÜß', 4)], + [('cn', 'äöüÄÖÜß', ldap.AVA_NONPRINTABLE)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('/dc=com/dc=example/ou=Testing/uid=test42', flags=ldap.DN_FORMAT_DCE), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], [('dc', 'example', 1)], [('dc', 'com', 1)] ] @@ -123,7 +133,7 @@ def test_dn2str(self): """ test function dn2str() """ - self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual(ldap.dn.dn2str([]), '') self.assertEqual( ldap.dn.dn2str([ [('uid', 'test42', 1)], @@ -162,12 +172,197 @@ def test_dn2str(self): ) self.assertEqual( ldap.dn.dn2str([ - [('cn', 'äöüÄÖÜß', 4)], + [('uid', 'test, 42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + r'uid=test\2C 42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_NONPRINTABLE)], [('dc', 'example', 1)], [('dc', 'com', 1)] ]), 'cn=äöüÄÖÜß,dc=example,dc=com' ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_NONPRINTABLE)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('cn', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_AD_CANONICAL), + 'example.com/Testing/test42,test42' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('cn', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_UFN), + 'test42 + test42, Testing, example.com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('cn', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_DCE), + '/dc=com/dc=example/ou=Testing/uid=test42,cn=test42' + ) + + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_BINARY)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + 'cn=#C3A4C3B6C3BCC384C396C39CC39F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_NULL)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_STRING)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', ldap.AVA_NONPRINTABLE)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ], ldap.DN_FORMAT_LDAPV3), + r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' + ) + + def test_dn_various_lengths(self): + base = [ + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + + test_lengths = [1, 10, 100, 500] + for n in test_lengths: + rdn_prefix = [ + [('ou', f'unit{i}', 1)] for i in range(n) + ] + full_dn = rdn_prefix + base + full_dn.insert(0, [('uid', f'user{n}', 1)]) + + result = ldap.dn.dn2str(full_dn, ldap.DN_FORMAT_LDAPV3) + + self.assertTrue(result.startswith(f'uid=user{n},')) + self.assertTrue(result.endswith(',dc=example,dc=com')) + self.assertEqual(result.count(','), n + 2) + + def test_dn2str_errors(self): + """ + test error handling of function dn2str() + """ + with self.assertRaises(RuntimeError): + ldap.dn.dn2str([[('uid', 'test42', 1)]], 142) + + DN_FORMAT_LBER = 0xf0 + with self.assertRaises(RuntimeError): + ldap.dn.dn2str([ + [('dc', 'com', 1)] + ], DN_FORMAT_LBER) + + ldap_format = ldap.DN_FORMAT_LDAPV3 + + with self.assertRaises(TypeError): + ldap.dn.dn2str(None) + + with self.assertRaises(TypeError): + ldap.dn.dn2str(None, ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([1], ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([[1]], ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([[('uid', 'test42', '1')]], ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([[('uid', 'test42', 1.0)]], ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([[['uid', 'test42', 1]]], ldap_format) + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('cn', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', '1')], + [('dc', 'com', 1)] + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('uid', 'test42', 1), ('cn', 'test42', '1')], + [('dc', 'com', 1)] + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + [('dc', 'com', None)], + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + [('dc', None, 1)], + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + [(None, 'com', 1)], + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + [None], + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + None, + ], ldap_format), + + with self.assertRaises(TypeError): + ldap.dn.dn2str([ + [('dc', 'example', 1)], + [('dc', 'com', 1), None], + ], ldap_format), def test_explode_dn(self): """ From ef837a246f3141aa8601e21a6c986fb39bdf50a4 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 18 Mar 2022 13:13:26 +0100 Subject: [PATCH 344/369] feat(ldap.dn): Add support for different formats in `ldap.dn.dn2str()` via flags In C `dn2str()` supports `flags` which works by providing one of `LDAP_DN_FORMAT_UFN`, `LDAP_DN_FORMAT_AD_CANONICAL`, `LDAP_DN_FORMAT_DCE`, `LDAP_DN_FORMAT_LDAPV3`. These symbols do exist in Python, but could not be used ultimately because the Python counterpart was pure Python and did not pass to `dn2str(3)`. Fix #257 --- Lib/ldap/dn.py | 12 ++- Modules/functions.c | 217 ++++++++++++++++++++++++++++++++++++++++++++ Tests/t_ldap_dn.py | 6 ++ 3 files changed, 232 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index a9d96846..b466a442 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -48,12 +48,17 @@ def str2dn(dn,flags=0): return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags) -def dn2str(dn): +def dn2str(dn, flags=0): """ This function takes a decomposed DN as parameter and returns - a single string. It's the inverse to str2dn() but will always - return a DN in LDAPv3 format compliant to RFC 4514. + a single string. It's the inverse to str2dn() but will by default always + return a DN in LDAPv3 format compliant to RFC 4514 if not otherwise specified + via flags. + + See also the OpenLDAP man-page ldap_dn2str(3) """ + if flags: + return ldap.functions._ldap_function_call(None, _ldap.dn2str, dn, flags) return ','.join([ '+'.join([ '='.join((atype,escape_dn_chars(avalue or ''))) @@ -61,6 +66,7 @@ def dn2str(dn): for rdn in dn ]) + def explode_dn(dn, notypes=False, flags=0): """ explode_dn(dn [, notypes=False [, flags=0]]) -> list diff --git a/Modules/functions.c b/Modules/functions.c index 9a977ff7..3f7f7eca 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -155,6 +155,222 @@ l_ldap_str2dn(PyObject *unused, PyObject *args) return result; } +/* ldap_dn2str */ + +static void +_free_dn_structure(LDAPDN dn) +{ + if (dn == NULL) + return; + + for (LDAPRDN *rdn = dn; *rdn != NULL; rdn++) { + for (LDAPAVA **avap = *rdn; *avap != NULL; avap++) { + LDAPAVA *ava = *avap; + + if (ava->la_attr.bv_val) { + free(ava->la_attr.bv_val); + } + if (ava->la_value.bv_val) { + free(ava->la_value.bv_val); + } + free(ava); + } + free(*rdn); + } + free(dn); +} + +/* + * Convert a Python list-of-list-of-(str, str, int) into an LDAPDN and + * call ldap_dn2bv to build a DN string. + * + * Python signature: dn2str(dn: list[list[tuple[str, str, int]]], flags: int) -> str + * Returns the DN string on success, or raises TypeError or RuntimeError on error. + */ +static PyObject * +l_ldap_dn2str(PyObject *self, PyObject *args) +{ + PyObject *dn_list = NULL; + int flags = 0; + LDAPDN dn = NULL; + LDAPAVA *ava; + LDAPAVA **rdn; + BerValue str = { 0, NULL }; + PyObject *py_rdn_seq = NULL, *py_ava_item = NULL; + PyObject *py_name = NULL, *py_value = NULL, *py_encoding = NULL; + PyObject *result = NULL; + Py_ssize_t nrdns = 0, navas = 0, name_len = 0, value_len = 0; + int i = 0, j = 0; + int ldap_err; + const char *name_utf8, *value_utf8; + + const char *type_error_message = "expected list[list[tuple[str, str, int]]]"; + + if (!PyArg_ParseTuple(args, "Oi:dn2str", &dn_list, &flags)) { + return NULL; + } + + if (!PySequence_Check(dn_list)) { + PyErr_SetString(PyExc_TypeError, type_error_message); + return NULL; + } + + nrdns = PySequence_Size(dn_list); + if (nrdns < 0) { + PyErr_SetString(PyExc_TypeError, type_error_message); + return NULL; + } + + /* Allocate array of LDAPRDN pointers (+1 for NULL terminator) */ + dn = (LDAPRDN *) calloc((size_t)nrdns + 1, sizeof(LDAPRDN)); + if (dn == NULL) { + PyErr_NoMemory(); + return NULL; + } + + for (i = 0; i < nrdns; i++) { + py_rdn_seq = PySequence_GetItem(dn_list, i); /* New reference */ + if (py_rdn_seq == NULL) { + goto error_cleanup; + } + if (!PySequence_Check(py_rdn_seq)) { + PyErr_SetString(PyExc_TypeError, type_error_message); + goto error_cleanup; + } + + navas = PySequence_Size(py_rdn_seq); + if (navas < 0) { + PyErr_SetString(PyExc_TypeError, type_error_message); + goto error_cleanup; + } + + /* Allocate array of LDAPAVA* pointers (+1 for NULL terminator) */ + rdn = (LDAPAVA **)calloc((size_t)navas + 1, sizeof(LDAPAVA *)); + if (rdn == NULL) { + PyErr_NoMemory(); + goto error_cleanup; + } + + for (j = 0; j < navas; j++) { + py_ava_item = PySequence_GetItem(py_rdn_seq, j); /* New reference */ + if (py_ava_item == NULL) { + goto error_cleanup; + } + /* Expect a 3‐tuple: (name: str, value: str, encoding: int) */ + if (!PyTuple_Check(py_ava_item) || PyTuple_Size(py_ava_item) != 3) { + PyErr_SetString(PyExc_TypeError, type_error_message); + goto error_cleanup; + } + + py_name = PyTuple_GetItem(py_ava_item, 0); /* Borrowed reference */ + py_value = PyTuple_GetItem(py_ava_item, 1); /* Borrowed reference */ + py_encoding = PyTuple_GetItem(py_ava_item, 2); /* Borrowed reference */ + + if (!PyUnicode_Check(py_name) || !PyUnicode_Check(py_value) || !PyLong_Check(py_encoding)) { + PyErr_SetString(PyExc_TypeError, type_error_message); + goto error_cleanup; + } + + name_len = 0; + value_len = 0; + name_utf8 = PyUnicode_AsUTF8AndSize(py_name, &name_len); + value_utf8 = PyUnicode_AsUTF8AndSize(py_value, &value_len); + if (name_utf8 == NULL || value_utf8 == NULL) { + goto error_cleanup; + } + + ava = (LDAPAVA *) calloc(1, sizeof(LDAPAVA)); + + if (ava == NULL) { + PyErr_NoMemory(); + goto error_cleanup; + } + + ava->la_attr.bv_val = (char *)malloc((size_t)name_len + 1); + if (ava->la_attr.bv_val == NULL) { + free(ava); + PyErr_NoMemory(); + goto error_cleanup; + } + memcpy(ava->la_attr.bv_val, name_utf8, (size_t)name_len); + ava->la_attr.bv_val[name_len] = '\0'; + ava->la_attr.bv_len = (ber_len_t) name_len; + + ava->la_value.bv_val = (char *)malloc((size_t)value_len + 1); + if (ava->la_value.bv_val == NULL) { + free(ava->la_attr.bv_val); + free(ava); + PyErr_NoMemory(); + goto error_cleanup; + } + memcpy(ava->la_value.bv_val, value_utf8, (size_t)value_len); + ava->la_value.bv_val[value_len] = '\0'; + ava->la_value.bv_len = (ber_len_t) value_len; + + ava->la_flags = (int)PyLong_AsLong(py_encoding); + if (PyErr_Occurred()) { + /* Encoding conversion failed */ + free(ava->la_attr.bv_val); + free(ava->la_value.bv_val); + free(ava); + goto error_cleanup; + } + + rdn[j] = ava; + Py_DECREF(py_ava_item); + py_ava_item = NULL; + } + + /* Null‐terminate the RDN */ + rdn[navas] = NULL; + + dn[i] = rdn; + Py_DECREF(py_rdn_seq); + py_rdn_seq = NULL; + } + + /* Null‐terminate the DN */ + dn[nrdns] = NULL; + + /* Call ldap_dn2bv to build a DN string */ + ldap_err = ldap_dn2bv(dn, &str, flags); + if (ldap_err != LDAP_SUCCESS) { + PyErr_SetString(PyExc_RuntimeError, ldap_err2string(ldap_err)); + goto error_cleanup; + } + + result = PyUnicode_FromString(str.bv_val); + if (result == NULL) { + goto error_cleanup; + } + + /* Free the memory allocated by ldap_dn2bv */ + ldap_memfree(str.bv_val); + str.bv_val = NULL; + + /* Free our local DN structure */ + _free_dn_structure(dn); + dn = NULL; + + return result; + + error_cleanup: + /* Free any partially built DN structure */ + _free_dn_structure(dn); + dn = NULL; + + /* If ldap_dn2bv allocated something, free it */ + if (str.bv_val) { + ldap_memfree(str.bv_val); + str.bv_val = NULL; + } + + /* Cleanup Python temporaries */ + Py_XDECREF(py_ava_item); + Py_XDECREF(py_rdn_seq); + return NULL; +} + /* ldap_set_option (global options) */ static PyObject * @@ -191,6 +407,7 @@ static PyMethodDef methods[] = { {"initialize_fd", (PyCFunction)l_ldap_initialize_fd, METH_VARARGS}, #endif {"str2dn", (PyCFunction)l_ldap_str2dn, METH_VARARGS}, + {"dn2str", (PyCFunction)l_ldap_dn2str, METH_VARARGS}, {"set_option", (PyCFunction)l_ldap_set_option, METH_VARARGS}, {"get_option", (PyCFunction)l_ldap_get_option, METH_VARARGS}, {NULL, NULL} diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 3d6a950d..abe6acb7 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -255,6 +255,12 @@ def test_dn2str(self): ], ldap.DN_FORMAT_LDAPV3), r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' ) + self.assertEqual( + ldap.dn.dn2str([ + [('c', 'DEU', 1)], # country code only allow two-letters + ], ldap.DN_FORMAT_LDAPV3), + r'c=DEU' + ) def test_dn_various_lengths(self): base = [ From 2e2913b6818f849393e133e923d68f5675451097 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Wed, 4 Jun 2025 08:00:13 +0200 Subject: [PATCH 345/369] feat(ldap.dn): add ldap.dn.normalize() --- Lib/ldap/dn.py | 5 +++++ Tests/t_ldap_dn.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index b466a442..dd7278b6 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -122,3 +122,8 @@ def is_dn(s,flags=0): return False else: return True + + +def normalize(s, flags=0): + """Returns a normalized distinguished name (DN)""" + return dn2str(str2dn(s, flags), flags) diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index abe6acb7..a41b74a0 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -222,7 +222,6 @@ def test_dn2str(self): ], ldap.DN_FORMAT_DCE), '/dc=com/dc=example/ou=Testing/uid=test42,cn=test42' ) - self.assertEqual( ldap.dn.dn2str([ [('cn', 'äöüÄÖÜß', ldap.AVA_BINARY)], @@ -446,6 +445,35 @@ def test_explode_rdn(self): ['cn=äöüÄÖÜß'] ) + def test_normalize(self): + """ + test function normalize() + """ + self.assertEqual( + ldap.dn.normalize('uid = test42 , ou = Testing , dc = example , dc = com', flags=ldap.DN_FORMAT_LDAPV3), + 'uid=test42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.normalize('cn=äöüÄÖÜß,dc=example,dc=com', flags=0), + 'cn=äöüÄÖÜß,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.normalize('cn=#C3A4C3B6C3BCC384C396C39CC39F,dc=example,dc=com', flags=0), + 'cn=äöüÄÖÜß,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.normalize('cn=#C3A4C3B6C3BCC384C396C39CC39F,dc=example,dc=com', flags=ldap.DN_FORMAT_LDAPV3), + 'cn=#C3A4C3B6C3BCC384C396C39CC39F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.normalize('cn=äöüÄÖÜß,dc=example,dc=com', flags=ldap.DN_FORMAT_LDAPV3), + r'cn=\C3\A4\C3\B6\C3\BC\C3\84\C3\96\C3\9C\C3\9F,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.normalize('/ dc = com / dc = example / ou = Testing / uid = test42 , cn = test42', flags=ldap.DN_FORMAT_DCE), + '/dc=com/dc=example/ou=Testing/uid=test42,cn=test42' + ) + if __name__ == '__main__': unittest.main() From 1d978c667a69482f6e4477cd5b512e9c78c06619 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Wed, 11 Jun 2025 21:42:31 +0200 Subject: [PATCH 346/369] style(Tests/t_ldap_dn): use raw strings instead of escape sequences --- Tests/t_ldap_dn.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index a41b74a0..eafa22b3 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -40,17 +40,17 @@ def test_escape_dn_chars(self): test function escape_dn_chars() """ 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('foo=bar'), 'foo\\=bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo,bar'), r'foo\,bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo=bar'), r'foo\=bar') 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('#foobar'), r'\#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(' foobar'), r'\ foobar') + self.assertEqual(ldap.dn.escape_dn_chars(' '), r'\ ') + self.assertEqual(ldap.dn.escape_dn_chars(' '), r'\ \ ') + self.assertEqual(ldap.dn.escape_dn_chars('foobar '), r'foobar\ ') self.assertEqual(ldap.dn.escape_dn_chars('f+o>o,bo\\,b\\ Date: Fri, 13 Nov 2020 23:52:51 +0100 Subject: [PATCH 347/369] test: Implement test cases for reconnection handling test_106_reconnect_restore() handles a SERVER_DOWN exception manually and tries to re-use the connection afterwards again. This established the connection again but did not bind(), so it now raises ldap.INSUFFICIENT_ACCESS. test_107_reconnect_restore() restarts the LDAP server during searches, which causes a UNAVAILABLE exception. --- Lib/slapdtest/_slapdtest.py | 10 ++++- Tests/t_ldapobject.py | 77 ++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 36841110..4110d945 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -467,8 +467,16 @@ def restart(self): """ Restarts the slapd server with same data """ - self._proc.terminate() + self.terminate() self.wait() + self.resume() + + def terminate(self): + """Terminate slapd server""" + self._proc.terminate() + + def resume(self): + """Start slapd server""" self._start_slapd() def wait(self): diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index ecf163b7..87a829a3 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -9,9 +9,13 @@ import os import re import socket +import threading +import time +import traceback import unittest import pickle + # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -639,7 +643,7 @@ def test105_reconnect_restore(self): 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.terminate() self.server.wait() try: l1.whoami_s() @@ -648,9 +652,78 @@ def test105_reconnect_restore(self): else: self.assertEqual(True, False) finally: - self.server._start_slapd() + self.server.resume() self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + def test106_reconnect_restore(self): + """ + The idea of this test is to stop the LDAP server, make a search and ignore the `SERVER_DOWN` exception which happens after the reconnect timeout + and then re-use the same connection when the LDAP server is available again. + After starting the server the LDAP connection can be re-used again as it will reconnect on the next operation. + Prior to fixing PR !267 the connection was reestablished but no `bind()` was done resulting in a anonymous search which caused `INSUFFICIENT_ACCESS` when anonymous seach is disallowed. + """ + lo = self.ldap_object_class(self.server.ldap_uri, retry_max=2, retry_delay=1) + bind_dn = 'cn=user1,' + self.server.suffix + lo.simple_bind_s(bind_dn, 'user1_pw') + + dn = lo.whoami_s()[3:] + + self.server.terminate() + self.server.wait() + + # do a search, wait for the timeout, ignore SERVER_DOWN + with self.assertRaises(ldap.SERVER_DOWN): + lo.search_s(dn, ldap.SCOPE_BASE, '(objectClass=*)') + + self.server.resume() + + # try to use the connection again + lo.search_s(dn, ldap.SCOPE_BASE, '(objectClass=*)') + + def test107_reconnect_restore(self): + """ + The idea of this test is to restart the LDAP-Server while there are ongoing searches. + This causes :class:`ldap.UNAVAILABLE` to be raised (with |OpenLDAP|) for a short time. + To increase the chance of triggering this bug we are starting multiple threads + with a large number of retry attempts in a short amount of time. + """ + excs = [] + thread_count = 10 + run_time = 10.0 + start_barrier = threading.Barrier(thread_count + 1) # +1 for the main thread + + def _reconnect_search_thread(): + lo = self.ldap_object_class(self.server.ldap_uri) + bind_dn = 'cn=user1,' + self.server.suffix + lo.simple_bind_s(bind_dn, 'user1_pw') + lo._retry_max = 10E4 + lo._retry_delay = 0.001 + lo.search_ext_s(self.server.suffix, ldap.SCOPE_SUBTREE, "cn=user1", attrlist=["cn"]) + start_barrier.wait() + end_time = time.time() + run_time + while time.time() < end_time: + lo.search_ext_s(self.server.suffix, ldap.SCOPE_SUBTREE, filterstr="cn=user1", attrlist=["cn"]) + + def reconnect_search_thread(): + try: + _reconnect_search_thread() + except Exception as exc: + excs.append((str(exc), traceback.format_exc())) + + threads = [threading.Thread(target=reconnect_search_thread) for _ in range(thread_count)] + for t in threads: + t.start() + + start_barrier.wait() # wait until all threads are ready to start + self.server.restart() # restart after all threads have started their search loop + + for t in threads: + t.join() + + for exc, tb in excs[:5]: + print('Exception occurred', exc, tb) + self.assertEqual(excs, []) + @requires_init_fd() class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): From f695daa01b18413c008d5fcd398b2d699b70b8e9 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Sat, 16 Mar 2019 19:00:51 +0100 Subject: [PATCH 348/369] fix(ReconnectLDAPObject): Also reconnect on UNAVILABLE, CONNECT_ERROR and TIMEOUT --- Lib/ldap/ldapobject.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index e8afb726..7e7b8158 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -820,8 +820,7 @@ def get_naming_contexts(self): class ReconnectLDAPObject(SimpleLDAPObject): """ :py:class:`SimpleLDAPObject` subclass whose synchronous request methods - automatically reconnect and re-try in case of server failure - (:exc:`ldap.SERVER_DOWN`). + automatically reconnect and re-try in case of server failure. The first arguments are same as for the :py:func:`~ldap.initialize()` function. @@ -833,6 +832,10 @@ class ReconnectLDAPObject(SimpleLDAPObject): * retry_delay: specifies the time in seconds between reconnect attempts. This class also implements the pickle protocol. + + .. versionadded:: 3.5 + The exceptions :py:exc:`ldap.SERVER_DOWN`, :py:exc:`ldap.UNAVAILABLE`, :py:exc:`ldap.CONNECT_ERROR` and + :py:exc:`ldap.TIMEOUT` (configurable via :py:attr:`_reconnect_exceptions`) now trigger a reconnect. """ __transient_attrs__ = { @@ -842,6 +845,7 @@ class ReconnectLDAPObject(SimpleLDAPObject): '_reconnect_lock', '_last_bind', } + _reconnect_exceptions = (ldap.SERVER_DOWN, ldap.UNAVAILABLE, ldap.CONNECT_ERROR, ldap.TIMEOUT) def __init__( self,uri, @@ -974,7 +978,7 @@ def _apply_method_s(self,func,*args,**kwargs): 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: + except self._reconnect_exceptions: # Try to reconnect self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=True) # Re-try last operation From a201133dcd1d4ec052527df5c78ff1c3af5f7e23 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Thu, 7 Aug 2025 09:50:35 +0200 Subject: [PATCH 349/369] fix(controls): make sure msg_id is not undefined in error case --- Lib/ldap/controls/openldap.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py index 24040ed7..26c76868 100644 --- a/Lib/ldap/controls/openldap.py +++ b/Lib/ldap/controls/openldap.py @@ -51,6 +51,7 @@ class SearchNoOpMixIn: """ def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)',timeout=-1): + msg_id = None try: msg_id = self.search_ext( base, @@ -66,9 +67,10 @@ def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*) ldap.TIMELIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED, ldap.ADMINLIMIT_EXCEEDED - ) as e: - self.abandon(msg_id) - raise e + ): + if msg_id is not None: + self.abandon(msg_id) + raise else: noop_srch_ctrl = [ c From a73ce552db1d5250e62ea88999a5b2d480cd8153 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Mon, 14 Jul 2025 04:32:41 +0200 Subject: [PATCH 350/369] docs(ldapobject): fix typo in docstring --- Doc/reference/ldap.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index a642f579..1d095adb 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -605,13 +605,13 @@ The module defines the following exceptions: .. py:exception:: COMPARE_FALSE A compare operation returned false. - (This exception should only be seen asynchronous operations, because + (This exception should only be seen in asynchronous operations, because :py:meth:`~LDAPObject.compare_s()` returns a boolean result.) .. py:exception:: COMPARE_TRUE A compare operation returned true. - (This exception should only be seen asynchronous operations, because + (This exception should only be seen in asynchronous operations, because :py:meth:`~LDAPObject.compare_s()` returns a boolean result.) .. py:exception:: CONFIDENTIALITY_REQUIRED From 8b4912919f13916f10efadb7cb9261c121083ba8 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mon, 6 Oct 2025 19:05:09 -0700 Subject: [PATCH 351/369] Package python-ldap with pyproject.toml (#589) --- .coveragerc | 27 ---------- .gitignore | 2 - Doc/installing.rst | 30 ++++++++--- Doc/spelling_wordlist.txt | 4 ++ INSTALL | 3 +- MANIFEST.in | 2 +- Makefile | 1 - pyproject.toml | 110 ++++++++++++++++++++++++++++++++++++-- setup.py | 74 ++----------------------- tox.ini | 1 + 10 files changed, 140 insertions(+), 114 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 738d86fa..00000000 --- a/.coveragerc +++ /dev/null @@ -1,27 +0,0 @@ -[run] -branch = True -source = - ldap - ldif - ldapurl - slapdtest - -[paths] -source = - Lib/ - .tox/*/lib/python*/site-packages/ - -[report] -ignore_errors = False -precision = 1 -exclude_lines = - pragma: no cover - raise NotImplementedError - if 0: - if __name__ == .__main__.: - if PY2 - if not PY2 - -[html] -directory = build/htmlcov -title = python-ldap coverage report diff --git a/.gitignore b/.gitignore index bab21878..75a13538 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,6 @@ *.pyc __pycache__/ .tox -.coverage* -!.coveragerc /.cache /.pytest_cache diff --git a/Doc/installing.rst b/Doc/installing.rst index 03e7a295..1c7ec8c3 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -76,12 +76,23 @@ The CVS repository of FreeBSD contains the package macOS ----- -You can install directly with pip:: +You can install directly with pip. First install Xcode command line tools:: $ xcode-select --install - $ pip install python-ldap \ - --global-option=build_ext \ - --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl" + +Then install python-ldap:: + + $ pip install python-ldap + +For custom installations, you may need to set environment variables:: + + $ export CPPFLAGS="-I$(xcrun --show-sdk-path)/usr/include/sasl" + $ pip install python-ldap + +If using Homebrew:: + + $ brew install openldap + $ pip install python-ldap .. _install-source: @@ -90,11 +101,14 @@ Installing from Source ====================== -python-ldap is built and installed using the Python setuptools. -From a source repository:: +python-ldap is built and installed using modern Python packaging standards +with pyproject.toml configuration. From a source repository:: + + $ pip install . + +For development installation with editable mode:: - $ python -m pip install setuptools - $ python setup.py install + $ pip install -e . If you have more than one Python interpreter installed locally, you should use the same one you plan to use python-ldap with. diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index e2150d9a..4381ebee 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -60,6 +60,7 @@ func Gohlke GPG Heimdal +Homebrew hostport hrefTarget hrefText @@ -106,6 +107,7 @@ previousDN processResultsCount Proxied py +pyproject pytest rdn readthedocs @@ -146,6 +148,7 @@ syncrepl syntaxes timelimit TLS +toml tracebacks tuple tuples @@ -162,5 +165,6 @@ userPassword usr uuids Valgrind +Xcode whitespace workflow diff --git a/INSTALL b/INSTALL index b9b13d2d..224df4a4 100644 --- a/INSTALL +++ b/INSTALL @@ -1,8 +1,7 @@ Quick build instructions: edit setup.cfg (see Build/ for platform-specific examples) - python setup.py build - python setup.py install + pip install . Detailed instructions are in Doc/installing.rst, or online at: diff --git a/MANIFEST.in b/MANIFEST.in index 687d2b0c..bedea8d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include MANIFEST.in Makefile CHANGES INSTALL LICENCE README TODO -include tox.ini .coveragerc +include tox.ini include Modules/*.c Modules/*.h recursive-include Build *.cfg* recursive-include Lib *.py diff --git a/Makefile b/Makefile index 2b52ddf5..da23b374 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,6 @@ Modules/constants_generated.h: Lib/ldap/constants.py .PHONY: clean clean: rm -rf build dist *.egg-info .tox MANIFEST - rm -f .coverage .coverage.* find . \( -name '*.py[co]' -or -name '*.so*' -or -name '*.dylib' \) \ -delete find . -depth -name __pycache__ -exec rm -rf {} \; diff --git a/pyproject.toml b/pyproject.toml index 75f7c06a..8781155d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,108 @@ +[build-system] +requires = [ + "setuptools", + "setuptools-scm", +] +build-backend = "setuptools.build_meta" + +[project] +name = "python-ldap" +license.text = "python-ldap" # Replace with 'license' once Python 3.8 is dropped +dynamic = ["version"] +description = "Python modules for implementing LDAP clients" +authors = [ + {name = "python-ldap project", email = "python-ldap@python.org"}, +] +readme = "README" +requires-python = ">=3.6" +keywords = ["ldap", "directory", "authentication"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Operating System :: OS Independent", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Programming Language :: C", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "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", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Database", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP", + "License :: OSI Approved :: Python Software Foundation License", +] +dependencies = [ + "pyasn1 >= 0.3.7", + "pyasn1_modules >= 0.1.5", +] + +[project.urls] +Homepage = "https://www.python-ldap.org/" +Documentation = "https://python-ldap.readthedocs.io/" +Repository = "https://github.com/python-ldap/python-ldap" +Download = "https://pypi.org/project/python-ldap/" +Changelog = "https://github.com/python-ldap/python-ldap/blob/main/CHANGES" + + + +[tool.setuptools] +zip-safe = false +include-package-data = true +license-files = ["LICENCE", "LICENCE.MIT"] +# Explicitly list all Python modules +py-modules = ["ldapurl", "ldif"] + +[tool.setuptools.dynamic] +version = {attr = "ldap.pkginfo.__version__"} + +[tool.setuptools.packages.find] +where = ["Lib"] + +[tool.setuptools.package-dir] +"" = "Lib" + [tool.isort] -line_length=88 -known_first_party=['ldap', '_ldap', 'ldapurl', 'ldif', 'slapdtest'] -sections=['FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER'] +line_length = 88 +known_first_party = ["ldap", "_ldap", "ldapurl", "ldif", "slapdtest"] +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] + +[tool.coverage.run] +branch = true +source = [ + "ldap", + "ldif", + "ldapurl", + "slapdtest", +] + +[tool.coverage.paths] +source = [ + "Lib/", + ".tox/*/lib/python*/site-packages/", +] + +[tool.coverage.report] +ignore_errors = false +precision = 1 +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", + "if 0:", + "if __name__ == .__main__.:", + "if PY2", + "if not PY2", +] + +[tool.coverage.html] +directory = "build/htmlcov" +title = "python-ldap coverage report" diff --git a/setup.py b/setup.py index ea7364cd..f2e816be 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,9 @@ """ -setup.py - Setup package with the help Python's DistUtils +setup.py - C extension module configuration for python-ldap See https://www.python-ldap.org/ for details. +This file handles only the C extension modules (_ldap) configuration, +while pyproject.toml handles all project metadata, dependencies, and other settings. """ import sys,os @@ -54,52 +56,8 @@ class OpenLDAP2: LDAP_CLASS.extra_link_args.append('-pg') LDAP_CLASS.libs.append('gcov') -#-- Let distutils/setuptools do the rest -name = 'python-ldap' - +#-- C extension modules configuration only setup( - #-- Package description - name = name, - license=pkginfo.__license__, - version=pkginfo.__version__, - description = 'Python modules for implementing LDAP clients', - long_description = """python-ldap: - python-ldap provides an object-oriented API to access LDAP directory servers - 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.). - """, - author = 'python-ldap project', - author_email = 'python-ldap@python.org', - url = 'https://www.python-ldap.org/', - download_url = 'https://pypi.org/project/python-ldap/', - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'Operating System :: OS Independent', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: C', - - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - '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', - 'Topic :: Internet', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP', - 'License :: OSI Approved :: Python Software Foundation License', - ], - #-- C extension modules ext_modules = [ Extension( '_ldap', @@ -135,28 +93,4 @@ class OpenLDAP2: ] ), ], - #-- Python "stand alone" modules - py_modules = [ - 'ldapurl', - 'ldif', - - ], - packages = [ - 'ldap', - 'ldap.controls', - 'ldap.extop', - 'ldap.schema', - 'slapdtest', - 'slapdtest.certs', - ], - package_dir = {'': 'Lib',}, - data_files = LDAP_CLASS.extra_files, - include_package_data=True, - install_requires=[ - 'pyasn1 >= 0.3.7', - 'pyasn1_modules >= 0.1.5', - ], - zip_safe=False, - python_requires='>=3.9', - test_suite = 'Tests', ) diff --git a/tox.ini b/tox.ini index 0b284a4e..0741ef29 100644 --- a/tox.ini +++ b/tox.ini @@ -33,6 +33,7 @@ commands = {envpython} -bb -Werror \ setenv = CFLAGS=-Wno-int-in-bool-context -Werror -std=c99 + [testenv:py3-nosasltls] basepython = python3 # don't install, install dependencies manually From 6ea80326a34ee6093219628d7690bced50c49a3f Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 10 Oct 2025 10:46:45 -0700 Subject: [PATCH 352/369] Merge commit from fork Update tests to expect \00 and verify RFC-compliant escaping --- Lib/ldap/dn.py | 3 ++- Tests/t_ldap_dn.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index dd7278b6..64d7d0e9 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -26,7 +26,8 @@ def escape_dn_chars(s): s = s.replace('>' ,'\\>') s = s.replace(';' ,'\\;') s = s.replace('=' ,'\\=') - s = s.replace('\000' ,'\\\000') + # RFC 4514 requires NULL (U+0000) to be escaped as hex pair "\00" + s = s.replace('\x00' ,'\\00') if s[-1]==' ': s = ''.join((s[:-1],'\\ ')) if s[0]=='#' or s[0]==' ': diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index eafa22b3..c4d9cb6c 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -49,7 +49,7 @@ def test_escape_dn_chars(self): self.assertEqual(ldap.dn.escape_dn_chars(' '), r'\ ') self.assertEqual(ldap.dn.escape_dn_chars(' '), r'\ \ ') self.assertEqual(ldap.dn.escape_dn_chars('foobar '), r'foobar\ ') - self.assertEqual(ldap.dn.escape_dn_chars('f+o>o,bo\\,b\\o,bo\,b\ Date: Fri, 10 Oct 2025 19:47:46 +0200 Subject: [PATCH 353/369] Merge commit from fork --- Lib/ldap/filter.py | 2 ++ Tests/t_ldap_filter.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py index 782737aa..5bd41b21 100644 --- a/Lib/ldap/filter.py +++ b/Lib/ldap/filter.py @@ -24,6 +24,8 @@ def escape_filter_chars(assertion_value,escape_mode=0): If 1 all NON-ASCII chars are escaped. If 2 all chars are escaped. """ + if not isinstance(assertion_value, str): + raise TypeError("assertion_value must be of type str.") if escape_mode: r = [] if escape_mode==1: diff --git a/Tests/t_ldap_filter.py b/Tests/t_ldap_filter.py index 313b3733..54312050 100644 --- a/Tests/t_ldap_filter.py +++ b/Tests/t_ldap_filter.py @@ -49,6 +49,10 @@ def test_escape_filter_chars_mode1(self): ), r'\c3\a4\c3\b6\c3\bc\c3\84\c3\96\c3\9c\c3\9f' ) + with self.assertRaises(TypeError): + escape_filter_chars(["abc@*()/xyz"], escape_mode=1) + with self.assertRaises(TypeError): + escape_filter_chars({"abc@*()/xyz": 1}, escape_mode=1) def test_escape_filter_chars_mode2(self): """ From 414ae1de91543a1c0fee0738f97fe1a33d0fe666 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Thu, 14 Aug 2025 02:50:35 +0200 Subject: [PATCH 354/369] fix(extop.dds): fix unset RefreshRequest.requestValue >>> from ldap.extop.dds import RefreshRequest >>> req = RefreshRequest(RefreshRequest.requestName, 'uid=temp,dc=freeiam,dc=org', 86400) >>> repr(req) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3/dist-packages/ldap/extop/__init__.py", line 31, in __repr__ return f'{self.__class__.__name__}({self.requestName},{self.requestValue})' ^^^^^^^^^^^^^^^^^ AttributeError: 'RefreshRequest' object has no attribute 'requestValue'. Did you mean: 'requestName'? --- Lib/ldap/extop/dds.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/ldap/extop/dds.py b/Lib/ldap/extop/dds.py index 7fab0813..50695ea4 100644 --- a/Lib/ldap/extop/dds.py +++ b/Lib/ldap/extop/dds.py @@ -35,6 +35,7 @@ class RefreshRequestValue(univ.Sequence): ) def __init__(self,requestName=None,entryName=None,requestTtl=None): + super().__init__(requestName, b'') self.entryName = entryName self.requestTtl = requestTtl or self.defaultRequestTtl From 0506a0f0a1824dd3028910ab3d8c94b0a0cfd039 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Tue, 21 Oct 2025 10:07:14 +0200 Subject: [PATCH 355/369] fix(extop.dds): make passing of requestName optional again requestName is already set at class member. It seems there is code out there which is not giving it as value but passing None. in that case, fallback to the class member. Fixes: 414ae1de91543a1c0fee0738f97fe1a33d0fe666 --- Lib/ldap/extop/dds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/extop/dds.py b/Lib/ldap/extop/dds.py index 50695ea4..a970d71d 100644 --- a/Lib/ldap/extop/dds.py +++ b/Lib/ldap/extop/dds.py @@ -35,7 +35,7 @@ class RefreshRequestValue(univ.Sequence): ) def __init__(self,requestName=None,entryName=None,requestTtl=None): - super().__init__(requestName, b'') + super().__init__(requestName or self.requestName, b'') self.entryName = entryName self.requestTtl = requestTtl or self.defaultRequestTtl From 2f0135cfd6004e8d5a10295fe307c5111e8da8ae Mon Sep 17 00:00:00 2001 From: dotlambda Date: Fri, 10 Oct 2025 16:00:13 -0700 Subject: [PATCH 356/369] remove superfluous dependency on setuptools-scm --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8781155d..77783f8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [build-system] requires = [ "setuptools", - "setuptools-scm", ] build-backend = "setuptools.build_meta" From aca9cb5fdbab78918fe6905cfe1cff8549039c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 25 Jun 2024 12:38:24 +0100 Subject: [PATCH 357/369] Add OpenLDAPSyncreplCookie Fixes: https://github.com/python-ldap/python-ldap/issues/562 --- Doc/reference/ldap-syncrepl.rst | 3 + Lib/ldap/syncrepl.py | 70 +++++++++++++ Lib/slapdtest/_slapdtest.py | 5 + Tests/__init__.py | 1 + Tests/t_ldap_syncrepl.py | 175 +++++++++++++++++++++++++++++++- 5 files changed, 253 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap-syncrepl.rst b/Doc/reference/ldap-syncrepl.rst index b3b2cf9a..046b15a9 100644 --- a/Doc/reference/ldap-syncrepl.rst +++ b/Doc/reference/ldap-syncrepl.rst @@ -20,3 +20,6 @@ This module defines the following classes: .. autoclass:: ldap.syncrepl.SyncreplConsumer :members: + +.. autoclass:: ldap.syncrepl.OpenLDAPSyncreplCookie + :members: diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index fd0c1285..0e1b6a3f 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -4,6 +4,7 @@ See https://www.python-ldap.org/ for project details. """ +from typing import AnyStr, Dict, List, Tuple, Union from uuid import UUID # Imports from pyasn1 @@ -15,6 +16,7 @@ from ldap import RES_SEARCH_RESULT, RES_SEARCH_ENTRY, RES_INTERMEDIATE __all__ = [ + 'OpenLDAPSyncreplCookie', 'SyncreplConsumer', ] @@ -535,3 +537,71 @@ def syncrepl_refreshdone(self): follows. """ pass + + +class OpenLDAPSyncreplCookie: + """ + OpenLDAPSyncreplCookie - allows a consumer to track a cookie across a + refreshAndPersist syncrepl session against a multi-provider OpenLDAP cluster + """ + + rid: int = 0 + sid: int = 0 + _csnset: Dict[int, str] + + def __init__(self, cookie: AnyStr = "") -> None: + self._csnset = {} + + if cookie: + self.update(cookie) + + def _parse_csn(self, csn: str) -> Tuple[str, str, str, str]: + time, order, sid, other = csn.split('#', 3) + return (time, order, sid, other) + + def _parse_cookie(self, cookie: AnyStr) -> Dict[str, Union[str, List[str]]]: + if isinstance(cookie, bytes): + cookie = cookie.decode() + + result = {} + parts = cookie.split(',') + for part in parts: + if part.startswith('rid='): + result['rid'] = part[4:] + elif part.startswith('sid='): + result['sid'] = part[4:] + elif part.startswith('csn='): + result['csn'] = part[4:].split(';') + elif part.startswith('delcsn='): + result['delcsn'] = part[7:] + else: + # Did not recognize this cookie part + pass + return result + + def update(self, cookie: AnyStr): + """ + Update the CSN set based on a cookie we just received, use in + syncrepl_set_cookie() to track the session state. + """ + components = self._parse_cookie(cookie) + for csn in components.get('csn', []): + _, _, sid, _ = self._parse_csn(csn) + if sid not in self._csnset or self._csnset[sid] < csn: + self._csnset[sid] = csn + + return self + + def unparse(self) -> str: + """ + Return the cookie as a string, use in syncrepl_get_cookie() or when + storing the state for later use. + """ + cookie = 'rid={:03},sid={:03x}'.format(self.rid or 0, self.sid or 0) + if self._csnset: + cookie += ',csn=' + cookie += ';'.join(csn for sid, csn in sorted(self._csnset.items())) + return cookie + + def __str__(self): + return self.unparse() diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 4110d945..00764ac3 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -41,6 +41,11 @@ cn: module olcModuleLoad: back_%(database)s +dn: olcDatabase=config,cn=config +objectClass: olcDatabaseConfig +olcDatabase: config +olcRootDN: %(rootdn)s + dn: olcDatabase=%(database)s,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig diff --git a/Tests/__init__.py b/Tests/__init__.py index ea28d0ce..1a6a8836 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -21,3 +21,4 @@ from . import t_untested_mods from . import t_ldap_controls_libldap from . import t_ldap_options +from . import t_ldap_syncrepl diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 6acc82c4..ab134a79 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -13,7 +13,8 @@ import ldap from ldap.ldapobject import SimpleLDAPObject -from ldap.syncrepl import SyncreplConsumer, SyncInfoMessage +from ldap.syncrepl import SyncreplConsumer, SyncInfoMessage, \ + OpenLDAPSyncreplCookie from slapdtest import SlapdObject, SlapdTestCase @@ -37,6 +38,10 @@ olcModuleLoad: back_%(database)s olcModuleLoad: syncprov +dn: olcDatabase=config,cn=config +objectClass: olcDatabaseConfig +olcRootDN: %(rootdn)s + dn: olcDatabase=%(database)s,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig @@ -442,6 +447,174 @@ def setUp(self): self.suffix = self.server.suffix +class TestMPRSyncrepl(BaseSyncreplTests, SlapdTestCase): + class MPRClient(SyncreplClient): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cookie = OpenLDAPSyncreplCookie() + + def syncrepl_set_cookie(self, cookie): + self.cookie.update(cookie) + super().syncrepl_set_cookie(self.cookie.unparse()) + + def setUp(self): + super().setUp() + self.tester = self.MPRClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw, + bytes_mode=False + ) + self.suffix = self.server.suffix + + # An active MPR should not have a sid=000 server in it + if self.server.server_id == 0: + self.skipTest("Server got serverid 0 assigned") + + def test_mpr_refresh_and_persist(self): + """ + Make sure we process cookie updates from a live MPR cluster correctly + """ + # Assumes that server_id is not used before the call to start() + self.server2 = self.server_class() + if self.server.server_id == self.server2.server_id: + self.server2.server_id += 1 + if self.server2.server_id % 4096 == 0: + self.server2.server_id = 1 + + with self.server2 as server2: + tester2 = self.MPRClient( + self.server2.ldap_uri, + self.server2.root_dn, + self.server2.root_pw, + bytes_mode=False + ) + self.addCleanup(tester2.unbind_s) + + self.tester.search( + self.suffix, + 'refreshAndPersist', + ) + + # Run a quick refresh, that shouldn't have any changes. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + # Again, server data should not have changed. + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + # set up replication between both + coords = [(1, self.server.ldap_uri, self.suffix, + self.server.root_dn, self.server.root_pw), + (2, self.server2.ldap_uri, self.suffix, + self.server2.root_dn, self.server2.root_pw), + ] + modifications = [ + (ldap.MOD_ADD, "olcSyncrepl", [ + ('rid=%d provider=%s searchbase="%s" type=refreshAndPersist ' + 'bindmethod=simple binddn="%s" credentials="%s" ' + 'retry="1 +"' % coord).encode() for coord in coords]), + # do we still support 2.4.x? Change to olcMultiProvider if not + (ldap.MOD_REPLACE, "olcMirrorMode", [b"TRUE"]), + ] + + self.tester.modify_s( + "olcDatabase={1}%s,cn=config" % (self.server.database), + modifications) + tester2.modify_s( + "olcDatabase={1}%s,cn=config" % (self.server.database), + modifications) + + tester2.search( + self.suffix, + 'refreshAndPersist', + ) + + # Wait till server2 catches up + while tester2.refresh_done is not True or \ + tester2.cookie.unparse() != self.tester.cookie.unparse(): + try: + poll_result = tester2.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + except ldap.NO_SUCH_OBJECT: + # 2.6+ Allows a refreshAndPersist against an empty DB, but + # with older ones we need to retry until there's at least + # one entry + tester2.search( + self.suffix, + 'refreshAndPersist', + ) + + # Again, server data should not have changed. + self.assertEqual(tester2.dn_attrs, LDAP_ENTRIES) + + # From here on, things get little hairy, server1 might not have + # finished its refresh from 2 and we can't easily confirm this + # without cn=monitor. We just read back our CSNs and make sure + # we've seen both. + + # send some mods to both + modification = [('objectClass', [b'device'])] + self.tester.add_s("cn=server1,%s" % self.suffix, modification) + + csn1 = self.tester.read_s("cn=server1,%s" % self.suffix, + attrlist=['entryCSN'] + )['entryCSN'][0].decode('utf8') + + tester2.add_s("cn=server2,%s" % self.suffix, modification) + csn2 = tester2.read_s("cn=server2,%s" % self.suffix, + attrlist=['entryCSN'] + )['entryCSN'][0].decode('utf8') + + new_state = LDAP_ENTRIES.copy() + new_state["cn=server1,%s" % self.suffix] = { + "objectClass": [b"device"], + "cn": [b"server1"], + } + new_state["cn=server2,%s" % self.suffix] = { + "objectClass": [b"device"], + "cn": [b"server2"], + } + + # Wait for the cookie to sync up, a failure would be that this + # doesn't happen, so impose a timeout + while csn1 not in self.tester.cookie.unparse() or \ + csn2 not in self.tester.cookie.unparse() or \ + csn1 not in tester2.cookie.unparse() or \ + csn2 not in tester2.cookie.unparse(): + if csn1 not in self.tester.cookie.unparse() or \ + csn2 not in self.tester.cookie.unparse(): + poll_result = self.tester.poll( + all=0, + timeout=5 + ) + self.assertTrue(poll_result) + if csn1 not in tester2.cookie.unparse() or \ + csn2 not in tester2.cookie.unparse(): + poll_result = tester2.poll( + all=0, + timeout=5 + ) + self.assertTrue(poll_result) + + self.assertEqual(self.tester.cookie.unparse(), + tester2.cookie.unparse()) + self.assertEqual(self.tester.dn_attrs, new_state) + self.assertEqual(tester2.dn_attrs, new_state) + + # self.tester seems to have been unbound by the time + # self.addCleanup callbacks get called? Cleanup manually... + self.tester.delete_s("cn=server1,%s" % self.suffix) + self.tester.delete_s("cn=server2,%s" % self.suffix) + + class DecodeSyncreplProtoTests(unittest.TestCase): """ Tests of the ASN.1 decoder for tricky cases or past issues to ensure that From 886139877811aaefedff400d85fd81c545385893 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mon, 6 Oct 2025 19:39:49 -0700 Subject: [PATCH 358/369] Prepare a new release cherry-pick bf666e918615b00dbcd1bbe71542522eedfdffc1 --- CHANGES | 29 +++++++++++++++++++++++++++++ Lib/ldap/cidict.py | 6 +++--- Lib/ldap/ldapobject.py | 2 +- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 7 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 0491b6ef..05fcf4b6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,32 @@ +Released 3.4.5 2025-10-10 + +Security fixes: +* CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m): Enforce ``str`` input in + ``ldap.filter.escape_filter_chars`` with ``escape_mode=1``; ensure proper + escaping. (thanks to lukas-eu) +* CVE-2025-61912 (GHSA-p34h-wq7j-h5v6): Correct NUL escaping in + ``ldap.dn.escape_dn_chars`` to ``\00`` per RFC 4514. (thanks to aradona91) + +Fixes: +* ReconnectLDAPObject now properly reconnects on UNAVAILABLE, CONNECT_ERROR + and TIMEOUT exceptions (previously only SERVER_DOWN), fixing reconnection + issues especially during server restarts +* Fixed syncrepl.py to use named constants instead of raw decimal values + for result types +* Fixed error handling in SearchNoOpMixIn to prevent a undefined variable error + +Tests: +* Added comprehensive reconnection test cases including concurrent operation + handling and server restart scenarios + +Doc/ +* Updated installation docs and fixed various documentation typos +* Added ReadTheDocs configuration file + +Infrastructure: +* Add testing and document support for Python 3.13 + +---------------------------------------------------------------- Released 3.4.4 2022-11-17 Fixes: diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index f846fd29..65041e0a 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.5", + "strlist functions are deprecated and will be removed in 4.0", 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.5", + "strlist functions are deprecated and will be removed in 4.0", 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.5", + "strlist functions are deprecated and will be removed in 4.0", category=DeprecationWarning, stacklevel=2, ) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 7e7b8158..c94df89d 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -833,7 +833,7 @@ class ReconnectLDAPObject(SimpleLDAPObject): This class also implements the pickle protocol. - .. versionadded:: 3.5 + .. versionadded:: 3.4.5 The exceptions :py:exc:`ldap.SERVER_DOWN`, :py:exc:`ldap.UNAVAILABLE`, :py:exc:`ldap.CONNECT_ERROR` and :py:exc:`ldap.TIMEOUT` (configurable via :py:attr:`_reconnect_exceptions`) now trigger a reconnect. """ diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 18ead66c..2ac6852d 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.4' +__version__ = '3.4.5' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index b4dfd890..57900028 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.4' +__version__ = '3.4.5' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index fa41321c..7bfe5b4c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.4' +__version__ = '3.4.5' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 7c410180..0fabc4c4 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.4' +__version__ = '3.4.5' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 7ab1fa99e95710f9007d5cd5736fd383cb0863c6 Mon Sep 17 00:00:00 2001 From: Iwan Date: Sat, 17 Jan 2026 15:26:55 +0100 Subject: [PATCH 359/369] fix(ldap.schema): Explicitly close url file to avoid ResourceWarning in Python 3.14 --- Lib/ldap/schema/subentry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index b83d819b..3f73df71 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -476,10 +476,10 @@ def urlfetch(uri,trace_level=0): l.unbind_s() del l else: - ldif_file = urlopen(uri) - ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) - ldif_parser.parse() - subschemasubentry_dn,s_temp = ldif_parser.all_records[0] + with urlopen(uri) as ldif_file: + ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) + ldif_parser.parse() + subschemasubentry_dn,s_temp = ldif_parser.all_records[0] # Work-around for mixed-cased attribute names subschemasubentry_entry = ldap.cidict.cidict() s_temp = s_temp or {} From 142a9ca2f3b01ce8e52a298abae1b542f657a393 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Wed, 6 May 2026 18:04:04 +0200 Subject: [PATCH 360/369] fix(ldif): explicitly close sockets after fetching URLs fixes resource warnings in Python 3.14 --- Lib/ldif.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/ldif.py b/Lib/ldif.py index 7bfe5b4c..356f95ea 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -373,7 +373,8 @@ def _next_key_and_value(self): if self._process_url_schemes: u = urlparse(url) if u[0] in self._process_url_schemes: - attr_value = urlopen(url).read() + with urlopen(url) as fd: + attr_value = fd.read() else: # All values should be valid ascii; we support UTF-8 as a # non-official, backwards compatibility layer. From de9119d7a0eb7a81931206f756003800bab5766f Mon Sep 17 00:00:00 2001 From: Florian Best Date: Wed, 6 May 2026 18:26:15 +0200 Subject: [PATCH 361/369] ci(github): update github actions > Ubuntu with Python pypy3.10 > Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4, actions/setup-python@v5. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ --- .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 06d0b2ed..36fde319 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - "ubuntu-22.04" steps: - name: Checkout - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Install apt dependencies run: | set -ex @@ -40,7 +40,7 @@ jobs: - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index bc6f45c5..cef8df1a 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@v4 + - uses: actions/checkout@v6 - name: Run Tox tests uses: fedora-python/tox-github-action@main with: From 54be2ee27426d81b6cd7a897b872e9401292c2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 29 Oct 2025 13:31:06 +0000 Subject: [PATCH 362/369] test: Switch to using slapd + -T for tool use Using a locally compiled OpenLDAP impossible without this as libtool wrappers eat argv[0] of the symlinked binaries. Using a locally compiled OpenLDAP still often needs changes to the configuration, this should be a reasonably easy step forward. --- Lib/slapdtest/_slapdtest.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 00764ac3..1563659b 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -264,7 +264,6 @@ 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: @@ -281,7 +280,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(cmd, var_name) + "override slapdtest's search path: {}.".format(cmd, var_name, path) ) return command @@ -352,6 +351,7 @@ def gen_config(self): 'cafile': self.cafile, 'servercert': self.servercert, 'serverkey': self.serverkey, + 'slapd_path': self.SBIN_PATH, } return self.slapd_conf_template % config_dict @@ -513,14 +513,17 @@ def _cli_auth_args(self): # no cover to avoid spurious coverage changes def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, - stdin_data=None): # pragma: no cover + stdin_data=None, tool=None): # pragma: no cover if ldap_uri is None: ldap_uri = self.default_ldap_uri if ldapcommand.split("/")[-1].startswith("ldap"): args = [ldapcommand, '-H', ldap_uri] + self._cli_auth_args() else: - args = [ldapcommand, '-F', self._slapd_conf] + if tool: + args = [ldapcommand, '-T', tool, '-F', self._slapd_conf] + else: + args = [ldapcommand, '-F', self._slapd_conf] args += (extra_args or []) @@ -579,9 +582,10 @@ def slapadd(self, ldif, extra_args=None): Runs slapadd on this slapd instance, passing it the ldif content """ self._cli_popen( - self.PATH_SLAPADD, + self.PATH_SLAPD, stdin_data=ldif.encode("utf-8") if ldif else None, extra_args=extra_args, + tool='add' ) def __enter__(self): From 0dcaa430f2234abb6cbf53113b0f31b30e68b0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 29 Oct 2025 13:40:10 +0000 Subject: [PATCH 363/369] test: Store logs and keep failed run data on failure --- Lib/slapdtest/_slapdtest.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 1563659b..b60313b0 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -412,12 +412,17 @@ def _start_slapd(self): '-F', self._slapd_conf, '-h', ' '.join(urls), ] + stderr = None if self._log.isEnabledFor(logging.DEBUG): slapd_args.extend(['-d', '-1']) + stderr = os.open(os.path.join(self.testrundir, 'slapd.log'), os.O_WRONLY|os.O_CREAT) else: slapd_args.extend(['-d', '0']) self._log.info('starting slapd: %r', ' '.join(slapd_args)) - self._proc = subprocess.Popen(slapd_args) + self._proc = subprocess.Popen(slapd_args, stderr=stderr) + if stderr is not None: + os.close(stderr) + stderr = None # Waits until the LDAP server socket is open, or slapd crashed deadline = time.monotonic() + 10 # no cover to avoid spurious coverage changes, see @@ -457,7 +462,7 @@ def start(self): self._proc.pid, self.ldap_uri, self.ldapi_uri ) - def stop(self): + def stop(self, cleanup=True): """ Stops the slapd server, and waits for it to terminate and cleans up """ @@ -465,7 +470,8 @@ def stop(self): self._log.debug('stopping slapd with pid %d', self._proc.pid) self._proc.terminate() self.wait() - self._cleanup_rundir() + if cleanup: + self._cleanup_rundir() atexit.unregister(self.stop) def restart(self): @@ -593,7 +599,7 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - self.stop() + self.stop(exc_type is None) class SlapdTestCase(unittest.TestCase): @@ -622,4 +628,4 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.server.stop() + cls.server.stop(False) From 7df168b90581ebf5a1821440f3bfb97b152ac4db Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 8 Aug 2025 09:19:45 +0200 Subject: [PATCH 364/369] feat(ldapobject): allow passing `uri=None` in `SimpleLDAPObject` and `ReconnectLDAPObject` to be consistent with `initialize()`. Fixes: 7af31254dcb22a58686cb140ac320af9a6f967fc Issue: #465 --- 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 c94df89d..fdbd09bc 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -67,7 +67,7 @@ class SimpleLDAPObject: } def __init__( - self,uri, + self,uri=None, trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, bytes_strictness=None, fileno=None ): @@ -848,7 +848,7 @@ class ReconnectLDAPObject(SimpleLDAPObject): _reconnect_exceptions = (ldap.SERVER_DOWN, ldap.UNAVAILABLE, ldap.CONNECT_ERROR, ldap.TIMEOUT) def __init__( - self,uri, + self,uri=None, trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, bytes_strictness=None, retry_max=1, retry_delay=60.0, fileno=None ): From 4364ede2b6b2da7bbec474548b6c5850c0d08669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=B6niger?= Date: Sun, 7 Jan 2024 09:44:32 +0100 Subject: [PATCH 365/369] test: Test valid and invalid attrlist parameters Add tests test_valid_attrlist_parameter_types and test_invalid_attrlist_parameter_types which test the behaviour when passing different Python types. All iterables which return only strings should pass, everything else should raise a TypeError. --- Tests/t_ldapobject.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 87a829a3..ab334b7b 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -478,6 +478,7 @@ def test_dse(self): [self.server.suffix.encode('utf-8')] ) + def test_compare_s_true(self): base = self.server.suffix l = self._ldap_conn @@ -569,6 +570,38 @@ def test_slapadd(self): ("myAttribute", b'foobar'), ]) + def test_valid_attrlist_parameter_types(self): + """Tests the case when a valid parameter type is passed to search_ext + + Any iterable which only contains strings should not raise any errors. + """ + + l = self._ldap_conn + + valid_attrlist_parameters = [{"a": "2"}, ["a", "b"], {}, set(), set(["a", "b"])] + + for attrlist in valid_attrlist_parameters: + out = l.search_ext( + "%s" % self.server.suffix, ldap.SCOPE_SUBTREE, attrlist=attrlist + ) + + def test_invalid_attrlist_parameter_types(self): + """Tests the case when an invalid parameter type is passed to search_ext + + Any object type that is either not a interable or does contain something + that isn't a string should raise a TypeError. The exception is the string type itself. + """ + + invalid_attrlist_parameters = [{1: 2}, 0, object(), "string"] + + l = self._ldap_conn + + for attrlist in invalid_attrlist_parameters: + with self.assertRaises(TypeError): + l.search_ext( + "%s" % self.server.suffix, ldap.SCOPE_SUBTREE, attrlist=attrlist + ) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ From f3f792e70d0d231efafb459d346e9f8e4bc1ca65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20K=C3=B6niger?= Date: Sun, 7 Jan 2024 10:14:55 +0100 Subject: [PATCH 366/369] fix(LDAPObject): Prevent memory errors in attrs_from_List Function `PySequence_Length` can return -1 on iterables like `dict`. The following PyMem_NEW still succeeds due `PyMem_NEW(char *, -1 + 1)` being equivalent to `char** PyMem_Malloc(1)`, which then can result in a segmentation fault later on. Solution: Use `seq` and `PySequence_Size` to determine the size of the sequence. This way any iterable which contains only strings can be used. Co-authored-by: Christian Heimes --- Modules/LDAPObject.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 71fac73e..a008b5f9 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -288,7 +288,10 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) if (seq == NULL) goto error; - len = PySequence_Length(attrlist); + len = PySequence_Size(seq); + if (len == -1) { + goto error; + } attrs = PyMem_NEW(char *, len + 1); From e6439247eed77dd6ea3bb03b8241ac02278ae62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 5 Oct 2023 12:23:57 +0100 Subject: [PATCH 367/369] feat(ldap): retrieve libldap version on load --- Modules/constants.c | 8 ++++++++ Modules/ldapmodule.c | 4 ++++ Modules/pythonldap.h | 2 ++ 3 files changed, 14 insertions(+) diff --git a/Modules/constants.c b/Modules/constants.c index f0a0da94..64804e8d 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -229,6 +229,14 @@ LDAPinit_constants(PyObject *m) if (PyModule_AddIntConstant(m, "LIBLDAP_R", thread_safe) != 0) return -1; + if (ldap_get_option(NULL, LDAP_OPT_API_INFO, &ldap_version_info) != LDAP_SUCCESS) { + PyErr_SetString(PyExc_ImportError, "unrecognised libldap version"); + return -1; + } + if (PyModule_AddIntConstant(m, "_VENDOR_VERSION_RUNTIME", + ldap_version_info.ldapai_vendor_version ) != 0) + return -1; + /* Generated constants -- see Lib/ldap/constants.py */ #define add_err(n) do { \ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index cb3f58fb..d0735356 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -5,6 +5,10 @@ #define _STR(x) #x #define STR(x) _STR(x) +LDAPAPIInfo ldap_version_info = { + .ldapai_info_version = LDAP_API_INFO_VERSION, +}; + static char version_str[] = STR(LDAPMODULE_VERSION); static char author_str[] = STR(LDAPMODULE_AUTHOR); static char license_str[] = STR(LDAPMODULE_LICENSE); diff --git a/Modules/pythonldap.h b/Modules/pythonldap.h index 7703af5e..35ed0d92 100644 --- a/Modules/pythonldap.h +++ b/Modules/pythonldap.h @@ -71,6 +71,8 @@ PYLDAP_FUNC(PyObject *) LDAPerror(LDAP *); PYLDAP_FUNC(PyObject *) LDAPraise_for_message(LDAP *, LDAPMessage *m); PYLDAP_FUNC(PyObject *) LDAPerr(int errnum); +PYLDAP_DATA(LDAPAPIInfo) ldap_version_info; + #ifndef LDAP_CONTROL_PAGE_OID #define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" #endif /* !LDAP_CONTROL_PAGE_OID */ From 6e471e3b828dc8a382086c7d54a4f8d3b4fd3a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 21 Aug 2023 19:09:58 +0100 Subject: [PATCH 368/369] feat(ldapobject): Add wrapper for ldap_connect --- Lib/ldap/ldapobject.py | 7 +++++++ Modules/LDAPObject.c | 33 +++++++++++++++++++++++++++++++++ Tests/t_cext.py | 23 +++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index fdbd09bc..057fe71a 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -171,6 +171,13 @@ def fileno(self): """ return self.get_option(ldap.OPT_DESC) + def connect(self): + """ + connect() -> None + Establishes LDAP connection if needed. + """ + return self._ldap_call(self._l.connect) + def abandon_ext(self,msgid,serverctrls=None,clientctrls=None): """ abandon_ext(msgid[,serverctrls=None[,clientctrls=None]]) -> None diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index a008b5f9..f96a6068 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1475,6 +1475,38 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args) return PyLong_FromLong(msgid); } +/* ldap_connect */ + +static PyObject * +l_ldap_connect(LDAPObject *self, PyObject Py_UNUSED(args)) +{ +#if LDAP_VENDOR_VERSION >= 20500 + int ldaperror; + + if (ldap_version_info.ldapai_vendor_version < 20500) +#endif + { + PyErr_SetString(PyExc_NotImplementedError, + "loaded libldap doesn't support this feature"); + return NULL; + } + +#if LDAP_VENDOR_VERSION >= 20500 + if (not_valid(self)) + return NULL; + + LDAP_BEGIN_ALLOW_THREADS(self); + ldaperror = ldap_connect(self->ldap); + LDAP_END_ALLOW_THREADS(self); + + if ( ldaperror != LDAP_SUCCESS ) + return LDAPerror(self->ldap); + + Py_INCREF(Py_None); + return Py_None; +#endif +} + /* methods */ static PyMethodDef methods[] = { @@ -1504,6 +1536,7 @@ static PyMethodDef methods[] = { {"cancel", (PyCFunction)l_ldap_cancel, METH_VARARGS}, #endif {"extop", (PyCFunction)l_ldap_extended_operation, METH_VARARGS}, + {"connect", (PyCFunction)l_ldap_connect, METH_NOARGS}, {NULL, NULL} }; diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 33fbf29a..846127f8 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -280,6 +280,29 @@ def test_simple_anonymous_bind(self): self.assertEqual(pmsg, []) self.assertEqual(ctrls, []) + @unittest.skipUnless( + _ldap.VENDOR_VERSION >= 20500 and \ + _ldap._VENDOR_VERSION_RUNTIME >= 20500, + reason="Test requires libldap 2.5+" + ) + def test_connect(self): + l = self._open_conn(bind=False) + invalid_fileno = l.get_option(_ldap.OPT_DESC) + l.connect() + fileno = l.get_option(_ldap.OPT_DESC) + self.assertNotEqual(invalid_fileno, fileno) + + self._bind_conn(l) + + @unittest.skipUnless( + _ldap._VENDOR_VERSION_RUNTIME < 20500, + reason="Test requires linking to libldap < 2.5" + ) + def test_connect_notimpl(self): + l = self._open_conn(bind=False) + with self.assertRaises(NotImplementedError): + l.connect() + def test_anon_rootdse_search(self): l = self._open_conn(bind=False) # see if we can get the rootdse with anon search (without prior bind) From bd877c978d4f029737c033139d7c8429950a8ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Fri, 6 Oct 2023 11:21:06 +0100 Subject: [PATCH 369/369] feat(ldapobject): Add documentation for LDAPObject.connect() --- Doc/reference/ldap.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1d095adb..397b7663 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -973,6 +973,15 @@ and wait for and return with the server's result, or with The *dn* and *attr* arguments are text strings; see :ref:`bytes_mode`. +.. py:method:: LDAPObject.connect() -> None + + Opens a connection to the server if one is not established already. If that + fails, an instance of :py:exc:`ldap.LDAPError` is raised. + + Requires libldap 2.5+ and will fail with :py:exc:`NotImplementedError` + if that is not met. + + .. py:method:: LDAPObject.delete(dn) -> int .. py:method:: LDAPObject.delete_s(dn) -> None