/**************************************************************************
    nss_ncp.c  NSS module for NDS

    Copyright (C) 2002  Patrick Pollet

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


    Revision history:

	1.00  	2003, January 06	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		initial release
	1.01	2003, January 08	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		added conf structure and control group
		added optional fallback UID and GID if none found in NDS (default is to skip user,group)
	1.02	2003, January 09	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		added initgroups
	1.03	2003, January 10	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		fixed bug in nds_user_info2 (bad structure received by nds_user_location2)
	1.04	2003, January 11	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		fixed setting ndsXXX=NULL trees in case of errors in _nss_ncp_setxxent()
		made always NAME_CONTEXT=[Root] in CreateContextAndConn
		calling NWCCloseIteration only it some errors has occured in the search
	1.05	2003, January 15	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		-Avoid multiple reading of conf file by removing recursive calls
		in nss_ncp_getxxent_r in case a entry has no Unix infos in NDS
		(replaced by a goto nextuser)
		-Added missing free_nw_xxx_info when leaving nss_ncp_getxxent_r (fixed memory leaks)
		-Added testing for failure in allocating tree structure in nss_ncp_setxxent_r
		-if (id !=(uid_t)-1)  and not if (id) in getentxx if we search by UID !!!!
		-getentbyxx give a warning in syslog if more that one entry match the name or id search criteria
	1.06	2003, January 16	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		-implemented reading of configuration file in /etc/nss_ncp.conf
		-in case of fatal errors, force log in syslog by using calls to traceForce ( previously fatal
		errors were only reported in debug mode, since the syslog file is not opened in normal mode)
	1.07	2003, January 16	Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Speed up the search:
			1)config informations are stored in the internal trees structures
			when calling nss_ncp_setxxent_r and freed by nss_ncp_endxxent_r
			so we don't read again config file at every call to nss_ncp_getxxent_r
			2) group infos are really slow with big groups,so we added a flag doGroup in conf file
			to skip the search (easier that to edit /etc/nsswitch.conf AND restarting nscd daemon).
************************************************************************/


#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <ncp/nwnet.h>


#include <unistd.h>
#include <ctype.h>
#include <wchar.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <shadow.h>
#include <sys/syslog.h>

#include <nss.h>

#include "nss_ncp.h"
#include "nss_cfgfile.h"

// only if logfile has been opened by nss API functions (debug mode)
static void trace (int debugMode,int err,const char * format,... ) {
	va_list args;
	if (debugMode)  {
		va_start(args,format);
		vsyslog (err,format,args);
		va_end(args);
	}
}

// send a message to syslog even if log file not opened (debugMode is false)
// needed for critical errors such a bad config file or ambiguous NDS search
void traceForce (int debugMode,int err, const char * format,... ) {
	va_list args;

	if (!debugMode)
		openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
	va_start(args,format);
	vsyslog (err,format,args);
	va_end(args);
	if (!debugMode)
		closelog();

}


static int
getnumber(int *val, const char **str)
{
	const char *p = *str;
	char *z;

	if (!*p)
		return 1;
	if (*p == ',') {
		*str = p + 1;
		return 1;
	}
	*val = strtoul(p, &z, 0);
	if (p == z)
		return -1;
	if (*z == ',')
		z++;
	*str = z;
	return 0;
}

static char* copy_to_buffer (char ** buffer, size_t *buflen, char * value) {
// copy the value to buffer , decrease buflen and return start of the value in buffer
	size_t len=strlen(value)+1;
	char * ptr=*buffer;

	if (len > *buflen) {
		return NULL;
	}
	memcpy(*buffer, value, len);
	*buflen -=len;
	(*buffer) +=len;
	return ptr;
}

/**********
struct nss_ncp_conf {
	int 	debug;
	int 	useTree;	// use Tree connection or server connection
	char * 	server;		// name of server or tree
	char * 	startCtx;  	// start searching is this context (and below)
	char * 	ctrlGroup; 	// limit search to members of this NDS group for passwd and shadow
	gid_t	defGid;		// if no primary group found in NDS use this value
	char *	defShell;	// if no shell found in NDS use this value
	uid_t	fallbackUid;	//  if no UID found in NDS use this one (-1= skip user, NFS_NOBODY= use this UID)
	gid_t	fallbackGid;	//  if no GID found in NDS use this one (-1= skip group, NFS_NOBODY= use this GID)
	int	doPassword;	// if  0, will return immediarly NSS_STATUS_UNAVAILABLE even if ncp is listed in /etc/nsswitch.conf
	int	doGroup;	// if  0, will return immediarly NSS_STATUS_UNAVAILABLE even if ncp is listed in /etc/nsswitch.conf
	int 	doShadow;	// if  0, will return immediarly NSS_STATUS_UNAVAILABLE even if ncp is listed in /etc/nsswitch.conf
};
**************/

//one day we will read it from /etc/ncp_nss.conf


/****************************************** internal structure for storing NDS user's password infos ***/
struct nw_user_info {
		char * 	cn;
		char * 	gecos;
		char *	shell;
		char *	dir;
		char *	fullName;
		char * passwd;
		uid_t	uid;
		gid_t	gid;
		int qflag;

	};

static void init_nw_user_info (struct nw_user_info *ui,int qflag){
	ui->cn=NULL;
	ui->gecos=NULL;
	ui->shell=NULL;
	ui->dir=NULL;
	ui->fullName=NULL;
	ui->passwd=NULL;
	ui->uid=(uid_t)-1;
	ui->gid=(gid_t)-1;
	ui->qflag= qflag;
}

static void free_nw_user_info (struct nw_user_info *ui){
#define FREEFIELD(x) do if (ui->x) {free(ui->x) ; ui->x=NULL;} while (0);
	FREEFIELD(cn);
	FREEFIELD(gecos);
	FREEFIELD(shell);
	FREEFIELD(dir);
	FREEFIELD(fullName);
	FREEFIELD(passwd);
	ui->uid=(uid_t)-1;
	ui->gid=(gid_t)-1;
	ui->qflag=0;
#undef FREEFIELD
}


static int fix_nw_user_info (struct nw_user_info *ui, struct nss_ncp_conf* conf){
/* fill NDS missing attributes (home, shell, gid, gecos) with default values
 	any user have a cn, and if uid was not found in NDS we consider
	that the corresponding Unix account is not activated
*/
	if (ui->cn) {
		if (ui->uid== (uid_t)-1)
			ui->uid=conf->fallbackUid;
		if (ui->gid== (gid_t)-1)
			ui->gid=conf->defGid;
		if (!ui->gecos) {
			ui->gecos= ui->fullName ? strdup(ui->fullName):strdup("");
		}
		ui->shell= ui->shell ? ui->shell: strdup(conf->defShell);
		ui->dir= ui->dir ? ui->dir: strdup("");
		ui->passwd= ui->passwd ? ui->passwd: strdup("x"); // cannot read passwd from ND
		if (!ui->shell || !ui->dir || !ui->gecos || !ui->passwd) {
				traceForce(conf->debug,LOG_ERR, "not enough memory when fixing nw_user_info\n ",ui->cn);
		return 1;
	}
	}else {
		ui->uid = (uid_t)-1; // unable to read CN (NDS browse rights not set) , skip it !
	}
	return 0;
}


// return found info to caller in the format expected by NSS that is:
// filling the passwd structure as pointers to the passed buffer


static enum nss_status nw_user_info_to_passwd(struct nw_user_info ui,struct passwd *pwd,
	char * buffer, size_t buflen, int * errnop, struct nss_ncp_conf *conf){

	if (ui.uid != (uid_t)-1) {
		pwd->pw_uid=ui.uid;
		pwd->pw_gid=ui.gid;

		pwd->pw_name=copy_to_buffer (&buffer,&buflen,ui.cn);
		if (!pwd->pw_name) goto outnomem;
		pwd->pw_passwd=copy_to_buffer (&buffer,&buflen,ui.passwd);
		if (!pwd->pw_passwd) goto outnomem;
		pwd->pw_gecos=copy_to_buffer (&buffer,&buflen,ui.gecos);
		if (!pwd->pw_gecos) goto outnomem;
		pwd->pw_dir=copy_to_buffer (&buffer,&buflen,ui.dir);
		if (!pwd->pw_dir) goto outnomem;
		pwd->pw_shell=copy_to_buffer (&buffer,&buflen,ui.shell);
		if (!pwd->pw_shell) goto outnomem;

		*errnop=0;
		return NSS_STATUS_SUCCESS;
outnomem:
			traceForce(conf->debug,LOG_ERR, "not enough memory when copying nw_user_info to passwd for %s\n ",ui.cn);
		*errnop=ERANGE;
		return NSS_STATUS_TRYAGAIN;
	} else {
			trace(conf->debug,LOG_NOTICE, "user %s has no Unix UID in NDS\n ", ui.cn);
		*errnop=ENOENT;
		return NSS_STATUS_NOTFOUND;
	}
}

static void print_nw_user_info (struct nw_user_info ui){
	printf("%s:x:%d:%d:%s:%s:%s\n",ui.cn,ui.uid,ui.gid,ui.gecos,ui.dir,ui.shell);
}

static void print_passwd (struct passwd pwd){
	printf("%s:%s:%d:%d:%s:%s:%s\n",pwd.pw_name,pwd.pw_passwd,pwd.pw_uid,pwd.pw_gid,pwd.pw_gecos,pwd.pw_dir,pwd.pw_shell);
}


/****************************************** internal structure for storing NDS group's infos ***/
struct nw_group_member {
	struct nw_group_member* next;
	char * member;
};

struct nw_group_info {
		char * 	cn;
		char * alias;
		char * passwd;
		gid_t	gid;
		struct nw_group_member * first;
		struct nw_group_member * last;
		int nbMembers;
		int qflag;
	};


static void init_nw_group_info (struct nw_group_info *gi,int qflag){
	gi->cn=NULL;
	gi->alias=NULL;
	gi->passwd=NULL;
	gi->gid=(gid_t)-1;
	gi->qflag= qflag;
	gi->first=NULL;
	gi->last=NULL;
	gi->nbMembers=0;
}

static void free_nw_group_info (struct nw_group_info *gi){

	struct nw_group_member* p;
	struct nw_group_member* bkp;
#define FREEFIELD(x) do if (gi->x) {free(gi->x) ; gi->x=NULL;} while (0);
	FREEFIELD(cn);
	FREEFIELD(alias);
	FREEFIELD(passwd);
	gi->gid=(gid_t)-1;
	gi->qflag=0;
#undef FREEFIELD
	for (p=gi->first; p; p=bkp) {
		bkp=p->next;
		free (p->member);
		free(p);
	}
	gi->first=gi->last=NULL;
	gi->nbMembers=0;
}



static int fix_nw_group_info (struct nw_group_info *gi ,struct nss_ncp_conf* conf){
/* fill NDS missing attributes  with default values
	any group have a cn, and if gid was not found in NDS we will later consider
	that the corresponding Unix group is not activated unless a fallback value is defined in conf
*/
	if (gi->cn) {
		if (gi->gid== (gid_t)-1)
			gi->gid=conf->fallbackGid;

		gi->passwd= gi->passwd ? gi->passwd: strdup("x"); // cannot read passwd from NDS
		if (!gi->passwd) {
			traceForce(conf->debug,LOG_ERR, "not enough memory when allocating password for group %s\n ",gi->cn);
			return 1;
		}
		if (gi->alias) {
			if (gi->cn) free(gi->cn);
			gi->cn=strdup(gi->alias);
			if (!gi->cn) {
				traceForce(conf->debug,LOG_ERR, "not enough memory when allocating alias for group %s\n ",gi->alias);
				return 1;
			}
		}
	}else {
		gi->gid = (gid_t)-1; // unable to read CN (NDS browse rights not set) , skip it !
	}
	return 0;
}



/***********
return found info to caller in the format expected by NSS that is:
filling the group structure as pointers to the passed buffer

struct group
  {
    char *gr_name;              // Group name.
    char *gr_passwd;            // Password.
    __gid_t gr_gid;             // Group ID.
    char **gr_mem;              // Member list.
  };
******************/

