Skip to content

Commit

Permalink
ext/ldap: Use FCC for rebind_proc callback (#17369)
Browse files Browse the repository at this point in the history
  • Loading branch information
Girgias authored Jan 8, 2025
1 parent 1ec95c3 commit 915feea
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 75 deletions.
49 changes: 26 additions & 23 deletions ext/ldap/ldap.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void ldap_memvfree(void **v)
typedef struct {
LDAP *link;
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && defined(HAVE_3ARG_SETREBINDPROC)
zval rebindproc;
zend_fcall_info_cache rebind_proc_fcc;
#endif
zend_object std;
} ldap_linkdata;
Expand Down Expand Up @@ -131,7 +131,9 @@ static void ldap_link_free(ldap_linkdata *ld)
ld->link = NULL;

#if defined(LDAP_API_FEATURE_X_OPENLDAP) && defined(HAVE_3ARG_SETREBINDPROC)
zval_ptr_dtor(&ld->rebindproc);
if (ZEND_FCC_INITIALIZED(ld->rebind_proc_fcc)) {
zend_fcc_dtor(&ld->rebind_proc_fcc);
}
#endif

LDAPG(num_links)--;
Expand Down Expand Up @@ -3711,19 +3713,20 @@ int _ldap_rebind_proc(LDAP *ldap, const char *url, ber_tag_t req, ber_int_t msgi
}

/* link exists and callback set? */
if (Z_ISUNDEF(ld->rebindproc)) {
if (!ZEND_FCC_INITIALIZED(ld->rebind_proc_fcc)) {
php_error_docref(NULL, E_WARNING, "No callback set");
return LDAP_OTHER;
}

/* callback */
ZVAL_COPY_VALUE(&cb_args[0], cb_link);
ZVAL_STRING(&cb_args[1], url);
if (call_user_function(EG(function_table), NULL, &ld->rebindproc, &cb_retval, 2, cb_args) == SUCCESS && !Z_ISUNDEF(cb_retval)) {
zend_call_known_fcc(&ld->rebind_proc_fcc, &cb_retval, 2, cb_args, NULL);
if (EXPECTED(!Z_ISUNDEF(cb_retval))) {
// TODO Use zval_try_get_long()
retval = zval_get_long(&cb_retval);
zval_ptr_dtor(&cb_retval);
} else {
php_error_docref(NULL, E_WARNING, "rebind_proc PHP callback failed");
retval = LDAP_OTHER;
}
zval_ptr_dtor(&cb_args[1]);
Expand All @@ -3735,35 +3738,35 @@ int _ldap_rebind_proc(LDAP *ldap, const char *url, ber_tag_t req, ber_int_t msgi
PHP_FUNCTION(ldap_set_rebind_proc)
{
zval *link;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
zend_fcall_info dummy_fci;
zend_fcall_info_cache fcc = empty_fcall_info_cache;
ldap_linkdata *ld;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "Of!", &link, ldap_link_ce, &fci, &fcc) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "OF!", &link, ldap_link_ce, &dummy_fci, &fcc) == FAILURE) {
RETURN_THROWS();
}

ld = Z_LDAP_LINK_P(link);
VERIFY_LDAP_LINK_CONNECTED(ld);

if (!ZEND_FCI_INITIALIZED(fci)) {
/* unregister rebind procedure */
if (!Z_ISUNDEF(ld->rebindproc)) {
zval_ptr_dtor(&ld->rebindproc);
ZVAL_UNDEF(&ld->rebindproc);
ldap_set_rebind_proc(ld->link, NULL, NULL);
}
RETURN_TRUE;
/* Inline VERIFY_LDAP_LINK_CONNECTED(ld); as we need to free trampoline */
if (!ld->link) {
zend_release_fcall_info_cache(&fcc);
zend_throw_error(NULL, "LDAP connection has already been closed");
RETURN_THROWS();
}

/* register rebind procedure */
if (Z_ISUNDEF(ld->rebindproc)) {
/* Free old FCC */
if (!ZEND_FCC_INITIALIZED(ld->rebind_proc_fcc)) {
zend_fcc_dtor(&ld->rebind_proc_fcc);
}
if (ZEND_FCC_INITIALIZED(fcc)) {
/* register rebind procedure */
ldap_set_rebind_proc(ld->link, _ldap_rebind_proc, (void *) link);
zend_fcc_dup(&ld->rebind_proc_fcc, &fcc);
} else {
zval_ptr_dtor(&ld->rebindproc);
}
/* unregister rebind procedure */
ldap_set_rebind_proc(ld->link, NULL, NULL);
}

ZVAL_COPY(&ld->rebindproc, &fci.function_name);
RETURN_TRUE;
}
/* }}} */
Expand Down
25 changes: 17 additions & 8 deletions ext/ldap/tests/ldap_set_rebind_proc_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,31 @@ require_once('skipifbindfailure.inc');
?>
--FILE--
<?php
/*** NOTE: THE CALLBACK IS NOT CALLED AS WE DON'T TEST THE REBINDING HAPPENS AS WE NEED MULTIPLE LDAP SERVERS ***/

require "connect.inc";

function rebind_proc ($ds, $ldap_url) {
global $user;
global $passwd;
global $protocol_version;
function rebind_proc ($ldap, $referral) {
global $user;
global $passwd;
global $protocol_version;

// required by most modern LDAP servers, use LDAPv3
ldap_set_option($a, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
// required by most modern LDAP servers, use LDAPv3
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);

if (!ldap_bind($a, $user, $passwd)) {
if (!ldap_bind($ldap, $user, $passwd)) {
// Failure
print "Cannot bind";
}
return 1;
}
// Success
return 0;
}

$link = ldap_connect_and_bind($uri, $user, $passwd, $protocol_version);
ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($link, LDAP_OPT_REFERRALS, true);

var_dump(ldap_set_rebind_proc($link, "rebind_proc"));
var_dump(ldap_set_rebind_proc($link, null));
?>
Expand Down
44 changes: 0 additions & 44 deletions ext/ldap/tests/ldap_set_rebind_proc_error.phpt

This file was deleted.

65 changes: 65 additions & 0 deletions ext/ldap/tests/ldap_set_rebind_proc_trampoline.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
ldap_set_rebind_proc() with a trampoline
--EXTENSIONS--
ldap
--SKIPIF--
<?php
if (!function_exists('ldap_set_rebind_proc')) die("skip ldap_set_rebind_proc() not available");
require_once('skipifbindfailure.inc');
?>
--FILE--
<?php
/*** NOTE: THE TRAMPOLINE IS NOT CALLED AS WE DON'T TEST THE REBINDING HAPPENS AS WE NEED MULTIPLE LDAP SERVERS ***/

require "connect.inc";

class TrampolineTest {
public function __construct(private $user, private $password, private $protocol_version) {}
public function __call(string $name, array $arguments) {
echo 'Trampoline for ', $name, PHP_EOL;
var_dump(count($arguments));
if ($name === 'trampolineThrow') {
throw new Exception('boo');
}
if ($name === 'trampolineWrongType') {
return ['not an int'];
}
// required by most modern LDAP servers, use LDAPv3
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, $this->$protocol_version);
if (!ldap_bind($ldap, $this->$user, $this->$password)) {
// Failure
print "Cannot bind";
return 1;
}
// Success
return 0;
}
}
$o = new TrampolineTest($user, $passwd, $protocol_version);
$callback = [$o, 'trampoline'];
$callbackThrow = [$o, 'trampolineThrow'];
$callbackWrongType = [$o, 'trampolineWrongType'];

$link = ldap_connect_and_bind($uri, $user, $passwd, $protocol_version);
var_dump(ldap_set_rebind_proc($link, $callback));
var_dump(ldap_set_rebind_proc($link, null));
var_dump(ldap_set_rebind_proc($link, $callbackThrow));
var_dump(ldap_set_rebind_proc($link, null));
var_dump(ldap_set_rebind_proc($link, $callbackWrongType));

var_dump(ldap_unbind($link));
try {
var_dump(ldap_set_rebind_proc($link, $callback));
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
Error: LDAP connection has already been closed

0 comments on commit 915feea

Please sign in to comment.