/******************************************************************************
Copyright (C) 2009 Hewlett-Packard Development Company, L.P.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Contributors:
Hewlett-Packard Development Company, L.P.

Copyright (C) 2021 Red Hat, Inc.
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include "acctpolicy.h"
#include "slapi-plugin.h"

/* Globals */
static void *global_plugin_id = NULL;
/* attributes that no clients are allowed to add or modify */
static char *protected_attrs_login_recording[] = {"createTimestamp",
                                                  NULL};


/*
   Checks whether an entry has a particular attribute type, and optionally
   returns the value.  Only for use with single-valued attributes - it returns
   the first value it finds.
*/
int
has_attr(Slapi_Entry *target_entry, char *attr_name, char **val)
{
    Slapi_ValueSet *values = NULL;
    Slapi_Value *sval;
    char *actual_type_name = NULL;
    int type_name_disposition = 0, attr_free_flags = 0, rc = 0;

    /* Use vattr interface to support virtual attributes, e.g.
       acctPolicySubentry has a good chance of being supplied by CoS */
    if (slapi_vattr_values_get(target_entry, attr_name, &values, &type_name_disposition, &actual_type_name, 0, &attr_free_flags) == 0) {
        if (slapi_valueset_first_value(values, &sval) == -1) {
            rc = 0;
        } else {
            rc = 1;
            if (val) {
                /* Caller wants a copy of the found attribute's value */
                *val = slapi_ch_strdup(slapi_value_get_string(sval));
            }
        }
    } else {
        rc = 0;
    }

    slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
    return (rc);
}

/*
  Lazy wrapper for has_attr()
*/
char *
get_attr_string_val(Slapi_Entry *target_entry, char *attr_name)
{
    char *ret = NULL;
    has_attr(target_entry, attr_name, &ret);
    return (ret);
}

/*
  Given an entry, provide the account policy in effect for that entry.
  Returns non-0 if function fails.  If account policy comes back NULL, it's
  not an error; the entry is simply not covered by a policy.
*/
int
get_acctpolicy(Slapi_PBlock *pb __attribute__((unused)), Slapi_Entry *target_entry, void *plugin_id, acctPolicy **policy)
{
    Slapi_PBlock *entry_pb = NULL;
    Slapi_DN *sdn = NULL;
    Slapi_Entry *policy_entry = NULL;
    Slapi_Attr *attr;
    Slapi_Value *sval = NULL;
    int ldrc;
    char *attr_name;
    char *policy_dn = NULL;
    acctPluginCfg *cfg;
    int rc = 0;

    if (policy == NULL) {
        /* Bad parameter */
        return (-1);
    }

    *policy = NULL;

    config_rd_lock();
    cfg = get_config();
    /* Return success and NULL policy */
    policy_dn = get_attr_string_val(target_entry, cfg->spec_attr_name);
    if (policy_dn == NULL) {
        slapi_log_err(SLAPI_LOG_PLUGIN, PLUGIN_NAME,
                      "get_acctpolicy - \"%s\" is not governed by an account inactivity "
                      "policy subentry\n",
                      slapi_entry_get_ndn(target_entry));
        if (cfg->inactivitylimit != ULONG_MAX) {
            goto dopolicy;
        }
        slapi_log_err(SLAPI_LOG_PLUGIN, PLUGIN_NAME,
                      "get_acctpolicy - \"%s\" is not governed by an account inactivity "
                      "global policy\n",
                      slapi_entry_get_ndn(target_entry));
        config_unlock();
        return rc;
    }

    sdn = slapi_sdn_new_dn_byref(policy_dn);
    ldrc = slapi_search_get_entry(&entry_pb, sdn, NULL, &policy_entry, plugin_id);
    slapi_sdn_free(&sdn);

    /* There should be a policy but it can't be retrieved; fatal error */
    if (policy_entry == NULL) {
        if (ldrc != LDAP_NO_SUCH_OBJECT) {
            slapi_log_err(SLAPI_LOG_ERR, PLUGIN_NAME,
                          "get_acctpolicy - Error retrieving policy entry \"%s\": %d\n", policy_dn, ldrc);
        } else {
            slapi_log_err(SLAPI_LOG_PLUGIN, PLUGIN_NAME,
                          "get_acctpolicy - Policy entry \"%s\" is missing: %d\n", policy_dn, ldrc);
        }
        rc = -1;
        goto done;
    }

dopolicy:
    *policy = (acctPolicy *)slapi_ch_calloc(1, sizeof(acctPolicy));

    if (!policy_entry) { /* global policy */
        (*policy)->inactivitylimit = cfg->inactivitylimit;
        goto done;
    }

    for (slapi_entry_first_attr(policy_entry, &attr); attr != NULL;
         slapi_entry_next_attr(policy_entry, attr, &attr)) {
        slapi_attr_get_type(attr, &attr_name);
        if (!strcasecmp(attr_name, cfg->limit_attr_name)) {
            if (slapi_attr_first_value(attr, &sval) == 0) {
                (*policy)->inactivitylimit = slapi_value_get_ulong(sval);
            }
        }
    }
done:
    config_unlock();
    slapi_ch_free_string(&policy_dn);
    slapi_search_get_entry_done(&entry_pb);
    return (rc);
}

