/* shadow.c - shadow account lookup routines */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>. 
 *
 * Copyright 2008-2021 The OpenLDAP Foundation.
 * Portions Copyright 2008 by Howard Chu, Symas Corp.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* ACKNOWLEDGEMENTS:
 * This code references portions of the nss-ldapd package
 * written by Arthur de Jong. The nss-ldapd code was forked
 * from the nss-ldap library written by Luke Howard.
 */

#include "nssov.h"

/* ( nisSchema.2.1 NAME 'shadowAccount' SUP top AUXILIARY
 *	 DESC 'Additional attributes for shadow passwords'
 *	 MUST uid
 *	 MAY ( userPassword $ shadowLastChange $ shadowMin
 *				 shadowMax $ shadowWarning $ shadowInactive $
 *				 shadowExpire $ shadowFlag $ description ) )
 */

/* the basic search filter for searches */
static struct berval shadow_filter = BER_BVC("(objectClass=shadowAccount)");

/* the attributes to request with searches */
static struct berval shadow_keys[] = {
	BER_BVC("uid"),
	BER_BVC("userPassword"),
	BER_BVC("shadowLastChange"),
	BER_BVC("shadowMin"),
	BER_BVC("shadowMax"),
	BER_BVC("shadowWarning"),
	BER_BVC("shadowInactive"),
	BER_BVC("shadowExpire"),
	BER_BVC("shadowFlag"),
	BER_BVNULL
};

#define UID_KEY	0
#define PWD_KEY	1
#define CHG_KEY	2
#define MIN_KEY	3
#define MAX_KEY 4
#define WRN_KEY 5
#define INA_KEY 6
#define EXP_KEY 7
#define FLG_KEY 8

/* default values for attributes */
static struct berval default_shadow_userPassword		 = BER_BVC("*"); /* unmatchable */
static int default_nums[] = { 0,0,
	-1, /* LastChange */
	-1, /* Min */
	-1, /* Max */
	-1, /* Warning */
	-1, /* Inactive */
	-1, /* Expire */
	0 /* Flag */
};

NSSOV_INIT(shadow)

static long to_date(struct berval *date,AttributeDescription *attr)
{
	long value;
	char *tmp;
	/* do some special handling for date values on AD */
	if (strcasecmp(attr->ad_cname.bv_val,"pwdLastSet")==0)
	{
		char buffer[8];
		size_t l;
		/* we expect an AD 64-bit datetime value;
			 we should do date=date/864000000000-134774
			 but that causes problems on 32-bit platforms,
			 first we divide by 1000000000 by stripping the
			 last 9 digits from the string and going from there */
		l=date->bv_len-9;
		if (l<1 || l>(sizeof(buffer)-1))
			return 0; /* error */
		strncpy(buffer,date->bv_val,l);
		buffer[l]='\0';
		value=strtol(buffer,&tmp,0);
		if ((buffer[0]=='\0')||(*tmp!='\0'))
		{
			Debug(LDAP_DEBUG_ANY,"shadow entry contains non-numeric %s value\n",
				attr->ad_cname.bv_val );
			return 0;
		}
		return value/864-134774;
		/* note that AD does not have expiry dates but a lastchangeddate
			 and some value that needs to be added */
	}
	value=strtol(date->bv_val,&tmp,0);
	if ((date->bv_val[0]=='\0')||(*tmp!='\0'))
	{
		Debug(LDAP_DEBUG_ANY,"shadow entry contains non-numeric %s value\n",
			attr->ad_cname.bv_val );
		return 0;
	}
	return value;
}

#ifndef UF_DONT_EXPIRE_PASSWD
#define UF_DONT_EXPIRE_PASSWD 0x10000
#endif

#define GET_OPTIONAL_LONG(var,key) \
	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[key].an_desc); \
	if ( !a || BER_BVISNULL(&a->a_vals[0])) \
		var = default_nums[key]; \
	else \
	{ \
		if (a->a_numvals > 1) \
		{ \
			Debug(LDAP_DEBUG_ANY,"shadow entry %s contains multiple %s values\n", \
				entry->e_name.bv_val, cbp->mi->mi_attrs[key].an_desc->ad_cname.bv_val); \
		} \
		var=strtol(a->a_vals[0].bv_val,&tmp,0); \
		if ((a->a_vals[0].bv_val[0]=='\0')||(*tmp!='\0')) \
		{ \
			Debug(LDAP_DEBUG_ANY,"shadow entry %s contains non-numeric %s value\n", \
				entry->e_name.bv_val, cbp->mi->mi_attrs[key].an_desc->ad_cname.bv_val); \
			return 0; \
		} \
	}

