/* pam.c - pam processing routines */
/* $OpenLDAP: pkg/ldap/contrib/slapd-modules/nssov/pam.c,v 1.13.2.2 2009/06/03 20:46:55 quanah Exp $ */
/*
 * Copyright 2009 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>.
 */

#include "nssov.h"
#include "lutil.h"

static int ppolicy_cid;
static AttributeDescription *ad_loginStatus;

const char *at_loginStatus =
	"( 1.3.6.1.4.1.4745.1.20.1 "
	"NAME ( 'loginStatus' ) "
	"DESC 'Currently logged in sessions for a user' "
	"EQUALITY caseIgnoreMatch "
	"SUBSTR caseIgnoreSubstringsMatch "
	"ORDERING caseIgnoreOrderingMatch "
	"SYNTAX OMsDirectoryString "
	"USAGE directoryOperation )";

struct paminfo {
	struct berval uid;
	struct berval dn;
	struct berval svc;
	struct berval pwd;
	int authz;
	struct berval msg;
};

static int pam_bindcb(
	Operation *op, SlapReply *rs)
{
	struct paminfo *pi = op->o_callback->sc_private;
	LDAPControl *ctrl = ldap_control_find(LDAP_CONTROL_PASSWORDPOLICYRESPONSE,
		rs->sr_ctrls, NULL);
	if (ctrl) {
		LDAP *ld;
		ber_int_t expire, grace;
		LDAPPasswordPolicyError error;

		ldap_create(&ld);
		if (ld) {
			int rc = ldap_parse_passwordpolicy_control(ld,ctrl,
				&expire,&grace,&error);
			if (rc == LDAP_SUCCESS) {
				if (expire >= 0) {
					char *unit = "seconds";
					if (expire > 60) {
						expire /= 60;
						unit = "minutes";
					}
					if (expire > 60) {
						expire /= 60;
						unit = "hours";
					}
					if (expire > 24) {
						expire /= 24;
						unit = "days";
					}
#if 0	/* Who warns about expiration so far in advance? */
					if (expire > 7) {
						expire /= 7;
						unit = "weeks";
					}
					if (expire > 4) {
						expire /= 4;
						unit = "months";
					}
					if (expire > 12) {
						expire /= 12;
						unit = "years";
					}
#endif
					pi->msg.bv_len = sprintf(pi->msg.bv_val,
						"\nWARNING: Password expires in %d %s\n", expire, unit);
				} else if (grace > 0) {
					pi->msg.bv_len = sprintf(pi->msg.bv_val,
						"Password expired; %d grace logins remaining",
						grace);
					pi->authz = NSLCD_PAM_NEW_AUTHTOK_REQD;
				} else if (error != PP_noError) {
					ber_str2bv(ldap_passwordpolicy_err2txt(error), 0, 0,
						&pi->msg);
					switch (error) {
					case PP_passwordExpired:
						/* report this during authz */
						rs->sr_err = LDAP_SUCCESS;
						/* fallthru */
					case PP_changeAfterReset:
						pi->authz = NSLCD_PAM_NEW_AUTHTOK_REQD;
					}
				}
			}
			ldap_ld_free(ld,0,NULL,NULL);
		}
	}
	return LDAP_SUCCESS;
}

int pam_do_bind(nssov_info *ni,TFILE *fp,Operation *op,
	struct paminfo *pi)
{
	int rc;
	slap_callback cb = {0};
	SlapReply rs = {REP_RESULT};
	struct berval sdn;

	pi->msg.bv_val = pi->pwd.bv_val;
	pi->msg.bv_len = 0;
	pi->authz = NSLCD_PAM_SUCCESS;
	BER_BVZERO(&pi->dn);

	if (!isvalidusername(&pi->uid)) {
		Debug(LDAP_DEBUG_ANY,"nssov_pam_do_bind(%s): invalid user name\n",
			pi->uid.bv_val,0,0);
		rc = NSLCD_PAM_USER_UNKNOWN;
		goto finish;
	}

