diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 277520e8..fd2082fe 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -84,7 +84,13 @@ This module defines the following functions: .. py:function:: set_option(option, invalue) -> None This function sets the value of the global option specified by *option* to - *invalue*. + *invalue*. Any change to global settings + + .. 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`. .. _ldap-constants: @@ -124,10 +130,10 @@ 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 @@ -220,34 +226,154 @@ SASL options TLS options ::::::::::: +.. warning:: + libldap does not materialize all TLS settings immediately. You must use + :py:const:`OPT_X_TLS_NEWCTX` to instruct libldap to apply pending TLS + settings and create a new internal TLS context:: + + conn = ldap.initialize(ldap_uri) + 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 + .. deprecated:: 3.0 + The option is deprecated in OpenLDAP and should no longer be used. It + will be removed in the future. + +.. py:data:: OPT_X_TLS_ALL + + Value for :py:const:`OPT_X_TLS_CRLCHECK` + .. py:data:: OPT_X_TLS_ALLOW + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. 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 + 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_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_CRLCHECK + + get/set CRL check mode. CRL validation needs :py:const:`OPT_X_TLS_CRLFILE` + + :py:const:`OPT_X_TLS_NONE` + Don't perform CRL checks + + :py:const:`OPT_X_TLS_PEER` + Perform CRL check for peer's end entity cert. + + :py:const:`OPT_X_TLS_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_CTX + get address of internal memory address of TLS context (**DO NOT USE**) + .. py:data:: OPT_X_TLS_DEMAND + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. py:data:: OPT_X_TLS_HARD + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. 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_NEVER + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + +.. py:data:: OPT_X_TLS_NEWCTX + + set and apply TLS settings to underlying TLS context + +.. py:data:: OPT_X_TLS_NONE + + Value for :py:const:`OPT_X_TLS_CRLCHECK` + +.. py:data:: OPT_X_TLS_PACKAGE + + Get TLS implementation, known values are + + * ``GnuTLS`` + * ``MozNSS`` (Mozilla NSS) + * ``OpenSSL`` + +.. py:data:: OPT_X_TLS_PEER + + Value for :py:const:`OPT_X_TLS_CRLCHECK` + +.. py:data:: OPT_X_TLS_PEERCERT + + Get peer's certificate as BER/DER data structure (not supported) + +.. py:data:: OPT_X_TLS_PROTOCOL_MIN + + get/set minimum protocol version (wire protocol version as int) + + * ``0x300`` for SSL 3.0 + * ``0x301`` for TLS 1.0 + * ``0x302`` for TLS 1.1 + * ``0x303`` for TLS 1.2 + * ``0x304`` for TLS 1.3 + .. py:data:: OPT_X_TLS_RANDOM_FILE + get/set path to /dev/urandom (**DO NOT USE**) + .. 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` + Ignore cert validation errors and don't check host name + + :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_TRY + .. deprecated:: 3.0 + This value is only used by slapd server internally. It will be removed + in the future. + +.. py:data:: OPT_X_TLS_VERSION + + Get negotiated TLS protocol version as string + .. _ldap-keepalive-options: Keepalive options @@ -564,6 +690,8 @@ The above exceptions are raised when a result code from an underlying API call does not indicate success. +.. _ldap-warnings: + Warnings ======== @@ -575,6 +703,16 @@ Warnings .. versionadded:: 3.0.0 +.. py:exception:: LDAPTLSWarning + + Raised when python-ldap detects missing call of + :py:meth:`LDAPObject.set_option` with + option :py:const:`OPT_X_TLS_NEWCTX`. + + See :ref:`ldap-tls-options` for details. + + .. versionadded:: 3.0.0 + .. _ldap-objects: diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 925ddc30..6964998a 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -39,6 +39,7 @@ defresult dereferenced dereferencing desc +dev directoryOperation distinguished distributedOperation @@ -144,6 +145,7 @@ UDP Umich unparsing unsigend +urandom uri urlPrefix urlscheme diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 3a86095f..282adf10 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -86,7 +86,7 @@ def release(self): from ldap.functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs -from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning +from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning, LDAPTLSWarning from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str del str2dn diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 4c109ac4..1f03407e 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -281,7 +281,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), diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index f37ef24a..6b9da293 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -14,7 +14,8 @@ 'LDAPObject', 'SimpleLDAPObject', 'ReconnectLDAPObject', - 'LDAPBytesWarning' + 'LDAPBytesWarning', + 'LDAPTLSWarning', ] @@ -25,6 +26,7 @@ import sys,time,pprint,_ldap,ldap,ldap.sasl,ldap.functions import warnings +from _ldap import LDAPTLSWarning from ldap.schema import SCHEMA_ATTRS from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples from ldap.extop import ExtendedRequest,ExtendedResponse diff --git a/Makefile b/Makefile index e4ff75ac..edf97774 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,10 @@ PYTHON_SUPP=/usr/share/doc/python3-devel/valgrind-python.supp .NOTPARALLEL: .PHONY: all -all: +all: Modules/constants_generated.h + +Modules/constants_generated.h: Lib/ldap/constants.py + $(PYTHON) $^ > $@ .PHONY: clean clean: diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index addbe961..ba352b40 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -29,6 +29,7 @@ newLDAPObject( LDAP* l ) self->ldap = l; self->_save = NULL; self->valid = 1; + self->need_tls_newctx = 0; return self; } @@ -54,18 +55,42 @@ dealloc( LDAPObject* self ) */ /* - * check to see if the LDAPObject is valid, + * check to see if the LDAPObject is valid and TLS settings are applied, * ie has been opened, and not closed. An exception is set if not valid. */ static int -not_valid( LDAPObject* l ) { - if (l->valid) { - return 0; - } else { +not_valid( LDAPObject* l, int check_tls ) { + if (!l->valid) { PyErr_SetString( LDAPexception_class, "LDAP connection invalid" ); return 1; } + if (l->need_tls_newctx) { + if (PyErr_WarnEx(LDAPTLSWarning_class, + "An OPT_X_TLS option was set but not applied. " + "You must call set_opt(ldap.OPT_X_TLS_NEWCTX, 0) " + "on the connection to apply new settings!", + 0) == -1) { + return 1; + } +#if 0 + else { + /* Apply setting and reset warning if warning was not fatal. */ + int res; + PyObject *intval = PyInt_FromLong(0); + + if (intval == NULL) { + return 1; + } + res = LDAP_set_option(l, LDAP_OPT_X_TLS_NEWCTX, intval); + Py_DECREF(intval); + if (res != 1) { + return 1; + } + } +#endif + } + return 0; } /* free a LDAPMod (complete or partially) allocated in Tuple_to_LDAPMod() */ @@ -362,7 +387,7 @@ l_ldap_unbind_ext( LDAPObject* self, PyObject* args ) int ldaperror; if (!PyArg_ParseTuple( args, "|OO", &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -405,7 +430,7 @@ l_ldap_abandon_ext( LDAPObject* self, PyObject* args ) int ldaperror; if (!PyArg_ParseTuple( args, "i|OO", &msgid, &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -450,7 +475,7 @@ l_ldap_add_ext( LDAPObject* self, PyObject *args ) LDAPMod **mods; if (!PyArg_ParseTuple( args, "sO|OO", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; mods = List_to_LDAPMods( modlist, 1 ); if (mods == NULL) @@ -502,7 +527,7 @@ l_ldap_simple_bind( LDAPObject* self, PyObject* args ) if (!PyArg_ParseTuple( args, "ss#|OO", &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, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -652,7 +677,7 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) if (!PyArg_ParseTuple(args, "zzz#OO", &dn, &mechanism, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; cred.bv_len = cred_len; @@ -719,7 +744,7 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) #endif return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -781,7 +806,7 @@ l_ldap_cancel( LDAPObject* self, PyObject* args ) int ldaperror; if (!PyArg_ParseTuple( args, "i|OO", &cancelid, &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -829,7 +854,7 @@ l_ldap_compare_ext( LDAPObject* self, PyObject *args ) if (!PyArg_ParseTuple( args, "sss#|OO", &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, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -872,7 +897,7 @@ l_ldap_delete_ext( LDAPObject* self, PyObject *args ) int ldaperror; if (!PyArg_ParseTuple( args, "s|OO", &dn, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -917,7 +942,7 @@ l_ldap_modify_ext( LDAPObject* self, PyObject *args ) LDAPMod **mods; if (!PyArg_ParseTuple( args, "sO|OO", &dn, &modlist, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; mods = List_to_LDAPMods( modlist, 0 ); if (mods == NULL) @@ -971,7 +996,7 @@ l_ldap_rename( LDAPObject* self, PyObject *args ) if (!PyArg_ParseTuple( args, "ss|ziOO", &dn, &newrdn, &newSuperior, &delold, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1024,7 +1049,7 @@ l_ldap_result4( LDAPObject* self, PyObject *args ) if (!PyArg_ParseTuple( args, "|iidiii", &msgid, &all, &timeout, &add_ctrls, &add_intermediates, &add_extop )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (timeout >= 0) { tvp = &tv; @@ -1165,7 +1190,7 @@ l_ldap_search_ext( LDAPObject* self, PyObject* args ) if (!PyArg_ParseTuple( args, "sis|OiOOdi", &base, &scope, &filter, &attrlist, &attrsonly, &serverctrls, &clientctrls, &timeout, &sizelimit )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!attrs_from_List( attrlist, &attrs )) return NULL; @@ -1225,7 +1250,7 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) int ldaperror; if (!PyArg_ParseTuple( args, "|OO", &serverctrls, &clientctrls)) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1266,7 +1291,7 @@ l_ldap_start_tls_s( LDAPObject* self, PyObject* args ) int ldaperror; if (!PyArg_ParseTuple( args, "" )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; LDAP_BEGIN_ALLOW_THREADS( self ); ldaperror = ldap_start_tls_s( self->ldap, NULL, NULL ); @@ -1292,6 +1317,7 @@ l_ldap_set_option(PyObject* self, PyObject *args) if (!PyArg_ParseTuple(args, "iO:set_option", &option, &value)) return NULL; + /* not_valid(self, 0) */ if (!LDAP_set_option((LDAPObject *)self, option, value)) return NULL; Py_INCREF(Py_None); @@ -1305,7 +1331,7 @@ static PyObject* l_ldap_get_option(PyObject* self, PyObject *args) { int option; - + /* not_valid(self, 0) */ if (!PyArg_ParseTuple(args, "i:get_option", &option)) return NULL; return LDAP_get_option((LDAPObject *)self, option); @@ -1338,7 +1364,7 @@ l_ldap_passwd( LDAPObject* self, PyObject *args ) 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, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) @@ -1390,7 +1416,7 @@ l_ldap_extended_operation( LDAPObject* self, PyObject *args ) if (!PyArg_ParseTuple( args, "sz#|OO", &reqoid, &reqvalue.bv_val, &reqvalue.bv_len, &serverctrls, &clientctrls )) return NULL; - if (not_valid(self)) return NULL; + if (not_valid(self, 1)) return NULL; if (!PyNone_Check(serverctrls)) { if (!LDAPControls_from_object(serverctrls, &server_ldcs)) diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index 8cd6fc3e..5f92af3c 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -22,6 +22,7 @@ typedef struct { LDAP* ldap; _threadstate _save; /* for thread saving on referrals */ int valid; + int need_tls_newctx; } LDAPObject; extern PyTypeObject LDAP_Type; diff --git a/Modules/constants.c b/Modules/constants.c index c2b595c1..f42d5661 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -11,6 +11,10 @@ PyObject* LDAPexception_class; +/* warnings */ + +PyObject *LDAPTLSWarning_class; + /* list of exception classes */ #define LDAP_ERROR_MIN LDAP_REFERRAL_LIMIT_EXCEEDED @@ -150,6 +154,18 @@ LDAPinit_constants( PyObject* m ) if (PyModule_AddObject(m, "error", LDAPexception_class) != 0) return -1; Py_INCREF(LDAPexception_class); + /* TLS Warning */ + LDAPTLSWarning_class = PyErr_NewException("ldap.LDAPTLSWarning", + PyExc_RuntimeWarning, + NULL); + if (LDAPTLSWarning_class == NULL) { + return -1; + } + if (PyModule_AddObject(m, "LDAPTLSWarning", LDAPTLSWarning_class) != 0) { + return -1; + } + Py_INCREF(LDAPTLSWarning_class); + /* Generated constants -- see Lib/ldap/constants.py */ #define add_err(n) do { \ diff --git a/Modules/constants.h b/Modules/constants.h index 4056f907..f68af13e 100644 --- a/Modules/constants.h +++ b/Modules/constants.h @@ -11,6 +11,7 @@ extern int LDAPinit_constants( PyObject* m ); extern PyObject* LDAPconstant( int ); extern PyObject* LDAPexception_class; +extern PyObject* LDAPTLSWarning_class; extern PyObject* LDAPerror( LDAP*, char*msg ); PyObject* LDAPerr(int errnum); diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 083ba161..78b68760 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -216,11 +216,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 diff --git a/Modules/options.c b/Modules/options.c index ee606d46..114da387 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -23,7 +23,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); @@ -184,14 +184,47 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) 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)) LDAPControl_List_DEL(controls); - + +#if defined(HAVE_TLS) && defined(LDAP_OPT_X_TLS_NEWCTX) + /* set needs_tls_newctx for any OPT_X_TLS option */ + if (self != NULL) { + switch(option) { + case LDAP_OPT_X_TLS_REQUIRE_CERT: /* fallthrough */ +#ifdef LDAP_OPT_X_TLS_CRLCHECK + case LDAP_OPT_X_TLS_CRLCHECK: /* fallthrough */ +#endif +#ifdef LDAP_OPT_X_TLS_CRLFILE + case LDAP_OPT_X_TLS_CRLFILE: /* fallthrough */ +#endif +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN + case LDAP_OPT_X_TLS_PROTOCOL_MIN: /* fallthrough */ +#endif + case LDAP_OPT_X_TLS_CACERTFILE: /* fallthrough */ + case LDAP_OPT_X_TLS_CACERTDIR: /* fallthrough */ + case LDAP_OPT_X_TLS_CERTFILE: /* fallthrough */ + case LDAP_OPT_X_TLS_KEYFILE: /* fallthrough */ + case LDAP_OPT_X_TLS_CIPHER_SUITE: /* fallthrough */ + case LDAP_OPT_X_TLS_RANDOM_FILE: /* fallthrough */ + case LDAP_OPT_X_TLS_DHFILE: /* fallthrough */ + self->need_tls_newctx = 1; + break; + case LDAP_OPT_X_TLS_NEWCTX: + self->need_tls_newctx = 0; + break; + default: + break; + } + } + #endif /* HAVE_TLS */ + + if (res != LDAP_OPT_SUCCESS) { option_error(res, "ldap_set_option"); return 0; @@ -223,7 +256,7 @@ LDAP_get_option(LDAPObject *self, int option) 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]) diff --git a/Modules/options.h b/Modules/options.h index 570fdc15..99096f6d 100644 --- a/Modules/options.h +++ b/Modules/options.h @@ -5,3 +5,5 @@ 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/Tests/t_cext.py b/Tests/t_cext.py index d8233dce..5b7b7740 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -8,7 +8,9 @@ from __future__ import unicode_literals import os +import sys import unittest +import warnings from slapdtest import SlapdTestCase, requires_tls @@ -817,6 +819,18 @@ def test_invalid_controls(self): l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,)) self.assertInvalidControls(l.unbind_ext) + def assertTLSError(self, exc): + self.assertIsInstance(exc, _ldap.CONNECT_ERROR) + # known resaons: + # Ubuntu on Travis: '(unknown error code)' + # OpenSSL 1.1: error:1416F086:SSL routines:\ + # tls_process_server_certificate:certificate verify failed + # NSS: TLS error -8172:Peer's certificate issuer has \ + # been marked as not trusted by the user. + candidates = ('certificate', 'tls', '(unknown error code)') + if not any(s in str(exc).lower() for s in candidates): + self.fail(exc) + @requires_tls() def test_tls_ext(self): l = self._open_conn(bind=False) @@ -833,16 +847,7 @@ def test_tls_ext_noca(self): l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) with self.assertRaises(_ldap.CONNECT_ERROR) as e: l.start_tls_s() - # known resaons: - # Ubuntu on Travis: '(unknown error code)' - # OpenSSL 1.1: error:1416F086:SSL routines:\ - # tls_process_server_certificate:certificate verify failed - # NSS: TLS error -8172:Peer's certificate issuer has \ - # been marked as not trusted by the user. - msg = str(e.exception) - candidates = ('certificate', 'tls', '(unknown error code)') - if not any(s in msg.lower() for s in candidates): - self.fail(msg) + self.assertTLSError(e.exception) @requires_tls() def test_tls_ext_clientcert(self): @@ -853,7 +858,35 @@ def test_tls_ext_clientcert(self): l.set_option(_ldap.OPT_X_TLS_KEYFILE, self.server.clientkey) l.set_option(_ldap.OPT_X_TLS_REQUIRE_CERT, _ldap.OPT_X_TLS_HARD) l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) - l.start_tls_s() + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('once', _ldap.LDAPTLSWarning) + l.start_tls_s() + # No warning for OPT_X_TLS_NEWCTX + self.assertEqual(len(w), 0) + + @requires_tls() + def test_tls_warn_newctx(self): + l = self._open_conn(bind=False) + l.set_option(_ldap.OPT_PROTOCOL_VERSION, _ldap.VERSION3) + # don't check cert chain + l.set_option(_ldap.OPT_X_TLS_REQUIRE_CERT, _ldap.OPT_X_TLS_NEVER) + l.set_option(_ldap.OPT_X_TLS_NEWCTX, 0) + # load CA cert but don't call NEWCTX + l.set_option(_ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + warnings.simplefilter('always', _ldap.LDAPTLSWarning) + # This emits one warning. + l.start_tls_s() + self.assertEqual(len(w), 1) + self.assertEqual(w[0].category, _ldap.LDAPTLSWarning) + self.assertEqual( + str(w[0].message), + "An OPT_X_TLS option was set but not applied. You must call " + "set_opt(ldap.OPT_X_TLS_NEWCTX, 0) on the connection to apply " + "new settings!" + ) @requires_tls() def test_tls_packages(self): diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 835512b0..ce691a51 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -350,6 +350,9 @@ def test_ldapbyteswarning(self): "LDAP connection" % self.server.suffix ) + def test_ldaptlswarning(self): + self.assertIsSubclass(ldap.LDAPBytesWarning, Warning) + class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """