[SSSD] [PATCH] LDAP: Detect the presence of POSIX attributes

Jakub Hrozek jhrozek at redhat.com
Tue Feb 11 20:33:33 UTC 2014


On Thu, Jan 30, 2014 at 11:17:06AM +0100, Sumit Bose wrote:
> On Wed, Jan 29, 2014 at 05:11:59PM +0100, Jakub Hrozek wrote:
> > On Wed, Jan 29, 2014 at 03:39:41PM +0100, Pavel Březina wrote:
> > > On 01/27/2014 11:33 PM, Jakub Hrozek wrote:
> > > >Hi,
> > > >
> > > >When the schema is set to AD and ID mapping is used, there is a one-time
> > > 
> > > you wanted to say "when ID mapping is *not* used"
> > > 
> > > >check ran when searching for users to detect the presence of POSIX attributes
> > > >in LDAP. If this check fails, the search fails as if no entry was found
> > > >and returns a special error code.
> > > >
> > > >If the AD identity lookup finds this error code, the GC is disabled for
> > > >the next search.
> > > >
> > > >The sdap_server_opts structure is filled every time a client connects to
> > > >a server so the posix check boolean is reset to false again on connecting
> > > >to the server.
> > > 
> > > GC lookups should not be disabled permanently. Administrator can
> > > setup POSIX attribute propagation to GC any time and SSSD should
> > > notice it
> > > without restarting.
> > > 
> > > So we should rather perform the check periodically and not only
> > > disable GC lookups, but also re-enable it if it succeeds.
> > 
> > See the attached patch, I added a timeout of 3600 seconds for the next
> > detection. I don't think we want to have the timeout configurable but
> > maybe we want the timeout to be reset on changing the online status as
> > well?
> 
> I think we do not have to reset it when changing the online status since
> having the POSIX IDs in the GC or not is a global feature of the forest
> and changes should be quite rare.

After some discussion last week with Simo I decided to remove the timer
altogether. The decision to change AD schema is so rare it makes sense
to let admins restart a service on clients.

> 
> > 
> > > 
> > > >It might be better to move the check to where the rootDSE is retrieved,
> > > >but the check depends on several features that are not known to the code
> > > >that retrieves the rootDSE (or the connection code for example) such as what
> > > >the attribute mappings are or the authentication method that should be used.
> > > >
> > > >Currently this patch only runs the check when users are requested. It
> > > >would be trivial to add the same code (about 70 lines) to the group
> > > >request as well.
> 
> I think it is not called when enumeration is the first task running. Can
> you add it here as well? And while you are on it it might be worth to
> add it to group lookups as well, just for completeness.

Done. During the testing of the AD enumeration I found some other issues
that are on the list right now as separate patches.

> 
> Please find more comments inline.
> 
> bye,
> Sumit
> 
> > > >
> > > >Additionally, I wonder if the absence of POSIX attributes in GC should
> > > >be reported louder. Currently there is just MINOR_FAILURE.
> > > 
> > > We have SSSDBG_IMPORTANT_INFO for such things.
> > 
> > Done.
> > 
> > > 
> > > >We could go as far as report to syslog when a user or a group from
> > > >subdomains is requested and the GC was already disabled, but I wanted to
> > > >check with the other developers before implementing this.
> > > 
> > > I haven't made up my mind on this question yet, but I'm more
> > > inclined to avoid using syslog for this situation. :-)
> > 
> > Yes, me too.
> > 
> > > 
> > > >One additional question -- currently the AD ID provider always retries
> > > >in LDAP if search in GC didn't find the result. With the possibility of
> > > >detecting the POSIX attributes, do we want to remove this fallback if we
> > > >were able to run the detection and know that GC is available but doesn't
> > > >contain the POSIX attributes?
> > > >
> > > >I would say the fallback should stay, because there is still a chance
> > > >other required attribute will be missing, after all the user can
> > > >configure additional filter and this fallback only affects negative
> > > >searches.
> > > 
> > > I'm not sure if I understand you. Isn't the whole point of this
> > > patch to actually avoid the fallback? I mean if we detect that GC
> > > doesn't have POSIX attributes we want to go directly to LDAP on next
> > > queries, don't we?
> > 
> > I meant the other way. Currently even if we know that GC *has* POSIX
> > attributes and we don't find the user in GC, we still retry LDAP. I was
> > wondering whether we can remove this fallback if we know that POSIX
> > attributes are present. But I think we shouldn't remove the fallback for
> > the general case because admins can still configure a custom filter in
> > the search base and this custom filter can contain any attributes.
> 
> > From c76a1058f1aea30b83f3dd133230a093b1de5b44 Mon Sep 17 00:00:00 2001
> > From: Jakub Hrozek <jhrozek at redhat.com>
> > Date: Mon, 16 Dec 2013 18:36:12 +0100
> > Subject: [PATCH] LDAP: Detect the presence of POSIX attributes
> > 
> > When the schema is set to AD and ID mapping is used, there is a one-time
> 
>                                                 ^ 'not' missing ?

Right, fixed

