From a9174ccf36e62989c7e6beb7029fcf86b7a02276 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Wed, 12 Jan 2011 16:41:26 -0500
Subject: [PATCH] Delete attributes that are removed from LDAP

Sometimes, a value in LDAP will cease to exist (the classic
example being shadowExpire). We need to make sure we purge that
value from SSSD's sysdb as well.

https://fedorahosted.org/sssd/ticket/750
---
 src/db/sysdb.h                           |    7 ++
 src/db/sysdb_ops.c                       |   94 ++++++++++++++++++++++++
 src/providers/ldap/ldap_common.c         |  117 ++++++++++++++++++++++++++++++
 src/providers/ldap/ldap_common.h         |   13 ++++
 src/providers/ldap/sdap_async_accounts.c |   42 +++++++++--
 src/providers/proxy/proxy_id.c           |   12 ++-
 src/tests/sysdb-tests.c                  |    2 +-
 7 files changed, 274 insertions(+), 13 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 3fefdf21536257edff6a314bef5310502d204e09..ae0b33ce1efcec4a895bd89633d27d4cc3c19182 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -521,6 +521,7 @@ int sysdb_store_user(TALLOC_CTX *mem_ctx,
                      const char *homedir,
                      const char *shell,
                      struct sysdb_attrs *attrs,
+                     char **remove_attrs,
                      uint64_t cache_timeout);
 
 int sysdb_store_group(TALLOC_CTX *mem_ctx,
@@ -712,4 +713,10 @@ errno_t sysdb_set_enumerated(struct sysdb_ctx *ctx,
                              struct sss_domain_info *dom,
                              bool enumerated);
 
+errno_t sysdb_remove_attrs(struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *domain,
+                           const char *name,
+                           enum sysdb_member_type type,
+                           char **remove_attrs);
+
 #endif /* __SYS_DB_H__ */
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index 23264b41ba0c5abfcdcddbe7200f87fb4902426e..572f1faf9f377b92ea07c124d73a85d1e1a8eaeb 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -1362,12 +1362,14 @@ int sysdb_store_user(TALLOC_CTX *mem_ctx,
                      const char *homedir,
                      const char *shell,
                      struct sysdb_attrs *attrs,
+                     char **remove_attrs,
                      uint64_t cache_timeout)
 {
     TALLOC_CTX *tmpctx;
     struct ldb_message *msg;
     time_t now;
     int ret;
+    errno_t ret_rm;
 
     tmpctx = talloc_new(mem_ctx);
     if (!tmpctx) {
@@ -1443,6 +1445,16 @@ int sysdb_store_user(TALLOC_CTX *mem_ctx,
 
     ret = sysdb_set_user_attr(tmpctx, ctx,
                               domain, name, attrs, SYSDB_MOD_REP);
+    if (ret != EOK) goto done;
+
+    if (remove_attrs) {
+        ret_rm = sysdb_remove_attrs(ctx, domain, name,
+                                    SYSDB_MEMBER_USER,
+                                    remove_attrs);
+        if (ret_rm != EOK) {
+            DEBUG(4, ("Could not remove missing attributes\n"));
+        }
+    }
 
 done:
     if (ret) {
@@ -2764,3 +2776,85 @@ done:
     talloc_free(msg);
     return ret;
 }
+
+errno_t sysdb_remove_attrs(struct sysdb_ctx *sysdb,
+                           struct sss_domain_info *domain,
+                           const char *name,
+                           enum sysdb_member_type type,
+                           char **remove_attrs)
+{
+    errno_t ret, sret;
+    bool in_transaction = false;
+    struct ldb_message *msg;
+    int lret;
+    size_t i;
+
+    msg = ldb_msg_new(NULL);
+    if (!msg) return ENOMEM;
+
+    if (type == SYSDB_MEMBER_USER) {
+        msg->dn = sysdb_user_dn(sysdb, msg, domain->name, name);
+        if (!msg->dn) {
+            ret = ENOMEM;
+            goto done;
+        }
+    } else if (type == SYSDB_MEMBER_GROUP) {
+        msg->dn = sysdb_group_dn(sysdb, msg, domain->name, name);
+        if (!msg->dn) {
+            ret = ENOMEM;
+            goto done;
+        }
+    } else {
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = sysdb_transaction_start(sysdb);
+    if (ret != EOK) goto done;
+
+    in_transaction = true;
+
+    for (i = 0; remove_attrs[i]; i++) {
+        DEBUG(8, ("Removing attribute [%s] from [%s]\n",
+                  remove_attrs[i], name));
+        lret = ldb_msg_add_empty(msg, remove_attrs[i],
+                                 LDB_FLAG_MOD_DELETE, NULL);
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
+        /* We need to do individual modifies so that we can
+         * skip unknown attributes. Otherwise, any nonexistent
+         * attribute in the sysdb will cause other removals to
+         * fail.
+         */
+        lret = ldb_modify(sysdb->ldb, msg);
+        if (lret != LDB_SUCCESS && lret != LDB_ERR_NO_SUCH_ATTRIBUTE) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
+        /* Remove this attribute and move on to the next one */
+        ldb_msg_remove_attr(msg, remove_attrs[i]);
+    }
+
+    ret = EOK;
+
+done:
+    if (in_transaction) {
+        if (ret == EOK) {
+            sret = sysdb_transaction_commit(sysdb);
+            if (sret != EOK) {
+                DEBUG(2, ("Could not commit transaction\n"));
+            }
+        } else {
+            sret = sysdb_transaction_cancel(sysdb);
+            if (sret != EOK) {
+                DEBUG(2, ("Could not cancel transaction\n"));
+            }
+        }
+    }
+    talloc_free(msg);
+    return ret;
+}
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index c98dd4ff3abbcd078268ebe6f1b37e3959e63cd4..3b5c703f424d7ed417dc65b9967b28019105a222 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -862,3 +862,120 @@ errno_t string_to_shadowpw_days(const char *s, long *d)
     return EOK;
 }
 
+errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx,
+                            struct sdap_attr_map *map,
+                            size_t map_size,
+                            const char *ldap_name,
+                            char **sysdb_name)
+{
+    size_t i;
+
+    for (i = 0; i < map_size; i++) {
+        /* Skip map entries with no name (may depend on
+         * schema selected)
+         */
+        if (!map[i].name) continue;
+
+        /* Check if it is a mapped attribute */
+        if(strcasecmp(ldap_name, map[i].name) == 0) break;
+    }
+
+    if (i < map_size) {
+        /* We found a mapped name, return that */
+        *sysdb_name = talloc_strdup(mem_ctx, map[i].sys_name);
+    } else {
+        /* Not mapped, use the same name */
+        *sysdb_name = talloc_strdup(mem_ctx, ldap_name);
+    }
+
+    if (!*sysdb_name) {
+        return ENOMEM;
+    }
+
+    return EOK;
+}
+
+errno_t list_missing_attrs(TALLOC_CTX *mem_ctx,
+                           struct sdap_attr_map *map,
+                           size_t map_size,
+                           const char **expected_attrs,
+                           struct sysdb_attrs *recvd_attrs,
+                           char ***missing_attrs)
+{
+    errno_t ret;
+    size_t attr_count = 0;
+    size_t i, j, k;
+    char **missing = NULL;
+    char *sysdb_name;
+    TALLOC_CTX *tmp_ctx;
+
+    if (!expected_attrs || !recvd_attrs || !missing_attrs) {
+        return EINVAL;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    /* Count the expected attrs */
+    while(expected_attrs[attr_count]) attr_count++;
+
+    /* Allocate the maximum possible values for missing_attrs, to
+     * be on the safe side
+     */
+    missing = talloc_array(tmp_ctx, char *, attr_count);
+    if (!missing) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    k = 0;
+    /* Check for each expected attribute */
+    for (i = 0; i < attr_count; i++) {
+        ret = get_sysdb_attr_name(tmp_ctx, map, map_size,
+                                  expected_attrs[i],
+                                  &sysdb_name);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        /* objectClass is a special-case and we need to
+         * check for it explicitly.
+         */
+        if (strcasecmp(sysdb_name, "objectClass") == 0) {
+            talloc_free(sysdb_name);
+            continue;
+        }
+
+        for (j = 0; j < recvd_attrs->num; j++) {
+            /* Check whether this expected attribute appeared in the
+             * received attributes and had a non-zero number of
+             * values.
+             */
+            if ((strcasecmp(recvd_attrs->a[j].name, sysdb_name) == 0) &&
+                (recvd_attrs->a[j].num_values > 0)) {
+                break;
+            }
+        }
+
+        if (j < recvd_attrs->num) {
+            /* Attribute was found, therefore not missing */
+            talloc_free(sysdb_name);
+        } else {
+            /* Attribute could not be found. Add to the missing list */
+            missing[k] = talloc_steal(missing, sysdb_name);
+            k++;
+        }
+    }
+
+    /* Terminate the list */
+    missing[k] = NULL;
+
+    ret = EOK;
+    *missing_attrs = talloc_steal(mem_ctx, missing);
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index ba8d130396ffaceacfb9b842a4a38633b609a7d4..3cbf3f600faf885efea9c67d0e36bdea69c88c1c 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -142,4 +142,17 @@ int setup_child(struct sdap_id_ctx *ctx);
 
 
 errno_t string_to_shadowpw_days(const char *s, long *d);
+
+errno_t get_sysdb_attr_name(TALLOC_CTX *mem_ctx,
+                            struct sdap_attr_map *map,
+                            size_t map_size,
+                            const char *ldap_name,
+                            char **sysdb_name);
+
+errno_t list_missing_attrs(TALLOC_CTX *mem_ctx,
+                           struct sdap_attr_map *map,
+                           size_t map_size,
+                           const char **expected_attrs,
+                           struct sysdb_attrs *recvd_attrs,
+                           char ***missing_attrs);
 #endif /* _LDAP_COMMON_H_ */
diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c
index 21c3237eb990c3f83b43db24d649e95117c661db..3c055fb36d10c8493420fe964a4332acd3797590 100644
--- a/src/providers/ldap/sdap_async_accounts.c
+++ b/src/providers/ldap/sdap_async_accounts.c
@@ -23,6 +23,7 @@
 #include "util/util.h"
 #include "db/sysdb.h"
 #include "providers/ldap/sdap_async_private.h"
+#include "providers/ldap/ldap_common.h"
 
 /* ==Save-User-Entry====================================================== */
 
@@ -33,6 +34,7 @@ static int sdap_save_user(TALLOC_CTX *memctx,
                           struct sdap_options *opts,
                           struct sss_domain_info *dom,
                           struct sysdb_attrs *attrs,
+                          const char **ldap_attrs,
                           bool is_initgr,
                           char **_usn_value)
 {
@@ -52,6 +54,7 @@ static int sdap_save_user(TALLOC_CTX *memctx,
     int cache_timeout;
     char *usn_value = NULL;
     size_t c;
+    char **missing = NULL;
 
     DEBUG(9, ("Save user\n"));
 
@@ -265,12 +268,28 @@ static int sdap_save_user(TALLOC_CTX *memctx,
         }
     }
 
+    /* Make sure that any attributes we requested from LDAP that we
+     * did not receive are also removed from the sysdb
+     */
+    ret = list_missing_attrs(NULL, opts->user_map, SDAP_OPTS_USER,
+                             ldap_attrs, attrs, &missing);
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    /* Remove missing attributes */
+    if (missing && !missing[0]) {
+        /* Nothing to remove */
+        talloc_zfree(missing);
+    }
+
     DEBUG(6, ("Storing info for user %s\n", name));
 
     ret = sysdb_store_user(memctx, ctx, dom,
                            name, pwd, uid, gid, gecos, homedir, shell,
-                           user_attrs, cache_timeout);
+                           user_attrs, missing, cache_timeout);
     if (ret) goto fail;
+    talloc_zfree(missing);
 
     if (_usn_value) {
         *_usn_value = usn_value;
@@ -280,6 +299,7 @@ static int sdap_save_user(TALLOC_CTX *memctx,
 
 fail:
     DEBUG(2, ("Failed to save user %s\n", name));
+    talloc_free(missing);
     return ret;
 }
 
@@ -288,6 +308,7 @@ fail:
 
 static int sdap_save_users(TALLOC_CTX *memctx,
                            struct sysdb_ctx *sysdb,
+                           const char **attrs,
                            struct sss_domain_info *dom,
                            struct sdap_options *opts,
                            struct sysdb_attrs **users,
@@ -319,7 +340,8 @@ static int sdap_save_users(TALLOC_CTX *memctx,
         usn_value = NULL;
 
         ret = sdap_save_user(tmpctx, sysdb, opts, dom,
-                             users[i], false, &usn_value);
+                             users[i], attrs, false,
+                             &usn_value);
 
         /* Do not fail completely on errors.
          * Just report the failure to save and go on */
@@ -443,6 +465,7 @@ static void sdap_get_users_process(struct tevent_req *subreq)
     }
 
     ret = sdap_save_users(state, state->sysdb,
+                          state->attrs,
                           state->dom, state->opts,
                           state->users, state->count,
                           &state->higher_usn);
@@ -1442,7 +1465,8 @@ next:
     }
 
     if (state->check_count == 0) {
-        ret = sdap_save_users(state, state->sysdb, state->dom, state->opts,
+        ret = sdap_save_users(state, state->sysdb, state->attrs,
+                              state->dom, state->opts,
                               state->new_members, state->count, NULL);
         if (ret) {
             DEBUG(2, ("Failed to store users.\n"));
@@ -1747,7 +1771,8 @@ static void sdap_nested_done(struct tevent_req *subreq)
     /* Save all of the users first so that they are in
      * place for the groups to add them.
      */
-    ret = sdap_save_users(state, state->sysdb, state->dom, state->opts,
+    ret = sdap_save_users(state, state->sysdb, state->attrs,
+                          state->dom, state->opts,
                           users, count, &state->higher_usn);
     if (ret != EOK) {
         tevent_req_error(req, ret);
@@ -2300,6 +2325,7 @@ struct sdap_get_initgr_state {
     struct sdap_handle *sh;
     const char *name;
     const char **grp_attrs;
+    const char **ldap_attrs;
 
     struct sysdb_attrs *orig_user;
 };
@@ -2320,7 +2346,6 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
     struct sdap_get_initgr_state *state;
     const char *base_dn;
     char *filter;
-    const char **attrs;
     int ret;
 
     DEBUG(9, ("Retrieving info for initgroups call\n"));
@@ -2354,7 +2379,7 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
     }
 
     ret = build_attrs_from_map(state, state->opts->user_map,
-                               SDAP_OPTS_USER, &attrs);
+                               SDAP_OPTS_USER, &state->ldap_attrs);
     if (ret) {
         talloc_zfree(req);
         return NULL;
@@ -2363,7 +2388,7 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
     subreq = sdap_get_generic_send(state, state->ev,
                                    state->opts, state->sh,
                                    base_dn, LDAP_SCOPE_SUBTREE,
-                                   filter, attrs,
+                                   filter, state->ldap_attrs,
                                    state->opts->user_map, SDAP_OPTS_USER);
     if (!subreq) {
         talloc_zfree(req);
@@ -2422,7 +2447,8 @@ static void sdap_get_initgr_user(struct tevent_req *subreq)
 
     ret = sdap_save_user(state, state->sysdb,
                          state->opts, state->dom,
-                         state->orig_user, true, NULL);
+                         state->orig_user, state->ldap_attrs,
+                         true, NULL);
     if (ret) {
         sysdb_transaction_cancel(state->sysdb);
         tevent_req_error(req, ret);
diff --git a/src/providers/proxy/proxy_id.c b/src/providers/proxy/proxy_id.c
index 4fd656fedac281fa6025685adfcef75232493e10..3df21063c8209b7a237e83db97cf6ae06dc51f4d 100644
--- a/src/providers/proxy/proxy_id.c
+++ b/src/providers/proxy/proxy_id.c
@@ -105,7 +105,8 @@ static int get_pw_name(TALLOC_CTX *mem_ctx,
                                pwd->pw_gecos,
                                pwd->pw_dir,
                                pwd->pw_shell,
-                               NULL, ctx->entry_cache_timeout);
+                               NULL, NULL,
+                               ctx->entry_cache_timeout);
         if (ret) {
             goto done;
         }
@@ -219,7 +220,8 @@ static int get_pw_uid(TALLOC_CTX *mem_ctx,
                                pwd->pw_gecos,
                                pwd->pw_dir,
                                pwd->pw_shell,
-                               NULL, ctx->entry_cache_timeout);
+                               NULL, NULL,
+                               ctx->entry_cache_timeout);
         if (ret) {
             goto done;
         }
@@ -358,7 +360,8 @@ again:
                                pwd->pw_gecos,
                                pwd->pw_dir,
                                pwd->pw_shell,
-                               NULL, ctx->entry_cache_timeout);
+                               NULL, NULL,
+                               ctx->entry_cache_timeout);
         if (ret) {
             /* Do not fail completely on errors.
              * Just report the failure to save and go on */
@@ -933,7 +936,8 @@ static int get_initgr(TALLOC_CTX *mem_ctx,
                                pwd->pw_gecos,
                                pwd->pw_dir,
                                pwd->pw_shell,
-                               NULL, ctx->entry_cache_timeout);
+                               NULL, NULL,
+                               ctx->entry_cache_timeout);
         if (ret) {
             goto done;
         }
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 2beb2588427355851abd856c4cea2e91506d37ee..eada9a3afa85c25f9bc24cd49b56bb41c6b7ea27 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -210,7 +210,7 @@ static int test_store_user(struct test_data *data)
                            data->ctx->domain, data->username, "x",
                            data->uid, 0, gecos, homedir,
                            data->shell ? data->shell : "/bin/bash",
-                           NULL, -1);
+                           NULL, NULL, -1);
     return ret;
 }
 
-- 
1.7.3.4

