/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/* pw_retry.c
*/

#include <time.h>
#include "slap.h"

/****************************************************************************/
/* prototypes                                                               */
/****************************************************************************/
/* Slapi_Entry *get_entry ( Slapi_PBlock *pb, const char *dn ); */
static int set_retry_cnt ( Slapi_PBlock *pb, int count);
static int set_retry_cnt_and_time ( Slapi_PBlock *pb, int count, time_t cur_time);

/*
 * update_pw_retry() is called when bind operation fails 
 * with LDAP_INVALID_CREDENTIALS (in backend bind.c ). 
 * It checks to see if the retry count can be reset,
 * increments retry count, and then check if need to lock the acount. 
 * To have a global password policy, these mods should be chained to the
 * master, and not applied locally. If they are applied locally, they should
 * not get replicated from master... 
 */

int update_pw_retry ( Slapi_PBlock *pb )
{
    Slapi_Entry           *e;
	int             retry_cnt=0; 
	time_t          reset_time; 
	time_t          cur_time;
	char            *cur_time_str = NULL;
	char *retryCountResetTime;
	int passwordRetryCount;
	int rc = 0;

    /* get the entry */
    e = get_entry ( pb, NULL );
	if ( e == NULL ) {
		return ( 1 );
	}

    cur_time = current_time();

    /* check if the retry count can be reset. */
	retryCountResetTime= slapi_entry_attr_get_charptr(e, "retryCountResetTime");
	if(retryCountResetTime!=NULL)
	{
        reset_time = parse_genTime (retryCountResetTime);
		slapi_ch_free((void **) &retryCountResetTime );

		cur_time_str = format_genTime ( cur_time );
        if ( difftime ( parse_genTime( cur_time_str ), reset_time) >= 0 )
        {
            /* set passwordRetryCount to 1 */
            /* reset retryCountResetTime */
			rc = set_retry_cnt_and_time ( pb, 1, cur_time );
			slapi_ch_free((void **) &cur_time_str );
			slapi_entry_free( e );
            return ( rc ); /* success */
        } else {
			slapi_ch_free((void **) &cur_time_str );
		}
    } else {
		/* initialize passwordRetryCount and retryCountResetTime */
		rc = set_retry_cnt_and_time ( pb, 1, cur_time );
		slapi_entry_free( e );
        return ( rc ); /* success */
	}
	passwordRetryCount = slapi_entry_attr_get_int(e, "passwordRetryCount"); 
    if (passwordRetryCount >= 0)
	{
        retry_cnt = passwordRetryCount + 1;
   		if ( retry_cnt == 1 ) {
        	/* set retryCountResetTime */
        	rc = set_retry_cnt_and_time ( pb, retry_cnt, cur_time );
		} else {
			/* set passwordRetryCount to retry_cnt */
			rc = set_retry_cnt ( pb, retry_cnt );
		}
    }	
	slapi_entry_free( e );
	return rc; /* success */
}

static
int set_retry_cnt_and_time ( Slapi_PBlock *pb, int count, time_t cur_time ) {
	const char  *dn = NULL;
	Slapi_DN    *sdn = NULL;
	Slapi_Mods	smods;
	time_t      reset_time;
	char		*timestr;
	passwdPolicy *pwpolicy = NULL;
	int rc = 0;

	slapi_pblock_get( pb, SLAPI_TARGET_SDN, &sdn );
	dn = slapi_sdn_get_dn(sdn);
	pwpolicy = new_passwdPolicy(pb, dn);
	slapi_mods_init(&smods, 0);

	reset_time = time_plus_sec ( cur_time, 
		pwpolicy->pw_resetfailurecount );
	
	timestr = format_genTime ( reset_time );
	slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "retryCountResetTime", timestr);
	slapi_ch_free((void **)&timestr);

	rc = set_retry_cnt_mods(pb, &smods, count);
	
	pw_apply_mods(sdn, &smods);
	slapi_mods_done(&smods);

	return rc;
}

