From 393f0d1169c23da607de583cd65dfb36e7d86759 Mon Sep 17 00:00:00 2001
From: Pierre Ossman <pierre@ossman.eu>
Date: Wed, 22 Dec 2010 22:29:03 +0100
Subject: [PATCH] Add host access control support

https://fedorahosted.org/sssd/ticket/746
---
 src/config/SSSDConfig.py                 |    1 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |    1 +
 src/db/sysdb.h                           |    1 +
 src/man/sssd-ldap.5.xml                  |   24 +++++
 src/providers/ipa/ipa_common.c           |    3 +-
 src/providers/ldap/ldap_common.c         |    6 +-
 src/providers/ldap/ldap_init.c           |    2 +
 src/providers/ldap/sdap.h                |    1 +
 src/providers/ldap/sdap_access.c         |  146 ++++++++++++++++++++++++++++++
 src/providers/ldap/sdap_access.h         |    2 +
 10 files changed, 184 insertions(+), 3 deletions(-)

diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index 9c61f06f56dbf8101605582babb85298f30eb802..0e91a2cce3d4c7d2eef1ada62917c66616a88b97 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -174,6 +174,7 @@ option_strings = {
     'ldap_user_shadow_expire' : _('shadowExpire attribute'),
     'ldap_user_shadow_flag' : _('shadowFlag attribute'),
     'ldap_user_authorized_service' : _('Attribute listing authorized PAM services'),
+    'ldap_user_host' : _('Attribute listing authorized server hosts'),
     'ldap_user_krb_last_pwd_change' : _('krbLastPwdChange attribute'),
     'ldap_user_krb_password_expiration' : _('krbPasswordExpiration attribute'),
     'ldap_pwd_attribute' : _('Attribute indicating that server side password policies are active'),
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index 0e1b2ca55daedc3cda4c9279649b23cf6e47a601..88fbbae8176a3f7aaf406276407fe23279a1e267 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -60,6 +60,7 @@ ldap_user_shadow_flag = str, None, false
 ldap_user_krb_last_pwd_change = str, None, false
 ldap_user_krb_password_expiration = str, None, false
 ldap_user_authorized_service = str, None, false
+ldap_user_host = str, None, false
 ldap_pwd_attribute = str, None, false
 ldap_user_ad_account_expires = str, None, false
 ldap_user_ad_user_account_control = str, None, false
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index ae0b33ce1efcec4a895bd89633d27d4cc3c19182..8937e6e175716a39c6e105f99bad88aa08338388 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -79,6 +79,7 @@
 #define SYSDB_INITGR_EXPIRE "initgrExpireTimestamp"
 
 #define SYSDB_AUTHORIZED_SERVICE "authorizedService"
+#define SYSDB_HOST "host"
 
 #define SYSDB_NETGROUP_TRIPLE "netgroupTriple"
 #define SYSDB_ORIG_NETGROUP_MEMBER "originalMemberNisNetgroup"
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index 58665eb4e1325cdcebc24965486991ddd1762413..234523546bd439e30bbc83cd26019edc4bf29cb7 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -587,6 +587,26 @@
                 </varlistentry>
 
                 <varlistentry>
+                    <term>ldap_user_host (string)</term>
+                    <listitem>
+                        <para>
+                            If access_provider=ldap and
+                            ldap_access_order=host, SSSD will use the presence
+                            of the host attribute in the user's LDAP entry to
+                            determine access privilege.
+                        </para>
+                        <para>
+                            An explicit deny (!host) is resolved first. Second,
+                            SSSD searches for explicit allow (host) and finally
+                            for allow_all (*).
+                        </para>
+                        <para>
+                            Default: host
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
                     <term>ldap_group_object_class (string)</term>
                     <listitem>
                         <para>
@@ -1249,6 +1269,10 @@ ldap_access_filter = memberOf=cn=allowedusers,ou=Groups,dc=example,dc=com
                             access
                         </para>
                         <para>
+                            <emphasis>host</emphasis>: use the host attribute
+                            to determine access
+                        </para>
+                        <para>
                             Default: filter
                         </para>
                         <para>
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 579b8b60e8df2f57ab17014561fa61ce933a4ead..470784d5a5af15840b4475df9065f10c9f4b8ba1 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -127,7 +127,8 @@ struct sdap_attr_map ipa_user_map[] = {
     { "ldap_user_authorized_service", "authorizedService", 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", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}
+    { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL},
+    { "ldap_user_host", "host", SYSDB_HOST, NULL }
 };
 
 struct sdap_attr_map ipa_group_map[] = {
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index ce6d41d5872a6dd474aff4bab8e2920688abda92..b1a1d5253c1419371c758f061ecc08967faec582 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -131,7 +131,8 @@ struct sdap_attr_map rfc2307_user_map[] = {
     { "ldap_user_authorized_service", "authorizedService", 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", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}
+    { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL},
+    { "ldap_user_host", "host", SYSDB_HOST, NULL }
 };
 
 struct sdap_attr_map rfc2307_group_map[] = {
@@ -174,7 +175,8 @@ struct sdap_attr_map rfc2307bis_user_map[] = {
     { "ldap_user_authorized_service", "authorizedService", 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", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL}
+    { "ldap_ns_account_lock", "nsAccountLock", SYSDB_NS_ACCOUNT_LOCK, NULL},
+    { "ldap_user_host", "host", SYSDB_HOST, NULL }
 };
 
 struct sdap_attr_map rfc2307bis_group_map[] = {
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index 6b05690edb1a9d6492007b2328b2367d61f0ad58..62588b77602ae057c1fd6c06c6f049b620a18887 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -358,6 +358,8 @@ int sssm_ldap_access_init(struct be_ctx *bectx,
             }
         } else if (strcasecmp(order_list[c], LDAP_ACCESS_SERVICE_NAME) == 0) {
             access_ctx->access_rule[c] = LDAP_ACCESS_SERVICE;
+        } else if (strcasecmp(order_list[c], LDAP_ACCESS_HOST_NAME) == 0) {
+            access_ctx->access_rule[c] = LDAP_ACCESS_HOST;
         } else {
             DEBUG(1, ("Unexpected access rule name [%s].\n", order_list[c]));
             ret = EINVAL;
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 31e72cd5b8326204ca6c8ffa718490c574c997e2..ce3271a6f814025ae1950824e14034a45cc78e5f 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -243,6 +243,7 @@ enum sdap_user_attrs {
     SDAP_AT_AD_ACCOUNT_EXPIRES,
     SDAP_AT_AD_USER_ACCOUNT_CONTROL,
     SDAP_AT_NS_ACCOUNT_LOCK,
+    SDAP_AT_HOST,
 
     SDAP_OPTS_USER /* attrs counter */
 };
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
index 5a6b4a5d3191b96298edafe1898095917ba11621..5879c1dd8281c5231dfb3955263ae66eda40f07b 100644
--- a/src/providers/ldap/sdap_access.c
+++ b/src/providers/ldap/sdap_access.c
@@ -85,6 +85,17 @@ static struct tevent_req *sdap_access_service_send(
 
 static void sdap_account_expired_done(struct tevent_req *subreq);
 
+static errno_t sdap_access_host_recv(struct tevent_req *req,
+                                        int *pam_status);
+static void sdap_access_host_done(struct tevent_req *subreq);
+
+static struct tevent_req *sdap_access_host_send(
+        TALLOC_CTX *mem_ctx,
+        struct tevent_context *ev,
+        struct sdap_access_ctx *access_ctx,
+        struct pam_data *pd,
+        struct ldb_message *user_entry);
+
 static void sdap_access_done(struct tevent_req *req);
 void sdap_pam_access_handler(struct be_req *breq)
 {
@@ -259,6 +270,18 @@ static errno_t select_next_rule(struct tevent_req *req)
             tevent_req_set_callback(subreq, sdap_access_service_done, req);
             return EOK;
 
+        case LDAP_ACCESS_HOST:
+            subreq = sdap_access_host_send(state, state->ev,
+                                              state->access_ctx,
+                                              state->pd,
+                                              state->user_entry);
+            if (subreq == NULL) {
+                DEBUG(1, ("sdap_access_host_send failed.\n"));
+                return ENOMEM;
+            }
+            tevent_req_set_callback(subreq, sdap_access_host_done, req);
+            return EOK;
+
         default:
             DEBUG(1, ("Unexpected access rule type. Access denied.\n"));
     }
@@ -1057,6 +1080,129 @@ static void sdap_access_service_done(struct tevent_req *subreq)
     return;
 }
 
+struct sdap_access_host_ctx {
+    int pam_status;
+};
+
+static struct tevent_req *sdap_access_host_send(
+        TALLOC_CTX *mem_ctx,
+        struct tevent_context *ev,
+        struct sdap_access_ctx *access_ctx,
+        struct pam_data *pd,
+        struct ldb_message *user_entry)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sdap_access_host_ctx *state;
+    struct ldb_message_element *el;
+    unsigned int i;
+    char *host;
+    char hostname[HOST_NAME_MAX+1];
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_access_host_ctx);
+    if (!req) {
+        return NULL;
+    }
+
+    state->pam_status = PAM_PERM_DENIED;
+
+    el = ldb_msg_find_element(user_entry, SYSDB_HOST);
+    if (!el || el->num_values == 0) {
+        DEBUG(1, ("Missing hosts. Access denied\n"));
+        ret = EOK;
+        goto done;
+    }
+
+    if (gethostname(hostname, sizeof(hostname)) == -1) {
+        DEBUG(1, ("Unable to get system hostname. Access denied\n"));
+        ret = EOK;
+        goto done;
+    }
+
+    /* FIXME: PADL's pam_ldap also calls gethostbyname() on the hostname
+     *        in some attempt to get aliases and/or FQDN for the machine.
+     *        Not sure this is a good idea, but we might want to add it in
+     *        order to be compatible...
+     */
+
+    for (i = 0; i < el->num_values; i++) {
+        host = (char *)el->values[i].data;
+        if (host[0] == '!' &&
+                strcasecmp(hostname, host+1) == 0) {
+            /* This host is explicitly denied */
+            state->pam_status = PAM_PERM_DENIED;
+            DEBUG(4, ("Access denied by [%s]\n", host));
+            /* A denial trumps all. Break here */
+            break;
+
+        } else if (strcasecmp(hostname, host) == 0) {
+            /* This host is explicitly allowed */
+            state->pam_status = PAM_SUCCESS;
+            DEBUG(4, ("Access granted for [%s]\n", host));
+            /* We still need to loop through to make sure
+             * that it's not also explicitly denied
+             */
+        } else if (strcmp("*", host) == 0) {
+            /* This user has access to all hosts */
+            state->pam_status = PAM_SUCCESS;
+            DEBUG(4, ("Access granted to all hosts\n"));
+            /* We still need to loop through to make sure
+             * that it's not also explicitly denied
+             */
+        }
+    }
+
+    if (state->pam_status != PAM_SUCCESS) {
+        DEBUG(4, ("No matching host rule found\n"));
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+
+    return req;
+}
+
+static errno_t sdap_access_host_recv(struct tevent_req *req,
+                                        int *pam_status)
+{
+    struct sdap_access_host_ctx *state =
+            tevent_req_data(req, struct sdap_access_host_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *pam_status = state->pam_status;
+
+    return EOK;
+}
+
+static void sdap_access_host_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sdap_access_host_recv(subreq, &state->pam_status);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(1, ("Error retrieving access check result.\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    next_access_rule(req);
+
+    return;
+}
+
 static errno_t sdap_access_recv(struct tevent_req *req, int *pam_status)
 {
     struct sdap_access_req_ctx *state =
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
index 32c45b8176ceb0dd8ae7307d944f2e92cb887942..d78fdb1eddc96deba6c3e5ff0674524fd64efd72 100644
--- a/src/providers/ldap/sdap_access.h
+++ b/src/providers/ldap/sdap_access.h
@@ -32,6 +32,7 @@
 #define LDAP_ACCESS_FILTER_NAME "filter"
 #define LDAP_ACCESS_EXPIRE_NAME "expire"
 #define LDAP_ACCESS_SERVICE_NAME "authorized_service"
+#define LDAP_ACCESS_HOST_NAME "host"
 
 #define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow"
 #define LDAP_ACCOUNT_EXPIRE_AD "ad"
@@ -44,6 +45,7 @@ enum ldap_access_rule {
     LDAP_ACCESS_FILTER = 0,
     LDAP_ACCESS_EXPIRE,
     LDAP_ACCESS_SERVICE,
+    LDAP_ACCESS_HOST,
     LDAP_ACCESS_LAST
 };
 
-- 
1.7.3.1