static enum nss_status nw_group_info_to_group(struct nw_group_info gi,struct group *grp,
	char * buffer, size_t buflen, int * errnop, struct nss_ncp_conf *conf){

	if (gi.gid != (gid_t)-1) {
		grp->gr_gid=gi.gid;
		grp->gr_name=copy_to_buffer (&buffer,&buflen,gi.cn);
		if (!grp->gr_name) goto outnomem;
		grp->gr_passwd=copy_to_buffer (&buffer,&buflen,gi.passwd);
		if (!grp->gr_passwd) goto outnomem;

		{// copy members to buffer
		// names are stored backwards from end of buffer
		// and adress pointers forward from start of buffer
		// exit with NSS_STATUS_TRYAGAIN if buffer is too small
		// code inspired from nss_mysql by Guillaume Morin

			size_t required=0;
			char ** addPtr;
			char * end_of_buffer,*tmp,*nm;
			struct nw_group_member *p;

			for (p=gi.first;p;p=p->next) {
				required += strlen(p->member)+1+sizeof(char**);
			}
			if (required + sizeof(char**) >=buflen) {
				traceForce(conf->debug,LOG_ERR, "unable to copy members of group '%s' to buffer :need=%d have= %d\n",gi.cn,required,buflen);
				goto outnomem;
			}
			addPtr= (char**)buffer;
			grp->gr_mem=addPtr;
			end_of_buffer= buffer+buflen-1;
			p=gi.first;
			while (p) {
				end_of_buffer -=strlen(p->member)+1;
				tmp=end_of_buffer; //do not change end_of_buffer when copying the new member !
				nm=copy_to_buffer(&tmp,&buflen,p->member);
				if (!nm) {
					traceForce(conf->debug,LOG_ERR, "not enough memory when copying nw_group_info to group for %s\n ",gi.cn);
					goto outnomem;
				}
				*addPtr=nm;
				//printf("%s=%s\n",p->member,*addPtr);
				addPtr++;
				p=p->next;
			}
			*addPtr=NULL; // end of table of pointers
		}
		*errnop=0;
		return NSS_STATUS_SUCCESS;
outnomem:

		*errnop=ERANGE;
		return NSS_STATUS_TRYAGAIN;
	} else {
		trace(conf->debug,LOG_NOTICE, "group %s has no Unix GID in NDS\n ", gi.cn);
		*errnop=ENOENT;
		return NSS_STATUS_NOTFOUND;
	}
}


static void print_nw_group_info (struct nw_group_info gi){
	struct nw_group_member* p;

	printf("%s:x:%d:%d members:",gi.cn,gi.gid,gi.nbMembers);
	for (p=gi.first;p;p=p->next) {
		printf("%s",p->member);
		if (p->next) printf(",");
	}
	printf("\n");
}

static void print_group (struct group grp){
	char ** mmb; int num;
	printf("%s:%s:%d:",grp.gr_name,grp.gr_passwd,grp.gr_gid);
	for (mmb=grp.gr_mem,num=0;*mmb; mmb++,num++) {
		if (num)
			printf(",");
		printf ("%s",*mmb);

	}
	printf("\n");

}


/****************************************** internal structure for storing NDS user's shadow infos ***/
struct nw_shadow_info {
		char * 	cn;
		char * 	passwd;
		long int lstchg;
		long int sp_min;
		long int sp_max;
		long int sp_warn;
		long int sp_inact;
		long int sp_expire;
		unsigned long sp_flag;
		uid_t	uid;
		int qflag;
	};

static void init_nw_shadow_info (struct nw_shadow_info *si,int qflag){
	si->cn=NULL;
	si->passwd=NULL;
	si->lstchg=0;
	si->sp_min=0;
	si->sp_max=0;
	si->sp_warn=0;
	si->sp_inact=0;
	si->sp_expire=0;
	si->sp_flag=-1;
	si->uid=(uid_t)-1;
	si->qflag= qflag;
}

static void free_nw_shadow_info (struct nw_shadow_info *si){
#define FREEFIELD(x) do if (si->x) {free(si->x) ; si->x=NULL;} while (0);
	FREEFIELD(cn);
	FREEFIELD(passwd);
	si->lstchg=0;
	si->sp_min=0;
	si->sp_max=0;
	si->sp_warn=0;
	si->sp_inact=0;
	si->sp_expire=0;
	si->sp_flag=-1;
	si->uid=(uid_t)-1;
	si->qflag=0;
#undef FREEFIELD
}


static int fix_nw_shadow_info (struct nw_shadow_info *si ,struct nss_ncp_conf *conf){
/* fill NDS missing attributes with default values
 	any user have a cn, and if uid was not found in NDS we will later consider
	that the corresponding Unix account is not activated unless a fallback value is defined in conf
*/
	if (si->cn) {
		if (si->uid== (uid_t)-1)
			si->uid=conf->fallbackUid;
		si->lstchg= si->lstchg ? si->lstchg : time(NULL)/24/3600;
		si->sp_min= si->sp_min ? si->sp_min: 0;
		si->sp_max= si->sp_max ? si->sp_max: 99999;
		si->sp_warn=si->sp_warn ? si->sp_warn: 7;
		si->passwd= si->passwd ? si->passwd: strdup("!!"); // cannot read passwd from NDS
		if (!si->passwd) {
			traceForce(conf->debug,LOG_ERR, "not enough memory when allocating password for shadow user %s\n ",si->cn);
			return 1;

		}
	}else {
		si->uid = (uid_t)-1; // unable to read CN (NDS browse rights not set) , skip it !
	}
	return 0;
}

/***
 return found info to caller in the format expected by NSS that is:
 filling the shadow structure as pointers to the passed buffer
    char *sp_namp;              // Login nae.
    char *sp_pwdp;              // Encrypted password.
    long int sp_lstchg;         // Date of last change.
    long int sp_min;            // Minimum number of days between changes.
    long int sp_max;            // Maximum number of days between changes.
    long int sp_warn;           // Number of days to warn user to change the password.
    long int sp_inact;          // Number of days the account may be inactive.
    long int sp_expire;         // Number of days since 1970-01-01 until account expires.
    unsigned long int sp_flag;  // Reserved.
*****/

static enum nss_status nw_shadow_info_to_shadow(struct nw_shadow_info si,struct spwd *spw,
	char * buffer, size_t buflen, int * errnop, struct nss_ncp_conf *conf){

	if (si.uid != (uid_t)-1) {
		spw->sp_namp=copy_to_buffer (&buffer,&buflen,si.cn);
		if (!spw->sp_namp) goto outnomem;
		spw->sp_pwdp=copy_to_buffer (&buffer,&buflen,si.passwd);
		if (!spw->sp_pwdp) goto outnomem;

		spw->sp_lstchg=si.lstchg;
		spw->sp_min=si.sp_min;
		spw->sp_max=si.sp_max;
		spw->sp_warn=si.sp_warn;
		spw->sp_inact=si.sp_inact;
		spw->sp_expire=si.sp_expire;
		spw->sp_flag=si.sp_flag;
		*errnop=0;
		return NSS_STATUS_SUCCESS;
outnomem:
		traceForce(conf->debug,LOG_ERR, "not enough memory when copying nw_shadow_info to shadow for %s\n ",si.cn);
		*errnop=ERANGE;
		return NSS_STATUS_TRYAGAIN;
	} else {
		trace(conf->debug,LOG_NOTICE, "user %s has no Unix UID in NDS\n ", si.cn);
		*errnop=ENOENT;
		return NSS_STATUS_NOTFOUND;
	}
}

static void print_nw_shadow_info (struct nw_shadow_info si){

	printf("%s[%d]:%s:%ld:%ld:%ld:%ld:%ld:%ld:%ld\n",si.cn,si.uid,si.passwd,si.lstchg,si.sp_min,si.sp_max,si.sp_warn,si.sp_inact,si.sp_expire,si.sp_flag);
}

static void print_shadow (struct spwd spw){
	printf("%s:%s:%ld:%ld:%ld:%ld:%ld:%ld:%ld\n",spw.sp_namp,spw.sp_pwdp,spw.sp_lstchg,spw.sp_min,spw.sp_max,spw.sp_warn,spw.sp_inact,spw.sp_expire,spw.sp_flag);
}



/****************************************** internal structure for storing NDS user's groups infos ***/
struct nw_user_group_info {
		char * 	cn;
		uid_t	uid;
		gid_t*	groups;
		size_t	used;
		size_t  alloc;
		int     qflag;
	};


static void init_nw_user_group_info (struct nw_user_group_info *ui,int qflag){
	ui->cn=NULL;
	ui->uid=(uid_t)-1;
	ui->groups=NULL;
	ui->used=0;
	ui->alloc=0;
	ui->qflag= qflag;
}

static void free_nw_user_group_info (struct nw_user_group_info *ui){
#define FREEFIELD(x) do if (ui->x) {free(ui->x) ; ui->x=NULL;} while (0);
	FREEFIELD(cn);
	FREEFIELD(groups);
	ui->used=0;
	ui->alloc=0;
	ui->uid=(uid_t)-1;
	ui->qflag=0;
#undef FREEFIELD
}


static int fix_nw_user_group_info (struct nw_user_group_info *ui ,struct nss_ncp_conf * conf){
/* fill NDS missing attributes with default values
 	any user have a cn, and if uid was not found in NDS we consider
	that the corresponding Unix account is not activated unless a fallback value is defined in conf
*/
	if (ui->cn) {
  		if (ui->uid== (uid_t)-1)
			ui->uid=conf->fallbackUid;
	}else {
		ui->uid = (uid_t)-1; // unable to read CN (NDS browse rights not set) , skip it !
	}
	return 0;
}


// return found info to caller in the format expected by NSS that is:
// filling up groups array
// code similar to nss_ldap

static enum nss_status nw_user_group_info_to_groups (struct nw_user_group_info ui,gid_t group, long int *start,
		 long int *size, gid_t * groups,long int limit,int *errnop,struct nss_ncp_conf * conf) {

	if (ui.uid != (uid_t)-1) {
		size_t i;
		for (i=0; i < ui.used; i++) {
			gid_t gid=ui.groups[i];
			if (gid != group) {  // group number to skip
				if (*start == *size) {
					if  (limit <=0) {  // no more space, realloc if permitted (limit <=0)
						gid_t* ngroups=realloc(groups, 2* *size * sizeof(*groups));
						if (!ngroups) {
							goto outnomem;
						}
						groups=ngroups;
						*size *=2;
					}else  // no reallocation permitted, leave returning found groups so far
						break;
				}
				groups[*start]=gid;
				*start +=1;
				if (*start ==limit) {
					break;  // stop storing gids and return found groups so far
				}
			}
		}
		*errnop=0;
		return NSS_STATUS_SUCCESS;
outnomem:
		traceForce(conf->debug,LOG_ERR, "initgroups: not enough memory when reallocating group array for %s \n ",ui.cn);
		*errnop=ERANGE;
		return NSS_STATUS_TRYAGAIN;
	} else {
		trace(conf->debug,LOG_NOTICE, "user %s has no Unix UID in NDS\n ", ui.cn);
		*errnop=ENOENT;
		return NSS_STATUS_NOTFOUND;
	}
}

static void print_nw_user_group_info (struct nw_user_group_info ui){
	size_t i;

	printf("%s:%d:%zd:%zd:",ui.cn,ui.uid,ui.used,ui.alloc);
	for (i=0;i <ui.used;i++)
		printf("%d ",ui.groups[i]);
	printf("\n");
}

static void print_user_groups(gid_t * groups, long int start, long int size){
	long int i;
	printf("start=%ld size=%ld\n",start,size);
	for (i=0; i<start; i++)
		printf("%d ",groups[i]);
	printf("\n");
}


/******************************************** generic NDS_Reading function ********************/
// calllback function prototypes
struct attrop {
	const NWDSChar *attrname;
	NWDSCCODE (*getval)(NWDSContextHandle, const void *val, void *arg);
	enum SYNTAX synt;
};