	if (ni->ni_pam_opts & NI_PAM_SASL2DN) {
		int hlen = global_host_bv.bv_len;

		/* cn=<service>+uid=<user>,cn=<host>,cn=pam,cn=auth */
		sdn.bv_len = pi->uid.bv_len + pi->svc.bv_len + hlen +
			STRLENOF( "cn=+uid=,cn=,cn=pam,cn=auth" );
		sdn.bv_val = op->o_tmpalloc( sdn.bv_len + 1, op->o_tmpmemctx );
		sprintf(sdn.bv_val, "cn=%s+uid=%s,cn=%s,cn=pam,cn=auth",
			pi->svc.bv_val, pi->uid.bv_val, global_host_bv.bv_val);
		slap_sasl2dn(op, &sdn, &pi->dn, 0);
		op->o_tmpfree( sdn.bv_val, op->o_tmpmemctx );
	}

	/* If no luck, do a basic uid search */
	if (BER_BVISEMPTY(&pi->dn) && (ni->ni_pam_opts & NI_PAM_UID2DN)) {
		nssov_uid2dn(op, ni, &pi->uid, &pi->dn);
		if (!BER_BVISEMPTY(&pi->dn)) {
			sdn = pi->dn;
			dnNormalize( 0, NULL, NULL, &sdn, &pi->dn, op->o_tmpmemctx );
		}
	}
	BER_BVZERO(&sdn);
	if (BER_BVISEMPTY(&pi->dn)) {
		rc = NSLCD_PAM_USER_UNKNOWN;
		goto finish;
	}

	if (BER_BVISEMPTY(&pi->pwd)) {
		rc = NSLCD_PAM_IGNORE;
		goto finish;
	}

	/* Should only need to do this once at open time, but there's always
	 * the possibility that ppolicy will get loaded later.
	 */
	if (!ppolicy_cid) {
		rc = slap_find_control_id(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
			&ppolicy_cid);
	}
	/* of course, 0 is a valid cid, but it won't be ppolicy... */
	if (ppolicy_cid) {
		op->o_ctrlflag[ppolicy_cid] = SLAP_CONTROL_NONCRITICAL;
	}
	cb.sc_response = pam_bindcb;
	cb.sc_private = pi;
	op->o_callback = &cb;
	op->o_dn.bv_val[0] = 0;
	op->o_dn.bv_len = 0;
	op->o_ndn.bv_val[0] = 0;
	op->o_ndn.bv_len = 0;
	op->o_tag = LDAP_REQ_BIND;
	op->o_protocol = LDAP_VERSION3;
	op->orb_method = LDAP_AUTH_SIMPLE;
	op->orb_cred = pi->pwd;
	op->o_req_dn = pi->dn;
	op->o_req_ndn = pi->dn;
	slap_op_time( &op->o_time, &op->o_tincr );
	rc = op->o_bd->be_bind( op, &rs );
	memset(pi->pwd.bv_val,0,pi->pwd.bv_len);
	/* quirk: on successful bind, caller has to send result. we need
	 * to make sure callbacks run.
	 */
	if (rc == LDAP_SUCCESS)
		send_ldap_result(op, &rs);
	switch(rs.sr_err) {
	case LDAP_SUCCESS: rc = NSLCD_PAM_SUCCESS; break;
	case LDAP_INVALID_CREDENTIALS: rc = NSLCD_PAM_AUTH_ERR; break;
	default: rc = NSLCD_PAM_AUTH_ERR; break;
	}
finish:
	return rc;
}

int pam_authc(nssov_info *ni,TFILE *fp,Operation *op)
{
	int32_t tmpint32;
	int rc;
	slap_callback cb = {0};
	SlapReply rs = {REP_RESULT};
	char dnc[1024];
	char uidc[32];
	char svcc[256];
	char pwdc[256];
	struct berval sdn, dn;
	struct paminfo pi;


	READ_STRING_BUF2(fp,uidc,sizeof(uidc));
	pi.uid.bv_val = uidc;
	pi.uid.bv_len = tmpint32;
	READ_STRING_BUF2(fp,dnc,sizeof(dnc));
	pi.dn.bv_val = dnc;
	pi.dn.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(svcc));
	pi.svc.bv_val = svcc;
	pi.svc.bv_len = tmpint32;
	READ_STRING_BUF2(fp,pwdc,sizeof(pwdc));
	pi.pwd.bv_val = pwdc;
	pi.pwd.bv_len = tmpint32;

	Debug(LDAP_DEBUG_TRACE,"nssov_pam_authc(%s)\n",pi.uid.bv_val,0,0);

	rc = pam_do_bind(ni, fp, op, &pi);

