/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* plugins/kdb/ldap/libkdb_ldap/ldap_tkt_policy.c */
/*
 * Copyright (c) 2004-2005, Novell, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *   * The copyright holder's name is not used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "ldap_main.h"
#include "kdb_ldap.h"
#include "ldap_tkt_policy.h"
#include "ldap_err.h"

/* Ticket policy object management */

static void
free_list(char **list)
{
    int i;

    for (i = 0; list != NULL && list[i] != NULL; i++)
        free(list[i]);
    free(list);
}

/*
 * create the Ticket policy object in Directory.
 */
krb5_error_code
krb5_ldap_create_policy(krb5_context context, krb5_ldap_policy_params *policy,
                        int mask)
{
    krb5_error_code             st=0;
    LDAP                        *ld=NULL;
    char                        *strval[3]={NULL}, *policy_dn = NULL;
    LDAPMod                     **mods=NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    /* validate the input parameters */
    if (policy == NULL || policy->policy == NULL) {
        st = EINVAL;
        k5_setmsg(context, st, _("Ticket Policy Name missing"));
        goto cleanup;
    }

    SETUP_CONTEXT();
    GET_HANDLE();

    if ((st = krb5_ldap_name_to_policydn (context, policy->policy, &policy_dn)) != 0)
        goto cleanup;

    memset(strval, 0, sizeof(strval));
    strval[0] = policy->policy;
    if ((st=krb5_add_str_mem_ldap_mod(&mods, "cn", LDAP_MOD_ADD, strval)) != 0)
        goto cleanup;

    memset(strval, 0, sizeof(strval));
    strval[0] = "krbTicketPolicy";
    strval[1] = "krbTicketPolicyaux";
    if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0)
        goto cleanup;

    if (mask & LDAP_POLICY_MAXTKTLIFE) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxticketlife", LDAP_MOD_ADD,
                                          policy->maxtktlife)) != 0)
            goto cleanup;
    }

    if (mask & LDAP_POLICY_MAXRENEWLIFE) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxrenewableage", LDAP_MOD_ADD,
                                          policy->maxrenewlife)) != 0)
            goto cleanup;
    }

    if (mask & LDAP_POLICY_TKTFLAGS) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbticketflags", LDAP_MOD_ADD,
                                          policy->tktflags)) != 0)
            goto cleanup;
    }

    /* ldap add operation */
    if ((st=ldap_add_ext_s(ld, policy_dn, mods, NULL, NULL)) != LDAP_SUCCESS) {
        st = set_ldap_error (context, st, OP_ADD);
        goto cleanup;
    }

cleanup:
    if (policy_dn != NULL)
        free(policy_dn);

    ldap_mods_free(mods, 1);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return st;
}


/*
 * modify the Ticket policy object in Directory.
 */

krb5_error_code
krb5_ldap_modify_policy(krb5_context context, krb5_ldap_policy_params *policy,
                        int mask)
{
    int                         objectmask=0;
    krb5_error_code             st=0;
    LDAP                        *ld=NULL;
    char                        *attrvalues[]={"krbTicketPolicy", "krbTicketPolicyAux", NULL}, *strval[2]={NULL};
    char                        *policy_dn = NULL;
    LDAPMod                     **mods=NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    /* validate the input parameters */
    if (policy == NULL || policy->policy==NULL) {
        st = EINVAL;
        k5_setmsg(context, st, _("Ticket Policy Name missing"));
        goto cleanup;
    }

    SETUP_CONTEXT();
    GET_HANDLE();

    if ((st = krb5_ldap_name_to_policydn (context, policy->policy, &policy_dn)) != 0)
        goto cleanup;

    /* the policydn object should be of the krbTicketPolicy object class */
    st = checkattributevalue(ld, policy_dn, "objectClass", attrvalues, &objectmask);
    CHECK_CLASS_VALIDITY(st, objectmask, _("ticket policy object: "));

    if ((objectmask & 0x02) == 0) { /* add krbticketpolicyaux to the object class list */
        memset(strval, 0, sizeof(strval));
        strval[0] = "krbTicketPolicyAux";
        if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0)
            goto cleanup;
    }

    if (mask & LDAP_POLICY_MAXTKTLIFE) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxticketlife", LDAP_MOD_REPLACE,
                                          policy->maxtktlife)) != 0)
            goto cleanup;
    }

    if (mask & LDAP_POLICY_MAXRENEWLIFE) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxrenewableage", LDAP_MOD_REPLACE,
                                          policy->maxrenewlife)) != 0)
            goto cleanup;
    }

    if (mask & LDAP_POLICY_TKTFLAGS) {
        if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbticketflags", LDAP_MOD_REPLACE,
                                          policy->tktflags)) != 0)
            goto cleanup;
    }

    if ((st=ldap_modify_ext_s(ld, policy_dn, mods, NULL, NULL)) != LDAP_SUCCESS) {
        st = set_ldap_error (context, st, OP_MOD);
        goto cleanup;
    }

