From d1f1337069f9020b2844c7aebcaa5a1c99c03984 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Sat, 28 Jan 2012 11:40:00 -0500
Subject: [PATCH 3/5] LDAP: Add support for service lookups (non-enum)

---
 Makefile.am                              |    2 +
 src/providers/ldap/ldap_common.c         |   33 ++
 src/providers/ldap/ldap_common.h         |   12 +
 src/providers/ldap/ldap_id.c             |   30 ++
 src/providers/ldap/ldap_id_services.c    |  291 ++++++++++++++++++
 src/providers/ldap/sdap.c                |   11 +
 src/providers/ldap/sdap.h                |   13 +
 src/providers/ldap/sdap_async.h          |   17 +
 src/providers/ldap/sdap_async_services.c |  485 ++++++++++++++++++++++++++++++
 9 files changed, 894 insertions(+), 0 deletions(-)
 create mode 100644 src/providers/ldap/ldap_id_services.c
 create mode 100644 src/providers/ldap/sdap_async_services.c

diff --git a/Makefile.am b/Makefile.am
index a8ae01d7c9c599765b99efba75e7465830b240c4..a928b28c5b08689c75336baf088f9e705a9cfd39 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -908,6 +908,7 @@ libsss_ldap_la_SOURCES = \
     src/providers/ldap/ldap_id_enum.c \
     src/providers/ldap/ldap_id_cleanup.c \
     src/providers/ldap/ldap_id_netgroup.c \
+    src/providers/ldap/ldap_id_services.c \
     src/providers/ldap/sdap_access.c \
     src/providers/ldap/ldap_auth.c \
     src/providers/ldap/ldap_init.c \
@@ -918,6 +919,7 @@ libsss_ldap_la_SOURCES = \
     src/providers/ldap/sdap_async_initgroups.c \
     src/providers/ldap/sdap_async_connection.c \
     src/providers/ldap/sdap_async_netgroups.c \
+    src/providers/ldap/sdap_async_services.c \
     src/providers/ldap/sdap_child_helpers.c \
     src/providers/ldap/sdap_fd_events.c \
     src/providers/ldap/sdap_id_op.c \
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index 3d7f9430f303efc4a16a5d4e9c2cebdecdfd65df..786e06b3d936f0cb2c86b0df2a399c14913e03fe 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -28,6 +28,7 @@
 #include "providers/krb5/krb5_common.h"
 #include "providers/ldap/sdap_sudo_timer.h"
 #include "db/sysdb_sudo.h"
+#include "db/sysdb_services.h"
 
 #include "util/sss_krb5.h"
 #include "util/crypto/sss_crypto.h"
