Description
Issue description:
If a server returns a response to a search request containing a response code that is in the range [LDAP_ERROR_MIN,LDAP_ERROR_MAX], but is not one that is actually known, then python-ldap will attempt to use NULL as an exception object, which then causes the Python runtime to raise a SystemError.
Steps to reproduce:
Perform a search against an LDAP server that returns an unknown response code. For example, OKTA (https://www.okta.com/) will return resultCode 92 (notSupported) if you attempt to perform a search using a non-trivial filter (e.g. (&(objectClass=person)(uid=foo))
. Sadly, this is a proprietary solution, and the LDAP functionality doesn't appear to be generally available. However, here's the raw BER-encoded response data they send:
30 0c 02 01 02 65 07 0a
01 5c 04 00 04 00
or, decoded:
30 0c -- LDAPMessage (payload size = 12 octets)
02 01 02 -- Message ID (integer object, value = 2)
65 07 -- SearchResultDone operation (payload size = 7 octets)
0a 01 5c -- result code (enumerated value 0x5c == 92)
04 00 -- matched DN (empty octet string)
04 00 -- diagnostic message (empty octet string)
(resultCode 92 is in the range reserved for client-side errors, so probably shouldn't be appearing on the wire, but still).
Operating system:
Debian Stretch
Python version:
2.7.13
python-ldap version:
2.4.28
Here's a trivial patch against HEAD of master as of the time I'm writing this (363e417) that causes the base LDAPError exception to be raised in this scenario (which is more likely to be expected by client code):
--- a/Modules/constants.c
+++ b/Modules/constants.c
@@ -32,7 +32,8 @@ static PyObject *errobjects[LDAP_ERROR_MAX - LDAP_ERROR_MIN + 1];
PyObject *
LDAPerr(int errnum)
{
- if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) {
+ if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX
+ && errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) {
PyErr_SetNone(errobjects[errnum + LDAP_ERROR_OFFSET]);
}
else {
@@ -72,7 +73,8 @@ LDAPerror(LDAP *l, char *msg)
if (errnum == LDAP_NO_MEMORY)
return PyErr_NoMemory();
- if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX)
+ if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX
+ && errobjects[errnum + LDAP_ERROR_OFFSET] != NULL)
errobj = errobjects[errnum + LDAP_ERROR_OFFSET];
else
errobj = LDAPexception_class;