From ed3b6bedff19730369329440c2dfa9477f84130b Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Mon, 26 Mar 2012 21:41:28 -0400
Subject: [PATCH 6/7] AD: Add AD identity provider

This new identity provider takes advantage of existing code for
the LDAP provider, but provides sensible defaults for operating
against an Active Directory 2008 R2 or later server.
---
 Makefile.am                   |   39 +++
 src/providers/ad/ad_common.c  |  607 +++++++++++++++++++++++++++++++++++++++++
 src/providers/ad/ad_common.h  |   85 ++++++
 src/providers/ad/ad_id.c      |   37 +++
 src/providers/ad/ad_id.h      |   29 ++
 src/providers/ad/ad_init.c    |  184 +++++++++++++
 src/providers/ad/ad_opts.h    |  229 ++++++++++++++++
 src/tests/ad_ldap_opt-tests.c |  109 ++++++++
 8 files changed, 1319 insertions(+)
 create mode 100644 src/providers/ad/ad_common.c
 create mode 100644 src/providers/ad/ad_common.h
 create mode 100644 src/providers/ad/ad_id.c
 create mode 100644 src/providers/ad/ad_id.h
 create mode 100644 src/providers/ad/ad_init.c
 create mode 100644 src/providers/ad/ad_opts.h
 create mode 100644 src/tests/ad_ldap_opt-tests.c

diff --git a/Makefile.am b/Makefile.am
index 99565ee1b596ad8d7a69ca9fe52dbf8c7b8ae23c..211656c55056a8cd1cc72ea058e16e3b52ad3ce8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -117,6 +117,7 @@ if HAVE_CHECK
         find_uid-tests \
         auth-tests \
         ipa_ldap_opt-tests \
+        ad_ldap_opt-tests \
         simple_access-tests \
         crypto-tests \
         util-tests \
@@ -151,6 +152,7 @@ sssdlib_LTLIBRARIES = \
     libsss_krb5.la \
     libsss_proxy.la \
     libsss_ipa.la \
+    libsss_ad.la \
     libsss_simple.la
 
 ldblib_LTLIBRARIES = \
@@ -931,6 +933,16 @@ ipa_ldap_opt_tests_LDADD = \
     $(TALLOC_LIBS) \
     libsss_test_common.la
 
+ad_ldap_opt_tests_SOURCES = \
+    src/tests/ad_ldap_opt-tests.c
+ad_ldap_opt_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(CHECK_CFLAGS)
+ad_ldap_opt_tests_LDADD = \
+    $(CHECK_LIBS) \
+    $(TALLOC_LIBS) \
+    libsss_test_common.la
+
 simple_access_tests_SOURCES = \
     src/tests/simple_access-tests.c \
     src/providers/simple/simple_access.c
@@ -1335,6 +1347,33 @@ libsss_ipa_la_SOURCES += src/providers/ipa/ipa_hostid.c
 endif
 
 
+libsss_ad_la_SOURCES = \
+    src/providers/ad/ad_common.c \
+    src/providers/ad/ad_common.h \
+    src/providers/ad/ad_init.c \
+    src/providers/ad/ad_id.c \
+    src/providers/ad/ad_id.h \
+    src/providers/ad/ad_opts.h \
+    src/util/find_uid.c \
+    src/util/user_info_msg.c \
+    src/util/sss_krb5.c \
+    src/util/sss_ldap.c
+
+libsss_ad_la_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(LDAP_CFLAGS) \
+    $(DHASH_CFLAGS) \
+    $(KRB5_CFLAGS)
+libsss_ad_la_LIBADD = \
+    $(OPENLDAP_LIBS) \
+    $(DHASH_LIBS) \
+    $(KEYUTILS_LIBS) \
+    $(KRB5_LIBS) \
+    libsss_util.la \
+    libsss_ldap_common.la \
+    libsss_krb5_common.la \
+    libsss_idmap.la
+
 krb5_child_SOURCES = \
     src/providers/krb5/krb5_become_user.c \
     src/providers/krb5/krb5_child.c \