@@ -51,6 +52,7 @@ struct dp_option default_basic_opts[] = {
     { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_service_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_sudo_refresh_enabled", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "ldap_sudo_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
@@ -234,6 +236,14 @@ struct sdap_attr_map native_sudorule_map[] = {
     { "ldap_sudorule_order", "sudoOrder", SYSDB_SUDO_CACHE_AT_ORDER, NULL }
 };
 
+struct sdap_attr_map service_map[] = {
+    { "ldap_service_object_class", "ipService", SYSDB_SVC_CLASS, NULL },
+    { "ldap_service_name", "cn", SYSDB_NAME, NULL },
+    { "ldap_service_port", "ipServicePort", SYSDB_SVC_PORT, NULL },
+    { "ldap_service_proto", "ipServiceProtocol", SYSDB_SVC_PROTO, NULL },
+    { "ldap_service_entry_usn", NULL, SYSDB_USN, NULL }
+};
+
 int ldap_get_options(TALLOC_CTX *memctx,
                      struct confdb_ctx *cdb,
                      const char *conf_path,
@@ -243,6 +253,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
     struct sdap_attr_map *default_user_map;
     struct sdap_attr_map *default_group_map;
     struct sdap_attr_map *default_netgroup_map;
+    struct sdap_attr_map *default_service_map;
     struct sdap_options *opts;
     char *schema;
     const char *search_base;
@@ -259,6 +270,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
     const int search_base_options[] = { SDAP_USER_SEARCH_BASE,
                                         SDAP_GROUP_SEARCH_BASE,
                                         SDAP_NETGROUP_SEARCH_BASE,
+                                        SDAP_SERVICE_SEARCH_BASE,
                                         -1 };
 
     opts = talloc_zero(memctx, struct sdap_options);
@@ -318,6 +330,12 @@ int ldap_get_options(TALLOC_CTX *memctx,
                                  &opts->netgroup_search_bases);
     if (ret != EOK && ret != ENOENT) goto done;
 
+    /* Service search */
+    ret = sdap_parse_search_base(opts, opts->basic,
+                                 SDAP_SERVICE_SEARCH_BASE,
+                                 &opts->service_search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
     pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY);
     if (pwd_policy == NULL) {
         DEBUG(1, ("Missing password policy, this may not happen.\n"));
@@ -402,6 +420,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
         default_user_map = rfc2307_user_map;
         default_group_map = rfc2307_group_map;
         default_netgroup_map = netgroup_map;
+        default_service_map = service_map;
     } else
     if (strcasecmp(schema, "rfc2307bis") == 0) {
         opts->schema_type = SDAP_SCHEMA_RFC2307BIS;
@@ -409,6 +428,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
         default_user_map = rfc2307bis_user_map;
         default_group_map = rfc2307bis_group_map;
         default_netgroup_map = netgroup_map;
+        default_service_map = service_map;
     } else
     if (strcasecmp(schema, "IPA") == 0) {
         opts->schema_type = SDAP_SCHEMA_IPA_V1;
@@ -416,6 +436,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
         default_user_map = rfc2307bis_user_map;
         default_group_map = rfc2307bis_group_map;
         default_netgroup_map = netgroup_map;
+        default_service_map = service_map;
     } else
     if (strcasecmp(schema, "AD") == 0) {
         opts->schema_type = SDAP_SCHEMA_AD;
@@ -423,6 +444,7 @@ int ldap_get_options(TALLOC_CTX *memctx,
         default_user_map = rfc2307bis_user_map;
         default_group_map = rfc2307bis_group_map;
         default_netgroup_map = netgroup_map;
+        default_service_map = service_map;
     } else {
         DEBUG(0, ("Unrecognized schema type: %s\n", schema));
         ret = EINVAL;
@@ -461,6 +483,14 @@ int ldap_get_options(TALLOC_CTX *memctx,
         goto done;
     }
 
+    ret = sdap_get_map(opts, cdb, conf_path,
+                       default_service_map,
+                       SDAP_OPTS_SERVICES,
+                       &opts->service_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
     /* If there is no KDC, try the deprecated krb5_kdcip option, too */
     /* FIXME - this can be removed in a future version */
     ret = krb5_try_kdcip(memctx, cdb, conf_path, opts->basic, SDAP_KRB5_KDC);
@@ -665,6 +695,9 @@ errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx,
     case SDAP_SUDO_SEARCH_BASE:
         class_name = "SUDO";
         break;
+    case SDAP_SERVICE_SEARCH_BASE:
+        class_name = "SERVICE";
+        break;
     default:
         DEBUG(SSSDBG_CONF_SETTINGS,
               ("Unknown search base type: [%d]\n", class));
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 0ab0a5e122071222794dd3cf2f88640dd0597496..cda21da41248cb693ccfc6a85b74c797b451424f 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -155,6 +155,18 @@ struct tevent_req *ldap_netgroup_get_send(TALLOC_CTX *memctx,
                                           struct sdap_id_ctx *ctx,
                                           const char *name);
 int ldap_netgroup_get_recv(struct tevent_req *req, int *dp_error_out);
+
+struct tevent_req *
+services_get_send(TALLOC_CTX *mem_ctx,
+                  struct tevent_context *ev,
+                  struct sdap_id_ctx *id_ctx,
+                  const char *name,
+                  const char *protocol,
+                  int filter_type);
+
+errno_t
+services_get_recv(struct tevent_req *req, int *dp_error_out);
+
 /* setup child logging */
 int setup_child(struct sdap_id_ctx *ctx);
 
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index feac63b67215e39494fbbe9c64096e31746101d7..db49e77d98da8ab5ca3acf5c7f1af48198f6fb7d 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -756,6 +756,7 @@ static void sdap_account_info_users_done(struct tevent_req *req);
 static void sdap_account_info_groups_done(struct tevent_req *req);
 static void sdap_account_info_initgr_done(struct tevent_req *req);
 static void sdap_account_info_netgroups_done(struct tevent_req *req);
+static void sdap_account_info_services_done(struct tevent_req *req);
 void sdap_handle_account_info(struct be_req *breq, struct sdap_id_ctx *ctx);
 
 void sdap_account_info_handler(struct be_req *breq)
@@ -858,6 +859,24 @@ void sdap_handle_account_info(struct be_req *breq, struct sdap_id_ctx *ctx)
         tevent_req_set_callback(req, sdap_account_info_netgroups_done, breq);
         break;
 
+    case BE_REQ_SERVICES:
+        /* skip enumerations on demand */
+        if (ar->filter_type == BE_FILTER_ENUM) {
+            return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success");
+        }
+
+        req = services_get_send(breq, breq->be_ctx->ev, ctx,
+                                ar->filter_value,
+                                ar->extra_value,
+                                ar->filter_type);
+        if (!req) {
+            return sdap_handler_done(breq, DP_ERR_FATAL,
+                                     ENOMEM, "Out of memory");
+        }
+        tevent_req_set_callback(req, sdap_account_info_services_done, breq);
+
+        break;
+
     default: /*fail*/
         ret = EINVAL;
         err = "Invalid request type";
@@ -933,3 +952,14 @@ static void sdap_account_info_netgroups_done(struct tevent_req *req)
 
     sdap_account_info_complete(breq, dp_error, ret, "Netgroup lookup failed");
 }
+
+static void sdap_account_info_services_done(struct tevent_req *req)
+{
+    struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+    int ret, dp_error;
+
+    ret = services_get_recv(req, &dp_error);
+    talloc_zfree(req);
+
+    sdap_account_info_complete(breq, dp_error, ret, "Service lookup failed");
+}
diff --git a/src/providers/ldap/ldap_id_services.c b/src/providers/ldap/ldap_id_services.c
new file mode 100644
index 0000000000000000000000000000000000000000..a66a94c589cc6a20cc1a8013e7a07aa821ba2381
--- /dev/null
+++ b/src/providers/ldap/ldap_id_services.c
@@ -0,0 +1,291 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2012 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/strtonum.h"
+#include "db/sysdb.h"
+#include "db/sysdb_services.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+struct sdap_services_get_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *id_ctx;
+    struct sdap_id_op *op;
+    struct sysdb_ctx *sysdb;
+    struct sss_domain_info *domain;
+
+    const char *name;
+    const char *protocol;
+
+    char *filter;
+    const char **attrs;
+
+    int filter_type;
+
+    int dp_error;
+};
+
+static errno_t
+services_get_retry(struct tevent_req *req);
+static void
+services_get_connect_done(struct tevent_req *subreq);
+static void
+services_get_done(struct tevent_req *subreq);
+
+struct tevent_req *
+services_get_send(TALLOC_CTX *mem_ctx,
+                  struct tevent_context *ev,
+                  struct sdap_id_ctx *id_ctx,
+                  const char *name,
+                  const char *protocol,
+                  int filter_type)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sdap_services_get_state *state;
+    const char *attr_name;
+    char *clean_name;
+    char *clean_protocol = NULL;
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_services_get_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->id_ctx = id_ctx;
+    state->dp_error = DP_ERR_FATAL;
+    state->sysdb = id_ctx->be->sysdb;
+    state->domain = state->id_ctx->be->domain;
+    state->name = name;
+    state->filter_type = filter_type;
+
+    state->op = sdap_id_op_create(state, state->id_ctx->conn_cache);
+    if (!state->op) {
+        DEBUG(SSSDBG_MINOR_FAILURE, ("sdap_id_op_create failed\n"));
+        ret = ENOMEM;
+        goto error;
+    }
+
+    switch(filter_type) {
+    case BE_FILTER_NAME:
+        attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_NAME].name;
+        break;
+    case BE_FILTER_IDNUM:
+        attr_name = id_ctx->opts->service_map[SDAP_AT_SERVICE_PORT].name;
+        break;
+    default:
+        ret = EINVAL;
+        goto error;
+    }
+
+    ret = sss_filter_sanitize(state, name, &clean_name);
+    if (ret != EOK)  goto error;
+
+    if (protocol) {
+        ret = sss_filter_sanitize(state, protocol, &clean_protocol);
+        if (ret != EOK)  goto error;
+    }
+
+    if (clean_protocol) {
+        state->filter = talloc_asprintf(
+                state, "(&(%s=%s)(%s=%s)(objectclass=%s))",
+                attr_name, clean_name,
+                id_ctx->opts->service_map[SDAP_AT_SERVICE_PROTOCOL].name,
+                clean_protocol,
+                id_ctx->opts->service_map[SDAP_OC_SERVICE].name);
+    } else {
+        state->filter =
+                talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+                                attr_name, clean_name,
+                                id_ctx->opts->service_map[SDAP_OC_SERVICE].name);
+    }
+    talloc_zfree(clean_name);
+    talloc_zfree(clean_protocol);
+    if (!state->filter) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to build the base filter\n"));
+        ret = ENOMEM;
+        goto error;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS,
+          ("Preparing to search for services with filter [%s]\n",
+           state->filter));
+
+    ret = build_attrs_from_map(state, id_ctx->opts->service_map,
+                               SDAP_OPTS_SERVICES, &state->attrs);
+    if (ret != EOK) goto error;
+
+    ret = services_get_retry(req);
+    if (ret != EOK) goto error;
+
+    return req;
+
+error:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t
+services_get_retry(struct tevent_req *req)
+{
+    errno_t ret;
+    struct sdap_services_get_state *state =
+            tevent_req_data(req, struct sdap_services_get_state);
+    struct tevent_req *subreq;
+
+    subreq = sdap_id_op_connect_send(state->op, state, &ret);
+    if (!subreq) {
+        return ret;
+    }
+
+    tevent_req_set_callback(subreq, services_get_connect_done, req);
+    return EOK;
+}
+
+static void
+services_get_connect_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_services_get_state *state =
+            tevent_req_data(req, struct sdap_services_get_state);
+    int dp_error = DP_ERR_FATAL;
+
+    ret = sdap_id_op_connect_recv(subreq, &dp_error);
+    talloc_zfree(subreq);
+
+    if (ret != EOK) {
+        state->dp_error = dp_error;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sdap_get_services_send(state, state->ev,
+                                    state->domain, state->sysdb,
+                                    state->id_ctx->opts,
+                                    state->id_ctx->opts->service_search_bases,
+                                    sdap_id_op_handle(state->op),
+                                    state->attrs, state->filter,
+                                    dp_opt_get_int(state->id_ctx->opts->basic,
+                                                   SDAP_SEARCH_TIMEOUT),
+                                    false);
+    if (!subreq) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, services_get_done, req);
+}
+
+static void
+services_get_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    uint16_t port;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_services_get_state *state =
+            tevent_req_data(req, struct sdap_services_get_state);
+    int dp_error = DP_ERR_FATAL;
+
+    ret = sdap_get_services_recv(NULL, subreq, NULL);
+    talloc_zfree(subreq);
+
+    /* Check whether we need to try again with another
+     * failover server.
+     */
+    ret = sdap_id_op_done(state->op, ret, &dp_error);
+    if (dp_error == DP_ERR_OK && ret != EOK) {
+        /* retry */
+        ret = services_get_retry(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+
+        /* Return to the mainloop to retry */
+        return;
+    }
+
+    /* An error occurred. */
+    if (ret && ret != ENOENT) {
+        state->dp_error = dp_error;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (ret == ENOENT) {
+        /* Ensure that this entry is removed from the sysdb */
+        switch(state->filter_type) {
+        case BE_FILTER_NAME:
+            ret = sysdb_svc_delete(state->sysdb, state->name,
+                                   0, state->protocol);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+                return;
+            }
+            break;
+
+        case BE_FILTER_IDNUM:
+            errno = 0;
+            port = strtouint16(state->name, NULL, 10);
+            if (errno) {
+                tevent_req_error(req, errno);
+                return;
+            }
+
+            ret = sysdb_svc_delete(state->sysdb, NULL,
+                                   port, state->protocol);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+                return;
+            }
+            break;
+
+        default:
+            tevent_req_error(req, EINVAL);
+            return;
+        }
+    }
+
+    state->dp_error = DP_ERR_OK;
+    tevent_req_done(req);
+}
+
+errno_t
+services_get_recv(struct tevent_req *req, int *dp_error_out)
+{
+    struct sdap_services_get_state *state =
+            tevent_req_data(req, struct sdap_services_get_state);
+
+    if (dp_error_out) {
+        *dp_error_out = state->dp_error;
+    }
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index c366dda0b11d167c0ebae7a56eb3ec003ed6c5d6..ba1c7911e0fb3a72de93e93e209e14f5996d7a7f 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -715,6 +715,9 @@ static errno_t sdap_set_search_base(struct sdap_options *opts,
     case SDAP_SUDO_SEARCH_BASE:
         bases = &opts->sudo_search_bases;
         break;
+    case SDAP_SERVICE_SEARCH_BASE:
+        bases = &opts->service_search_bases;
+        break;
     default:
         return EINVAL;
     }
@@ -796,6 +799,14 @@ errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse,
         if (ret != EOK) goto done;
     }
 
