]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) Add the ldap-search option to mod_authnz_ldap, allowing authorization
authorJim Jagielski <jim@apache.org>
Tue, 7 Jan 2025 15:04:23 +0000 (15:04 +0000)
committerJim Jagielski <jim@apache.org>
Tue, 7 Jan 2025 15:04:23 +0000 (15:04 +0000)
     to be based on arbitrary expressions that do not include the username.
     Make sure that when ldap searches are too long, we explicitly log the
     error.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1922957 13f79535-47bb-0310-9956-ffa450edef68

STATUS
docs/manual/expr.xml
docs/manual/mod/mod_authnz_ldap.xml
modules/aaa/mod_authnz_ldap.c

diff --git a/STATUS b/STATUS
index 8d701cfebe45f160552f5da4a2eff25302de678f..b76df56223870bf37d72e36160ad0d7be2b2128d 100644 (file)
--- a/STATUS
+++ b/STATUS
@@ -157,25 +157,6 @@ RELEASE SHOWSTOPPERS:
 PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
   [ start all new proposals below, under PATCHES PROPOSED. ]
 
-  *) Add the ldap-search option to mod_authnz_ldap, allowing authorization
-     to be based on arbitrary expressions that do not include the username.
-     Make sure that when ldap searches are too long, we explicitly log the
-     error.
-     Trunk version of patch:
-        https://svn.apache.org/r1589993
-        https://svn.apache.org/r1591012
-        https://svn.apache.org/r1596108
-        https://svn.apache.org/r1745033
-        https://svn.apache.org/r1913958
-        https://svn.apache.org/r1913959
-        https://svn.apache.org/r1914091
-        https://svn.apache.org/r1914281
-     Backport version for 2.4.x of patch:
-        https://svn.apache.org/repos/asf/httpd/httpd/patches/2.4.x/httpd-2.4-httpd-2.4-ldap-search5.patch
-     +1: minfrin, covener, jim
-     rpluem says: https://svn.apache.org/repos/asf/httpd/httpd/patches/2.4.x/httpd-2.4-ldap-search5.patch returns 404
-       covener: fixed slightly diff URL above
-
   *) mod_proxy: Honor parameters of ProxyPassMatch workers with substitution
                 in the host name or port. PR 69233.
      trunk patch: https://svn.apache.org/r1912462
index 0c9892abfbc8f4f6ee8fa2fc682ee48651b3902b..6a5ea072e5a39637d8f53c2e601e389a8b10fd7f 100644 (file)
@@ -61,6 +61,7 @@
 <seealso><a href="mod/mod_authnz_ldap.html#reqdn">Require ldap-dn</a></seealso>
 <seealso><a href="mod/mod_authnz_ldap.html#reqattribute">Require ldap-attribute</a></seealso>
 <seealso><a href="mod/mod_authnz_ldap.html#reqfilter">Require ldap-filter</a></seealso>
+<seealso><a href="mod/mod_authnz_ldap.html#reqsearch">Require ldap-search</a></seealso>
 <seealso><a href="mod/mod_authz_dbd.html#reqgroup">Require dbd-group</a></seealso>
 <seealso><a href="mod/mod_authz_dbm.html#reqgroup">Require dbm-group</a></seealso>
 <seealso><a href="mod/mod_authz_groupfile.html#reqgroup">Require group</a></seealso>
index 8c1ae9614ded13f98fe1f3ce238570ed3e85d74d..bed3a883ce3c4a1c7890b2b8e4f2ecf425f03fdf 100644 (file)
@@ -89,6 +89,7 @@ for HTTP Basic authentication.</description>
           <li><a href="#reqdn">Require ldap-dn</a></li>
           <li><a href="#reqattribute">Require ldap-attribute</a></li>
           <li><a href="#reqfilter">Require ldap-filter</a></li>
+          <li><a href="#reqsearch">Require ldap-search</a></li>
         </ul>
       </li>
 
@@ -234,6 +235,11 @@ in <module>mod_ldap</module> for details of the cache tunables.
       directive, and the search filter successfully finds a single user
       object that matches the dn of the authenticated user.</li>
 