finish:
	WRITE_INT32(fp,NSLCD_VERSION);
	WRITE_INT32(fp,NSLCD_ACTION_PAM_AUTHC);
	WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
	WRITE_BERVAL(fp,&pi.uid);
	WRITE_BERVAL(fp,&pi.dn);
	WRITE_INT32(fp,rc);
	WRITE_INT32(fp,pi.authz);	/* authz */
	WRITE_BERVAL(fp,&pi.msg);	/* authzmsg */
	return 0;
}

static struct berval grpmsg =
	BER_BVC("Access denied by group check");
static struct berval hostmsg =
	BER_BVC("Access denied for this host");
static struct berval svcmsg =
	BER_BVC("Access denied for this service");
static struct berval uidmsg =
	BER_BVC("Access denied by UID check");

int pam_authz(nssov_info *ni,TFILE *fp,Operation *op)
{
	struct berval dn, uid, svc, ruser, rhost, tty;
	struct berval authzmsg = BER_BVNULL;
	int32_t tmpint32;
	char dnc[1024];
	char uidc[32];
	char svcc[256];
	char ruserc[32];
	char rhostc[256];
	char ttyc[256];
	int rc = NSLCD_PAM_SUCCESS;
	Entry *e = NULL;
	Attribute *a;
	SlapReply rs = {REP_RESULT};
	slap_callback cb = {0};

	READ_STRING_BUF2(fp,uidc,sizeof(uidc));
	uid.bv_val = uidc;
	uid.bv_len = tmpint32;
	READ_STRING_BUF2(fp,dnc,sizeof(dnc));
	dn.bv_val = dnc;
	dn.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(svcc));
	svc.bv_val = svcc;
	svc.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(ruserc));
	ruser.bv_val = ruserc;
	ruser.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(rhostc));
	rhost.bv_val = rhostc;
	rhost.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(ttyc));
	tty.bv_val = ttyc;
	tty.bv_len = tmpint32;

	Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(%s)\n",dn.bv_val,0,0);

	/* We don't do authorization if they weren't authenticated by us */
	if (BER_BVISEMPTY(&dn)) {
		rc = NSLCD_PAM_USER_UNKNOWN;
		goto finish;
	}

	/* See if they have access to the host and service */
	if ((ni->ni_pam_opts & NI_PAM_HOSTSVC) && nssov_pam_svc_ad) {
		AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
		struct berval hostdn = BER_BVNULL;
		struct berval odn = op->o_ndn;
		op->o_dn = dn;
		op->o_ndn = dn;
		{
			nssov_mapinfo *mi = &ni->ni_maps[NM_host];
			char fbuf[1024];
			struct berval filter = {sizeof(fbuf),fbuf};
			SlapReply rs2 = {REP_RESULT};

			/* Lookup the host entry */
			nssov_filter_byname(mi,0,&global_host_bv,&filter);
			cb.sc_private = &hostdn;
			cb.sc_response = nssov_name2dn_cb;
			op->o_callback = &cb;
			op->o_req_dn = mi->mi_base;
			op->o_req_ndn = mi->mi_base;
			op->ors_scope = mi->mi_scope;
			op->ors_filterstr = filter;
			op->ors_filter = str2filter_x(op, filter.bv_val);
			op->ors_attrs = slap_anlist_no_attrs;
			op->ors_tlimit = SLAP_NO_LIMIT;
			op->ors_slimit = 2;
			rc = op->o_bd->be_search(op, &rs2);
			filter_free_x(op, op->ors_filter, 1);

			if (BER_BVISEMPTY(&hostdn) &&
				!BER_BVISEMPTY(&ni->ni_pam_defhost)) {
				filter.bv_len = sizeof(fbuf);
				filter.bv_val = fbuf;
				memset(&rs2, 0, sizeof(rs2));
				rs2.sr_type = REP_RESULT;
				nssov_filter_byname(mi,0,&ni->ni_pam_defhost,&filter);
				op->ors_filterstr = filter;
				op->ors_filter = str2filter_x(op, filter.bv_val);
				rc = op->o_bd->be_search(op, &rs2);
				filter_free_x(op, op->ors_filter, 1);
			}

			/* no host entry, no default host -> deny */
			if (BER_BVISEMPTY(&hostdn)) {
				rc = NSLCD_PAM_PERM_DENIED;
				authzmsg = hostmsg;
				goto finish;
			}
		}

		cb.sc_response = slap_null_cb;
		cb.sc_private = NULL;
		op->o_tag = LDAP_REQ_COMPARE;
		op->o_req_dn = hostdn;
		op->o_req_ndn = hostdn;
		ava.aa_desc = nssov_pam_svc_ad;
		ava.aa_value = svc;
		op->orc_ava = &ava;
		rc = op->o_bd->be_compare( op, &rs );
		if ( rs.sr_err != LDAP_COMPARE_TRUE ) {
			authzmsg = svcmsg;
			rc = NSLCD_PAM_PERM_DENIED;
			goto finish;
		}
		op->o_dn = odn;
		op->o_ndn = odn;
	}

	/* See if they're a member of the group */
	if ((ni->ni_pam_opts & NI_PAM_USERGRP) &&
		!BER_BVISEMPTY(&ni->ni_pam_group_dn) &&
		ni->ni_pam_group_ad) {
		AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
		op->o_callback = &cb;
		cb.sc_response = slap_null_cb;
		op->o_tag = LDAP_REQ_COMPARE;
		op->o_req_dn = ni->ni_pam_group_dn;
		op->o_req_ndn = ni->ni_pam_group_dn;
		ava.aa_desc = ni->ni_pam_group_ad;
		ava.aa_value = dn;
		op->orc_ava = &ava;
		rc = op->o_bd->be_compare( op, &rs );
		if ( rs.sr_err != LDAP_COMPARE_TRUE ) {
			authzmsg = grpmsg;
			rc = NSLCD_PAM_PERM_DENIED;
			goto finish;
		}
	}

	/* We need to check the user's entry for these bits */
	if ((ni->ni_pam_opts & (NI_PAM_USERHOST|NI_PAM_USERSVC)) ||
		ni->ni_pam_template_ad ||
		ni->ni_pam_min_uid || ni->ni_pam_max_uid ) {
		rc = be_entry_get_rw( op, &dn, NULL, NULL, 0, &e );
		if (rc != LDAP_SUCCESS) {
			rc = NSLCD_PAM_USER_UNKNOWN;
			goto finish;
		}
	}
	if ((ni->ni_pam_opts & NI_PAM_USERHOST) && nssov_pam_host_ad) {
		a = attr_find(e->e_attrs, nssov_pam_host_ad);
		if (!a || value_find_ex( nssov_pam_host_ad,
			SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
			a->a_vals, &global_host_bv, op->o_tmpmemctx )) {
			rc = NSLCD_PAM_PERM_DENIED;
			authzmsg = hostmsg;
			goto finish;
		}
	}
	if ((ni->ni_pam_opts & NI_PAM_USERSVC) && nssov_pam_svc_ad) {
		a = attr_find(e->e_attrs, nssov_pam_svc_ad);
		if (!a || value_find_ex( nssov_pam_svc_ad,
			SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
			a->a_vals, &svc, op->o_tmpmemctx )) {
			rc = NSLCD_PAM_PERM_DENIED;
			authzmsg = svcmsg;
			goto finish;
		}
	}