cleanup:
    if (policy_dn != NULL)
        free(policy_dn);

    ldap_mods_free(mods, 1);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return st;
}


/*
 * Read the policy object from the Directory and populate the krb5_ldap_policy_params
 * structure.
 */

krb5_error_code
krb5_ldap_read_policy(krb5_context context, char *policyname,
                      krb5_ldap_policy_params **policy, int *omask)
{
    krb5_error_code             st=0, tempst=0;
    int                         objectmask=0, val=0;
    LDAP                        *ld=NULL;
    LDAPMessage                 *result=NULL,*ent=NULL;
    char                        *attributes[] = { "krbMaxTicketLife", "krbMaxRenewableAge", "krbTicketFlags", NULL};
    char                        *attrvalues[] = { "krbTicketPolicy", NULL}, *policy_dn = NULL;
    krb5_ldap_policy_params     *lpolicy=NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    /* validate the input parameters */
    if (policyname == NULL  || policy == NULL) {
        st = EINVAL;
        k5_setmsg(context, st, _("Ticket Policy Object information missing"));
        goto cleanup;
    }

    SETUP_CONTEXT();
    GET_HANDLE();

    if ((st = krb5_ldap_name_to_policydn (context, policyname, &policy_dn)) != 0)
        goto cleanup;

    /* the policydn object should be of the krbTicketPolicy object class */
    st = checkattributevalue(ld, policy_dn, "objectClass", attrvalues, &objectmask);
    CHECK_CLASS_VALIDITY(st, objectmask, _("ticket policy object: "));

    /* Initialize ticket policy structure */
    lpolicy =(krb5_ldap_policy_params *) malloc(sizeof(krb5_ldap_policy_params));
    CHECK_NULL(lpolicy);
    memset(lpolicy, 0, sizeof(krb5_ldap_policy_params));

    if ((lpolicy->policy = strdup (policyname)) == NULL) {
        st = ENOMEM;
        goto cleanup;
    }

    lpolicy->tl_data = calloc (1, sizeof(*lpolicy->tl_data));
    CHECK_NULL(lpolicy->tl_data);
    lpolicy->tl_data->tl_data_type = KDB_TL_USER_INFO;

    LDAP_SEARCH(policy_dn, LDAP_SCOPE_BASE, "(objectclass=krbTicketPolicy)", attributes);

    *omask = 0;

    ent=ldap_first_entry(ld, result);
    if (ent != NULL) {
        if (krb5_ldap_get_value(ld, ent, "krbmaxticketlife", &val) == 0) {
            lpolicy->maxtktlife = val;
            *omask |= LDAP_POLICY_MAXTKTLIFE;
        }
        if (krb5_ldap_get_value(ld, ent, "krbmaxrenewableage", &val) == 0) {
            lpolicy->maxrenewlife = val;
            *omask |= LDAP_POLICY_MAXRENEWLIFE;
        }
        if (krb5_ldap_get_value(ld, ent, "krbticketflags", &val) == 0) {
            lpolicy->tktflags = val;
            *omask |= LDAP_POLICY_TKTFLAGS;
        }
    }

    lpolicy->mask = *omask;
    store_tl_data(lpolicy->tl_data, KDB_TL_MASK, omask);
    *policy = lpolicy;

cleanup:
    if (st != 0) {
        krb5_ldap_free_policy(context, lpolicy);
        *policy = NULL;
    }
    free(policy_dn);
    ldap_msgfree(result);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return st;
}


/*
 * Function to delete ticket policy object from the directory.  Before
 * calling this function krb5_ldap_read_policy should be called to
 * check the existence of the object.  This serves one major purpose,
 * i.e., if the object to be is anything other than the ticket policy
 * object then the krb5_ldap_read_policy returns an error and thus is
 * not accidentally deleted in this function.
 *
 * NOTE: Other kerberos objects (user/realm object) might be having
 * references to the policy object to be deleted. This situation is
 * not handled here, instead is taken care of at all the places where
 * the deleted policy object is read, to ignore a return status of
 * LDAP_NO_SUCH_OBJECT and continue.
 */

krb5_error_code
krb5_ldap_delete_policy(krb5_context context, char *policyname)
{
    int                         refcount = 0;
    char                        *policy_dn = NULL;
    krb5_error_code             st = 0;
    LDAP                        *ld = NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    if (policyname == NULL) {
        st = EINVAL;
        k5_prependmsg(context, st, _("Ticket Policy Object DN missing"));
        goto cleanup;
    }


    SETUP_CONTEXT();
    GET_HANDLE();

    if ((st = krb5_ldap_name_to_policydn (context, policyname, &policy_dn)) != 0)
        goto cleanup;

    /* Checking for policy count for 0 and will not permit delete if
     * it is greater than 0.  */

    if ((st = krb5_ldap_get_reference_count (context, policy_dn,
                                             "krbTicketPolicyReference", &refcount, ld)) != 0)
        goto cleanup;

    if (refcount == 0) {
        if ((st=ldap_delete_ext_s(ld, policy_dn, NULL, NULL)) != 0) {
            k5_prependmsg(context, st, "%s", ldap_err2string(st));

            goto cleanup;
        }
    } else {
        st = EINVAL;
        k5_prependmsg(context, st,
                      _("Delete Failed: One or more Principals associated "
                        "with the Ticket Policy"));
        goto cleanup;
    }

cleanup:
    if (policy_dn != NULL)
        free (policy_dn);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return st;
}