+    /* Services */
+    if (!opts->service_search_bases) {
+       ret = sdap_set_search_base(opts,
+                                  SDAP_SERVICE_SEARCH_BASE,
+                                  naming_context);
+        if (ret != EOK) goto done;
+    }
+
     ret = EOK;
 
 done:
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 1827d7c6ea19c4a44e9eb7fef42959213c8acaf9..bbc59414a724df49068dd611dca3c965f3ee8386 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -164,6 +164,7 @@ enum sdap_basic_opt {
     SDAP_GROUP_SEARCH_BASE,
     SDAP_GROUP_SEARCH_SCOPE,
     SDAP_GROUP_SEARCH_FILTER,
+    SDAP_SERVICE_SEARCH_BASE,
     SDAP_SUDO_SEARCH_BASE,
     SDAP_SUDO_REFRESH_ENABLED,
     SDAP_SUDO_REFRESH_TIMEOUT,
@@ -302,6 +303,15 @@ enum sdap_sudorule_attrs {
     SDAP_OPTS_SUDO  /* attrs counter */
 };
 
+enum sdap_service_attrs {
+    SDAP_OC_SERVICE = 0,
+    SDAP_AT_SERVICE_NAME,
+    SDAP_AT_SERVICE_PORT,
+    SDAP_AT_SERVICE_PROTOCOL,
+    SDAP_AT_SERVICE_USN,
+    SDAP_OPTS_SERVICES /* attrs counter */
+};
+
 struct sdap_attr_map {
     const char *opt_name;
     const char *def_name;
@@ -322,6 +332,8 @@ struct sdap_options {
     struct sdap_attr_map *group_map;
     struct sdap_attr_map *netgroup_map;
     struct sdap_attr_map *host_map;
+    struct sdap_attr_map *service_map;
+
     /* FIXME - should this go to a special struct to avoid mixing with name-service-switch maps? */
     struct sdap_attr_map *sudorule_map;
 
@@ -338,6 +350,7 @@ struct sdap_options {
     struct sdap_search_base **group_search_bases;
     struct sdap_search_base **netgroup_search_bases;
     struct sdap_search_base **sudo_search_bases;
+    struct sdap_search_base **service_search_bases;
 };
 
 struct sdap_server_opts {
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index 2fd606bcaa52d557dee4c85782df0e2b4e6c6522..abf16b0c6c30bb5cbcb0d338ab3bc842cdd2337b 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -211,4 +211,21 @@ errno_t sdap_save_all_names(const char *name,
                             bool lowercase,
                             struct sysdb_attrs *attrs);
 
+struct tevent_req *
+sdap_get_services_send(TALLOC_CTX *memctx,
+                       struct tevent_context *ev,
+                       struct sss_domain_info *dom,
+                       struct sysdb_ctx *sysdb,
+                       struct sdap_options *opts,
+                       struct sdap_search_base **search_bases,
+                       struct sdap_handle *sh,
+                       const char **attrs,
+                       const char *filter,
+                       int timeout,
+                       bool enumeration);
+errno_t
+sdap_get_services_recv(TALLOC_CTX *mem_ctx,
+                       struct tevent_req *req,
+                       char **usn_value);
+
 #endif /* _SDAP_ASYNC_H_ */
diff --git a/src/providers/ldap/sdap_async_services.c b/src/providers/ldap/sdap_async_services.c
new file mode 100644
index 0000000000000000000000000000000000000000..6fab4ace13dd9901d9e75229b74838a5baa5a4d9
--- /dev/null
+++ b/src/providers/ldap/sdap_async_services.c
@@ -0,0 +1,485 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2012 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "db/sysdb_services.h"
+#include "providers/ldap/sdap_async_private.h"
+#include "providers/ldap/ldap_common.h"
+
+struct sdap_get_services_state {
+    struct tevent_context *ev;
+    struct sdap_options *opts;
+    struct sdap_handle *sh;
+    struct sss_domain_info *dom;
+    struct sysdb_ctx *sysdb;
+    const char **attrs;
+    const char *base_filter;
+    char *filter;
+    int timeout;
+    bool enumeration;
+
+    char *higher_usn;
+    struct sysdb_attrs **services;
+    size_t count;
+
+    size_t base_iter;
+    struct sdap_search_base **search_bases;
+};
+
+static errno_t
+sdap_get_services_next_base(struct tevent_req *req);
+static void
+sdap_get_services_process(struct tevent_req *subreq);
+static errno_t
+sdap_save_services(TALLOC_CTX *memctx,
+                   struct sysdb_ctx *sysdb,
+                   const char **attrs,
+                   struct sss_domain_info *dom,
+                   struct sdap_options *opts,
+                   struct sysdb_attrs **services,
+                   size_t num_services,
+                   char **_usn_value);
+static errno_t
+sdap_save_service(TALLOC_CTX *mem_ctx,
+                  struct sysdb_ctx *sysdb,
+                  struct sdap_options *opts,
+                  struct sss_domain_info *dom,
+                  struct sysdb_attrs *attrs,
+                  const char **ldap_attrs,
+                  char **_usn_value,
+                  time_t now);
+
+struct tevent_req *
+sdap_get_services_send(TALLOC_CTX *memctx,
+                       struct tevent_context *ev,
+                       struct sss_domain_info *dom,
+                       struct sysdb_ctx *sysdb,
+                       struct sdap_options *opts,
+                       struct sdap_search_base **search_bases,
+                       struct sdap_handle *sh,
+                       const char **attrs,
+                       const char *filter,
+                       int timeout,
+                       bool enumeration)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sdap_get_services_state *state;
+
+    req = tevent_req_create(memctx, &state, struct sdap_get_services_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->opts = opts;
+    state->dom = dom;
+    state->sh = sh;
+    state->sysdb = sysdb;
+    state->attrs = attrs;
+    state->higher_usn = NULL;
+    state->services =  NULL;
+    state->count = 0;
+    state->timeout = timeout;
+    state->base_filter = filter;
+    state->base_iter = 0;
+    state->search_bases = search_bases;
+    state->enumeration = enumeration;
+
+    ret = sdap_get_services_next_base(req);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, state->ev);
+    }
+
+    return req;
+}
+
+static errno_t
+sdap_get_services_next_base(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct sdap_get_services_state *state;
+
+    state = tevent_req_data(req, struct sdap_get_services_state);
+
+    talloc_zfree(state->filter);
+    state->filter = sdap_get_id_specific_filter(state,
+                        state->base_filter,
+                        state->search_bases[state->base_iter]->filter);
+    if (!state->filter) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Searching for services with base [%s]\n",
+           state->search_bases[state->base_iter]->basedn));
+
+    subreq = sdap_get_generic_send(
+            state, state->ev, state->opts, state->sh,
+            state->search_bases[state->base_iter]->basedn,
+            state->search_bases[state->base_iter]->scope,
+            state->filter, state->attrs,
+            state->opts->service_map, SDAP_OPTS_SERVICES,
+            state->timeout);
+    if (!subreq) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(subreq, sdap_get_services_process, req);
+
+    return EOK;
+}
+
+static void
+sdap_get_services_process(struct tevent_req *subreq)
+{
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_get_services_state *state =
+            tevent_req_data(req, struct sdap_get_services_state);
+    int ret;
+    size_t count, i;
+    struct sysdb_attrs **services;
+    bool next_base = false;
+
+    ret = sdap_get_generic_recv(subreq, state,
+                                &count, &services);
+    talloc_zfree(subreq);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Search for services, returned %d results.\n",
+           count));
+
+    if (state->enumeration || count == 0) {
+        /* No services found in this search or enumerating */
+        next_base = true;
+    }
+
+    /* Add this batch of sevices to the list */
+    if (count > 0) {
+        state->services =
+                talloc_realloc(state,
+                               state->services,
+                               struct sysdb_attrs *,
+                               state->count + count + 1);
+        if (!state->services) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+
+        /* Copy the new services into the list
+         * They're already allocated on 'state'
+         */
+        for (i = 0; i < count; i++) {
+            state->services[state->count + i] = services[i];
+        }
+
+        state->count += count;
+        state->services[state->count] = NULL;
+    }
+
+    if (next_base) {
+        state->base_iter++;
+        if (state->search_bases[state->base_iter]) {
+            /* There are more search bases to try */
+            ret = sdap_get_services_next_base(req);
+            if (ret != EOK) {
+                tevent_req_error(req, ret);
+            }
+            return;
+        }
+    }
+
+    /* No more search bases
+     * Return ENOENT if no services were found
+     */
+    if (state->count == 0) {
+        tevent_req_error(req, ENOENT);
+        return;
+    }
+
+    ret = sdap_save_services(state, state->sysdb,
+                             state->attrs,
+                             state->dom, state->opts,
+                             state->services, state->count,
+                             &state->higher_usn);
+    if (ret) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to store services.\n"));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL,
+          ("Saving %d services - Done\n", state->count));
+
+    tevent_req_done(req);
+}
+
+static errno_t
+sdap_save_services(TALLOC_CTX *mem_ctx,
+                   struct sysdb_ctx *sysdb,
+                   const char **attrs,
+                   struct sss_domain_info *dom,
+                   struct sdap_options *opts,
+                   struct sysdb_attrs **services,
+                   size_t num_services,
+                   char **_usn_value)
+{
+    errno_t ret, sret;
+    time_t now;
+    size_t i;
+    bool in_transaction;
+    char *higher_usn = NULL;
+    char *usn_value;
+    TALLOC_CTX *tmp_ctx;
+
+    if (num_services == 0) {
+        /* Nothing to do */
+        return ENOENT;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    ret = sysdb_transaction_start(sysdb);
+    if (ret != EOK) goto done;
+
+    in_transaction = true;
+
+    now = time(NULL);
+    for (i = 0; i < num_services; i++) {
+        usn_value = NULL;
+
+        ret = sdap_save_service(tmp_ctx, sysdb, opts, dom,
+                                services[i], attrs,
+                                &usn_value, now);
+
+        /* Do not fail completely on errors.
+         * Just report the failure to save and go on */
+        if (ret) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Failed to store service %d. Ignoring.\n", i));
+        } else {
+            DEBUG(SSSDBG_TRACE_INTERNAL,
+                  ("Service [%lu/%lu] processed!\n", i, num_services));
+        }
+
+        if (usn_value) {
+            if (higher_usn) {
+                if ((strlen(usn_value) > strlen(higher_usn)) ||
+                    (strcmp(usn_value, higher_usn) > 0)) {
+                    talloc_zfree(higher_usn);
+                    higher_usn = usn_value;
+                } else {
+                    talloc_zfree(usn_value);
+                }
+            } else {
+                higher_usn = usn_value;
+            }
+        }
+    }
+
+    ret = sysdb_transaction_commit(sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Failed to commit transaction!\n"));
+        goto done;
+    }
+    in_transaction = false;
+
+    if (_usn_value) {
+        *_usn_value = talloc_steal(mem_ctx, higher_usn);
+    }
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Failed to cancel transaction!\n"));
+        }
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+sdap_save_service(TALLOC_CTX *mem_ctx,
+                  struct sysdb_ctx *sysdb,
+                  struct sdap_options *opts,
+                  struct sss_domain_info *dom,
+                  struct sysdb_attrs *attrs,
+                  const char **ldap_attrs,
+                  char **_usn_value,
+                  time_t now)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct sysdb_attrs *svc_attrs;
+    struct ldb_message_element *el;
+    char *usn_value = NULL;
+    const char *name = NULL;
+    const char **aliases;
+    const char **protocols;
+    char **missing;
+    uint16_t port;
+    uint64_t cache_timeout;
+
+    DEBUG(SSSDBG_TRACE_ALL, ("Saving service\n"));
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    svc_attrs = sysdb_new_attrs(tmp_ctx);
+    if (!svc_attrs) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Identify the primary name of this services */
+    ret = sysdb_attrs_primary_name(
+            sysdb, attrs,
+            opts->service_map[SDAP_AT_SERVICE_NAME].name,
+            &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not determine the primary name of the service\n"));
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, ("Primary name: [%s]\n", name));
+
+
+    /* Handle any available aliases */
+    ret = sysdb_attrs_get_aliases(tmp_ctx, attrs, name,
+                                  !dom->case_sensitive,
+                                  &aliases);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to identify service aliases\n"));
+        goto done;
+    }
+
+    /* Get the port number */
+    ret = sysdb_attrs_get_uint16_t(attrs, SYSDB_SVC_PORT, &port);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to identify service port: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    /* Get the protocols this service offers on that port */
+    ret = sysdb_attrs_get_string_array(attrs, SYSDB_SVC_PROTO,
+                                       tmp_ctx, &protocols);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to identify service protocols: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    /* Get the USN value, if available */
+    ret = sysdb_attrs_get_el(attrs,
+                      opts->service_map[SDAP_AT_SERVICE_USN].sys_name, &el);
+    if (ret && ret != ENOENT) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to retrieve USN value: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+    if (ret == ENOENT || el->num_values == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("Original USN value is not available for [%s].\n",
+               name));
+    } else {
+        ret = sysdb_attrs_add_string(svc_attrs,
+                          opts->service_map[SDAP_AT_SERVICE_USN].sys_name,
+                          (const char*)el->values[0].data);
+        if (ret) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Failed to add USN value: [%s]\n",
+                   strerror(ret)));
+            goto done;
+        }
+        usn_value = talloc_strdup(tmp_ctx, (const char*)el->values[0].data);
+        if (!usn_value) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    /* Make sure to remove any extra attributes from the sysdb
+     * that have been removed from LDAP
+     */
+    ret = list_missing_attrs(svc_attrs, opts->service_map, SDAP_OPTS_SERVICES,
+                             ldap_attrs, attrs, &missing);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to identify removed attributes: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    cache_timeout = dp_opt_get_int(opts->basic, SDAP_ENTRY_CACHE_TIMEOUT);
+
+    ret = sysdb_store_service(sysdb, name, port, aliases, protocols,
+                              svc_attrs, missing, cache_timeout, now);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Failed to store service in the sysdb: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    *_usn_value = talloc_steal(mem_ctx, usn_value);
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sdap_get_services_recv(TALLOC_CTX *mem_ctx,
+                       struct tevent_req *req,
+                       char **usn_value)
+{
+    struct sdap_get_services_state *state =
+            tevent_req_data(req, struct sdap_get_services_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (usn_value) {
+        *usn_value = talloc_steal(mem_ctx, state->higher_usn);
+    }
+
+    return EOK;
+}
-- 
1.7.7.6

