Wed Jul 19 16:20:07 2017 UTC ()
Add php70 and php71 patches for the exop option

The LDAP controls and extended operations (EXOP) has been merged upstream
with some modifications. The patches backports upstream code to php70
and php71, with additionnal EXOP REFRESH support that is missing for now.


(manu)
diff -r1.27 -r1.28 pkgsrc/databases/php-ldap/Makefile
diff -r0 -r1.1 pkgsrc/databases/php-ldap/files/ldap-ctrl-exop70.patch
diff -r0 -r1.1 pkgsrc/databases/php-ldap/files/ldap-ctrl-exop71.patch

cvs diff -r1.27 -r1.28 pkgsrc/databases/php-ldap/Makefile (expand / switch to unified diff)

--- pkgsrc/databases/php-ldap/Makefile 2016/04/02 09:05:22 1.27
+++ pkgsrc/databases/php-ldap/Makefile 2017/07/19 16:20:07 1.28
@@ -1,16 +1,17 @@ @@ -1,16 +1,17 @@
1# $NetBSD: Makefile,v 1.27 2016/04/02 09:05:22 taca Exp $ 1# $NetBSD: Makefile,v 1.28 2017/07/19 16:20:07 manu Exp $
2 2
3MODNAME= ldap 3MODNAME= ldap
 4PKGREVISION= 1
4CATEGORIES+= databases 5CATEGORIES+= databases
5COMMENT= PHP extension for LDAP database access 6COMMENT= PHP extension for LDAP database access
6 7
7CONFLICTS= php-ldap-[0-9]* 8CONFLICTS= php-ldap-[0-9]*
8 9
9USE_PHP_EXT_PATCHES= yes 10USE_PHP_EXT_PATCHES= yes
10 11
11.include "options.mk" 12.include "options.mk"
12 13
13CONFIGURE_ARGS+= --with-${MODNAME}=shared,${BUILDLINK_PREFIX.openldap-client} 14CONFIGURE_ARGS+= --with-${MODNAME}=shared,${BUILDLINK_PREFIX.openldap-client}
14 15
15.include "../../lang/php/ext.mk" 16.include "../../lang/php/ext.mk"
16.include "../../databases/openldap-client/buildlink3.mk" 17.include "../../databases/openldap-client/buildlink3.mk"

File Added: pkgsrc/databases/php-ldap/files/ldap-ctrl-exop70.patch
From upstream:
git diff 035a27cbc63d87a6acc761ce51109bcf47f9c27b \
    ed8bfcc6ea60c9d5b8c6b10f9e5175237fe28751 -- ext/ldap/ldap.c \
    ext/ldap/config.m4 ext/ldap/config.w32

Addditionnal uncommitted yet modification for ldap_exop_refresh
(NB: previous patch had ldap_refresh with different prototype)

--- ext/ldap/config.m4.orig
+++ ext/ldap/config.m4
@@ -203,9 +203,9 @@
   fi
 
   dnl Solaris 2.8 claims to be 2004 API, but doesn't have
   dnl ldap_parse_reference() nor ldap_start_tls_s()
-  AC_CHECK_FUNCS([ldap_parse_result ldap_parse_reference ldap_start_tls_s ldap_control_find])
+  AC_CHECK_FUNCS([ldap_parse_result ldap_parse_reference ldap_start_tls_s ldap_control_find ldap_parse_extended_result ldap_extended_operation ldap_extended_operation_s ldap_passwd_s ldap_whoami_s ldap_refresh_s])
 
   dnl
   dnl SASL check
   dnl
--- ext/ldap/config.w32.orig
+++ ext/ldap/config.w32
@@ -22,8 +22,14 @@
 		AC_DEFINE('HAVE_LDAP_SASL', 1);
 		AC_DEFINE('HAVE_LDAP_SASL_SASL_H', 1);
 		AC_DEFINE('LDAP_DEPRECATED', 1);
 		AC_DEFINE('HAVE_LDAP_CONTROL_FIND', 1);
+		AC_DEFINE('HAVE_LDAP_PARSE_EXTENDED_RESULT', 1);
+		AC_DEFINE('HAVE_LDAP_EXTENDED_OPERATION_S', 1);
+		AC_DEFINE('HAVE_LDAP_PASSWD_S', 1);
+		AC_DEFINE('HAVE_LDAP_WHOAMI_S', 1);
+		AC_DEFINE('HAVE_LDAP_REFRESH_S', 1);
+		AC_DEFINE('HAVE_LDAP_EXTENDED_OPERATION', 1);
 
 	} else {
 		WARNING("ldap not enabled; libraries and headers not found");
 	}