+      <li>Grant access if there is a <a href="#reqsearch">
+      <code>Require ldap-search</code></a>
+      directive, and the search filter successfully returns a single
+      matching object with any distinguished name.</li>
+
       <li>otherwise, deny or decline access</li>
     </ul>
 
@@ -531,6 +537,28 @@ Require ldap-filter "&amp;(cell=*)(department=marketing)"
 
 </section>
 
+<section id="reqsearch"><title>Require ldap-search</title>
+
+    <p>The <code>Require ldap-search</code> directive allows the
+    administrator to grant access based on a generic LDAP search filter using an
+    <a href="../expr.html">expression</a>. If there is exactly one match to the search filter,
+    regardless of the distinguished name, access is granted.</p>
+
+    <p>The following directive would grant access to URLs that match the given objects in the
+    LDAP server:</p>
+
+<highlight language="config">
+&lt;LocationMatch ^/dav/(?<SITENAME>[^/]+)/&gt;
+Require ldap-search (cn=%{ldap:%{unescape:%{env:MATCH_SITENAME}} Website)
+&lt;/LocationMatch&gt;
+</highlight>
+
+    <p>Note: care must be taken to ensure that any expressions are properly escaped to guard
+    against LDAP injection. The <strong>ldap</strong> function can be used as per the example
+    above.</p>
+
+</section>
+
 </section>
 
 <section id="examples"><title>Examples</title>
index 95d090a5cec87a3599d8e18e5886fa63a7d72322..d5b8b80c4f7bb01a032be90c827773904f6a94b3 100644 (file)
@@ -84,10 +84,10 @@ typedef struct {
 } authn_ldap_config_t;
 
 typedef struct {
-    char *dn;                       /* The saved dn from a successful search */
-    char *user;                     /* The username provided by the client */
+    const char *dn;                 /* The saved dn from a successful search */
+    const char *user;               /* The username provided by the client */
     const char **vals;              /* The additional values pulled during the DN search*/
-    char *password;                 /* if this module successfully authenticates, the basic auth password, else null */
+    const char *password;           /* if this module successfully authenticates, the basic auth password, else null */
 } authn_ldap_request_t;
 
 enum auth_ldap_phase {
@@ -192,11 +192,8 @@ static const char* authn_ldap_xlate_password(request_rec *r,
 
 /*
  * Build the search filter, or at least as much of the search filter that
- * will fit in the buffer. We don't worry about the buffer not being able
- * to hold the entire filter. If the buffer wasn't big enough to hold the
- * filter, ldap_search_s will complain, but the only situation where this
- * is likely to happen is if the client sent a really, really long
- * username, most likely as part of an attack.
+ * will fit in the buffer, and return APR_EGENERAL if it won't fit, otherwise
+ * APR_SUCCESS.
  *
  * The search filter consists of the filter provided with the URL,
  * combined with a filter made up of the attribute provided with the URL,
@@ -209,31 +206,24 @@ static const char* authn_ldap_xlate_password(request_rec *r,
  * search filter will be (&(posixid=*)(uid=userj)).
  */
 #define FILTER_LENGTH MAX_STRING_LEN
-static void authn_ldap_build_filter(char *filtbuf,
+static apr_status_t authn_ldap_build_filter(char filtbuf[FILTER_LENGTH],
                              request_rec *r,
-                             const char* sent_user,
-                             const char* sent_filter,
+                             const char *user,
+                             const char *filter,
                              authn_ldap_config_t *sec)
 {
-    char *p, *q, *filtbuf_end;
-    char *user, *filter;
+    char *q;
+    const char *p, *filtbuf_end;
     apr_xlate_t *convset = NULL;
     apr_size_t inbytes;
     apr_size_t outbytes;
     char *outbuf;
-    int nofilter = 0;
+    int nofilter = 0, len;
+    apr_status_t rv = APR_SUCCESS;
 
-    if (sent_user != NULL) {
-        user = apr_pstrdup (r->pool, sent_user);
-    }
-    else
-        return;
-
-    if (sent_filter != NULL) {
-        filter = apr_pstrdup (r->pool, sent_filter);
-    }
-    else
+    if (!filter) {
         filter = sec->filter;
+    }
 
     if (charset_conversions) {
         convset = get_conv_set(r);
@@ -246,7 +236,7 @@ static void authn_ldap_build_filter(char *filtbuf,
 
         /* Convert the user name to UTF-8.  This is only valid for LDAP v3 */
         if (apr_xlate_conv_buffer(convset, user, &inbytes, outbuf, &outbytes) == APR_SUCCESS) {
-            user = apr_pstrdup(r->pool, outbuf);
+            user = outbuf;
         }
     }
 
@@ -255,11 +245,11 @@ static void authn_ldap_build_filter(char *filtbuf,
      * config-supplied portions.
      */
 
-    if ((nofilter = (filter && !strcasecmp(filter, "none")))) { 
-        apr_snprintf(filtbuf, FILTER_LENGTH, "(%s=", sec->attribute);
+    if ((nofilter = (!filter || !*filter || !strcasecmp(filter, "none")))) { 
+        len = apr_snprintf(filtbuf, FILTER_LENGTH, "(%s=", sec->attribute);
     }
     else { 
-        apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(%s=", filter, sec->attribute);
+        len = apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(%s=", filter, sec->attribute);
     }
 
     /*
@@ -267,12 +257,13 @@ static void authn_ldap_build_filter(char *filtbuf,
      * LDAP filter metachars are escaped.
      */
     filtbuf_end = filtbuf + FILTER_LENGTH - 1;
-#if APR_HAS_MICROSOFT_LDAPSDK
-    for (p = user, q=filtbuf + strlen(filtbuf);
-         *p && q < filtbuf_end; ) {
+    for (p = user, q = filtbuf + len; *p; ) {
         if (strchr("*()\\", *p) != NULL) {
-            if ( q + 3 >= filtbuf_end)
-              break;  /* Don't write part of escape sequence if we can't write all of it */
+#if APR_HAS_MICROSOFT_LDAPSDK
+            if (q + 3 >= filtbuf_end) { /* accounts for final \0 */
+                rv = APR_EGENERAL;
+                goto out;
+            }
             *q++ = '\\';
             switch ( *p++ )
             {
@@ -292,23 +283,24 @@ static void authn_ldap_build_filter(char *filtbuf,
                 *q++ = '5';
                 *q++ = 'c';
                 break;
-                        }
-        }
-        else
-            *q++ = *p++;
-    }
+            }
 #else
-    for (p = user, q=filtbuf + strlen(filtbuf);
-         *p && q < filtbuf_end; *q++ = *p++) {
-        if (strchr("*()\\", *p) != NULL) {
+            if (q + 2 >= filtbuf_end) { /* accounts for final \0 */
+                rv = APR_EGENERAL;
+                goto out;
+            }
             *q++ = '\\';
-            if (q >= filtbuf_end) {
-              break;
+            *q++ = *p++;
+#endif
+        }
+        else {
+            if (q + 1 >= filtbuf_end) { /* accounts for final \0 */
+                rv = APR_EGENERAL;
+                goto out;
             }
+            *q++ = *p++;
         }
     }
-#endif
-    *q = '\0';
 
     /*
      * Append the closing parens of the filter, unless doing so would
@@ -316,14 +308,24 @@ static void authn_ldap_build_filter(char *filtbuf,
      */
 
     if (nofilter) { 
-        if (q + 1 <= filtbuf_end)
-            strcat(filtbuf, ")");
+        if (q + 1 >= filtbuf_end) { /* accounts for final \0 */
+            rv = APR_EGENERAL;
+            goto out;
+        }
+        *q++ = ')';
     } 
     else { 
-        if (q + 2 <= filtbuf_end)
-            strcat(filtbuf, "))");
+        if (q + 2 >= filtbuf_end) { /* accounts for final \0 */
+            rv = APR_EGENERAL;
+            goto out;
+        }
+        *q++ = ')';
+        *q++ = ')';
     }
 
+out:
+    *q = '\0';
+    return rv;
 }
 
 static void *create_authnz_ldap_dir_config(apr_pool_t *p, char *d)
@@ -371,15 +373,12 @@ static apr_status_t authnz_ldap_cleanup_connection_close(void *param)
     return APR_SUCCESS;
 }
 
-static int set_request_vars(request_rec *r, enum auth_ldap_phase phase) {
+static int set_request_vars(request_rec *r, enum auth_ldap_phase phase, const char **vals) {
     char *prefix = NULL;
     int prefix_len;
     int remote_user_attribute_set = 0;
-    authn_ldap_request_t *req =
-        (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
     authn_ldap_config_t *sec =
         (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
-    const char **vals = req->vals;
 
     prefix = (phase == LDAP_AUTHN) ? AUTHN_PREFIX : sec->authz_prefix;
     prefix_len = strlen(prefix);
@@ -441,8 +440,8 @@ static util_ldap_connection_t *get_connection_for_authz(request_rec *r, enum aut
     authn_ldap_config_t *sec =
         (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
 
-    char *binddn = sec->binddn;
-    char *bindpw = sec->bindpw;
+    const char *binddn = sec->binddn;
+    const char *bindpw = sec->bindpw;
 
     /* If the per-request config isn't set, we didn't authenticate this user, and leave the default credentials */
     if (req && req->password &&
@@ -549,7 +548,13 @@ static authn_status authn_ldap_check_password(request_rec *r, const char *user,
                   "auth_ldap authenticate: using URL %s", sec->url);
 
     /* build the username filter */
-    authn_ldap_build_filter(filtbuf, r, user, NULL, sec);
+    if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, user, NULL, sec)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02622)
+                      "auth_ldap authenticate: ldap filter too long (>%d): %s",
+                      FILTER_LENGTH, filtbuf);
+        util_ldap_connection_close(ldc);
+        return AUTH_GENERAL_ERROR;
+    }
 
     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
                       "auth_ldap authenticate: final authn filter is %s", filtbuf);
@@ -606,15 +611,15 @@ static authn_status authn_ldap_check_password(request_rec *r, const char *user,
     }
 
     /* mark the user and DN */
-    req->dn = apr_pstrdup(r->pool, dn);
-    req->user = apr_pstrdup(r->pool, user);
-    req->password = apr_pstrdup(r->pool, password);
+    req->dn = dn;
+    req->user = user;
+    req->password = password;
     if (sec->user_is_dn) {
-        r->user = req->dn;
+        r->user = (char *)req->dn;
     }
 
     /* add environment variables */
-    remote_user_attribute_set = set_request_vars(r, LDAP_AUTHN);
+    remote_user_attribute_set = set_request_vars(r, LDAP_AUTHN, req->vals);
 
     /* sanity check */
     if (sec->remote_user_attribute && !remote_user_attribute_set) {
@@ -696,7 +701,12 @@ static authz_status ldapuser_check_authorization(request_rec *r,
             sizeof(authn_ldap_request_t));
 
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02623)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -710,7 +720,7 @@ static authz_status ldapuser_check_authorization(request_rec *r,
         }
 
         ap_set_module_config(r->request_config, &authnz_ldap_module, req);
-        req->dn = apr_pstrdup(r->pool, dn);
+        req->dn = dn;
         req->user = r->user;
 
     }
@@ -740,7 +750,7 @@ static authz_status ldapuser_check_authorization(request_rec *r,
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01703)
                           "auth_ldap authorize: require user: authorization "
                           "successful");
-            set_request_vars(r, LDAP_AUTHZ);
+            set_request_vars(r, LDAP_AUTHZ, req->vals);
             return AUTHZ_GRANTED;
         }
         default: {
@@ -762,7 +772,7 @@ static authz_status ldapuser_check_authorization(request_rec *r,
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01705)
                               "auth_ldap authorize: "
                               "require user: authorization successful");
-                set_request_vars(r, LDAP_AUTHZ);
+                set_request_vars(r, LDAP_AUTHZ, req->vals);
                 return AUTHZ_GRANTED;
             }
             default: {
@@ -880,7 +890,12 @@ static authz_status ldapgroup_check_authorization(request_rec *r,
         req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
             sizeof(authn_ldap_request_t));
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02624)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -894,7 +909,7 @@ static authz_status ldapgroup_check_authorization(request_rec *r,
         }
 
         ap_set_module_config(r->request_config, &authnz_ldap_module, req);
-        req->dn = apr_pstrdup(r->pool, dn);
+        req->dn = dn;
         req->user = r->user;
     }
 
@@ -949,7 +964,7 @@ static authz_status ldapgroup_check_authorization(request_rec *r,
                           "[%s][%d - %s]",
                           ent[i].name, ldc->reason, result,
                           ldap_err2string(result));
-            set_request_vars(r, LDAP_AUTHZ);
+            set_request_vars(r, LDAP_AUTHZ, req->vals);
             return AUTHZ_GRANTED;
         }
         else { 
@@ -988,7 +1003,7 @@ static authz_status ldapgroup_check_authorization(request_rec *r,
                           "(attribute %s) [%s][%d - %s]",
                           ent[i].name, ldc->reason, result,
                           ldap_err2string(result));
-            set_request_vars(r, LDAP_AUTHZ);
+            set_request_vars(r, LDAP_AUTHZ, req->vals);
             return AUTHZ_GRANTED;
         }
         else {
@@ -1069,7 +1084,12 @@ static authz_status ldapdn_check_authorization(request_rec *r,
         req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
             sizeof(authn_ldap_request_t));
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02625)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -1083,7 +1103,7 @@ static authz_status ldapdn_check_authorization(request_rec *r,
         }
 
         ap_set_module_config(r->request_config, &authnz_ldap_module, req);
-        req->dn = apr_pstrdup(r->pool, dn);
+        req->dn = dn;
         req->user = r->user;
     }
 
@@ -1110,7 +1130,7 @@ static authz_status ldapdn_check_authorization(request_rec *r,
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01726)
                           "auth_ldap authorize: "
                           "require dn: authorization successful");
-            set_request_vars(r, LDAP_AUTHZ);
+            set_request_vars(r, LDAP_AUTHZ, req->vals);
             return AUTHZ_GRANTED;
         }
         default: {
@@ -1191,7 +1211,12 @@ static authz_status ldapattribute_check_authorization(request_rec *r,
         req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
             sizeof(authn_ldap_request_t));
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02626)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -1205,7 +1230,7 @@ static authz_status ldapattribute_check_authorization(request_rec *r,
         }
 
         ap_set_module_config(r->request_config, &authnz_ldap_module, req);
-        req->dn = apr_pstrdup(r->pool, dn);
+        req->dn = dn;
         req->user = r->user;
     }
 
@@ -1239,7 +1264,7 @@ static authz_status ldapattribute_check_authorization(request_rec *r,
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01735)
                               "auth_ldap authorize: "
                               "require attribute: authorization successful");
-                set_request_vars(r, LDAP_AUTHZ);
+                set_request_vars(r, LDAP_AUTHZ, req->vals);
                 return AUTHZ_GRANTED;
             }
             default: {
@@ -1319,7 +1344,12 @@ static authz_status ldapfilter_check_authorization(request_rec *r,
         req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
             sizeof(authn_ldap_request_t));
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02627)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -1333,7 +1363,7 @@ static authz_status ldapfilter_check_authorization(request_rec *r,
         }
 
         ap_set_module_config(r->request_config, &authnz_ldap_module, req);