/* from passwd.c */
#define UIDN_KEY	2

	if (ni->ni_pam_min_uid || ni->ni_pam_max_uid) {
		int id;
		char *tmp;
		nssov_mapinfo *mi = &ni->ni_maps[NM_host];
		a = attr_find(e->e_attrs, mi->mi_attrs[UIDN_KEY].an_desc);
		if (!a) {
			rc = NSLCD_PAM_PERM_DENIED;
			authzmsg = uidmsg;
			goto finish;
		}
		id = (int)strtol(a->a_vals[0].bv_val,&tmp,0);
		if (a->a_vals[0].bv_val[0] == '\0' || *tmp != '\0') {
			rc = NSLCD_PAM_PERM_DENIED;
			authzmsg = uidmsg;
			goto finish;
		}
		if ((ni->ni_pam_min_uid && id < ni->ni_pam_min_uid) ||
			(ni->ni_pam_max_uid && id > ni->ni_pam_max_uid)) {
			rc = NSLCD_PAM_PERM_DENIED;
			authzmsg = uidmsg;
			goto finish;
		}
	}

	if (ni->ni_pam_template_ad) {
		a = attr_find(e->e_attrs, ni->ni_pam_template_ad);
		if (a)
			uid = a->a_vals[0];
		else if (!BER_BVISEMPTY(&ni->ni_pam_template))
			uid = ni->ni_pam_template;
	}