static NWDSCCODE
nds_read_attrs(NWDSContextHandle ctx, const NWDSChar * objname, void *arg, const struct attrop *atlist,int debugMode)
{
	Buf_T *attrlist;
	Buf_T *info;
	NWDSCCODE dserr;
	const struct attrop *ptr;
	nuint32 iterHandle;

	dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrlist);
	if (dserr) {
		traceForce(debugMode,LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
		goto bailout;
	}
	dserr = NWDSInitBuf(ctx, DSV_READ, attrlist);
	if (dserr) {
		traceForce(debugMode,LOG_WARNING, "NWDSInitBuf() failed with %s\n", strnwerror(dserr));
		goto bailoutbuf1;
	}
	for (ptr = atlist; ptr->attrname; ptr++) {
		dserr = NWDSPutAttrName(ctx, attrlist, ptr->attrname);
		if (dserr) {
			traceForce(debugMode,LOG_WARNING, "NWDSPutAttrName(%s) failed with %s\n", ptr->attrname, strnwerror(dserr));
			goto bailoutbuf1;
		}
	}
	dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &info);
	if (dserr) {
		traceForce(debugMode,LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
		goto bailoutbuf1;
	}
	iterHandle = NO_MORE_ITERATIONS;
	do {
		NWObjectCount attrs;

		dserr = NWDSRead(ctx, objname, DS_ATTRIBUTE_VALUES, 0, attrlist, &iterHandle, info);
		if (dserr) {
			if (dserr == ERR_NO_SUCH_ATTRIBUTE)
				dserr = 0;// attribute is missing . OK
			else
				traceForce(debugMode,LOG_WARNING, "NWDSRead() failed for %s with %s\n", objname,strnwerror(dserr));
			goto bailoutbuf2;
		}
		dserr = NWDSGetAttrCount(ctx, info, &attrs);
		if (dserr) {
			traceForce(debugMode,LOG_WARNING, "NWDSGetAttrCount() failed with %s\n", strnwerror(dserr));
			goto bailoutcloit;
		}
		while (attrs--) {
			NWDSChar attrname[MAX_SCHEMA_NAME_BYTES];
			enum SYNTAX synt;
			NWObjectCount vals;

			dserr = NWDSGetAttrName(ctx, info, attrname, &vals, &synt);
			if (dserr) {
				traceForce(debugMode,LOG_WARNING, "NWDSGetAttrName() failed with %s\n", strnwerror(dserr));
				goto bailoutcloit;
			}
			while (vals--) {
				size_t sz;
				void *val;

				dserr = NWDSComputeAttrValSize(ctx, info, synt, &sz);
				if (dserr) {
					traceForce(debugMode,LOG_WARNING, "NWDSComputeAttrValSize() failed with %s\n", strnwerror(dserr));
					goto bailoutcloit;
				}
				val = malloc(sz);
				if (!val) {
					traceForce(debugMode,LOG_WARNING, "malloc() failed with %s\n", strnwerror(ENOMEM));
					goto bailoutcloit;
				}
				dserr = NWDSGetAttrVal(ctx, info, synt, val);
				if (dserr) {
					free(val);
					traceForce(debugMode,LOG_WARNING, "NWDSGetAttrVal() failed with %s\n", strnwerror(dserr));
					goto bailoutcloit;
				}
				for (ptr = atlist; ptr->attrname; ptr++) {
					if (!strcasecmp(ptr->attrname, attrname))
						break;
				}
				if (ptr->getval) {
					if (ptr->synt != synt) {
						traceForce(debugMode,LOG_WARNING, "Incompatible tree schema, %s has syntax %d instead of %d\n", attrname, synt, ptr->synt);
					} else {
						// ajout PP  dserr= !!! en cas de pb mémoire
						dserr = ptr->getval(ctx, val, arg);
					}
				}
				free(val);
				if (dserr) {
					goto bailoutcloit;
				}
			}
		}
	} while (iterHandle != NO_MORE_ITERATIONS);
bailoutcloit:;
	if (iterHandle != NO_MORE_ITERATIONS) {
		NWDSCCODE dserr2 = NWDSCloseIteration(ctx, DSV_READ, iterHandle);
		if (dserr2) {
			traceForce(debugMode,LOG_WARNING, "NWDSCloseIteration() failed with %s\n", strnwerror(dserr2));
		}
	}
bailoutbuf2:;
	NWDSFreeBuf(info);
bailoutbuf1:;
	NWDSFreeBuf(attrlist);
bailout:;
	return dserr;
}


/*****************************************************GET USER INFO FROM NDS *************/
/************************************ helper functions to extract NDS  properties ********/
// called as callbacks by nds_read_attrs

static NWDSCCODE nds_user_cn(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	if (!ui->cn) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->cn = v;
		trace(ui->qflag,LOG_NOTICE, "got a Unix cn %s from %s\n ", ui->cn, ATTR_CN);
	}
	return 0;
}

static NWDSCCODE nds_user_unixuid(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	if (ui->uid == (uid_t) -1) {
		ui->uid = *(const Integer_T *) val;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(ui->qflag,LOG_NOTICE, "got a Unix ID %d from %s\n ", ui->uid, ATTR_UID);
	}
	return 0;
}

static NWDSCCODE nds_user_unixpgid(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	if (ui->gid == (gid_t) -1) {
		ui->gid = *(const Integer_T *) val;
		// talk a bit (real NDS8 attribute or dummy ?)
			trace(ui->qflag,LOG_NOTICE, "got a Unix PGID %d from %s\n ", ui->gid, ATTR_PGID);
	}
	return 0;
}

// this is the same founction as above ???
// does Netware has two synonyms for the same property (UNIX:GID"
// and UNIX:Primary GroupID???
static NWDSCCODE nds_user_unixgid(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	if (ui->gid == (gid_t) -1) {
		ui->gid = *(const Integer_T *) val;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(ui->qflag,LOG_NOTICE, "got a Unix GID %d from %s\n ", ui->gid, ATTR_GID);
	}
	return 0;
}

static NWDSCCODE nds_user_unixhome(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	if (!ui->dir) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->dir = v;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(ui->qflag,LOG_NOTICE, "got a Unix Home %s from %s\n ", ui->dir, ATTR_HOME);
	}
	return 0;
}

static NWDSCCODE nds_user_unixshell(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	if (!ui->shell) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->shell = v;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(ui->qflag,LOG_NOTICE, "got a Unix shell %s from %s\n ", ui->shell, ATTR_SHELL);
	}
	return 0;
}

static NWDSCCODE nds_update_gecos(struct nw_user_info *ui, const char *str) {
	char *v;
	size_t sadd = strlen(str) + 1;

	if (ui->gecos) {	// already got the name
		size_t sold = strlen(ui->gecos);
		trace(ui->qflag,LOG_NOTICE, "extending gecos %d %d\n",sadd,sold);

		v = realloc(ui->gecos, sold + 1 + sadd);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		v[sold] = ',';
		memcpy(v + sold+1, str, sadd); // bizarre sold a disparu dans pam_ncp ????
	} else {
		trace(ui->qflag,LOG_NOTICE, "creating gecos %d \n",sadd);
		v = malloc(sadd);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		memcpy(v, str, sadd);
	}
	ui->gecos = v;
	return 0;
}

// PP we append the Comment after the full name, separated by a comma
static NWDSCCODE nds_user_unixcomment(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;

	// talk a bit (real NDS8 attribute or dummy ?)
	trace(ui->qflag,LOG_NOTICE, "got a Unix Comment %s from %s\n ", (const char *) val, ATTR_COM);
	return nds_update_gecos(ui, (const char *) val);
}

// PP can be any naming attribute returning a SYN_CI_STRING see define before nds_user_info()
// PP we add the name before any comment that can be there
static NWDSCCODE nds_user_gecos(NWDSContextHandle ctx, const void *val, void *arg) {
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	NWDSCCODE err;

	trace(ui->qflag,LOG_NOTICE, "before full name gecos is %s\n ", ui->gecos ? : "(null)");
	err = nds_update_gecos(ui, (const char *) val);
	if (err)
		return err;
	trace(ui->qflag,LOG_NOTICE, "after full name gecos is %s\n ", ui->gecos);
	return 0;
}

// PP: id no NDS8 is present, collect the user's basic  Unix informations from  the location
// strings with the format X:nnnnnnnn  , X = [U,G,H,S,P,O,C,Z] upper of lower case
// Of course, even if NDS8 IS present, we still look at these, just in case the migration
// is not complete and to look for the user's ZENFLAG

static NWDSCCODE nds_user_location(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	const char *pt = (const char *) val;
	char *v;
	int n;
	int err;

	trace(ui->qflag,LOG_NOTICE, "start of NW location got %s\n ", pt);
	if (strlen(pt) > 2 && pt[1] == ':') {
		const char *cur_pt = pt + 2;
		switch (*pt) {
		case 'u':	//user ID leading spaces not significant
		case 'U':
			if (ui->uid == (uid_t) -1) {	// do not overwrite a DS 8 answer
				switch (getnumber(&n, &cur_pt)) {
					case 0:
						ui->uid = n;
						break;
					default:
						traceForce(ui->qflag,LOG_ERR, "Invalid user ID %s\n", pt);
				}
			}
			break;
		case 'g':	// primary group number GID leading spaces not significant
		case 'G':
			if (ui->gid == (gid_t) -1) {	// do not overwrite a DS 8 answer
				switch (getnumber(&n, &cur_pt)) {
					case 0:
						ui->gid = n;
						break;
					default:
					traceForce(ui->qflag,LOG_ERR, "Invalid primary user GID %s\n", pt);
				}
			}
			break;
		case 'h':	// home Unix all spaces significant (must have none ?)
		case 'H':
			if (!ui->dir) {	// do not overwrite a DS 8 answer
				v = strdup(cur_pt);
				if (!v) {
					traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
					return ENOMEM;
				}
				ui->dir = v;
			}
			break;
		case 's':	//shell Unix all spaces significant (must have none ?)
		case 'S':
			if (!ui->shell) {	// do not overwrite a DS 8 answer
				v = strdup(cur_pt);
				if (!v) {
					traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
					return ENOMEM;
				}
				ui->shell = v;
			}
			break;
		case 'c':	// comment all spaces significant. Will be appended to the gecos naming
		case 'C':	// attribute with a comma and set by calling chfn -f xxxx -o xxxx
			// if comma are present in the string chfn will fails
			trace(ui->qflag,LOG_NOTICE, "before comment gecos is %s\n ", ui->gecos);
			err = nds_update_gecos(ui, cur_pt);
			if (err)
				return err;
			trace(ui->qflag,LOG_NOTICE, "gecos %s\n ", ui->gecos);
			break;
		}
	}
	return 0;
}

static NWDSCCODE nds_user_location2(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	const char *pt = (const char *) val;
	int n;

	trace(ui->qflag,LOG_NOTICE, "start of NW location got %s\n ", pt);

	if (strlen(pt) > 2 && pt[1] == ':') {
		const char *cur_pt = pt + 2;
		switch (*pt) {
		case 'u':	//user ID leading spaces not significant
		case 'U':
			if (ui->uid == (uid_t) -1) {	// do not overwrite a DS 8 answer
				switch (getnumber(&n, &cur_pt)) {
					case 0:
						ui->uid = n;
						break;
					default:
						traceForce(ui->qflag,LOG_ERR, "Invalid user ID %s\n", pt);
				}
			}
		}

	}
	return 0;
}


