You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@apr.apache.org by mi...@apache.org on 2025/03/11 13:27:43 UTC

svn commit: r1924295 [3/3] - in /apr/apr/trunk: ./ build/ include/ include/private/ ldap/ misc/unix/ test/

Modified: apr/apr/trunk/test/testldap.c
URL: http://svn.apache.org/viewvc/apr/apr/trunk/test/testldap.c?rev=1924295&r1=1924294&r2=1924295&view=diff
==============================================================================
--- apr/apr/trunk/test/testldap.c (original)
+++ apr/apr/trunk/test/testldap.c Tue Mar 11 13:27:42 2025
@@ -210,70 +210,470 @@ typedef struct test_ldap_connection_t {
     apr_pollfd_t socket_write;
     apu_err_t err;
     int must_bind;
+    const char *context;
+    const char *dn;
+    const char *rdn;
+    const char *newdn;
 } test_ldap_connection_t;
 
+static apr_status_t test_ldap_vlv_result_cb(apr_ldap_t *ldap,
+                                            apr_status_t status,
+                                            apr_size_t nentries,
+                                            const char *matcheddn,
+                                            apr_hash_t *serverctrls,
+                                            void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+
+    apr_ldap_control_t *sort = apr_hash_get(serverctrls, APR_LDAP_CONTROL_SORT_RESPONSE_OID, APR_HASH_KEY_STRING);
+    apr_ldap_control_t *vlv = apr_hash_get(serverctrls, APR_LDAP_CONTROL_VLV_RESPONSE_OID, APR_HASH_KEY_STRING);
+
+    /*
+     * Step 30: vlv search result callback triggered, result is complete.
+     */
+
+    abts_log_message("vlv search matcheddn: \n", matcheddn);
+
+    if (sort) {
+        abts_log_message("apr_ldap_search() sort control response: %s\n", apr_strerror(sort->c.sortrs.result, errbuf, sizeof(errbuf)));
+    }
+
+    if (vlv) {
+        abts_log_message("apr_ldap_search() vlv control response: %s\n", apr_strerror(sort->c.vlvrs.result, errbuf, sizeof(errbuf)));
+    }
+
+    if (APR_SUCCESS == status) {
+        /* success */
+    }
+    if (APR_STATUS_IS_OPERATIONS_ERROR(status)) {
+        abts_log_message("apr_ldap_search() failed, most likely because the LDAP server does not have a VLV index configured: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+    }
+    else {
+        abts_log_message("apr_ldap_search() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+    }
+
+    return status;
+}
+
+static apr_status_t test_ldap_vlv_entry_cb(apr_ldap_t *ldap,
+                                           const char *dn,
+                                           apr_size_t eidx,
+                                           apr_ldap_search_entry_t *entry,
+                                           void *ctx, apu_err_t *err)
+{
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    /*
+     * Step 29: search vlv entry callback triggered, start processing results.
+     */
+
+    if (entry && !entry->aidx && !entry->vidx) {
+        /* first attribute and first value and attr present? output dn */
+        abts_log_message("dn: %s", dn);
+    }
+
+    if (entry) {
+        abts_log_message("%s: %s", entry->attr, apr_buffer_pstrdup(test->pool, &entry->val));
+    }
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t test_ldap_delete_cb(apr_ldap_t *ldap,
+                                        apr_status_t status,
+                                        const char *matcheddn,
+                                        apr_ldap_control_t **serverctrls,
+                                        void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    /*
+     * Step 27: delete result callback triggered, next is vlv search.
+     */
+
+    abts_log_message("delete matcheddn: \n", matcheddn);
+
+    if (APR_SUCCESS == status) {
+
+        /*
+         * Step 28: we're writable, trigger a vlv search, then wait for readable.
+         */
+        apr_array_header_t *scontrols = apr_array_make(test->pool, 2, sizeof(apr_ldap_control_t));
+        apr_ldap_control_t *sort = apr_array_push(scontrols);
+        apr_ldap_control_t *vlv = apr_array_push(scontrols);
+        apr_ldap_control_sortkey_t *sortkey;
+
+        /* make a sort control */
+        sort->type = APR_LDAP_CONTROL_SORT_REQUEST;
+        sort->critical = 1;
+        sort->c.sortrq.keys = apr_array_make(test->pool, 1, sizeof(apr_ldap_control_sortkey_t));
+
+        /* sort by ou */
+        sortkey = apr_array_push(sort->c.sortrq.keys);
+        sortkey->attribute = "ou";
+        sortkey->order = "caseIgnoreOrderingMatch";
+        sortkey->direction = APR_LDAP_CONTROL_SORT_FORWARD;
+
+        /* make a vlv control */
+        vlv->type = APR_LDAP_CONTROL_VLV_REQUEST;
+        vlv->c.vlvrq.before = 1; /* we want the one before the second entry */
+        vlv->c.vlvrq.after = 1; /* we want one after the second entry */
+        vlv->c.vlvrq.offset = 2; /* we want the second entry */
+
+        status = apr_ldap_search(test->pool, test->ldap, test->context /* base dn */,
+                                 APR_LDAP_SCOPE_ONELEVEL, "(objectclass=*)" /* filter */,
+                                 NULL /* attrs */, APR_LDAP_OPT_ON /* attrsonly */,
+                                 scontrols /* serverctls */, NULL /* clientctls */,
+                                 apr_time_from_sec(5) /* timelimit */, 0 /* sizelimit */,
+                                 test_ldap_vlv_result_cb, test_ldap_vlv_entry_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_result: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else {
+        abts_log_message("apr_ldap_delete() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+        ABTS_INT_EQUAL(test->tc, APR_SUCCESS, status);
+    }
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t test_ldap_rename_cb(apr_ldap_t *ldap,
+                                        apr_status_t status,
+                                        const char *matcheddn,
+                                        apr_ldap_control_t **serverctrls,
+                                        void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    /*
+     * Step 24: rename result callback triggered.
+     */
+
+    abts_log_message("rename matcheddn: \n", matcheddn);
+
+    if (APR_SUCCESS == status || APR_ALREADY_EXISTS == status) {
+
+        /*
+         * Step 25: success or object already exists, it is time to trigger the delete.
+         */
+
+        status = apr_ldap_delete(test->pool, test->ldap, test->newdn /* dn to delete */,
+                                 NULL /* serverctls */, NULL /* clientctls */,
+                                 apr_time_from_sec(5) /* timeout */,
+                                 test_ldap_delete_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_delete: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else {
+        abts_log_message("apr_ldap_rename() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+        ABTS_INT_EQUAL(test->tc, APR_SUCCESS, status);
+    }
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t test_ldap_modify_cb(apr_ldap_t *ldap,
+                                        apr_status_t status,
+                                        const char *matcheddn,
+                                        apr_ldap_control_t **serverctrls,
+                                        void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    /*
+     * Step 21: modify result callback triggered, result is complete.
+     */
+
+    abts_log_message("modify matcheddn: \n", matcheddn);
+
+    if (APR_SUCCESS == status) {
+
+        /*
+         * Step 22: modify succeeded, it is time to trigger the rename.
+         */
+
+        test->rdn = "ou=Apache Portable Runtime Test Organizational Unit 2";
+        test->newdn = apr_pstrcat(test->pool, test->rdn , ",", test->context, NULL);
+
+        status = apr_ldap_rename(test->pool, test->ldap, test->dn /* dn to rename */,
+                                 test->rdn /* new rdn */, NULL, APR_LDAP_RENAME_DELETEOLDRDN,
+                                 NULL /* serverctls */, NULL /* clientctls */,
+                                 apr_time_from_sec(5) /* timeout */,
+                                 test_ldap_rename_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_rename: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else {
+        abts_log_message("apr_ldap_modify() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+        ABTS_INT_EQUAL(test->tc, APR_SUCCESS, status);
+    }
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t test_ldap_add_cb(apr_ldap_t *ldap,
+                                     apr_status_t status,
+                                     const char *matcheddn,
+                                     apr_ldap_control_t **serverctrls,
+                                     void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    /*
+     * Step 18: add result callback triggered, next is modify.
+     */
+
+    abts_log_message("add matcheddn: \n", matcheddn);
+
+    if (APR_SUCCESS == status) {
+
+        apr_array_header_t *mods = apr_array_make(test->pool, 10, sizeof(apr_ldap_modify_t));
+        apr_ldap_modify_t *mod;
+        apr_buffer_t *val;
+
+        /* modify our newly added object */
+
+        /*
+         * Step 19: add successful, it is time to trigger the modify.
+         */
+
+        /* create objectclass */
+        mod = apr_array_push(mods);
+        mod->op = APR_LDAP_MOD_ADD;
+        mod->pair.attr = "objectclass";
+        mod->pair.vals = apr_array_make(test->pool, 1, sizeof(apr_buffer_t));
+
+        /* objectclass: extensibleObject */
+        val = apr_array_push(mod->pair.vals);
+        apr_buffer_str_set(val, "extensibleObject", APR_BUFFER_STRING);
+
+
+
+        status = apr_ldap_modify(test->pool, test->ldap, test->dn /* dn to modify */,
+                                 mods /* modifications */,
+                                 NULL /* serverctls */, NULL /* clientctls */,
+                                 apr_time_from_sec(5) /* timeout */,
+                                 test_ldap_modify_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_modify: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else if (APR_ALREADY_EXISTS == status) {
+
+        /* previous run has left objects lying around, clean up */
+
+        /*
+         * Step 25: add said object already exists, it is time to trigger the delete.
+         */
+
+        status = apr_ldap_delete(test->pool, test->ldap, test->dn /* dn to delete */,
+                                 NULL /* serverctls */, NULL /* clientctls */,
+                                 apr_time_from_sec(5) /* timeout */,
+                                 test_ldap_delete_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_delete: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else if (APR_INSUFFICIENT_ACCESS == status) {
+        abts_log_message("apr_ldap_add() was denied, skipping: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+    }
+    else {
+        abts_log_message("apr_ldap_add() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+        ABTS_INT_EQUAL(test->tc, APR_SUCCESS, status);
+    }
+
+    return APR_SUCCESS;
+}
+
 static apr_status_t test_ldap_compare_cb(apr_ldap_t *ldap,
                                          apr_status_t status,
                                          const char *matcheddn,
                                          apr_ldap_control_t **serverctrls,
                                          void *ctx, apu_err_t *err)
 {
+    char errbuf[128];
     test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
 
     /*
-     * Step 13: compare result callback triggered, result is complete.
+     * Step 15: compare result callback triggered, next is add.
      */
 
     abts_log_message("comparison matcheddn: \n", matcheddn);
 
     ABTS_INT_EQUAL(test->tc, APR_COMPARE_TRUE, status);
 
+    abts_log_message("\n");
+
+    if (APR_COMPARE_TRUE != status) {
+        abts_log_message("apr_ldap_compare() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+    }
+    else if (!test->context) {
+        abts_log_message("no namingContext in root DSE, we're done.\n");
+    }
+    else {
+
+        apr_array_header_t *adds = apr_array_make(test->pool, 10, sizeof(apr_ldap_pair_t));
+        apr_ldap_pair_t *pair;
+        apr_buffer_t *val;
+
+        /*
+         * Step 16: compare successful, it is time to trigger the add.
+         */
+
+        test->dn = apr_pstrcat(test->pool, "ou=Apache Portable Runtime Test Organizational Unit,", test->context, NULL);
+
+        /* create objectclass */
+        pair = apr_array_push(adds);
+        pair->attr = "objectclass";
+        pair->vals = apr_array_make(test->pool, 2, sizeof(apr_buffer_t));
+
+        /* objectclass: top */
+        val = apr_array_push(pair->vals);
+        apr_buffer_str_set(val, "top", APR_BUFFER_STRING);
+
+        /* objectclass: organizationalUnit */
+        val = apr_array_push(pair->vals);
+        apr_buffer_str_set(val, "organizationalUnit", APR_BUFFER_STRING);
+
+        /* create cn */
+        pair = apr_array_push(adds);
+        pair->attr = "ou";
+        pair->vals = apr_array_make(test->pool, 1, sizeof(apr_buffer_t));
+
+        /* cn: Apache Portable Runtime Test Organizational Unit */
+        val = apr_array_push(pair->vals);
+        apr_buffer_str_set(val, "Apache Portable Runtime Test Organizational Unit", APR_BUFFER_STRING);
+
+        /* create description */
+        pair = apr_array_push(adds);
+        pair->attr = "description";
+        pair->vals = apr_array_make(test->pool, 1, sizeof(apr_buffer_t));
+
+        /* description: If found, please delete. */
+        val = apr_array_push(pair->vals);
+        apr_buffer_str_set(val, "This object was created by a test case. If found, please delete.", APR_BUFFER_STRING);
+
+        status = apr_ldap_add(test->pool, test->ldap, test->dn /* dn to add */,
+                              adds /* attribute-value pairs */,
+                              NULL /* serverctls */, NULL /* clientctls */,
+                              apr_time_from_sec(5) /* timeout */,
+                              test_ldap_add_cb, test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_add: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+
+
     return APR_SUCCESS;
 }      
 
 static apr_status_t test_ldap_search_entry_cb(apr_ldap_t *ldap,
                                               const char *dn,
-                                              int eidx,
-                                              int nattrs,
-                                              int aidx,
-                                              const char *attr,
-                                              int nvals,
-                                              int vidx,
-                                              apr_buffer_t *val,
-                                              int binary,
+                                              apr_size_t eidx,
+                                              apr_ldap_search_entry_t *entry,
                                               void *ctx, apu_err_t *err)
 {
     test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
 
     /*
-     * Step 8: search entry callback triggered, start processing results.
+     * Step 11: search entry callback triggered, start processing results.
      */
 
-    if (!nattrs && !vidx && attr) {
+    if (entry && !entry->aidx && !entry->vidx) {
         /* first attribute and first value and attr present? output dn */
         abts_log_message("dn: %s", dn);
     }
 
-    if (val) {
-        abts_log_message("%s: %s", attr, apr_buffer_pstrdup(test->pool, val));
+    if (entry) {
+        abts_log_message("%s: %s", entry->attr, apr_buffer_pstrdup(test->pool, &entry->val));
+
+        if (!strcmp(entry->attr, "namingContexts")) {
+            test->context = apr_buffer_pstrdup(test->pool, &entry->val);
+        }
     }
 
     return APR_SUCCESS;
 }
 
-
 static apr_status_t test_ldap_search_result_cb(apr_ldap_t *ldap, 
                                                apr_status_t status,
                                                apr_size_t nentries,
                                                const char *matcheddn,
-                                               apr_ldap_control_t **serverctrls,
+                                               apr_hash_t *serverctrls,
                                                void *ctx, apu_err_t *err)
 {
     char errbuf[128];
     test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
 
     /*
-     * Step 9: search result callback triggered, finish up.
+     * Step 12: search result callback triggered, next is compare.
      */
 
     abts_log_message("\n");
@@ -283,7 +683,7 @@ static apr_status_t test_ldap_search_res
     if (APR_SUCCESS == status) {
 
         /*
-         * Step 10: search successful, it is time to trigger the compare.
+         * Step 13: search successful, it is time to trigger the compare.
          */
 
         apr_buffer_t val;
@@ -319,32 +719,42 @@ static apr_status_t test_ldap_search_res
     return status;
 }
 
-static apr_status_t test_ldap_bind_cb(apr_ldap_t *ldap, apr_status_t status,
-                                         const char *matcheddn,
-                                         apr_ldap_control_t **serverctrls,
-                                         void *ctx, apu_err_t *err)
+static apr_status_t test_ldap_whoami_cb(apr_ldap_t *ldap, apr_status_t status,
+                                        const char *roid,
+                                        apr_buffer_t *rdata,
+                                        void *ctx, apu_err_t *err)
 {
     char errbuf[128];
     test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
 
     if (APR_SUCCESS == status) {
 
+        abts_log_message("whoami: %s\n", apr_buffer_pstrdup(test->pool, rdata));
+
         /*
-         * Step 5: bind successful, it is time to trigger the search.
+         * Step 8: extended operation successful, it is time to trigger the search.
          */
-
         const char *attrs[2];
+
+        apr_array_header_t *scontrols = apr_array_make(test->pool, 1, sizeof(apr_ldap_control_t));
+        apr_ldap_control_t *page = apr_array_push(scontrols);
+
+        /* make a page control */
+        page->type = APR_LDAP_CONTROL_PAGE_REQUEST;
+        page->critical = 0;
+        page->c.pagerq.size = 1; /* one entry per page */
+
         attrs[0] = "+";
         attrs[1] = NULL;
 
         /*
-         * Step 6: we're writable, trigger a search, then wait for readable.
+         * Step 9: we're writable, trigger a search, then wait for readable.
          */
 
         status = apr_ldap_search(test->pool, test->ldap, "" /* root dn */,
                                  APR_LDAP_SCOPE_BASE, "(objectclass=*)" /* filter */,
                                  attrs /* attrs */, APR_LDAP_OPT_OFF /* attrsonly */,
-                                 NULL /* serverctls */, NULL /* clientctls */,
+                                 scontrols /* serverctls */, NULL /* clientctls */,
                                  apr_time_from_sec(5) /* timelimit */, 0 /* sizelimit */,
                                  test_ldap_search_result_cb, test_ldap_search_entry_cb, test, &test->err);
 
@@ -362,6 +772,51 @@ static apr_status_t test_ldap_bind_cb(ap
 
     }
     else {
+        abts_log_message("apr_ldap_extended() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
+    }
+
+    return status;
+}
+
+static apr_status_t test_ldap_bind_cb(apr_ldap_t *ldap, apr_status_t status,
+                                         const char *matcheddn,
+                                         apr_ldap_control_t **serverctrls,
+                                         void *ctx, apu_err_t *err)
+{
+    char errbuf[128];
+    test_ldap_connection_t *test = (test_ldap_connection_t *)ctx;
+
+    if (APR_SUCCESS == status) {
+
+        /*
+         * Step 5: bind successful, it is time to trigger the extended operation.
+         */
+
+        const char *whoami = "1.3.6.1.4.1.4203.1.11.3";
+
+        /*
+         * Step 6: we're writable, trigger an extended operation, then wait for readable.
+         */
+
+        status = apr_ldap_extended(test->pool, test->ldap, whoami, NULL,
+                                   NULL /* serverctls */, NULL /* clientctls */,
+                                   apr_time_from_sec(5), test_ldap_whoami_cb,
+                                   test, &test->err);
+
+        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
+
+        switch (status) {
+        case APR_SUCCESS:
+        case APR_WANT_READ:
+        case APR_WANT_WRITE:
+            break;
+        default:
+            abts_log_message("apr_ldap_result: %s [%s]\n", test->err.reason,
+                              apr_strerror(status, errbuf, sizeof(errbuf)));
+        }
+
+    }
+    else {
         abts_log_message("apr_ldap_bind() failed: %s\n", apr_strerror(status, errbuf, sizeof(errbuf)));
     }
 
@@ -383,8 +838,6 @@ static apr_status_t test_ldap_initialise
         status = apr_ldap_bind(test->pool, test->ldap, test->mech, test->interact, NULL,
                                apr_time_from_sec(0), test_ldap_bind_cb, test, &test->err);
 
-        ABTS_INT_EQUAL(test->tc, APR_WANT_READ, status);
-
         switch (status) {
         case APR_SUCCESS:
         case APR_WANT_READ:
@@ -444,11 +897,31 @@ static apr_status_t test_ldap_connection
          */
 
         /*
-         * Step 7: we're readable, fetch the search result, then trigger search callback.
+         * Step 7: we're readable, fetch the extended operation result, then trigger extended callback.
+         */
+
+        /*
+         * Step 10: we're readable, fetch the search result, then trigger search callback.
+         */
+
+        /*
+         * Step 14: we're readable, fetch the compare result, then trigger compare callback.
+         */
+
+        /*
+         * Step 17: we're readable, fetch the add result, then trigger add callback.
+         */
+
+        /*
+         * Step 20: we're readable, fetch the modify result, then trigger modify callback.
+         */
+
+        /*
+         * Step 23: we're readable, fetch the rename result, then trigger rename callback.
          */
 
         /*
-         * Step 12: we're readable, fetch the compare result, then trigger compare callback.
+         * Step 26: we're readable, fetch the delete result, then trigger delete callback.
          */
 
         status = apr_ldap_result(test->pool, test->ldap, -1, &test->err);
@@ -486,7 +959,7 @@ static void test_ldap_connection(abts_ca
     apu_err_t err;
     test_ldap_connection_t test;
     apr_ldap_opt_t opt;
-    apr_status_t status;
+    apr_status_t status, want;
 
     memset(&test, 0, sizeof(test_ldap_connection_t));
  
@@ -505,6 +978,9 @@ static void test_ldap_connection(abts_ca
      * Step 1: call us back when we're writable.
      */
 
+    want = APR_WANT_WRITE;
+
+
     /* set the initialise callback */
     apr_ldap_prepare(pool, ldap, test_ldap_initialise_cb, &test);
 
@@ -514,6 +990,23 @@ static void test_ldap_connection(abts_ca
 
     status = apr_ldap_connect(pool, ldap, apr_time_from_sec(5), &err);
 
+    if (APR_ENOTIMPL == status) {
+
+        /* macos is a pain - as of this writing their ancient openldap
+         * does not support ldap_connect. To work around this,
+         * synchronously fire off the first callback, which will force
+         * a connect, which will in turn avoid a bad descriptor on the
+         * socket below.
+         */
+
+        status = apr_ldap_process(pool, ldap, apr_time_from_sec(0), &err);
+
+        if (APR_WANT_READ == status) {
+            status = APR_SUCCESS;
+            want = APR_WANT_READ;
+        }
+    }
+
     if (APR_SUCCESS != status) {
         abts_log_message("%s - %s (%d)\n", err.reason, err.msg, err.rc);
         return;
@@ -546,15 +1039,13 @@ static void test_ldap_connection(abts_ca
     test.socket_write.desc.s = opt.socket;
     test.socket_write.client_data = opt.socket;
 
-    status = APR_WANT_WRITE;
-
     do {
 
-        if (APR_SUCCESS == status) {
+        if (APR_SUCCESS == want) {
             abts_log_message("apr_pollcb_poll() complete\n");
             break;
         }
-        else if (APR_WANT_READ == status) {
+        else if (APR_WANT_READ == want) {
 
             /* wait for socket to be readable, then process another result */
             status = apr_pollcb_add(test.poll, &test.socket_read);
@@ -564,7 +1055,7 @@ static void test_ldap_connection(abts_ca
             }
 
         }
-        else if (APR_WANT_WRITE == status) {
+        else if (APR_WANT_WRITE == want) {
 
             /* wait for socket to be writeable, then process result */
             status = apr_pollcb_add(test.poll, &test.socket_write);
@@ -576,17 +1067,19 @@ static void test_ldap_connection(abts_ca
         }
         else {
             abts_log_message("apr_ldap_process/apr_ldap_result: %s [%s]\n", test.err.reason,
-                              apr_strerror(status, errbuf, sizeof(errbuf)));
+                              apr_strerror(want, errbuf, sizeof(errbuf)));
             break;
         }
 
-        status = apr_pollcb_poll(test.poll, -1, test_ldap_connection_cb, &test);
+        want = apr_pollcb_poll(test.poll, -1, test_ldap_connection_cb, &test);
 
     } while (1);
 
 
     /*
-     * Step 14: bind, search, compare, result is complete, event loop unwound.
+     * Step 31: bind, extended, search, compare, add, modify, rename, delete, search.
+     *
+     * result is complete, event loop unwound.
      */
 
     return;
@@ -1106,8 +1599,6 @@ static void test_ldapi(abts_case *tc, vo
 
     apr_pool_create(&pool, p);
 
-    opt.tls = APR_LDAP_TLS_STARTTLS;
-
     url = apr_psprintf(pool, "ldapi://%s", apr_pescape_urlencoded(pool, testldapi));
 
     apr_ldap_initialise(pool, &ldap, &(err));