diff --git a/src/providers/ad/ad_common.c b/src/providers/ad/ad_common.c
new file mode 100644
index 0000000000000000000000000000000000000000..8c80faac69702ede96c13d008abe317dbe80f8f1
--- /dev/null
+++ b/src/providers/ad/ad_common.c
@@ -0,0 +1,607 @@
+/*
+    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 <ctype.h>
+
+#include "providers/ad/ad_common.h"
+#include "providers/ad/ad_opts.h"
+
+errno_t
+ad_get_common_options(TALLOC_CTX *mem_ctx,
+                      struct confdb_ctx *cdb,
+                      const char *conf_path,
+                      struct sss_domain_info *dom,
+                      struct ad_options **_opts)
+{
+    errno_t ret;
+    int gret;
+    size_t i;
+    struct ad_options *opts = NULL;
+    char *domain;
+    char *server;
+    char *realm;
+    char *ad_hostname;
+    char hostname[HOST_NAME_MAX + 1];
+
+    opts = talloc_zero(mem_ctx, struct ad_options);
+    if (!opts) return ENOMEM;
+
+    ret = dp_get_options(opts, cdb, conf_path,
+                         ad_basic_opts,
+                         AD_OPTS_BASIC,
+                         &opts->basic);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* If the AD domain name wasn't explicitly set, assume that it
+     * matches the SSSD domain name
+     */
+    domain = dp_opt_get_string(opts->basic, AD_DOMAIN);
+    if (!domain) {
+        ret = dp_opt_set_string(opts->basic, AD_DOMAIN, dom->name);
+        if (ret != EOK) {
+            goto done;
+        }
+        domain = dom->name;
+    }
+
+    /* Did we get an explicit server name, or are we discovering it? */
+    server = dp_opt_get_string(opts->basic, AD_SERVER);
+    if (!server) {
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              ("No AD server set, will use service discovery!\n"));
+    }
+
+    /* Set the machine's hostname to the local host name if it
+     * wasn't explicitly specified.
+     */
+    ad_hostname = dp_opt_get_string(opts->basic, AD_HOSTNAME);
+    if (ad_hostname == NULL) {
+        gret = gethostname(hostname, HOST_NAME_MAX);
+        if (gret != 0) {
+            ret = errno;
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  ("gethostname failed [%s].\n",
+                   strerror(ret)));
+            goto done;
+        }
+        hostname[HOST_NAME_MAX] = '\0';
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              ("Setting ad_hostname to [%s].\n", hostname));
+        ret = dp_opt_set_string(opts->basic, AD_HOSTNAME, hostname);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  ("Setting ad_hostname failed [%s].\n",
+                   strerror(ret)));
+            goto done;
+        }
+    }
+
+    /* Set the Kerberos realm explicitly or use the upper-case
+     * domain name
+     */
+    realm = dp_opt_get_string(opts->basic, AD_KRB5_REALM);
+    if (!realm) {
+        /* No explicit krb5_realm, use the AD domain */
+        realm = talloc_strdup(opts, domain);
+        if (!realm) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        /* Use the upper-case AD domain for the kerberos realm */
+        for (i = 0; realm[i]; i++) {
+            realm[i] = toupper(realm[i]);
+        }
+
+        ret = dp_opt_set_string(opts->basic, AD_KRB5_REALM,
+                                realm);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    ret = EOK;
+    *_opts = opts;
+
+done:
+    if (ret != EOK) {
+        talloc_zfree(opts);
+    }
+    return ret;
+}
+
+static void
+ad_resolve_callback(void *private_data, struct fo_server *server);
+
+errno_t
+ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx,
+                 const char *servers,
+                 struct ad_options *options,
+                 struct ad_service **_service)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct ad_service *service;
+    char *ad_domain;
+    char *realm;
+    char **list;
+    size_t i;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (!tmp_ctx) return ENOMEM;
+
+    service = talloc_zero(tmp_ctx, struct ad_service);
+    if (!service) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    service->sdap = talloc_zero(service, struct sdap_service);
+    if (!service->sdap) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    service->krb5_service = talloc_zero(service, struct krb5_service);
+    if (!service->krb5_service) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = be_fo_add_service(bectx, AD_SERVICE_NAME);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to create failover service!\n"));
+        goto done;
+    }
+
+    service->sdap->name = talloc_strdup(service, AD_SERVICE_NAME);
+    if (!service->sdap->name) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    service->krb5_service->name = talloc_strdup(service, AD_SERVICE_NAME);
+    if (!service->krb5_service->name) {
+        ret = ENOMEM;
+        goto done;
+    }
+    service->sdap->kinit_service_name = service->krb5_service->name;
+
+    realm = dp_opt_get_string(options->basic, AD_KRB5_REALM);
+    if (!realm) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("No Kerberos realm set\n"));
+        ret = EINVAL;
+        goto done;
+    }
+    service->krb5_service->realm =
+        talloc_strdup(service->krb5_service, realm);
+    if (!service->krb5_service->realm) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (!servers) {
+        servers = BE_SRV_IDENTIFIER;
+    }
+
+    /* Split the server list */
+    ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to parse server list!\n"));
+        goto done;
+    }
+
+    ad_domain = dp_opt_get_string(options->basic, AD_DOMAIN);
+
+    /* Add each of these servers to the failover service */
+    for (i = 0; list[i]; i++) {
+        if (be_fo_is_srv_identifier(list[i])) {
+            ret = be_fo_add_srv_server(bectx, AD_SERVICE_NAME, "ldap",
+                                       ad_domain, BE_FO_PROTO_TCP,
+                                       false, NULL);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_FATAL_FAILURE,
+                      ("Failed to add service discovery to failover: [%s]",
+                       strerror(ret)));
+                goto done;
+            }
+
+            DEBUG(SSSDBG_CONF_SETTINGS, ("Added service discovery for AD\n"));
+            continue;
+        }
+
+        ret = be_fo_add_server(bectx, AD_SERVICE_NAME, list[i], 0, NULL);
+        if (ret && ret != EEXIST) {
+            DEBUG(SSSDBG_FATAL_FAILURE, ("Failed to add server\n"));
+            goto done;
+        }
+
+        DEBUG(SSSDBG_CONF_SETTINGS, ("Added failover server %s\n", list[i]));
+    }
+
+    ret = be_fo_service_add_callback(mem_ctx, bectx, AD_SERVICE_NAME,
+                                     ad_resolve_callback, service);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Failed to add failover callback! [%s]\n", strerror(ret)));
+        goto done;
+    }
+
+    *_service = talloc_steal(mem_ctx, service);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static void
+ad_resolve_callback(void *private_data, struct fo_server *server)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct ad_service *service;
+    struct resolv_hostent *srvaddr;
+    struct sockaddr_storage *sockaddr;
+    char *address;
+    const char *safe_address;
+    char *new_uri;
+    const char *srv_name;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n"));
+        return;
+    }
+
+    service = talloc_get_type(private_data, struct ad_service);
+    if (!service) {
+        ret = EINVAL;
+        goto done;
+    }
+
+    srvaddr = fo_get_server_hostent(server);
+    if (!srvaddr) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("No hostent available for server (%s)\n",
+               fo_get_server_str_name(server)));
+        ret = EINVAL;
+        goto done;
+    }
+
+    sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, LDAP_PORT);
+    if (sockaddr == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("resolv_get_sockaddr_address failed.\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    address = resolv_get_string_address(tmp_ctx, srvaddr);
+    if (address == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("resolv_get_string_address failed.\n"));
+        ret = EIO;
+        goto done;
+    }
+
+    srv_name = fo_get_server_name(server);
+    if (srv_name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Could not get server host name\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    new_uri = talloc_asprintf(service, "ldap://%s", srv_name);
+    if (!new_uri) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to copy URI\n"));
+        ret = ENOMEM;
+        goto done;
+    }
+    DEBUG(SSSDBG_CONF_SETTINGS, ("Constructed uri '%s'\n", new_uri));
+
+    /* free old one and replace with new one */
+    talloc_zfree(service->sdap->uri);
+    service->sdap->uri = new_uri;
+    talloc_zfree(service->sdap->sockaddr);
+    service->sdap->sockaddr = talloc_steal(service, sockaddr);
+
+    safe_address = sss_escape_ip_address(tmp_ctx,
+                                         srvaddr->family,
+                                         address);
+    if (safe_address == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("sss_escape_ip_address failed.\n"));
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = write_krb5info_file(service->krb5_service->realm, safe_address,
+                              SSS_KRB5KDC_FO_SRV);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("write_krb5info_file failed, authentication might fail.\n"));
+    }
+
+    ret = EOK;
+done:
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Error: [%s]\n", strerror(ret)));
+    }
+    talloc_free(tmp_ctx);
+    return;
+}
+
+errno_t
+ad_set_search_bases(struct sdap_options *id_opts);
+
+errno_t
+ad_get_id_options(struct ad_options *ad_opts,
+                   struct confdb_ctx *cdb,
+                   const char *conf_path,
+                   struct sdap_options **_opts)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct sdap_options *id_opts;
+    char *krb5_realm;
+    char *sasl_primary;
+    char *desired_primary;
+    char *sasl_realm;
+    char *desired_realm;
+    char *keytab_path;
+    bool primary_requested = true;
+    bool realm_requested = true;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    id_opts = talloc_zero(tmp_ctx, struct sdap_options);
+    if (!id_opts) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = dp_get_options(id_opts, cdb, conf_path,
+                         ad_def_ldap_opts,
+                         SDAP_OPTS_BASIC,
+                         &id_opts->basic);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* Set up search bases if they were assigned explicitly */
+    ret = ad_set_search_bases(id_opts);
+    if (ret != EOK) goto done;
+
+    /* We only support Kerberos password policy with AD, so
+     * force that on.
+     */
+    ret = dp_opt_set_string(id_opts->basic,
+                            SDAP_PWD_POLICY,
+                            PWD_POL_OPT_MIT);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, ("Could not set password policy\n"));
+        goto done;
+    }
+
+    /* Set the Kerberos Realm for GSSAPI */
+    krb5_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM);
+    if (!krb5_realm) {
+        /* Should be impossible, this is set in ad_get_common_options() */
+        DEBUG(SSSDBG_FATAL_FAILURE, ("No Kerberos realm\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = dp_opt_set_string(id_opts->basic, SDAP_KRB5_REALM, krb5_realm);
+    if (ret != EOK) goto done;
+    DEBUG(SSSDBG_CONF_SETTINGS,
+          ("Option %s set to %s\n",
+           id_opts->basic[SDAP_KRB5_REALM].opt_name,
+           krb5_realm));
+
+    /* Configuration of SASL auth ID and realm */
+    desired_primary = dp_opt_get_string(id_opts->basic, SDAP_SASL_AUTHID);
+    if (!desired_primary) {
+        primary_requested = false;
+        desired_primary = dp_opt_get_string(ad_opts->basic, AD_HOSTNAME);
+    }
+
+    desired_realm = dp_opt_get_string(id_opts->basic, SDAP_SASL_REALM);
+    if (!desired_realm) {
+        realm_requested = false;
+        desired_realm = dp_opt_get_string(ad_opts->basic, AD_KRB5_REALM);
+    }
+
+    keytab_path = dp_opt_get_string(id_opts->basic, SDAP_KRB5_KEYTAB);
+    /* It's okay if this is NULL here */
+
+    ret = select_principal_from_keytab(tmp_ctx,
+                                       desired_primary, desired_realm,
+                                       keytab_path, NULL,
+                                       &sasl_primary, &sasl_realm);
+    if (ret != EOK) goto done;
+
+    if ((primary_requested && strcmp(desired_primary, sasl_primary) != 0) ||
+        (realm_requested && strcmp(desired_realm, sasl_realm) != 0)) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Configured SASL auth ID/realm not found in keytab.\n"));
+        ret = ENOENT;
+        goto done;
+    }
+
+    ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_AUTHID, sasl_primary);
+    if (ret != EOK) goto done;
+    DEBUG(SSSDBG_CONF_SETTINGS,
+          ("Option %s set to %s\n",
+           id_opts->basic[SDAP_SASL_AUTHID].opt_name,
+           sasl_primary));
+
+    ret = dp_opt_set_string(id_opts->basic, SDAP_SASL_REALM, sasl_realm);
+    if (ret != EOK) goto done;
+    DEBUG(SSSDBG_CONF_SETTINGS,
+          ("Option %s set to %s\n",
+           id_opts->basic[SDAP_SASL_REALM].opt_name,
+           sasl_realm));
+
+    /* fix schema to AD  */
+    id_opts->schema_type = SDAP_SCHEMA_AD;
+
+    /* Get sdap option maps */
+
+    /* General Attribute Map */
+    ret = sdap_get_map(id_opts,
+                       cdb, conf_path,
+                       ad_2008r2_attr_map,
+                       SDAP_AT_GENERAL,
+                       &id_opts->gen_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* User map */
+    ret = sdap_get_map(id_opts,
+                       cdb, conf_path,
+                       ad_2008r2_user_map,
+                       SDAP_OPTS_USER,
+                       &id_opts->user_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* Group map */
+    ret = sdap_get_map(id_opts,
+                       cdb, conf_path,
+                       ad_2008r2_group_map,
+                       SDAP_OPTS_GROUP,
+                       &id_opts->group_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* Netgroup map */
+    ret = sdap_get_map(id_opts,
+                       cdb, conf_path,
+                       ad_netgroup_map,
+                       SDAP_OPTS_NETGROUP,
+                       &id_opts->netgroup_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    /* Services map */
+    ret = sdap_get_map(id_opts,
+                       cdb, conf_path,
+                       ad_service_map,
+                       SDAP_OPTS_SERVICES,
+                       &id_opts->service_map);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ad_opts->id = talloc_steal(ad_opts, id_opts);
+    *_opts = id_opts;
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+ad_set_search_bases(struct sdap_options *id_opts)
+{
+    errno_t ret;
+    char *default_search_base;
+    size_t o;
+    const int search_base_options[] = { SDAP_USER_SEARCH_BASE,
+                                        SDAP_GROUP_SEARCH_BASE,
+                                        SDAP_NETGROUP_SEARCH_BASE,
+                                        SDAP_SERVICE_SEARCH_BASE,
+                                        -1 };
+
+    /* AD servers provide defaultNamingContext, so we will
+     * rely on that to specify the search base unless it has
+     * been specifically overridden.
+     */
+
+    default_search_base =
+            dp_opt_get_string(id_opts->basic, SDAP_SEARCH_BASE);
+
+    if (default_search_base) {
+        /* set search bases if they are not */
+        for (o = 0; search_base_options[o] != -1; o++) {
+            if (NULL == dp_opt_get_string(id_opts->basic,
+                                          search_base_options[o])) {
+                ret = dp_opt_set_string(id_opts->basic,
+                                        search_base_options[o],
+                                        default_search_base);
+                if (ret != EOK) {
+                    goto done;
+                }
+                DEBUG(SSSDBG_CONF_SETTINGS,
+                      ("Option %s set to %s\n",
+                       id_opts->basic[search_base_options[o]].opt_name,
+                       dp_opt_get_string(id_opts->basic,
+                                         search_base_options[o])));
+            }
+        }
+    } else {
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              ("Search base not set. SSSd will attempt to discover it later, "
+               "when connecting to the LDAP server.\n"));
+    }
+
+    /* Default search */
+    ret = sdap_parse_search_base(id_opts, id_opts->basic,
+                                 SDAP_SEARCH_BASE,
+                                 &id_opts->search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* User search */
+    ret = sdap_parse_search_base(id_opts, id_opts->basic,
+                                 SDAP_USER_SEARCH_BASE,
+                                 &id_opts->user_search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* Group search base */
+    ret = sdap_parse_search_base(id_opts, id_opts->basic,
+                                 SDAP_GROUP_SEARCH_BASE,
+                                 &id_opts->group_search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* Netgroup search */
+    ret = sdap_parse_search_base(id_opts, id_opts->basic,
+                                 SDAP_NETGROUP_SEARCH_BASE,
+                                 &id_opts->netgroup_search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    /* Service search */
+    ret = sdap_parse_search_base(id_opts, id_opts->basic,
+                                 SDAP_SERVICE_SEARCH_BASE,
+                                 &id_opts->service_search_bases);
+    if (ret != EOK && ret != ENOENT) goto done;
+
+    ret = EOK;
+done:
+    return ret;
+}
diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
new file mode 100644
index 0000000000000000000000000000000000000000..fefb67b60b1c70f67210fe3d192c94e9961029f5
--- /dev/null
+++ b/src/providers/ad/ad_common.h
@@ -0,0 +1,85 @@
+/*
+    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/>.
+*/
+
+#ifndef AD_COMMON_H_
+#define AD_COMMON_H_
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+
+#define AD_SERVICE_NAME "AD"
+
+struct ad_options;
+
+enum ad_basic_opt {
+    AD_DOMAIN = 0,
+    AD_SERVER,
+    AD_HOSTNAME,
+    AD_KRB5_REALM,
+
+    AD_OPTS_BASIC /* opts counter */
+};
+
+struct ad_id_ctx {
+    struct sdap_id_ctx *sdap_id_ctx;
+    struct ad_options *ad_options;
+};
+
+struct ad_service {
+    struct sdap_service *sdap;
+    struct krb5_service *krb5_service;
+};
+
+struct ad_options {
+    /* Common options */
+    struct dp_option *basic;
+    struct ad_service *service;
+
+    /* ID Provider */
+    struct sdap_options *id;
+    struct ad_id_ctx *id_ctx;
+
+    /* Auth and chpass Provider */
+    struct dp_option *auth;
+    struct ad_auth_ctx *auth_ctx;
+};
+
+errno_t
+ad_get_common_options(TALLOC_CTX *mem_ctx,
+                      struct confdb_ctx *cdb,
+                      const char *conf_path,
+                      struct sss_domain_info *dom,
+                      struct ad_options **_opts);
+
+errno_t
+ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *ctx,
+                 const char *servers,
+                 struct ad_options *options,
+                 struct ad_service **_service);
+
+errno_t
+ad_get_id_options(struct ad_options *ad_opts,
+                   struct confdb_ctx *cdb,
+                   const char *conf_path,
+                   struct sdap_options **_opts);
+
+#endif /* AD_COMMON_H_ */
diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c
new file mode 100644
index 0000000000000000000000000000000000000000..41510b2458aa857bca4f84eabbb08af92078b0f1
--- /dev/null
+++ b/src/providers/ad/ad_id.c
@@ -0,0 +1,37 @@
+/*
+    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 "providers/ad/ad_common.h"
+#include "providers/ad/ad_id.h"
+
+void
+ad_account_info_handler(struct be_req *breq)
+{
+    struct ad_id_ctx *ad_ctx;
+    struct sdap_id_ctx *sdap_id_ctx;
+
+    ad_ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+                             struct ad_id_ctx);
+    sdap_id_ctx = ad_ctx->sdap_id_ctx;
+
+    return sdap_handle_account_info(breq, sdap_id_ctx);
+}
diff --git a/src/providers/ad/ad_id.h b/src/providers/ad/ad_id.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f480aab76a5b2cd84b81c6f29685ba6f7dcf493
--- /dev/null
+++ b/src/providers/ad/ad_id.h
@@ -0,0 +1,29 @@
+/*
+    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/>.
+*/
+
+#ifndef AD_ID_H_
+#define AD_ID_H_
+
+void
+ad_account_info_handler(struct be_req *breq);
+
+#endif /* AD_ID_H_ */
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
new file mode 100644
index 0000000000000000000000000000000000000000..da659da254289b44b5d54f98ed79aa0b40e8fa7d
--- /dev/null
+++ b/src/providers/ad/ad_init.c
@@ -0,0 +1,184 @@
+/*
+    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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "util/util.h"
+#include "providers/ad/ad_common.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_idmap.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/ad/ad_id.h"
+
+struct ad_options *ad_options = NULL;
+
+static void
+ad_shutdown(struct be_req *req);
+
+struct bet_ops ad_id_ops = {
+    .handler = ad_account_info_handler,
+    .finalize = ad_shutdown,
+    .check_online = sdap_check_online
+};
+
+struct bet_ops ad_auth_ops = {
+    .handler = krb5_pam_handler,
+    .finalize = NULL
+};
+
+struct bet_ops ad_chpass_ops = {
+    .handler = krb5_pam_handler,
+    .finalize = NULL
+};
+
+static errno_t
+common_ad_init(struct be_ctx *bectx)
+{
+    errno_t ret;
+    char *ad_servers = NULL;
+
+    /* Get AD-specific options */
+    ret = ad_get_common_options(bectx, bectx->cdb,
+                                bectx->conf_path,
+                                bectx->domain,
+                                &ad_options);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Could not parse common options: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER);
+
+    /* Set up the failover service */
+    ret = ad_failover_init(ad_options, bectx, ad_servers, ad_options,
+                           &ad_options->service);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Failed to init AD failover service: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    return ret;
+}
+
+int
+sssm_ad_id_init(struct be_ctx *bectx,
+                struct bet_ops **ops,
+                void **pvt_data)
+{
+    errno_t ret;
+    struct ad_id_ctx *ad_ctx;
+    struct sdap_id_ctx *sdap_ctx;
+
+    if (!ad_options) {
+        ret = common_ad_init(bectx);
+        if (ret != EOK) {
+            return ret;
+        }
+    }
+
+    if (ad_options->id_ctx) {
+        /* already initialized */
+        *ops = &ad_id_ops;
+        *pvt_data = ad_options->id_ctx;
+        return EOK;
+    }
+
+    ad_ctx = talloc_zero(ad_options, struct ad_id_ctx);
+    if (!ad_options) {
+        return ENOMEM;
+    }
+    ad_ctx->ad_options = ad_options;
+    ad_options->id_ctx = ad_ctx;
+
+    sdap_ctx = talloc_zero(ad_options, struct sdap_id_ctx);
+    if (!sdap_ctx) {
+        return ENOMEM;
+    }
+    sdap_ctx->be = bectx;
+    sdap_ctx->service = ad_options->service->sdap;
+    ad_ctx->sdap_id_ctx = sdap_ctx;
+
+    ret = ad_get_id_options(ad_options, bectx->cdb,
+                            bectx->conf_path,
+                            &sdap_ctx->opts);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = setup_tls_config(sdap_ctx->opts->basic);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("setup_tls_config failed [%s]\n", strerror(ret)));
+        goto done;
+    }
+
+    ret = sdap_id_conn_cache_create(sdap_ctx, sdap_ctx, &sdap_ctx->conn_cache);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (dp_opt_get_bool(sdap_ctx->opts->basic, SDAP_ID_MAPPING)) {
+        /* Set up the ID mapping object */
+        ret = sdap_idmap_init(sdap_ctx, sdap_ctx, &sdap_ctx->opts->idmap_ctx);
+        if (ret != EOK) goto done;
+    }
+
+    ret = sdap_id_setup_tasks(sdap_ctx);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = setup_child(sdap_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("setup_child failed [%d][%s].\n",
+               ret, strerror(ret)));
+        goto done;
+    }
+
+    *ops = &ad_id_ops;
+    *pvt_data = ad_ctx;
+
+    ret = EOK;
+done:
+    if (ret != EOK) {
+        talloc_zfree(ad_options->id_ctx);
+    }
+    return ret;
+}
+
+static void
+ad_shutdown(struct be_req *req)
+{
+    /* TODO: Clean up any internal data */
+    sdap_handler_done(req, DP_ERR_OK, EOK, NULL);
+}
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
new file mode 100644
index 0000000000000000000000000000000000000000..a4dbea01b352d8e76500165e80d8ff67c4c78e77
--- /dev/null
+++ b/src/providers/ad/ad_opts.h
@@ -0,0 +1,229 @@
+/*
+    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/>.
+*/
+
+#ifndef AD_OPTS_H_
+#define AD_OPTS_H_
+
+#include "src/providers/data_provider.h"
+#include "db/sysdb_services.h"
+#include "db/sysdb_autofs.h"
+
+struct dp_option ad_basic_opts[] = {
+    { "ad_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ad_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ad_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING}
+};
+
+struct dp_option ad_def_ldap_opts[] = {
+    { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_default_authtok_type", DP_OPT_STRING, { "password" }, NULL_STRING},
+    { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB },
+    { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+    { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+    { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+    { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING },
+    { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+    { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "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_full_refresh_interval", DP_OPT_NUMBER, { .number = 21600 }, NULL_NUMBER }, /* 360 mins */
+    { "ldap_sudo_smart_refresh_interval", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER }, /* 15 mins */
+    { "ldap_sudo_use_host_filter", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "ldap_sudo_hostnames", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sudo_ip", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sudo_include_netgroups", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "ldap_sudo_include_regexp", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "ldap_autofs_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_schema", DP_OPT_STRING, { "ad" }, NULL_STRING },
+    { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+    { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE },
+    { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
+    { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 10800 }, NULL_NUMBER },
+    { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_tls_cert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_tls_key", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_tls_cipher_suite", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_id_mapping", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE },
+    { "ldap_sasl_mech", DP_OPT_STRING, { "gssapi" }, NULL_STRING },
+    { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sasl_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sasl_minssf", DP_OPT_NUMBER, { .number = -1 }, NULL_NUMBER },
+    { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    /* use the same parm name as the krb5 module so we set it only once */
+    { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_TRUE },
+    { "ldap_pwd_policy", DP_OPT_STRING, { "none" }, NULL_STRING },
+    { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER },
+    { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING },
+    { "ldap_krb5_ticket_lifetime", DP_OPT_NUMBER, { .number = (24 * 60 * 60) }, NULL_NUMBER },
+    { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER },
+    { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_account_expire_policy", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_access_order", DP_OPT_STRING, { "filter" }, NULL_STRING },
+    { "ldap_chpass_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_chpass_dns_service_name", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_chpass_update_last_change", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_enumeration_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+    /* Do not include ldap_auth_disable_tls_never_use_in_production in the
+     * manpages or SSSDConfig API
+     */
+    { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER },
+    { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER },
+    { "ldap_sasl_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_connection_expire_timeout", DP_OPT_NUMBER, { .number = 900 }, NULL_NUMBER },
+    { "ldap_disable_paging", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_idmap_range_min", DP_OPT_NUMBER, { .number = 100001 }, NULL_NUMBER },
+    { "ldap_idmap_range_max", DP_OPT_NUMBER, { .number = 2000100000LL }, NULL_NUMBER },
+    { "ldap_idmap_range_size", DP_OPT_NUMBER, { .number = 200000 }, NULL_NUMBER },
+    { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_groups_use_matching_rule_in_chain", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_initgroups_use_matching_rule_in_chain", DP_OPT_BOOL, BOOL_TRUE, BOOL_FALSE },
+    DP_OPTION_TERMINATOR
+};
+
+struct dp_option ad_def_krb5_opts[] = {
+    { "krb5_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_ccachedir", DP_OPT_STRING, { DEFAULT_CCACHE_DIR }, NULL_STRING },
+    { "krb5_ccname_template", DP_OPT_STRING, { DEFAULT_CCNAME_TEMPLATE }, NULL_STRING},
+    { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER },
+    { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING },
+    { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_renew_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER },
+    { "krb5_use_fast", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_fast_principal", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+struct sdap_attr_map ad_2008r2_attr_map[] = {
+    { "ldap_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL },
+    { "ldap_rootdse_last_usn", SDAP_AD_LAST_USN, SYSDB_HIGH_USN, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_2008r2_user_map[] = {
+    { "ldap_user_object_class", "user", SYSDB_USER_CLASS, NULL },
+    { "ldap_user_name", "sAMAccountName", SYSDB_NAME, NULL },
+    { "ldap_user_pwd", "unixUserPassword", SYSDB_PWD, NULL },
+    { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+    { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+    { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+    { "ldap_user_home_directory", "unixHomeDirectory", SYSDB_HOMEDIR, NULL },
+    { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+    { "ldap_user_principal", "userPrincipalName", SYSDB_UPN, NULL },
+    { "ldap_user_fullname", "name", SYSDB_FULLNAME, NULL },
+    { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL },
+    { "ldap_user_uuid", "objectGUID", SYSDB_UUID, NULL },
+    { "ldap_user_objectsid", "objectSID", SYSDB_SID, NULL },
+    { "ldap_user_primary_group", "primaryGroupID", SYSDB_PRIMARY_GROUP, NULL },
+    { "ldap_user_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL },
+    { "ldap_user_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL },
+    { "ldap_user_shadow_last_change", NULL, SYSDB_SHADOWPW_LASTCHANGE, NULL },
+    { "ldap_user_shadow_min", NULL, SYSDB_SHADOWPW_MIN, NULL },
+    { "ldap_user_shadow_max", NULL, SYSDB_SHADOWPW_MAX, NULL },
+    { "ldap_user_shadow_warning", NULL, SYSDB_SHADOWPW_WARNING, NULL },
+    { "ldap_user_shadow_inactive", NULL, SYSDB_SHADOWPW_INACTIVE, NULL },
+    { "ldap_user_shadow_expire", NULL, SYSDB_SHADOWPW_EXPIRE, NULL },
+    { "ldap_user_shadow_flag", NULL, SYSDB_SHADOWPW_FLAG, NULL },
+    { "ldap_user_krb_last_pwd_change", NULL, SYSDB_KRBPW_LASTCHANGE, NULL },
+    { "ldap_user_krb_password_expiration", NULL, SYSDB_KRBPW_EXPIRATION, NULL },
+    { "ldap_pwd_attribute", NULL, SYSDB_PWD_ATTRIBUTE, NULL },
+    { "ldap_user_authorized_service", NULL, SYSDB_AUTHORIZED_SERVICE, NULL },
+    { "ldap_user_ad_account_expires", "accountExpires", SYSDB_AD_ACCOUNT_EXPIRES, NULL},
+    { "ldap_user_ad_user_account_control", "userAccountControl", SYSDB_AD_USER_ACCOUNT_CONTROL, NULL},
+    { "ldap_ns_account_lock", NULL, SYSDB_NS_ACCOUNT_LOCK, NULL},
+    { "ldap_user_authorized_host", NULL, SYSDB_AUTHORIZED_HOST, NULL },
+    { "ldap_user_nds_login_disabled", NULL, SYSDB_NDS_LOGIN_DISABLED, NULL },
+    { "ldap_user_nds_login_expiration_time", NULL, SYSDB_NDS_LOGIN_EXPIRATION_TIME, NULL },
+    { "ldap_user_nds_login_allowed_time_map", NULL, SYSDB_NDS_LOGIN_ALLOWED_TIME_MAP, NULL },
+    { "ldap_user_ssh_public_key", NULL, SYSDB_SSH_PUBKEY, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_2008r2_group_map[] = {
+    { "ldap_group_object_class", "group", SYSDB_GROUP_CLASS, NULL },
+    { "ldap_group_name", "name", SYSDB_NAME, NULL },
+    { "ldap_group_pwd", NULL, SYSDB_PWD, NULL },
+    { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+    { "ldap_group_member", "member", SYSDB_MEMBER, NULL },
+    { "ldap_group_uuid", "objectGUID", SYSDB_UUID, NULL },
+    { "ldap_group_objectsid", "objectSID", SYSDB_SID, NULL },
+    { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL },
+    { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_netgroup_map[] = {
+    { "ldap_netgroup_object_class", "nisNetgroup", SYSDB_NETGROUP_CLASS, NULL },
+    { "ldap_netgroup_name", "cn", SYSDB_NAME, NULL },
+    { "ldap_netgroup_member", "memberNisNetgroup", SYSDB_ORIG_NETGROUP_MEMBER, NULL },
+    { "ldap_netgroup_triple", "nisNetgroupTriple", SYSDB_NETGROUP_TRIPLE, NULL },
+    /* FIXME: this is 389ds specific */
+    { "ldap_netgroup_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+    { "ldap_netgroup_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_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 },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_autofs_mobject_map[] = {
+    { "ldap_autofs_map_object_class", "nisMap", SYSDB_AUTOFS_MAP_OC, NULL },
+    { "ldap_autofs_map_name", "nisMapName", SYSDB_AUTOFS_MAP_NAME, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+struct sdap_attr_map ad_autofs_entry_map[] = {
+    { "ldap_autofs_entry_object_class", "nisObject", SYSDB_AUTOFS_ENTRY_OC, NULL },
+    { "ldap_autofs_entry_key", "cn", SYSDB_AUTOFS_ENTRY_KEY, NULL },
+    { "ldap_autofs_entry_value", "nisMapEntry", SYSDB_AUTOFS_ENTRY_VALUE, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
+#endif /* AD_OPTS_H_ */
diff --git a/src/tests/ad_ldap_opt-tests.c b/src/tests/ad_ldap_opt-tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..e9ce9d02bd10eb9c8abb8dd3f33b2853e699be22
--- /dev/null
+++ b/src/tests/ad_ldap_opt-tests.c
@@ -0,0 +1,109 @@
+/*
+   SSSD
+
+   Tests if AD and LDAP backend options are in sync
+
+   Authors:
+       Jakub Hrozek <jhrozek@redhat.com>
+       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 <check.h>
+#include <stdlib.h>
+#include <talloc.h>
+
+#include "providers/ad/ad_common.h"
+#include "providers/ad/ad_opts.h"
+#include "providers/ldap/sdap.h"
+#include "providers/ldap/ldap_opts.h"
+#include "providers/krb5/krb5_opts.h"
+#include "providers/krb5/krb5_common.h"
+#include "tests/common.h"
+
+START_TEST(test_compare_opts)
+{
+    errno_t ret;
+
+    ret = compare_dp_options(default_basic_opts, SDAP_OPTS_BASIC,
+                             ad_def_ldap_opts);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    ret = compare_dp_options(default_krb5_opts, KRB5_OPTS,
+                             ad_def_krb5_opts);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+}
+END_TEST
+
+START_TEST(test_compare_sdap_attrs)
+{
+    errno_t ret;
+
+    /* General Attributes */
+    ret = compare_sdap_attr_maps(generic_attr_map, SDAP_AT_GENERAL,
+                                 ad_2008r2_attr_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    /* User Attributes */
+    ret = compare_sdap_attr_maps(rfc2307_user_map, SDAP_OPTS_USER,
+                                 ad_2008r2_user_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    /* Group Attributes */
+    ret = compare_sdap_attr_maps(rfc2307_group_map, SDAP_OPTS_GROUP,
+                                 ad_2008r2_group_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    /* Netgroup Attributes */
+    ret = compare_sdap_attr_maps(netgroup_map, SDAP_OPTS_NETGROUP,
+                                 ad_netgroup_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    /* Service Attributes */
+    ret = compare_sdap_attr_maps(service_map, SDAP_OPTS_SERVICES,
+                                 ad_service_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+}
+END_TEST
+
+Suite *ad_ldap_opt_suite (void)
+{
+    Suite *s = suite_create ("ad_ldap_opt");
+
+    TCase *tc_ad_ldap_opt = tcase_create ("ad_ldap_opt");
+
+    tcase_add_test (tc_ad_ldap_opt, test_compare_opts);
+    tcase_add_test (tc_ad_ldap_opt, test_compare_sdap_attrs);
+    suite_add_tcase (s, tc_ad_ldap_opt);
+
+    return s;
+}
+
+int main(void)
+{
+    int number_failed;
+
+    tests_set_cwd();
+
+    Suite *s = ad_ldap_opt_suite ();
+    SRunner *sr = srunner_create (s);
+    /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+    srunner_run_all(sr, CK_ENV);
+    number_failed = srunner_ntests_failed (sr);
+    srunner_free (sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
-- 
1.7.10.2