--- ext/ldap/ldap.c.orig
+++ ext/ldap/ldap.c
@@ -239,8 +239,86 @@
 
 	REGISTER_LONG_CONSTANT("LDAP_ESCAPE_FILTER", PHP_LDAP_ESCAPE_FILTER, CONST_PERSISTENT | CONST_CS);
 	REGISTER_LONG_CONSTANT("LDAP_ESCAPE_DN", PHP_LDAP_ESCAPE_DN, CONST_PERSISTENT | CONST_CS);
 
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_START_TLS", LDAP_EXOP_START_TLS, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_MODIFY_PASSWD", LDAP_EXOP_MODIFY_PASSWD, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_REFRESH", LDAP_EXOP_REFRESH, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_WHO_AM_I", LDAP_EXOP_WHO_AM_I, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_TURN", LDAP_EXOP_TURN, CONST_PERSISTENT | CONST_CS);
+#endif
+
+/* LDAP Controls */
+/*	standard track controls */
+#ifdef LDAP_CONTROL_MANAGEDSAIT
+	/* RFC 3296 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_MANAGEDSAIT", LDAP_CONTROL_MANAGEDSAIT, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_PROXY_AUTHZ
+	/* RFC 4370 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PROXY_AUTHZ", LDAP_CONTROL_PROXY_AUTHZ, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SUBENTRIES
+	/* RFC 3672 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SUBENTRIES", LDAP_CONTROL_SUBENTRIES, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_VALUESRETURNFILTER
+	/* RFC 3876 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VALUESRETURNFILTER", LDAP_CONTROL_VALUESRETURNFILTER, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_ASSERT
+	/* RFC 4528 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_ASSERT", LDAP_CONTROL_ASSERT, CONST_PERSISTENT | CONST_CS);
+	/* RFC 4527 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PRE_READ", LDAP_CONTROL_PRE_READ, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_POST_READ", LDAP_CONTROL_POST_READ, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SORTREQUEST
+	/* RFC 2891 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SORTREQUEST", LDAP_CONTROL_SORTREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SORTRESPONSE", LDAP_CONTROL_SORTRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+/*	non-standard track controls */
+#ifdef LDAP_CONTROL_PAGEDRESULTS
+	/* RFC 2696 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PAGEDRESULTS", LDAP_CONTROL_PAGEDRESULTS, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_AUTHZID_REQUEST
+	/* RFC 3829 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_AUTHZID_REQUEST", LDAP_CONTROL_AUTHZID_REQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_AUTHZID_RESPONSE", LDAP_CONTROL_AUTHZID_RESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SYNC
+	/* LDAP Content Synchronization Operation -- RFC 4533 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC", LDAP_CONTROL_SYNC, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC_STATE", LDAP_CONTROL_SYNC_STATE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC_DONE", LDAP_CONTROL_SYNC_DONE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_DONTUSECOPY
+	/* LDAP Don't Use Copy Control (RFC 6171) */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_DONTUSECOPY", LDAP_CONTROL_DONTUSECOPY, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
+	/* Password policy Controls */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PASSWORDPOLICYREQUEST", LDAP_CONTROL_PASSWORDPOLICYREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PASSWORDPOLICYRESPONSE", LDAP_CONTROL_PASSWORDPOLICYRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_X_INCREMENTAL_VALUES
+	/* MS Active Directory controls */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_INCREMENTAL_VALUES", LDAP_CONTROL_X_INCREMENTAL_VALUES, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_DOMAIN_SCOPE", LDAP_CONTROL_X_DOMAIN_SCOPE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_PERMISSIVE_MODIFY", LDAP_CONTROL_X_PERMISSIVE_MODIFY, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_SEARCH_OPTIONS", LDAP_CONTROL_X_SEARCH_OPTIONS, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_TREE_DELETE", LDAP_CONTROL_X_TREE_DELETE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_EXTENDED_DN", LDAP_CONTROL_X_EXTENDED_DN, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_X_INCREMENTAL_VALUES
+	/* LDAP VLV */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VLVREQUEST", LDAP_CONTROL_VLVREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VLVRESPONSE", LDAP_CONTROL_VLVRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+
 	le_link = zend_register_list_destructors_ex(_close_ldap_link, NULL, "ldap link", module_number);
 	le_result = zend_register_list_destructors_ex(_free_ldap_result, NULL, "ldap result", module_number);
 	le_result_entry = zend_register_list_destructors_ex(_free_ldap_result_entry, NULL, "ldap result entry", module_number);
 
@@ -2184,11 +2262,41 @@
 			zval_ptr_dtor(retval);
 			ZVAL_STRING(retval, val);
 			ldap_memfree(val);
 		} break;
-/* options not implemented
 	case LDAP_OPT_SERVER_CONTROLS:
 	case LDAP_OPT_CLIENT_CONTROLS:
+		{
+			zval tmp1;
+			int num_entries;
+			LDAPControl **ctrls = NULL, **ctrlp;
+
+			if (ldap_get_option(ld->link, option, &ctrls) || ctrls == NULL) {
+				if (ctrls) {
+					ldap_memfree(ctrls);
+				}
+				RETURN_FALSE;
+			}
+
+			zval_ptr_dtor(retval);
+			array_init(retval);
+			num_entries = 0;
+			ctrlp = ctrls;
+			while (*ctrlp != NULL)
+			{
+				array_init(&tmp1);
+				add_assoc_string(&tmp1, "oid", (*ctrlp)->ldctl_oid);
+				add_assoc_bool(&tmp1, "iscritical", ((*ctrlp)->ldctl_iscritical != 0));
+				if ((*ctrlp)->ldctl_value.bv_len) {
+					add_assoc_stringl(&tmp1, "value", (*ctrlp)->ldctl_value.bv_val, (*ctrlp)->ldctl_value.bv_len);
+				}
+				zend_hash_index_update(Z_ARRVAL_P(retval), num_entries, &tmp1);
+				num_entries++;
+				ctrlp++;
+			}
+			ldap_controls_free(ctrls);
+		} break;
+/* options not implemented
 	case LDAP_OPT_API_INFO:
 	case LDAP_OPT_API_FEATURE_INFO:
 */
 	default:
@@ -2455,8 +2563,69 @@
 }
 /* }}} */
 #endif
 
+/* {{{ Extended operation response parsing, Pierangelo Masarati */
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+/* {{{ proto bool ldap_parse_exop(resource link, resource result [, string retdata [, string retoid]])
+   Extract information from extended operation result */
+PHP_FUNCTION(ldap_parse_exop)
+{
+	zval *link, *result, *retdata, *retoid;
+	ldap_linkdata *ld;
+	LDAPMessage *ldap_result;
+	char *lretoid;
+	struct berval *lretdata;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr|z/z/", &link, &result, &retdata, &retoid) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	if ((ldap_result = (LDAPMessage *)zend_fetch_resource(Z_RES_P(result), "ldap result", le_result)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	rc = ldap_parse_extended_result(ld->link, ldap_result,
+				myargcount > 3 ? &lretoid: NULL,
+				myargcount > 2 ? &lretdata: NULL,
+				0);
+	if (rc != LDAP_SUCCESS) {
+		php_error_docref(NULL, E_WARNING, "Unable to parse extended operation result: %s", ldap_err2string(rc));
+		RETURN_FALSE;
+	}
+
+	/* Reverse -> fall through */
+	switch (myargcount) {
+		case 4:
+			zval_dtor(retoid);
+			if (lretoid == NULL) {
+				ZVAL_EMPTY_STRING(retoid);
+			} else {
+				ZVAL_STRING(retoid, lretoid);
+				ldap_memfree(lretoid);
+			}
+		case 3:
+			/* use arg #3 as the data returned by the server */
+			zval_dtor(retdata);
+			if (lretdata == NULL) {
+				ZVAL_EMPTY_STRING(retdata);
+			} else {
+				ZVAL_STRINGL(retdata, lretdata->bv_val, lretdata->bv_len);
+				ldap_memfree(lretdata->bv_val);
+				ldap_memfree(lretdata);
+			}
+	}
+	RETURN_TRUE;
+}
+/* }}} */
+#endif
+/* }}} */
+
 /* {{{ proto resource ldap_first_reference(resource link, resource result)
    Return first reference */
 PHP_FUNCTION(ldap_first_reference)
 {
@@ -3035,8 +3204,245 @@
 }
 /* }}} */
 #endif
 
+/* {{{ Extended operations, Pierangelo Masarati */
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+/* {{{ proto ? ldap_exop(resource link, string reqoid [, string reqdata [, string retdata [, string retoid]]])
+   Extended operation */
+PHP_FUNCTION(ldap_exop)
+{
+	zval *link, *reqoid, *reqdata, *retdata, *retoid;
+	char *lreqoid, *lretoid = NULL;
+	struct berval lreqdata, *lretdata = NULL;
+	ldap_linkdata *ld;
+	LDAPMessage *ldap_res;
+	int rc, msgid, myargcount = ZEND_NUM_ARGS();
+	/* int reqoid_len, reqdata_len, retdata_len, retoid_len, retdat_len; */
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz|zz/z/", &link, &reqoid, &reqdata, &retdata, &retoid) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	switch (myargcount) {
+	case 5:
+	case 4:
+	case 3:
+		convert_to_string_ex(reqdata);
+		lreqdata.bv_val = Z_STRVAL_P(reqdata);
+		lreqdata.bv_len = Z_STRLEN_P(reqdata);
+		/* fallthru */
+	case 2:
+		convert_to_string_ex(reqoid);
+		lreqoid = Z_STRVAL_P(reqoid);
+	}
+
+	if (myargcount > 3) {
+		/* synchronous call */
+		rc = ldap_extended_operation_s(ld->link, lreqoid,
+			lreqdata.bv_len > 0 ? &lreqdata: NULL,
+			NULL,
+			NULL,
+			myargcount > 4 ? &lretoid : NULL,
+			&lretdata );
+		if (rc != LDAP_SUCCESS ) {
+			php_error_docref(NULL, E_WARNING, "Extended operation %s failed: %s (%d)", lreqoid, ldap_err2string(rc), rc);
+			RETURN_FALSE;
+		}
+
+		/* Reverse -> fall through */
+		switch (myargcount) {
+			case 5:
+				zval_dtor(retoid);
+				if (lretoid == NULL) {
+					ZVAL_EMPTY_STRING(retoid);
+				} else {
+					ZVAL_STRING(retoid, lretoid);
+					ldap_memfree(lretoid);
+				}
+			case 4:
+				/* use arg #4 as the data returned by the server */
+				zval_dtor(retdata);
+				if (lretdata == NULL) {
+					ZVAL_EMPTY_STRING(retdata);
+				} else {
+					ZVAL_STRINGL(retdata, lretdata->bv_val, lretdata->bv_len);
+					ldap_memfree(lretdata->bv_val);
+					ldap_memfree(lretdata);
+				}
+		}
+
+		RETURN_TRUE;
+	}
+
+	/* asynchronous call */
+	rc = ldap_extended_operation(ld->link, lreqoid,
+		lreqdata.bv_len > 0 ? &lreqdata: NULL,
+		NULL, NULL, &msgid);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Extended operation %s failed: %s (%d)", lreqoid, ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	rc = ldap_result(ld->link, msgid, 1 /* LDAP_MSG_ALL */, NULL, &ldap_res);
+	if (rc == -1) {
+		php_error_docref(NULL, E_WARNING, "Extended operation %s failed", lreqoid);
+		RETURN_FALSE;
+	}
+
+	/* return a PHP control object */
+	RETVAL_RES(zend_register_resource(ldap_res, le_result));
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_PASSWD_S
+/* {{{ proto bool|string ldap_exop_passwd(resource link [, string user [, string oldpw [, string newpw ]]])
+   Passwd modify extended operation */
+PHP_FUNCTION(ldap_exop_passwd)
+{
+	zval *link, *user, *newpw, *oldpw;
+	struct berval luser, loldpw, lnewpw, lgenpasswd;
+	ldap_linkdata *ld;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|zzz", &link, &user, &oldpw, &newpw) == FAILURE) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	luser.bv_len = 0;
+	loldpw.bv_len = 0;
+	lnewpw.bv_len = 0;
+
+	switch (myargcount) {
+		case 4:
+			convert_to_string_ex(newpw);
+			lnewpw.bv_val = Z_STRVAL_P(newpw);
+			lnewpw.bv_len = Z_STRLEN_P(newpw);
+
+		case 3:
+			convert_to_string_ex(oldpw);
+			loldpw.bv_val = Z_STRVAL_P(oldpw);
+			loldpw.bv_len = Z_STRLEN_P(oldpw);
+
+		case 2:
+			convert_to_string_ex(user);
+			luser.bv_val = Z_STRVAL_P(user);
+			luser.bv_len = Z_STRLEN_P(user);
+	}
+
+	/* synchronous call */
+	rc = ldap_passwd_s(ld->link, &luser,
+		loldpw.bv_len > 0 ? &loldpw : NULL,
+		lnewpw.bv_len > 0 ? &lnewpw : NULL,
+		&lgenpasswd, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Passwd modify extended operation failed: %s (%d)", ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	if (lnewpw.bv_len == 0) {
+		if (lgenpasswd.bv_len == 0) {
+			RETVAL_EMPTY_STRING();
+		} else {
+			RETVAL_STRINGL(lgenpasswd.bv_val, lgenpasswd.bv_len);
+		}
+	} else {
+		RETURN_TRUE;
+	}
+
+	ldap_memfree(lgenpasswd.bv_val);
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_WHOAMI_S
+/* {{{ proto bool|string ldap_exop_whoami(resource link)
+   Whoami extended operation */
+PHP_FUNCTION(ldap_exop_whoami)
+{
+	zval *link;
+	struct berval *lauthzid;
+	ldap_linkdata *ld;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &link) == FAILURE) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	/* synchronous call */
+	rc = ldap_whoami_s(ld->link, &lauthzid, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Whoami extended operation failed: %s (%d)", ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	if (lauthzid == NULL) {
+		RETVAL_EMPTY_STRING();
+	} else {
+		RETVAL_STRINGL(lauthzid->bv_val, lauthzid->bv_len);
+		ldap_memfree(lauthzid->bv_val);
+		ldap_memfree(lauthzid);
+	}
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_REFRESH_S
+/* {{{ proto int ldap_exop_refresh(resource link , string dn , int ttl)
+   DDS refresh extended operation */
+PHP_FUNCTION(ldap_exop_refresh) 
+{
+	zval *link, *dn, *ttl;
+	struct berval ldn;
+	ber_int_t lttl;
+	ber_int_t newttl;
+	ldap_linkdata *ld;
+	int rc;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rzz", &link, &dn, &ttl) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	ldn.bv_len = 0;
+	convert_to_string_ex(dn);
+	ldn.bv_val = Z_STRVAL_P(dn);
+	ldn.bv_len = Z_STRLEN_P(dn);
+
+	convert_to_long_ex(ttl);
+	lttl = (ber_int_t)Z_LVAL_P(ttl);
+
+	rc = ldap_refresh_s(ld->link, &ldn, lttl, &newttl, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, 
+				 "Refresh extended operation failed: %s (%d)", 
+				 ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	RETURN_LONG(newttl);
+}
+/* }}} */
+#endif
+
+/* }}} */
+
 /* {{{ arginfo */
 ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_connect, 0, 0, 0)
 	ZEND_ARG_INFO(0, hostname)
 	ZEND_ARG_INFO(0, port)
@@ -3310,8 +3716,50 @@
 ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_8859_to_t61, 0, 0, 1)
 	ZEND_ARG_INFO(0, value)
 ZEND_END_ARG_INFO()
 #endif
