From c78c61c93342be4d4e3af24a067d79853739977d Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 29 May 2020 12:10:09 +0200 Subject: [PATCH 1/2] 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 00afc3f26fae568ea751074d8d4c937e9ff0929b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 29 May 2020 12:09:55 +0200 Subject: [PATCH 2/2] 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 f4381212..56529dad 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 a92b0886..4586740a 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 @@ -1086,7 +1093,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 @@ -1102,7 +1109,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 a64b5f0c..01d35457 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 @@ -754,5 +758,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), ] ), ], pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy