>From 8ccde4695fd493dc07391be9a8eecbefee6cb631 Mon Sep 17 00:00:00 2001
From: Simo Sorce <ssorce@redhat.com>
Date: Wed, 26 Aug 2009 16:52:18 -0400
Subject: [PATCH 2/2] Make enumeration an independent task

Always immediately return to DP, and update users/groups in the background.
Also implements an optimization to retrieve only changed/new users/groups
by filtering using the modifyTimestamp after the first query.
---
 server/providers/ldap/ldap_id.c    |  575 +++++++++++++++++++++++++++++++++---
 server/providers/ldap/sdap.c       |   22 +-
 server/providers/ldap/sdap.h       |   10 +-
 server/providers/ldap/sdap_async.c |  148 +++++++++-
 server/providers/ldap/sdap_async.h |    6 +-
 5 files changed, 693 insertions(+), 68 deletions(-)

diff --git a/server/providers/ldap/ldap_id.c b/server/providers/ldap/ldap_id.c
index a809330..b5fdc63 100644
--- a/server/providers/ldap/ldap_id.c
+++ b/server/providers/ldap/ldap_id.c
@@ -32,6 +32,8 @@
 #include "providers/ldap/sdap_async.h"
 
 struct sdap_id_ctx {
+    struct be_ctx *be;
+
     struct sdap_options *opts;
 
     /* global sdap handler */
@@ -39,6 +41,12 @@ struct sdap_id_ctx {
 
     time_t went_offline;
     bool offline;
+
+    /* enumeration loop timer */
+    struct timeval last_run;
+
+    char *max_user_timestamp;
+    char *max_group_timestamp;
 };
 
 static void sdap_req_done(struct be_req *req, int ret, const char *err)
@@ -79,20 +87,23 @@ static int build_attrs_from_map(TALLOC_CTX *memctx,
                                 const char ***_attrs)
 {
     char **attrs;
-    int i;
+    int i, j;
 
     attrs = talloc_array(memctx, char *, size + 1);
     if (!attrs) return ENOMEM;
 
-    /* first attribute is the objectclass value not the attribute name */
+    /* first attribute is "objectclass" not the specifc one */
     attrs[0] = talloc_strdup(memctx, "objectClass");
     if (!attrs[0]) return ENOMEM;
 
     /* add the others */
-    for (i = 1; i < size; i++) {
-        attrs[i] = map[i].name;
+    for (i = j = 1; i < size; i++) {
+        if (map[i].name) {
+            attrs[j] = map[i].name;
+            j++;
+        }
     }
-    attrs[i] = NULL;
+    attrs[j] = NULL;
 
     *_attrs = (const char **)attrs;
 
@@ -229,9 +240,6 @@ struct users_get_state {
     struct tevent_context *ev;
     struct sdap_id_ctx *ctx;
 
-    struct sss_domain_info *dom;
-    struct sysdb_ctx *sysdb;
-
     char *filter;
     const char **attrs;
 };
@@ -242,8 +250,7 @@ static void users_get_op_done(struct tevent_req *subreq);
 static struct tevent_req *users_get_send(TALLOC_CTX *memctx,
                                          struct tevent_context *ev,
                                          struct sdap_id_ctx *ctx,
-                                         struct be_ctx *bectx,
-                                         const char *wildcard,
+                                         const char *name,
                                          int filter_type,
                                          int attrs_type)
 {
@@ -257,8 +264,6 @@ static struct tevent_req *users_get_send(TALLOC_CTX *memctx,
 
     state->ev = ev;
     state->ctx = ctx;
-    state->dom = bectx->domain;
-    state->sysdb = bectx->sysdb;
 
     switch(filter_type) {
     case BE_FILTER_NAME:
@@ -273,7 +278,7 @@ static struct tevent_req *users_get_send(TALLOC_CTX *memctx,
     }
 
     state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
-                                    attr_name, wildcard,
+                                    attr_name, name,
                                     ctx->opts->user_map[SDAP_OC_USER].name);
     if (!state->filter) {
         DEBUG(2, ("Failed to build filter\n"));
@@ -307,7 +312,8 @@ static struct tevent_req *users_get_send(TALLOC_CTX *memctx,
     }
 
     subreq = sdap_get_users_send(state, state->ev,
-                                 state->dom, bectx->sysdb,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
                                  state->ctx->opts, state->ctx->gsh,
                                  state->attrs, state->filter);
     if (!subreq) {
@@ -340,7 +346,8 @@ static void users_get_connect_done(struct tevent_req *subreq)
     }
 
     subreq = sdap_get_users_send(state, state->ev,
-                                 state->dom, state->sysdb,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
                                  state->ctx->opts, state->ctx->gsh,
                                  state->attrs, state->filter);
     if (!subreq) {
@@ -356,7 +363,7 @@ static void users_get_op_done(struct tevent_req *subreq)
                                                       struct tevent_req);
     int ret;
 
-    ret = sdap_get_users_recv(subreq);
+    ret = sdap_get_users_recv(subreq, NULL, NULL);
     talloc_zfree(subreq);
     if (ret) {
         tevent_req_error(req, ret);
@@ -389,9 +396,6 @@ struct groups_get_state {
     struct tevent_context *ev;
     struct sdap_id_ctx *ctx;
 
-    struct sss_domain_info *dom;
-    struct sysdb_ctx *sysdb;
-
     char *filter;
     const char **attrs;
 };
@@ -402,8 +406,7 @@ static void groups_get_op_done(struct tevent_req *subreq);
 static struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
                                           struct tevent_context *ev,
                                           struct sdap_id_ctx *ctx,
-                                          struct be_ctx *bectx,
-                                          const char *wildcard,
+                                          const char *name,
                                           int filter_type,
                                           int attrs_type)
 {
@@ -417,8 +420,6 @@ static struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
 
     state->ev = ev;
     state->ctx = ctx;
-    state->dom = bectx->domain;
-    state->sysdb = bectx->sysdb;
 
     switch(filter_type) {
     case BE_FILTER_NAME:
@@ -433,7 +434,7 @@ static struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
     }
 
     state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
-                                    attr_name, wildcard,
+                                    attr_name, name,
                                     ctx->opts->group_map[SDAP_OC_GROUP].name);
     if (!state->filter) {
         DEBUG(2, ("Failed to build filter\n"));
@@ -467,7 +468,8 @@ static struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
     }
 
     subreq = sdap_get_groups_send(state, state->ev,
-                                 state->dom, bectx->sysdb,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
                                  state->ctx->opts, state->ctx->gsh,
                                  state->attrs, state->filter);
     if (!subreq) {
@@ -500,9 +502,10 @@ static void groups_get_connect_done(struct tevent_req *subreq)
     }
 
     subreq = sdap_get_groups_send(state, state->ev,
-                                 state->dom, state->sysdb,
-                                 state->ctx->opts, state->ctx->gsh,
-                                 state->attrs, state->filter);
+                                  state->ctx->be->domain,
+                                  state->ctx->be->sysdb,
+                                  state->ctx->opts, state->ctx->gsh,
+                                  state->attrs, state->filter);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
         return;
@@ -516,7 +519,7 @@ static void groups_get_op_done(struct tevent_req *subreq)
                                                       struct tevent_req);
     int ret;
 
-    ret = sdap_get_groups_recv(subreq);
+    ret = sdap_get_groups_recv(subreq, NULL, NULL);
     talloc_zfree(subreq);
     if (ret) {
         tevent_req_error(req, ret);
@@ -548,8 +551,6 @@ static void groups_get_done(struct tevent_req *req)
 struct groups_by_user_state {
     struct tevent_context *ev;
     struct sdap_id_ctx *ctx;
-    struct sss_domain_info *dom;
-    struct sysdb_ctx *sysdb;
     const char *name;
     const char **attrs;
 };
@@ -560,8 +561,6 @@ static void groups_by_user_op_done(struct tevent_req *subreq);
 static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
                                               struct tevent_context *ev,
                                               struct sdap_id_ctx *ctx,
-                                              struct sss_domain_info *dom,
-                                              struct sysdb_ctx *sysdb,
                                               const char *name)
 {
     struct tevent_req *req, *subreq;
@@ -573,8 +572,6 @@ static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
 
     state->ev = ev;
     state->ctx = ctx;
-    state->dom = dom;
-    state->sysdb = sysdb;
     state->name = name;
 
     ret = build_attrs_from_map(state, ctx->opts->group_map,
@@ -602,7 +599,8 @@ static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
     }
 
     subreq = sdap_get_initgr_send(state, state->ev,
-                                  state->dom, state->sysdb,
+                                  state->ctx->be->domain,
+                                  state->ctx->be->sysdb,
                                   state->ctx->opts, state->ctx->gsh,
                                   state->name, state->attrs);
     if (!subreq) {
@@ -635,7 +633,8 @@ static void groups_by_user_connect_done(struct tevent_req *subreq)
     }
 
     subreq = sdap_get_initgr_send(state, state->ev,
-                                  state->dom, state->sysdb,
+                                  state->ctx->be->domain,
+                                  state->ctx->be->sysdb,
                                   state->ctx->opts, state->ctx->gsh,
                                   state->name, state->attrs);
     if (!subreq) {
@@ -703,8 +702,12 @@ static void sdap_get_account_info(struct be_req *breq)
     switch (ar->entry_type) {
     case BE_REQ_USER: /* user */
 
-        req = users_get_send(breq, breq->be_ctx->ev,
-                             ctx, breq->be_ctx,
+        /* skip enumerations on demand */
+        if (strcmp(ar->filter_value, "*") == 0) {
+            return sdap_req_done(breq, EOK, "Success");
+        }
+
+        req = users_get_send(breq, breq->be_ctx->ev, ctx,
                              ar->filter_value,
                              ar->filter_type,
                              ar->attr_type);
@@ -718,8 +721,12 @@ static void sdap_get_account_info(struct be_req *breq)
 
     case BE_REQ_GROUP: /* group */
 
-        req = groups_get_send(breq, breq->be_ctx->ev,
-                              ctx, breq->be_ctx,
+        if (strcmp(ar->filter_value, "*") == 0) {
+            return sdap_req_done(breq, EOK, "Success");
+        }
+
+        /* skip enumerations on demand */
+        req = groups_get_send(breq, breq->be_ctx->ev, ctx,
                               ar->filter_value,
                               ar->filter_type,
                               ar->attr_type);
@@ -748,7 +755,6 @@ static void sdap_get_account_info(struct be_req *breq)
             break;
         }
         req = groups_by_user_send(breq, breq->be_ctx->ev, ctx,
-                                  breq->be_ctx->domain, breq->be_ctx->sysdb,
                                   ar->filter_value);
         if (!req) ret = ENOMEM;
         /* tevent_req_set_callback(req, groups_by_user_done, breq); */
@@ -765,6 +771,478 @@ static void sdap_get_account_info(struct be_req *breq)
     if (ret != EOK) return sdap_req_done(breq, ret, err);
 }
 
+
+/* ==Enumeration-Task===================================================== */
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+                                                 struct sdap_id_ctx *ctx);
+static void ldap_id_enumerate_reschedule(struct tevent_req *req);
+static void ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx,
+                                        struct timeval tv);
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+                                      struct tevent_timer *te,
+                                      struct timeval tv, void *pvt);
+
+static void ldap_id_enumerate(struct tevent_context *ev,
+                              struct tevent_timer *tt,
+                              struct timeval tv, void *pvt)
+{
+    struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx);
+    struct tevent_timer *timeout;
+    struct tevent_req *req;
+
+    ctx->last_run = tv;
+
+    req = ldap_id_enumerate_send(ev, ctx);
+    if (!req) {
+        DEBUG(1, ("Failed to schedule enumeration, retrying later!\n"));
+        /* schedule starting from now, not the last run */
+        ldap_id_enumerate_set_timer(ctx, tevent_timeval_current());
+        return;
+    }
+
+    tevent_req_set_callback(req, ldap_id_enumerate_reschedule, ctx);
+
+    /* if enumeration takes so long, either we try to enumerate too
+     * frequently, or something went seriously wrong */
+    tv = tevent_timeval_current();
+    tv = tevent_timeval_add(&tv, ctx->opts->enum_refresh_timeout, 0);
+    timeout = tevent_add_timer(ctx->be->ev, req, tv,
+                               ldap_id_enumerate_timeout, req);
+    return;
+}
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+                                      struct tevent_timer *te,
+                                      struct timeval tv, void *pvt)
+{
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+                                                       struct sdap_id_ctx);
+
+    DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n",
+              ctx->opts->enum_refresh_timeout));
+    ldap_id_enumerate_set_timer(ctx, tevent_timeval_current());
+
+    talloc_zfree(req);
+}
+
+static void ldap_id_enumerate_reschedule(struct tevent_req *req)
+{
+    struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+                                                       struct sdap_id_ctx);
+    ldap_id_enumerate_set_timer(ctx, ctx->last_run);
+}
+
+static void ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx,
+                                        struct timeval tv)
+{
+    struct tevent_timer *enum_task;
+
+    tv = tevent_timeval_add(&tv, ctx->opts->enum_refresh_timeout, 0);
+    enum_task = tevent_add_timer(ctx->be->ev, ctx, tv, ldap_id_enumerate, ctx);
+    if (!enum_task) {
+        DEBUG(0, ("FATAL: failed to setup enumeration task!\n"));
+        /* shutdown! */
+        exit(1);
+    }
+}
+
+
+
+struct global_enum_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *ctx;
+};
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+                                          struct tevent_context *ev,
+                                          struct sdap_id_ctx *ctx);
+static void ldap_id_enum_users_done(struct tevent_req *subreq);
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+                                          struct tevent_context *ev,
+                                          struct sdap_id_ctx *ctx);
+static void ldap_id_enum_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+                                                 struct sdap_id_ctx *ctx)
+{
+    struct global_enum_state *state;
+    struct tevent_req *req, *subreq;
+
+    req = tevent_req_create(ctx, &state, struct global_enum_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->ctx = ctx;
+
+    subreq = enum_users_send(state, ev, ctx);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, ldap_id_enum_users_done, req);
+
+    return req;
+}
+
+static void ldap_id_enum_users_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct global_enum_state *state = tevent_req_data(req,
+                                                 struct global_enum_state);
+    enum tevent_req_state tstate;
+    uint64_t err;
+
+    if (tevent_req_is_error(subreq, &tstate, &err)) {
+        goto fail;
+    }
+    talloc_zfree(subreq);
+
+    subreq = enum_groups_send(state, state->ev, state->ctx);
+    if (!subreq) {
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req);
+
+    return;
+
+fail:
+    DEBUG(1, ("Failed to enumerate users, retrying later!\n"));
+    /* schedule starting from now, not the last run */
+    ldap_id_enumerate_set_timer(state->ctx, tevent_timeval_current());
+
+    talloc_zfree(req);
+}
+
+static void ldap_id_enum_groups_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct global_enum_state *state = tevent_req_data(req,
+                                                 struct global_enum_state);
+    enum tevent_req_state tstate;
+    uint64_t err;
+
+    if (tevent_req_is_error(subreq, &tstate, &err)) {
+        goto fail;
+    }
+    talloc_zfree(req);
+
+    ldap_id_enumerate_set_timer(state->ctx, state->ctx->last_run);
+
+    return;
+
+fail:
+    DEBUG(1, ("Failed to enumerate groups, retrying later!\n"));
+    /* schedule starting from now, not the last run */
+    ldap_id_enumerate_set_timer(state->ctx, tevent_timeval_current());
+
+    talloc_zfree(req);
+}
+
+/* ==User-Enumeration===================================================== */
+
+struct enum_users_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *ctx;
+
+    char *filter;
+    const char **attrs;
+};
+
+static void enum_users_connect_done(struct tevent_req *subreq);
+static void enum_users_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+                                          struct tevent_context *ev,
+                                          struct sdap_id_ctx *ctx)
+{
+    struct tevent_req *req, *subreq;
+    struct users_get_state *state;
+    int ret;
+
+    req = tevent_req_create(memctx, &state, struct users_get_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->ctx = ctx;
+
+    if (ctx->max_user_timestamp) {
+        state->filter = talloc_asprintf(state,
+                               "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+                               ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+                               ctx->opts->user_map[SDAP_OC_USER].name,
+                               ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+                               ctx->max_user_timestamp,
+                               ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+                               ctx->max_user_timestamp);
+    } else {
+        state->filter = talloc_asprintf(state,
+                               "(&(%s=*)(objectclass=%s))",
+                               ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+                               ctx->opts->user_map[SDAP_OC_USER].name);
+    }
+    if (!state->filter) {
+        DEBUG(2, ("Failed to build filter\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    /* TODO: handle attrs_type */
+    ret = build_attrs_from_map(state, ctx->opts->user_map,
+                               SDAP_OPTS_USER, &state->attrs);
+    if (ret != EOK) goto fail;
+
+    if (!connected(ctx)) {
+
+        if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+        /* FIXME: add option to decide if tls should be used
+         * or SASL/GSSAPI, etc ... */
+        subreq = sdap_id_connect_send(state, ev, ctx, false,
+                              ctx->opts->basic[SDAP_DEFAULT_BIND_DN].value,
+                              ctx->opts->basic[SDAP_DEFAULT_AUTHTOK_TYPE].value,
+                              ctx->opts->basic[SDAP_DEFAULT_AUTHTOK].value);
+        if (!subreq) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, enum_users_connect_done, req);
+
+        return req;
+    }
+
+    subreq = sdap_get_users_send(state, state->ev,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
+                                 state->ctx->opts,
+                                 state->ctx->gsh,
+                                 state->attrs, state->filter);
+    if (!subreq) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, enum_users_op_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void enum_users_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct enum_users_state *state = tevent_req_data(req,
+                                                     struct enum_users_state);
+    int ret;
+
+    ret = sdap_id_connect_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sdap_get_users_send(state, state->ev,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
+                                 state->ctx->opts, state->ctx->gsh,
+                                 state->attrs, state->filter);
+    if (!subreq) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, enum_users_op_done, req);
+}
+
+static void enum_users_op_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct enum_users_state *state = tevent_req_data(req,
+                                                     struct enum_users_state);
+    char *timestamp;
+    int ret;
+
+    ret = sdap_get_users_recv(subreq, state, &timestamp);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (timestamp) {
+        talloc_zfree(state->ctx->max_user_timestamp);
+        state->ctx->max_user_timestamp = talloc_steal(state->ctx, timestamp);
+    }
+
+    DEBUG(4, ("Users higher timestamp: [%s]\n",
+              state->ctx->max_user_timestamp));
+
+    tevent_req_done(req);
+}
+
+/* =Group-Enumeration===================================================== */
+
+struct enum_groups_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *ctx;
+
+    char *filter;
+    const char **attrs;
+};
+
+static void enum_groups_connect_done(struct tevent_req *subreq);
+static void enum_groups_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+                                          struct tevent_context *ev,
+                                          struct sdap_id_ctx *ctx)
+{
+    struct tevent_req *req, *subreq;
+    struct enum_groups_state *state;
+    const char *attr_name;
+    int ret;
+
+    req = tevent_req_create(memctx, &state, struct enum_groups_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->ctx = ctx;
+
+        attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+
+    if (ctx->max_group_timestamp) {
+        state->filter = talloc_asprintf(state,
+                              "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+                              ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+                              ctx->opts->group_map[SDAP_OC_GROUP].name,
+                              ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+                              ctx->max_group_timestamp,
+                              ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+                              ctx->max_group_timestamp);
+    } else {
+        state->filter = talloc_asprintf(state,
+                              "(&(%s=*)(objectclass=%s))",
+                              ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+                              ctx->opts->group_map[SDAP_OC_GROUP].name);
+    }
+    if (!state->filter) {
+        DEBUG(2, ("Failed to build filter\n"));
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    /* TODO: handle attrs_type */
+    ret = build_attrs_from_map(state, ctx->opts->group_map,
+                               SDAP_OPTS_GROUP, &state->attrs);
+    if (ret != EOK) goto fail;
+
+    if (!connected(ctx)) {
+
+        if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+        /* FIXME: add option to decide if tls should be used
+         * or SASL/GSSAPI, etc ... */
+        subreq = sdap_id_connect_send(state, ev, ctx, false,
+                              ctx->opts->basic[SDAP_DEFAULT_BIND_DN].value,
+                              ctx->opts->basic[SDAP_DEFAULT_AUTHTOK_TYPE].value,
+                              ctx->opts->basic[SDAP_DEFAULT_AUTHTOK].value);
+        if (!subreq) {
+            ret = ENOMEM;
+            goto fail;
+        }
+
+        tevent_req_set_callback(subreq, enum_groups_connect_done, req);
+
+        return req;
+    }
+
+    subreq = sdap_get_groups_send(state, state->ev,
+                                 state->ctx->be->domain,
+                                 state->ctx->be->sysdb,
+                                 state->ctx->opts, state->ctx->gsh,
+                                 state->attrs, state->filter);
+    if (!subreq) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, enum_groups_op_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void enum_groups_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct enum_groups_state *state = tevent_req_data(req,
+                                                 struct enum_groups_state);
+    int ret;
+
+    ret = sdap_id_connect_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sdap_get_groups_send(state, state->ev,
+                                  state->ctx->be->domain,
+                                  state->ctx->be->sysdb,
+                                  state->ctx->opts, state->ctx->gsh,
+                                  state->attrs, state->filter);
+    if (!subreq) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, enum_groups_op_done, req);
+}
+
+static void enum_groups_op_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct enum_groups_state *state = tevent_req_data(req,
+                                                 struct enum_groups_state);
+    char *timestamp;
+    int ret;
+
+    ret = sdap_get_groups_recv(subreq, state, &timestamp);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (timestamp) {
+        talloc_zfree(state->ctx->max_group_timestamp);
+        state->ctx->max_group_timestamp = talloc_steal(state->ctx, timestamp);
+    }
+
+    DEBUG(4, ("Groups higher timestamp: [%s]\n",
+              state->ctx->max_group_timestamp));
+
+    tevent_req_done(req);
+}
+
+
+
+/* ==Initialization-Functions============================================= */
+
 static void sdap_shutdown(struct be_req *req)
 {
     /* TODO: Clean up any internal data */
@@ -782,6 +1260,7 @@ int sssm_ldap_init(struct be_ctx *bectx,
                    void **pvt_data)
 {
     int ldap_opt_x_tls_require_cert;
+    struct tevent_timer *enum_task;
     struct sdap_id_ctx *ctx;
     char *tls_reqcert;
     int ret;
@@ -789,6 +1268,8 @@ int sssm_ldap_init(struct be_ctx *bectx,
     ctx = talloc_zero(bectx, struct sdap_id_ctx);
     if (!ctx) return ENOMEM;
 
+    ctx->be = bectx;
+
     ret = sdap_get_options(ctx, bectx->cdb, bectx->conf_path,
                            &ctx->opts);
 
@@ -825,6 +1306,16 @@ int sssm_ldap_init(struct be_ctx *bectx,
         }
     }
 
+    /* set up enumeration task */
+    ctx->last_run = tevent_timeval_current(); /* run the first immediately */
+    enum_task = tevent_add_timer(ctx->be->ev, ctx, ctx->last_run,
+                                 ldap_id_enumerate, ctx);
+    if (!enum_task) {
+        DEBUG(0, ("FATAL: failed to setup enumeration task!\n"));
+        ret = EFAULT;
+        goto done;
+    }
+
     *ops = &sdap_id_ops;
     *pvt_data = ctx;
     ret = EOK;
diff --git a/server/providers/ldap/sdap.c b/server/providers/ldap/sdap.c
index 3260043..dacce9c 100644
--- a/server/providers/ldap/sdap.c
+++ b/server/providers/ldap/sdap.c
@@ -40,8 +40,9 @@ struct sdap_gen_opts default_basic_opts[] = {
     { "groupSearchScope", "sub", NULL },
     { "groupSearchFilter", NULL, NULL },
     { "ldapSchema", "rfc2307", NULL },
-    { "offline_timeout", "5", NULL },
-    { "force_upper_case_realm", "0", NULL }
+    { "offline_timeout", "60", NULL },
+    { "force_upper_case_realm", "0", NULL },
+    { "enumeration_refresh_timeout", "300", NULL }
 };
 
 struct sdap_id_map rfc2307_user_map[] = {
@@ -56,7 +57,8 @@ struct sdap_id_map rfc2307_user_map[] = {
     { "userPrincipal", "krbPrincipalName", SYSDB_UPN, NULL },
     { "userFullname", "cn", SYSDB_FULLNAME, NULL },
     { "userMemberOf", NULL, SYSDB_MEMBEROF, NULL },
-    { "userUUID", NULL, SYSDB_UUID, NULL }
+    { "userUUID", NULL, SYSDB_UUID, NULL },
+    { "userModifyTimestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
 };
 
 struct sdap_id_map rfc2307_group_map[] = {
@@ -65,7 +67,8 @@ struct sdap_id_map rfc2307_group_map[] = {
     { "groupPwd", "userPassword", SYSDB_PWD, NULL },
     { "groupGidNumber", "gidNumber", SYSDB_GIDNUM, NULL },
     { "groupMember", "memberuid", SYSDB_MEMBER, NULL },
-    { "groupUUID", NULL, SYSDB_UUID, NULL }
+    { "groupUUID", NULL, SYSDB_UUID, NULL },
+    { "groupModifyTimestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
 };
 
 struct sdap_id_map rfc2307bis_user_map[] = {
@@ -81,7 +84,8 @@ struct sdap_id_map rfc2307bis_user_map[] = {
     { "userFullname", "cn", SYSDB_FULLNAME, NULL },
     { "userMemberOf", "memberOf", SYSDB_MEMBEROF, NULL },
     /* FIXME: this is 389ds specific */
-    { "userUUID", "nsUniqueId", SYSDB_UUID, NULL }
+    { "userUUID", "nsUniqueId", SYSDB_UUID, NULL },
+    { "userModifyTimestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
 };
 
 struct sdap_id_map rfc2307bis_group_map[] = {
@@ -91,7 +95,8 @@ struct sdap_id_map rfc2307bis_group_map[] = {
     { "groupGidNumber", "gidNumber", SYSDB_GIDNUM, NULL },
     { "groupMember", "member", SYSDB_MEMBER, NULL },
     /* FIXME: this is 389ds specific */
-    { "groupUUID", "nsUniqueId", SYSDB_UUID, NULL }
+    { "groupUUID", "nsUniqueId", SYSDB_UUID, NULL },
+    { "groupModifyTimestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
 };
 
 /* =Retrieve-Options====================================================== */
@@ -171,6 +176,11 @@ int sdap_get_options(TALLOC_CTX *memctx,
                          &opts->force_upper_case_realm);
     if (ret != EOK) goto done;
 
+    ret = confdb_get_int(cdb, opts, conf_path,
+                         "enumeration_refresh_timeout", 300,
+                         &opts->enum_refresh_timeout);
+    if (ret != EOK) goto done;
+
     /* schema type */
     if (strcasecmp(opts->basic[SDAP_SCHEMA].value, "rfc2307") == 0) {
         opts->schema_type = SDAP_SCHEMA_RFC2307;
diff --git a/server/providers/ldap/sdap.h b/server/providers/ldap/sdap.h
index 50fc3d1..42af68f 100644
--- a/server/providers/ldap/sdap.h
+++ b/server/providers/ldap/sdap.h
@@ -85,8 +85,9 @@ enum sdap_result {
 #define SDAP_SCHEMA 13
 #define SDAP_OFFLINE_TIMEOUT 14
 #define SDAP_FORCE_UPPER_CASE_REALM 15
+#define SDAP_ENUM_REFRESH_TIMEOUT 16
 
-#define SDAP_OPTS_BASIC 16 /* opts counter */
+#define SDAP_OPTS_BASIC 17 /* opts counter */
 
 /* the objectclass must be the first attribute.
  * Functions depend on this */
@@ -102,8 +103,9 @@ enum sdap_result {
 #define SDAP_AT_USER_PRINC 9
 #define SDAP_AT_USER_FULLNAME 10
 #define SDAP_AT_USER_MEMBEROF 11
+#define SDAP_AT_USER_MODSTAMP 12
 
-#define SDAP_OPTS_USER 12 /* attrs counter */
+#define SDAP_OPTS_USER 13 /* attrs counter */
 
 /* the objectclass must be the first attribute.
  * Functions depend on this */
@@ -113,8 +115,9 @@ enum sdap_result {
 #define SDAP_AT_GROUP_GID 3
 #define SDAP_AT_GROUP_MEMBER 4
 #define SDAP_AT_GROUP_UUID 5
+#define SDAP_AT_GROUP_MODSTAMP 6
 
-#define SDAP_OPTS_GROUP 6 /* attrs counter */
+#define SDAP_OPTS_GROUP 7 /* attrs counter */
 
 struct sdap_gen_opts {
     const char *opt_name;
@@ -140,6 +143,7 @@ struct sdap_options {
     int network_timeout;
     int opt_timeout;
     int offline_timeout;
+    int enum_refresh_timeout;
     bool force_upper_case_realm;
 
     /* supported schema types */
diff --git a/server/providers/ldap/sdap_async.c b/server/providers/ldap/sdap_async.c
index 57ccd86..4d74061 100644
--- a/server/providers/ldap/sdap_async.c
+++ b/server/providers/ldap/sdap_async.c
@@ -834,6 +834,7 @@ struct sdap_save_user_state {
 
     const char *name;
     struct sysdb_attrs *attrs;
+    char *timestamp;
 };
 
 static void sdap_save_user_done(struct tevent_req *subreq);
@@ -871,6 +872,7 @@ static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx,
     state->sh = sh;
     state->dom = dom;
     state->opts = opts;
+    state->timestamp = NULL;
 
     ret = sdap_parse_user(state, state->opts, state->sh,
                           entry, &state->attrs, NULL);
@@ -966,6 +968,29 @@ static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx,
     }
 
     ret = sysdb_attrs_get_el(state->attrs,
+                      opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, &el);
+    if (ret) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+                  state->name));
+    } else {
+        ret = sysdb_attrs_add_string(user_attrs,
+                          opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name,
+                          (const char*)el->values[0].data);
+        if (ret) {
+            goto fail;
+        }
+        state->timestamp = talloc_strdup(state,
+                                         (const char*)el->values[0].data);
+        if (!state->timestamp) {
+            ret = ENOMEM;
+            goto fail;
+        }
+    }
+
+    ret = sysdb_attrs_get_el(state->attrs,
                              opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el);
     if (ret) {
         goto fail;
@@ -991,10 +1016,10 @@ static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx,
 
     DEBUG(6, ("Storing info for user %s\n", state->name));
 
-    subreq = sysdb_store_user_with_attrs_send(state, state->ev, state->handle,
-                                              state->dom, state->name, pwd,
-                                              uid, gid, gecos, homedir, shell,
-                                              user_attrs);
+    subreq = sysdb_store_user_send(state, state->ev, state->handle,
+                                   state->dom, state->name, pwd,
+                                   uid, gid, gecos, homedir, shell,
+                                   user_attrs);
     if (!subreq) {
         ret = ENOMEM;
         goto fail;
@@ -1017,7 +1042,7 @@ static void sdap_save_user_done(struct tevent_req *subreq)
                                             struct sdap_save_user_state);
     int ret;
 
-    ret = sysdb_store_user_with_attrs_recv(subreq);
+    ret = sysdb_store_user_recv(subreq);
     talloc_zfree(subreq);
     if (ret) {
         DEBUG(2, ("Failed to save user %s\n", state->name));
@@ -1028,8 +1053,11 @@ static void sdap_save_user_done(struct tevent_req *subreq)
     tevent_req_done(req);
 }
 
-int sdap_save_user_recv(struct tevent_req *req)
+static int sdap_save_user_recv(struct tevent_req *req,
+                               TALLOC_CTX *mem_ctx, char **timestamp)
 {
+    struct sdap_save_user_state *state = tevent_req_data(req,
+                                            struct sdap_save_user_state);
     enum tevent_req_state tstate;
     uint64_t err;
 
@@ -1038,6 +1066,8 @@ int sdap_save_user_recv(struct tevent_req *req)
         return err;
     }
 
+    *timestamp = talloc_steal(mem_ctx, state->timestamp);
+
     return EOK;
 }
 
@@ -1054,6 +1084,7 @@ struct sdap_save_group_state {
 
     const char *name;
     struct sysdb_attrs *attrs;
+    char *timestamp;
 };
 
 static void sdap_save_group_done(struct tevent_req *subreq);
@@ -1076,6 +1107,7 @@ static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx,
     const char **members;
     long int l;
     gid_t gid;
+    struct sysdb_attrs *group_attrs;
 
     req = tevent_req_create(memctx, &state, struct sdap_save_group_state);
     if (!req) return NULL;
@@ -1085,6 +1117,7 @@ static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx,
     state->sh = sh;
     state->dom = dom;
     state->opts = opts;
+    state->timestamp = NULL;
 
     ret = sdap_parse_group(state, state->opts, state->sh,
                            entry, &state->attrs, NULL);
@@ -1126,10 +1159,41 @@ static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx,
     }
     gid = l;
 
+    group_attrs = sysdb_new_attrs(state);
+    if (!group_attrs) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    ret = sysdb_attrs_get_el(state->attrs,
+                      opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, &el);
+    if (ret) {
+        goto fail;
+    }
+    if (el->num_values == 0) {
+        DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+                  state->name));
+    } else {
+        ret = sysdb_attrs_add_string(group_attrs,
+                          opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name,
+                          (const char*)el->values[0].data);
+        if (ret) {
+            goto fail;
+        }
+        state->timestamp = talloc_strdup(state,
+                                         (const char*)el->values[0].data);
+        if (!state->timestamp) {
+            ret = ENOMEM;
+            goto fail;
+        }
+    }
+
     DEBUG(6, ("Storing info for group %s\n", state->name));
 
-    subreq = sysdb_store_group_send(state, state->ev, state->handle,
-                                    state->dom, state->name, gid, members);
+    subreq = sysdb_store_group_send(state, state->ev,
+                                    state->handle, state->dom,
+                                    state->name, gid, members,
+                                    group_attrs);
     if (!subreq) {
         ret = ENOMEM;
         goto fail;
@@ -1155,7 +1219,7 @@ static void sdap_save_group_done(struct tevent_req *subreq)
     ret = sysdb_store_group_recv(subreq);
     talloc_zfree(subreq);
     if (ret) {
-        DEBUG(2, ("Failed to save group %s\n", state->name));
+        DEBUG(2, ("Failed to save group %s [%d]\n", state->name, ret));
         tevent_req_error(req, ret);
         return;
     }
@@ -1163,8 +1227,11 @@ static void sdap_save_group_done(struct tevent_req *subreq)
     tevent_req_done(req);
 }
 
-int sdap_save_group_recv(struct tevent_req *req)
+static int sdap_save_group_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx, char **timestamp)
 {
+    struct sdap_save_group_state *state = tevent_req_data(req,
+                                            struct sdap_save_group_state);
     enum tevent_req_state tstate;
     uint64_t err;
 
@@ -1173,6 +1240,8 @@ int sdap_save_group_recv(struct tevent_req *req)
         return err;
     }
 
+    *timestamp = talloc_steal(mem_ctx, state->timestamp);
+
     return EOK;
 }
 
@@ -1189,6 +1258,8 @@ struct sdap_get_users_state {
 
     struct sysdb_handle *handle;
     struct sdap_op *op;
+
+    char *higher_timestamp;
 };
 
 static void sdap_get_users_transaction(struct tevent_req *subreq);
@@ -1218,6 +1289,7 @@ struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
     state->sh = sh;
     state->filter = filter;
     state->attrs = attrs;
+    state->higher_timestamp = NULL;
 
     subreq = sysdb_transaction_send(state, state->ev, sysdb);
     if (!subreq) return NULL;
@@ -1344,23 +1416,41 @@ static void sdap_get_users_save_done(struct tevent_req *subreq)
                                                       struct tevent_req);
     struct sdap_get_users_state *state = tevent_req_data(req,
                                             struct sdap_get_users_state);
+    char *timestamp;
     int ret;
 
-    ret = sdap_save_user_recv(subreq);
+    ret = sdap_save_user_recv(subreq, state, &timestamp);
     talloc_zfree(subreq);
 
     /* Do not fail completely on errors.
      * Just report the failure to save and go on */
     if (ret) {
         DEBUG(2, ("Failed to store user. Ignoring.\n"));
+        timestamp = NULL;
+    }
+
+    if (timestamp) {
+        if (state->higher_timestamp) {
+            if (strcmp(timestamp, state->higher_timestamp) > 0) {
+                talloc_zfree(state->higher_timestamp);
+                state->higher_timestamp = timestamp;
+            } else {
+                talloc_zfree(timestamp);
+            }
+        } else {
+            state->higher_timestamp = timestamp;
+        }
     }
 
     /* unlock the operation so that we can proceed with the next result */
     sdap_unlock_next_reply(state->op);
 }
 
-int sdap_get_users_recv(struct tevent_req *req)
+int sdap_get_users_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx, char **timestamp)
 {
+    struct sdap_get_users_state *state = tevent_req_data(req,
+                                            struct sdap_get_users_state);
     enum tevent_req_state tstate;
     uint64_t err;
 
@@ -1369,6 +1459,10 @@ int sdap_get_users_recv(struct tevent_req *req)
         return EIO;
     }
 
+    if (timestamp) {
+        *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+    }
+
     return EOK;
 }
 
@@ -1384,6 +1478,7 @@ struct sdap_get_groups_state {
 
     struct sysdb_handle *handle;
     struct sdap_op *op;
+    char *higher_timestamp;
 };
 
 static void sdap_get_groups_transaction(struct tevent_req *subreq);
@@ -1413,6 +1508,7 @@ struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
     state->sh = sh;
     state->filter = filter;
     state->attrs = attrs;
+    state->higher_timestamp = NULL;
 
     subreq = sysdb_transaction_send(state, state->ev, sysdb);
     if (!subreq) return NULL;
@@ -1544,9 +1640,10 @@ static void sdap_get_groups_save_done(struct tevent_req *subreq)
                                                       struct tevent_req);
     struct sdap_get_groups_state *state = tevent_req_data(req,
                                             struct sdap_get_groups_state);
+    char *timestamp;
     int ret;
 
-    ret = sdap_save_group_recv(subreq);
+    ret = sdap_save_group_recv(subreq, state, &timestamp);
     talloc_zfree(subreq);
 
     /* Do not fail completely on errors.
@@ -1554,14 +1651,31 @@ static void sdap_get_groups_save_done(struct tevent_req *subreq)
 
     if (ret) {
         DEBUG(2, ("Failed to store group. Ignoring.\n"));
+        timestamp = NULL;
+    }
+
+    if (timestamp) {
+        if (state->higher_timestamp) {
+            if (strcmp(timestamp, state->higher_timestamp) > 0) {
+                talloc_zfree(state->higher_timestamp);
+                state->higher_timestamp = timestamp;
+            } else {
+                talloc_zfree(timestamp);
+            }
+        } else {
+            state->higher_timestamp = timestamp;
+        }
     }
 
     /* unlock the operation so that we can proceed with the next result */
     sdap_unlock_next_reply(state->op);
 }
 
-int sdap_get_groups_recv(struct tevent_req *req)
+int sdap_get_groups_recv(struct tevent_req *req,
+                         TALLOC_CTX *mem_ctx, char **timestamp)
 {
+    struct sdap_get_groups_state *state = tevent_req_data(req,
+                                            struct sdap_get_groups_state);
     enum tevent_req_state tstate;
     uint64_t err;
 
@@ -1570,6 +1684,10 @@ int sdap_get_groups_recv(struct tevent_req *req)
         return EIO;
     }
 
+    if (timestamp) {
+        *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+    }
+
     return EOK;
 }
 