+
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop, 0, 0, 5)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, reqoid)
+	ZEND_ARG_INFO(0, reqdata)
+	ZEND_ARG_INFO(1, retdata)
+	ZEND_ARG_INFO(1, retoid)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_PASSWD_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_passwd, 0, 0, 4)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, user)
+	ZEND_ARG_INFO(0, oldpw)
+	ZEND_ARG_INFO(0, newpw)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_WHOAMI_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_whoami, 0, 0, 1)
+	ZEND_ARG_INFO(0, link)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_REFRESH_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_refresh, 0, 0, 3)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, dn)
+	ZEND_ARG_INFO(0, ttl)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_parse_exop, 0, 0, 4)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, result)
+	ZEND_ARG_INFO(1, retdata)
+	ZEND_ARG_INFO(1, retoid)
+ZEND_END_ARG_INFO()
+#endif
 /* }}} */
 
 /*
 	This is just a small subset of the functionality provided by the LDAP library. All the
@@ -3374,8 +3822,23 @@
 #endif
 #ifdef HAVE_LDAP_START_TLS_S
 	PHP_FE(ldap_start_tls,								arginfo_ldap_resource)
 #endif
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+	PHP_FE(ldap_exop,									arginfo_ldap_exop)
+#endif
+#ifdef HAVE_LDAP_PASSWD_S
+	PHP_FE(ldap_exop_passwd,							arginfo_ldap_exop_passwd)
+#endif
+#ifdef HAVE_LDAP_WHOAMI_S
+	PHP_FE(ldap_exop_whoami,							arginfo_ldap_exop_whoami)
+#endif
+#ifdef HAVE_LDAP_REFRESH_S
+	PHP_FE(ldap_exop_refresh,							arginfo_ldap_exop_refresh)
+#endif
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+	PHP_FE(ldap_parse_exop,								arginfo_ldap_parse_exop)
+#endif
 #endif
 
 #if defined(LDAP_API_FEATURE_X_OPENLDAP) && defined(HAVE_3ARG_SETREBINDPROC)
 	PHP_FE(ldap_set_rebind_proc,						arginfo_ldap_set_rebind_proc)

File Added: pkgsrc/databases/php-ldap/files/ldap-ctrl-exop71.patch
From upstream: 
git diff 035a27cbc63d87a6acc761ce51109bcf47f9c27b \
    ed8bfcc6ea60c9d5b8c6b10f9e5175237fe28751 -- ext/ldap/ldap.c \
    ext/ldap/config.m4 ext/ldap/config.w32

Addditionnal uncommitted yet modification for ldap_exop_refresh
(NB: previous patch had ldap_refresh with different prototype)

--- ext/ldap/config.m4.orig
+++ ext/ldap/config.m4
@@ -203,9 +203,9 @@
   fi
 
   dnl Solaris 2.8 claims to be 2004 API, but doesn't have
   dnl ldap_parse_reference() nor ldap_start_tls_s()
-  AC_CHECK_FUNCS([ldap_parse_result ldap_parse_reference ldap_start_tls_s ldap_control_find])
+  AC_CHECK_FUNCS([ldap_parse_result ldap_parse_reference ldap_start_tls_s ldap_control_find ldap_parse_extended_result ldap_extended_operation ldap_extended_operation_s ldap_passwd_s ldap_whoami_s ldap_refresh_s])
 
   dnl
   dnl SASL check
   dnl
--- ext/ldap/config.w32.orig
+++ ext/ldap/config.w32
@@ -20,8 +20,14 @@
 		AC_DEFINE('HAVE_LDAP_SASL', 1);
 		AC_DEFINE('HAVE_LDAP_SASL_SASL_H', 1);
 		AC_DEFINE('LDAP_DEPRECATED', 1);
 		AC_DEFINE('HAVE_LDAP_CONTROL_FIND', 1);
+		AC_DEFINE('HAVE_LDAP_PARSE_EXTENDED_RESULT', 1);
+		AC_DEFINE('HAVE_LDAP_EXTENDED_OPERATION_S', 1);
+		AC_DEFINE('HAVE_LDAP_PASSWD_S', 1);
+		AC_DEFINE('HAVE_LDAP_WHOAMI_S', 1);
+		AC_DEFINE('HAVE_LDAP_REFRESH_S', 1);
+		AC_DEFINE('HAVE_LDAP_EXTENDED_OPERATION', 1);
 
 	} else {
 		WARNING("ldap not enabled; libraries and headers not found");
 	}
--- ext/ldap/ldap.c.orig
+++ ext/ldap/ldap.c
@@ -288,8 +288,86 @@
 
 	REGISTER_LONG_CONSTANT("LDAP_ESCAPE_FILTER", PHP_LDAP_ESCAPE_FILTER, CONST_PERSISTENT | CONST_CS);
 	REGISTER_LONG_CONSTANT("LDAP_ESCAPE_DN", PHP_LDAP_ESCAPE_DN, CONST_PERSISTENT | CONST_CS);
 
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_START_TLS", LDAP_EXOP_START_TLS, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_MODIFY_PASSWD", LDAP_EXOP_MODIFY_PASSWD, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_REFRESH", LDAP_EXOP_REFRESH, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_WHO_AM_I", LDAP_EXOP_WHO_AM_I, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_EXOP_TURN", LDAP_EXOP_TURN, CONST_PERSISTENT | CONST_CS);
+#endif
+
+/* LDAP Controls */
+/*	standard track controls */
+#ifdef LDAP_CONTROL_MANAGEDSAIT
+	/* RFC 3296 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_MANAGEDSAIT", LDAP_CONTROL_MANAGEDSAIT, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_PROXY_AUTHZ
+	/* RFC 4370 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PROXY_AUTHZ", LDAP_CONTROL_PROXY_AUTHZ, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SUBENTRIES
+	/* RFC 3672 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SUBENTRIES", LDAP_CONTROL_SUBENTRIES, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_VALUESRETURNFILTER
+	/* RFC 3876 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VALUESRETURNFILTER", LDAP_CONTROL_VALUESRETURNFILTER, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_ASSERT
+	/* RFC 4528 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_ASSERT", LDAP_CONTROL_ASSERT, CONST_PERSISTENT | CONST_CS);
+	/* RFC 4527 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PRE_READ", LDAP_CONTROL_PRE_READ, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_POST_READ", LDAP_CONTROL_POST_READ, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SORTREQUEST
+	/* RFC 2891 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SORTREQUEST", LDAP_CONTROL_SORTREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SORTRESPONSE", LDAP_CONTROL_SORTRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+/*	non-standard track controls */
+#ifdef LDAP_CONTROL_PAGEDRESULTS
+	/* RFC 2696 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PAGEDRESULTS", LDAP_CONTROL_PAGEDRESULTS, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_AUTHZID_REQUEST
+	/* RFC 3829 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_AUTHZID_REQUEST", LDAP_CONTROL_AUTHZID_REQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_AUTHZID_RESPONSE", LDAP_CONTROL_AUTHZID_RESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_SYNC
+	/* LDAP Content Synchronization Operation -- RFC 4533 */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC", LDAP_CONTROL_SYNC, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC_STATE", LDAP_CONTROL_SYNC_STATE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_SYNC_DONE", LDAP_CONTROL_SYNC_DONE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_DONTUSECOPY
+	/* LDAP Don't Use Copy Control (RFC 6171) */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_DONTUSECOPY", LDAP_CONTROL_DONTUSECOPY, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
+	/* Password policy Controls */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PASSWORDPOLICYREQUEST", LDAP_CONTROL_PASSWORDPOLICYREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_PASSWORDPOLICYRESPONSE", LDAP_CONTROL_PASSWORDPOLICYRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_X_INCREMENTAL_VALUES
+	/* MS Active Directory controls */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_INCREMENTAL_VALUES", LDAP_CONTROL_X_INCREMENTAL_VALUES, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_DOMAIN_SCOPE", LDAP_CONTROL_X_DOMAIN_SCOPE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_PERMISSIVE_MODIFY", LDAP_CONTROL_X_PERMISSIVE_MODIFY, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_SEARCH_OPTIONS", LDAP_CONTROL_X_SEARCH_OPTIONS, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_TREE_DELETE", LDAP_CONTROL_X_TREE_DELETE, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_X_EXTENDED_DN", LDAP_CONTROL_X_EXTENDED_DN, CONST_PERSISTENT | CONST_CS);
+#endif
+#ifdef LDAP_CONTROL_X_INCREMENTAL_VALUES
+	/* LDAP VLV */
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VLVREQUEST", LDAP_CONTROL_VLVREQUEST, CONST_PERSISTENT | CONST_CS);
+	REGISTER_STRING_CONSTANT("LDAP_CONTROL_VLVRESPONSE", LDAP_CONTROL_VLVRESPONSE, CONST_PERSISTENT | CONST_CS);
+#endif
+
 	le_link = zend_register_list_destructors_ex(_close_ldap_link, NULL, "ldap link", module_number);
 	le_result = zend_register_list_destructors_ex(_free_ldap_result, NULL, "ldap result", module_number);
 	le_result_entry = zend_register_list_destructors_ex(_free_ldap_result_entry, NULL, "ldap result entry", module_number);
 
