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/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2f835d76 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +--- +name: CI + +on: + push: + pull_request: + schedule: + # every Monday + - cron: '30 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + distros: + name: "Ubuntu with Python ${{ matrix.python-version }}" + runs-on: "${{ matrix.image }}" + strategy: + fail-fast: false + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "pypy3.9" + - "pypy3.10" + image: + - "ubuntu-22.04" + include: + - python-version: "3.6" + image: "ubuntu-20.04" + steps: + - name: Checkout + uses: "actions/checkout@v4" + - name: Install apt dependencies + run: | + set -ex + sudo apt update + 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 }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - 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/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml new file mode 100644 index 00000000..4c4c18f0 --- /dev/null +++ b/.github/workflows/tox-fedora.yml @@ -0,0 +1,40 @@ +on: [push, pull_request] + +name: Tox on Fedora + +permissions: + contents: read + +jobs: + tox_test: + name: Tox env "${{matrix.tox_env}}" on Fedora + steps: + - uses: actions/checkout@v4 + - name: Run Tox tests + uses: fedora-python/tox-github-action@main + with: + tox_env: ${{ matrix.tox_env }} + dnf_install: > + @c-development openldap-devel python3-devel + openldap-servers openldap-clients lcov clang-analyzer valgrind + enchant python3-setuptools + 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 diff --git a/.gitignore b/.gitignore index 962248fe..bab21878 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Auto-generated .*.swp *.pyc @@ -7,6 +6,7 @@ __pycache__/ .coverage* !.coveragerc /.cache +/.pytest_cache # shared libs installed by 'setup.py test' /Lib/*.so* @@ -21,3 +21,6 @@ PKG-INFO # generated in the sample workflow /__venv__/ + +# test dirs +python-ldap-test-* diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 00000000..4e8adc1b --- /dev/null +++ b/.indent.pro @@ -0,0 +1,31 @@ +--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 +-T PyMethodDef +-T LDAP +-T LDAPMod +-T LDAPMessage +-T LDAPControl +-T LDAPObject +-T sasl_interact_t diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5479573f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,76 +0,0 @@ -language: python - -sudo: false - -cache: pip - -addons: - apt: - packages: - - ldap-utils - - slapd - - enchant - -# 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 - env: - - TOXENV=pypy - - python: 3.7-dev - env: - - TOXENV=py37 - - 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 - allow_failures: - - env: - - TOXENV=py37 - - WITH_GCOV=1 - - env: - - TOXENV=pypy - -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="-std=c90 -Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" - # 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" - - pip install tox-travis tox codecov coverage - -script: tox - -after_success: - # gather Python coverage - - python -m coverage combine - # send Python and GCOV coverage - - codecov 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/CHANGES b/CHANGES index 7a5e102d..0491b6ef 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,256 @@ +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. +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 +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, +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`` + +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, + ``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: +* ``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 + +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 + +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 diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 68d3643e..9db52c84 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -1,11 +1,9 @@ -from __future__ import print_function - import ldap,ldap.async 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/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/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/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 59c594ff..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 @@ -61,4 +60,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..7a8ee9db 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ldap host="localhost:1390" @@ -24,7 +22,3 @@ #print("time limit:",l.get_option(ldap.OPT_TIMELIMIT)) print("Binding...") l.simple_bind_s("","") - - - - 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/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/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 10faa2b2..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 @@ -39,8 +38,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 +55,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 +66,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 +82,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 +93,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 +107,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/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 33ddddab..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' @@ -58,4 +56,3 @@ ldap_url.attrs or ['*'], serverctrls=[st_ctrl] ) - 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/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 61d63ad9..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 @@ -93,8 +91,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/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 4d350f02..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 @@ -47,7 +46,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..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 @@ -15,9 +14,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) @@ -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]: @@ -98,4 +97,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..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", [""]) @@ -97,11 +96,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..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 @@ -50,7 +49,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 +99,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 +124,3 @@ except: print_exc() - diff --git a/Doc/bytes_mode.rst b/Doc/bytes_mode.rst index d7c2aa4f..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,103 +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 an ``unicode`` value supplied to it, if 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 `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/ - - -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/conf.py b/Doc/conf.py index eb9f5a01..e79cfb34 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. @@ -11,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 @@ -31,7 +31,11 @@ # 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', + 'sphinx.ext.napoleon', +] try: import sphinxcontrib.spelling @@ -46,12 +50,12 @@ # 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' -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. @@ -148,3 +152,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/contributing.rst b/Doc/contributing.rst index 9061332d..de63a2e3 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! @@ -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`_. @@ -102,6 +99,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 ``black`` 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_. @@ -132,7 +140,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:: @@ -192,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. @@ -207,13 +213,18 @@ 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``). -* 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. * 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 diff --git a/Doc/faq.rst b/Doc/faq.rst index ef479a87..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 @@ -26,10 +26,10 @@ 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)? +**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. @@ -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. @@ -65,6 +65,10 @@ What's happening here? 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 @@ -80,6 +84,11 @@ What's happening here? `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 @@ -114,7 +123,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 bb06c212..03e7a295 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 ==================== @@ -22,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` @@ -34,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 @@ -70,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 `_ @@ -80,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:: @@ -106,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: @@ -120,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. @@ -139,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 ------ @@ -152,19 +143,28 @@ 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 python2.7-dev \ - libldap2-dev libsasl2-dev slapd ldap-utils python-tox \ + # apt-get install build-essential ldap-utils \ + libldap2-dev libsasl2-dev slapd python3-dev tox \ lcov valgrind +.. note:: + + On older releases ``tox`` was called ``python-tox``. + 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:: @@ -173,14 +173,13 @@ 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 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 @@ -253,4 +252,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-controls.rst b/Doc/reference/ldap-controls.rst index 1520b241..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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -197,6 +198,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: @@ -206,3 +210,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/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..ad70e4e7 100644 --- a/Doc/reference/ldap-extop.rst +++ b/Doc/reference/ldap-extop.rst @@ -39,3 +39,4 @@ This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. .. autoclass:: ldap.extop.dds.RefreshResponse :members: +.. |ASN.1| replace:: Asn1Type 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 5d15158e..d059dfa4 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -29,10 +29,10 @@ 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, [fileno=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 @@ -40,6 +40,18 @@ 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://``. + 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) `_ is called which just initializes the LDAP connection struct in the C API @@ -53,42 +65,33 @@ 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, :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. - .. seealso:: - - :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator + 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. -.. py:function:: open(host [, port=PORT]) -> LDAPObject object + The function additionally takes *bytes_mode* and *bytes_strictness* keyword + arguments, which are deprecated and ignored. See :ref:`bytes_mode` for + details. - 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). + .. seealso:: - .. deprecated:: 3.0 + :rfc:`4516` - Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator - ``ldap.open()`` is deprecated. It will be removed in version 3.1. Use - :func:`ldap.initialize()` with a URI like ``'ldap://:'`` - instead. + .. versionadded:: 3.3 -.. py:function:: init(host [, port=PORT]) -> LDAPObject object + The *fileno* argument was added. - Alias of :func:`ldap.open()`. + .. deprecated:: 3.4 - .. deprecated:: 3.0 + *bytes_mode* and *bytes_strictness* arguments are deprecated. - ``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 +103,16 @@ 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. + .. _ldap-constants: @@ -128,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: @@ -139,9 +158,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 @@ -207,6 +226,9 @@ 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 @@ -215,7 +237,7 @@ 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. .. py:data:: OPT_X_SASL_REALM @@ -234,33 +256,237 @@ 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 + + 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_ALLOW .. 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_REQUIRE_SAN + + get/set how OpenLDAP validates subject alternative name extension, + available in OpenLDAP 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` + 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 -.. py:data:: OPT_X_TLS_KEYFILE + 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. + + +.. 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 (DER) + + .. versionadded:: 3.4.1 + + .. note:: + The option leaks memory with OpenLDAP < 2.5.8. + +.. py:data:: OPT_X_TLS_PROTOCOL_MIN + + get/set minimum protocol version (wire protocol version as int) + +.. py:data:: OPT_X_TLS_PROTOCOL_MAX + + get/set maximum protocol version (wire protocol version as int), + available in OpenLDAP 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 + + 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:: + + 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: @@ -324,16 +550,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 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 @@ -367,14 +604,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 @@ -471,10 +708,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 @@ -578,52 +811,46 @@ The above exceptions are raised when a result code from an underlying API call does not indicate success. +.. _ldap-warnings: + 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: LDAPObject classes ================== -.. py:class:: LDAPObject +.. py:class:: ldap.ldapobject.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. + :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()`. - The first arguments are same like for function :py:func:`initialize()`. + (It is also possible, but not recommended, to change the default by setting + ``ldap.ldapobject.LDAPObject`` to a different class.) - For automatic reconnects it has additional arguments: +.. autoclass:: ldap.ldapobject.SimpleLDAPObject - *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. +.. autoclass:: ldap.ldapobject.ReconnectLDAPObject .. _ldap-controls: @@ -722,18 +949,19 @@ 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 - :py:meth:`result()`. + 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()`. 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 @@ -743,11 +971,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 @@ -842,7 +1065,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*. @@ -853,6 +1076,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. @@ -862,6 +1092,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` @@ -986,7 +1217,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`. @@ -1097,7 +1328,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 @@ -1238,4 +1469,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..eb2106b3 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:: @@ -66,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 ^^^^^^^^^^^^^^^^^^^ @@ -124,4 +128,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..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 @@ -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/reference/slapdtest.rst b/Doc/reference/slapdtest.rst index bd54bb69..7517e662 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. +Test fixtures for the popular `pytest` framework are developed in an external project, `pytest-ldap`_. + Functions ^^^^^^^^^ @@ -26,3 +28,5 @@ Classes .. autoclass:: slapdtest.SlapdTestCase :members: + +.. _pytest-ldap: https://pypi.org/project/pytest-ldap/ 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 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`. diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index 97e51c67..76017034 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -1,4 +1,3 @@ - .. _sample workflow: Sample workflow for python-ldap development @@ -27,15 +26,12 @@ 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:: @@ -65,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/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 925ddc30..e2150d9a 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -25,6 +25,7 @@ changeNumber changesOnly changeType changeTypes +Christoph cidict clientctrls conf @@ -39,6 +40,7 @@ defresult dereferenced dereferencing desc +dev directoryOperation distinguished distributedOperation @@ -55,6 +57,8 @@ filterstr filterStr formatOID func +Gohlke +GPG Heimdal hostport hrefTarget @@ -83,6 +87,7 @@ LDIFWriter libldap libs Libs +macOS modlist modrdn msgid @@ -95,14 +100,15 @@ oc oid oids OpenLDAP -Pagure postalAddress pre previousDN processResultsCount Proxied py +pytest rdn +readthedocs reentrant refmodule refreshAndPersist @@ -124,8 +130,10 @@ serverctrls sessionSourceIp sessionSourceName sessionTrackingIdentifier +slapadd sizelimit slapd +startup stderr stdout str @@ -137,6 +145,7 @@ subtree syncrepl syntaxes timelimit +TLS tracebacks tuple tuples @@ -144,6 +153,7 @@ UDP Umich unparsing unsigend +urandom uri urlPrefix urlscheme diff --git a/LICENCE.MIT b/LICENCE.MIT new file mode 100644 index 00000000..0c2021f6 --- /dev/null +++ b/LICENCE.MIT @@ -0,0 +1,55 @@ +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 +* Christian Heimes +* Éloi Rivard +* Eyal Cherevatzki +* Florian Best +* Fred Thomsen +* Ivan A. Melnikov +* johnthagen +* Jonathon Reinhart +* Jon Dufresne +* Martin Basti +* Marti Raudsepp +* Miro Hrončok +* Paul Aurich +* Petr Viktorin +* Pieterjan De Potter +* Raphaël Barrois +* Robert Kuska +* Stanislav Láznička +* Tobias Bräutigam +* Tom van Dijk +* Wentao Han +* 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/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 3a86095f..b1797078 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -8,18 +8,32 @@ 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 = 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 +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__, \ - 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) @@ -40,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 @@ -70,21 +83,21 @@ 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() # 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/asyncsearch.py b/Lib/ldap/asyncsearch.py index 80b5b2a9..6c6929dd 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, @@ -28,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), ) @@ -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/cidict.py b/Lib/ldap/cidict.py index f7e8d398..f846fd29 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -5,53 +5,78 @@ See https://www.python-ldap.org/ for details. """ +import warnings +from collections.abc import MutableMapping from ldap import __version__ -from ldap.compat import IterableUserDict +class cidict(MutableMapping): + """ + Case-insensitive but case-respecting dictionary. + """ + __slots__ = ('_keys', '_data') -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 __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 __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 __init__(self,default=None): - self._keys = {} - IterableUserDict.__init__(self,{}) - self.update(default or {}) + def __len__(self): + return len(self._keys) - def __getitem__(self,key): - return self.data[key.lower()] + # Specializations for performance - def __setitem__(self,key,value): - lower_key = key.lower() - self._keys[lower_key] = key - self.data[lower_key] = value + def __contains__(self, key): + return key.lower() in self._keys - def __delitem__(self,key): - lower_key = key.lower() - del self._keys[lower_key] - del self.data[lower_key] + def clear(self): + self._keys.clear() + self._data.clear() - def update(self,dict): - for key in dict.keys(): - self[key] = dict[key] + def copy(self): + inst = self.__class__.__new__(self.__class__) + inst._data = self._data.copy() + inst._keys = self._keys.copy() + return inst - def has_key(self,key): - return key in self + __copy__ = copy - def __contains__(self,key): - return IterableUserDict.__contains__(self, key.lower()) + # Backwards compatibility - def keys(self): - return self._keys.values() + def has_key(self, key): + """Compatibility with python-ldap 2.x""" + return key in self - def items(self): - result = [] - for k in self._keys.values(): - result.append((k,self[k])) - return result + @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): @@ -59,6 +84,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.5", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in b: temp[elt] = elt @@ -74,6 +104,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.5", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in a: temp[elt] = elt @@ -89,6 +124,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.5", + category=DeprecationWarning, + stacklevel=2, + ) temp = cidict() for elt in a: temp[elt] = elt diff --git a/Lib/ldap/compat.py b/Lib/ldap/compat.py index cbfeef57..a287ce4e 100644 --- a/Lib/ldap/compat.py +++ b/Lib/ldap/compat.py @@ -1,113 +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 - - 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 - - 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/constants.py b/Lib/ldap/constants.py index 9eb5ceb9..0e7df6e7 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): - """Base class for a definition of an OpenLDAP constant +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 @@ -245,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'), @@ -262,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), @@ -281,7 +282,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), @@ -300,6 +300,20 @@ 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_ECNAME', optional=True), + TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), + + # 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'), Int('OPT_X_SASL_AUTHCID'), @@ -340,11 +354,10 @@ 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'), Str("CONTROL_MANAGEDSAIT"), Str("CONTROL_PROXY_AUTHZ"), @@ -392,7 +405,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 43b938d8..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,14 +12,11 @@ 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 -try: - from pyasn1.error import PyAsn1Error -except ImportError: - PyAsn1Error = None +from pyasn1.error import PyAsn1Error __all__ = [ diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index 0d43ab1f..e5b2a7ec 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -1,6 +1,5 @@ -# -*- 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/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 aa761f34..f3a8416d 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) @@ -11,13 +10,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): @@ -41,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): @@ -63,32 +63,43 @@ 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 + self.timeBeforeExpiration = None + self.graceAuthNsRemaining = None + self.error = None def decodeControlValue(self,encodedControlValue): ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) 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/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..7b2a7e89 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) @@ -43,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/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..96837e2a 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 @@ -8,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): """ @@ -58,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 @@ -67,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): diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index a5312d2f..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) @@ -12,6 +11,8 @@ ] +import sys + import ldap from ldap.ldapobject import LDAPObject from ldap.controls import (RequestControl, ResponseControl, @@ -57,7 +58,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/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 00c7b064..a9d96846 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -3,13 +3,11 @@ See https://www.python-ldap.org/ for details. """ - -import sys from ldap.pkginfo import __version__ 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 @@ -29,10 +27,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 @@ -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) @@ -111,7 +107,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: diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index d18c6829..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): """ @@ -63,10 +63,6 @@ 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 ldap.extop.passwd import PasswordModifyResponse 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 new file mode 100644 index 00000000..13e9f252 --- /dev/null +++ b/Lib/ldap/extop/passwd.py @@ -0,0 +1,32 @@ +""" +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/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 6c351eff..8658db40 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): """ @@ -44,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)) )) @@ -65,7 +62,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, fileno=None, **kwargs +): """ Return LDAPObject instance by opening LDAP connection to LDAP host specified by LDAP URL @@ -81,38 +81,17 @@ 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. - """ - return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode) + fileno + If not None the socket file descriptor is used to connect to an + LDAP server. - -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. + Additional keyword arguments (such as ``bytes_strictness``) are + passed to ``LDAPObject``. """ - 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 LDAPObject( + uri, trace_level, trace_file, trace_stack_limit, bytes_mode, + fileno=fileno, **kwargs ) - return initialize('ldap://%s:%d' % (host,port),trace_level,trace_file,trace_stack_limit,bytes_mode) - -init = open def get_option(option): diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 9e92ce66..7a9c17f6 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__ @@ -27,44 +24,20 @@ from ldap.schema import SCHEMA_ATTRS from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples -from ldap.extop import ExtendedRequest,ExtendedResponse -from ldap.compat import reraise +from ldap.extop import ExtendedRequest,ExtendedResponse,PasswordModifyResponse 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): @@ -76,7 +49,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 = { @@ -94,196 +69,36 @@ 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 - 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') - self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) - 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 + 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( + ldap._ldap_module_lock, _ldap.initialize_fd, fileno, uri ) 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') + self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri) + self.timeout = -1 + self.protocol_version = ldap.VERSION3 - 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: @@ -299,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__)), @@ -317,18 +132,14 @@ 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']) except IndexError: 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 + self._trace_file.write('=> LDAPError - {}: {}\n'.format(e.__class__.__name__,str(e))) + raise else: if __debug__ and self._trace_level>=2: if not diagnostic_message_success is None: @@ -348,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) )) @@ -404,9 +215,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): @@ -431,9 +239,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): @@ -495,14 +300,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. @@ -510,9 +315,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): @@ -520,11 +322,11 @@ 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) + f'Compare operation returned wrong result: {ldap_res!r}' ) def compare(self,dn,attr,value): @@ -543,7 +345,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): @@ -583,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 @@ -592,9 +393,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): @@ -648,15 +446,18 @@ 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): - 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): """ @@ -673,10 +474,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): @@ -765,8 +562,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): @@ -814,24 +609,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, @@ -928,12 +707,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 = '' + attrname = 'subschemaSubentry' if dn is None: dn = empty_dn try: @@ -956,9 +731,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 @@ -986,14 +760,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 = '(objectClass=subschema)' + if attrs is None: + attrs = SCHEMA_ATTRS try: subschemasubentry = self.read_s( subschemasubentry_dn, @@ -1028,12 +797,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 = '' + attrlist = attrlist or ['*', '+'] ldap_rootdse = self.read_s( base, filterstr=filterstr, @@ -1046,10 +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 """ - if self.bytes_mode: - name = b'namingContexts' - else: - name = u'namingContexts' + name = 'namingContexts' return self.read_rootdse_s( attrlist=[name] ).get(name, []) @@ -1057,15 +819,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__ = { @@ -1079,7 +846,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 @@ -1095,7 +862,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 @@ -1123,11 +891,12 @@ 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 - self.reconnect(self._uri) + # XXX cannot pickle file, use default trace file + self._trace_file = ldap._trace_file + self.reconnect(self._uri,force=True) - 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: @@ -1145,30 +914,39 @@ 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) 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: - # 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' % ( + self._trace_file.write('*** {} reconnect to {} failed\n'.format( counter_text,uri )) reconnect_counter = reconnect_counter-1 @@ -1177,10 +955,9 @@ 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' % ( + self._trace_file.write('*** {} reconnect to {} successful => repeat last operation\n'.format( counter_text,uri )) self._reconnects_done = self._reconnects_done + 1 @@ -1190,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) 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/modlist.py b/Lib/ldap/modlist.py index 6f513127..bf4e4819 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() @@ -50,15 +50,15 @@ 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 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/pkginfo.py b/Lib/ldap/pkginfo.py index 47199e92..18ead66c 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.0.0' -__author__ = u'python-ldap project' +__version__ = '3.4.4' +__author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 9a8cb5b2..3d9322c0 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 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) @@ -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)] @@ -106,28 +106,34 @@ 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 + 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' + schema_attribute = 'objectClasses' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -137,7 +143,8 @@ class ObjectClass(SchemaElement): 'AUXILIARY':None, 'ABSTRACT':None, 'MUST':(()), - 'MAY':() + 'MAY':(), + 'X-ORIGIN':() } def _set_attrs(self,l,d): @@ -146,6 +153,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'] # Default is STRUCTURAL, see RFC2552 or draft-ietf-ldapbis-syntaxes self.kind = 0 if d['ABSTRACT']!=None: @@ -168,6 +176,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_list('X-ORIGIN',self.x_origin,quoted=1)) return '( %s )' % ''.join(result) @@ -190,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 @@ -202,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 @@ -222,10 +231,16 @@ 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 + 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' + schema_attribute = 'attributeTypes' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -239,7 +254,7 @@ class AttributeType(SchemaElement): 'COLLECTIVE':None, 'NO-USER-MODIFICATION':None, 'USAGE':('userApplications',), - 'X-ORIGIN':(None,), + 'X-ORIGIN':(), 'X-ORDERED':(None,), } @@ -251,7 +266,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] @@ -302,7 +317,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) @@ -314,12 +329,12 @@ 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) """ - schema_attribute = u'ldapSyntaxes' + schema_attribute = 'ldapSyntaxes' token_defaults = { 'DESC':(None,), 'X-NOT-HUMAN-READABLE':(None,), @@ -358,16 +373,17 @@ 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' + schema_attribute = 'matchingRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -403,17 +419,17 @@ 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' + schema_attribute = 'matchingRuleUse' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -449,28 +465,31 @@ 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' + schema_attribute = 'dITContentRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -515,19 +534,20 @@ 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' + schema_attribute = 'dITStructureRules' token_defaults = { 'NAME':(()), @@ -573,25 +593,24 @@ 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' + schema_attribute = 'nameForms' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -621,7 +640,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(IterableUserDict): +class Entry(UserDict): """ Schema-aware implementation of an LDAP entry class. @@ -634,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): @@ -655,8 +674,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.items(): + 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..b83d819b 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -4,10 +4,16 @@ See https://www.python-ldap.org/ for details. """ -import ldap.cidict,ldap.schema +import copy +from urllib.request import urlopen +import ldap.cidict,ldap.schema from ldap.schema.models import * +import ldapurl +import ldif + + SCHEMA_CLASS_MAPPING = ldap.cidict.cidict() SCHEMA_ATTR_MAPPING = {} @@ -16,7 +22,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): @@ -115,7 +121,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) @@ -123,7 +129,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__() @@ -136,8 +142,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 +159,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: @@ -162,7 +167,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 @@ -186,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 ('_',): @@ -211,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 @@ -244,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: @@ -257,7 +262,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'): @@ -417,14 +421,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] @@ -453,12 +457,11 @@ 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) 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 @@ -473,8 +476,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() diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index 20958c09..623b86d5 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/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 0de5cec4..fd0c1285 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) @@ -13,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', @@ -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) + # 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 + 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: @@ -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 diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index b3e11712..b4dfd890 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.4.4' __all__ = [ # constants @@ -16,7 +16,8 @@ 'LDAPUrlExtension','LDAPUrlExtensions','LDAPUrl' ] -from ldap.compat import UserDict, quote, unquote +from collections.abc import MutableMapping +from urllib.parse import quote, unquote LDAP_SCOPE_BASE = 0 LDAP_SCOPE_ONELEVEL = 1 @@ -47,21 +48,16 @@ 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): """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. @@ -102,9 +98,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 '') ) @@ -113,7 +109,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)), @@ -130,61 +126,74 @@ 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 - ) + __slots__ = ('_data', ) + + def __init__(self, default=None): + self._data = {} + if default is not None: + self.update(default) + + def __setitem__(self, name, value): + """Store an extension + + name + string + value + LDAPUrlExtension instance, whose extype nust match `name` + """ + 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): + 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 '<{}.{} instance at {}: {}>'.format( + 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): + if not isinstance(other, self.__class__): + return NotImplemented + 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): +class LDAPUrl: """ Class for parsing and unparsing LDAP URLs as described in RFC 4516. @@ -221,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 @@ -256,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): @@ -313,9 +320,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): """ @@ -326,7 +333,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): """ @@ -347,7 +354,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 ) @@ -366,24 +373,30 @@ 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 - return '%s' % ( - target,urlPrefix,self.unparse(),hrefText + if not isinstance(hrefTarget, str): + raise TypeError("hrefTarget must be str, not " + + type(hrefTarget).__name__) + target = ' target="%s"' % hrefTarget + return '{}'.format( + target, urlPrefix, self.unparse(), hrefText ) 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)), @@ -400,7 +413,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__() @@ -429,4 +442,3 @@ def __delattr__(self,name): pass else: del self.__dict__[name] - diff --git a/Lib/ldif.py b/Lib/ldif.py index cdcccc0a..fa41321c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,10 +3,7 @@ See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - -__version__ = '3.0.0' +__version__ = '3.4.4' __all__ = [ # constants @@ -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'(([^,]|\\,)+|".*?")' @@ -150,8 +148,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): diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 82970243..7c410180 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- """ slapdtest - module for spawning test instances of OpenLDAP's slapd server See https://www.python-ldap.org/ for details. """ -__version__ = '3.0.0' +__version__ = '3.4.4' 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 484eb54b..36841110 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -1,12 +1,8 @@ -# -*- 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 sys @@ -16,43 +12,43 @@ 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__)) -# 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)" - +# 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' @@ -109,6 +105,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) @@ -154,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. @@ -164,20 +168,30 @@ 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. + + :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 """ 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' - local_host = '127.0.0.1' + local_host = LOCALHOST testrunsubdirs = ( - 'schema', + 'slapd.d', ) openldap_schema_files = ( - 'core.schema', + 'core.ldif', ) TMPDIR = os.environ.get('TMP', os.getcwd()) @@ -193,9 +207,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') @@ -204,16 +215,15 @@ 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/" % (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) 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 @@ -232,16 +242,28 @@ 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) + + @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') 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: 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: @@ -254,7 +276,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 @@ -268,7 +290,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): """ @@ -315,7 +336,6 @@ def gen_config(self): """ config_dict = { 'serverid': hex(self.server_id), - 'schema_prefix':self._schema_prefix, 'loglevel': self.slapd_loglevel, 'database': self.database, 'directory': self._db_directory, @@ -339,37 +359,39 @@ 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_SLAPTEST, - "-f", self._slapd_conf, - '-u', + self.PATH_SLAPD, + "-Ttest", + "-F", self._slapd_conf, + "-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) @@ -382,8 +404,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): @@ -393,20 +414,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") @@ -438,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): """ @@ -488,10 +503,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, @@ -542,6 +561,23 @@ 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 + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + class SlapdTestCase(unittest.TestCase): """ @@ -566,7 +602,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): 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----- diff --git a/Makefile b/Makefile index e4ff75ac..2b52ddf5 100644 --- a/Makefile +++ b/Makefile @@ -6,14 +6,20 @@ LCOV_REPORT_OPTIONS=--show-details -no-branch-coverage \ SCAN_REPORT=build/scan_report PYTHON_SUPP=/usr/share/doc/python3-devel/valgrind-python.supp + .NOTPARALLEL: .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 $(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 @@ -29,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) @@ -77,3 +83,18 @@ valgrind: build $(PYTHON_SUPP) echo "Found definitive leak, see build/valgrind.log"; \ exit 1; \ fi + +# Code autoformatter +.PHONY: autoformat indent black black-check +autoformat: indent black + +indent: + indent Modules/*.c + indent -npsl Modules/pythonldap.h + rm -f Modules/*.c~ Modules/*.h~ + +black: + $(PYTHON) -m black $(CURDIR) + +black-check: + $(PYTHON) -m black $(CURDIR) --check diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index d7265a42..71fac73e 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1,30 +1,25 @@ /* 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 #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 +30,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; @@ -53,17 +48,19 @@ 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. */ 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 +68,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; @@ -86,8 +83,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. * @@ -98,8 +95,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,15 +110,17 @@ 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); + if (lm == NULL) goto nomem; @@ -130,43 +129,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 +182,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,45 +194,51 @@ 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); } -/* - * 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 */ -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 +248,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,30 +262,27 @@ 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; 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 + } + else if (PyUnicode_Check(attrlist)) { /* 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 - char *str; -#endif seq = PySequence_Fast(attrlist, "expected list of strings or None"); if (seq == NULL) @@ -280,35 +291,27 @@ 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; 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); + 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. */ - attrs[i] = (char *)PyMem_NEW(char *, strlen + 1); + attrs[i] = (char *)PyMem_NEW(char, strlen + 1); + if (attrs[i] == NULL) goto nomem; memcpy(attrs[i], str, strlen + 1); @@ -320,9 +323,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 +334,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 +355,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 +377,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); self->valid = 0; Py_INCREF(Py_None); @@ -393,19 +399,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 +423,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); Py_INCREF(Py_None); return Py_None; @@ -436,58 +445,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); - return PyInt_FromLong(msgid); + return PyLong_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 +508,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 +528,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); - return PyInt_FromLong( msgid ); + return PyLong_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. @@ -546,7 +564,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 @@ -566,48 +584,43 @@ 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 "*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 @@ -615,26 +628,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 +657,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,64 +679,64 @@ 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); + return PyLong_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; 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(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 (!PyArg_ParseTuple + (args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, + &serverctrls, &clientctrls, &sasl_flags)) + 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 +745,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 +763,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); + return PyLong_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 +809,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); - return PyInt_FromLong( msgid ); + return PyLong_FromLong(msgid); } #endif @@ -813,23 +833,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 +862,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); - return PyInt_FromLong( msgid ); + return PyLong_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 +909,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); - return PyInt_FromLong(msgid); + return PyLong_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); - return PyInt_FromLong( msgid ); + return PyLong_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 +1015,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); - return PyInt_FromLong( msgid ); + return PyLong_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,42 +1047,48 @@ 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; + PyObject *retval, *pmsg, *pyctrls = 0; 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: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); 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,88 +1100,86 @@ 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 ); - } - - 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_msgfree(msg); + + LDAP_BEGIN_ALLOW_THREADS(self); + rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, NULL, + &serverctrls, 0); + LDAP_END_ALLOW_THREADS(self); + } + + if (result != LDAP_SUCCESS) { /* result error */ + ldap_controls_free(serverctrls); Py_XDECREF(valuestr); - return LDAPerror( self->ldap, e ); + return LDAPraise_for_message(self->ldap, msg); } 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_controls_free(serverctrls); ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror(self->ldap, "LDAPControls_to_List"); + return LDAPerror(self->ldap); } ldap_controls_free(serverctrls); - 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 ); - } + pmsg = + LDAPmessage_to_python(self->ldap, msg, add_ctrls, add_intermediates); 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); + retval = Py_BuildValue("(iOiOsO)", res_type, pmsg, res_msgid, + pyctrls, retoid, + valuestr ? valuestr : Py_None); + } + else { + retval = + Py_BuildValue("(iOiO)", res_type, pmsg, res_msgid, pyctrls); } if (pmsg != Py_None) { - Py_DECREF(pmsg); + Py_DECREF(pmsg); } } Py_XDECREF(valuestr); Py_XDECREF(pyctrls); - Py_DECREF(result_str); 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 +1190,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 ); + 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 ); - - 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); + return PyLong_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 +1267,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 +1279,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); } result = LDAPberval_to_unicode_object(bvalue); @@ -1260,20 +1305,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); } Py_INCREF(Py_None); @@ -1284,38 +1331,40 @@ 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(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; } - /* ldap_get_option */ -static PyObject* -l_ldap_get_option(PyObject* self, PyObject *args) +static PyObject * +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 */ 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 +1374,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 +1399,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); + return PyLong_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 +1451,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); - return PyInt_FromLong( msgid ); + return PyLong_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 deleted file mode 100644 index 8cd6fc3e..00000000 --- a/Modules/LDAPObject.h +++ /dev/null @@ -1,51 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_LDAPObject -#define __h_LDAPObject - -#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 -typedef PyThreadState* _threadstate; -#endif - -typedef struct { - 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* ); - -/* 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); \ - _threadstate _save = lo->_save; \ - lo->_save = NULL; \ - PyEval_RestoreThread( _save ); \ - } - -#endif /* __h_LDAPObject */ - diff --git a/Modules/berval.c b/Modules/berval.c index 7435ee0a..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, @@ -17,7 +16,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/berval.h b/Modules/berval.h deleted file mode 100644 index 9702b8ce..00000000 --- a/Modules/berval.h +++ /dev/null @@ -1,12 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_berval -#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); - -#endif /* __h_berval_ */ diff --git a/Modules/common.c b/Modules/common.c index 0f0cd36a..4cfee744 100644 --- a/Modules/common.c +++ b/Modules/common.c @@ -1,26 +1,29 @@ /* 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 */ 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 deleted file mode 100644 index 029f234f..00000000 --- a/Modules/common.h +++ /dev/null @@ -1,39 +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 - -#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 c2b595c1..f0a0da94 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -1,140 +1,209 @@ /* constants defined for LDAP * See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "lber.h" -#include "ldap.h" +#include "pythonldap.h" /* 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 && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { + 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 * +LDAPraise_for_message(LDAP *l, LDAPMessage *m) { - 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); + ldap_msgfree(m); + return NULL; } + else { + int myerrno, errnum, opt_errnum, msgid = -1, msgtype = 0; + PyObject *errobj; + PyObject *info; + PyObject *str; + PyObject *pyerrno; + 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; + + if (m != NULL) { + msgid = ldap_msgid(m); + msgtype = ldap_msgtype(m); + ldap_parse_result(l, m, &errnum, &matched, &error, &refs, + &serverctrls, 1); + } - 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 (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); } - ldap_memfree(matched); - } - if (errnum == LDAP_REFERRAL) { - str = PyUnicode_FromString(msg); + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { + errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; + } + else { + errobj = LDAPexception_class; + } + + info = PyDict_New(); + if (info == NULL) { + ldap_memfree(matched); + ldap_memfree(error); + ldap_memvfree((void **)refs); + ldap_controls_free(serverctrls); + return NULL; + } + + if (msgtype > 0) { + pyresult = PyLong_FromLong(msgtype); + if (pyresult) + PyDict_SetItemString(info, "msgtype", pyresult); + Py_XDECREF(pyresult); + } + + if (msgid >= 0) { + pyresult = PyLong_FromLong(msgid); + if (pyresult) + PyDict_SetItemString(info, "msgid", pyresult); + Py_XDECREF(pyresult); + } + + pyresult = PyLong_FromLong(errnum); + if (pyresult) + PyDict_SetItemString(info, "result", pyresult); + Py_XDECREF(pyresult); + + str = PyUnicode_FromString(ldap_err2string(errnum)); if (str) - PyDict_SetItemString( info, "info", str ); + PyDict_SetItemString(info, "desc", str); Py_XDECREF(str); - } else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { - if (error != NULL && *error != '\0') { + + if (myerrno != 0) { + pyerrno = PyLong_FromLong(myerrno); + if (pyerrno) + PyDict_SetItemString(info, "errno", pyerrno); + Py_XDECREF(pyerrno); + } + + 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) + PyDict_SetItemString(info, "matched", str); + Py_XDECREF(str); + } + ldap_memfree(matched); + } + + 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 (error != NULL && *error != '\0') { str = PyUnicode_FromString(error); if (str) - PyDict_SetItemString( info, "info", str ); + PyDict_SetItemString(info, "info", str); Py_XDECREF(str); } + + PyErr_SetObject(errobj, info); + Py_DECREF(info); + ldap_memvfree((void **)refs); ldap_memfree(error); + return NULL; } - PyErr_SetObject( errobj, info ); - Py_DECREF(info); - return NULL; - } +} + +PyObject * +LDAPerror(LDAP *l) +{ + return LDAPraise_for_message(l, NULL); } /* initialise the module constants */ int -LDAPinit_constants( PyObject* m ) +LDAPinit_constants(PyObject *m) { - PyObject *exc; + PyObject *exc, *nobj; + struct ldap_apifeature_info info = { 1, "X_OPENLDAP_THREAD_SAFE", 0 }; + int thread_safe = 0; /* 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,18 +212,32 @@ 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); +#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 { \ 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); \ diff --git a/Modules/constants.h b/Modules/constants.h deleted file mode 100644 index 4056f907..00000000 --- a/Modules/constants.h +++ /dev/null @@ -1,25 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_constants_ -#define __h_constants_ - -#include "common.h" -#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 */ - -#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/constants_generated.h b/Modules/constants_generated.h index 083ba161..3e59f828 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) @@ -216,11 +222,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 @@ -263,6 +264,51 @@ add_int(OPT_X_TLS_PROTOCOL_MIN); 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 + + +#if defined(LDAP_OPT_X_TLS_PEERCERT) +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); @@ -324,13 +370,6 @@ 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; #else @@ -344,6 +383,13 @@ if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 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; +#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 6bbf487b..f7d9cf37 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -1,37 +1,89 @@ /* 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 */ -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; + PyThreadState *save; if (!PyArg_ParseTuple(args, "s:initialize", &uri)) - return NULL; + return NULL; - Py_BEGIN_ALLOW_THREADS + save = PyEval_SaveThread(); ret = ldap_initialize(&ld, uri); - Py_END_ALLOW_THREADS + PyEval_RestoreThread(save); + if (ret != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_initialize"); - return (PyObject*)newLDAPObject(ld); + return LDAPerror(ld); + + return (PyObject *)newLDAPObject(ld); } +#ifdef HAVE_LDAP_INIT_FD +/* initialize_fd(fileno, url) */ + +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 /* 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 +98,58 @@ 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 +157,49 @@ 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}, +#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}, + {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 deleted file mode 100644 index 854a9403..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 c8f9dfce..4a37b614 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -1,12 +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 "lber.h" +#include "pythonldap.h" /* Prints to stdout the contents of an array of LDAPControl objects */ @@ -27,13 +21,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 +36,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 +56,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,14 +67,16 @@ 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(); return NULL; @@ -89,6 +86,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); @@ -110,7 +108,7 @@ Tuple_to_LDAPControl( PyObject* tup ) LDAPControl_DEL(lc); return NULL; } - + lc->ldctl_value = berbytes; return lc; @@ -120,41 +118,43 @@ 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 +162,7 @@ LDAPControls_from_object(PyObject* list, LDAPControl ***controls_ret) return 1; } -PyObject* +PyObject * LDAPControls_to_List(LDAPControl **ldcs) { PyObject *res = 0, *pyctrl; @@ -170,7 +170,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 +191,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 +285,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 +299,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 +322,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; @@ -337,6 +336,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)) { @@ -346,41 +346,41 @@ 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 + save = PyEval_SaveThread(); err = ldap_create(&ld); - Py_END_ALLOW_THREADS - + 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); + 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 + LDAPerror(ld); + save = PyEval_SaveThread(); ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS + PyEval_RestoreThread(save); return NULL; } - Py_BEGIN_ALLOW_THREADS + save = PyEval_SaveThread(); ldap_unbind_ext(ld, NULL, NULL); - Py_END_ALLOW_THREADS - + PyEval_RestoreThread(save); 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 deleted file mode 100644 index 1c09d954..00000000 --- a/Modules/ldapcontrol.h +++ /dev/null @@ -1,14 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_ldapcontrol -#define __h_ldapcontrol - -#include "common.h" -#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); - -#endif /* __h_ldapcontrol */ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index f37c1b8d..cb3f58fb 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -1,17 +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" - -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC PyInit__ldap(void); -#else -PyMODINIT_FUNC init_ldap(void); -#endif +#include "pythonldap.h" #define _STR(x) #x #define STR(x) _STR(x) @@ -21,78 +10,57 @@ 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 */ +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); - return NULL; - } - - /* Add some symbolic constants to the module */ - d = PyModule_GetDict(m); - - init_pkginfo(m); - - if (LDAPinit_constants(m) == -1) { - return NULL; - } - - 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) { + PyObject *m, *d; + + /* Create the module and add the functions */ + m = PyModule_Create(&ldap_moduledef); + + /* Initialize LDAP class */ + if (PyType_Ready(&LDAP_Type) < 0) { + Py_DECREF(m); return NULL; } - /* Check for errors */ - if (PyErr_Occurred()) - Py_FatalError("can't initialize module _ldap"); + /* Add some symbolic constants to the module */ + d = PyModule_GetDict(m); - return m; -} + init_pkginfo(m); + + if (LDAPinit_constants(m) == -1) { + return NULL; + } + LDAPinit_functions(d); + LDAPinit_control(d); -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC init_ldap() { - init_ldap_module(); -} -#else -PyMODINIT_FUNC PyInit__ldap() { - return init_ldap_module(); + /* Check for errors */ + if (PyErr_Occurred()) + Py_FatalError("can't initialize module _ldap"); + + return m; } -#endif diff --git a/Modules/message.c b/Modules/message.c index b7f3ae79..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. @@ -21,261 +17,291 @@ * 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); + } - 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); + } - /* 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); + } + 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)) { + /* 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); + 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); + } + /* 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); + } + 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); + } + /* 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); + } + ldap_controls_free(serverctrls); + + 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; + } + Py_DECREF(valtuple); + } + } + } + ldap_msgfree(m); + return result; } diff --git a/Modules/message.h b/Modules/message.h deleted file mode 100644 index c3522ac3..00000000 --- a/Modules/message.h +++ /dev/null @@ -1,12 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#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 ); - -#endif /* __h_message_ */ diff --git a/Modules/options.c b/Modules/options.c index ee606d46..4577b075 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -1,15 +1,12 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "options.h" +#include "pythonldap.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 +20,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); @@ -39,24 +36,33 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) { int res; int intval; + unsigned int uintval; double doubleval; char *strval; struct timeval tv; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif void *ptr; LDAP *ld; LDAPControl **controls = NULL; ld = self ? self->ldap : NULL; - switch(option) { + 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 - /* Read-only options */ - PyErr_SetString(PyExc_ValueError, "read-only option"); - return 0; +#ifdef LDAP_OPT_X_TLS_PEERCERT + case LDAP_OPT_X_TLS_PEERCERT: +#endif + /* 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 +71,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: @@ -87,10 +93,12 @@ 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 -#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: @@ -102,11 +110,31 @@ 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; + +#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; + break; +#endif + case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE @@ -125,73 +153,89 @@ 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 - /* String valued options */ - if (!PyArg_Parse(value, "s:set_option", &strval)) - return 0; - ptr = strval; - break; +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: +#endif + /* 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 %S", + value); 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); - res = ldap_set_option(ld, option, ptr); - if (self) LDAP_END_ALLOW_THREADS(self); - if ((option == LDAP_OPT_SERVER_CONTROLS) || (option == LDAP_OPT_CLIENT_CONTROLS)) + if (self) { + LDAP_BEGIN_ALLOW_THREADS(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)) LDAPControl_List_DEL(controls); - + if (res != LDAP_OPT_SUCCESS) { option_error(res, "ldap_set_option"); return 0; @@ -200,60 +244,86 @@ 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) { int res; int intval; + unsigned int uintval; struct timeval *tv; LDAPAPIInfo apiinfo; LDAPControl **lcs; char *strval; + struct berval berbytes; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif 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); - 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); - - 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; + switch (option) { #ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF: + case LDAP_OPT_X_SASL_SECPROPS: + 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); + 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); + + 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; + case LDAP_OPT_REFERRALS: case LDAP_OPT_RESTART: case LDAP_OPT_DEREF: @@ -272,10 +342,12 @@ 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 -#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: @@ -292,13 +364,32 @@ 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 */ + res = LDAP_int_get_option(self, option, &intval); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyLong_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) + return option_error(res, "ldap_get_option"); + return PyLong_FromUnsignedLong(blen); +#endif case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: @@ -327,9 +418,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: @@ -338,53 +431,62 @@ 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; +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: +#endif + /* String-valued options */ + res = LDAP_int_get_option(self, option, &strval); + 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; + +#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 */ - 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 */ + res = LDAP_int_get_option(self, option, &tv); + 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); + res = LDAP_int_get_option(self, option, &lcs); + 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 deleted file mode 100644 index 570fdc15..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..7703af5e --- /dev/null +++ b/Modules/pythonldap.h @@ -0,0 +1,131 @@ +/* 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 + +#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/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. 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. 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_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 ff5fb6c2..33fbf29a 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's C wrapper module _ldap See https://www.python-ldap.org/ for details. """ - -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 @@ -15,7 +14,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): @@ -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): @@ -91,19 +90,34 @@ 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 - def assertNotNone(self, expr, msg=None): - self.assertFalse(expr is None, msg or repr(expr)) + @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)) - 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 @@ -128,88 +142,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 @@ -220,21 +234,48 @@ 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() + 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) + sock.close() + 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) + 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("", "") 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, []) @@ -246,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) @@ -263,7 +304,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 +342,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: @@ -386,30 +427,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): """ @@ -629,7 +676,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) @@ -737,15 +784,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) @@ -884,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() diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 8e5d8d6e..97146ec8 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 @@ -7,6 +6,7 @@ import os import unittest +import warnings # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -30,12 +30,14 @@ 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) 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"] @@ -46,6 +48,42 @@ 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) + + 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) + + 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() diff --git a/Tests/t_edit.py b/Tests/t_edit.py index a5b3f657..5d8b3f06 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 @@ -24,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_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_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() 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() 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() diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 4b4dd319..86d36403 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -1,12 +1,8 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.dn See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - # from Python's standard lib import os import unittest @@ -50,6 +46,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\\= (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 @@ -156,143 +166,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 @@ -314,7 +193,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): @@ -417,52 +296,35 @@ 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) self.assertEqual( sorted(subschema), [ - u'attributeTypes', - u'ldapSyntaxes', - u'matchingRuleUse', - u'matchingRules', - u'objectClasses' + 'attributeTypes', + 'ldapSyntaxes', + 'matchingRuleUse', + 'matchingRules', + 'objectClasses' ] ) - @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_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(f"expected info={expected_info!r}, got {info!r}") else: self.fail("expected SERVER_DOWN, got %r" % r) @@ -479,7 +341,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()) @@ -488,6 +350,30 @@ 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() + 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): l = self.ldap_object_class(self.server.ldap_uri) m = l.search_ext(self.server.suffix, ldap.SCOPE_SUBTREE, '(objectClass=*)') @@ -504,50 +390,14 @@ 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'') - - @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 + self.assertEqual(l.whoami_s(), '') 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, @@ -564,44 +414,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 @@ -616,35 +428,142 @@ 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) + + 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) + + self.assertEqual(server_der, peercert) + 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(), [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 + 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', 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') + + 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) + + 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) + + # try changing password back + respoid, respvalue = l.passwd_s(dn, password, "initial") + self.assertEqual(respoid, None) + self.assertEqual(respvalue, None) + + 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): @@ -680,22 +599,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'): 0, - 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, + '_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, }, ) @@ -709,6 +626,36 @@ 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) + + +@requires_init_fd() +class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): + def _open_ldap_conn(self, who=None, cred=None, **kwargs): + sock = socket.create_connection( + (self.server.hostname, self.server.port) + ) + result = super()._open_ldap_conn( + who=who, cred=cred, fileno=sock.fileno(), **kwargs + ) + sock.detach() + return result + if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index c4a813dd..f9c72098 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -1,20 +1,15 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldapurl 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 @@ -38,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): @@ -61,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): @@ -149,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( @@ -164,7 +185,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), @@ -174,7 +195,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), @@ -183,10 +204,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 +240,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 +302,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 +320,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 +372,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 +397,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): diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 4f181df1..2f9ed679 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -1,12 +1,8 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldif See https://www.python-ldap.org/ for details. """ - -from __future__ import unicode_literals - import os import textwrap import unittest @@ -467,7 +463,7 @@ def test_weird_empty_lines(self): """ # comment before version - + version: 1 @@ -586,7 +582,7 @@ def test_weird_empty_lines(self): """ # comment before version - + version: 1 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) 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'] diff --git a/setup.cfg b/setup.cfg index 87cdfd7e..fdb32fbc 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 @@ -12,14 +16,16 @@ # 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 = +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] @@ -29,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 034c4dd3..8e7963a1 100644 --- a/setup.py +++ b/setup.py @@ -7,15 +7,12 @@ 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, found %s' % sys.version_info + ) -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 @@ -71,12 +68,12 @@ 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', 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', @@ -88,13 +85,16 @@ 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', - # Note: when updating Python versions, also change .travis.yml and tox.ini + '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', + # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', 'Topic :: Internet', @@ -118,15 +118,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, @@ -136,7 +129,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)] + \ [ @@ -158,6 +150,7 @@ class OpenLDAP2: 'ldap.extop', 'ldap.schema', 'slapdtest', + 'slapdtest.certs', ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, @@ -167,6 +160,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 abba0b01..22752067 100644 --- a/tox.ini +++ b/tox.ini @@ -4,69 +4,94 @@ # and then run "tox" from this directory. [tox] -# Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report +# 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 minver = 1.8 +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38, doc, py3-nosasltls + 3.9: py39, py3-trace + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 + pypy3.9: pypy3.9 + pypy3.10: pypy3.10 + [testenv] -deps = coverage +deps = setuptools 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 -# DeprecationWarning. +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement -std=c99 commands = {envpython} -bb -Werror \ - "-Wignore:the imp module is deprecated:DeprecationWarning" \ - "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ - -m coverage run --parallel setup.py \ - clean --all \ - test + -m unittest discover -v -s Tests -p 't_*' {posargs} -[testenv:py27] -# No warnings with Python 2.7 -passenv = {[testenv]passenv} -commands = {envpython} \ - -m coverage run --parallel setup.py test +[testenv:py312] +# Python 3.12 headers are incompatible with declaration-after-statement +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -std=c99 -[testenv:py2-nosasltls] -basepython = python2 -deps = {[testenv]deps} +[testenv:py3-nosasltls] +basepython = python3 +# 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]commands} -[testenv:py3-nosasltls] +[testenv:py3-trace] basepython = python3 deps = {[testenv]deps} 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 +setenv = + PYTHON_LDAP_TRACE_LEVEL=9 + PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log +commands = {[testenv]commands} -[testenv:pypy35] -# PyPy-5.9.0 -basepython = pypy3.5 -deps = {[testenv:pypy]deps} -commands = {[testenv:pypy]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:coverage-report] -deps = coverage -skip_install = true +[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 coverage combine - {envpython} -m coverage report --show-missing - {envpython} -m coverage html + {envpython} -m unittest -v {posargs} \ + 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 +commands = {envpython} -m pytest {posargs} [testenv:doc] basepython = python3 @@ -75,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