static NWDSCCODE nds_user_info(NWDSContextHandle ctx, const NWDSChar * objname, void *ui, int modeDebug){
	static const struct attrop atlist[] = {
		{ATTR_CN, nds_user_cn, SYN_CN},
		{ATTR_UID, nds_user_unixuid, SYN_UID},
		{ATTR_PGID, nds_user_unixpgid, SYN_PGID},
		{ATTR_GID, nds_user_unixgid, SYN_GID},
		{ATTR_HOME, nds_user_unixhome, SYN_HOME},
		{ATTR_SHELL, nds_user_unixshell, SYN_SHELL},
		{ATTR_COM, nds_user_unixcomment, SYN_COM},
		{ATTR_GECOS, nds_user_gecos, SYN_CI_STRING},
		{NULL, NULL, SYN_UNKNOWN}
	};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_user_location, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}
	};

	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, ui, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, ui, atlist2,modeDebug);
}


/*****************   GET limited user's information (used when searching for group members) */
/* we search only UID either stored in NDS8 attributes ( Real or Dummy) or in Location */
static NWDSCCODE nds_user_info2(NWDSContextHandle ctx, const NWDSChar * objname, void *ui,int modeDebug){
	static const struct attrop atlist[] = {
		{ATTR_CN, nds_user_cn, SYN_CN},
		{ATTR_UID, nds_user_unixuid, SYN_UID},
		{NULL, NULL, SYN_UNKNOWN}
	};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_user_location2, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}
	};

	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, ui, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, ui, atlist2,modeDebug);
}


/*****************************************************GET GROUP INFO FROM NDS *************/


/** gather Unix groups informations */

static NWDSCCODE nds_group_cn(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_group_info *gi = (struct nw_group_info *) arg;

	if (!gi->cn) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(gi->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		gi->cn = v;
		trace(gi->qflag,LOG_NOTICE, "got a Unix cn %s from %s\n ", gi->cn, ATTR_CN);
	}
	return 0;
}


static NWDSCCODE nds_group_members(NWDSContextHandle ctx, const void *val, void *arg){

	struct nw_group_info *gi = (struct nw_group_info *) arg;
	NWDSCCODE err=0;
	struct nw_user_info ui;
	struct nw_group_member* newMember;


	init_nw_user_info(&ui,gi->qflag);
	//check whether member has some Unix properties
	err=nds_user_info2(ctx, (const char *)val, &ui,gi->qflag);
	if (!err && ui.uid !=(uid_t)-1 && ui.cn)  {
		char *v = strdup(ui.cn);
		if (!v ) {
			traceForce(gi->qflag,LOG_WARNING, "Not enough memory for adding member %s tp group %s\n",ui.cn,gi->cn);
			err=ENOMEM;
		} else {
			newMember=malloc(sizeof(*newMember));
			if (newMember) {
				newMember->member=v;
				newMember->next=NULL;
				if (!gi->first) gi->first=newMember;
				else gi->last->next=newMember;
				gi->last=newMember;
				gi->nbMembers++;
				trace(gi->qflag,LOG_NOTICE, "got a Unix members %s from %s\n ", ui.cn,gi->cn);
			} else {
				free(v);
				traceForce(gi->qflag,LOG_WARNING, "Not enough memory for adding member %s tp group %s\n",ui.cn,gi->cn);
				err=ENOMEM;
			}
		}
	}
	free_nw_user_info(&ui);
	return err;
}


static NWDSCCODE nds_group_unixgid(NWDSContextHandle ctx, const void* val, void* arg) {
	struct nw_group_info* gi = (struct nw_group_info*)arg;

	if (gi->gid == (gid_t)-1) {
		gi->gid = *(const Integer_T*)val;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(gi->qflag,LOG_NOTICE, "got a Unix GID %d from %s\n ", gi->gid, ATTR_GID);
	}
	return 0;
}

// PP: id no NDS8 is present, collect the group Unix ID from one of the location
// string with the format G:nnn
// can also used to specify a name of unix group different of the NDS'one
// eg. everyone --> users or staff --> root
static NWDSCCODE nds_group_location(NWDSContextHandle ctx, const void* val, void* arg) {

	struct nw_group_info* gi = (struct nw_group_info*)arg;
	const char *pt= (const char*) val;
	int n;

	if (strlen(pt)>2 && pt[1]==':') {
 		const char* cur_pt=pt+2;
          	switch (*pt) {
          	case 'g':
          	case 'G':if (gi->gid == (gid_t)-1) {
				switch (getnumber(&n,&cur_pt)) {
                        		case 0: gi->gid=n; break;
                        		default:traceForce(gi->qflag,LOG_ERR, "Invalid group GID %s for %s\n",pt,gi->cn);
				}
			}
			break;
		case 'n':
		case 'N':  // unix equivalent name
			if (!gi->alias) {
				char* v = strdup(cur_pt);
				if (!v) {
					traceForce(gi->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
					return ENOMEM;
				}
				gi->alias = v;
				trace(gi->qflag,LOG_NOTICE, "group:got a Unix alias %s from %s\n ", gi->alias, gi->cn);
			}
        		break;
		}
       	}
       return 0;
}


static NWDSCCODE nds_group_info(NWDSContextHandle ctx, const NWDSChar* objname, void * gi, int modeDebug) {
	static const struct attrop atlist[] = {
		{ ATTR_CN, 	        nds_group_cn,	SYN_CN },
		{ ATTR_GID, 	        nds_group_unixgid,	SYN_GID },
		{ ATTR_MEMBERS,		nds_group_members,	SYN_MEMBERS},
		{ NULL,			NULL,			SYN_UNKNOWN }};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_group_location, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}};


	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, gi, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, gi, atlist2,modeDebug);
}


/*****************   GET limited group's information (used when searching for groups  to which a user belongs) */
static NWDSCCODE nds_group_location2(NWDSContextHandle ctx, const void* val, void* arg) {

	struct nw_group_info* gi = (struct nw_group_info*)arg;
	const char *pt= (const char*) val;
	int n;

	if (strlen(pt)>2 && pt[1]==':') {
		const char* cur_pt=pt+2;
		switch (*pt) {
			case 'g':
			case 'G':if (gi->gid == (gid_t)-1) {
					switch (getnumber(&n,&cur_pt)) {
						case 0: gi->gid=n; break;
						default:traceForce(gi->qflag,LOG_ERR, "Invalid group GID %s for %s\n",pt,gi->cn);
					}
				}
			break;
		}
	}
	return 0;
}

/* we search only GID either stored in NDS8 attributes ( Real or Dummy) or in Location */
static NWDSCCODE nds_group_info2(NWDSContextHandle ctx, const NWDSChar* objname, void * gi, int modeDebug) {

	static const struct attrop atlist[] = {
		{ ATTR_GID, 	        nds_group_unixgid,	SYN_GID },
		{ NULL,			NULL,			SYN_UNKNOWN }};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_group_location2, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}};


	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, gi, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, gi, atlist2,modeDebug);
}


/*****************************************************GET SHADOW INFO FROM NDS *************/

static NWDSCCODE nds_shadow_cn(NWDSContextHandle ctx, const void *val, void *arg){

	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;

	if (!si->cn) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(si->qflag & QF_DEBUG,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		si->cn = v;
		trace(si->qflag & QF_DEBUG,LOG_NOTICE, "shadow:got a Unix cn %s from %s\n ", si->cn, ATTR_CN);
	}
	return 0;
}


static NWDSCCODE nds_shadow_unixuid(NWDSContextHandle ctx, const void *val, void *arg){

	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	if (si->uid == (uid_t) -1) {
		si->uid = *(const Integer_T *) val;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(si->qflag & QF_DEBUG,LOG_NOTICE, "shadow: got a Unix ID %d from %s\n ", si->uid, ATTR_UID);
	}
	return 0;
}


// PP: id no NDS8 is present, collect the user's basic  Unix informations from  the location
// strings with the format X:nnnnnnnn  , X = [U,G,H,S,P,O,C,Z] upper of lower case
// Of course, even if NDS8 IS present, we still look at these, just in case the migration
// is not complete and to look for the user's ZENFLAG

static NWDSCCODE nds_shadow_location(NWDSContextHandle ctx, const void *val, void *arg){

	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	const char *pt = (const char *) val;
	int n;

	trace(si->qflag & QF_DEBUG,LOG_NOTICE, "shadow: start of NW location got %s\n ", pt);

	if (strlen(pt) > 2 && pt[1] == ':') {
		const char *cur_pt = pt + 2;
		switch (*pt) {
		case 'u':	//user ID leading spaces not significant
		case 'U':
			if (si->uid == (uid_t) -1) {	// do not overwrite a DS 8 answer
				switch (getnumber(&n, &cur_pt)) {
				case 0:
					si->uid = n;
					break;
				default:
					traceForce(si->qflag & QF_DEBUG,LOG_ERR, "shadow:Invalid user ID %s\n", pt);
				}
			}
			break;
		}

	}
	return 0;
}

static NWDSCCODE nds_shadow_pwd_expire(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	si->sp_expire = *(const Time_T *) val/3600/24;
	return 0;
}

static NWDSCCODE nds_shadow_int_pwd_expire(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	si->sp_min = *(const Integer_T *) val/3600/24;
	si->sp_max = *(const Integer_T *) val/3600/24;
	return 0;
}

static NWDSCCODE nds_shadow_acct_expire(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	si->sp_inact = *(const Time_T *) val/3600/24;
	return 0;
}

static NWDSCCODE nds_shadow_grace_pwd_expire (NWDSContextHandle ctx, const void *val, void *arg) {
// in NDS it is the number of grace login, in Unix the number of DAYS
	struct nw_shadow_info *si = (struct nw_shadow_info *) arg;
	si->sp_warn = *(const Integer_T *) val;
	return 0;
}


/*****************************************************GET USER SHADOW INFOS FROM NDS *************/

static NWDSCCODE nds_shadow_info(NWDSContextHandle ctx, const NWDSChar * objname, void *si,int modeDebug){
	static const struct attrop atlist[] = {
		{ATTR_CN, nds_shadow_cn, SYN_CN},
		{ATTR_UID, nds_shadow_unixuid, SYN_UID},
		{ATTR_DATE_PWD_EXPIRE,nds_shadow_pwd_expire,SYN_TIME},
		{ATTR_DATE_ACCT_EXPIRE,nds_shadow_acct_expire,SYN_TIME},
		{ATTR_INT_PWD_EXPIRE,nds_shadow_int_pwd_expire,SYN_INTERVAL},
		{ATTR_GRACE_LIMIT,nds_shadow_grace_pwd_expire,SYN_INTEGER},
		{NULL, NULL, SYN_UNKNOWN}
	};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_shadow_location, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}
	};

	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, si, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, si, atlist2,modeDebug);
}


/***************get all group id of groups  userName belongs to *********************/