/*
 * list policy objects from Directory
 */

krb5_error_code
krb5_ldap_list_policy(krb5_context context, char *containerdn, char ***policy)
{
    int                         i, j, count;
    char                        **list = NULL;
    char                        *policycontainerdn = containerdn;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_error_code             st=0;

    SETUP_CONTEXT();
    if (policycontainerdn == NULL) {
        policycontainerdn = ldap_context->lrparams->realmdn;
    }

    if ((st = krb5_ldap_list(context, &list, "krbTicketPolicy", policycontainerdn)) != 0)
        goto cleanup;

    for (i = 0; list[i] != NULL; i++);

    count = i;

    *policy = (char **) calloc ((unsigned) count + 1, sizeof(char *));
    if (*policy == NULL) {
        st = ENOMEM;
        goto cleanup;
    }

    for (i = 0, j = 0; list[i] != NULL; i++, j++) {
        int ret;
        ret = krb5_ldap_policydn_to_name (context, list[i], &(*policy)[i]);
        if (ret != 0)
            j--;
    }

cleanup:
    free_list(list);
    return st;
}

/*
 * Function to free the ticket policy object structure.
 * Note: this function assumes that memory of the policy structure is dynamically allocated and hence the whole
 * structure is freed up. Care should be taken not to call this function on a static structure
 */

krb5_error_code
krb5_ldap_free_policy(krb5_context context, krb5_ldap_policy_params *policy)
{

    krb5_error_code st=0;

    if (policy == NULL)
        return st;

    if (policy->policy)
        free (policy->policy);

    if (policy->tl_data) {
        if (policy->tl_data->tl_data_contents)
            free (policy->tl_data->tl_data_contents);
        free (policy->tl_data);
    }
    free (policy);

    return st;
}

/*
 * This function is general object listing routine.  It is currently
 * used for ticket policy object listing.
 */

krb5_error_code
krb5_ldap_list(krb5_context context, char ***list, char *objectclass,
               char *containerdn)
{
    char                        *filter=NULL, *dn=NULL;
    krb5_error_code             st=0, tempst=0;
    int                         count=0, filterlen=0;
    LDAP                        *ld=NULL;
    LDAPMessage                 *result=NULL,*ent=NULL;
    kdb5_dal_handle             *dal_handle=NULL;
    krb5_ldap_context           *ldap_context=NULL;
    krb5_ldap_server_handle     *ldap_server_handle=NULL;

    SETUP_CONTEXT();
    GET_HANDLE();

    /* check if the containerdn exists */
    if (containerdn) {
        if ((st=checkattributevalue(ld, containerdn, NULL, NULL, NULL)) != 0) {
            k5_prependmsg(context, st, _("Error reading container object"));
            goto cleanup;
        }
    }

    /* set the filter for the search operation */
    filterlen = strlen("(objectclass=") + strlen(objectclass) + 1 + 1;
    filter = malloc ((unsigned) filterlen);
    if (filter == NULL) {
        st = ENOMEM;
        goto cleanup;
    }
    snprintf(filter, (unsigned) filterlen,"(objectclass=%s)",objectclass);

    LDAP_SEARCH(containerdn, LDAP_SCOPE_SUBTREE, filter, NULL);

    count = ldap_count_entries(ld, result);
    if (count == -1) {
        ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &st);
        st = set_ldap_error(context, st, OP_SEARCH);
        goto cleanup;
    }
    *list = (char **) calloc ((unsigned) count+1, sizeof(char *));
    if (*list == NULL) {
        st = ENOMEM;
        goto cleanup;
    }

    for (ent=ldap_first_entry(ld, result), count=0; ent != NULL; ent=ldap_next_entry(ld, ent), ++count) {
        if ((dn=ldap_get_dn(ld, ent)) == NULL)
            continue;
        if (((*list)[count] = strdup(dn)) == NULL) {
            ldap_memfree (dn);
            st = ENOMEM;
            goto cleanup;
        }
        ldap_memfree(dn);
    }

cleanup:
    if (filter)
        free (filter);

    /* some error, free up all the memory */
    if (st != 0) {
        free_list(*list);
        *list = NULL;
    }
    ldap_msgfree(result);
    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
    return st;
}
