diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 3254375..1f093ab 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -321,6 +321,9 @@ The module defines the following exceptions: is set to a truncated form of the name provided or alias dereferenced for the lowest entry (object or alias) that was matched. + Most exceptions from protocol results also carry the :py:attr:`errnum` + attribute. + .. py:exception:: ADMINLIMIT_EXCEEDED diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index ed4df4c..0f52d22 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -415,7 +415,7 @@ l_ldap_unbind_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_unbind_ext"); + return LDAPerror(self->ldap); self->valid = 0; Py_INCREF(Py_None); @@ -461,7 +461,7 @@ l_ldap_abandon_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_abandon_ext"); + return LDAPerror(self->ldap); Py_INCREF(Py_None); return Py_None; @@ -517,7 +517,7 @@ l_ldap_add_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_add_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -568,7 +568,7 @@ l_ldap_simple_bind(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_simple_bind"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -727,7 +727,7 @@ l_ldap_sasl_bind_s(LDAPObject *self, PyObject *args) servercred->bv_len); } else if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "l_ldap_sasl_bind_s"); + return LDAPerror(self->ldap); return PyInt_FromLong(ldaperror); } @@ -806,7 +806,7 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (msgid != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_sasl_interactive_bind_s"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } #endif @@ -854,7 +854,7 @@ l_ldap_cancel(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_cancel"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -908,7 +908,7 @@ l_ldap_compare_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_compare_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -954,7 +954,7 @@ l_ldap_delete_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_delete_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1011,7 +1011,7 @@ l_ldap_modify_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_modify_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1061,7 +1061,7 @@ l_ldap_rename(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_rename"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1081,12 +1081,11 @@ l_ldap_result4(LDAPObject *self, PyObject *args) struct timeval *tvp; int res_type; LDAPMessage *msg = NULL; - PyObject *result_str, *retval, *pmsg, *pyctrls = 0; + PyObject *retval, *pmsg, *pyctrls = 0; int res_msgid = 0; char *retoid = 0; PyObject *valuestr = NULL; int result = LDAP_SUCCESS; - char **refs = NULL; LDAPControl **serverctrls = 0; if (!PyArg_ParseTuple @@ -1109,7 +1108,7 @@ l_ldap_result4(LDAPObject *self, PyObject *args) LDAP_END_ALLOW_THREADS(self); if (res_type < 0) /* LDAP or system error */ - return LDAPerror(self->ldap, "ldap_result4"); + return LDAPerror(self->ldap); if (res_type == 0) { /* Polls return (None, None, None, None); timeouts raise an exception */ @@ -1157,24 +1156,15 @@ l_ldap_result4(LDAPObject *self, PyObject *args) } LDAP_BEGIN_ALLOW_THREADS(self); - rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, &refs, + rc = ldap_parse_result(self->ldap, msg, &result, NULL, NULL, NULL, &serverctrls, 0); LDAP_END_ALLOW_THREADS(self); } if (result != LDAP_SUCCESS) { /* result error */ - char *e, err[1024]; - - if (result == LDAP_REFERRAL && refs && refs[0]) { - snprintf(err, sizeof(err), "Referral:\n%s", refs[0]); - e = err; - } - else - e = "ldap_parse_result"; ldap_controls_free(serverctrls); - ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror(self->ldap, e); + return LDAPraise_for_message(self->ldap, msg); } if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -1186,34 +1176,26 @@ l_ldap_result4(LDAPObject *self, PyObject *args) ldap_controls_free(serverctrls); ldap_msgfree(msg); Py_XDECREF(valuestr); - return LDAPerror(self->ldap, "LDAPControls_to_List"); + return LDAPerror(self->ldap); } ldap_controls_free(serverctrls); pmsg = LDAPmessage_to_python(self->ldap, msg, add_ctrls, add_intermediates); - if (res_type == 0) { - result_str = Py_None; - Py_INCREF(Py_None); - } - else { - result_str = PyInt_FromLong(res_type); - } - if (pmsg == NULL) { retval = NULL; } else { /* s handles NULL, but O does not */ if (add_extop) { - retval = Py_BuildValue("(OOiOsO)", result_str, pmsg, res_msgid, + retval = Py_BuildValue("(iOiOsO)", res_type, pmsg, res_msgid, pyctrls, retoid, valuestr ? valuestr : Py_None); } else { retval = - Py_BuildValue("(OOiO)", result_str, pmsg, res_msgid, pyctrls); + Py_BuildValue("(iOiO)", res_type, pmsg, res_msgid, pyctrls); } if (pmsg != Py_None) { @@ -1222,7 +1204,6 @@ l_ldap_result4(LDAPObject *self, PyObject *args) } Py_XDECREF(valuestr); Py_XDECREF(pyctrls); - Py_DECREF(result_str); return retval; } @@ -1296,7 +1277,7 @@ l_ldap_search_ext(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_search_ext"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1343,7 +1324,7 @@ l_ldap_whoami_s(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) { ber_bvfree(bvalue); - return LDAPerror(self->ldap, "ldap_whoami_s"); + return LDAPerror(self->ldap); } result = LDAPberval_to_unicode_object(bvalue); @@ -1370,7 +1351,7 @@ l_ldap_start_tls_s(LDAPObject *self, PyObject *args) LDAP_END_ALLOW_THREADS(self); if (ldaperror != LDAP_SUCCESS) { ldap_set_option(self->ldap, LDAP_OPT_ERROR_NUMBER, &ldaperror); - return LDAPerror(self->ldap, "ldap_start_tls_s"); + return LDAPerror(self->ldap); } Py_INCREF(Py_None); @@ -1462,7 +1443,7 @@ l_ldap_passwd(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_passwd"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } @@ -1513,7 +1494,7 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args) LDAPControl_List_DEL(client_ldcs); if (ldaperror != LDAP_SUCCESS) - return LDAPerror(self->ldap, "ldap_extended_operation"); + return LDAPerror(self->ldap); return PyInt_FromLong(msgid); } diff --git a/Modules/constants.c b/Modules/constants.c index f8da373..a447310 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -3,6 +3,7 @@ #include "common.h" #include "constants.h" +#include "ldapcontrol.h" #include "lber.h" #include "ldap.h" @@ -48,29 +49,48 @@ LDAPerr(int errnum) /* Convert an LDAP error into an informative python exception */ PyObject * -LDAPerror(LDAP *l, char *msg) +LDAPraise_for_message(LDAP *l, LDAPMessage *m) { if (l == NULL) { PyErr_SetFromErrno(LDAPexception_class); + ldap_msgfree(m); return NULL; } else { - int myerrno, errnum, opt_errnum; + int myerrno, errnum, opt_errnum, msgid = -1, msgtype = 0; PyObject *errobj; PyObject *info; PyObject *str; PyObject *pyerrno; - char *matched, *error; + PyObject *pyresult; + PyObject *pyctrls = NULL; + char *matched = NULL, + *error = NULL, + **refs = NULL; + LDAPControl **serverctrls = NULL; /* at first save errno for later use before it gets overwritten by another call */ myerrno = errno; - opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); - if (opt_errnum != LDAP_OPT_SUCCESS) - errnum = opt_errnum; + if (m != NULL) { + msgid = ldap_msgid(m); + msgtype = ldap_msgtype(m); + ldap_parse_result(l, m, &errnum, &matched, &error, &refs, + &serverctrls, 1); + } - if (errnum == LDAP_NO_MEMORY) - return PyErr_NoMemory(); + if (msgtype <= 0) { + opt_errnum = ldap_get_option(l, LDAP_OPT_ERROR_NUMBER, &errnum); + if (opt_errnum != LDAP_OPT_SUCCESS) + errnum = opt_errnum; + + if (errnum == LDAP_NO_MEMORY) { + return PyErr_NoMemory(); + } + + ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched); + ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error); + } if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; @@ -78,8 +98,32 @@ LDAPerror(LDAP *l, char *msg) errobj = LDAPexception_class; info = PyDict_New(); - if (info == NULL) + if (info == NULL) { + ldap_memfree(matched); + ldap_memfree(error); + ldap_memvfree((void **)refs); + ldap_controls_free(serverctrls); return NULL; + } + + if (msgtype > 0) { + pyresult = PyInt_FromLong(msgtype); + if (pyresult) + PyDict_SetItemString(info, "msgtype", pyresult); + Py_XDECREF(pyresult); + } + + if (msgid >= 0) { + pyresult = PyInt_FromLong(msgid); + if (pyresult) + PyDict_SetItemString(info, "msgid", pyresult); + Py_XDECREF(pyresult); + } + + pyresult = PyInt_FromLong(errnum); + if (pyresult) + PyDict_SetItemString(info, "result", pyresult); + Py_XDECREF(pyresult); str = PyUnicode_FromString(ldap_err2string(errnum)); if (str) @@ -93,8 +137,21 @@ LDAPerror(LDAP *l, char *msg) Py_XDECREF(pyerrno); } - if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 - && matched != NULL) { + if (!(pyctrls = LDAPControls_to_List(serverctrls))) { + int err = LDAP_NO_MEMORY; + + ldap_set_option(l, LDAP_OPT_ERROR_NUMBER, &err); + ldap_memfree(matched); + ldap_memfree(error); + ldap_memvfree((void **)refs); + ldap_controls_free(serverctrls); + return PyErr_NoMemory(); + } + ldap_controls_free(serverctrls); + PyDict_SetItemString(info, "ctrls", pyctrls); + Py_XDECREF(pyctrls); + + if (matched != NULL) { if (*matched != '\0') { str = PyUnicode_FromString(matched); if (str) @@ -104,33 +161,42 @@ LDAPerror(LDAP *l, char *msg) ldap_memfree(matched); } - if (errnum == LDAP_REFERRAL) { - str = PyUnicode_FromString(msg); + 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); Py_XDECREF(str); } - else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { - if (error != NULL && *error != '\0') { - str = PyUnicode_FromString(error); - if (str) - PyDict_SetItemString(info, "info", str); - Py_XDECREF(str); - } - ldap_memfree(error); - } + PyErr_SetObject(errobj, info); Py_DECREF(info); + ldap_memvfree((void **)refs); + ldap_memfree(error); return NULL; } } +PyObject * +LDAPerror(LDAP *l) +{ + return LDAPraise_for_message(l, NULL); +} + /* initialise the module constants */ int LDAPinit_constants(PyObject *m) { - PyObject *exc; + PyObject *exc, *nobj; /* simple constants */ @@ -160,6 +226,10 @@ LDAPinit_constants(PyObject *m) #define add_err(n) do { \ exc = PyErr_NewException("ldap." #n, LDAPexception_class, NULL); \ if (exc == NULL) return -1; \ + nobj = PyLong_FromLong(LDAP_##n); \ + if (nobj == NULL) return -1; \ + if (PyObject_SetAttrString(exc, "errnum", nobj) != 0) return -1; \ + Py_DECREF(nobj); \ errobjects[LDAP_##n+LDAP_ERROR_OFFSET] = exc; \ if (PyModule_AddObject(m, #n, exc) != 0) return -1; \ Py_INCREF(exc); \ diff --git a/Modules/constants.h b/Modules/constants.h index 8a390b5..b815094 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -11,7 +11,8 @@ extern int LDAPinit_constants(PyObject *m); extern PyObject *LDAPconstant(int); extern PyObject *LDAPexception_class; -extern PyObject *LDAPerror(LDAP *, char *msg); +extern PyObject *LDAPerror(LDAP *); +extern PyObject *LDAPraise_for_message(LDAP *, LDAPMessage *m); PyObject *LDAPerr(int errnum); #ifndef LDAP_CONTROL_PAGE_OID diff --git a/Modules/functions.c b/Modules/functions.c index 3a43c7c..2a567fd 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -25,7 +25,7 @@ l_ldap_initialize(PyObject *unused, PyObject *args) PyEval_RestoreThread(save); if (ret != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_initialize"); + return LDAPerror(ld); return (PyObject *)newLDAPObject(ld); } diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 6615598..17f52cb 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -353,13 +353,13 @@ encode_assertion_control(PyObject *self, PyObject *args) err = ldap_create(&ld); PyEval_RestoreThread(save); if (err != LDAP_SUCCESS) - return LDAPerror(ld, "ldap_create"); + return LDAPerror(ld); err = ldap_create_assertion_control_value(ld, assertion_filterstr, &ctrl_val); if (err != LDAP_SUCCESS) { - LDAPerror(ld, "ldap_create_assertion_control_value"); + LDAPerror(ld); save = PyEval_SaveThread(); ldap_unbind_ext(ld, NULL, NULL); PyEval_RestoreThread(save); diff --git a/Modules/message.c b/Modules/message.c index 2c05488..d225a1d 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -53,7 +53,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, if (dn == NULL) { Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_get_dn"); + return LDAPerror(ld); } attrdict = PyDict_New(); @@ -69,7 +69,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(result); ldap_msgfree(m); ldap_memfree(dn); - return LDAPerror(ld, "ldap_get_entry_controls"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ @@ -81,7 +81,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, ldap_msgfree(m); ldap_memfree(dn); ldap_controls_free(serverctrls); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); @@ -201,7 +201,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(reflist); Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_parse_reference"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -212,7 +212,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, Py_DECREF(result); ldap_msgfree(m); ldap_controls_free(serverctrls); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); if (refs) { @@ -255,7 +255,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, 0) != LDAP_SUCCESS) { Py_DECREF(result); ldap_msgfree(m); - return LDAPerror(ld, "ldap_parse_intermediate"); + return LDAPerror(ld); } /* convert serverctrls to list of tuples */ if (!(pyctrls = LDAPControls_to_List(serverctrls))) { @@ -267,7 +267,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, ldap_controls_free(serverctrls); ldap_memfree(retoid); ber_bvfree(retdata); - return LDAPerror(ld, "LDAPControls_to_List"); + return LDAPerror(ld); } ldap_controls_free(serverctrls); diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 96c3b2c..668107e 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -381,30 +381,36 @@ def test_compare(self): self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) self.assertEqual(result, _ldap.RES_ADD) + # try a false compare m = l.compare_ext(dn, "userPassword", "bad_string") - try: + with self.assertRaises(_ldap.COMPARE_FALSE) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.COMPARE_FALSE: - pass - else: - self.fail("expected COMPARE_FALSE, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 5) + self.assertFalse(e.exception.args[0]['ctrls']) + # try a true compare m = l.compare_ext(dn, "userPassword", "the_password") - try: + with self.assertRaises(_ldap.COMPARE_TRUE) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.COMPARE_TRUE: - pass - else: - self.fail("expected COMPARE_TRUE, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 6) + self.assertFalse(e.exception.args[0]['ctrls']) + # try a compare on bad attribute m = l.compare_ext(dn, "badAttribute", "ignoreme") - try: + with self.assertRaises(_ldap.error) as e: r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.error: - pass - else: - self.fail("expected LDAPError, got %r" % r) + + self.assertEqual(e.exception.args[0]['msgid'], m) + self.assertEqual(e.exception.args[0]['msgtype'], _ldap.RES_COMPARE) + self.assertEqual(e.exception.args[0]['result'], 17) + self.assertFalse(e.exception.args[0]['ctrls']) def test_delete_no_such_object(self): """ diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 36e2acf..a64b5f0 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -465,7 +465,7 @@ def test004_enotconn(self): info = ldap_err.args[0]['info'] expected_info = os.strerror(errno.ENOTCONN) if info != expected_info: - self.fail("expected info=%r, got %d" % (expected_info, info)) + self.fail("expected info=%r, got %r" % (expected_info, info)) else: self.fail("expected SERVER_DOWN, got %r" % r)