static NWDSCCODE nds_user_cn2(NWDSContextHandle ctx, const void *val, void *arg){

	struct nw_user_group_info *ui = (struct nw_user_group_info *) arg;
	if (!ui->cn) {
		char *v = strdup((const char *) val);
		if (!v) {
			traceForce(ui->qflag,LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->cn = v;
		trace(ui->qflag & QF_DEBUG,LOG_NOTICE, "got a Unix cn %s from %s\n ", ui->cn, ATTR_CN);
	}
	return 0;
}



static NWDSCCODE nds_user_unixuid2(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_group_info *ui = (struct nw_user_group_info *) arg;
	if (ui->uid == (uid_t) -1) {
		ui->uid = *(const Integer_T *) val;
		// talk a bit (real NDS8 attribute or dummy ?)
		trace(ui->qflag & QF_DEBUG,LOG_NOTICE, "got a Unix ID %d from %s\n ", ui->uid, ATTR_UID);
	}
	return 0;
}


static NWDSCCODE nds_user_location3(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_group_info *ui = (struct nw_user_group_info *) arg;
	const char *pt = (const char *) val;
	int n;

	trace(ui->qflag & QF_DEBUG,LOG_NOTICE, "start of NW location got %s\n ", pt);

	if (strlen(pt) > 2 && pt[1] == ':') {
		const char *cur_pt = pt + 2;
		switch (*pt) {
		case 'u':	//user ID leading spaces not significant
		case 'U':
			if (ui->uid == (uid_t) -1) {	// do not overwrite a DS 8 answer
				switch (getnumber(&n, &cur_pt)) {
				case 0:
					ui->uid = n;
					break;
				default:
					traceForce(ui->qflag & QF_DEBUG,LOG_ERR, "Invalid user ID %s\n", pt);
				}
			}
			break;
		}

	}
	return 0;
}

static NWDSCCODE nds_get_one_user_group(NWDSContextHandle ctx, const void *val, void *arg){
	struct nw_user_group_info *ui = (struct nw_user_group_info *) arg;
	struct nw_group_info gi;
	NWDSCCODE ccode;

	init_nw_group_info(&gi,ui->qflag);
	//check whether group has some Unix properties
	trace(ui->qflag & QF_DEBUG,LOG_NOTICE, "found group  %s for user %s\n ", (const char*)val, ui->cn);
	ccode=nds_group_info2(ctx,(const char *)val,&gi,ui->qflag);
	// found a real GID , no fallback here else all groups would have the same number !!!!
	if (!ccode && gi.gid != (gid_t) -1) {
			trace(ui->qflag & QF_DEBUG,LOG_NOTICE, "found group GID %d for user %s\n ", gi.gid, ui->cn);
		if (ui->used >=ui->alloc) {
			gid_t* np;
			size_t ns;
			if (ui->groups) {
				ns=ui->alloc +8;
				np=(gid_t*)realloc (ui->groups,ns*sizeof(gid_t));
			}else {
				ns=8;
				np=(gid_t*)malloc (ns*sizeof(gid_t));
			}
			if (!np) {
				traceForce(ui->qflag & QF_DEBUG,LOG_WARNING, "Not enough memory for allocating GID table\n");
				return ENOMEM;
			}
			ui->groups=np;
			ui->alloc=ns;
		}
		ui->groups[ui->used++]=gi.gid;
	}
	free_nw_group_info(&gi);
	return ccode;
}


static NWDSCCODE nds_get_user_groups(NWDSContextHandle ctx, const NWDSChar * objname, void *ui, int modeDebug){
// get all groups of userName
static const struct attrop atlist[] = {
		{ATTR_CN, nds_user_cn2, SYN_CN},
		{ATTR_UID, nds_user_unixuid2, SYN_UID},
		{ATTR_GRP_MBS,	nds_get_one_user_group,	SYN_GRP_MBS},
		{NULL, NULL, SYN_UNKNOWN}
	};

	static const struct attrop atlist2[] = {
		{ATTR_LOCATION, nds_user_location3, SYN_LOCATION},
		{NULL, NULL, SYN_UNKNOWN}
	};

	NWDSCCODE err;

// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
	err = nds_read_attrs(ctx, objname, ui, atlist,modeDebug);
	if (err)
		return err;
	return nds_read_attrs(ctx, objname, ui, atlist2,modeDebug);

}

/*******************************************************************************************************************/
static NWDSCCODE CreateContextAndConn ( NWDSContextHandle *context,NWCONN_HANDLE *conn, struct nss_ncp_conf* conf) {
	NWDSCCODE ccode;
	nuint32 contextFlags;

	trace(conf->debug, LOG_NOTICE,"Entering create context and conn");

	ccode = NWDSCreateContextHandle(context);
	if(ccode) {
		traceForce(conf->debug,LOG_WARNING,"Error creating context.\n");
		 goto Exit1;
	}

	trace(conf->debug, LOG_NOTICE,"CreateContextHandle OK");

	//ccode=NWDSSetContext(*context, DCK_NAME_CONTEXT, conf->startCtx);

	ccode=NWDSSetContext(*context, DCK_NAME_CONTEXT, "[Root]");
	if(ccode){
		traceForce(conf->debug,LOG_WARNING,"Error NWDSSetContext(): %d\n",ccode);
		 goto Exit2;
	}

	trace(conf->debug, LOG_NOTICE,"SetContext OK");

	ccode= NWDSGetContext(*context, DCK_FLAGS, &contextFlags);
	if( ccode){
		traceForce(conf->debug,LOG_WARNING,"NWDSGetContext (DCK_FLAGS) failed, returns %d\n", ccode);
		goto Exit2;
	}

	trace(conf->debug, LOG_NOTICE,"GetContext OK");

	contextFlags|= DCV_TYPELESS_NAMES;
	ccode= NWDSSetContext( *context, DCK_FLAGS, &contextFlags);
	if( ccode)	{
		traceForce(conf->debug,LOG_WARNING,"NWDSSetContext (DCK_FLAGS DCV_TYPELESS_NAMES) failed, returns %d\n", ccode);
		goto Exit2;
	}

	trace(conf->debug, LOG_NOTICE,"SetContext OK");
	trace(conf->debug, LOG_NOTICE,"calling NWCCOpenConnByName ...");

	if (conf->server[0] == '/')   //using a permanent connection
		ccode = ncp_open_mount(conf->server, conn);
	else {

		if (!conf->useTree)
			ccode = NWCCOpenConnByName(NULL, conf->server, NWCC_NAME_FORMAT_BIND, 0, 0, conn);
		else
			 //ccode=NWCXAttachToTreeByName( conn, conf->server);
			ccode = NWCCOpenConnByName(NULL, conf->server, NWCC_NAME_FORMAT_NDS_TREE, 0, 0, conn);
		if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"Error: NWCCOpenConnByName failed %s\n",strnwerror(ccode));
				goto Exit2;
		}
	}

	trace(conf->debug, LOG_NOTICE,"NWCCOpenConnByName OK");

	ccode= NWDSAddConnection(*context, *conn);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"Error: NWCCOpenConnByName failed %s\n",strnwerror(ccode));
		goto Exit2;
	}

	trace(conf->debug, LOG_NOTICE,"AddConnection OK");

	if (conf->debug & QF_DEBUG) {
		char aux[512];
		aux[0] = 0;
		if (!conf->useTree) {
			NWCCGetConnInfo(*conn, NWCC_INFO_TREE_NAME, sizeof (aux), aux);
			trace(conf->debug, LOG_DEBUG, "sucessful connection to tree %s by server %s \n",aux,conf->server);
		}else {
			NWCCGetConnInfo(*conn, NWCC_INFO_SERVER_NAME, sizeof (aux), aux);
			trace(conf->debug,LOG_DEBUG, "successful connection  by NDS server %s on tree %s\n", aux,conf->server);
		}
	}

Exit2:
	if (ccode) {
		trace(conf->debug, LOG_NOTICE,"Closing context and conn due to errors");
		if(conn) NWCCCloseConn (*conn);
	        if (context) NWDSFreeContext(*context);
		*context=NULL;
		*conn=NULL;
	}
Exit1:
	trace(conf->debug, LOG_NOTICE,"leaving CreateContextAndConn");
	return ccode;
}


/****  PP TreeScaning routines ***********************/

struct TreeNode {
	struct TreeNode* left;
	struct TreeNode* right;
	struct TreeNode* next;
	struct TreeNode** pprev;
	size_t cnt;
	char name [MAX_DN_CHARS+1];
};

struct ObjectList {
	struct TreeNode* first;
	struct TreeNode* lin;
	struct TreeNode* curr;
	int dups;
	size_t uniqueObjects;
 	size_t remainObjects;
	size_t totalObjects;
	NWDSContextHandle context;
	NWCONN_HANDLE conn;
	struct nss_ncp_conf * conf;
};

static struct ObjectList* __allocTree(int dups,struct nss_ncp_conf * conf) {
	struct ObjectList* t;

	t = (struct ObjectList*)malloc(sizeof(*t));
	if (t) {
		t->first = NULL;
		t->lin = NULL;
		t->curr = NULL;
		t->dups = dups;
		t->uniqueObjects = 0;
		t->conf=conf;
	}
	return t;
}

static void __freeNode(struct TreeNode* n) {
	while (n) {
		struct TreeNode* tmp;

		__freeNode(n->left);
		tmp = n;
		n = n->right;
		free(tmp);
	}
}

static void __freeTree(struct ObjectList* t) {
	if (t) {
		struct TreeNode* n = t->first;
		if (t->conf)
			free_nss_ncp_conf(t->conf);
		free(t);
		__freeNode(n);
	}
}

static NWDSCCODE __allocNode(struct TreeNode** pn, const char* objectName) {
	struct TreeNode* n;
	size_t len=strlen(objectName);

	if (len > MAX_DN_CHARS)
		return NWE_BUFFER_OVERFLOW;
	n = (struct TreeNode*)malloc(sizeof(*n));
	if (!n)
		return ERR_NOT_ENOUGH_MEMORY;
	n->left = n->right = NULL;
	n->cnt = 1;
	memcpy(n->name, objectName, len + 1);
	*pn = n;
	return 0;
}

static NWDSCCODE __insertNode(struct ObjectList* t, const char* objectName) {
	struct TreeNode** p;
	struct TreeNode* n;
	NWDSCCODE err;

	p = &t->first;
	while ((n = *p) != NULL) {
		int cmp = strcasecmp(objectName, n->name);
		if (cmp < 0) {
			p = &n->left;
			if (!*p) {
				err = __allocNode(p, objectName);
				if (!err) {
					struct TreeNode* q = *p;

					q->next = n;
					q->pprev = n->pprev;
					n->pprev = &q->next;
					*(q->pprev) = q;
					t->uniqueObjects++;
					t->totalObjects++;
				}
				return err;
			}
		} else if (cmp > 0) {
			p = &n->right;
			if (!*p) {
				err = __allocNode(p, objectName);
				if (!err) {
					struct TreeNode* q = *p;

					q->next = n->next;
					if (q->next)
						q->next->pprev = &q->next;
					n->next = q;
					q->pprev = &n->next;
					t->uniqueObjects++;
					t->totalObjects++;
				}
				return err;
			}
		} else {
			if (t->dups) {
				n->cnt++;
				t->totalObjects++;
				return 0;
			} else {
				return EINVAL; // no dups allowed and one found
			}
		}
	}
	err = __allocNode(p, objectName);
	if (!err) {
		struct TreeNode* q = *p;
		t->lin = q;
		q->next = NULL;
		q->pprev = &t->lin;
		t->uniqueObjects++;
		t->totalObjects++;
	}
	return err;
}

static void print_nodes(struct TreeNode* n, int crlf) {
	if (n) {
		print_nodes(n->left,crlf);
		if (crlf)
			printf ("%s [%zd]\n",n->name,n->cnt);
		else
			printf ("%s [%zd]",n->name,n->cnt);
		print_nodes(n->right,crlf);
	}
}

static void print_tree(struct ObjectList* t, int crlf) {
  printf("total:%zd unique:%zd\n",t->totalObjects,t->uniqueObjects);
  print_nodes (t->first,crlf);
}


// callback for getallentxx()
static NWDSCCODE
nds_insert_info(NWDSContextHandle ctx, const NWDSChar * objectName, void *tree, int modeDebug){
	return __insertNode((struct ObjectList*)tree, objectName);
}

/* eot*/


/*** collecting in a tree every member of a group, if his UID defined in NDS */

static NWDSCCODE
nds_get_group_members(NWDSContextHandle ctx, const void *val, void *arg){

	struct ObjectList *tree = (struct ObjectList *) arg;
	NWDSCCODE ccode=0;
	struct nw_user_info ui;

	init_nw_user_info(&ui,0);
	//check whether member has some Unix properties
	ccode=nds_user_info2(ctx, (const char *)val, &ui,tree->conf->debug);
	// found a UID real (no fallback otherwise all users would have the same UID)
	if (!ccode && ui.uid !=(uid_t)-1)  {
		ccode=__insertNode(tree,(const char *)val);
	}
	free_nw_user_info(&ui);
	return ccode;
}