finish:
	WRITE_INT32(fp,NSLCD_VERSION);
	WRITE_INT32(fp,NSLCD_ACTION_PAM_AUTHZ);
	WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
	WRITE_BERVAL(fp,&uid);
	WRITE_BERVAL(fp,&dn);
	WRITE_INT32(fp,rc);
	WRITE_BERVAL(fp,&authzmsg);
	if (e) {
		be_entry_release_r(op, e);
	}
	return 0;
}

static int pam_sess(nssov_info *ni,TFILE *fp,Operation *op,int action)
{
	struct berval dn, uid, svc, tty, rhost, ruser;
	int32_t tmpint32;
	char dnc[1024];
	char svcc[256];
	char uidc[32];
	char ttyc[32];
	char rhostc[256];
	char ruserc[32];
	slap_callback cb = {0};
	SlapReply rs = {REP_RESULT};
	char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];
	struct berval timestamp, bv[2], *nbv;
	time_t stamp;
	Modifications mod;

	READ_STRING_BUF2(fp,uidc,sizeof(uidc));
	uid.bv_val = uidc;
	uid.bv_len = tmpint32;
	READ_STRING_BUF2(fp,dnc,sizeof(dnc));
	dn.bv_val = dnc;
	dn.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(svcc));
	svc.bv_val = svcc;
	svc.bv_len = tmpint32;
	READ_STRING_BUF2(fp,ttyc,sizeof(ttyc));
	tty.bv_val = ttyc;
	tty.bv_len = tmpint32;
	READ_STRING_BUF2(fp,rhostc,sizeof(rhostc));
	rhost.bv_val = rhostc;
	rhost.bv_len = tmpint32;
	READ_STRING_BUF2(fp,ruserc,sizeof(ruserc));
	ruser.bv_val = ruserc;
	ruser.bv_len = tmpint32;
	READ_INT32(fp,stamp);

	Debug(LDAP_DEBUG_TRACE,"nssov_pam_sess_%c(%s)\n",
		action==NSLCD_ACTION_PAM_SESS_O ? 'o' : 'c', dn.bv_val,0);

	if (!dn.bv_len || !ni->ni_pam_sessions) return 0;

	{
		int i, found=0;
		for (i=0; !BER_BVISNULL(&ni->ni_pam_sessions[i]); i++) {
			if (ni->ni_pam_sessions[i].bv_len != svc.bv_len)
				continue;
			if (!strcasecmp(ni->ni_pam_sessions[i].bv_val, svc.bv_val)) {
				found = 1;
				break;
			}
		}
		if (!found) return 0;
	}

	slap_op_time( &op->o_time, &op->o_tincr );
	timestamp.bv_len = sizeof(timebuf);
	timestamp.bv_val = timebuf;
	if (action == NSLCD_ACTION_PAM_SESS_O )
		stamp = op->o_time;
	slap_timestamp( &stamp, &timestamp );
	bv[0].bv_len = timestamp.bv_len + global_host_bv.bv_len + svc.bv_len +
		tty.bv_len + ruser.bv_len + rhost.bv_len + STRLENOF("    (@)");
	bv[0].bv_val = op->o_tmpalloc( bv[0].bv_len+1, op->o_tmpmemctx );
	sprintf(bv[0].bv_val, "%s %s %s %s (%s@%s)",
		timestamp.bv_val, global_host_bv.bv_val, svc.bv_val, tty.bv_val,
		ruser.bv_val, rhost.bv_val);
	
	mod.sml_numvals = 1;
	mod.sml_values = bv;
	BER_BVZERO(&bv[1]);
	attr_normalize( ad_loginStatus, bv, &nbv, op->o_tmpmemctx );
	mod.sml_nvalues = nbv;
	mod.sml_desc = ad_loginStatus;
	mod.sml_op = action == NSLCD_ACTION_PAM_SESS_O ? LDAP_MOD_ADD :
		LDAP_MOD_DELETE;
	mod.sml_flags = SLAP_MOD_INTERNAL;
	mod.sml_next = NULL;

	cb.sc_response = slap_null_cb;
	op->o_callback = &cb;
	op->o_tag = LDAP_REQ_MODIFY;
	op->o_dn = op->o_bd->be_rootdn;
	op->o_ndn = op->o_bd->be_rootndn;
	op->orm_modlist = &mod;
	op->orm_no_opattrs = 1;
	op->o_req_dn = dn;
	op->o_req_ndn = dn;
	op->o_bd->be_modify( op, &rs );
	if ( mod.sml_next ) {
		slap_mods_free( mod.sml_next, 1 );
	}
	ber_bvarray_free_x( nbv, op->o_tmpmemctx );

	WRITE_INT32(fp,NSLCD_VERSION);
	WRITE_INT32(fp,action);
	WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
	WRITE_INT32(fp,op->o_time);
	return 0;
}