int set_retry_cnt_mods(Slapi_PBlock *pb, Slapi_Mods *smods, int count)
{
	char 		*timestr;
	time_t		unlock_time;
	char        retry_cnt[8]; /* 1-65535 */
	const char *dn = NULL; 
	Slapi_DN *sdn = NULL; 
	passwdPolicy *pwpolicy = NULL;
	int rc = 0;

	slapi_pblock_get( pb, SLAPI_TARGET_SDN, &sdn );
	dn = slapi_sdn_get_dn(sdn);
	pwpolicy = new_passwdPolicy(pb, dn);

	if (smods) {
		sprintf ( retry_cnt, "%d", count );
		slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "passwordRetryCount", retry_cnt);
		/* lock account if reache retry limit */
		if ( count >=  pwpolicy->pw_maxfailure ) {
			/* Remove lock_account function to perform all mods at once */
			/* lock_account ( pb ); */
			/* reach the retry limit, lock the account  */
			if ( pwpolicy->pw_unlock == 0 ) {
				/* lock until admin reset password */
				unlock_time = NO_TIME;
			} else {
				unlock_time = time_plus_sec ( current_time(),
											  pwpolicy->pw_lockduration );
			}
			timestr= format_genTime ( unlock_time );
			slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "accountUnlockTime", timestr);
			slapi_ch_free((void **)&timestr);
			rc = LDAP_CONSTRAINT_VIOLATION;
		}
	}
	return rc;
}

static
int set_retry_cnt ( Slapi_PBlock *pb, int count)
{
	Slapi_DN *sdn = NULL; 
	Slapi_Mods	smods;
	int rc = 0;
	
	slapi_pblock_get( pb, SLAPI_TARGET_SDN, &sdn );
	slapi_mods_init(&smods, 0);
	rc = set_retry_cnt_mods(pb, &smods, count);
	pw_apply_mods(sdn, &smods);
	slapi_mods_done(&smods);
	return rc;
}


/* 
 * If "dn" is passed, get_entry returns an entry which dn is "dn".
 * If "dn" is not passed, it returns an entry which dn is set in 
 * SLAPI_TARGET_SDN in pblock.
 * Note: pblock is not mandatory for get_entry (e.g., new_passwdPolicy).
 */
Slapi_Entry *get_entry ( Slapi_PBlock *pb, const char *dn)
{
	int             search_result = 0;
	Slapi_Entry     *retentry = NULL;
	Slapi_DN        *target_sdn = NULL;
	const char      *target_dn = dn;
	Slapi_DN        sdn;

	if (pb) {
		slapi_pblock_get( pb, SLAPI_TARGET_SDN, &target_sdn );
		if (target_dn == NULL) {
			target_dn = slapi_sdn_get_dn(target_sdn);
		}
	}

	if (target_dn == NULL) {
		LDAPDebug0Args(LDAP_DEBUG_TRACE,
		               "WARNING: 'get_entry' - no dn specified.\n");
		goto bail;
	}

	if (target_dn == dn) { /* target_dn is NOT from target_sdn */
		slapi_sdn_init_dn_byref(&sdn, target_dn);
		target_sdn = &sdn;
	}

	search_result = slapi_search_internal_get_entry(target_sdn, NULL,
	                                                &retentry, 
	                                                pw_get_componentID());
	if (search_result != LDAP_SUCCESS) {
		LDAPDebug2Args(LDAP_DEBUG_TRACE,
		               "WARNING: 'get_entry' can't find entry '%s', err %d\n",
		               target_dn, search_result);
	}
	if (target_dn == dn) { /* target_dn is NOT from target_sdn */
		slapi_sdn_done(&sdn);
	}
bail:
	return retentry;
}

void
pw_apply_mods(const Slapi_DN *sdn, Slapi_Mods *mods) 
{
	Slapi_PBlock pb;
	int res;
	
	if (mods && (slapi_mods_get_num_mods(mods) > 0)) 
	{
		pblock_init(&pb);
		/* We don't want to overwrite the modifiersname, etc. attributes,
		 * so we set a flag for this operation */
		slapi_modify_internal_set_pb_ext (&pb, sdn, 
					  slapi_mods_get_ldapmods_byref(mods),
					  NULL, /* Controls */
					  NULL, /* UniqueID */
					  pw_get_componentID(), /* PluginID */
					  OP_FLAG_SKIP_MODIFIED_ATTRS); /* Flags */
		slapi_modify_internal_pb (&pb);
		
		slapi_pblock_get(&pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
		if (res != LDAP_SUCCESS){
			LDAPDebug2Args(LDAP_DEBUG_ANY,
			        "WARNING: passwordPolicy modify error %d on entry '%s'\n",
					res, slapi_sdn_get_dn(sdn));
		}
		
		pblock_done(&pb);
	}
	
	return;
}

/* Handle the component ID for the password policy */

static struct slapi_componentid * pw_componentid = NULL;

void pw_set_componentID(struct slapi_componentid *cid)
{
	pw_componentid = cid;
}

struct slapi_componentid * pw_get_componentID()
{
	return pw_componentid;
}