@@ -2270,11 +2348,41 @@
 			zval_ptr_dtor(retval);
 			ZVAL_STRING(retval, val);
 			ldap_memfree(val);
 		} break;
-/* options not implemented
 	case LDAP_OPT_SERVER_CONTROLS:
 	case LDAP_OPT_CLIENT_CONTROLS:
+		{
+			zval tmp1;
+			int num_entries;
+			LDAPControl **ctrls = NULL, **ctrlp;
+
+			if (ldap_get_option(ld->link, option, &ctrls) || ctrls == NULL) {
+				if (ctrls) {
+					ldap_memfree(ctrls);
+				}
+				RETURN_FALSE;
+			}
+
+			zval_ptr_dtor(retval);
+			array_init(retval);
+			num_entries = 0;
+			ctrlp = ctrls;
+			while (*ctrlp != NULL)
+			{
+				array_init(&tmp1);
+				add_assoc_string(&tmp1, "oid", (*ctrlp)->ldctl_oid);
+				add_assoc_bool(&tmp1, "iscritical", ((*ctrlp)->ldctl_iscritical != 0));
+				if ((*ctrlp)->ldctl_value.bv_len) {
+					add_assoc_stringl(&tmp1, "value", (*ctrlp)->ldctl_value.bv_val, (*ctrlp)->ldctl_value.bv_len);
+				}
+				zend_hash_index_update(Z_ARRVAL_P(retval), num_entries, &tmp1);
+				num_entries++;
+				ctrlp++;
+			}
+			ldap_controls_free(ctrls);
+		} break;
+/* options not implemented
 	case LDAP_OPT_API_INFO:
 	case LDAP_OPT_API_FEATURE_INFO:
 */
 	default:
@@ -2569,8 +2677,69 @@
 }
 /* }}} */
 #endif
 
+/* {{{ Extended operation response parsing, Pierangelo Masarati */
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+/* {{{ proto bool ldap_parse_exop(resource link, resource result [, string retdata [, string retoid]])
+   Extract information from extended operation result */
+PHP_FUNCTION(ldap_parse_exop)
+{
+	zval *link, *result, *retdata, *retoid;
+	ldap_linkdata *ld;
+	LDAPMessage *ldap_result;
+	char *lretoid;
+	struct berval *lretdata;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr|z/z/", &link, &result, &retdata, &retoid) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	if ((ldap_result = (LDAPMessage *)zend_fetch_resource(Z_RES_P(result), "ldap result", le_result)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	rc = ldap_parse_extended_result(ld->link, ldap_result,
+				myargcount > 3 ? &lretoid: NULL,
+				myargcount > 2 ? &lretdata: NULL,
+				0);
+	if (rc != LDAP_SUCCESS) {
+		php_error_docref(NULL, E_WARNING, "Unable to parse extended operation result: %s", ldap_err2string(rc));
+		RETURN_FALSE;
+	}
+
+	/* Reverse -> fall through */
+	switch (myargcount) {
+		case 4:
+			zval_dtor(retoid);
+			if (lretoid == NULL) {
+				ZVAL_EMPTY_STRING(retoid);
+			} else {
+				ZVAL_STRING(retoid, lretoid);
+				ldap_memfree(lretoid);
+			}
+		case 3:
+			/* use arg #3 as the data returned by the server */
+			zval_dtor(retdata);
+			if (lretdata == NULL) {
+				ZVAL_EMPTY_STRING(retdata);
+			} else {
+				ZVAL_STRINGL(retdata, lretdata->bv_val, lretdata->bv_len);
+				ldap_memfree(lretdata->bv_val);
+				ldap_memfree(lretdata);
+			}
+	}
+	RETURN_TRUE;
+}
+/* }}} */
+#endif
+/* }}} */
+
 /* {{{ proto resource ldap_first_reference(resource link, resource result)
    Return first reference */
 PHP_FUNCTION(ldap_first_reference)
 {
@@ -3156,8 +3325,245 @@
 }
 /* }}} */
 #endif
 