int pam_sess_o(nssov_info *ni,TFILE *fp,Operation *op)
{
	return pam_sess(ni,fp,op,NSLCD_ACTION_PAM_SESS_O);
}

int pam_sess_c(nssov_info *ni,TFILE *fp,Operation *op)
{
	return pam_sess(ni,fp,op,NSLCD_ACTION_PAM_SESS_C);
}

int pam_pwmod(nssov_info *ni,TFILE *fp,Operation *op)
{
	struct berval npw;
	int32_t tmpint32;
	char dnc[1024];
	char uidc[32];
	char opwc[256];
	char npwc[256];
	char svcc[256];
	struct paminfo pi;
	int rc;

	READ_STRING_BUF2(fp,uidc,sizeof(uidc));
	pi.uid.bv_val = uidc;
	pi.uid.bv_len = tmpint32;
	READ_STRING_BUF2(fp,dnc,sizeof(dnc));
	pi.dn.bv_val = dnc;
	pi.dn.bv_len = tmpint32;
	READ_STRING_BUF2(fp,svcc,sizeof(svcc));
	pi.svc.bv_val = svcc;
	pi.svc.bv_len = tmpint32;
	READ_STRING_BUF2(fp,opwc,sizeof(opwc));
	pi.pwd.bv_val = opwc;
	pi.pwd.bv_len = tmpint32;
	READ_STRING_BUF2(fp,npwc,sizeof(npwc));
	npw.bv_val = npwc;
	npw.bv_len = tmpint32;

	Debug(LDAP_DEBUG_TRACE,"nssov_pam_pwmod(%s), %s\n",
		pi.dn.bv_val,pi.uid.bv_val,0);

	BER_BVZERO(&pi.msg);

	/* This is a prelim check */
	if (BER_BVISEMPTY(&pi.dn)) {
		rc = pam_do_bind(ni,fp,op,&pi);
		if (rc == NSLCD_PAM_IGNORE)
			rc = NSLCD_PAM_SUCCESS;
	} else {
		BerElementBuffer berbuf;
		BerElement *ber = (BerElement *)&berbuf;
		struct berval bv;
		SlapReply rs = {REP_RESULT};
		slap_callback cb = {0};

		ber_init_w_nullc(ber, LBER_USE_DER);
		ber_printf(ber, "{");
		if (!BER_BVISEMPTY(&pi.pwd))
			ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD,
				&pi.pwd);
		if (!BER_BVISEMPTY(&npw))
			ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW,
				&npw);
		ber_printf(ber, "N}");
		ber_flatten2(ber, &bv, 0);
		op->o_tag = LDAP_REQ_EXTENDED;
		op->ore_reqoid = slap_EXOP_MODIFY_PASSWD;
		op->ore_reqdata = &bv;
		op->o_dn = pi.dn;
		op->o_ndn = pi.dn;
		op->o_callback = &cb;
		op->o_conn->c_authz_backend = op->o_bd;
		cb.sc_response = slap_null_cb;
		op->o_bd = frontendDB;
		rc = op->o_bd->be_extended(op, &rs);
		if (rs.sr_text)
			ber_str2bv(rs.sr_text, 0, 0, &pi.msg);
		if (rc == LDAP_SUCCESS)
			rc = NSLCD_PAM_SUCCESS;
		else
			rc = NSLCD_PAM_PERM_DENIED;
	}
	WRITE_INT32(fp,NSLCD_VERSION);
	WRITE_INT32(fp,NSLCD_ACTION_PAM_PWMOD);
	WRITE_INT32(fp,NSLCD_RESULT_SUCCESS);
	WRITE_BERVAL(fp,&pi.uid);
	WRITE_BERVAL(fp,&pi.dn);
	WRITE_INT32(fp,rc);
	WRITE_BERVAL(fp,&pi.msg);
	return 0;
}

int nssov_pam_init()
{
	int code = 0;
	if (!ad_loginStatus)
		code = register_at( at_loginStatus, &ad_loginStatus, 0 );
	return code;
}