static NWDSCCODE
getgroupmembers(NWDSContextHandle *context,NWCONN_HANDLE *conn,
	const char* groupName,struct ObjectList * tree,	struct nss_ncp_conf* conf){
// get all members of a group

	NWDSCCODE ccode;

	static const struct attrop atlist[] = {
		{ ATTR_MEMBERS,		nds_get_group_members,	SYN_MEMBERS},
		{ NULL,			NULL,			SYN_UNKNOWN }};

	trace(conf->debug, LOG_NOTICE,"entering getgroupmembers for group %s",groupName);
	ccode=CreateContextAndConn ( context,conn,conf);
	if (ccode)
		return ccode;
	ccode=nds_read_attrs(*context, groupName, tree, atlist,conf->debug);
	trace(conf->debug, LOG_NOTICE,"leaving getgroupmembers for group %s err=%s",groupName,strnwerror(ccode));
	return ccode;
}


// static lists. Allocated by nss_ncp_setxxent_r, used buy nss_ncp_getxxent_r and released by nss_ncp_endxxent_r
// TODO :mutex and threads needed  as in nss_ldap or nss_mysql ???

static struct ObjectList* ndsUsers=NULL;
static struct ObjectList* ndsGroups=NULL;
static struct ObjectList* ndsShadows=NULL;

// description of a NDS class to be searched by getentbyxx
struct class_info {
	const char * className;
	const char * nds8Attribute; // name of ID attribute in NDS8
	const char * LID1;          // markers in L attribute for ID (U: or G:)
	const char * LID2;          // markers in L attribute for ID (u: or g:), may be in lower case
	const char * LAlias1;       // markers in L attribute for alias (N:)
	const char * LAlias2;       // markers in L attribute for alias (n:)  may be in lower case

};

// we care only about user's and group classes
static struct class_info  USER_CLASS =  {"User", ATTR_UID,"U:","u:","N:","n:"};
static struct class_info  GROUP_CLASS=  {"Group",ATTR_GID,"G:","g:","N:","n:"};


/*protoptype of callback functions used in getentbyxx*/

struct infoop {
	NWDSCCODE (*getinfo) (NWDSContextHandle ctx , const NWDSChar * objectName, void * info, int debug);
};
/*
  currently match
  	nds_user_info(context,objectName,ui,modeDebug);
	nds_user_info2(context,objectName,ui,modeDebug);
	nds_group_info(context,objectName,gi,modeDebug);
	nds_group_info2(context,objectName,gi,modeDebug);
	nds_shadow_info(context,objectName,si,modeDebug);
	nds_insert_info(context,objectName,tree,modeDebug):
*/


// generic NDS search routine
static NWDSCCODE getentbyxx(
	NWDSContextHandle 	*retContext,  	// context to return (NULL= close when leaving)
	NWCONN_HANDLE 		*retConn,	// connexion to return (NULL= close when leaving)
	struct class_info	classType,	// class to search (User of Group)
	void			*info,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info or a ObjectList)
	const struct infoop     callBack,	// routine to call for all found NDS objects
	const char		*unixName,	// Unix objet name to find in NDS (if NULL search by ID)
	uid_t			id,		// Unix ID to find in NDS , if -1 search by unixName)
	int 			allEntries,	// Ignore unixName and id and return all entries belonging to classType.className
	struct nss_ncp_conf 	*conf) 	{	// configuration record (debug...)

	NWDSContextHandle context;
	NWCONN_HANDLE conn;
	NWDSCCODE ccode;
	nuint32	iterationHandle = NO_MORE_ITERATIONS; // to be set as such at Exit4
	nuint32	countObjectsSearched;
	nuint32	objCntr;
	nuint32	objCount;
	nuint32	attrCount;
	char	objectName[MAX_DN_CHARS+1];

	// buffers
	pBuf_T	searchFilter=NULL;	// search filter
	pBuf_T	retBuf=NULL;		// result buffer for NWDSSearch
	pBuf_T	attrNames=NULL;		// specify which attribute values to return
	Filter_Cursor_T* cur=NULL;	// search expression tree temporary buffer
	Object_Info_T	objectInfo;


	//few checks

	if (!allEntries) {
		if (unixName && id !=(uid_t)-1)
			return EINVAL;
		if (!unixName && id ==(uid_t)-1)
			return EINVAL;
	}

	trace(conf->debug, LOG_NOTICE,"entering getentbyxx");

	ccode=CreateContextAndConn ( &context,&conn,conf);
	if (ccode)
		return ccode;

	trace(conf->debug, LOG_NOTICE,"context and conn OK");
	/*
	In order to search, we need:
		A Filter Cursor (to build the search expression)
		A Filter Buffer (to store the expression; used by NWDSSearch)
		A Buffer to store which attributes we need information on
		A Result Buffer (to store the search results)
	*/
	ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN,&searchFilter);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAllocBuf returned: %d\n", ccode);
		goto Exit3;
	}
	trace(conf->debug, LOG_NOTICE,"NWDSAllocBuf searchFilter OK");
	// Initialize the searchFilter buffer
	ccode = NWDSInitBuf(context,DSV_SEARCH_FILTER,searchFilter);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSInitBuf returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug, LOG_NOTICE, "NWDSInitBuf searchFilter OK");

	// Allocate a filter cursor to put the search expression
	ccode = NWDSAllocFilter(&cur);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAllocFilter returned: %d\n", ccode);
		goto Exit4;
	}
	trace(conf->debug, LOG_NOTICE,"NWDSAllocFilter cur OK");
	// Build the expression tree in cur, then place into searchFilter
	// Object Class = User
	ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"Object Class",SYN_CLASS_NAME);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken OBJECTCLASS returned: %d\n", ccode);
		goto Exit4;
	}
	ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_EQ returned: %d\n", ccode);
		goto Exit4;
	}

	ccode = NWDSAddFilterToken(cur,FTOK_AVAL,classType.className,SYN_CLASS_NAME);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AVAL User returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug, LOG_NOTICE,"AddFilter for classname OK");

	if (!allEntries) {
		ccode = NWDSAddFilterToken(cur,FTOK_AND,NULL,0);
		if (ccode ) {
			traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AND returned: %d\n", ccode);
			goto Exit4;
		}

		if (unixName) {
			/* search for the CN or an alias in location strings */
			/* CN=unixName or L=N:unixNname or l=n:unixName */
			char buf1[255];
			char buf2[255];

			if (strlen(unixName)+3 >sizeof(buf1)) {
				//buffer overflow !!!
				ccode=EINVAL;
		   		traceForce(conf->debug,LOG_WARNING,"unixName %s is too long !!!!\n", unixName);
				goto Exit4;
			}
			sprintf (buf1,"%s%s",classType.LAlias1,unixName);
			sprintf (buf2,"%s%s",classType.LAlias2,unixName);

			ccode = NWDSAddFilterToken(cur,FTOK_LPAREN,NULL,0);
			if (ccode ) {
   				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_RPAREN returned: %d\n", ccode);
				goto Exit4;
			}

			ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"CN",0);
			if (ccode) {
		 		traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME CN returned: %d\n", ccode);
				goto Exit4;
			}

			ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
			if (ccode ) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_EQ returned: %d\n", ccode);
				goto Exit4;
			}

			ccode = NWDSAddFilterToken(cur,FTOK_AVAL,unixName,SYN_CI_STRING);
			if (ccode ) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AVAL %s returned: %d\n", unixName,ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_OR,NULL,0);
			if (ccode ) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_OR returned: %d\n", ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"L",0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME L returned: %d\n", ccode);
				goto Exit4;
			}

			ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_EQ returned: %d\n", ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_AVAL,buf1,SYN_CI_STRING);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_VAL %s returned: %d\n", buf1,ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_OR,NULL,0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_OR returned: %d\n", ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"L",0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME returned: %d\n", ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_END returned: %d\n", ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_AVAL,buf2,SYN_CI_STRING);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AVAL %s returned: %d\n", buf2,ccode);
				goto Exit4;
			}
			ccode = NWDSAddFilterToken(cur,FTOK_RPAREN,NULL,0);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_RPAREN returned: %d\n", ccode);
				goto Exit4;
			}

		} else {
			if (id !=(uid_t)-1) {
				/* search by UID.or GID
				AND (L="U:xxxxx" OR L="u:xxxx" OR UNIX:UID = xxxx) for users
				AND (L="G:xxxxx" OR L="g:xxxx" OR UNIX:GID = xxxx) for groups
				*/
				char buf1[80];
				char buf2[80];

				sprintf (buf1,"%s%d",classType.LID1,id);
				sprintf (buf2,"%s%d",classType.LID2,id);

				ccode = NWDSAddFilterToken(cur,FTOK_LPAREN,NULL,0);
				if (ccode) {
		  			traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_LPAREN returned: %d\n", ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"L",0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME L returned: %d\n", ccode);
					goto Exit4;
				}

				ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_EQ returned: %d\n", ccode);
					goto Exit4;
				}

				ccode = NWDSAddFilterToken(cur,FTOK_AVAL,buf1,SYN_CI_STRING);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_VAL %s returned: %d\n", buf1,ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_OR,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_OR returned: %d\n", ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_ANAME,"L",0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME returned: %d\n", ccode);
					goto Exit4;
				}

				ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_END returned: %d\n", ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_AVAL,buf2,SYN_CI_STRING);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AVAL %s returned: %d\n", buf2,ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_OR,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_OR returned: %d\n", ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_ANAME,classType.nds8Attribute,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_ANAME returned: %d\n", ccode);
					goto Exit4;
				}

				ccode = NWDSAddFilterToken(cur,FTOK_EQ,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_END returned: %d\n", ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_AVAL,&buf1[2],SYN_INTEGER);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_AVAL %s returned: %d\n", buf2,ccode);
					goto Exit4;
				}
				ccode = NWDSAddFilterToken(cur,FTOK_RPAREN,NULL,0);
				if (ccode) {
					traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_RPAREN returned: %d\n", ccode);
					goto Exit4;
				}

			}
		}
	}
	ccode = NWDSAddFilterToken(cur,FTOK_END,NULL,0);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAddFilterToken FTOK_END returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug,  LOG_NOTICE,"AddFilterToken FTOK_END OK");

	// now place the cursor into the searchFilter buffer
	// NWDSPutFilter frees the expression tree filter (cur)
	//	so if it succeeds, set cur to NULL so it won't be freed below
	ccode = NWDSPutFilter(context,searchFilter,cur,NULL);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSPutFilter returned: %d\n", ccode);
		goto Exit4;
	}
	else
		cur=NULL;

	trace(conf->debug, LOG_NOTICE,"PutFilter OK");

	// allocate and initialize the attrNames buffer (not used , needed ???)
	ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN,&attrNames);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAllocBuf returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug, LOG_NOTICE,"AllocBuf attrNamesOK");

	ccode = NWDSInitBuf(context,DSV_SEARCH,attrNames);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSInitBuf returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug, LOG_NOTICE,"InitBuf attrNames OK");

	// Allocate a result buffer
		ccode = NWDSAllocBuf(65500,&retBuf);
	if (ccode) {
		traceForce(conf->debug,LOG_WARNING,"NWDSAllocBuf returned: %d\n", ccode);
		goto Exit4;
	}

	trace(conf->debug, LOG_NOTICE,"AllocBuf retBuf OK, start searching...");

	// PP 11-01-2003
	// with NCP_DEBUG=cleanup, if we exit here, usecount of the connection is still 1 (OK)
	// so usecount is augmented below
	//ccode=-1;
	//goto Exit4;

	iterationHandle = NO_MORE_ITERATIONS;
	// while NWDSSearch still can get some objects...( I expect 0 or 1)
	do {
		ccode = NWDSSearch(context,
				//"[Root]",
				conf->startCtx,
				DS_SEARCH_SUBTREE,
				FALSE,		// don't dereference aliases
				searchFilter,
				FALSE,		// we want ONLY attributes names
				FALSE,		// only want information in attrNames
				attrNames,
				&iterationHandle,
				0,		// reserved
				&countObjectsSearched,
				retBuf);
		if (ccode) {
			traceForce(conf->debug,LOG_WARNING,"NWDSSearch returned: %s\n", strnwerror(ccode));
			goto Exit4;
		}
		// PP 11-01-2003
		// with NCP_DEBUG=cleanup, if we exit here, usecount of the connection is now 2  (bad)
		// so it is NWDSSearch that augmented usecount !!!!
		//ccode=-1;
		//goto Exit4;


		trace(conf->debug, LOG_NOTICE,"NWDSearch OK");
		// count the object returned in the buffer
		ccode = NWDSGetObjectCount(context,retBuf,&objCount);
		if (ccode) {
			traceForce(conf->debug,LOG_WARNING,"NWDSGetObjectCount returned: %d\n", ccode);
			goto Exit4;
		}
		if (objCount <= 0) {
			ccode=-1;
			goto Exit4;
		}
		if ( !allEntries && (objCount >1)) {
			if (unixName)
				traceForce(conf->debug,LOG_WARNING,"more than one NDS entry match the name=%s criteria. ",unixName);
			else
				traceForce(conf->debug,LOG_WARNING,"more than one NDS entry match the id=%d criteria. ",id);
		}
		trace(conf->debug, LOG_NOTICE,"GetObjectCount OK");
		// for the number of objects returned...
		// for  nss it should be only one !!!
		for (objCntr=0;objCntr<objCount;objCntr++) {
			// get an object name
			ccode = NWDSGetObjectName(context,retBuf,objectName,&attrCount,&objectInfo);
			if (ccode) {
				traceForce(conf->debug,LOG_WARNING,"NWDSGetObjectName returned: %d\n", ccode);
				goto Exit4;
			}

			trace(conf->debug, LOG_NOTICE,"GetObjectName OK");

			if (callBack.getinfo) {
				ccode= callBack.getinfo(context,objectName,info,conf->debug);
				if (ccode)
					goto Exit4;
			}
		}
		trace(conf->debug, LOG_NOTICE,"callback return OK");
	} while (iterationHandle != NO_MORE_ITERATIONS);

	trace(conf->debug, LOG_NOTICE,"End of iteration  attrNamesOK");