+/* {{{ Extended operations, Pierangelo Masarati */
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+/* {{{ proto ? ldap_exop(resource link, string reqoid [, string reqdata [, string retdata [, string retoid]]])
+   Extended operation */
+PHP_FUNCTION(ldap_exop)
+{
+	zval *link, *reqoid, *reqdata, *retdata, *retoid;
+	char *lreqoid, *lretoid = NULL;
+	struct berval lreqdata, *lretdata = NULL;
+	ldap_linkdata *ld;
+	LDAPMessage *ldap_res;
+	int rc, msgid, myargcount = ZEND_NUM_ARGS();
+	/* int reqoid_len, reqdata_len, retdata_len, retoid_len, retdat_len; */
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz|zz/z/", &link, &reqoid, &reqdata, &retdata, &retoid) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	switch (myargcount) {
+	case 5:
+	case 4:
+	case 3:
+		convert_to_string_ex(reqdata);
+		lreqdata.bv_val = Z_STRVAL_P(reqdata);
+		lreqdata.bv_len = Z_STRLEN_P(reqdata);
+		/* fallthru */
+	case 2:
+		convert_to_string_ex(reqoid);
+		lreqoid = Z_STRVAL_P(reqoid);
+	}
+
+	if (myargcount > 3) {
+		/* synchronous call */
+		rc = ldap_extended_operation_s(ld->link, lreqoid,
+			lreqdata.bv_len > 0 ? &lreqdata: NULL,
+			NULL,
+			NULL,
+			myargcount > 4 ? &lretoid : NULL,
+			&lretdata );
+		if (rc != LDAP_SUCCESS ) {
+			php_error_docref(NULL, E_WARNING, "Extended operation %s failed: %s (%d)", lreqoid, ldap_err2string(rc), rc);
+			RETURN_FALSE;
+		}
+
+		/* Reverse -> fall through */
+		switch (myargcount) {
+			case 5:
+				zval_dtor(retoid);
+				if (lretoid == NULL) {
+					ZVAL_EMPTY_STRING(retoid);
+				} else {
+					ZVAL_STRING(retoid, lretoid);
+					ldap_memfree(lretoid);
+				}
+			case 4:
+				/* use arg #4 as the data returned by the server */
+				zval_dtor(retdata);
+				if (lretdata == NULL) {
+					ZVAL_EMPTY_STRING(retdata);
+				} else {
+					ZVAL_STRINGL(retdata, lretdata->bv_val, lretdata->bv_len);
+					ldap_memfree(lretdata->bv_val);
+					ldap_memfree(lretdata);
+				}
+		}
+
+		RETURN_TRUE;
+	}
+
+	/* asynchronous call */
+	rc = ldap_extended_operation(ld->link, lreqoid,
+		lreqdata.bv_len > 0 ? &lreqdata: NULL,
+		NULL, NULL, &msgid);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Extended operation %s failed: %s (%d)", lreqoid, ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	rc = ldap_result(ld->link, msgid, 1 /* LDAP_MSG_ALL */, NULL, &ldap_res);
+	if (rc == -1) {
+		php_error_docref(NULL, E_WARNING, "Extended operation %s failed", lreqoid);
+		RETURN_FALSE;
+	}
+
+	/* return a PHP control object */
+	RETVAL_RES(zend_register_resource(ldap_res, le_result));
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_PASSWD_S
+/* {{{ proto bool|string ldap_exop_passwd(resource link [, string user [, string oldpw [, string newpw ]]])
+   Passwd modify extended operation */
+PHP_FUNCTION(ldap_exop_passwd)
+{
+	zval *link, *user, *newpw, *oldpw;
+	struct berval luser, loldpw, lnewpw, lgenpasswd;
+	ldap_linkdata *ld;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|zzz", &link, &user, &oldpw, &newpw) == FAILURE) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	luser.bv_len = 0;
+	loldpw.bv_len = 0;
+	lnewpw.bv_len = 0;
+
+	switch (myargcount) {
+		case 4:
+			convert_to_string_ex(newpw);
+			lnewpw.bv_val = Z_STRVAL_P(newpw);
+			lnewpw.bv_len = Z_STRLEN_P(newpw);
+
+		case 3:
+			convert_to_string_ex(oldpw);
+			loldpw.bv_val = Z_STRVAL_P(oldpw);
+			loldpw.bv_len = Z_STRLEN_P(oldpw);
+
+		case 2:
+			convert_to_string_ex(user);
+			luser.bv_val = Z_STRVAL_P(user);
+			luser.bv_len = Z_STRLEN_P(user);
+	}
+
+	/* synchronous call */
+	rc = ldap_passwd_s(ld->link, &luser,
+		loldpw.bv_len > 0 ? &loldpw : NULL,
+		lnewpw.bv_len > 0 ? &lnewpw : NULL,
+		&lgenpasswd, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Passwd modify extended operation failed: %s (%d)", ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	if (lnewpw.bv_len == 0) {
+		if (lgenpasswd.bv_len == 0) {
+			RETVAL_EMPTY_STRING();
+		} else {
+			RETVAL_STRINGL(lgenpasswd.bv_val, lgenpasswd.bv_len);
+		}
+	} else {
+		RETURN_TRUE;
+	}
+
+	ldap_memfree(lgenpasswd.bv_val);
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_WHOAMI_S
+/* {{{ proto bool|string ldap_exop_whoami(resource link)
+   Whoami extended operation */
+PHP_FUNCTION(ldap_exop_whoami)
+{
+	zval *link;
+	struct berval *lauthzid;
+	ldap_linkdata *ld;
+	int rc, myargcount = ZEND_NUM_ARGS();
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &link) == FAILURE) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	/* synchronous call */
+	rc = ldap_whoami_s(ld->link, &lauthzid, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, "Whoami extended operation failed: %s (%d)", ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	if (lauthzid == NULL) {
+		RETVAL_EMPTY_STRING();
+	} else {
+		RETVAL_STRINGL(lauthzid->bv_val, lauthzid->bv_len);
+		ldap_memfree(lauthzid->bv_val);
+		ldap_memfree(lauthzid);
+	}
+}
+/* }}} */
+#endif
+
+#ifdef HAVE_LDAP_REFRESH_S
+/* {{{ proto int ldap_exop_refresh(resource link , string dn , int ttl)
+   DDS refresh extended operation */
+PHP_FUNCTION(ldap_exop_refresh) 
+{
+	zval *link, *dn, *ttl;
+	struct berval ldn;
+	ber_int_t lttl;
+	ber_int_t newttl;
+	ldap_linkdata *ld;
+	int rc;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rzz", &link, &dn, &ttl) != SUCCESS) {
+		WRONG_PARAM_COUNT;
+	}
+
+	if ((ld = (ldap_linkdata *)zend_fetch_resource(Z_RES_P(link), "ldap link", le_link)) == NULL) {
+		RETURN_FALSE;
+	}
+
+	ldn.bv_len = 0;
+	convert_to_string_ex(dn);
+	ldn.bv_val = Z_STRVAL_P(dn);
+	ldn.bv_len = Z_STRLEN_P(dn);
+
+	convert_to_long_ex(ttl);
+	lttl = (ber_int_t)Z_LVAL_P(ttl);
+
+	rc = ldap_refresh_s(ld->link, &ldn, lttl, &newttl, NULL, NULL);
+	if (rc != LDAP_SUCCESS ) {
+		php_error_docref(NULL, E_WARNING, 
+				 "Refresh extended operation failed: %s (%d)", 
+				 ldap_err2string(rc), rc);
+		RETURN_FALSE;
+	}
+
+	RETURN_LONG(newttl);
+}
+/* }}} */
+#endif
+
+/* }}} */
+
 /* {{{ arginfo */
 ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_connect, 0, 0, 0)
 	ZEND_ARG_INFO(0, hostname)
 	ZEND_ARG_INFO(0, port)
