/*
    SSSD

    SELinux-related utility functions

    Authors:
        Jan Zeleny <jzeleny@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/sss_selinux.h"
#include "util/sss_utf8.h"
#include "db/sysdb_selinux.h"

static bool match_entity(struct ldb_message_element *values,
                         struct ldb_message_element *sought_values)
{
    int i, j;

    for (i = 0; i < values->num_values; i++) {
        for (j = 0; j < sought_values->num_values; j++) {
            if (values->values[i].length != sought_values->values[j].length) {
                continue;
            }

            if (strncasecmp((char *)values->values[i].data,
                            (char *)sought_values->values[j].data,
                            values->values[i].length) == 0)
                return true;
        }
    }

    return false;
}

bool sss_selinux_match(struct sysdb_attrs *usermap,
                       struct sysdb_attrs *user,
                       struct sysdb_attrs *host)
{
    struct ldb_message_element *users_el = NULL;
    struct ldb_message_element *usercat = NULL;
    struct ldb_message_element *hosts_el = NULL;
    struct ldb_message_element *hostcat = NULL;
    struct ldb_message_element *dn;
    struct ldb_message_element *memberof;
    int i;
    errno_t ret;

    if (usermap == NULL) {
        DEBUG(SSSDBG_MINOR_FAILURE, ("NULL given as usermap! Skipping ...\n"));
        return false;
    }

    /* Search for user and host related elements */
    for (i = 0; i < usermap->num; i++) {
        if (!strcasecmp(usermap->a[i].name, SYSDB_ORIG_MEMBER_USER)) {
            users_el = &usermap->a[i];
        } else if (!strcasecmp(usermap->a[i].name, SYSDB_ORIG_MEMBER_HOST)) {
            hosts_el = &usermap->a[i];
        } else if (!strcasecmp(usermap->a[i].name, SYSDB_USER_CATEGORY)) {
            usercat = &usermap->a[i];
        } else if (!strcasecmp(usermap->a[i].name, SYSDB_HOST_CATEGORY)) {
            hostcat = &usermap->a[i];
        }
    }

    if (user) {
        ret = sysdb_attrs_get_el(user, SYSDB_ORIG_DN, &dn);
        if (ret != EOK) return false;
        ret = sysdb_attrs_get_el(user, SYSDB_ORIG_MEMBEROF, &memberof);
        if (ret != EOK) return false;

        /**
         * The rule won't match if user category != "all" and user map doesn't
         * contain neither user nor any of his groups in memberUser attribute
         */
        if (usercat == NULL || usercat->num_values == 0 ||
            strcasecmp((char *)usercat->values[0].data, "all") != 0) {
            if (users_el == NULL || (!match_entity(users_el, dn) &&
                !match_entity(users_el, memberof))) {
                return false;
            }
        }
    }

    if (host) {
        ret = sysdb_attrs_get_el(host, SYSDB_ORIG_DN, &dn);
        if (ret != EOK) return false;
        ret = sysdb_attrs_get_el(host, SYSDB_ORIG_MEMBEROF, &memberof);
        if (ret != EOK) return false;

        /**
         * The rule won't match if host category != "all" and user map doesn't
         * contain neither host nor any of its groups in memberHost attribute
         */
        if (hostcat == NULL || hostcat->num_values == 0 ||
            strcasecmp((char *)hostcat->values[0].data, "all") != 0) {
            if (hosts_el == NULL || (!match_entity(hosts_el, dn) &&
                !match_entity(hosts_el, memberof))) {
                return false;
            }
        }
    }

    return true;
}

errno_t sss_selinux_extract_user(TALLOC_CTX *mem_ctx,
                                 struct sysdb_ctx *sysdb,
                                 const char *username,
                                 struct sysdb_attrs **_user_attrs)
{
    TALLOC_CTX *tmp_ctx;
    const char **attrs;
    struct sysdb_attrs *user_attrs;
    struct ldb_message *user_msg;

    errno_t ret;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        ret = ENOMEM;
        goto done;
    }

    attrs = talloc_array(tmp_ctx, const char *, 3);
    if (attrs == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_array failed.\n"));
        ret = ENOMEM;
        goto done;
    }
    attrs[0] = SYSDB_ORIG_DN;
    attrs[1] = SYSDB_ORIG_MEMBEROF;
    attrs[2] = NULL;

    ret = sysdb_search_user_by_name(tmp_ctx, sysdb, username,
                                    attrs, &user_msg);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, ("sysdb_search_user_by_name failed.\n"));
        goto done;
    }

    user_attrs = talloc_zero(tmp_ctx, struct sysdb_attrs);
    if (user_attrs == NULL) {
        ret = ENOMEM;
        goto done;
    }
    user_attrs->a = talloc_steal(user_attrs, user_msg->elements);
    user_attrs->num = user_msg->num_elements;

    *_user_attrs = talloc_steal(mem_ctx, user_attrs);
    ret = EOK;

done:
    talloc_free(tmp_ctx);
    return ret;
}

const char *sss_selinux_map_get_seuser(struct ldb_message *usermap)
{
    int i;
    const uint8_t *name;
    const uint8_t *template = (const uint8_t *)SYSDB_SELINUX_USER;

    for (i = 0; i < usermap->num_elements; i++) {
        name = (const uint8_t *)usermap->elements[i].name;
        if (sss_utf8_case_eq(name, template) == 0) {
            return (const char *)usermap->elements[i].values[0].data;
        }
    }

    return NULL;
}