Exit4:
	if (iterationHandle != NO_MORE_ITERATIONS){
		NWDSCCODE ccode2;
		if ((ccode2=NWDSCloseIteration(context,iterationHandle,DSV_SEARCH)) != 0) {
				traceForce(conf->debug,LOG_WARNING,"NWDSCloseIteration  returned: %d\n", ccode2);
		}
	}
	if (retBuf)
		NWDSFreeBuf(retBuf);
	if (cur)
		NWDSFreeFilter(cur, NULL);
	if (searchFilter)
		NWDSFreeBuf(searchFilter);
	if (attrNames)
		NWDSFreeBuf(attrNames);

	trace(conf->debug, LOG_NOTICE,"All buffers cleaned OK");

Exit3:
	if (ccode || !retConn){
		NWDSCCODE ccode2;
		trace(conf->debug,LOG_NOTICE,"Closing connection %lx",conn);
		ccode2=NWCCCloseConn (conn);
		if (ccode2)
	 		traceForce(conf->debug,LOG_WARNING,"NWCCloseConnection  returned: %d %s\n", ccode,strnwerror(ccode2));
	}else
		if (retConn)
			*retConn=conn;
	if (ccode || !retContext) {
	 	NWDSCCODE ccode2=NWDSFreeContext(context);
		trace(conf->debug, LOG_NOTICE,"Freeing context");
		if (ccode2)
			traceForce(conf->debug,LOG_WARNING,"NWDSFreeContext  returned: %d %s\n", ccode,strnwerror(ccode2));
	}else
		if (retContext)
			*retContext=context;
	trace(conf->debug, LOG_NOTICE,"Leaving ...");
	return ccode;
}




static NWDSCCODE
getpwdentbyxx(struct nw_user_info *ui, const char* userName, uid_t userId, struct nss_ncp_conf *conf){

static const struct infoop getInfo={nds_user_info};

return getentbyxx(
	NULL,		// context to return (NULL= close when leaving)
	NULL,		// connexion to return (NULL= close when leaving)
	USER_CLASS,	// class to search (User of Group)
	ui,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info)
	getInfo,	 // routine to call for all found NDS objects
	userName,	// Unix objet name to find in NDS (if NULL search by ID)
	userId,		// Unix ID to find in NDS , if -1 search by unixName)
	FALSE,		// Ignore objectName and id and return all entries belonging to className
	conf);		// debug flag
}


static NWDSCCODE
getgrpentbyxx(struct nw_group_info *gi, const char* groupName, gid_t groupId, struct nss_ncp_conf *conf){

	static const struct infoop getInfo={nds_group_info};

	return getentbyxx(
	NULL,		// context to return (NULL= close when leaving)
	NULL,		// connexion to return (NULL= close when leaving)
	GROUP_CLASS,	// class to search (User of Group)
	gi,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info)
	getInfo,	// routine to call for all found NDS objects
	groupName,	// Unix objet name to find in NDS (if NULL search by ID)
	groupId,	// Unix ID to find in NDS , if -1 search by unixName)
	FALSE,		// Ignore objectName and id and return all entries belonging to className
	conf);		// debug flag
}




static NWDSCCODE
getspentbyxx(struct nw_shadow_info *si, const char* userName, struct nss_ncp_conf *conf){
// in shadow we  search only by userName
	static const struct infoop getInfo={nds_shadow_info};

	return getentbyxx(
	NULL,		// context to return (NULL= close when leaving)
	NULL,		// connexion to return (NULL= close when leaving)
	USER_CLASS,	// class to search (User of Group)
	si,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info)
	getInfo,	// routine to call for all found NDS objects
	userName,	// Unix objet name to find in NDS (if NULL search by ID)
	(uid_t)-1,	// Unix ID to find in NDS , if -1 search by unixName)
	FALSE,		// Ignore objectName and id and return all entries belonging to className
	conf);		// debug flag
}

static NWDSCCODE
getusergroupsbyxx(struct nw_user_group_info *ui, const char* userName, uid_t userId, struct nss_ncp_conf *conf){

static const struct infoop getInfo={nds_get_user_groups};

	return getentbyxx(
	NULL,		// context to return (NULL= close when leaving)
	NULL,		// connexion to return (NULL= close when leaving)
	USER_CLASS,	// class to search (User of Group)
	ui,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info)
	getInfo,	 // routine to call for all found NDS objects
	userName,	// Unix objet name to find in NDS (if NULL search by ID)
	userId,		// Unix ID to find in NDS , if -1 search by unixName)
	FALSE,		// Ignore objectName and id and return all entries belonging to className
	conf);		// debug flag
}



static NWDSCCODE
getallents(NWDSContextHandle *context,NWCONN_HANDLE *conn,
	const struct class_info  classType, struct ObjectList * tree, struct nss_ncp_conf *conf){
// called by all setxxent() if not control group is present in conf
// get all entries by className
// and add them to a sorted tree in memory
// return context and conncetion for later use by

static const struct infoop getInfo={nds_insert_info};

	return getentbyxx(
	context,	// context to return (NULL= close when leaving)
	conn,		// connexion to return (NULL= close when leaving)
	classType,	// class to search (User of Group)
	tree,		// structure to fill (can be nw_user_info,nw_shadow_info,nw_group_info)
	getInfo,	// routine to call for all found NDS objects
	NULL,		// Unix objet name to find in NDS (if NULL search by ID)
	(uid_t)-1,	// Unix ID to find in NDS , if -1 search by unixName)
	TRUE,		// Ignore objectName and id and return all entries belonging to className
	conf);		// debug flag
}

/*******************************************************************************/



/******************************* NSS API ***************************************/


enum nss_status _nss_ncp_initgroups (const char *userName, gid_t group,
                long int *start, long int *size, gid_t * groups,
                long int limit,int *errnop) {

	struct nw_user_group_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doGroup)
		return NSS_STATUS_UNAVAIL;
	 if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_initgroups entering");
	}

	init_nw_user_group_info(&inf,conf->debug);
	err= getusergroupsbyxx(&inf, userName, (uid_t)-1,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_user_group_info(inf);
			if (!fix_nw_user_group_info(&inf,conf))
				err=nw_user_group_info_to_groups(inf,group,start,size,groups,limit,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_user_group_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_initgroups leaving");
                closelog();
	}
	return err;
}


enum nss_status _nss_ncp_getpwnam_r (const char* name, struct passwd *pwd,
				char * buffer, size_t buflen, int * errnop) {

	struct nw_user_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doPassword)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwnam entering");
	}
	init_nw_user_info(&inf,conf->debug);
	err= getpwdentbyxx(&inf, name, (uid_t)-1,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_user_info(inf);
			if (!fix_nw_user_info(&inf,conf))
				err=nw_user_info_to_passwd(inf,pwd,buffer,buflen,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_user_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwnam leaving");
                closelog();
	}
	return err;
}


enum nss_status _nss_ncp_getpwuid_r (uid_t uid, struct passwd *pwd,
				char * buffer, size_t buflen, int * errnop) {

	struct nw_user_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doPassword)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwuid entering");
	}
	init_nw_user_info(&inf,conf->debug);
	err= getpwdentbyxx(&inf, NULL, uid,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_user_info(inf);
			if (!fix_nw_user_info(&inf,conf))
				err=nw_user_info_to_passwd(inf,pwd,buffer,buflen,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_user_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwuid leaving");
                closelog();
	}
	return err;
}


enum nss_status _nss_ncp_getgrnam_r (const char* name, struct group *grp,
				char * buffer, size_t buflen, int * errnop) {

	struct nw_group_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doGroup)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
		openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrnam entering");
	}
	init_nw_group_info(&inf,conf->debug);
	err= getgrpentbyxx(&inf, name, (gid_t)-1,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_group_info(inf);
			if (!fix_nw_group_info(&inf,conf))
				err=nw_group_info_to_group(inf,grp,buffer,buflen,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_group_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrnam leaving");
                closelog();
	}
	return err;
}


enum nss_status _nss_ncp_getspnam_r (const char * name, struct spwd *spw,
                char *buffer, size_t buflen,int * errnop) {
	struct nw_shadow_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doShadow)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getspnam entering");
	}
	init_nw_shadow_info(&inf,conf->debug);
	err= getspentbyxx(&inf, name,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_shadow_info(inf);
			if (!fix_nw_shadow_info(&inf,conf))
				err=nw_shadow_info_to_shadow(inf,spw,buffer,buflen,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_shadow_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getspnam leaving");
                closelog();
	}
	return err;

}


enum nss_status _nss_ncp_getgrgid_r (gid_t gid, struct group *grp,
				char * buffer, size_t buflen, int * errnop) {
	struct nw_group_info inf;
	NWDSCCODE err;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doGroup)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrgid entering");
	}
	init_nw_group_info(&inf,conf->debug);
	err= getgrpentbyxx(&inf, NULL, gid,conf);
	switch (err) {
		case 0:
			if (conf->debug &QF_VERBOSE)
				print_nw_group_info(inf);
			if (!fix_nw_group_info(&inf,conf))
				err=nw_group_info_to_group(inf,grp,buffer,buflen,errnop,conf);
			else err=NSS_STATUS_UNAVAIL;
			break;
		case -1: // NOT FOUND IN NDS
			err=NSS_STATUS_NOTFOUND;
			*errnop=ENOENT;
			break;
		default: // NDS error
			err=NSS_STATUS_UNAVAIL;
			*errnop=ENOENT;
			break;

	}
	free_nw_group_info(&inf);
	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrid leaving");
                closelog();
	}
	return err;
}


enum nss_status _nss_ncp_setpwent(void) {
	NWDSContextHandle context;
	NWCONN_HANDLE conn;
	NWDSCCODE ccode;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doPassword)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setpwent entering");
	}
	ndsUsers=  __allocTree(FALSE,conf);
	if (ndsUsers) {
		if (conf->ctrlGroup && conf->ctrlGroup[0]) // caution if empty string from conf or default
			ccode=getgroupmembers(&context,&conn,conf->ctrlGroup,ndsUsers,conf);
		else
			ccode=getallents(&context,&conn,USER_CLASS,ndsUsers,conf);

		if (!ccode) {
			ndsUsers->context=context;
			ndsUsers->conn=conn;
			ndsUsers->curr = ndsUsers->lin;
			ndsUsers->remainObjects = ndsUsers->uniqueObjects;
			ccode= NSS_STATUS_SUCCESS;
		} else {
			__freeTree(ndsUsers);
			ndsUsers=NULL;
			ccode= NSS_STATUS_UNAVAIL;
		}
	}else
		ccode= NSS_STATUS_UNAVAIL;

	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setpwent leaving");
  		closelog();
	}
	return ccode;

}