@@ -1853,7 +1971,7 @@ static void sdap_get_initgr_save_done(struct tevent_req *subreq)
                                                struct sdap_get_initgr_state);
     int ret;
 
-    ret = sdap_save_group_recv(subreq);
+    ret = sdap_save_group_recv(subreq, NULL, NULL);
     talloc_zfree(subreq);
 
     /* Do not fail completely on errors.
diff --git a/server/providers/ldap/sdap_async.h b/server/providers/ldap/sdap_async.h
index 0cb0b90..c9af750 100644
--- a/server/providers/ldap/sdap_async.h
+++ b/server/providers/ldap/sdap_async.h
@@ -40,7 +40,8 @@ struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
                                        struct sdap_handle *sh,
                                        const char **attrs,
                                        const char *wildcard);
-int sdap_get_users_recv(struct tevent_req *req);
+int sdap_get_users_recv(struct tevent_req *req,
+                        TALLOC_CTX *mem_ctx, char **timestamp);
 
 struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
                                        struct tevent_context *ev,
@@ -50,7 +51,8 @@ struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
                                        struct sdap_handle *sh,
                                        const char **attrs,
                                        const char *wildcard);
-int sdap_get_groups_recv(struct tevent_req *req);
+int sdap_get_groups_recv(struct tevent_req *req,
+                         TALLOC_CTX *mem_ctx, char **timestamp);
 
 struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
                                   struct tevent_context *ev,
-- 
1.6.2.5