-        req->dn = apr_pstrdup(r->pool, dn);
+        req->dn = dn;
         req->user = r->user;
     }
 
@@ -1359,7 +1389,12 @@ static authz_status ldapfilter_check_authorization(request_rec *r,
                       "auth_ldap authorize: checking filter %s", t);
 
         /* Build the username filter */
-        authn_ldap_build_filter(filtbuf, r, req->user, t, sec);
+        if (APR_SUCCESS != authn_ldap_build_filter(filtbuf, r, req->user, t, sec)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02628)
+                          "auth_ldap authorize: ldap filter too long (>%d): %s",
+                          FILTER_LENGTH, filtbuf);
+            return AUTHZ_DENIED;
+        }
 
         /* Search for the user DN */
         result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
@@ -1384,7 +1419,7 @@ static authz_status ldapfilter_check_authorization(request_rec *r,
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01745)
                               "auth_ldap authorize: require ldap-filter: "
                               "authorization successful");
-                set_request_vars(r, LDAP_AUTHZ);
+                set_request_vars(r, LDAP_AUTHZ, req->vals);
                 return AUTHZ_GRANTED;
             }
             case LDAP_FILTER_ERROR: {
@@ -1411,6 +1446,83 @@ static authz_status ldapfilter_check_authorization(request_rec *r,
     return AUTHZ_DENIED;
 }
 
+static authz_status ldapsearch_check_authorization(request_rec *r,
+                                                   const char *require_args,
+                                                   const void *parsed_require_args)
+{
+    int result = 0;
+    authn_ldap_request_t *req =
+        (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+    authn_ldap_config_t *sec =
+        (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+    util_ldap_connection_t *ldc = NULL;
+
+    const char *err = NULL;
+    const ap_expr_info_t *expr = parsed_require_args;
+    const char *require;
+    const char *t;
+    const char *dn = NULL;
+
+    if (!sec->have_ldap_url) {
+        return AUTHZ_DENIED;
+    }
+
+    if (sec->host) {
+        ldc = get_connection_for_authz(r, LDAP_SEARCH);
+        apr_pool_cleanup_register(r->pool, ldc,
+                                  authnz_ldap_cleanup_connection_close,
+                                  apr_pool_cleanup_null);
+    }
+    else {
+        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02636)
+                      "auth_ldap authorize: no sec->host - weird...?");
+        return AUTHZ_DENIED;
+    }
+
+    require = ap_expr_str_exec(r, expr, &err);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02629)
+                      "auth_ldap authorize: require ldap-search: Can't "
+                      "evaluate require expression: %s", err);
+        return AUTHZ_DENIED;
+    }
+
+    t = require;
+
+    if (t[0]) {
+
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02630)
+                      "auth_ldap authorize: checking filter %s", t);
+
+        /* Search for the user DN */
+        result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+             sec->scope, sec->attributes, t, &dn, &(req->vals));
+
+        /* Make sure that the filtered search returned a single dn */
+        if (result == LDAP_SUCCESS && dn) {
+            req->dn = dn;
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02631)
+                          "auth_ldap authorize: require ldap-search: "
+                          "authorization successful");
+            set_request_vars(r, LDAP_AUTHZ, req->vals);
+            return AUTHZ_GRANTED;
+        }
+        else {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02632)
+                          "auth_ldap authorize: require ldap-search: "
+                          "%s authorization failed [%s][%s]",
+                          t, ldc->reason, ldap_err2string(result));
+        }
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02633)
+                  "auth_ldap authorize search: authorization denied for "
+                  "to %s", r->uri);
+
+    return AUTHZ_DENIED;
+}
+
 static const char *ldap_parse_config(cmd_parms *cmd, const char *require_line,
                                      const void **parsed_require_line)
 {
@@ -1919,6 +2031,12 @@ static const authz_provider authz_ldapfilter_provider =
     &ldap_parse_config,
 };
 
+static const authz_provider authz_ldapsearch_provider =
+{
+    &ldapsearch_check_authorization,
+    &ldap_parse_config,
+};
+
 static void ImportULDAPOptFn(void)
 {
     util_ldap_connection_close  = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_close);
@@ -1959,6 +2077,10 @@ static void register_hooks(apr_pool_t *p)
                               AUTHZ_PROVIDER_VERSION,
                               &authz_ldapfilter_provider,
                               AP_AUTH_INTERNAL_PER_CONF);
+    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-search",
+                              AUTHZ_PROVIDER_VERSION,
+                              &authz_ldapsearch_provider,
+                              AP_AUTH_INTERNAL_PER_CONF);
 
     ap_hook_post_config(authnz_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);