/*
  Frees an account policy allocated by get_acctpolicy()
*/
void
free_acctpolicy(acctPolicy **policy)
{
    slapi_ch_free((void **)policy);
    return;
}

/*
  Plugin plumbing
*/
void
set_identity(void *identity)
{
    global_plugin_id = identity;
}

/*
  Plugin plumbing
*/
void *
get_identity()
{
    return (global_plugin_id);
}

/*
  A more flexible atoi(), converts to integer and returns the characters
  between (src+offset) and (src+offset+len). No support for negative numbers,
  which doesn't affect our time parsing.
*/
int
antoi(char *src, int offset, int len)
{
    int pow = 1, res = 0;

    if (len < 0) {
        return (-1);
    }
    while (--len != -1) {
        if (!isdigit(src[offset + len])) {
            res = -1;
            break;
        } else {
            res += (src[offset + len] - '0') * pow;
            pow *= 10;
        }
    }
    return (res);
}

/*
  Converts generalized time to UNIX GMT time.  For example:
  "20060807211257Z" -> 1154981577
*/
time_t
gentimeToEpochtime(char *gentimestr)
{
    time_t epochtime, cur_local_epochtime, cur_gm_epochtime, zone_offset;
    struct tm t, *cur_gm_time;

    /* Find the local offset from GMT */
    cur_gm_time = (struct tm *)slapi_ch_calloc(1, sizeof(struct tm));
    cur_local_epochtime = slapi_current_utc_time();
    gmtime_r(&cur_local_epochtime, cur_gm_time);
    cur_gm_epochtime = mktime(cur_gm_time);
    free(cur_gm_time);
    zone_offset = cur_gm_epochtime - cur_local_epochtime;

    /* Parse generalizedtime string into a tm struct */
    t.tm_year = antoi(gentimestr, 0, 4) - 1900;
    t.tm_mon = antoi(gentimestr, 4, 2) - 1;
    t.tm_mday = antoi(gentimestr, 6, 2);
    t.tm_hour = antoi(gentimestr, 8, 2);
    t.tm_min = antoi(gentimestr, 10, 2);
    t.tm_sec = antoi(gentimestr, 12, 2);
    t.tm_isdst = 0; /* DST does not apply to UTC */

    /* Turn tm object into local epoch time */
    epochtime = mktime(&t);

    /* Turn local epoch time into GMT epoch time */
    epochtime -= zone_offset;

    return (epochtime);
}

/*
  Converts UNIX time to generalized time.  For example:
  1154981577 -> "20060807211257Z"
*/
char *
epochtimeToGentime(time_t epochtime)
{
    char *gentimestr;
    struct tm t;

    gmtime_r(&epochtime, &t);
    gentimestr = slapi_ch_malloc(SLAPI_TIMESTAMP_BUFSIZE);
    /* Format is YYYYmmddHHMMSSZ (15+1 chars) */
    strftime(gentimestr, SLAPI_TIMESTAMP_BUFSIZE, "%Y%m%d%H%M%SZ", &t);

    return (gentimestr);
}

int
update_is_allowed_attr(const char *attr)
{
    int i;

    /* check list of attributes that cannot be used for login recording */
    for (i = 0; protected_attrs_login_recording[i]; i++) {
        if (strcasecmp(attr, protected_attrs_login_recording[i]) == 0) {
            /* this attribute is not allowed */
            return 0;
        }
    }
    return 1;
}
