/*
    SSSD

    Authors:
        Stephen Gallagher <sgallagh@redhat.com>

    Copyright (C) 2011 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 "src/responder/pam/pam_helpers.h"

struct pam_initgr_table_ctx {
    hash_table_t *id_table;
    char *name;
};

static void pam_initgr_cache_remove(struct tevent_context *ev,
                                    struct tevent_timer *te,
                                    struct timeval tv,
                                    void *pvt);

errno_t pam_initgr_cache_set(struct tevent_context *ev,
                             hash_table_t *id_table,
                             char *name,
                             long timeout)
{
    errno_t ret;
    hash_key_t key;
    hash_value_t val;
    int hret;
    struct tevent_timer *te;
    struct timeval tv;
    struct pam_initgr_table_ctx *table_ctx;

    ret = pam_initgr_check_timeout(id_table, name);
    if (ret == EOK) {
        /* user is already in the cache */
        goto done;
    }

    table_ctx = talloc_zero(id_table, struct pam_initgr_table_ctx);
    if (!table_ctx) return ENOMEM;

    table_ctx->id_table = id_table;
    table_ctx->name = talloc_strdup(table_ctx, name);
    if (!table_ctx->name) {
        ret = ENOMEM;
        goto done;
    }

    key.type = HASH_KEY_STRING;
    key.str = name;

    /* The value isn't relevant, since we're using
     * a timer to remove the entry.
     */
    val.type = HASH_VALUE_UNDEF;

    hret = hash_enter(id_table, &key, &val);
    if (hret != HASH_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "Could not update initgr cache for [%s]: [%s]\n",
               name, hash_error_string(hret));
        ret = EIO;
        goto done;
    } else {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "[%s] added to PAM initgroup cache\n",
               name);
    }

    /* Create a timer event to remove the entry from the cache */
    tv = tevent_timeval_current_ofs(timeout, 0);
    te = tevent_add_timer(ev, table_ctx, tv,
                          pam_initgr_cache_remove,
                          table_ctx);
    if (!te) {
        ret = ENOMEM;
        goto done;
    }

    ret = EOK;

done:
    if (ret != EOK) {
        talloc_free(table_ctx);
    }
    return ret;
}

static void pam_initgr_cache_remove(struct tevent_context *ev,
                                    struct tevent_timer *te,
                                    struct timeval tv,
                                    void *pvt)
{
    int hret;
    hash_key_t key;

    struct pam_initgr_table_ctx *table_ctx =
            talloc_get_type(pvt, struct pam_initgr_table_ctx);

    key.type = HASH_KEY_STRING;
    key.str = table_ctx->name;

    hret = hash_delete(table_ctx->id_table, &key);
    if (hret != HASH_SUCCESS
            && hret != HASH_ERROR_KEY_NOT_FOUND) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "Could not clear [%s] from initgr cache: [%s]\n",
               table_ctx->name,
               hash_error_string(hret));
    } else {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "[%s] removed from PAM initgroup cache\n",
               table_ctx->name);
    }

    talloc_free(table_ctx);
}

errno_t pam_initgr_check_timeout(hash_table_t *id_table,
                                 char *name)
{
    hash_key_t key;
    hash_value_t val;
    int hret;

    key.type = HASH_KEY_STRING;
    key.str = name;

    hret = hash_lookup(id_table, &key, &val);
    if (hret != HASH_SUCCESS
            && hret != HASH_ERROR_KEY_NOT_FOUND) {
            DEBUG(SSSDBG_TRACE_ALL, "Error searching user [%s] in PAM cache.\n",
                                    name);
        return EIO;
    } else if (hret == HASH_ERROR_KEY_NOT_FOUND) {
        DEBUG(SSSDBG_TRACE_ALL, "User [%s] not found in PAM cache.\n", name);
        return ENOENT;
    }

    /* If there's a value here, then the cache
     * entry is still valid.
     */
    DEBUG(SSSDBG_TRACE_INTERNAL, "User [%s] found in PAM cache.\n", name);
    return EOK;
}