> 
> > check ran when searching for users to detect the presence of POSIX
> > attributes in LDAP. If this check fails, the search fails as if no entry
> > was found and returns a special error code.
> > 
> > The sdap_server_opts structure is filled every time a client connects to
> > a server so the posix check boolean is reset to false again on connecting
> > to the server.
> > 
> > It might be better to move the check to where the rootDSE is retrieved,
> > but the check depends on several features that are not known to the code
> > that retrieves the rootDSE (or the connection code for example) such as what
> > the attribute mappings are or the authentication method that should be used.
> > ---
> >  src/providers/ad/ad_common.h          |   3 +
> >  src/providers/ad/ad_id.c              |  45 ++++++++-
> >  src/providers/ad/ad_id.h              |   1 +
> >  src/providers/ipa/ipa_subdomains_id.c |   2 +-
> >  src/providers/ldap/ldap_id.c          |  79 ++++++++++++++-
> >  src/providers/ldap/sdap.h             |   1 +
> >  src/providers/ldap/sdap_async.c       | 185 ++++++++++++++++++++++++++++++++++
> >  src/providers/ldap/sdap_async.h       |   9 ++
> >  src/util/util_errors.c                |   1 +
> >  src/util/util_errors.h                |   1 +
> >  10 files changed, 320 insertions(+), 7 deletions(-)
> > 
> > diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
> > index d370cef69124c127f41d7c4cbaa25713363e7752..d70d4656c1d1878a882f261e481a2507f8e58c55 100644
> > --- a/src/providers/ad/ad_common.h
> > +++ b/src/providers/ad/ad_common.h
> > @@ -69,6 +69,9 @@ struct ad_options {
> >      struct sdap_options *id;
> >      struct ad_id_ctx *id_ctx;
> >  
> > +    /* POSIX detection */
> > +    time_t gc_check_timer;
> > +
> >      /* Auth and chpass Provider */
> >      struct krb5_ctx *auth_ctx;
> >  
> > diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c
> > index db921b11850c16cc7e818cad481705acbb4b9127..164855fb13a9ae88fcf244529b357824762178a8 100644
> > --- a/src/providers/ad/ad_id.c
> > +++ b/src/providers/ad/ad_id.c
> > @@ -27,6 +27,8 @@
> >  #include "providers/ldap/sdap_async_enum.h"
> >  #include "providers/ldap/sdap_idmap.h"
> >  
> > +#define GC_POSIX_CHECK_TIMEOUT  3600
> > +
> >  struct ad_handle_acct_info_state {
> >      struct be_req *breq;
> >      struct be_acct_req *ar;
> > @@ -34,6 +36,7 @@ struct ad_handle_acct_info_state {
> >      struct sdap_id_conn_ctx **conn;
> >      struct sdap_domain *sdom;
> >      size_t cindex;
> > +    struct ad_options *ad_options;
> >  
> >      int dp_error;
> >      const char *err;
> > @@ -47,6 +50,7 @@ ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
> >                           struct be_req *breq,
> >                           struct be_acct_req *ar,
> >                           struct sdap_id_ctx *ctx,
> > +                         struct ad_options *ad_options,
> >                           struct sdap_domain *sdom,
> >                           struct sdap_id_conn_ctx **conn)
> >  {
> > @@ -64,6 +68,7 @@ ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
> >      state->ctx = ctx;
> >      state->sdom = sdom;
> >      state->conn = conn;
> > +    state->ad_options = ad_options;
> >      state->cindex = 0;
> >  
> >      ret = ad_handle_acct_info_step(req);
> > @@ -137,12 +142,29 @@ ad_handle_acct_info_done(struct tevent_req *subreq)
> >      if (sdap_err == EOK) {
> >          tevent_req_done(req);
> >          return;
> > +    } else if (sdap_err == ERR_NO_POSIX) {
> > +        if (dp_opt_get_bool(state->ad_options->basic, AD_ENABLE_GC)) {
> > +            DEBUG(SSSDBG_IMPORTANT_INFO, ("POSIX attributes were requested "
> > +                  "but are not present on the server side. Global Catalog "
> > +                  "lookups will be disabled for %d seconds\n",
> > +                  GC_POSIX_CHECK_TIMEOUT));
> > +
> > +            state->ad_options->gc_check_timer = time(NULL);
> 
> I would like to suggest rename gc_check_timer to gc_next_check and set
> it to time(NULL)+GC_POSIX_CHECK_TIMEOUT here. This will save you an add
> operation later which might be called more often (ok, compared to
> calling time() the cycles spend for an add are negligible, but still the
> check might be easier to read).

The timer is gone now :-)

> 
> > +
> > +            ret = dp_opt_set_bool(state->ad_options->basic,
> > +                                  AD_ENABLE_GC, false);
> > +            if (ret != EOK) {
> > +                DEBUG(SSSDBG_MINOR_FAILURE,
> > +                      ("Could not turn off GC support\n"));
> > +                /* Not fatal */
> > +            }
> > +        }
> >      } else if (sdap_err != ENOENT) {
> >          tevent_req_error(req, EIO);
> >          return;
> >      }
> >  
> > -    /* Ret is only ENOENT now. Try the next connection */
> > +    /* Ret is only ENOENT or ERR_NO_POSIX now. Try the next connection */
> >      state->cindex++;
> >      ret = ad_handle_acct_info_step(req);
> >      if (ret != EAGAIN) {
> > @@ -188,6 +210,25 @@ get_conn_list(struct be_req *breq, struct ad_id_ctx *ad_ctx,
> >                struct sss_domain_info *dom, struct be_acct_req *ar)
> >  {
> >      struct sdap_id_conn_ctx **clist;
> > +    time_t now;
> > +    errno_t ret;
> > +
> > +    /* If the last POSIX check was too far in the past, we might need to
> > +     * re-check again
> > +     */
> > +    now = time(NULL);
> > +    if (ad_ctx->ad_options->gc_check_timer &&
> > +            ad_ctx->ad_options->gc_check_timer + GC_POSIX_CHECK_TIMEOUT < now) {
> 
> see above
> 
> > +        ad_ctx->sdap_id_ctx->srv_opts->posix_checked = false;
> > +
> > +        ret = dp_opt_set_bool(ad_ctx->ad_options->basic,
> > +                              AD_ENABLE_GC, true);
> > +        if (ret != EOK) {
> > +            DEBUG(SSSDBG_MINOR_FAILURE,
> > +                    ("Could not turn on GC support\n"));
> > +            /* Not fatal */
> > +        }
> > +    }
> >  
> >      switch (ar->entry_type & BE_REQ_TYPE_MASK) {
> >      case BE_REQ_USER: /* user */
> 
> ...
> 
> > +static errno_t sdap_posix_check_parse(struct sdap_handle *sh,
> > +                                      struct sdap_msg *msg,
> > +                                      void *pvt)
> > +{
> > +    struct berval **vals;
> > +    struct sdap_posix_check_state *state =
> > +        talloc_get_type(pvt, struct sdap_posix_check_state);
> > +    char *dn;
> > +
> > +    dn = ldap_get_dn(sh->ldap, msg->msg);
> > +    if (dn == NULL) {
> > +        DEBUG(SSSDBG_TRACE_LIBS,
> > +              ("Search did not find any entry with POSIX attributes\n"));
> > +        goto done;
> > +    }
> > +    DEBUG(SSSDBG_TRACE_LIBS, ("Found [%s] with POSIX attributes\n", dn));
> > +    ldap_memfree(dn);
> > +
> > +    vals = ldap_get_values_len(sh->ldap, msg->msg,
> > +                               state->opts->user_map[SDAP_AT_USER_UID].name);
> > +    if (vals == NULL) {
> > +        vals = ldap_get_values_len(sh->ldap, msg->msg,
> > +                               state->opts->group_map[SDAP_AT_GROUP_GID].name);
> > +        if (vals == NULL) {
> > +            DEBUG(SSSDBG_TRACE_LIBS, ("Entry does not have POSIX attrs?\n"));
> > +            goto done;
> > +        }
> > +    }
> 
> If you really want to check if one of the attributes is present you
> might also want to check if the value is really an integer?

I'm not sure, this code specifically checks the presence of the
attributes, the semantics is checked later when the attributes are used.
But the performance hit is very small and the additional checks might
bring some nicer error messages, so I've added them.

Thank you for the review a new patch is attached.
-------------- next part --------------
>From faf9a01d18f9da1b9bf12fa3c535ddc03bad7e25 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 16 Dec 2013 18:36:12 +0100
Subject: [PATCH] LDAP: Detect the presence of POSIX attributes

When the schema is set to AD and ID mapping is not used, there is a one-time
check ran when searching for users to detect the presence of POSIX
attributes in LDAP. If this check fails, the search fails as if no entry
was found and returns a special error code.

The sdap_server_opts structure is filled every time a client connects to
a server so the posix check boolean is reset to false again on connecting
to the server.

It might be better to move the check to where the rootDSE is retrieved,
but the check depends on several features that are not known to the code
that retrieves the rootDSE (or the connection code for example) such as what
the attribute mappings are or the authentication method that should be used.
---
 src/providers/ad/ad_id.c              |  50 ++++++++-
 src/providers/ad/ad_id.h              |   1 +
 src/providers/ipa/ipa_subdomains_id.c |   2 +-
 src/providers/ldap/ldap_id.c          | 158 +++++++++++++++++++++++++--
 src/providers/ldap/sdap.h             |   1 +
 src/providers/ldap/sdap_async.c       | 200 ++++++++++++++++++++++++++++++++++
 src/providers/ldap/sdap_async.h       |   9 ++
 src/providers/ldap/sdap_async_enum.c  |  96 +++++++++++++++-
 src/util/util_errors.c                |   1 +
 src/util/util_errors.h                |   1 +
 10 files changed, 504 insertions(+), 15 deletions(-)

diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c
index db921b11850c16cc7e818cad481705acbb4b9127..9e748a24adf47b5b2c4b92e8e0df219e160fcbba 100644
--- a/src/providers/ad/ad_id.c
+++ b/src/providers/ad/ad_id.c
@@ -27,6 +27,28 @@
 #include "providers/ldap/sdap_async_enum.h"
 #include "providers/ldap/sdap_idmap.h"
 
+static void
+disable_gc(struct ad_options *ad_options)
+{
+    errno_t ret;
+
+    if (dp_opt_get_bool(ad_options->basic, AD_ENABLE_GC) == false) {
+        return;
+    }
+
+    DEBUG(SSSDBG_IMPORTANT_INFO, ("POSIX attributes were requested "
+          "but are not present on the server side. Global Catalog "
+          "lookups will be disabled\n"));
+
+    ret = dp_opt_set_bool(ad_options->basic,
+                          AD_ENABLE_GC, false);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+                ("Could not turn off GC support\n"));
+        /* Not fatal */
+    }
+}
+
 struct ad_handle_acct_info_state {
     struct be_req *breq;
     struct be_acct_req *ar;
@@ -34,6 +56,7 @@ struct ad_handle_acct_info_state {
     struct sdap_id_conn_ctx **conn;
     struct sdap_domain *sdom;
     size_t cindex;
+    struct ad_options *ad_options;
 
     int dp_error;
     const char *err;
@@ -47,6 +70,7 @@ ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
                          struct be_req *breq,
                          struct be_acct_req *ar,
                          struct sdap_id_ctx *ctx,
+                         struct ad_options *ad_options,
                          struct sdap_domain *sdom,
                          struct sdap_id_conn_ctx **conn)
 {
@@ -64,6 +88,7 @@ ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
     state->ctx = ctx;
     state->sdom = sdom;
     state->conn = conn;
+    state->ad_options = ad_options;
     state->cindex = 0;
 
     ret = ad_handle_acct_info_step(req);
@@ -137,12 +162,14 @@ ad_handle_acct_info_done(struct tevent_req *subreq)
     if (sdap_err == EOK) {
         tevent_req_done(req);
         return;
+    } else if (sdap_err == ERR_NO_POSIX) {
+        disable_gc(state->ad_options);
     } else if (sdap_err != ENOENT) {
         tevent_req_error(req, EIO);
         return;
     }
 
-    /* Ret is only ENOENT now. Try the next connection */
+    /* Ret is only ENOENT or ERR_NO_POSIX now. Try the next connection */
     state->cindex++;
     ret = ad_handle_acct_info_step(req);
     if (ret != EAGAIN) {
@@ -356,7 +383,7 @@ ad_account_info_handler(struct be_req *be_req)
     }
 
     req = ad_handle_acct_info_send(be_req, be_req, ar, sdap_id_ctx,
-                                   sdom, clist);
+                                   ad_ctx->ad_options, sdom, clist);
     if (req == NULL) {
         ret = ENOMEM;
         goto fail;
@@ -611,9 +638,24 @@ ad_enumeration_done(struct tevent_req *subreq)
 
     ret = sdap_dom_enum_ex_recv(subreq);
     talloc_zfree(subreq);
-    if (ret != EOK) {
+    if (ret == ERR_NO_POSIX) {
+        /* Retry enumerating the same domain again, this time w/o
+         * connecting to GC
+         */
+        disable_gc(state->id_ctx->ad_options);
+        ret = ad_enum_sdom(req, state->sditer, state->id_ctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                ("Could not retry domain %s\n", state->sditer->dom->name));
+            tevent_req_error(req, ret);
+            return;
+        }
+
+        /* Execution will resume in ad_enumeration_done */
+        return;
+    } else if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE,
-              ("Could not enumerate domain %s\n", state->sdom->dom->name));
+              ("Could not enumerate domain %s\n", state->sditer->dom->name));
         tevent_req_error(req, ret);
         return;
     }
diff --git a/src/providers/ad/ad_id.h b/src/providers/ad/ad_id.h
index 74b85645c2d6458617a4064fe3fb3f99696c3741..9eb0ac3754be2fd687ed2257b91854f5fceb82a2 100644
--- a/src/providers/ad/ad_id.h
+++ b/src/providers/ad/ad_id.h
@@ -31,6 +31,7 @@ ad_handle_acct_info_send(TALLOC_CTX *mem_ctx,
                          struct be_req *breq,
                          struct be_acct_req *ar,
                          struct sdap_id_ctx *ctx,
+                         struct ad_options *ad_options,
                          struct sdap_domain *sdom,
                          struct sdap_id_conn_ctx **conn);
 errno_t
diff --git a/src/providers/ipa/ipa_subdomains_id.c b/src/providers/ipa/ipa_subdomains_id.c
index 4c70545cb72f7af19764faf00c2ec30769490dbd..91e0f292773207a23fd0f62eccfaad40e0bb4886 100644
--- a/src/providers/ipa/ipa_subdomains_id.c
+++ b/src/providers/ipa/ipa_subdomains_id.c
@@ -323,7 +323,7 @@ ipa_get_ad_acct_send(TALLOC_CTX *mem_ctx,
     }
 
     subreq = ad_handle_acct_info_send(req, be_req, ar, sdap_id_ctx,
-                                      sdom, clist);
+                                      ad_id_ctx->ad_options, sdom, clist);
     if (subreq == NULL) {
         ret = ENOMEM;
         goto fail;
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index 422a3b92c281e75eb6c652da756d1271f27dffdf..dd34c85cace107bd1a49522d524b494f49e38a3d 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -50,6 +50,7 @@ struct users_get_state {
 
     char *filter;
     const char **attrs;
+    bool use_id_mapping;
 
     int dp_error;
     int sdap_ret;
@@ -58,6 +59,8 @@ struct users_get_state {
 
 static int users_get_retry(struct tevent_req *req);
 static void users_get_connect_done(struct tevent_req *subreq);
+static void users_get_posix_check_done(struct tevent_req *subreq);
+static void users_get_search(struct tevent_req *req);
 static void users_get_done(struct tevent_req *subreq);
 
 struct tevent_req *users_get_send(TALLOC_CTX *memctx,
@@ -79,7 +82,6 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
     uid_t uid;
     enum idmap_error_code err;
     char *sid;
-    bool use_id_mapping;
 
     req = tevent_req_create(memctx, &state, struct users_get_state);
     if (!req) return NULL;
@@ -103,7 +105,7 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
     state->name = name;
     state->filter_type = filter_type;
 
-    use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
+    state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
                                                           ctx->opts->idmap_ctx,
                                                           sdom->dom->name,
                                                           sdom->dom->domain_id);
@@ -116,7 +118,7 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
         }
         break;
     case BE_FILTER_IDNUM:
-        if (use_id_mapping) {
+        if (state->use_id_mapping) {
             /* If we're ID-mapping, we need to use the objectSID
              * in the search filter.
              */
@@ -183,7 +185,7 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
         goto fail;
     }
 
-    if (use_id_mapping || filter_type == BE_FILTER_SECID) {
+    if (state->use_id_mapping || filter_type == BE_FILTER_SECID) {
         /* When mapping IDs or looking for SIDs, we don't want to limit
          * ourselves to users with a UID value. But there must be a SID to map
          * from.
@@ -268,6 +270,75 @@ static void users_get_connect_done(struct tevent_req *subreq)
         return;
     }
 
+    /* If POSIX attributes have been requested with an AD server and we
+     * have no idea about POSIX attributes support, run a one-time check
+     */
+    if (state->use_id_mapping == false &&
+            state->ctx->opts->schema_type == SDAP_SCHEMA_AD &&
+            state->ctx->srv_opts &&
+            state->ctx->srv_opts->posix_checked == false) {
+        subreq = sdap_posix_check_send(state, state->ev, state->ctx->opts,
+                                       sdap_id_op_handle(state->op),
+                                       state->sdom->user_search_bases,
+                                       dp_opt_get_int(state->ctx->opts->basic,
+                                                      SDAP_SEARCH_TIMEOUT));
+        if (subreq == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq, users_get_posix_check_done, req);
+        return;
+    }
+
+    users_get_search(req);
+}
+
+static void users_get_posix_check_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    bool has_posix;
+    int dp_error;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct users_get_state *state = tevent_req_data(req,
+                                                    struct users_get_state);
+
+    ret = sdap_posix_check_recv(subreq, &has_posix);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* We can only finish the id_op on error as the connection
+         * is re-used by the user search
+         */
+        ret = sdap_id_op_done(state->op, ret, &dp_error);
+        if (dp_error == DP_ERR_OK && ret != EOK) {
+            /* retry */
+            ret = users_get_retry(req);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+            }
+            return;
+        }
+    }
+
+    state->ctx->srv_opts->posix_checked = true;
+
+    /* If the check ran to completion, we know for certain about the attributes
+     */
+    if (has_posix == false) {
+        state->sdap_ret = ERR_NO_POSIX;
+        tevent_req_done(req);
+        return;
+    }
+
+    users_get_search(req);
+}
+
+static void users_get_search(struct tevent_req *req)
+{
+    struct users_get_state *state = tevent_req_data(req,
+                                                     struct users_get_state);
+    struct tevent_req *subreq;
+
     subreq = sdap_get_users_send(state, state->ev,
                                  state->domain, state->sysdb,
                                  state->ctx->opts,
@@ -431,6 +502,7 @@ struct groups_get_state {
 
     char *filter;
     const char **attrs;
+    bool use_id_mapping;
 
     int dp_error;
     int sdap_ret;
@@ -439,6 +511,8 @@ struct groups_get_state {
 
 static int groups_get_retry(struct tevent_req *req);
 static void groups_get_connect_done(struct tevent_req *subreq);
+static void groups_get_posix_check_done(struct tevent_req *subreq);
+static void groups_get_search(struct tevent_req *req);
 static void groups_get_done(struct tevent_req *subreq);
 
 struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
@@ -460,7 +534,6 @@ struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
     gid_t gid;
     enum idmap_error_code err;
     char *sid;
-    bool use_id_mapping;
     const char *member_filter[2];
 
     req = tevent_req_create(memctx, &state, struct groups_get_state);
@@ -485,7 +558,7 @@ struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
     state->name = name;
     state->filter_type = filter_type;
 
-    use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
+    state->use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
                                                           ctx->opts->idmap_ctx,
                                                           sdom->dom->name,
                                                           sdom->dom->domain_id);
@@ -500,7 +573,7 @@ struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
         }
         break;
     case BE_FILTER_IDNUM:
-        if (use_id_mapping) {
+        if (state->use_id_mapping) {
             /* If we're ID-mapping, we need to use the objectSID
              * in the search filter.
              */
@@ -567,7 +640,7 @@ struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
         goto fail;
     }
 
-    if (use_id_mapping || filter_type == BE_FILTER_SECID) {
+    if (state->use_id_mapping || filter_type == BE_FILTER_SECID) {
         /* When mapping IDs or looking for SIDs, we don't want to limit
          * ourselves to groups with a GID value
          */
@@ -656,6 +729,75 @@ static void groups_get_connect_done(struct tevent_req *subreq)
         return;
     }
 
+    /* If POSIX attributes have been requested with an AD server and we
+     * have no idea about POSIX attributes support, run a one-time check
+     */
+    if (state->use_id_mapping == false &&
+            state->ctx->opts->schema_type == SDAP_SCHEMA_AD &&
+            state->ctx->srv_opts &&
+            state->ctx->srv_opts->posix_checked == false) {
+        subreq = sdap_posix_check_send(state, state->ev, state->ctx->opts,
+                                       sdap_id_op_handle(state->op),
+                                       state->sdom->user_search_bases,
+                                       dp_opt_get_int(state->ctx->opts->basic,
+                                                      SDAP_SEARCH_TIMEOUT));
+        if (subreq == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq, groups_get_posix_check_done, req);
+        return;
+    }
+
+    groups_get_search(req);
+}
+
+static void groups_get_posix_check_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    bool has_posix;
+    int dp_error;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct groups_get_state *state = tevent_req_data(req,
+                                                     struct groups_get_state);
+
+    ret = sdap_posix_check_recv(subreq, &has_posix);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* We can only finish the id_op on error as the connection
+         * is re-used by the group search
+         */
+        ret = sdap_id_op_done(state->op, ret, &dp_error);
+        if (dp_error == DP_ERR_OK && ret != EOK) {
+            /* retry */
+            ret = groups_get_retry(req);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+            }
+            return;
+        }
+    }
+
+    state->ctx->srv_opts->posix_checked = true;
+
+    /* If the check ran to completion, we know for certain about the attributes
+     */
+    if (has_posix == false) {
+        state->sdap_ret = ERR_NO_POSIX;
+        tevent_req_done(req);
+        return;
+    }
+
+    groups_get_search(req);
+}
+
+static void groups_get_search(struct tevent_req *req)
+{
+    struct groups_get_state *state = tevent_req_data(req,
+                                                     struct groups_get_state);
+    struct tevent_req *subreq;
+
     subreq = sdap_get_groups_send(state, state->ev,
                                   state->sdom,
                                   state->ctx->opts,
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 73c7055f15f9ff2cb5ee19e439512a2978491e5d..ad239a8d07a445013e5bf86a514acb505fc40a13 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -447,6 +447,7 @@ struct sdap_server_opts {
     char *max_group_value;
     char *max_service_value;
     char *max_sudo_value;
+    bool posix_checked;
 };
 
 struct sdap_id_ctx;
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index 367007bde0011ed4de283b2a50b22538830a5275..1022a093f06ec7e9a50b13160fc9a4660a255e92 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -21,6 +21,7 @@
 
 #include <ctype.h>
 #include "util/util.h"
+#include "util/strtonum.h"
 #include "providers/ldap/sdap_async_private.h"
 
 #define REALM_SEPARATOR '@'
@@ -2083,6 +2084,205 @@ int sdap_asq_search_recv(struct tevent_req *req,
     return EOK;
 }
 
+/* ==Posix attribute presence test================================= */
+static errno_t sdap_posix_check_next(struct tevent_req *req);
+static void sdap_posix_check_done(struct tevent_req *subreq);
+static errno_t sdap_posix_check_parse(struct sdap_handle *sh,
+                                      struct sdap_msg *msg,
+                                      void *pvt);
+
+struct sdap_posix_check_state {
+    struct tevent_context *ev;
+    struct sdap_options *opts;
+    struct sdap_handle *sh;
+    struct sdap_search_base **search_bases;
+    int timeout;
+
+    const char **attrs;
+    const char *filter;
+    size_t base_iter;
+
+    bool has_posix;
+};
+
+struct tevent_req *
+sdap_posix_check_send(TALLOC_CTX *memctx, struct tevent_context *ev,
+                      struct sdap_options *opts, struct sdap_handle *sh,
+                      struct sdap_search_base **search_bases,
+                      int timeout)
+{
+    struct tevent_req *req = NULL;
+    struct sdap_posix_check_state *state;
+    errno_t ret;
+
+    req = tevent_req_create(memctx, &state, struct sdap_posix_check_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ev = ev;
+    state->sh = sh;
+    state->opts = opts;
+    state->search_bases = search_bases;
+    state->timeout = timeout;
+
+    state->attrs = talloc_array(state, const char *, 4);
+    if (state->attrs == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    state->attrs[0] = "objectclass";
+    state->attrs[1] = opts->user_map[SDAP_AT_USER_UID].name;
+    state->attrs[2] = opts->group_map[SDAP_AT_GROUP_GID].name;
+    state->attrs[3] = NULL;
+
+    state->filter = talloc_asprintf(state, "(|(%s=*)(%s=*))",
+                                    opts->user_map[SDAP_AT_USER_UID].name,
+                                    opts->group_map[SDAP_AT_GROUP_GID].name);
+    if (state->filter == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    ret = sdap_posix_check_next(req);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t sdap_posix_check_next(struct tevent_req *req)
+{
+    struct tevent_req *subreq = NULL;
+    struct sdap_posix_check_state *state =
+        tevent_req_data(req, struct sdap_posix_check_state);
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Searching for POSIX attributes with base [%s]\n",
+           state->search_bases[state->base_iter]->basedn));
+
+    subreq = sdap_get_generic_ext_send(state, state->ev, state->opts,
+                                 state->sh,
+                                 state->search_bases[state->base_iter]->basedn,
+                                 LDAP_SCOPE_SUBTREE, state->filter,
+                                 state->attrs, false,
+                                 NULL, NULL, 1, state->timeout,
+                                 false, sdap_posix_check_parse,
+                                 state);
+    if (subreq == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(subreq, sdap_posix_check_done, req);
+
+    return EOK;
+}
+
+static errno_t sdap_posix_check_parse(struct sdap_handle *sh,
+                                      struct sdap_msg *msg,
+                                      void *pvt)
+{
+    struct berval **vals = NULL;
+    struct sdap_posix_check_state *state =
+        talloc_get_type(pvt, struct sdap_posix_check_state);
+    char *dn;
+    char *endptr;
+
+    dn = ldap_get_dn(sh->ldap, msg->msg);
+    if (dn == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("Search did not find any entry with POSIX attributes\n"));
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, ("Found [%s] with POSIX attributes\n", dn));
+    ldap_memfree(dn);
+
+    vals = ldap_get_values_len(sh->ldap, msg->msg,
+                               state->opts->user_map[SDAP_AT_USER_UID].name);
+    if (vals == NULL) {
+        vals = ldap_get_values_len(sh->ldap, msg->msg,
+                               state->opts->group_map[SDAP_AT_GROUP_GID].name);
+        if (vals == NULL) {
+            DEBUG(SSSDBG_TRACE_LIBS, ("Entry does not have POSIX attrs?\n"));
+            goto done;
+        }
+    }
+
+    if (vals[0] == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS, ("No value for POSIX attr\n"));
+        goto done;
+    }
+
+    errno = 0;
+    strtouint32(vals[0]->bv_val, &endptr, 10);
+    if (errno || *endptr || (vals[0]->bv_val == endptr)) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("POSIX attribute is not a number: %s\n", vals[0]->bv_val));
+        goto done;
+    }
+
+    state->has_posix = true;
+done:
+    ldap_value_free_len(vals);
+    return EOK;
+}
+
+static void sdap_posix_check_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_posix_check_state *state =
+        tevent_req_data(req, struct sdap_posix_check_state);
+    errno_t ret;
+
+    ret = sdap_get_generic_ext_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("sdap_get_generic_ext_recv failed [%d]: %s\n",
+              ret, strerror(ret)));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* Positive hit is definitve, no need to search other bases */
+    if (state->has_posix == true) {
+        DEBUG(SSSDBG_FUNC_DATA, ("Server has POSIX attributes\n"));
+        tevent_req_done(req);
+        return;
+    }
+
+    state->base_iter++;
+    if (state->search_bases[state->base_iter]) {
+        /* There are more search bases to try */
+        ret = sdap_posix_check_next(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+        }
+        return;
+    }
+
+    /* All bases done! */
+    DEBUG(SSSDBG_TRACE_LIBS, ("Cycled through all bases\n"));
+    tevent_req_done(req);
+}
+
+int sdap_posix_check_recv(struct tevent_req *req,
+                          bool *_has_posix)
+{
+    struct sdap_posix_check_state *state = tevent_req_data(req,
+                                            struct sdap_posix_check_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_has_posix = state->has_posix;
+    return EOK;
+}
+
 /* ==Generic Deref Search============================================ */
 enum sdap_deref_type {
     SDAP_DEREF_OPENLDAP,
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index 33e8708ab7e80ab4280df300fdc300d4ecd18305..593404af3c460f8d956b0832b8f7e398b331729b 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -210,6 +210,15 @@ int sdap_deref_search_recv(struct tevent_req *req,
                            size_t *reply_count,
                            struct sdap_deref_attrs ***reply);
 
+struct tevent_req *
+sdap_posix_check_send(TALLOC_CTX *memctx, struct tevent_context *ev,
+                      struct sdap_options *opts, struct sdap_handle *sh,
+                      struct sdap_search_base **search_bases,
+                      int timeout);
+
+int sdap_posix_check_recv(struct tevent_req *req,
+                          bool *_has_posix);
+
 errno_t
 sdap_attrs_add_ldap_attr(struct sysdb_attrs *ldap_attrs,
                          const char *attr_name,
diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c
index ad4fa896371d102c76fe334c2490db6f4ad2de80..cbee59ee5f517e25cb76db41df4d73104e979388 100644
--- a/src/providers/ldap/sdap_async_enum.c
+++ b/src/providers/ldap/sdap_async_enum.c
@@ -69,6 +69,8 @@ static errno_t sdap_dom_enum_ex_retry(struct tevent_req *req,
                                       tevent_req_fn tcb);
 static bool sdap_dom_enum_ex_connected(struct tevent_req *subreq);
 static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq);
+static void sdap_dom_enum_ex_posix_check_done(struct tevent_req *subreq);
+static errno_t sdap_dom_enum_search_users(struct tevent_req *req);
 static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq);
 static void sdap_dom_enum_ex_get_groups(struct tevent_req *subreq);
 static void sdap_dom_enum_ex_groups_done(struct tevent_req *subreq);
@@ -179,19 +181,109 @@ static void sdap_dom_enum_ex_get_users(struct tevent_req *subreq)
                                                       struct tevent_req);
     struct sdap_dom_enum_ex_state *state = tevent_req_data(req,
                                                 struct sdap_dom_enum_ex_state);
+    bool use_id_mapping;
+    errno_t ret;
 
     if (sdap_dom_enum_ex_connected(subreq) == false) {
         return;
     }
 
+    use_id_mapping = sdap_idmap_domain_has_algorithmic_mapping(
+                                            state->ctx->opts->idmap_ctx,
+                                            state->sdom->dom->name,
+                                            state->sdom->dom->domain_id);
+
+    /* If POSIX attributes have been requested with an AD server and we
+     * have no idea about POSIX attributes support, run a one-time check
+     */
+    if (use_id_mapping == false &&
+            state->ctx->opts->schema_type == SDAP_SCHEMA_AD &&
+            state->ctx->srv_opts &&
+            state->ctx->srv_opts->posix_checked == false) {
+        subreq = sdap_posix_check_send(state, state->ev, state->ctx->opts,
+                                       sdap_id_op_handle(state->user_op),
+                                       state->sdom->user_search_bases,
+                                       dp_opt_get_int(state->ctx->opts->basic,
+                                                      SDAP_SEARCH_TIMEOUT));
+        if (subreq == NULL) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq,
+                                sdap_dom_enum_ex_posix_check_done, req);
+        return;
+    }
+
+
+    ret = sdap_dom_enum_search_users(req);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    /* Execution resumes in sdap_dom_enum_ex_users_done */
+}
+
+static void sdap_dom_enum_ex_posix_check_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    bool has_posix;
+    int dp_error;
+
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_dom_enum_ex_state *state = tevent_req_data(req,
+                                                struct sdap_dom_enum_ex_state);
+
+    ret = sdap_posix_check_recv(subreq, &has_posix);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* We can only finish the id_op on error as the connection
+         * is re-used by the user search
+         */
+        ret = sdap_id_op_done(state->user_op, ret, &dp_error);
+        if (dp_error == DP_ERR_OK && ret != EOK) {
+            /* retry */
+            ret = sdap_dom_enum_ex_retry(req, state->user_op,
+                                         sdap_dom_enum_ex_get_users);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+            }
+            return;
+        }
+    }
+
+    state->ctx->srv_opts->posix_checked = true;
+
+    /* If the check ran to completion, we know for certain about the attributes
+     */
+    if (has_posix == false) {
+        tevent_req_error(req, ERR_NO_POSIX);
+        return;
+    }
+
+
+    ret = sdap_dom_enum_search_users(req);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    /* Execution resumes in sdap_dom_enum_ex_users_done */
+}
+
+static errno_t sdap_dom_enum_search_users(struct tevent_req *req)
+{
+    struct sdap_dom_enum_ex_state *state = tevent_req_data(req,
+                                                struct sdap_dom_enum_ex_state);
+    struct tevent_req *subreq;
+
     subreq = enum_users_send(state, state->ev,
                              state->ctx, state->sdom,
                              state->user_op, state->purge);
     if (subreq == NULL) {
-        tevent_req_error(req, ENOMEM);
-        return;
+        return ENOMEM;
     }
     tevent_req_set_callback(subreq, sdap_dom_enum_ex_users_done, req);
+    return EOK;
 }
 
 static void sdap_dom_enum_ex_users_done(struct tevent_req *subreq)
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 633257e8da0ef039e555a07ad8b51125114ca01c..c9b507557da07555c719bb0dd18145e6799a53eb 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -52,6 +52,7 @@ struct err_string error_to_str[] = {
     { "Domain not found" }, /* ERR_DOMAIN_NOT_FOUND */
     { "Missing configuration file" }, /* ERR_MISSING_CONF */
     { "Malformed search filter" }, /* ERR_INVALID_FILTER, */
+    { "No POSIX attributes detected" }, /* ERR_NO_POSIX */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 1332085031dbe6935cbdc94543fa14b09fe81028..3dd94af1f304d65e22515c859c6f69a021fa7e92 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -74,6 +74,7 @@ enum sssd_errors {
     ERR_DOMAIN_NOT_FOUND,
     ERR_MISSING_CONF,
     ERR_INVALID_FILTER,
+    ERR_NO_POSIX,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
1.8.5.3



More information about the sssd-devel mailing list