@@ -209,6 +209,12 @@ protected function getConnection()
209209 $ this ->ldap ->setOption (null , LDAP_OPT_X_TLS_REQUIRE_CERT , LDAP_OPT_X_TLS_NEVER );
210210 }
211211
212+ // Configure any user-provided CA cert files for LDAP.
213+ // This option works globally and must be set before a connection is created.
214+ if ($ this ->config ['tls_ca_cert ' ]) {
215+ $ this ->configureTlsCaCerts ($ this ->config ['tls_ca_cert ' ]);
216+ }
217+
212218 $ ldapHost = $ this ->parseServerString ($ this ->config ['server ' ]);
213219 $ ldapConnection = $ this ->ldap ->connect ($ ldapHost );
214220
@@ -223,7 +229,14 @@ protected function getConnection()
223229
224230 // Start and verify TLS if it's enabled
225231 if ($ this ->config ['start_tls ' ]) {
226- $ started = $ this ->ldap ->startTls ($ ldapConnection );
232+ try {
233+ $ started = $ this ->ldap ->startTls ($ ldapConnection );
234+ } catch (\Exception $ exception ) {
235+ $ error = $ exception ->getMessage () . ' :: ' . ldap_error ($ ldapConnection );
236+ ldap_get_option ($ ldapConnection , LDAP_OPT_DIAGNOSTIC_MESSAGE , $ detail );
237+ Log::info ("LDAP STARTTLS failure: {$ error } {$ detail }" );
238+ throw new LdapException ('Could not start TLS connection. Further details in the application log. ' );
239+ }
227240 if (!$ started ) {
228241 throw new LdapException ('Could not start TLS connection ' );
229242 }
@@ -234,6 +247,33 @@ protected function getConnection()
234247 return $ this ->ldapConnection ;
235248 }
236249
250+ /**
251+ * Configure TLS CA certs globally for ldap use.
252+ * This will detect if the given path is a directory or file, and set the relevant
253+ * LDAP TLS options appropriately otherwise throw an exception if no file/folder found.
254+ *
255+ * Note: When using a folder, certificates are expected to be correctly named by hash
256+ * which can be done via the c_rehash utility.
257+ *
258+ * @throws LdapException
259+ */
260+ protected function configureTlsCaCerts (string $ caCertPath ): void
261+ {
262+ $ errMessage = "Provided path [ {$ caCertPath }] for LDAP TLS CA certs could not be resolved to an existing location " ;
263+ $ path = realpath ($ caCertPath );
264+ if ($ path === false ) {
265+ throw new LdapException ($ errMessage );
266+ }
267+
268+ if (is_dir ($ path )) {
269+ $ this ->ldap ->setOption (null , LDAP_OPT_X_TLS_CACERTDIR , $ path );
270+ } else if (is_file ($ path )) {
271+ $ this ->ldap ->setOption (null , LDAP_OPT_X_TLS_CACERTFILE , $ path );
272+ } else {
273+ throw new LdapException ($ errMessage );
274+ }
275+ }
276+
237277 /**
238278 * Parse an LDAP server string and return the host suitable for a connection.
239279 * Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
@@ -249,13 +289,18 @@ protected function parseServerString(string $serverString): string
249289
250290 /**
251291 * Build a filter string by injecting common variables.
292+ * Both "${var}" and "{var}" style placeholders are supported.
293+ * Dollar based are old format but supported for compatibility.
252294 */
253295 protected function buildFilter (string $ filterString , array $ attrs ): string
254296 {
255297 $ newAttrs = [];
256298 foreach ($ attrs as $ key => $ attrText ) {
257- $ newKey = '${ ' . $ key . '} ' ;
258- $ newAttrs [$ newKey ] = $ this ->ldap ->escape ($ attrText );
299+ $ escapedText = $ this ->ldap ->escape ($ attrText );
300+ $ oldVarKey = '${ ' . $ key . '} ' ;
301+ $ newVarKey = '{ ' . $ key . '} ' ;
302+ $ newAttrs [$ oldVarKey ] = $ escapedText ;
303+ $ newAttrs [$ newVarKey ] = $ escapedText ;
259304 }
260305
261306 return strtr ($ filterString , $ newAttrs );
0 commit comments