enum nss_status _nss_ncp_setgrent(void) {
	NWDSContextHandle context;
	NWCONN_HANDLE conn;
	enum nss_status ccode;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doGroup)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
		openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setgrent entering");
	}
	ndsGroups=  __allocTree(FALSE,conf);
	if (ndsGroups) {
		if (! getallents(&context,&conn,GROUP_CLASS,ndsGroups,conf)) {
			ndsGroups->context=context;
			ndsGroups->conn=conn;
			ndsGroups->curr = ndsGroups->lin;
			ndsGroups->remainObjects = ndsGroups->uniqueObjects;
			ccode=NSS_STATUS_SUCCESS;
		} else {
			__freeTree(ndsGroups);
			ndsGroups=NULL;
			ccode=NSS_STATUS_UNAVAIL;
		}
	}else
		ccode= NSS_STATUS_UNAVAIL;

	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setgrent leaving");
                closelog();
	}
	return ccode;

}


enum nss_status _nss_ncp_setspent (void) {
	NWDSContextHandle context;
	NWCONN_HANDLE conn;
	NWDSCCODE ccode;
	struct nss_ncp_conf* conf = parse_conf();

	if (!conf || !conf->doShadow)
		return NSS_STATUS_UNAVAIL;

	if (conf->debug) {
                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setspent entering");
	}
	ndsShadows=  __allocTree(FALSE,conf);
	if (ndsShadows) {
		if (conf->ctrlGroup && conf->ctrlGroup[0]) // caution if empty string from conf or default
			ccode=getgroupmembers(&context,&conn,conf->ctrlGroup,ndsShadows,conf);
		else
			ccode=getallents(&context,&conn,USER_CLASS,ndsShadows,conf);

		if (!ccode) {
			ndsShadows->context=context;
			ndsShadows->conn=conn;
			ndsShadows->curr = ndsShadows->lin;
			ndsShadows->remainObjects = ndsShadows->uniqueObjects;
			ccode=NSS_STATUS_SUCCESS;
		} else {
			__freeTree(ndsShadows);
			ndsShadows=NULL;
			ccode=NSS_STATUS_UNAVAIL;
		}
	}else
		ccode= NSS_STATUS_UNAVAIL;

	if (conf->debug) {
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setspent leaving");
                closelog();
	}
	return ccode;

}


enum nss_status _nss_ncp_endpwent(void) {
	enum nss_status ccode;


	if (ndsUsers) {
		struct nss_ncp_conf *conf=ndsUsers->conf;

		if (conf->debug) {
         	       openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_endpwent entering");
		}
		NWCCCloseConn (ndsUsers->conn);
		NWDSFreeContext(ndsUsers->context);
		__freeTree(ndsUsers);
		ndsUsers=NULL;
		trace (conf->debug, LOG_NOTICE,"nss_ncp_setpwent all cleaned up OK");
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_setpwent leaving");
                	closelog();
		}
		ccode=NSS_STATUS_SUCCESS;
	}else {
		trace (QF_DEBUG, LOG_ERR,"nss_ncp_endpwent called without a previous nss_ncp_setpwent: nothing to cleanup");
		ccode=NSS_STATUS_UNAVAIL;
	}
	return ccode;
}


enum nss_status _nss_ncp_endgrent(void) {
	enum nss_status ccode;


	if (ndsGroups) {
		struct nss_ncp_conf *conf=ndsGroups->conf;

		if (conf->debug) {
         	       openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_endgrent entering");
		}
		NWCCCloseConn (ndsGroups->conn);
		NWDSFreeContext(ndsGroups->context);
		__freeTree(ndsGroups);
		ndsGroups=NULL;
		trace (conf->debug, LOG_NOTICE,"nss_ncp_endgrent all cleaned up OK");
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_endpwent leaving");
                	closelog();
		}
		ccode=NSS_STATUS_SUCCESS;
	}else {
		trace (QF_DEBUG, LOG_ERR,"nss_ncp_endgrent called without a previous nss_ncp_setgrent : nothing to cleanup");
		ccode=NSS_STATUS_UNAVAIL;
	}
	return ccode;
}


enum nss_status _nss_ncp_endspent (void) {
	enum nss_status ccode;


	if (ndsShadows) {
		struct nss_ncp_conf *conf=ndsShadows->conf;
		if (conf->debug) {
	                openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_endspent entering");
		}

		NWCCCloseConn (ndsShadows->conn);
		NWDSFreeContext(ndsShadows->context);
		__freeTree(ndsShadows);
		ndsShadows=NULL;
		trace (conf->debug, LOG_NOTICE,"nss_ncp_endspent all cleaned up up OK");
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_endspent leaving");
                	closelog();
		}
		ccode=NSS_STATUS_SUCCESS;
	}else {
		trace (QF_DEBUG, LOG_ERR,"nss_ncp_endspent called without a previous nss_ncp_setspent : nothing to cleanup");
		ccode=NSS_STATUS_UNAVAIL;
	}
	return ccode;
}

enum nss_status _nss_ncp_getpwent_r(struct passwd *pwd,
				char * buffer, size_t buflen, int * errnop) {
	enum nss_status err;

	if (ndsUsers) {
		struct TreeNode* n;
		NWDSCCODE ccode;
		struct nw_user_info ui;
		struct nss_ncp_conf *conf=ndsUsers->conf;
		const char* ndsName;
		if (conf->debug) {
         	       openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwent entering");
		}

next_user:; // avoid a recursive call
		n = ndsUsers->curr;
		if (!n) {
			//ready to scan again from the beginning (a good idea ??)
			ndsUsers->curr = ndsUsers->lin;
			ndsUsers->remainObjects = ndsUsers->uniqueObjects;
			err= NSS_STATUS_NOTFOUND;
		} else {
				ndsName=n->name;
				init_nw_user_info(&ui,conf->debug);
				ccode=nds_user_info(ndsUsers->context,n->name,&ui,conf->debug);

				if (ccode) {
					free_nw_user_info(&ui);
					err=NSS_STATUS_NOTFOUND;
					goto exit;
				}
				if (conf->debug &QF_VERBOSE)
					print_nw_user_info(ui);
				if (!fix_nw_user_info(&ui,conf))
					err=nw_user_info_to_passwd(ui,pwd,buffer,buflen,&errno,conf);
				else err=NSS_STATUS_UNAVAIL;
				free_nw_user_info(&ui);
				switch (err) {
					case NSS_STATUS_TRYAGAIN:
				// buffer was too small
				// do not advance to next user
				// and let nss to try again with a bigger buffer;
						break;
					default:
				// move to next user
						if (!--n->cnt) {
							ndsUsers->curr = n->next;
							ndsUsers->remainObjects--;
						}
						if (err==NSS_STATUS_NOTFOUND) {// user has no unix property,  skip
							trace (conf->debug, LOG_NOTICE,"user %s has not Unix properties in NDS,skipping",ndsName);
							goto next_user;
						}else
							err=NSS_STATUS_SUCCESS;
			}
		}
exit:
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getpwent leaving");
                	closelog();
		}

	}else {
		err=NSS_STATUS_UNAVAIL;
	}
	return err;

}


enum nss_status _nss_ncp_getgrent_r(struct group *grp,
				char * buffer, size_t buflen, int * errnop) {
	enum nss_status err;

	if (ndsGroups) {
		struct TreeNode* n;
		NWDSCCODE ccode;
		const char* ndsName;
		struct nw_group_info gi;
		struct nss_ncp_conf *conf=ndsGroups->conf;

		if (conf->debug) {
         	       openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrent entering");
		}
next_group:; // avoid a recursive call
		n = ndsGroups->curr;
		if (!n) {
			//ready to scan again from the beginning (a good idea ??)
			ndsGroups->curr = ndsGroups->lin;
			ndsGroups->remainObjects = ndsGroups->uniqueObjects;
			err= NSS_STATUS_NOTFOUND;

		}else {
			ndsName=n->name;
			init_nw_group_info(&gi,conf->debug);
			ccode=nds_group_info(ndsGroups->context,n->name,&gi,conf->debug);
			if (ccode) {
				free_nw_group_info(&gi);
				err= NSS_STATUS_NOTFOUND;
				goto exit;
			}
			if (conf->debug &QF_VERBOSE)
				print_nw_group_info(gi);
			if (!fix_nw_group_info(&gi,conf))
				err=nw_group_info_to_group(gi,grp,buffer,buflen,&errno,conf);
			else err=NSS_STATUS_UNAVAIL;
			free_nw_group_info(&gi);
			switch (err) {
				case NSS_STATUS_TRYAGAIN:
				// buffer was too small
				// do not advance to next group
				// and let nss to try again with a bigger buffer
					break;
				default:
				// move to next group
					if (!--n->cnt) {
						ndsGroups->curr = n->next;
						ndsGroups->remainObjects--;
					}
					if (err==NSS_STATUS_NOTFOUND) {// group has no unix property,  skip
						trace (conf->debug, LOG_NOTICE,"group %s has not Unix properties in NDS,skipping",ndsName);
						goto next_group;
					} else
						err=NSS_STATUS_SUCCESS;
			}
		}
exit:
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrent leaving");
                	closelog();
		}

	}else {
		err= NSS_STATUS_UNAVAIL;
	}
	return err;
}


enum nss_status _nss_ncp_getspent_r (struct spwd *spw,
			char * buffer, size_t buflen,int * errnop) {
	enum nss_status err;


	if (ndsShadows) {
		struct TreeNode* n;
		NWDSCCODE ccode;
		const char* ndsName;
		struct nw_shadow_info si;
		struct nss_ncp_conf *conf=ndsShadows->conf;

		if (conf->debug) {
                	openlog("ncp_nss", LOG_PID, LOG_AUTHPRIV);
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getgrent entering");
		}
next_user:; // avoid a recursive call
		n = ndsShadows->curr;
		if (!n) {
			//ready to scan again from the beginning (a good idea ??)
			ndsShadows->curr = ndsShadows->lin;
			ndsShadows->remainObjects = ndsShadows->uniqueObjects;
			err= NSS_STATUS_NOTFOUND;
		}else {
			ndsName=n->name;
			init_nw_shadow_info(&si,conf->debug);
			ccode=nds_shadow_info(ndsShadows->context,n->name,&si,conf->debug);
			if (ccode) {
				free_nw_shadow_info(&si);
				err=NSS_STATUS_NOTFOUND;
				goto exit;
			}
			if (conf->debug &QF_VERBOSE)
				print_nw_shadow_info(si);
			if (!fix_nw_shadow_info(&si,conf))
				err=nw_shadow_info_to_shadow(si,spw,buffer,buflen,&errno,conf);
			else err=NSS_STATUS_UNAVAIL;
			free_nw_shadow_info(&si);
			switch (err) {
				case NSS_STATUS_TRYAGAIN:
				// buffer was too small
				// do not advance to next user
				// and let nss to try again with a bigger buffer;
					break;
				default:
				// move to next user
					if (!--n->cnt) {
						ndsShadows->curr = n->next;
						ndsShadows->remainObjects--;
					}
					if (err==NSS_STATUS_NOTFOUND) {// user has no unix property,  skip
						trace (conf->debug, LOG_NOTICE,"user %s has not Unix properties in NDS,skipping",ndsName);
						goto next_user;
					}
					else
						err=NSS_STATUS_SUCCESS;
			}
		}
exit:
		if (conf->debug) {
			trace (conf->debug, LOG_NOTICE,"nss_ncp_getspent leaving");
                	closelog();
		}
	}else {
		return  NSS_STATUS_UNAVAIL;
	}
	return err;

}

