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