@@ -3431,8 +3837,50 @@
 ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_8859_to_t61, 0, 0, 1)
 	ZEND_ARG_INFO(0, value)
 ZEND_END_ARG_INFO()
 #endif
+
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop, 0, 0, 5)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, reqoid)
+	ZEND_ARG_INFO(0, reqdata)
+	ZEND_ARG_INFO(1, retdata)
+	ZEND_ARG_INFO(1, retoid)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_PASSWD_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_passwd, 0, 0, 4)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, user)
+	ZEND_ARG_INFO(0, oldpw)
+	ZEND_ARG_INFO(0, newpw)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_WHOAMI_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_whoami, 0, 0, 1)
+	ZEND_ARG_INFO(0, link)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_REFRESH_S
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_exop_refresh, 0, 0, 3)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, dn)
+	ZEND_ARG_INFO(0, ttl)
+ZEND_END_ARG_INFO()
+#endif
+
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+ZEND_BEGIN_ARG_INFO_EX(arginfo_ldap_parse_exop, 0, 0, 4)
+	ZEND_ARG_INFO(0, link)
+	ZEND_ARG_INFO(0, result)
+	ZEND_ARG_INFO(1, retdata)
+	ZEND_ARG_INFO(1, retoid)
+ZEND_END_ARG_INFO()
+#endif
 /* }}} */
 
 /*
 	This is just a small subset of the functionality provided by the LDAP library. All the
@@ -3495,8 +3943,23 @@
 #endif
 #ifdef HAVE_LDAP_START_TLS_S
 	PHP_FE(ldap_start_tls,								arginfo_ldap_resource)
 #endif
+#ifdef HAVE_LDAP_EXTENDED_OPERATION_S
+	PHP_FE(ldap_exop,									arginfo_ldap_exop)
+#endif
+#ifdef HAVE_LDAP_PASSWD_S
+	PHP_FE(ldap_exop_passwd,							arginfo_ldap_exop_passwd)
+#endif
+#ifdef HAVE_LDAP_WHOAMI_S
+	PHP_FE(ldap_exop_whoami,							arginfo_ldap_exop_whoami)
+#endif
+#ifdef HAVE_LDAP_REFRESH_S
+	PHP_FE(ldap_exop_refresh,							arginfo_ldap_exop_refresh)
+#endif
+#ifdef HAVE_LDAP_PARSE_EXTENDED_RESULT
+	PHP_FE(ldap_parse_exop,								arginfo_ldap_parse_exop)
+#endif
 #endif
 
 #if defined(LDAP_API_FEATURE_X_OPENLDAP) && defined(HAVE_3ARG_SETREBINDPROC)
 	PHP_FE(ldap_set_rebind_proc,						arginfo_ldap_set_rebind_proc)