#define GET_OPTIONAL_DATE(var,key) \
	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[key].an_desc); \
	if ( !a || BER_BVISNULL(&a->a_vals[0])) \
		var = default_nums[key]; \
	else \
	{ \
		if (a->a_numvals > 1) \
		{ \
			Debug(LDAP_DEBUG_ANY,"shadow entry %s contains multiple %s values\n", \
				entry->e_name.bv_val, cbp->mi->mi_attrs[key].an_desc->ad_cname.bv_val); \
		} \
		var=to_date(&a->a_vals[0],cbp->mi->mi_attrs[key].an_desc); \
	}

NSSOV_CBPRIV(shadow,
	char buf[256];
	struct berval name;);

static int write_shadow(nssov_shadow_cbp *cbp,Entry *entry)
{
	struct berval tmparr[2];
	struct berval *names;
	Attribute *a;
	char *tmp;
	struct berval passwd = {0};
	long lastchangedate;
	long mindays;
	long maxdays;
	long warndays;
	long inactdays;
	long expiredate;
	unsigned long flag;
	int i;
	int32_t tmpint32;
	/* get username */
	if (BER_BVISNULL(&cbp->name))
	{
		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[UID_KEY].an_desc);
		if (!a)
		{
			Debug(LDAP_DEBUG_ANY,"shadow entry %s does not contain %s value\n",
				entry->e_name.bv_val, cbp->mi->mi_attrs[UID_KEY].an_desc->ad_cname.bv_val );
			return 0;
		}
		names = a->a_vals;
	}
	else
	{
		names=tmparr;
		names[0]=cbp->name;
		BER_BVZERO(&names[1]);
	}
	/* get password */
	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[PWD_KEY].an_desc);
	if ( a )
		get_userpassword(&a->a_vals[0], &passwd);
	if (BER_BVISNULL(&passwd))
		passwd=default_shadow_userPassword;
	/* get lastchange date */
	GET_OPTIONAL_DATE(lastchangedate,CHG_KEY);
	/* get mindays */
	GET_OPTIONAL_LONG(mindays,MIN_KEY);
	/* get maxdays */
	GET_OPTIONAL_LONG(maxdays,MAX_KEY);
	/* get warndays */
	GET_OPTIONAL_LONG(warndays,WRN_KEY);
	/* get inactdays */
	GET_OPTIONAL_LONG(inactdays,INA_KEY);
	/* get expire date */
	GET_OPTIONAL_LONG(expiredate,EXP_KEY);
	/* get flag */
	GET_OPTIONAL_LONG(flag,FLG_KEY);
	/* if we're using AD handle the flag specially */
	if (strcasecmp(cbp->mi->mi_attrs[CHG_KEY].an_desc->ad_cname.bv_val,"pwdLastSet")==0)
	{
		if (flag&UF_DONT_EXPIRE_PASSWD)
			maxdays=99999;
		flag=0;
	}
	/* write the entries */
	for (i=0;!BER_BVISNULL(&names[i]);i++)
	{
		WRITE_INT32(cbp->fp,NSLCD_RESULT_BEGIN);
		WRITE_BERVAL(cbp->fp,&names[i]);
		WRITE_BERVAL(cbp->fp,&passwd);
		WRITE_INT32(cbp->fp,lastchangedate);
		WRITE_INT32(cbp->fp,mindays);
		WRITE_INT32(cbp->fp,maxdays);
		WRITE_INT32(cbp->fp,warndays);
		WRITE_INT32(cbp->fp,inactdays);
		WRITE_INT32(cbp->fp,expiredate);
		WRITE_INT32(cbp->fp,flag);
	}
	return 0;
}

NSSOV_CB(shadow)

NSSOV_HANDLE(
	shadow,byname,
	char fbuf[1024];
	struct berval filter = {sizeof(fbuf)};
	filter.bv_val = fbuf;
	READ_STRING(fp,cbp.buf);,
	cbp.name.bv_len = tmpint32;
	cbp.name.bv_val = cbp.buf;
	Debug(LDAP_DEBUG_ANY,"nssov_shadow_byname(%s)\n",cbp.name.bv_val);,
	NSLCD_ACTION_SHADOW_BYNAME,
	nssov_filter_byname(cbp.mi,UID_KEY,&cbp.name,&filter)
)

NSSOV_HANDLE(
	shadow,all,
	struct berval filter;
	/* no parameters to read */
	BER_BVZERO(&cbp.name);,
	Debug(LDAP_DEBUG_ANY,"nssov_shadow_all()\n");,
	NSLCD_ACTION_SHADOW_ALL,
	(filter=cbp.mi->mi_filter,0)
)
