/* $Header: /usr/src/redhat/BUILD/Linux-PAM-0.59/modules/pam_ncp/RCS/pam_ncp_auth.c,v 1.3 1998/03/04 02:52:07 dwmw2 Exp $ */

/*
 * Copyright David Woodhouse, 1998.  All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * $Log: pam_ncp_auth.c,v $
 * Revision 1.26 2002/05/24 PP
 *    Extended -Z option to allow some zenflags to be turned OFF on that workstation.
 *    With -Z all zenflags are off, but with -ZABCD, only flags ABCD are off.
 *
 *    Some support is given for the case of the same CN in different contexts. In that case, if the
 *	password is good but some other login errors is reported ( account disabled, too many connexions...)
 *	we report error and abort.
 *    But if the password is bad, then we try to find another user with the same CN in another context ( if any).
 *  TODO: since the original code, expired password error is ignored and user is allowed to login...(?)
 *  Added the service su to the table of allowed service.
 *    still problems with impersonnating ( operation not permitted)
 *    authentication and access is granted but pam still fails ( check /etc/pam.d/su )
 *
 * Revision 1.25 2002/05/10 PP
 *   The modifications required for operations in a pure IP network have been added.
 *   These modifications were proposed by Jean-François Burdet <jean-francois.burdet@adm.unige.ch>
 *     and are described at http://lnxjfb.unige.ch/grpnov/article.php3?id_article=81
 *   They include:
 *     adding a -A that trigger adding -A server option to ncpmount
 *     adding logic for context searches in case no list has been provided on the command line
 *
 *    Merging of group zenflags is now done before automatic group creation, so if creation fails
 *	(due to a name too long for example) zenflags are still unherited
 * Revision 1.24 2002/05/09 PP
 * EXPERIMENTAL CODE
 * NEW CODE is marked with #ifdef INPERSONATE
 *     Provisions have been added for home directories on shared NFS servers
 *	In that case, new user home directory already exists and will be mounted via autofs
 *	so
 *	1)  this module should stop complaining that user's home already exists !!!
 *	2)  the useradd command should be called with -M option ( DO NOT create HOME) !
 *	    instead of -m (DO create home)
 *	3) actually the module should complains if the home DO NOT EXIST (not created on NFS server
 *		or NFS server down).
 *	This is the exact reverse behaviour of the previous versions !!!!
 *	In order not to break things a flag (-n) has been added to the command line
 *	When present the module do not attempt to autocreate a local home; it expects the home to be present
 *		either locally or more likely as a NFS mounted ressource.
 *
 * This leads to an extra security problem. The PAM module is ran with root privileges and requires
 *   - read privileges to check existence of nwhome in current user home
 *   - write privileges to the NFS mounted user's home to create a directory (nwhome) and some files
 *  (.forward, .nwclient and .nwinfos).
 *   Thus the NFS directories must be exported with the no_root_squash option which is quite unsafe.
 *
 * This means that if somebody get root access on a local workstation, he can access any home directory on the NFS server
 * with root privileges !!!
 *  And any student in the campus that is root on his own machine have full rights on the NFS directories !
 *
 *  PARTIAL FIX: We do not set the no_root_squash option on NFS servers; As soon as this module has determined
 *  the current user's Unix UID ( either server required or calculated), and
 *   eventually autocreated it on the local machine, it impersonnates to that UID at every access to his home
 *
 *  MORE SERIOUS: if NFS directory is mounted with no_root_squash, internally called ncpmount fails with "mount point not found"
 *    since this suid program has no rights in NFS remote directories.
 *    Same problems with ncplogin, ncpmap that have no rights to "autocreate" the mounting point ~/ncp/SERVER/VOLUME !
 *
 * so when the -n parameter is present we mount Netware home in a local /mnt/ncp/$USER directory chmoded to 0700
 * and pass this directory to ncpmount...(this is the new behaviour of ncplogin/ncpmap with the -l option)
 *
 * Revision 1.23  2002/02/21 pp
 *	Fixed possible buffer overflow in mount_nwhome in verbose mode
 *	Fixed remote access granting table for samba >2.07 ( pam_tty=samba and pam_rhost=hostname)
 *		was null, null in samba 2.06
 *
 * Revision 1.22  2002/02/03 vana
 *	Fix couple of possible and real buffer overflows in the code.
 *	It still needs review, but buffer overflows exploitable by malicious
 *		NDS administrators should be fixed now. It is true that if you
 *		trust them enough to manage logins for your box, they do not have
 *		need for setting user's home directories to 4KB strings, but
 *		if they do, it should not crash PAM authentication module.
 *	I believe that no buffer overflow was exploitable by mere user.
 *	Reformatted some functions. We use tab size 8, not 4, not 6, not 7.5...
 *
 * Revision 1.21  2002/01/20 PP
 *	kscreensaver (from KDE) calls again PAM when tring to unlock the screen.
 *	currently only the authenticate service is called (not session)  But in that case
 *		current user environnment is used, so it is quite likely that a default context will be red from there.
 *		ncp_auth_tree has been modified to take this in account.
 *	furthermore, user's info and zen processing should not be done again . So another flag has been added
 *		this skip these parts if the PAM service name contains "saver". I hope it does include other
 *		screensavers 
 *
 * Revision 1.20  2002/01/14 PP
 * as suggested by vana, changed code in exechelper() to simplify use of su. rather than calling su -l username ....
 *
 *
 * Revision 1.19 2002/01/10 PP
 *	automatic ncpmounting is now run by this module as the current user and not as root
 *	this has two major consequenes:
 *	1) nwhome directory now get group permissions for the group users the group root. This is a cosmetic detail
 *	   since permissions are set to 700
 *	2) more important. If ncpmount is done by root, user cannot read back later its private key data for this connection.
 *	   ncp_get_private_data grant access to the private data but returns a length of 0 for the private data (???).
 *	   Note that if root requests these private data (using contrib/testing/pp/nwgetconnlist -a), he get a EACCESS (13) 
 *         error as expected. Private IS private!
 *      so this initial connection was not flagged as authenticated for this user and cannot be used later by ncpmap to mount
 *      additionnal netware volumes of the same tree with the same credentials.
 *
 *	changed mount_nwhome to call /bin/su -l unixname -c "/usr/sbin/ncpmount  -S sss -V vvvv -U uuu ... /home/user/nwhome"
 *	changed process zenscripts to do the same : for security reasons zenscripts are now run with current user's permissions
 *		and not anymore as root.
 *
 *	added a new account flag NDS_IS_NEW_USER in .nwinfos to ease zenscript processing
 *		is account is new, we can further personnalize config files such as kmailrc, liprefs.js ...
 *
 * Revision 1.18 2002/01/09 PP
 *	With redHat 7.2 the RHOST value passed to PAM with X remote access is emty end not NULL
 *		the kde entry in the zen table has been duplicated
 *	added -o "symlinks" to options of ncpmount.
 *
 * Revision 1.17 2001/12/12 PP
 *	With redHat 7.2 the return value of pam_set-cred and pam_get_cred that was PAM_IGNORE
 *		is really used, so authentication fails !
 *	Changed to PAM_SUCCESS
 *
 * Revision 1.16  2001/11/01 PP
 *	added the new entries needed by nwnet.c v >1.17 in ~/.nwclient
 *	must be after the usual server/user pawwd line since many script use 'read server passwd <~/.nwclient'
 *
 * Revision 1.15 2001/10/31  pp
 *	added support for Server AND contextless authentication upon request of Steve Flynn <sflyn@tpg.com.au>
 *		--> the server command line now accept a list of context to search ndsserver=SERVER_NAME:Ctx1,Ctx2,...Ctxn/group
 *	idem for password changing
 *
 * Revision 1.15  2001/11/01 PP
 *	added the new entries needed by nwnet.c v >1.17 in ~/.nwclient
 *	must be after the usual server/user pawwd line since many script use 'read server passwd <~/.nwclient'
 *
 * Revision 1.14 2001/10/27 PP
 *     Made the -m "empty" feature more reliable:
 *      1) if empty the Netware home directory is mounted as /home/user but we
 *       still have problem with chown and kde on a mounted netware
 *      2) parsemntpoint() did not scan the argument to the end, so with option
 *             -mnwhome, it was called twice ; once for "nwhome" and once for "me" !
 *        result is that the mounting point was ~/e !!! Corrected in pam_sm_athenticate.
 *     Due to this feature in -m option:
 *         we moved the creation of the .forward file during the authentication section.
 *         so:  if user is not currently logged in, system will still find the .forward file in local /home/user ( it cannot mount user's netware
 *           home to find it)...
 *         We do it twice (before and after automounting) so if user IS logged in system will
 *           find the .forward file... This second time could be done in a zenscript ...
 *
 * Revision 1.13 2001/10/24  pp
 *   added support for Tree & contextless authentication
 *                 --> new command line tree=TREE_NAME:Ctx1,Ctx2,...Ctxn/group
 *                 --> reorganized authentication code for the group verification part to be common
 *                         to auth_server and auth_tree
 *                 --> modified nw_auto_mount_home to pass a FQDN to ncpmount
 *  idem for password changing
 *
 * Revision 1.12 2001/10/24  pp
 *   added support for ssh remote access ( under the same ZEN flag as Telnet access)
 *   due to current implementation of OpenSSH, user must have an account & home on the local machine
 *   for authentication by Netware to work. sshd peeks first in user's home to find data files...
 * so automagic creation does not work with ssh
 *
 * Revision 1.11 2001/02/24  pp
 * Added some support for reading bindery properties
 *    IDENTIFICATION --> Gecos field
 *    HOME_DIRECTORY --> nwhome ( must be on the same server as the "autenticator"
 *
 * Revision 1.10 2001/02/21 12:34:56  vana
 * Cleaned warnings, fixed bindery login.
 *
 * Revision 1.9  2001/01/26 16:50:00  pp
 * Added Zenux Flags concept for tuning user's account on Linux
 * for more documentation see http://cipcinsa.insa-lyon.fr/ppollet/pamncp/
 *
 * Revision 1.8  2001/01/15 19:30:00  pp
 * Added optional automounting of Netware Home directory
 * in ~/nwhome, and dismounting upon session ending
 *
 * Revision 1.7  2001/01/06 10:50:00  pp
 * Added support for pré DS8 network using either Location
 * attribute or Postal Adress
 *
 * Revision 1.6  2000/07/06 14:40:00  vana
 * Added DCV_DEREF_ALIASES where needed
 *
 * Revision 1.5  2000/06/02 17:40:00  vana
 * Tons of new code - passwd, changed auth code
 *
 * Revision 1.4  1999/10/31 15:44:44  vana
 * Couple of new options, new check for group membership
 *
 * Revision 1.3  1998/03/04 02:52:07  dwmw2
 * Oops - licensing has to be GPL as it's using the ncpfs libraries and headers.
 *
 * Revision 1.2  1998/03/04 02:39:12  dwmw2
 * Tidied up (a bit) for initial release.
 *
 * Revision 1.1  1998/03/04 02:21:07  dwmw2
 * Initial revision
 *
 *
 */

#define _GNU_SOURCE
#define _BSD_SOURCE
#define inline __inline__
#include <ncp/nwcalls.h>
#include <ncp/nwnet.h>
#include <ncp/nwclient.h>

#include <features.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>

#include "support.h"

static const char rcsid[] = "$Id: pam_ncp_auth.c,v 1.9 2001/21/26 16:52:07 PP Exp $ pam_ncp authentication functions. Dave@imladris.demon.co.uk,vandrove@vc.cvut.cz,patrick.pollet@insa-lyon.fr ";

/* Define function prototypes */

static void
nw_cleanup_conn(pam_handle_t * pamh, void *data, int error_status)
{
	ncp_close((struct ncp_conn *) data);
}

#define QFC_FIRST_UNUSED	0
#define QFC_NEXT_UNUSED		1
#define QFC_PREFFER_SERVER	2
#define QFC_REQUIRE_SERVER	4

// caldera open linux does not likes the -G parameter in useradd !!!PP
// so we will have a specific flag for these and use a second call to usermod -G
#define NO_PROCESS_GROUPS	1

#define QFMU_GID	0x0001
#define QFMU_GECOS	0x0002
#define QFMU_DIR	0x0004
#define QFMU_SHELL	0x0008

/* reactivate if you prefer NDS property Postal Address
rather than Location for pre-DS 8 systems*/
#if 0
#define USE_POSTAL_ADDRESS
#endif

// from the snapin C code
// names of NDS attributes used

// USE_DUMMY_ATTRibutes must be set in the Makefile

#ifndef USING_DUMMY_ATTRIBUTES
// the real ones
#define ATTR_UID	"UNIX:UID"
#define ATTR_PGNAME	"UNIX:Primary GroupName"
#define ATTR_PGID	"UNIX:Primary GroupID"
#define ATTR_GID	"UNIX:GID"
#define ATTR_SHELL	"UNIX:Login Shell"
#define ATTR_COM	"UNIX:Comments"
#define ATTR_HOME	"UNIX:Home Directory"
#else
// dummy attributes for testing
// created with Schemax with the same syntax
// and associated to user class and group class
#define ATTR_UID	              "LINUX:UID"
#define ATTR_PGNAME	"LINUX:Primary GroupName"
#define ATTR_PGID	"LINUX:Primary GroupID"
#define ATTR_GID	"LINUX:GID"
#define ATTR_SHELL	"LINUX:Login Shell"
#define ATTR_COM	"LINUX:Comments"
#define ATTR_HOME	"LINUX:Home Directory"
#endif

// the attribute used to test presence of NDS8
// either real or dummy (not used yet)
#define ATTR_NDS8	ATTR_UID

// other attributes used
// absent NDS8 attributes are searched in L attribute
// also new properties ( Zenux Flags, Other group...)
#define ATTR_LOCATION	"L"

#define ATTR_LOGIN_SCRIPT	"Login Script"
#define ATTR_GRP_MBS		"Group Membership"
#define ATTR_HOST_SERVER	"Host Server"
#define ATTR_HOST_RN		"Host Resource Name"
#define ATTR_SMTP_EMAIL		"Email Address"
#define ATTR_LDAP_EMAIL		"Internet Email Address"
#define ATTR_FULL_NAME		"Full Name"
#define ATTR_SURNAME		"Surname"
#define ATTR_GIVEN_NAME		"Given Name"
#define ATTR_POSTAL_ADDRESS	"Postal Address"
#define ATTR_HOME_NW		"Home Directory"
#define ATTR_MESSAGE_SERVER	"Message Server"

// the proper naming attribute may be customized here (must be a CI_STRING )
#define ATTR_GECOS		ATTR_FULL_NAME

// syntaxes of the used attributes
#define SYN_LOCATION		SYN_CI_STRING
#define SYN_LOGIN_SCRIPT	SYN_STREAM
#define SYN_UID			SYN_INTEGER
#define SYN_PGNAME		SYN_DIST_NAME
#define SYN_PGID		SYN_INTEGER
#define SYN_GID			SYN_INTEGER
#define SYN_SHELL		SYN_CE_STRING
#define SYN_COM			SYN_CI_STRING
#define SYN_HOME		SYN_CE_STRING
#define SYN_GRP_MBS		SYN_DIST_NAME

/* user account tuning flags red from the NDS  (ZenFlag) */
// zenFlag processing can be turned off by a command line -Z
// it is on by default

#define NDS_ZF_AUTOMOUNT_NWHOME		'A'
#define NDS_ZF_BROADCAST_ALL		'B'
#define NDS_ZF_BROADCAST_CONSOLE	'C'
#define NDS_ZF_D			'D'
#define NDS_ZF_E			'E'
#define NDS_ZF_ALLOW_FTP_ACCESS		'F'
#define NDS_ZF_G			'G'
#define NDS_ZF_ALLOW_RSH_ACCESS		'H'
#define NDS_ZF_CREATE_NWINFOS		'I'
#define NDS_ZF_J			'J'
#define NDS_ZF_K			'K'
#define NDS_ZF_L			'L'
#define NDS_ZF_FORWARD_MAIL		'M'
#define NDS_ZF_CREATE_NWCLIENT		'N'
#define NDS_ZF_OVERWRITE_NWCLIENT	'O'
#define NDS_ZF_PASSWD_IN_NWCLIENT	'P'
#define NDS_ZF_Q			'Q'
#define NDS_ZF_ALLOW_RLOGIN_ACCESS	'R'
#define NDS_ZF_ALLOW_SAMBA_ACCESS	'S'
#define NDS_ZF_ALLOW_TELNET_ACCESS	'T'
#define NDS_ZF_U			'U'
#define NDS_ZF_VOLATILE_ACCOUNT		'V'
// I reserve this letter, but I still don't know
// how auth_nds_mod of Apache works ( TODO !)
#define NDS_ZF_ALLOW_WEB_ACCESS		'W'
#define NDS_ZF_ALLOW_X_ACCESS		'X'
#define NDS_ZF_Y			'Y'
#define NDS_ZF_Z			'Z'
// three admin defined scripts that can be launched at session opening
// these scripts WILL source the file ~/.nwinfos to get user's data
// if more than one will be executed in this order
#define NDS_ZF_0			'0'
#define NDS_ZF_1			'1'
#define NDS_ZF_2			'2'
// three admin defined scripts that can be launched at session closing
// these scripts WILL source the file ~/.nwinfos to get user's data
// if more than one will be executed in this order
#define NDS_ZF_3			'3'
#define NDS_ZF_4			'4'
#define NDS_ZF_5			'5'

/*corresponding binary flag*/

#define ZF_UNSUPPORTED			0x00000000

#define ZF_AUTOMOUNT_NWHOME		0x00000001
#define ZF_BROADCAST_ALL		0x00000002
#define ZF_BROADCAST_CONSOLE		0x00000004
#define ZF_D				0x00000008
#define ZF_E				0x00000010
#define ZF_ALLOW_FTP_ACCESS		0x00000020
#define ZF_G				0x00000040
#define ZF_ALLOW_RSH_ACCESS		0x00000080
#define ZF_CREATE_NWINFOS		0x00000100
#define ZF_J				0x00000200
#define ZF_K				0x00000400
#define ZF_L				0x00000800
#define ZF_FORWARD_MAIL			0x00001000
#define ZF_CREATE_NWCLIENT		0x00002000
#define ZF_OVERWRITE_NWCLIENT		0x00004000
#define ZF_PASSWD_IN_NWCLIENT		0x00008000
#define ZF_Q				0x00010000
#define ZF_ALLOW_RLOGIN_ACCESS		0x00020000
#define ZF_ALLOW_SAMBA_ACCESS		0x00040000
#define ZF_ALLOW_TELNET_ACCESS		0x00080000
#define ZF_U				0x00100000
#define ZF_V				0x00200000
#define ZF_ALLOW_WEB_ACCESS		0x00400000
#define ZF_ALLOW_X_ACCESS		0x00800000
#define ZF_Y				0x01000000
#define ZF_Z				0x02000000
#define ZF_0				0x04000000
#define ZF_1				0x08000000
#define ZF_2				0x10000000
#define ZF_3				0x20000000
#define ZF_4				0x40000000
#define ZF_5				0x80000000

#define ZF_OPENING_SCRIPTS		(ZF_0 | ZF_1 | ZF_2)
#define ZF_CLOSING_SCRIPTS		(ZF_3 | ZF_4 | ZF_5)

//maybe overwritten ( not ORED !) by a -z directive on the command line
//note: no passwd in ~/.nwclient by default
#define ZF_DEFAULTS	ZF_CREATE_NWCLIENT | ZF_AUTOMOUNT_NWHOME | ZF_CREATE_NWINFOS

// where must be the scripts
// TODO: let PAM finds them somewhere in its path
// (if it has one ?)
#define ZEN_SCRIPT_0	"/usr/local/bin/zenscript0"
#define ZEN_SCRIPT_1	"/usr/local/bin/zenscript1"
#define ZEN_SCRIPT_2	"/usr/local/bin/zenscript2"
#define ZEN_SCRIPT_3	"/usr/local/bin/zenscript3"
#define ZEN_SCRIPT_4	"/usr/local/bin/zenscript4"
#define ZEN_SCRIPT_5	"/usr/local/bin/zenscript5"

#define NWINFOS_FILE	".nwinfos"
#define NWCLIENT_FILE	".nwclient"
#define DEF_MNT_PNT	"nwhome"

struct zenElement {
	char letter;
	long value;
};
/* only the flag processed in this version are !=ZF_UNSUPPORTED */
static struct zenElement zenTable[]={
{ NDS_ZF_AUTOMOUNT_NWHOME,	ZF_AUTOMOUNT_NWHOME },
{ NDS_ZF_BROADCAST_ALL,		ZF_BROADCAST_ALL },
{ NDS_ZF_BROADCAST_CONSOLE,	ZF_BROADCAST_CONSOLE },
{ NDS_ZF_D,			ZF_UNSUPPORTED },
{ NDS_ZF_E,			ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_FTP_ACCESS,	ZF_ALLOW_FTP_ACCESS },
{ NDS_ZF_G,			ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_RSH_ACCESS,	ZF_ALLOW_RSH_ACCESS },
{ NDS_ZF_CREATE_NWINFOS,	ZF_CREATE_NWINFOS },
{ NDS_ZF_J,			ZF_UNSUPPORTED },
{ NDS_ZF_K,			ZF_UNSUPPORTED },
{ NDS_ZF_L,			ZF_UNSUPPORTED },
{ NDS_ZF_FORWARD_MAIL,		ZF_FORWARD_MAIL },
{ NDS_ZF_CREATE_NWCLIENT,	ZF_CREATE_NWCLIENT },
{ NDS_ZF_OVERWRITE_NWCLIENT,	ZF_OVERWRITE_NWCLIENT },
{ NDS_ZF_PASSWD_IN_NWCLIENT,	ZF_PASSWD_IN_NWCLIENT },
{ NDS_ZF_Q,			ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_RLOGIN_ACCESS,	ZF_ALLOW_RLOGIN_ACCESS },
{ NDS_ZF_ALLOW_SAMBA_ACCESS,	ZF_ALLOW_SAMBA_ACCESS },
{ NDS_ZF_ALLOW_TELNET_ACCESS,	ZF_ALLOW_TELNET_ACCESS },
{ NDS_ZF_U,			ZF_UNSUPPORTED },
{ NDS_ZF_VOLATILE_ACCOUNT,	ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_WEB_ACCESS,	ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_X_ACCESS,	ZF_ALLOW_X_ACCESS },
{ NDS_ZF_Y,			ZF_UNSUPPORTED },
{ NDS_ZF_Z,			ZF_UNSUPPORTED },
// zen scripts should source ~/.nwinfos file, so make sure
// it is created
{ NDS_ZF_0,			ZF_0 | ZF_CREATE_NWINFOS },
{ NDS_ZF_1,			ZF_1 | ZF_CREATE_NWINFOS },
{ NDS_ZF_2,			ZF_2 | ZF_CREATE_NWINFOS },
{ NDS_ZF_3,			ZF_3 | ZF_CREATE_NWINFOS },
{ NDS_ZF_4,			ZF_4 | ZF_CREATE_NWINFOS },
{ NDS_ZF_5,			ZF_5 | ZF_CREATE_NWINFOS },
{ 0,				0 }
};

static long
decodeZenFlag(const char **optp)
{
	long r = 0;
	char c;
	const char *ZF = *optp;

	while ((c = *ZF++) != 0) {
		struct zenElement *z;

		c = toupper(c);
		for (z = zenTable; z->letter; z++) {
			if (c == z->letter) {
				r |= z->value;
				break;
			}
		}
	}
	*optp = ZF;
	return r;
}

struct pam_ncp_state {
	struct {
		uid_t	min;
		uid_t	max;
		int	flags;
		int	modflags;
	} uid;
	struct {
		gid_t	min;
		gid_t	max;
		int	flags;
	} gid;

};

// PP:moved up here to be used by some of my routines
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;
}

// flip Dos antislash to Unix
// converts to uppercase for ncpmount to work with ROOT options ON
static char *
unixifyPathUC(char *dosPath)
{
	size_t i;

	for (i = 0; i < strlen(dosPath); i++) {
		if (dosPath[i] == '\\')
			dosPath[i] = '/';
	}
	str_upper(dosPath);
	return dosPath;
}

// remove all spaces from a NDS name
static void
trim(char *string)
{
	char *aux = string;
	char c;

	while ((c = *string++) != 0) {
		if (c != ' ')
			*aux++ = c;
	}
	*aux = 0;
}

#define QF_VERBOSE		0x0001
#define QF_DEBUG		0x0002
#define QF_NOSU			0x0004
#define QF_NOSUEQ		0x0008
#define QF_AUTOCREATE		0x0010
#define QF_AUTOMODIFY		0x0020
#define QF_BINDERY		0x0040
#define QF_NO_PEER_CHECKS	0x0080
// rev 1.25 add an extra -A server parameter to ncpmount (with -A option on command line)
#define QF_USE_NETWARE_IP	0x1000
// version 1.24. automatically create home directory on local machine for new users
// it is on by default
// should be turned of by -n option if homes are on a remote NFS server (automounted)
#define QF_MOUNTLOCALLY		0x2000
#define QF_CREATEHOME		0x4000
// test v 1.25 impersonnating to current user when accessing his NFS mounted home
#define IMPERSONNATE 1

static int
is_member_of_bindery_group(NWCONN_HANDLE conn, const char *user, const char *group)
{
	int err;

	err = NWIsObjectInSet(conn, user, NCP_BINDERY_USER, "GROUPS_I'M_IN", group, NCP_BINDERY_UGROUP);
	if (!err) {
		err = NWIsObjectInSet(conn, group, NCP_BINDERY_UGROUP, "GROUP_MEMBERS", user, NCP_BINDERY_USER);
		if (!err) {
			return 0;
		} else {
			syslog(LOG_WARNING, "inconsistent bindery database for user %s and group %s: %s\n", user, group, strnwerror(err));
		}
	} else {
		syslog(LOG_WARNING, "user %s is not member of %s: %s\n", user, group, strnwerror(err));
	}
	return -1;
}

static int
is_member_of_nds_group(NWDSContextHandle ctx, NWCONN_HANDLE conn, NWObjectID oid, const char *group)
{
	int eval = 0;
	Buf_T *buf = NULL;
	NWDSCCODE dserr;
	nbool8 match;
	dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &buf);
	if (dserr) {
		syslog(LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
		eval = 120;
		goto bailout;
	}
	dserr = NWDSInitBuf(ctx, DSV_COMPARE, buf);
	if (dserr) {
		syslog(LOG_WARNING, "NWDSInitBuf() failed with %s\n", strnwerror(dserr));
		eval = 121;
		goto bailout;
	}
	dserr = NWDSPutAttrName(ctx, buf, "Group Membership");
	if (dserr) {
		syslog(LOG_WARNING, "NWDSPutAttrName() failed with %s\n", strnwerror(dserr));
		eval = 122;
		goto bailout;
	}
	dserr = NWDSPutAttrVal(ctx, buf, SYN_DIST_NAME, group);
	if (dserr) {
		syslog(LOG_WARNING, "NWDSPutAttrVal() failed with %s\n", strnwerror(dserr));
		eval = 123;
		goto bailout;
	}
	dserr = __NWDSCompare(ctx, conn, oid, buf, &match);
	if (dserr) {
		syslog(LOG_WARNING, "__NWDSCompare() failed with %s(oid=%x)\n", strnwerror(dserr), oid);
		eval = 124;
		goto bailout;
	}
	if (!match) {
		eval = 125;
	}
bailout:;
	if (buf)
		NWDSFreeBuf(buf);
	return eval;
}

static NWCCODE
nw_create_conn_to_server(NWCONN_HANDLE * conn, const char *server, const char *user, const char *pwd, int qflag)
{
	struct ncp_bindery_object uinfo;
	unsigned char ncp_key[8];
	NWCONN_HANDLE cn;
	char *pwd2;
	long err;

	if (qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "Trying to contact %s/%s\n", server, user);

	pwd2 = strdup(pwd);
	if (!pwd2) {
		syslog(LOG_WARNING, "Not enough memory when uppercasing password\n");
		err = PAM_TRY_AGAIN;
		goto fail2;
	}
	str_upper(pwd2);
	err = NWCCOpenConnByName(NULL, server, NWCC_NAME_FORMAT_BIND, 0, NWCC_RESERVED, &cn);
	if (err) {
		syslog(LOG_WARNING, "%s when trying to open connection\n", strnwerror(err));
		err = PAM_TRY_AGAIN;
		goto fail2;
	}
	if (!(qflag & QF_BINDERY) && NWIsDSServer(cn, NULL)) {
		err = nds_login_auth(cn, user, pwd2);
	} else {
		err = ncp_get_encryption_key(cn, ncp_key);
		if (err) {
			syslog(LOG_WARNING, "%s when trying to get encryption key. Doing unencrypted\n", strnwerror(err));
			err = ncp_login_unencrypted(cn, NCP_BINDERY_USER, user, pwd2);
		} else {
			err = ncp_get_bindery_object_id(cn, NCP_BINDERY_USER, user, &uinfo);
			if (err) {
				syslog(LOG_WARNING, "%s when trying to get object ID\n", strnwerror(err));
				err = PAM_USER_UNKNOWN;
				goto fail;
			}
			err = ncp_login_encrypted(cn, &uinfo, ncp_key, pwd2);
		}
	}
	if (err && err != NWE_PASSWORD_EXPIRED) {
		syslog(LOG_WARNING, "%s when trying to login\n", strnwerror(err));
		switch (err) {
		case ERR_NO_SUCH_ENTRY:
		case NWE_SERVER_UNKNOWN:
			err = PAM_USER_UNKNOWN;
			break;
		case NWE_LOGIN_MAX_EXCEEDED:
		case NWE_LOGIN_UNAUTHORIZED_TIME:
		case NWE_LOGIN_UNAUTHORIZED_STATION:
		case NWE_ACCT_DISABLED:
			err = PAM_AUTH_ERR;
			break;
		default:
			err = PAM_AUTH_ERR;
			break;
		}
		goto fail;
	}
// PP: I don't understand why we do not report this error ???
#if 0
	if (err)
		err = PAM_NEW_AUTHTOK_REQD;
#else
	err = 0;
#endif
	if (qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "User %s/%s was successfully authorized\n", server, user);
	*conn = cn;
	return err;
fail:;
	NWCCCloseConn(cn);
fail2:;
	*conn = NULL;
	return err;
}

static int
nw_get_nwid(NWCONN_HANDLE conn, NWObjectID * id, int qflag)
{
	NWCCODE err;

	err = NWCCGetConnInfo(conn, NWCC_INFO_USER_ID, sizeof (*id), id);
	if (err) {
		syslog(LOG_WARNING, "%s when retrieving object ID\n", strnwerror(err));
		return PAM_SYSTEM_ERR;
	}
	return PAM_SUCCESS;
}

/* old server centric code called by server=XXX/group  cmd line parameter */
static int
nw_create_verify_conn_to_server(NWCONN_HANDLE * conn, NWObjectID * id, 
				const char *server, const char *user, const char *pwd, 
				int qflag, const char *group)
{
	NWCONN_HANDLE cn;
	NWCCODE err;
	NWObjectID oid;

	err = nw_create_conn_to_server(&cn, server, user, pwd, qflag);
	if (err && err != PAM_NEW_AUTHTOK_REQD)
		return err;
	err = nw_get_nwid(cn, &oid, qflag);
	if (err) {
		syslog(LOG_WARNING, "Error %s retrieving user ID for %s\n", strnwerror(err), user);
		goto bailout;
	}

	if (qflag & QF_NOSU) {
		if (oid == 0x00000001) {
			err = PAM_AUTH_ERR;
			syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor\n", server, user);
			goto bailout;
		}
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "User %s/%s passed supervisor check\n", server, user);
	}
	if (qflag & QF_NOSUEQ) {
		nuint8 level;

		err = NWGetBinderyAccessLevel(cn, &level, NULL);
		if (err) {
			syslog(LOG_WARNING, "Access denied for %s/%s because of I/O error during object rights verification\n", server, user);
			err = PAM_AUTH_ERR;
			goto bailout;
		}
		if ((level >= 0x30) || ((level & 0xF) >= 3)) {
			syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor equivalent\n", server, user);
			err = PAM_AUTH_ERR;
			goto bailout;
		}
	}
	if (group) {
/*v 1.11 : if we talk to bindery  we should not use NDS to test group membership ? */
		if (!(qflag & QF_BINDERY)) {
			NWDSContextHandle ctx;
			nuint32 c;

			err = NWDSCreateContextHandle(&ctx);
			if (err) {
				syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
				err = PAM_SYSTEM_ERR;
				goto bailout;
			}
			c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
			err = NWDSSetContext(ctx, DCK_FLAGS, &c);
			if (err) {
				syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
				err = PAM_SYSTEM_ERR;
				NWDSFreeContext(ctx);
				goto bailout;
			}
			err = is_member_of_nds_group(ctx, cn, oid, group);
			NWDSFreeContext(ctx);
			if (err) {
				syslog(LOG_WARNING, "%s is not member of NDS %s\n", user, group);
				err = PAM_AUTH_ERR;
				goto bailout;
			}
		} else {	/* bindery testing */
			if (is_member_of_bindery_group(cn, user, group)) {
				syslog(LOG_WARNING, "%s is not member of BINDERY %s\n", user, group);
				err = PAM_AUTH_ERR;
				goto bailout;
			}
		}
	}
	if (id)
		*id = oid;
	if (conn)
		*conn = cn;
	else
		NWCCCloseConn(cn);
	return PAM_SUCCESS;
bailout:;
	/* Close the connection. */
	NWCCCloseConn(cn);
	return err;
}

// this code was contributed by Jean Francois Burdet <jean.francois.burdet@adm.unige.ch>
// to implement a contextless login if a list of context to search is not provided in the command line
// The parameter ndserver was replaced by a conn  since it is now called from nw_create_verify_conn_to_tree
// with an open connection to a tree or a ndsserver
// extra errors checkings and buffer overflow tested
//static void nw_ctx_search(const char * user_cn, const char * nds_server, char * contexts) {
static NWDSCCODE
nw_ctx_search(const char *user_cn, NWCONN_HANDLE conn, char *contexts, size_t maxsize)
{
	NWDSContextHandle context;
	NWDSCCODE ccode;
	nuint32 iterationHandle;
	nint32 countObjectsSearched;
	nuint32 objCntr;
	nuint32 objCount;
	char objectName[MAX_DN_CHARS + 1];
	size_t ctxp;
	nuint32 contextFlags;

	// buffers
	pBuf_T searchFilter;	// search filter
	pBuf_T retBuf;		// result buffer for NWDSSearch
	Filter_Cursor_T *cur;	// search expression tree temporary buffer

	if (!contexts || maxsize < MAX_DN_CHARS + 1)
		return EINVAL;

	ccode = NWDSCreateContextHandle(&context);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSCreateContextHandle (DCK_FLAGS) failed, returns %d\n", ccode);
		goto Exit1;
	}
	ccode = NWDSSetContext(context, DCK_NAME_CONTEXT, "[Root]");
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetContext (DCK_FLAGS) failed, returns %d\n", ccode);
		goto Exit2;
	}

	ccode = NWDSGetContext(context, DCK_FLAGS, &contextFlags);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetContext (DCK_FLAGS) failed, returns %d\n", ccode);
		goto Exit2;
	}

	contextFlags |= DCV_TYPELESS_NAMES;

	ccode = NWDSSetContext(context, DCK_FLAGS, &contextFlags);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSSetContext (DCK_FLAGS DCV_TYPELESS_NAMES) failed, returns %d\n", ccode);
		goto Exit2;
	}

	ccode = NWDSAddConnection(context, conn);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSAddConnection failed, returns %d\n", ccode);
		goto Exit2;
	}

/*******************************************************************
	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)
	*/

	/*******************************************************************
	** Allocate Filter buffer and Cursor and populate
	*/
	ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &searchFilter);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocBuf returned: %d\n", ccode);
		goto Exit3;
	}
	// Initialize the searchFilter buffer
	ccode = NWDSInitBuf(context, DSV_SEARCH_FILTER, searchFilter);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSInitBuf returned: %d\n", ccode);
		goto Exit6;
	}
	// Allocate a filter cursor to put the search expression
	ccode = NWDSAllocFilter(&cur);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocFilter returned: %d\n", ccode);
		goto Exit6;
	}
	// Build the expression tree in cur, then place into searchFilter
	// Object Class = User AND CN =  user_cn
	ccode = NWDSAddFilterToken(cur, FTOK_ANAME, "Object Class", SYN_CLASS_NAME);
	ccode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0);
	ccode = NWDSAddFilterToken(cur, FTOK_AVAL, "User", SYN_CLASS_NAME);
	ccode = NWDSAddFilterToken(cur, FTOK_AND, NULL, 0);
	ccode = NWDSAddFilterToken(cur, FTOK_ANAME, "CN", SYN_CI_STRING);
	ccode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0);
	ccode = NWDSAddFilterToken(cur, FTOK_AVAL, user_cn, SYN_CI_STRING);
	ccode = NWDSAddFilterToken(cur, FTOK_END, NULL, 0);

	ccode = NWDSPutFilter(context, searchFilter, cur, NULL);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSPutFilter returned: %d\n", ccode);
		goto Exit5;
	} else
		cur = NULL;

	ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &retBuf);
	if (ccode) {
		syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocBuf returned: %d\n", ccode);
		goto Exit5;
	}

	ctxp = 0;
	iterationHandle = NO_MORE_ITERATIONS;
	// while NWDSSearch still can get some objects...
	do {
		ccode = NWDSSearch(context, "[Root]", DS_SEARCH_SUBTREE, 0,	// don't dereference aliases
				   searchFilter, 0,	// we want attributes and values
				   0,	// only want information in attrNames
				   NULL, &iterationHandle, 0,	// reserved
				   &countObjectsSearched, retBuf);
		if (ccode) {
			syslog(LOG_NOTICE, "nw_ctx_search:NWDSSearch returned: %s\n", strnwerror(ccode));
			goto Exit4;
		}
		// count the object returned in the buffer
		ccode = NWDSGetObjectCount(context, retBuf, &objCount);
		if (ccode) {
			syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetObjectCount returned: %d\n", ccode);
			goto Exit4;
		}
		// for the number of objects returned...

		for (objCntr = 0; objCntr < objCount; objCntr++) {
			char *p;
			size_t ln;

			// get an object name
			ccode = NWDSGetObjectName(context, retBuf, objectName, NULL, NULL);
			if (ccode) {
				syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetObjectName returned: %d\n", ccode);
				goto Exit4;
			}
			// now, we get the user context wich starts at the first . occurence in the string
			p = strchr(objectName, '.');
			if (!p) {
				break;
			}
			ln = strlen(p + 1);
			if (ctxp + ln >= maxsize) {
				break;
			}
			if (ctxp) {
				*contexts++ = ',';
				maxsize--;
			}
			memcpy(contexts, p + 1, ln);
			contexts += ln;
			maxsize -= ln;
			ctxp = 1;

		}		// objCntr
	} while (iterationHandle != NO_MORE_ITERATIONS);
Exit4:;
	if (iterationHandle != NO_MORE_ITERATIONS) {
		// let's keep the final dserr as the 'out of memory error' from ptr->getval()
		NWDSCloseIteration(context, DSV_SEARCH_FILTER, iterationHandle);
	}
	//lets remove trailing ':'
	if (ctxp)
		*contexts = 0;
	else
		ccode = PAM_USER_UNKNOWN;	// not found...

	if (retBuf)
		NWDSFreeBuf(retBuf);
Exit5:;
	if (cur)
		NWDSFreeFilter(cur, NULL);
Exit6:;
	if (searchFilter)
		NWDSFreeBuf(searchFilter);
Exit3:;
Exit2:;
	NWDSFreeContext(context);
Exit1:;
	return ccode;
}

/* new code called by tree=XXX:ctx1,ctx2...,ctxn/group  cmd line parameter  */
/* or by ndsserver =XXX:ctx1.../Group */
static int
nw_create_verify_conn_to_tree(NWCONN_HANDLE * conn, NWObjectID * id,
			      const char *tree, const char *user, const char *contexts, const char *pwd,
			      int qflag, const char *group, nuint nameFormat)
{
	NWCONN_HANDLE cn = NULL;
	NWCCODE err;
	NWObjectID oid;
	NWDSContextHandle ctx = NULL;
	nuint32 c;
	char fqdn[MAX_DN_CHARS + 1];
	const char *ctxStart;
	const char *ctxEnd;
	char ctxbuf[4096];	// buffer to hold possible matches in a contexless login

	err = NWCCOpenConnByName(NULL, tree, nameFormat, NWCC_OPEN_NEW_CONN, NWCC_RESERVED, &cn);
	if (err) {
		if (nameFormat == NWCC_NAME_FORMAT_NDS_TREE)
			syslog(LOG_WARNING, "%s when trying to open connection to tree %s \n", strnwerror(err), tree);
		else
			syslog(LOG_WARNING, "%s when trying to open connection to NDS server !%s! \n", strnwerror(err), tree);
		return PAM_TRY_AGAIN;
	}

	err = PAM_SYSTEM_ERR;
	if (NWDSCreateContextHandle(&ctx)) {
		syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
		goto bailout;
	}
	if (NWDSGetContext(ctx, DCK_FLAGS, &c)) {
		syslog(LOG_WARNING, "NWDSGetContext(DCK_FLAGS) failed with %s\n", strnwerror(err));
		goto bailout;
	}
	c |= DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
	c &= ~DCV_CANONICALIZE_NAMES;

	if (NWDSSetContext(ctx, DCK_FLAGS, &c)) {
		syslog(LOG_WARNING, "NWDSSetContext(DCK_FLAGS) failed with %s\n", strnwerror(err));
		goto bailout;
	}

	if (NWDSAddConnection(ctx, cn)) {
		syslog(LOG_WARNING, "NWDSAddConnection failed with %s\n", strnwerror(err));
		goto bailout;
	}
	/* test 1.21* WITH PAM first login context is [Root]  (pam has no $HOME)
	   but with xscreensaver, context is red by ncpfs from a nwclient file in user's home
	   and resolution fails !
	   same problem with nds_group checking OK with PAM since both value are realtive to [root]
	   by with xscreensaver , the ctx is set to default, so comparing is done to the wrong group name
	   if (NWDSGetContext(ctx,DCK_NAME_CONTEXT,fqdn))
	   syslog(LOG_WARNING, "NWDSGetContext (DCK_NAME_CONTEXT) failed with %s\n", strnwerror(err));
	   else
	   syslog(LOG_WARNING, "Default context is %s :\n", fqdn);

	 */

// rev 1.25 , implement contextless login if no contexts are provided in the contexts list

	if (!contexts) {
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "Trying contexless login for %s on %s\n", tree, user);
		if (nw_ctx_search(user, cn, ctxbuf, sizeof (ctxbuf))) {
			err = PAM_AUTH_ERR;
			goto bailout;
		}
		contexts = ctxbuf;
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "Found  %s for %s\n", ctxbuf, user);
	}

	/* scan the search contexts list */
	ctxStart = contexts;
	do {
		size_t ln;

		err = PAM_AUTH_ERR;
		ln = strlen(user);
		if (ln >= sizeof (fqdn)) {
			syslog(LOG_ERR, "Buffer overflow attack attempted at %s:%u (%s)\n", __FILE__, __LINE__, __FUNCTION__);
			goto bailout;
		}
		memcpy(fqdn, user, ln + 1);
		if (ctxStart) {
			size_t appln;

			ctxEnd = strchr(ctxStart, ',');
			if (ctxEnd) {
				appln = ctxEnd - ctxStart;
				ctxEnd++;
			} else {
				appln = strlen(ctxStart);
			}
			if (ln + 1 + appln >= sizeof (fqdn)) {
				syslog(LOG_ERR, "Buffer overflow attack attempted at %s:%u (%s)\n", __FILE__, __LINE__, __FUNCTION__);
				goto bailout;
			}
			fqdn[ln] = '.';
			memcpy(fqdn + ln + 1, ctxStart, appln);
			fqdn[ln + 1 + appln] = 0;

			ctxStart = ctxEnd;
		}
		if (qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "nw_create_verify_conn_to_tree: trying to resolve  %s\n", fqdn);
		/* rev 1.26 : some support for the same CN in different contexts
		   the password makes the difference */
		err = NWDSMapNameToID(ctx, cn, fqdn, &oid);
		if (!err) {
			if (qflag & QF_DEBUG)
				syslog(LOG_WARNING, "trying to login as %s\n", fqdn);
			err = nds_login_auth(cn, fqdn, pwd);
			if (err && err != NWE_PASSWORD_EXPIRED) {
				syslog(LOG_WARNING, "%s when trying to login\n", strnwerror(err));
				switch (err) {
					/* case 1:good password but some other problem, report failure */
				case NWE_LOGIN_MAX_EXCEEDED:
				case NWE_LOGIN_UNAUTHORIZED_TIME:
				case NWE_LOGIN_UNAUTHORIZED_STATION:
				case NWE_ACCT_DISABLED:
					err = PAM_AUTH_ERR;
					goto bailout;
					/* case 2: one day we should really do something about this ? */
				case NWE_PASSWORD_EXPIRED:
					err = PAM_NEW_AUTHTOK_REQD;
					goto bailout;
					/*case 3: wrong password or other errors. let's try in another context */
				case ERR_NO_SUCH_ENTRY:
				case NWE_SERVER_UNKNOWN:
					err = PAM_USER_UNKNOWN;
					break;
				default:
					err = PAM_AUTH_ERR;
					break;
				}
				//goto bailout; not anymore
			} else
				break;	/* everything is Ok, except maybe password expired */
		} else {
			syslog(LOG_WARNING, "NWDSMapNameToID for %s failed with %s\n", fqdn, strnwerror(err));
		}

	} while (ctxStart);

	if (err) {
		err = PAM_AUTH_ERR;
		goto bailout;
	}
	/* PP:: not very sure this code is STILL relevant against NDS tree */
	err = nw_get_nwid(cn, &oid, qflag);	// needed again ?
	if (err) {
		syslog(LOG_WARNING, "Error %s retrieving user ID for %s\n", strnwerror(err), user);
		goto bailout;
	}
	if (qflag & QF_NOSU) {
		if (oid == 0x00000001) {
			err = PAM_AUTH_ERR;
			syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor\n", tree, user);
			goto bailout;
		}
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "User %s/%s passed supervisor check\n", tree, user);
	}
	if (qflag & QF_NOSUEQ) {
		nuint8 level;

		err = NWGetBinderyAccessLevel(cn, &level, NULL);
		if (err) {
			syslog(LOG_WARNING, "Access denied for %s/%s because of I/O error during object rights verification\n", tree, user);
			err = PAM_AUTH_ERR;
			goto bailout;
		}
		if ((level >= 0x30) || ((level & 0xF) >= 3)) {
			syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor equivalent\n", tree, user);
			err = PAM_AUTH_ERR;
			goto bailout;
		}
	}
	if (qflag & QF_DEBUG) {
		char aux[512];
		aux[0] = 0;
		if (nameFormat == NWCC_NAME_FORMAT_NDS_TREE) {
			NWCCGetConnInfo(cn, NWCC_INFO_SERVER_NAME, sizeof (aux), aux);
			syslog(LOG_DEBUG, "User %s was successfully authorized on tree %s by server %s \n", fqdn, tree, aux);
		} else {
			NWCCGetConnInfo(cn, NWCC_INFO_TREE_NAME, sizeof (aux), aux);
			syslog(LOG_DEBUG, "User %s was successfully authorized by NDS server %s on tree %s\n", fqdn, tree, aux);
		}
	}

	if (group) {
		/* v 1.21 */
		err = is_member_of_nds_group(ctx, cn, oid, group);
		if (err) {
			syslog(LOG_WARNING, "%s is not member of NDS %s\n", user, group);
			err = PAM_AUTH_ERR;
			goto bailout;
		}
	}
	if (id)
		*id = oid;
	if (conn)
		*conn = cn;
	else
		NWCCCloseConn(cn);
	NWDSFreeContext(ctx);
	return PAM_SUCCESS;
bailout:;
	/* code to be executed upon any error */
	if (ctx)
		NWDSFreeContext(ctx);
	if (cn)
		NWCCCloseConn(cn);
	return err;
}

static int
nw_attempt_auth_server(pam_handle_t * pamh, const char *server, const char *user, const char *pwd, int qflag, const char *group)
{
	int err;
	NWCONN_HANDLE conn;

	err = nw_create_verify_conn_to_server(&conn, NULL, server, user, pwd, qflag, group);
	if (err)
		return err;
	pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
	return PAM_SUCCESS;
}

/* trial code server is a Tree or a NDS server... and contexts is a comma separated list of CTX to search */
/* rev 1.25: if contexts is NULL, contextless login is attempted */
static int
nw_attempt_auth_tree(pam_handle_t * pamh,
		     const char *tree, const char *user, const char *contexts, const char *pwd,
		     int qflag, const char *group, nuint nameFormat)	// either  NWCC_NAME_FORMAT_NDS_TREE,  NWCC_NAME_FORMAT_BIND
{
	int err;
	NWCONN_HANDLE conn;

	err = nw_create_verify_conn_to_tree(&conn, NULL, tree, user, contexts, pwd, qflag, group, nameFormat);
	if (err)
		return err;
	pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
	return PAM_SUCCESS;
}

/********************************************************* part II NDS properties reading *******************/
struct nw_group_info {
	struct nw_group_info *next;
	char *name;
	gid_t gid;
	long zenFlag;		//PP
};

// thes infos will be "dumped" in a ~/nwinfos file that can be "sourced" by the 6 "Zen scripts"
// or any future scripts / applications. (see the pam_session_opening part for the used format
// and script variables names
struct nw_user_info {
	char *name;
	char *gecos;
	char *shell;
	char *dir;
	uid_t uid;
	gid_t gid;
	struct nw_group_info *groups;
	pam_handle_t *pamh;
	struct pam_ncp_state state;
	int qflag;
	char *fqdn;		/* User's FQDN from NDS */
	char *nwhomeServer;	//PP   CN of the server extracted from NDS prop Home Directory
	char *nwhomeVolume;	//PP   real name of the volume (SYS...)extracted from NDS prop Home Directory
	char *nwhomePath;	//PP   "unixified" and "uppercased" extracted from NDS prop Home Directory
	char *nwhomeMntPnt;	//PP   defaut =nwhome , can be changed by -m option
	char *emailSMTP;	//PP   NDS property (obsolete but still used)
	char *emailLDAP;	//PP   NDS property
	char *messageServer;	//PP   CN of the NDS property maybe != authenticating server used by PAM
	char *defaultTree;	//PP   the tree of the server that authenticated him
	char *defaultNameCtx;	//PP   from his canonical name
	long zenFlag;		//PP   see above
	long zenFlagOFF;	//PP   rev 1.26 turn off some zenflags on that workstation
	int isNewUser;		// PP rev 1.19
	int isScreenSaverRelogin;	// PP rev 1.21
};

static void
init_nw_user_info(struct nw_user_info *ui)
{
	ui->pamh = NULL;
	ui->qflag = 0;
	ui->zenFlag = ZF_DEFAULTS;	//PP
	ui->zenFlagOFF = 0;
	ui->name = NULL;
	ui->gecos = NULL;
	ui->shell = NULL;
	ui->dir = NULL;
	ui->uid = (uid_t) -1;
	ui->gid = (gid_t) -1;
	ui->groups = NULL;
	ui->fqdn = NULL;
	ui->nwhomeServer = NULL;	//PP
	ui->nwhomeVolume = NULL;	//PP
	ui->nwhomePath = NULL;	//PP
	ui->nwhomeMntPnt = NULL;	//PP
	ui->emailSMTP = NULL;	//PP
	ui->emailLDAP = NULL;	//PP
	ui->messageServer = NULL;	//PP
	ui->defaultTree = NULL;	//PP
	ui->defaultNameCtx = NULL;	//PP
	ui->isNewUser = 0;	//PP
}

static void
free_nw_user_info(struct nw_user_info *ui)
{
	struct nw_group_info *gi;
	struct nw_group_info *bkgi;

#define FREEFIELD(x) do if (ui->x) { free(ui->x); ui->x = NULL; } while (0)
	FREEFIELD(name);
	FREEFIELD(gecos);
	FREEFIELD(shell);
	FREEFIELD(dir);
	FREEFIELD(fqdn);
	FREEFIELD(nwhomeServer);
	FREEFIELD(nwhomeVolume);
	FREEFIELD(nwhomePath);
	FREEFIELD(nwhomeMntPnt);
	FREEFIELD(emailSMTP);
	FREEFIELD(emailLDAP);
	FREEFIELD(messageServer);
	FREEFIELD(defaultTree);
	FREEFIELD(defaultNameCtx);

#undef FREEFIELD
	for (gi = ui->groups; gi; gi = bkgi) {
		bkgi = gi->next;
		free(gi->name);
		free(gi);
	}
	ui->groups = NULL;
}

// PP:called by PAM at pam_end
// There is a possible problem storing the user_data in PAM during all the session
// If an admin changes something to that user, the data is out on sync
// so we should use this data ONLY in a "PAM_open_session" call back
// that happens just after the successful login
static void
cleanup_user_info(pam_handle_t * pamh, void *data, int error_status)
{
	free_nw_user_info((struct nw_user_info *) data);
}

static int
nw_retrieve_bindery_user_info(struct nw_user_info *ui, NWCONN_HANDLE conn, NWObjectID oid)
{

/* we have to give some support here since:
  1) writing the .nwclient file in user's home (flags NOP) requires that
     either ui.messageServer or ui.nwHomeServer to be not NULL
  2) we need user's home from bindery to mount it (flag H)
      warning: the home MUST be on the authenticating server else
       the bindery property is likely to be empty
 */
	char serverName[128];
	char userName[128];
	char *v;
	NWCCODE err;
	struct nw_property p;
	const char me[] = "nw_retrieve_bindery_user_info";

	err = NWCCGetConnInfo(conn, NWCC_INFO_USER_NAME, sizeof (userName), userName);
	if (err) {
		syslog(LOG_WARNING, "%s:unable to get back user name from connection.\n", me);
		return err;
	}

	err = NWCCGetConnInfo(conn, NWCC_INFO_SERVER_NAME, sizeof (serverName), serverName);
	if (err) {
		syslog(LOG_WARNING, "%s:unable to get back server name from connection.\n", me);
		return err;
	}
	v = strdup(serverName);
	if (!v) {
		syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
		return ENOMEM;
	}
	ui->messageServer = v;

	memset(&p, 0, sizeof (p));
	err = ncp_read_property_value(conn, NCP_BINDERY_USER, userName, 1, "IDENTIFICATION", &p);
	if (!err && p.value[0]) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "%s:got a full name %s for %s\n ", me, p.value, userName);

		v = strdup(p.value);
		if (!v) {
			syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
			return ENOMEM;
		}
		ui->gecos = v;
	}
	/*do not rely too much on this with NW4 servers:
	   in my place they do not answer correctly to this call even if
	   the user DO have his home on that server.
	   in contrast, one NW5 server, were the user DO NOT has his home on it
	   DID reply correctly ???
	 */

	memset(&p, 0, sizeof (p));
	err = ncp_read_property_value(conn, NCP_BINDERY_USER, userName, 1, "HOME_DIRECTORY", &p);
	if (!err && p.value[0]) {
		char *v1;
		char *mark = strchr(p.value, ':');
		if (mark && *(mark + 1)) {
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "%s:got a home directory %s for %s\n ", me, p.value, userName);

			*mark = 0;
			v = strdup(p.value);
			if (!v) {
				syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
				return ENOMEM;
			}
			v1 = strdup(mark + 1);
			if (!v1) {
				syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
				return ENOMEM;
			}

			ui->nwhomeServer = strdup(ui->messageServer);
			ui->messageServer = NULL;
			ui->nwhomeVolume = v;
			ui->nwhomePath = unixifyPathUC(v1);

		}
	}
	return 0;
}

/******************************************** NDS_Reading ********************/
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)
{
	Buf_T *attrlist;
	Buf_T *info;
	NWDSCCODE dserr;
	const struct attrop *ptr;
	nuint32 iterHandle;

	dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrlist);
	if (dserr) {
		syslog(LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
		goto bailout;
	}
	dserr = NWDSInitBuf(ctx, DSV_READ, attrlist);
	if (dserr) {
		syslog(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) {
			syslog(LOG_WARNING, "NWDSPutAttrName(%s) failed with %s\n", ptr->attrname, strnwerror(dserr));
			goto bailoutbuf1;
		}
	}
	dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &info);
	if (dserr) {
		syslog(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;
			else
				syslog(LOG_WARNING, "NWDSRead() failed with %s\n", strnwerror(dserr));
			goto bailoutbuf2;
		}
		dserr = NWDSGetAttrCount(ctx, info, &attrs);
		if (dserr) {
			syslog(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) {
				syslog(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) {
					syslog(LOG_WARNING, "NWDSComputeAttrValSize() failed with %s\n", strnwerror(dserr));
					goto bailoutcloit;
				}
				val = malloc(sz);
				if (!val) {
					syslog(LOG_WARNING, "malloc() failed with %s\n", strnwerror(ENOMEM));
					goto bailoutcloit;
				}
				dserr = NWDSGetAttrVal(ctx, info, synt, val);
				if (dserr) {
					free(val);
					syslog(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) {
						syslog(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) {
		// PP let's keep the final dserr as the 'out of memory error' from ptr->getval()
		NWDSCCODE dserr2 = NWDSCloseIteration(ctx, DSV_READ, iterHandle);
		if (dserr2) {
			syslog(LOG_WARNING, "NWDSCloseIteration() failed with %s\n", strnwerror(dserr2));
		}
	}
bailoutbuf2:;
	NWDSFreeBuf(info);
bailoutbuf1:;
	NWDSFreeBuf(attrlist);
bailout:;
	return dserr;
}

/*PP ************************************************* splitting home directory attribute*/

struct nw_home_info {
	char *hs;
	char *hrn;
};

static NWDSCCODE
nds_pp_host_server(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_home_info *hi = (struct nw_home_info *) arg;
	const char *src = val;
	size_t len;
	char *dot;
	char *v;

	dot = strchr(src, '.');
	if (dot) {
		len = dot - src;
	} else {
		len = strlen(src);
	}
	v = malloc((len + 1) * sizeof (char));
	if (!v) {
		syslog(LOG_WARNING, "Not enough memory for strdup()\n");
		return ENOMEM;
	}
	memcpy(v, src, len);
	v[len] = 0;
	hi->hs = v;
	return 0;
}

static NWDSCCODE
nds_pp_host_resource_name(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_home_info *hi = (struct nw_home_info *) arg;

	char *v = strdup((const char *) val);
	if (!v) {
		syslog(LOG_WARNING, "Not enough memory for strdup()\n");
		return ENOMEM;
	}
	hi->hrn = v;
	return 0;
}

static NWDSCCODE
nds_home_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_home_info *hi)
{
	static const struct attrop atlist[] = {
		{ATTR_HOST_SERVER, nds_pp_host_server, SYN_DIST_NAME},
		{ATTR_HOST_RN, nds_pp_host_resource_name, SYN_CI_STRING},
		{NULL, NULL, SYN_UNKNOWN}
	};

	return nds_read_attrs(ctx, objname, hi, atlist);
}

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

/** gather Unix groups informations */

static NWDSCCODE
nds_ga_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;
	}
	return 0;
}

// PP: id no NDS8 is present, collect the group Unix ID from one of the location
// string with the format G:nnn
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// can also used to specify a name of unix group different of the NDS'one
// eg. everyone --> users
// staff --> root
static NWDSCCODE
nds_pp_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;

	//syslog(LOG_NOTICE, "start of NW group location got %s\n ",pt);
	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:
					syslog(LOG_ERR, "Invalid group GID %s for %s\n", pt, gi->name);
				}
			}
			break;
		case 'n':
		case 'N':	// unix equivalent name
			if (!gi->name) {
				char *v = strdup(cur_pt);
				if (!v) {
					syslog(LOG_WARNING, "Not enough memory for strdup()\n");
					return ENOMEM;
				}
				gi->name = v;
			}
			break;
			/* interesting feature: since we OR them, a group may have
			   serveral Z:xxxx strings in the location property */
		case 'z':
		case 'Z':
			gi->zenFlag |= decodeZenFlag(&cur_pt);
			break;
		}
	}
	return 0;
}

static NWDSCCODE
nds_group_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_group_info *gi)
{
	static const struct attrop atlist[] = {
		{ATTR_GID, nds_ga_group_unixgid, SYN_INTEGER},
		{ATTR_LOCATION, nds_pp_group_location, SYN_CI_STRING},
		{NULL, NULL, SYN_UNKNOWN}
	};

	gi->gid = (gid_t) -1;
	gi->zenFlag = 0;
	gi->name = NULL;
	return nds_read_attrs(ctx, objname, gi, atlist);
}

// PP:called only if no "Unix name has been found in NDS for that group
// a string N:nnnnn in L attribute
static NWDSCCODE
nds_group_name(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_group_info *gi)
{
	char *buff;
	const char *f;
	int c;

	/* Worst case: output is Z, two chars per one in input, and zero terminating byte */
	gi->name = buff = malloc(strlen(objname) * 2 + 2);
	if (!buff) {
		syslog(LOG_WARNING, "Not enough memory for strdup()\n");
		return ENOMEM;
	}
	f = objname;
	for (; (c = *((const unsigned char *) f)++) != 0; *buff++ = c) {
		if (c >= 'a' && c <= 'z')
			continue;
		if (c >= 'A' && c <= 'Z') {
			c += 'a' - 'A';
			continue;
		}

		if (gi->name == buff)
			*buff++ = 'Z';
// PP: why ? a group starting by a digit is not legal?
// 24/01/2001 I know why now !!!  grpadd will add user to group 1, 2 ... (bin, daemon !!!)
		if (c >= '0' && c <= '9')
			continue;
		if (c == '_' || c == ' ') {
			c = '_';
			continue;
		}
		if (c == '.') {	//stop at first dot
			if (1)
				break;
			c = 'U';
			continue;
		}
		*buff++ = 'A' + (c >> 4);
		c = 'A' + (c & 0xF);
	}
	*buff = 0;
	return 0;
}

static int nw_update_group_info(struct nw_user_info *ui, struct nw_group_info *gi);

static int
rqgroup(struct nw_user_info *ui, NWDSContextHandle ctx, const NWDSChar * objname, int primary)
{
	struct nw_group_info gi;
	struct group *grp;
	struct nw_group_info *i;
	int err;

	//syslog(LOG_NOTICE, "call of rggroup for %s\n ",objname);
	err = nds_group_info(ctx, objname, &gi);
	if (err)
		return err;
	ui->zenFlag |= gi.zenFlag;	// merge group zenFlag to user's (rev 1.25 now, so it is done even if group cannot be autocreated)
	if (!gi.name)		// no alias sent by NDS
		if (nds_group_name(ctx, objname, &gi))	// make a name
			return -1;
	setgrent();		//PP
	grp = getgrnam(gi.name);
	endgrent();		//PP
	if (grp) {
		gi.gid = grp->gr_gid;

	} else {
		err = nw_update_group_info(ui, &gi);
		if (err)
			return err;
	}
	if (primary) {
		ui->gid = gi.gid;
	}
	// PP no duplicates in group list. may happen with an alias sent by NDS
	for (i = ui->groups; i; i = i->next) {
		if (!strcmp(gi.name, i->name))
			break;
	}
	if (!i) {
		i = malloc(sizeof (*i));
		if (!i)
			return ENOMEM;
		i->name = gi.name;
		i->gid = gi.gid;
		i->next = ui->groups;
		i->zenFlag = gi.zenFlag;
		ui->groups = i;
	}
	return 0;
}

static int
build_groups_list(const struct nw_user_info *ui, char **list)
{
	const struct nw_group_info *gi;
	size_t ln;
	char *p;

	*list = NULL;
	ln = 1;
	for (gi = ui->groups; gi; gi = gi->next)
		ln += strlen(gi->name) + 1;
	if (ln == 1)
		return 0;

	p = (char *) malloc(ln);
	if (!p)
		return ENOMEM;
	*list = p;
	for (gi = ui->groups; gi; gi = gi->next) {
		ln = strlen(gi->name);
		memcpy(p, gi->name, ln);
		p += ln;
		*p++ = ',';
	}
	*--p = 0;
	//syslog(LOG_NOTICE, "end of build group list got %s\n ",*list);
	return 0;
}

/************************************ helper functions to extract some user's properties **/
static NWDSCCODE
nds_ga_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 ?)
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got a Unix ID %d from %s\n ", ui->uid, ATTR_UID);
	}
	return 0;
}

static NWDSCCODE
nds_ga_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 ?)
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got a Unix PGID %d from %s\n ", ui->gid, ATTR_PGID);
	}
	return 0;
}

// PP this is the same founction as below ???
// does Netware has two synonyms for the same property (UNIX:GID"
// and UNIX:Primary GroupID???
static NWDSCCODE
nds_ga_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 ?)
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got a Unix GID %d from %s\n ", ui->gid, ATTR_GID);
	}
	return 0;
}

static NWDSCCODE
nds_ga_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) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->dir = v;
		// talk a bit (real NDS8 attribute or dummy ?)
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got a Unix Home %s from %s\n ", ui->dir, ATTR_HOME);
	}
	return 0;
}

static NWDSCCODE
nds_ga_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) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->shell = v;
		// talk a bit (real NDS8 attribute or dummy ?)
		if (ui->qflag & QF_DEBUG)
			syslog(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);

		v = realloc(ui->gecos, sold + 1 + sadd);
		if (!v) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		v[sold] = ',';
		memcpy(v + 1, str, sadd);
	} else {
		v = malloc(sadd);
		if (!v) {
			syslog(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_ga_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 ?)
	if (ui->qflag & QF_DEBUG)
		syslog(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_ga_gecos(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	NWDSCCODE err;

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

static NWDSCCODE
nds_ga_unixpgname(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 ?)
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "got a Unix PGroup Name %s from %s\n ", (const char *) val, ATTR_PGNAME);
	if (ui->gid == (gid_t) -1) {
		rqgroup(ui, ctx, val, 1);
	}
	return 0;
}

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

	rqgroup(ui, ctx, val, 0);
	return 0;
}

#ifdef USE_POSTAL_ADDRESS
// PP: id no NDS8 is present, collect the user's basic  Unix informations from
// 6 fields of the Postal Address attribute
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// and if zenFlag processing is activated.
// some people may prefer the postal address instead of the Location property
// used here by default.
// advantages: can be directty edited.No need to prepend X:
// disadvantages: limited to 30 chars and 6 strings
//    The location attribute is more convenient
//    ( no limit of number of strings and 128 chars rather than 30).

static NWDSCCODE
nds_pp_postal_address(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	Postal_Address_T *pa = (Postal_Address_T *) val;
	int i, n;
	const char *pt;
	//rqgroup(ui, ctx, val, 0);
	// adress 0 = unix UID        leading spaces not significant
	// adress 1= unix primary GID leading spaces not significant
	// adress 2= unix home dir    spaces significant
	// adress 3= unix shell       spaces  significant
	// adress 4= other group name spaces  turned to _
	//       ONLY ONE: do not use a comma separated list group1,group2,.....
	// adress 5= Zen flags        spaces ignored
/* note that this attribute is not mandatory
so if it does not exists that "call back" is not called */

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "start of PO address\n");
	for (i = 0; i < 6; i++) {
		pt = (const char *) (*pa)[i];
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "reading po_ad %d %s ", i, pt);
		// caution it may be there but empty (pointing to à 0) !!!
		if (pt && strlen(pt))
			switch (i) {
			case 0:
				if (ui->uid == (uid_t) -1) {	// do not overwrite a DS 8 answer
					const char *cur_pt = pt;
					switch (getnumber(&n, &cur_pt)) {
					case 0:
						ui->uid = n;
						break;
					default:
						syslog(LOG_ERR, "Invalid user ID %s for users %s\n", *pt, ui->name);
					}
				}
				break;
			case 1:
				if (ui->gid == (gid_t) -1) {	// do not overwrite a DS 8 answer
					const char *cur_pt = pt;
					switch (getnumber(&n, &cur_pt)) {
					case 0:
						ui->gid = n;
						break;
					default:
						syslog(LOG_ERR, "Invalid primary user GID %s for user %s\n", *pt, ui->name);
					}
				}
				break;
			case 2:
				if (!ui->dir) {	// do not overwrite a DS 8 answer
					char *v = strdup(pt);
					if (!v) {
						syslog(LOG_WARNING, "Not enough memory for strdup()\n");
						return ENOMEM;
					}

					ui->dir = v;
				}
				break;
			case 3:
				if (!ui->shell) {	// do not overwrite a DS 8 answer
					char *v = strdup(pt);
					if (!v) {
						syslog(LOG_WARNING, "Not enough memory for strdup()\n");
						return ENOMEM;
					}
					ui->shell = v;
				}
				break;
			case 4:	// other group name: only one may be specified, unless we
				// separe them by comma and parse. but limited to 30 chars
				//(one of the reasons why we dropped Postal Address as a substitue for NDS8)
				rqgroup(ui, ctx, val, 0);
				break;

			case 5:	// ZenFlag per user ABCDEFGHIJKLMNOPQRSTUVWXYZ  (30 max!!!)
				ui->zenFlag |= decodeZenFlag(&pt);
				break;
			}
	}
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of PO address\n");
	return 0;
}
#else
// 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
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// 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

// feature I: since zenflags are ORED, a user can have SEVERAL z:xxxx strings in his location strings
// better than deleting and recreating it !!!
// feature II: several O:groups strings are possible ( secondary groups defintion)
// DO NOT use o:group1,group2.....
static NWDSCCODE
nds_pp_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;

	if (ui->qflag & QF_DEBUG)
		syslog(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:
					syslog(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:
					syslog(LOG_ERR, "Invalid primary user GID %s\n", pt);
				}
			}
			break;
		case 'p':	// primary group name, illegal chars converted by nds_group_name
		case 'P':
			if (ui->gid == (gid_t) -1)	// do not overwrite a DS 8 answer
				rqgroup(ui, ctx, cur_pt, 1);
			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) {
					syslog(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) {
					syslog(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
			if (ui->qflag & QF_DEBUG) {
				syslog(LOG_NOTICE, "before comment gecos is %s\n ", ui->gecos);
			}
			err = nds_update_gecos(ui, cur_pt);
			if (err) {
				return err;
			}
			if (ui->qflag & QF_DEBUG) {
				syslog(LOG_NOTICE, "gecos %s\n ", ui->gecos);
			}
			break;
		case 'o':	// other group names
		case 'O':	// wze can have several entries o:group (but not o:group1,group2...)
			rqgroup(ui, ctx, cur_pt, 0);
			break;

		case 'z':	// ZenFlag per user ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 (32 max)
		case 'Z':
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "before decode ZF is %s\n ", cur_pt);
			ui->zenFlag |= decodeZenFlag(&cur_pt);
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "after decode ZF is %lx\n ", ui->zenFlag);
			break;
		}
	}
	return 0;
}
#endif

static NWDSCCODE
nds_pp_home_directory(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	const Path_T *pa = (const Path_T *) val;
	NWDSCCODE dserr;
	struct nw_home_info hi = { NULL, NULL };
	char *v;

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "start of NW home dir got %s %s \n", pa->volumeName, pa->path);
	dserr = nds_home_info(ctx, (char *) pa->volumeName, &hi);
	if (dserr)
		return dserr;
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "got %s %s ", hi.hs, hi.hrn);

	ui->nwhomeServer = hi.hs;
	ui->nwhomeVolume = hi.hrn;

	v = strdup(pa->path);
	if (!v) {
		syslog(LOG_WARNING, "Not enough memory for strdup()\n");
		return ENOMEM;
	}
	ui->nwhomePath = unixifyPathUC(v);
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of NW home dir\n");
	return 0;
}

static NWDSCCODE
nds_pp_smtp_email_address(NWDSContextHandle ctx, const void *val, void *arg)
{
	struct nw_user_info *ui = (struct nw_user_info *) arg;
	const EMail_Address_T *em = (const EMail_Address_T *) val;
	char *v;

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "start of NW smtp email got %u %s\n", em->type, em->address);
	// pick up only the first with type=0 (SMTP) !
	if ((!ui->emailSMTP && em->type == 0) && (!strncmp(em->address, "SMTP:", 5))) {
		// skip header SMTP: must be if type is 0 ?
		v = strdup(em->address + 5);
		if (!v) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		ui->emailSMTP = v;

	}
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of NW smtp email GOT %s\n", ui->emailSMTP);
	return 0;
}

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

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "start of NW ldap email got %s\n", (const char *) val);

	// pick up only the first of a multi-valued attribute !
	if (!ui->emailLDAP) {
		char *v = strdup((const char *) val);
		if (!v) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		trim(v);	// remove the leading space added by other PP utilities
		ui->emailLDAP = v;
	}
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of NW ldap email GOT [%s]\n", ui->emailLDAP);
	return 0;
}

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

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "start of NW message server got %s\n", (const char *) val);

	if (!ui->messageServer) {
		char *v;
		const char *dot;
		const char *str = val;
		size_t ln;

		dot = strchr(str, '.');
		if (dot) {
			ln = dot - str;
		} else {
			ln = strlen(str);
		}
		v = malloc(ln + 1);
		if (!v) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			return ENOMEM;
		}
		memcpy(v, str, ln);
		v[ln] = 0;
		ui->messageServer = v;
	}
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of NW message server GOT [%s]\n", ui->messageServer);
	return 0;
}

/*****************************************************GET ALL USER INFO FROM NDS *************/

static int
nds_user_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_user_info *ui)
{
	static const struct attrop atlist[] = {
		{ATTR_UID, nds_ga_unixuid, SYN_INTEGER},
		{ATTR_PGNAME, nds_ga_unixpgname, SYN_DIST_NAME},
		{ATTR_PGID, nds_ga_unixpgid, SYN_INTEGER},
		{ATTR_GID, nds_ga_unixgid, SYN_INTEGER},
		{ATTR_HOME, nds_ga_unixhome, SYN_CE_STRING},
		{ATTR_SHELL, nds_ga_unixshell, SYN_CE_STRING},
		{ATTR_COM, nds_ga_unixcomment, SYN_CI_STRING},
		{ATTR_GECOS, nds_ga_gecos, SYN_CI_STRING},
		{ATTR_GRP_MBS, nds_ga_group_membership, SYN_DIST_NAME},
		 /*PP*/ {ATTR_HOME_NW, nds_pp_home_directory, SYN_PATH},
		 /*PP*/ {ATTR_SMTP_EMAIL, nds_pp_smtp_email_address, SYN_EMAIL_ADDRESS},
		 /*PP*/ {ATTR_LDAP_EMAIL, nds_pp_ldap_email_address, SYN_CI_STRING},
		 /*PP*/ {ATTR_MESSAGE_SERVER, nds_pp_message_server, SYN_DIST_NAME},
		{NULL, NULL, SYN_UNKNOWN}
	};

	static const struct attrop atlist2[] = {
#ifdef USE_POSTAL_ADDRESS
		 /*PP*/ {ATTR_POSTAL_ADDRESS, nds_pp_postal_address, SYN_PO_ADDRESS},
#else
		 /*PP*/ {ATTR_LOCATION, nds_pp_location, SYN_CI_STRING},
#endif
		{NULL, NULL, SYN_UNKNOWN}
	};

	int 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);
	if (err)
		return err;
	if (ui->qflag & QF_DEBUG) {
#ifdef USE_POSTAL_ADDRESS
		syslog(LOG_NOTICE, "using postal address attribute\n");
#else
		syslog(LOG_NOTICE, "using location attribute\n");
#endif
	}
	return nds_read_attrs(ctx, objname, ui, atlist2);
}

/****************** main NDS reading function ********************************************/
static int
nw_retrieve_nds_user_info(struct nw_user_info *ui, NWCONN_HANDLE conn, NWObjectID oid)
{
	NWDSContextHandle ctx;
	nuint32 c;
	NWDSChar username[MAX_DN_BYTES];
	int err;

	err = NWDSCreateContextHandle(&ctx);
	if (err) {
		syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
		err = PAM_SYSTEM_ERR;
		goto bailout;
	}
	c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
	err = NWDSSetContext(ctx, DCK_FLAGS, &c);
	if (err) {
		syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
		err = PAM_SYSTEM_ERR;
		goto bailoutctx;
	}
	NWDSAddConnection(ctx, conn);
	err = NWDSMapIDToName(ctx, conn, oid, username);
	if (err) {
		syslog(LOG_WARNING, "NWDSMapIDToName() failed with %s\n", strnwerror(err));
		err = PAM_USER_UNKNOWN;
		goto bailoutctx;
	}
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "User has DN %s\n", username);
	err = nds_user_info(ctx, username, ui);
	if (err) {
		syslog(LOG_NOTICE, "Could not retrieve nds user info: %s\n", strnwerror(err));
		err = PAM_AUTHTOK_ERR;
		goto bailoutctx;
	}
	{
		ui->fqdn = strdup(username);
		if (!ui->fqdn) {
			syslog(LOG_WARNING, "Not enough memory for strdup()\n");
			err = PAM_SYSTEM_ERR;
			goto bailoutctx;
		}
	}
//PP a good spot to retrieve user's defaultNameCtx and defaultTree
	{
		char *p = username;
		char tn[MAX_TREE_NAME_CHARS + 1];

		while (*p && (*p != '.'))
			p++;	//luckily we are in XLATE_STRINGS[TYPELESS_NAMES !!!
		if (*p && *(p + 1)) {	// skip also the first dot
			ui->defaultNameCtx = strdup(p + 1);
			if (!ui->defaultNameCtx) {
				syslog(LOG_WARNING, "Not enough memory for strdup()\n");
				err = ENOMEM;
			}
		}
		if (!(err = NWCCGetConnInfo(conn, NWCC_INFO_TREE_NAME, sizeof (tn), tn))) {
			ui->defaultTree = strdup(tn);
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "NWCCGetConnInfo(NWCC_INFO_TREE_NAME) returned %s\n", tn);
			if (!ui->defaultTree) {
				syslog(LOG_WARNING, "Not enough memory for strdup()\n");
				err = ENOMEM;
			}
		} else {
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_WARNING, "NWCCGetConnInfo(NWCC_INFO_TREE_NAME) returned %x\n", err);
			err = 0;	// not lethal !
		}
	}
//end PP
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "end of retrieve nds user info code: %s\n", strnwerror(err));
bailoutctx:;
	NWDSFreeContext(ctx);
bailout:;
	if (!err & (ui->qflag & QF_DEBUG))
		syslog(LOG_NOTICE, "%u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
	return err;
}

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

static int
nw_retrieve_user_info(struct nw_user_info *ui)
{
	struct ncp_conn *conn;
	long err;
	NWObjectID oid;

	err = pam_get_data(ui->pamh, "pam.ncpfs.passwd.conn", (const void **) &conn);
	if (err)
		return err;

	err = nw_get_nwid(conn, &oid, ui->qflag);
	if (err)
		return err;

	if (ui->qflag & QF_BINDERY) {
		return nw_retrieve_bindery_user_info(ui, conn, oid);
	} else {
		return nw_retrieve_nds_user_info(ui, conn, oid);
	}
}

static int
exechelper(const char *program, const char *argv[], const char *username)
{				// NULL is PAM so root 
	int i;
	int err;

	i = fork();
	if (i < 0) {
		err = errno;
		syslog(LOG_ERR, "Cannot fork: %s\n", strerror(err));
		return err;
	}
	if (i) {
		int status;

		switch (waitpid(i, &status, 0)) {
		case 0:
			syslog(LOG_ERR, "waitpid unexpectedly terminated: %s\n", strerror(errno));
			return -1;
		case (pid_t) - 1:
			syslog(LOG_ERR, "waitpid: Fatal: No child processes\n");
			return -1;
		default:
			if (!WIFEXITED(status)) {
				syslog(LOG_ERR, "%s killed by signal\n", program);
				return -1;
			}
			if (WEXITSTATUS(status)) {
				syslog(LOG_ERR, "%s finished with error %d\n", program, WEXITSTATUS(status));
				return -1;
			}
		}
		return 0;
	} else {
		int fd;

		fd = open("/dev/null", O_RDWR);
		if (fd == -1) {
			err = errno;
			syslog(LOG_ERR, "Cannot open /dev/null: %s\n", strerror(err));
			exit(126);
		}
		dup2(fd, 0);
		dup2(fd, 1);
		dup2(fd, 2);
		argv[0] = program;

		if (username) {	// run child process as username
			struct passwd *pwd = getpwnam(username);
			//syslog(LOG_ERR, " as user %s ..",username);
			if (!pwd) {
				syslog(LOG_ERR, "Oops, something wicked happened, user %s does not exist...", username);
				exit(111);
			}
			if (initgroups(username, pwd->pw_gid)) {
				/* initgroups() fails on shutdown as we 
				   do not run as 'root' at shutdown time...
				   Just ignore error from initgroups,
				   but treat setgid/setuid problems still
				   as fatal. */
				if (errno != EPERM) {
					syslog(LOG_ERR, "Oops, initgroups failed for user %s: %s\n", username, strerror(errno));
					exit(112);
				}
			}
			if (setgid(pwd->pw_gid)) {
				syslog(LOG_ERR, "Oops, setgid failed for user %s: %s\n", username, strerror(errno));
				exit(113);
			}
			if (setuid(pwd->pw_uid)) {
				syslog(LOG_ERR, "Oops, setuid failed for user %s: %s\n", username, strerror(errno));
				exit(114);
			}
		}
		execv(program, (char *const *) (unsigned long) argv);
		err = errno;
		syslog(LOG_ERR, "Cannot execute %s: %s\n", program, strerror(err));
		exit(127);
	}
}

static int
groupadd(const char *name, gid_t gid, int verbose)
{
	char gidstr[30];
	const char *argv[10];
	int err;

	sprintf(gidstr, "%u", gid);

	argv[1] = "-g";
	argv[2] = gidstr;
	argv[3] = name;
	argv[4] = NULL;

//TODO: it would be nice to have the name of the adding group utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the group creation ( a good shell script)
	err = exechelper("/usr/sbin/groupadd", argv, NULL);
	if (err)
		return err;
	if (verbose)
		syslog(LOG_NOTICE, "Group %s(%u) created\n", name, gid);
	return 0;
}

static int
do_chfn(const char *uname, const char *gecos, int verbose)
{
	char *gecosbuf;
	char *p;
	const char *argv[10];
	int err;
	int i = 1;

	gecosbuf = strdup(gecos);
	if (!gecosbuf) {
		syslog(LOG_WARNING, "Not enough memory for gecos buffer\n");
		return ENOMEM;
	}
	p = strchr(gecosbuf, ',');
	if (p) {
		*p++ = '\0';
	} else {
		(const char *) p = "";	/* Use empty string for deletion */
	}

	argv[i++] = "-f";
	argv[i++] = gecosbuf;

	argv[i++] = "-o";
	argv[i++] = p;

	argv[i++] = uname;
	argv[i++] = NULL;

	/******/
	if (verbose) {
		int j;
		char s[8192];
		char *pos = s;
		size_t space = sizeof (s) - 1;

		for (j = 1; j < i; j++) {
			size_t ln = strlen(argv[j]);

			if (ln > space) {
				ln = space;
			}
			memcpy(pos, argv[j], ln);
			pos += ln;
			space -= ln;
			if (space) {
				*pos++ = ' ';
				space--;
			}
		}
		*pos = 0;
		syslog(LOG_WARNING, "%s", s);
	}
//TODO: it would be nice to have the name of the chfn utility
// in some config file (/etc/pam_ncp.conf)
	err = exechelper("/usr/bin/chfn", argv, NULL);
	if (verbose)
		syslog(LOG_NOTICE, "chfn (%s) for user %s ended with error code %d\n", gecos, uname, err);

	return err;
}

static int
usermod(const char *uname, gid_t gid, const char *gecos, const char *dir, const char *shell,
/*PP added */ const char *grplist, int verbose)
{

	char gidstr[30];
	const char *argv[30];
	int err;
	int i, j;
	int usechfn = 0;

	i = 1;
	if (gid != (gid_t) -1) {
		sprintf(gidstr, "%u", gid);
		argv[i++] = "-g";
		argv[i++] = gidstr;
	}
	if (gecos) {		// donne un erreur 3 !!!
		if (!strchr(gecos, ',')) {
			argv[i++] = "-c";
			argv[i++] = gecos;
		} else {
			usechfn = 1;
		}
	}
	if (dir) {
		argv[i++] = "-d";
		argv[i++] = dir;
	}
	if (shell) {
		argv[i++] = "-s";
		argv[i++] = shell;
	}
/******************** OK with COL 24 usermod !!! ****/
	if (grplist) {
		argv[i++] = "-G";
		argv[i++] = grplist;
	}

	argv[i++] = uname;
	argv[i] = NULL;
	// temp debug
	if (verbose) {
		for (j = 1; j < i; j++)
			syslog(LOG_NOTICE, "usermod %u %s", j, argv[j]);
	}
//TODO: it would be nice to have the name of the modif user utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the user modification ( a good shell script)
	err = exechelper("/usr/sbin/usermod", argv, NULL);
	if (err)
		return err;

	if (usechfn) {
		if (verbose)
			syslog(LOG_NOTICE, "User %s has a comma in his gecos %s\n", uname, gecos);
								/*err= */ do_chfn(uname, gecos, verbose);
								//don't return err, it is not lethal
	}
	if (verbose)
		syslog(LOG_NOTICE, "User %s modified\n", uname);

	return 0;
}

#define concat2a(one,two)	({		\
	size_t lone = strlen(one);		\
	size_t ltwo = strlen(two);		\
	char* buf = alloca(lone + ltwo + 1);	\
	memcpy(buf, one, lone);			\
	memcpy(buf + lone, two, ltwo + 1);	\
	buf; })

#define concat3a(one,med,two)	({		\
	size_t lone = strlen(one);		\
	size_t ltwo = strlen(two);		\
	char* buf = alloca(lone + 1+ ltwo + 1);	\
	memcpy(buf, one, lone);			\
	buf[lone] = med;			\
	memcpy(buf + lone + 1, two, ltwo + 1);	\
	buf; })

static int
useradd(const char *uname, uid_t uid, gid_t gid, const char *gecos, const char *dir, const char *shell, const char *grplist, int process_groups_later, int create_local_home, int verbose)
{
/* rev PP
  -Caldera Open Linux does not like the -G parameter in useradd, but it is OK in usermod ?
  - so we call usermod at the end if needed...
  -add some defaut if nothing has been retrieved from NDS and we are in SERVER_REQUIRED MODE
    Il dir is empty change it to /home/username
    if shell is empty change it to /bin/bash
    if guid is empty change it to 100
    so only UNIX_UID is REALLY SERVER_REQUIRED ( others get default values)

*/
	char gidstr[30];
	char uidstr[30];
	const char *argv[30];
	int err;
	int i;
	int usechfn = 0;

	i = 1;
	if (gid == (gid_t) -1) {
		gid = 100;
	}
	sprintf(gidstr, "%u", gid);
	argv[i++] = "-g";
	argv[i++] = gidstr;

	//PP
	/******************** Not with COL 24 useradd****/
	if (!process_groups_later && grplist) {
		argv[i++] = "-G";
		argv[i++] = grplist;
	}
	//PP

	if (!gecos) {
		gecos = uname;
	}
	// may have a comma in it ( full name,comment...)
	if (!strchr(gecos, ',')) {
		argv[i++] = "-c";
		argv[i++] = gecos;
	} else {
		usechfn = 1;
	}

	// PP modified for a  default value and still testing for no existence
	if (!dir) {
		dir = concat2a("/home/", uname);
	}
	{
		struct stat statbuf;
		argv[i++] = "-d";
		argv[i++] = dir;
		if (create_local_home && !lstat(dir, &statbuf)) {	// rev 1.24
			syslog(LOG_ERR, "Will not create %s because of home directory %s already exist\n", uname, dir);
			return -1;
		}
	}
/*
Currently if the home is remote and the NFS server is down, the home WILL not be created locally
So console login will not find the home and fall to / directory, and graphical login will fails
*/

	if (!shell) {
		shell = "/bin/bash";
	}
	argv[i++] = "-s";
	argv[i++] = shell;
	// end PP
	sprintf(uidstr, "%u", uid);
	argv[i++] = "-u";
	argv[i++] = uidstr;
	argv[i++] = create_local_home ? "-m" : "-M";	// rev 1.24
	argv[i++] = uname;
	argv[i] = NULL;
	if (verbose) {
		syslog(LOG_NOTICE, "useradd %s %s %s %s %s %s", argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
		syslog(LOG_NOTICE, "useradd %s %s %s %s %s %s", argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]);
	}
//TODO: it would be nice to have the name of the adding user utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the user autocreation ( a good shell script)

	err = exechelper("/usr/sbin/useradd", argv, NULL);
	if (err)
		return err;
	if (verbose)
		syslog(LOG_NOTICE, "User %s(%u) added\n", uname, uid);

	if (usechfn) {
		if (verbose)
			syslog(LOG_NOTICE, "User %s has a comma in his gecos %s\n", uname, gecos);
								/*err= */ do_chfn(uname, gecos, verbose);
								// not lethal if it fails
	}
	// PP: use a call to usermod to process the other groups lists. Needed at least with Caldera OpenLinux
	if (process_groups_later && grplist) {
		return usermod(uname, (gid_t) -1, NULL, NULL, NULL, grplist, verbose);
	}
	return 0;
}

static int
uidcmp(const void *p1, const void *p2)
{
	if (*(const uid_t *) p1 < *(const uid_t *) p2)
		return -1;
	if (*(const uid_t *) p1 > *(const uid_t *) p2)
		return 1;
	return 0;
}

static int
gidcmp(const void *p1, const void *p2)
{
	if (*(const gid_t *) p1 < *(const gid_t *) p2)
		return -1;
	if (*(const gid_t *) p1 > *(const gid_t *) p2)
		return 1;
	return 0;
}

static int
allocategid(const struct pam_ncp_state *state, gid_t *gid)
{
	struct group *grp;
	gid_t now;

	now = state->gid.min;
	setgrent();
	if (state->gid.flags & QFC_NEXT_UNUSED) {
		while ((grp = getgrent()) != NULL) {
			if (grp->gr_gid < state->gid.min)
				continue;
			if (grp->gr_gid >= state->gid.max)
				continue;
			if (now <= grp->gr_gid)
				now = grp->gr_gid + 1;
		}
	} else {
		struct {
			gid_t *array;
			size_t used;
			size_t alloc;
		} gids;
		gid_t *p;

		gids.array = NULL;
		gids.alloc = 0;
		gids.used = 0;
		while ((grp = getgrent()) != NULL) {
			if (grp->gr_gid < state->gid.min)
				continue;
			if (grp->gr_gid >= state->gid.max)
				continue;
			if (gids.used >= gids.alloc) {
				gid_t *np;
				size_t ns;

				if (gids.array) {
					ns = gids.alloc * 2;
					np = (gid_t *) realloc(gids.array, ns * sizeof (gid_t));
				} else {
					ns = 16;
					np = (gid_t *) malloc(ns * sizeof (gid_t));
				}
				if (!np) {
					syslog(LOG_WARNING, "Not enough memory\n");
					if (gids.array)
						free(gids.array);
					return -1;
				}
				gids.array = np;
				gids.alloc = ns;
			}
			gids.array[gids.used++] = grp->gr_gid;
		}
		qsort(gids.array, gids.used, sizeof (*gids.array), gidcmp);
		p = gids.array;
		while (gids.used-- && (now == *p)) {
			now++;
			p++;
		}
		free(gids.array);
	}
	if (now >= state->gid.max) {
		return -1;
	}
	endgrent();
	*gid = now;
	return 0;
}

static int
allocateuid(const struct pam_ncp_state *state, uid_t *uid)
{
	struct passwd *pwd;
	uid_t now;

	now = state->uid.min;
	setpwent();
	if (state->uid.flags & QFC_NEXT_UNUSED) {
		while ((pwd = getpwent()) != NULL) {
			if (pwd->pw_uid < state->uid.min)
				continue;
			if (pwd->pw_uid >= state->uid.max)
				continue;
			if (now <= pwd->pw_uid)
				now = pwd->pw_uid + 1;
		}
	} else {
		struct {
			uid_t *array;
			size_t used;
			size_t alloc;
		} uids;
		uid_t *p;

		uids.array = NULL;
		uids.alloc = 0;
		uids.used = 0;
		while ((pwd = getpwent()) != NULL) {
			if (pwd->pw_uid < state->uid.min)
				continue;
			if (pwd->pw_uid >= state->uid.max)
				continue;
			if (uids.used >= uids.alloc) {
				uid_t *np;
				size_t ns;

				if (uids.array) {
					ns = uids.alloc * 2;
					np = (uid_t *) realloc(uids.array, ns * sizeof (uid_t));
				} else {
					ns = 16;
					np = (uid_t *) malloc(ns * sizeof (uid_t));
				}
				if (!np) {
					syslog(LOG_WARNING, "Not enough memory\n");
					if (uids.array)
						free(uids.array);
					return -1;
				}
				uids.array = np;
				uids.alloc = ns;
			}
			uids.array[uids.used++] = pwd->pw_uid;
		}
		qsort(uids.array, uids.used, sizeof (*uids.array), uidcmp);
		p = uids.array;
		while (uids.used-- && (now == *p)) {
			now++;
			p++;
		}
		free(uids.array);
	}
	if (now >= state->uid.max) {
		return -1;
	}
	endpwent();
	*uid = now;
	return 0;
}

/*
   -10     : 100% match with server required, but no UNIX:GID or
             gid or name already in use
   -11     : no more free gids
   -12     : group creation failed
 */
static int
nw_update_group_info(struct nw_user_info *ui, struct nw_group_info *gi)
{
	struct group *grp;

// PP: added endgrent()
// no more return but goto bailout.
	int err = PAM_SUCCESS;

	setgrent();
	// check again for grp name, so if an alias has been sent by NDS,
	// Location string of a NDS group with N:UnixAlias, we will find it here.
	// i.e.  everyone -> users or staff -> root
	grp = getgrnam(gi->name);
	if (!grp) {
		while (ui->state.gid.flags & (QFC_PREFFER_SERVER | QFC_REQUIRE_SERVER)) {
			if (gi->gid == (gid_t) -1) {
				if ((ui->qflag & QF_DEBUG) && (ui->state.gid.flags & QFC_REQUIRE_SERVER))
					syslog(LOG_DEBUG, "Will not create group %s: no UNIX:GID present\n", gi->name);
				break;
			}
			grp = getgrgid(gi->gid);
			if (grp) {
				if ((ui->qflag & QF_DEBUG) && (ui->state.gid.flags & QFC_REQUIRE_SERVER))
					syslog(LOG_DEBUG, "Will not create group %s: gid %u is already used by %s\n", gi->name, gi->gid, grp->gr_name);
				break;
			}
			if (groupadd(gi->name, gi->gid, ui->qflag & QF_DEBUG))
				break;	// trouble
			goto bailout;	// OK
		}
		if (ui->state.gid.flags & QFC_REQUIRE_SERVER) {
			err = -10;
			goto bailout;
			//return -10;
		}

		if (allocategid(&ui->state, &gi->gid)) {
			syslog(LOG_WARNING, "Cannot allocate gid for group %s\n", gi->name);
			err = -11;
			goto bailout;
			//return -11;
		}
		if (groupadd(gi->name, gi->gid, ui->qflag & QF_DEBUG)) {
			syslog(LOG_WARNING, "Could not create group %s\n", gi->name);
			err = -12;
			goto bailout;
			//return -12;
		}
	} else {
		gi->gid = grp->gr_gid;
	}
bailout:;
	endgrent();		//PP
	return err;
}

static int
nw_update_user_info(struct nw_user_info *ui)
{

	struct passwd *pwd;
	char *glist;
	const char *gecos;
// PP: added endpwent() and freeing of malloced glist
// no more return but goto bailout.
	int err = PAM_SUCCESS;

	if (!ui->name) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "NW_UPDATE_USER:failed user has no name %u\n", ui->uid);
		return -1;
	}

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "NW_UPDATE_USER: %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);

	if (build_groups_list(ui, &glist)) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "NW_UPDATE_USER:failed to build group list for %s\n", ui->name);
		return -1;
	}

	setpwent();
	pwd = getpwnam(ui->name);

	//PP
	gecos = ui->gecos ? : "";
	// end PP
	if (!pwd) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "NW_UPDATE_USER:creating %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
		while (ui->state.uid.flags & (QFC_PREFFER_SERVER | QFC_REQUIRE_SERVER)) {
			if (ui->uid == (uid_t) -1) {
				if ((ui->qflag & QF_DEBUG) && (ui->state.uid.flags & QFC_REQUIRE_SERVER))
					syslog(LOG_DEBUG, "Will not create user %s: no UNIX:UID present\n", ui->name);
				break;
			}
			pwd = getpwuid(ui->uid);
			if (pwd) {
				if ((ui->qflag & QF_DEBUG) && (ui->state.uid.flags & QFC_REQUIRE_SERVER))
					syslog(LOG_DEBUG, "Will not create user %s: uid %u is already used by %s\n", ui->name, ui->uid, pwd->pw_name);
				break;
			}

			err = useradd(ui->name, ui->uid, ui->gid, gecos, ui->dir, ui->shell, glist, NO_PROCESS_GROUPS, ui->qflag & QF_CREATEHOME, ui->qflag & QF_DEBUG);
			if (err)
				break;
			ui->isNewUser = 1;	// rev 1.19
			err = PAM_SUCCESS;
			goto bailout;
		}
		if (ui->state.uid.flags & QFC_REQUIRE_SERVER) {
			syslog(LOG_WARNING, "Cannot create uid %u required by server for user %s\n", ui->uid, ui->name);
			err = -10;
			goto bailout;
			//return -10;
		}
		if (allocateuid(&ui->state, &ui->uid)) {
			syslog(LOG_WARNING, "Cannot allocate uid for user %s\n", ui->name);
			err = -11;
			goto bailout;
			//return -11;
		}

		if (useradd(ui->name, ui->uid, ui->gid, gecos, ui->dir, ui->shell, glist, NO_PROCESS_GROUPS, ui->qflag & QF_CREATEHOME, ui->qflag & QF_DEBUG)) {
			syslog(LOG_WARNING, "Cannot create user %s\n", ui->name);
			err = -12;
			goto bailout;
			//return -12;
		}
	} else {

		int diff;
		ui->uid = pwd->pw_uid;
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "NW_UPDATE_USER:updating %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);

		diff = 0;
		if ((ui->gid != (gid_t) -1) && ui->gid != pwd->pw_gid)
			diff |= QFMU_GID;
		if (*gecos && (!pwd->pw_gecos || strcmp(gecos, pwd->pw_gecos)))
			diff |= QFMU_GECOS;
		if (ui->dir && (!pwd->pw_dir || strcmp(ui->dir, pwd->pw_dir)))
			diff |= QFMU_DIR;
		if (ui->shell && (!pwd->pw_shell || strcmp(ui->shell, pwd->pw_shell)))
			diff |= QFMU_SHELL;
		diff &= ui->state.uid.modflags;
		// FIXME: glist is not processed if it is the only change ! 26/01/2001
		if (diff) {
			err = usermod(ui->name, (diff & QFMU_GID) ? ui->gid : (gid_t) -1, (diff & QFMU_GECOS) ? gecos : NULL,	//PP
				      (diff & QFMU_DIR) ? ui->dir : NULL, (diff & QFMU_SHELL) ? ui->shell : NULL, glist,	//  process groups          //PP
				      ui->qflag & QF_DEBUG);
		}
	}

bailout:;
	endpwent();
	if (glist)
		free(glist);
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "return value of update_user_info  %d for %s\n", err, ui->name);

	return err;
}

static int
getcflag(int *val, const char **str)
{
	const char *p = *str;
	int valm;
	int c;

	*val = 0;
	valm = 1;
	if (!*p)
		return 1;
	while ((c = *p++) != 0) {
		if (c == ',')
			break;
		switch (c) {
		case 'f':
		case 'N':
			*val &= ~QFC_NEXT_UNUSED;
			break;
		case 'n':
		case 'F':
			*val |= QFC_NEXT_UNUSED;
			break;
		case 'P':
			*val &= ~QFC_PREFFER_SERVER;
			break;
		case 'p':
			*val |= QFC_PREFFER_SERVER;
			break;
		case 'R':
			*val &= ~QFC_REQUIRE_SERVER;
			break;
		case 'r':
			*val |= QFC_REQUIRE_SERVER;
			break;
		default:
			return -1;
		}
		valm = 0;
	}
	if (!c)
		p--;
	*str = p;
	return valm;
}

static int
getmodflag(int *val, const char **str)
{
	const char *p = *str;
	int valm;
	int c;

	*val = 0;
	valm = 1;
	if (!*p)
		return 1;
	while ((c = *p++) != 0) {
		if (c == ',')
			break;
		switch (c) {
		case 'G':
			*val &= ~QFMU_GID;
			break;
		case 'g':
			*val |= QFMU_GID;
			break;
		case 'C':
			*val &= ~QFMU_GECOS;
			break;
		case 'c':
			*val |= QFMU_GECOS;
			break;
		case 'D':
			*val &= ~QFMU_DIR;
			break;
		case 'd':
			*val |= QFMU_DIR;
			break;
		case 'S':
			*val &= ~QFMU_SHELL;
			break;
		case 's':
			*val |= QFMU_SHELL;
			break;

		default:
			return -1;
		}
		valm = 0;
	}
	if (!c)
		p--;
	*str = p;
	return valm;
}

static int
parseuid(struct nw_user_info *ui, const char **str)
{
	int val;

	switch (getnumber(&val, str)) {
	case 0:
		ui->state.uid.min = val;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown min uid value %s\n", *str);
		return -1;
	}
	switch (getnumber(&val, str)) {
	case 0:
		ui->state.uid.max = val;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown max uid value %s\n", *str);
		return -1;
	}
	switch (getcflag(&val, str)) {
	case 0:
		ui->state.uid.flags = val;
		ui->qflag |= QF_AUTOCREATE;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown uid flags value %s\n", *str);
		return -1;
	}
	switch (getmodflag(&val, str)) {
	case 0:
		ui->state.uid.modflags = val;
		ui->qflag |= QF_AUTOMODIFY;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown uid modflags value %s\n", *str);
		return -1;
	}
	return 0;
}

static int
parsegid(struct nw_user_info *ui, const char **str)
{
	int val;

	switch (getnumber(&val, str)) {
	case 0:
		ui->state.gid.min = val;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown min gid value %s\n", *str);
		return -1;
	}
	switch (getnumber(&val, str)) {
	case 0:
		ui->state.gid.max = val;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown max gid value %s\n", *str);
		return -1;
	}
	switch (getcflag(&val, str)) {
	case 0:
		ui->state.gid.flags = val;
		break;
	case 1:
		break;
	default:
		syslog(LOG_ERR, "Unknown gid flags value %s\n", *str);
		return -1;
	}
	return 0;
}

// ajout PP 2000/12/19 session_on et off
// will mount Netware home directory in ~/nwhome

static int
parsemntpnt(struct nw_user_info *ui, const char **str)
{
// found another value that default nwhome in the command line (-mPATH (no space!)
// and cannot have space in it ;-)
// v 1.14: if the argument of -m is empty user's Netware home will be mounted as Unix home
	ui->nwhomeMntPnt = strdup(*str);
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "new default mounting point is \"%s\"", ui->nwhomeMntPnt);
	return 0;
}

// found a default zenFlag value on this machine
// apply it to user instead of the ZF_DEFAULTS
static int
parsezenflag_on(struct nw_user_info *ui, const char **str)
{
	ui->zenFlag = decodeZenFlag(str);
	return 0;
}

// found a zenFlags not allowed on this machine
// apply it to user
static int
parsezenflag_off(struct nw_user_info *ui, const char **str)
{
	if (**str)
		ui->zenFlagOFF = decodeZenFlag(str);
	else
		ui->zenFlagOFF = ~0UL;	// all are off

	return 0;
}

static void
mergeZenFlags(struct nw_user_info *ui)
{
	ui->zenFlag &= ~ui->zenFlagOFF;
	// make sure .nwinfos file is created if any zenscript is required
	if (ui->zenFlag & (ZF_OPENING_SCRIPTS | ZF_CLOSING_SCRIPTS))
		ui->zenFlag |= ZF_CREATE_NWINFOS;
}

// rev 1.19 we now  run the following command
// su -l USER -c "ncpmount ..... "
static int
mount_nwhome(const char *unixname, const char *uname,	// nw FQDN name
	     const char *pwd, const char *server, const char *volume, const char *path, const char *mntpoint, int uid, int zenFlag, int useIP, int verbose)
{

	const char *argv[30];
	int err;
	int i = 1, j = 0;
	char uidstr[30];

/* -B flag not yet supported by ncpmount */
#ifdef SET_BCAST
	int bMode = 0;
	char bModeStr[128];

	switch (zenFlag & (ZF_BROADCAST_ALL | ZF_BROADCAST_CONSOLE)) {
	case ZF_BROADCAST_ALL:
		bMode = NWCC_BCAST_PERMIT_ALL;
		break;
	case ZF_BROADCAST_CONSOLE:
		bMode = NWCC_BCAST_PERMIT_SYSTEM;
		break;
	default:
		bMode = NWCC_BCAST_PERMIT_NONE;
	}
	sprintf(bModeStr, "%u", bMode);
	argv[i++] = "-B";
	argv[i++] = bModeStr;
#endif

	if (uname) {
		argv[i++] = "-U";
		argv[i++] = uname;
	}
	if (pwd) {
		argv[i++] = "-P";
		argv[i++] = pwd;
	}
	if (server) {
		argv[i++] = "-S";
		argv[i++] = server;
		// added for JFB
		if (useIP) {
			argv[i++] = "-A";
			argv[i++] = server;
		}
	}
	if (volume) {
#ifdef NCP_IOC_GETROOT
		if (path) {	// already in Unix & uppercase
			char *fullPathToHome;

			fullPathToHome = concat3a(volume, ':', path);
			argv[i++] = "-V";
			argv[i++] = fullPathToHome;
		} else
#endif
		{
			argv[i++] = "-V";
			argv[i++] = volume;
		}
	}
	argv[i++] = "-o";
	argv[i++] = "symlinks,exec";

	sprintf(uidstr, "%u", uid);	// current user is owner

	argv[i++] = "-u";
	argv[i++] = uidstr;

	argv[i++] = "-c";
	argv[i++] = uidstr;

	argv[i++] = "-d";
	argv[i++] = "0700";	// current user is owner and only him

	if (mntpoint)		// better be ;-)
		argv[i++] = mntpoint;

	argv[i] = NULL;

	if (verbose) {
		char s[4096];	/* buf... buf... buffer overflow... */

		// do not log a clear password in /var/log/secure !
		sprintf(s, "running as %s \"%s", unixname, NCPMOUNT_PATH);
		for (j = 1; j < i; j++) {
			if (strlen(s) + strlen(argv[j]) >= sizeof (s) - 2)
				break;	/* fix the possible buffer overflow */
			if (strcmp(argv[j], "-P")) {
				strcat(s, " ");
				strcat(s, argv[j]);
			} else {
				j++;
			}
		}
		strcat(s, "\"");
		syslog(LOG_WARNING, "%s", s);
	}

	err = exechelper(NCPMOUNT_PATH, argv, unixname);
	if (verbose) {
		if (err)
			syslog(LOG_DEBUG, "user %s had trouble mounting %s/%s on %s", uname, server, volume, mntpoint);
		else
			syslog(LOG_NOTICE, "User %s has mounted %s/%s as %s\n", uname, server, volume, mntpoint);
	}

	return err;
}

// called by pam_end_session
static int
umount_nwhome(const char *unixname, const char *mntpoint, int verbose)
{
	const char *argv[5];
	int err;
	int i;

	i = 1;
	if (mntpoint) {		// better be ;-)
		argv[i++] = mntpoint;
	}
	argv[i] = NULL;
	err = exechelper(NCPUMOUNT_PATH, argv, unixname);	// umount as user
	if (verbose) {
		if (err)
			syslog(LOG_DEBUG, "user %s had trouble unmounting %s", unixname, mntpoint);
		else
			syslog(LOG_NOTICE, "User %s has unmounted  %s\n", unixname, mntpoint);
	}

	return err;
}

static int
nw_automount_home(const char *uname, struct nw_user_info *ui, const struct passwd *pwd, const char *pass)
{
	long err;
	struct stat stt;
	char *mountpnt;
	const char *fqdn;
	int perm_err;

	if (ui->qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "start of nw_auto_mount_home \n");

	// few sanity checks !!!
	if (!ui->nwhomeServer || !ui->nwhomeVolume)
		return -1;
	// no value red from the command line, use default ~/nwhome
	if (!ui->nwhomeMntPnt)
		ui->nwhomeMntPnt = strdup(DEF_MNT_PNT);

	if (pwd == NULL) {
		syslog(LOG_DEBUG, "/etc/passwd/%s not found !\n", uname);
		return PAM_USER_UNKNOWN;
	}

	if (ui->qflag & QF_MOUNTLOCALLY) {	// rev 1.24
#		define TOP_OF_ALL_MOUNTS "/mnt/ncp"
		if (stat(TOP_OF_ALL_MOUNTS, &stt))
			if (mkdir(TOP_OF_ALL_MOUNTS, 0711)) {
				syslog(LOG_DEBUG, "Unable to create common mounting point %s \n", TOP_OF_ALL_MOUNTS);
				return PAM_USER_UNKNOWN;
			}
		mountpnt = concat3a(TOP_OF_ALL_MOUNTS, '/', pwd->pw_name);
		if (stat(mountpnt, &stt)) {
			perm_err = mkdir(mountpnt, 0700) || chown(mountpnt, pwd->pw_uid, pwd->pw_gid);
			if (perm_err) {
				syslog(LOG_DEBUG, "Unable to create local mounting point %s \n", mountpnt);
				return PAM_USER_UNKNOWN;
			}
		}
		mountpnt = concat3a(mountpnt, '/', ui->nwhomeMntPnt);

	} else {
		if (stat(pwd->pw_dir, &stt) != 0) {
			syslog(LOG_DEBUG, "Unix home %s not found !\n", pwd->pw_dir);
			return PAM_USER_UNKNOWN;
		}
		mountpnt = concat3a(pwd->pw_dir, '/', ui->nwhomeMntPnt);
	}
	if (stat(mountpnt, &stt) != 0) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "Netware home mounting point %s not found. Will create it \n", mountpnt);
		perm_err = mkdir(mountpnt, 0700) || chown(mountpnt, pwd->pw_uid, pwd->pw_gid);
		if (perm_err) {
			syslog(LOG_DEBUG, "error creating Netware home mounting point %s :%d (%s)\n", mountpnt, perm_err, strerror(errno));
			return PAM_USER_UNKNOWN;
		}
	}
//rev 1.24 :remember full path to Netware home instead of simply 'nwhome'
// will be written in $HOME/.nwinfos file and red again at PAM end session
	free(ui->nwhomeMntPnt);
	ui->nwhomeMntPnt = strdup(mountpnt);

	/* v 1.13 if we allow contextless login, we must pass to ncpmount a FQDN and not a CN ! */

	if (ui->fqdn) {
		/* Add leading dot to denote absolute DN... */
		fqdn = concat2a(".", ui->fqdn);
	} else {
		/* Bindery... */
		fqdn = uname;
	}

	err = mount_nwhome(uname,	// unix name for su
			   fqdn,	// NW name for ncpmount
			   pass, ui->nwhomeServer, ui->nwhomeVolume, ui->nwhomePath, mountpnt, pwd->pw_uid, ui->zenFlag, ui->qflag & QF_USE_NETWARE_IP, ui->qflag & QF_DEBUG);
	if (!err)
		err = PAM_SUCCESS;

	return err;
}

/* create the 600 .nwclient file in user's home
   this file is used by others ncp utilities
   it contains the line SERVER/USER password or SERVER/USER
   depending of ZF_PASSWD_IN_NWCLIENT */
static int
nw_create_nwclient(const char *uname, const struct nw_user_info *ui, const struct passwd *pwd, const char *pass)
{
	char *nwclient;
	const char *server;
	FILE *f;
#ifdef IMPERSONNATE
	uid_t currentUID = getuid();
#endif
	int perm_err;

	if (pwd == NULL) {
		syslog(LOG_DEBUG, "/etc/passwd/%s not found !\n", uname);
		return PAM_USER_UNKNOWN;
	}
// I have a problem here. They may be different . If the Netware home is automounted
// the connection to the homeServer (not to the messageServer) will be marked as permanent.
	server = (ui->messageServer ? ui->messageServer : ui->nwhomeServer);
	if (!server)
		return PAM_SUCCESS;

	nwclient = concat3a(pwd->pw_dir, '/', NWCLIENT_FILE);

#ifdef IMPERSONNATE
	// v 1.24 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
	if (seteuid(pwd->pw_uid)) {
		syslog(LOG_DEBUG, "Cannot impersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
		return -1;
	}
#endif
	if (ui->zenFlag & ZF_OVERWRITE_NWCLIENT)
		f = fopen(nwclient, "w");	// erase it every time
	else
		f = fopen(nwclient, "a");	// I should append ?

	if (f) {
		// must have a CR/LF
		if (ui->zenFlag & ZF_PASSWD_IN_NWCLIENT)
			fprintf(f, "%s/%s %s\n", server, uname, pass);
		else
			fprintf(f, "%s/%s \n", server, uname);

		/* rev 1.15 */

		if (ui->defaultTree) {
			fprintf(f, "\n[Requester]\n");
			fprintf(f, "Default Tree Name=%s\n", ui->defaultTree);
			if (ui->defaultNameCtx)
				fprintf(f, "Default Name Context=%s\n", ui->defaultNameCtx);
		}

		fclose(f);
		// must be 600 and owner by current user, else ncpfs apps disregard it
		//perm_err=chmod(nwclient,0600) || chown(nwclient,pwd->pw_uid,pwd->pw_gid);
		perm_err = chmod(nwclient, 0600);
#ifdef IMPERSONNATE
		seteuid(currentUID);
#endif
		if (perm_err)
			syslog(LOG_DEBUG, "problem %d (%s)changing permissions of %s (%d %d)\n", perm_err, strerror(errno), nwclient, pwd->pw_uid, pwd->pw_gid);
		else {
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_DEBUG, "DONE writing to %s\n", nwclient);
			return PAM_SUCCESS;	// OK OK
		}
	} else {
#ifdef IMPERSONNATE
		seteuid(currentUID);
#endif
		syslog(LOG_DEBUG, "problem writing to %s\n", nwclient);
	}
	return -1;		// trouble somewhere (should not be lethal)
}

static int
nw_process_forward_file(const char *uname, const struct nw_user_info *ui, const struct passwd *pwd)
{
// what we do here it to setup a forward to any email address found in NDS
// we do it during the authentication part  so if the Netware home directory is mounted
// as /home/user ( -m with no argument) the .forward file is on the local machine if user is not currently logged in ...
//We do it again in the pam_session part ( if the argument of -m is empty) so if user IS logged in,
// the local /home/user is "masqued off"  and system will still find this file in the mounted Netware home ...
	FILE *f;
	char *p;
	char *forward;
#ifdef IMPERSONNATE
	uid_t currentUID = getuid();
#endif
	int perm_err;

	if (ui->emailLDAP) {
		p = ui->emailLDAP;	// priority to the "new format"
	} else if (ui->emailSMTP) {
		p = ui->emailSMTP;
	} else {
		return 0;	// too bad
	}

	forward = concat2a(pwd->pw_dir, "/.forward");

#ifdef IMPERSONNATE
	// v 1.25 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
	if (seteuid(pwd->pw_uid)) {
		syslog(LOG_DEBUG, "Cannot inpersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
		return -1;
	}
#endif
	f = fopen(forward, "w");	// erase it every time
	if (f) {
		// must have a CR/LF ??
		fprintf(f, "%s\n", p);
		fclose(f);
		// makes no harm to restrict reading to user ?
		//perm_err= chmod(forward,0600) || chown(forward,pwd->pw_uid,pwd->pw_gid);
		perm_err = chmod(forward, 0600);
#ifdef IMPERSONNATE
		seteuid(currentUID);
#endif
		if (perm_err)
			syslog(LOG_DEBUG, "problem %d (%s)changing permissions of %s\n", perm_err, strerror(errno), forward);
	} else {
#ifdef IMPERSONNATE
		seteuid(currentUID);
#endif
		syslog(LOG_DEBUG, "Cannot open %s: %s\n", forward, strerror(errno));
	}
	return 0;
}

/******************************* check for allowed remote access by zen ***/

#if 1
static void
report(int err, const char *what, const char *info)
{

	if (err != PAM_SUCCESS) {
		syslog(LOG_NOTICE, "error reading pam item %s \n", what);
	} else {
		if (!info)
			syslog(LOG_NOTICE, "pam item %s is missing \n", what);
		else if (*info == '\0')
			syslog(LOG_NOTICE, "pam item %s is empty \n", what);
		else
			syslog(LOG_NOTICE, "pam item %s value is %s\n", what, info);
	}
}

static void
test_pam_items(const char *me, pam_handle_t * pamh)
{
	const char *info;
	int err;
	syslog(LOG_NOTICE, "%s testing for PAM items \n", me);
	err = pam_get_item(pamh, PAM_RUSER, (const void **) &info);
	report(err, "PAM_RUSER", info);
	err = pam_get_item(pamh, PAM_SERVICE, (const void **) &info);
	report(err, "PAM_SERVICE", info);
	err = pam_get_item(pamh, PAM_RHOST, (const void **) &info);
	report(err, "PAM_RHOST", info);
	err = pam_get_item(pamh, PAM_TTY, (const void **) &info);
	report(err, "PAM_TTY", info);
}
#endif

struct pam_auth_element {
	const char *service;
	const char *rhost;
	const char *tty;
	const char *ruser;
	int zenFlag;
	int result;
	int cutZen;		// turn off incompatible flags
};

// this table was built on Caldera openLinux 2.4 and  Redhat 7.1
// using the call to test_pam_items()  to determine the correct values received by PAM
// for each service...
// you may have to adapt it to your distribution
// use the very same trick to add other remote access
// STRANGE: with samba pam_rhost is missing ( but pam_tty) also
// fixed with samba >=2.07 pam_rhost=hostname pam_tty=samba
// FIXME: with ftp : my ftpd does not call pam session, so mounted nwhome stays mounted
// I am sure I have the line in /etc/pam.d/ftp "session    required     pam_ncp_auth.so -d"
// SAME with samba
// so my current solution is to turn off automounting !!!
// ssh  RHOST=IP TTY=ssh SERVICE=sshd RUSER=missing
// X access  RHOST=missing TTY=hostname only SERVICE=kde  is tested, others pass through ?
// TESTME: rlogin, rsh  (I never used these utilities ;-))
// TODO LATER: Web authorization

static struct pam_auth_element pam_auth_table[] = {
	{ "login",  "*",  "*",     NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
	{ "ftp",    "*",  NULL,    NULL, ZF_ALLOW_FTP_ACCESS,	 PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME},
	{ "samba",  "*",  "samba", NULL, ZF_ALLOW_SAMBA_ACCESS,	 PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME},	//samba >2.07
	{ "samba",  NULL, NULL,    NULL, ZF_ALLOW_SAMBA_ACCESS,	 PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME},
	{ "kde",    NULL, "*",     NULL, ZF_ALLOW_X_ACCESS,	 PAM_SUCCESS, 0},	// RedHat 7.1 send NULL RHOST
	{ "kde",    "",   "*",     NULL, ZF_ALLOW_X_ACCESS,	 PAM_SUCCESS, 0},	// RedHat 7.2 send empty RHOST
	{ "rlogin", "*",  "*",     "*",  ZF_ALLOW_RLOGIN_ACCESS, PAM_SUCCESS, 0},
	{ "rsh",    "*",  "*",     "*",  ZF_ALLOW_RSH_ACCESS,	 PAM_SUCCESS, 0},
	{ "sshd",   "*",  "ssh",   NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
	{ "su",     NULL, NULL,    NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
	{ NULL,     NULL, NULL,    NULL, 0,			 PAM_AUTH_ERR, 0}
};

static int
process_zenflag_remote(pam_handle_t * pamh, const char *user, struct nw_user_info *ui)
{
	int err;
	char *service;
	char *rhost;
	char *ruser;
	char *tty;

// zenflags restricting remote access are LETHAL
//reactivate to test values of ruser, tty, rhost and service for a new service
// see output in /var/log/secure
#if 0
	test_pam_items(user, pamh);
#endif
	if (ui->qflag & QF_NO_PEER_CHECKS) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "Remote host and tty port are not checked\n");
		return PAM_SUCCESS;
	}
	err = pam_get_item(pamh, PAM_TTY, (const void **) &tty);
	if (err != PAM_SUCCESS)
		return PAM_SYSTEM_ERR;
// local access
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "testing remote acces PAM_TTY is %s", tty);

	// PP not really sure this is good enough
	// JFB added tty="vc/" for Mandrake 8.2
	if (tty) {
		if (!memcmp(tty, "/dev/", 5)) {
			tty += 5;
		}
		if (!memcmp(tty, "tty", 3) || !memcmp(tty, ":0", 2) || !memcmp(tty, "vc/", 2)) {
			if (ui->qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "local acces OK to %s", tty);
			return PAM_SUCCESS;
		}
	}

	err = pam_get_item(pamh, PAM_RHOST, (const void **) &rhost);
	if (err != PAM_SUCCESS) {
		syslog(LOG_WARNING, "error getting  PAM_RHOST");
		return PAM_SYSTEM_ERR;
	}

	err = pam_get_item(pamh, PAM_RUSER, (const void **) &ruser);
	if (err != PAM_SUCCESS) {
		syslog(LOG_WARNING, "error getting  PAM_RUSER");
		return PAM_SYSTEM_ERR;
	}

	err = pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
	if (err != PAM_SUCCESS) {
		syslog(LOG_WARNING, "error getting  PAM_SERVICE");
		return PAM_SYSTEM_ERR;
	}

	{
		struct pam_auth_element *z;

		err = PAM_AUTH_ERR;

		for (z = pam_auth_table; z->service; z++) {
			if (!strcmp(z->service, service))
				if ((rhost && z->rhost) || (!rhost && !z->rhost))
					if ((tty && z->tty) || (!tty && !z->tty))
						if ((ruser && z->ruser) || (!ruser && !z->ruser)) {
							if (!(z->zenFlag & ui->zenFlag)) {
								if (ui->qflag & QF_DEBUG)
									syslog(LOG_NOTICE, "remote acces for service %s rejected by Zen for %s", service, ui->name);
								return PAM_AUTH_ERR;

							} else {
								// turn OFF incompatible FLAGS ( automounting ?)
								ui->zenFlag &= ~z->cutZen;
								if (ui->qflag & QF_DEBUG)
									syslog(LOG_NOTICE, "remote acces for service %s granted by Zen for %s", service, ui->name);
								err = PAM_SUCCESS;
								break;
							}
						}
#if 0
						else
							syslog(LOG_NOTICE, "Mismatching ruser found: %s expecting:%s", ruser, z->ruser);
					else
						syslog(LOG_NOTICE, "Mismatching tty found: %s expecting:%s", tty, z->tty);
				else
					syslog(LOG_NOTICE, "Mismatching rhost found: %s expecting:%s", rhost, z->rhost);
#endif
		}

	}
#if 0
	syslog(LOG_NOTICE, "final return on remote check %d", err);
#endif
	return err;		//PAM_SUCCESS;
}

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

// must be done at this stage since they requires the user's current passwd
static int
process_zenflag_auth(pam_handle_t * pamh, const char *user, struct nw_user_info *ui, const struct passwd *pwd, const char *userpass)
{

	int err;		// ignore errors for now

	/* must be done BEFORE mounting Netware home since local /home/user
	   can become unavailable if the Netware home directory is mounted as /home/user
	   using the -m option with NO ARGUMENT */
	if (ui->zenFlag & ZF_FORWARD_MAIL)
		err = nw_process_forward_file(user, ui, pwd);
	if (ui->zenFlag & ZF_AUTOMOUNT_NWHOME) {
		err = nw_automount_home(user, ui, pwd, userpass);
		/* do it AGAIN after mounting Netware home, so the .forward file is still available
		   when user is logged in */
		if (!err && ui->zenFlag & ZF_FORWARD_MAIL)
			if (ui->nwhomeMntPnt && ui->nwhomeMntPnt[0] == 0)
				err = nw_process_forward_file(user, ui, pwd);
	}
	if (ui->zenFlag & (ZF_CREATE_NWCLIENT | ZF_OVERWRITE_NWCLIENT))
		nw_create_nwclient(user, ui, pwd, userpass);	// ignore errors for now
	return PAM_SUCCESS;
}

/* The code - what there is of it :) */

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
	int retval;
	const char *name;
	char *p;
	int c;
	struct nw_user_info inf;
	struct passwd *pwd_entry;

	openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);

	init_nw_user_info(&inf);
	inf.pamh = pamh;
	inf.qflag = QF_VERBOSE | QF_CREATEHOME;

	inf.state.uid.min = 1000;
	inf.state.uid.max = 60000;
	inf.state.uid.flags = 0;
	inf.state.uid.modflags = 0;
	inf.state.gid.min = 1000;
	inf.state.gid.max = 60000;
	inf.state.gid.flags = QFC_NEXT_UNUSED | QFC_PREFFER_SERVER;

	/* Get options */
	for (c = 0; c < argc; c++) {
		const char *chrp = argv[c];

		if (*chrp++ == '-') {
			int chr;

			while ((chr = *chrp++) != 0) {
				switch (chr) {
				case 'v':
					inf.qflag |= QF_VERBOSE;
					break;	/* verbose */
				case 'q':
					inf.qflag &= ~QF_VERBOSE;
					break;	/* quiet */
				case 'd':
					inf.qflag |= QF_DEBUG;
					break;	/* debug */
				case 's':
					inf.qflag |= QF_NOSU;
					break;	/* no supervisor */
				case 'S':
					inf.qflag |= QF_NOSUEQ;
					break;	/* no supervisor equivalent */
				case 'a':
					inf.qflag |= QF_AUTOCREATE;
					break;	/* create account automagically */
				case 'b':
					inf.qflag |= QF_BINDERY;
					break;	/* use bindery access */
				case 'u':
					parseuid(&inf, &chrp);
					break;
				case 'g':
					parsegid(&inf, &chrp);
					break;
				case 'm':
					parsemntpnt(&inf, &chrp);
					while ((chr = *chrp++) != 0) ;	// bug corrected v 1.14
					break;	//PP
				case 'z':
					parsezenflag_on(&inf, &chrp);
					break;	//PP
				case 'Z':
					parsezenflag_off(&inf, &chrp);
					break;	//PP
				case 'n':
					inf.qflag &= ~QF_CREATEHOME;
					break;	// PP v 1.24 (DO NOT create home for new users!)
				case 'l':
					inf.qflag |= QF_MOUNTLOCALLY;
					break;	// PP v 1.24 (homes are NFS mounted !)
				case 'A':
					inf.qflag |= QF_USE_NETWARE_IP;	// PP v 1.24
					break;
				case 'L':
					inf.qflag |= QF_NO_PEER_CHECKS;
					break;
				default:;	/* just silently ignore unknown option... */
				}
			}
		}
	}

	//if (inf.qflag &QF_DEBUG)
	//   test_pam_items ("authenticate",pamh);
	/* Get username */
	if ((retval = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS)
		goto quit;

	/* Get password */
	pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);

	if (!p) {
		retval = _set_auth_tok(pamh, flags);
		if (retval != PAM_SUCCESS)
			goto quit;
	}

	pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);

	/* v 1.22 check the service is not a screensaver */
	{
		const char *service;
		if ((retval = pam_get_item(pamh, PAM_SERVICE, (const void **) &service)) != PAM_SUCCESS)
			goto quit;
		inf.isScreenSaverRelogin = strstr(service, "saver") != NULL;
		// report (retval, "PAM_SERVICE",service);
		if (inf.isScreenSaverRelogin && inf.qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "relogin of %s from the screen saver %s ", name, service);

	}
	/* Find the server name in the configuration. */

	for (c = 0; c < argc; c++) {
		if (!strncmp("server=", argv[c], 7)) {
			const char *server;
			const char *group;
			char sbuf[256];

			server = argv[c] + 7;
			group = strchr(server, '/');
			if (group) {
				if ((size_t) (group - server) < sizeof (sbuf) - 1) {
					memcpy(sbuf, server, group - server);
					sbuf[group - server] = 0;
					server = sbuf;
					group = group + 1;
				} else {
					syslog(LOG_ALERT, "Error in configuration file: server name too long!\n");
					continue;
				}
			}
			retval = nw_attempt_auth_server(pamh, server, name, p, inf.qflag, group);
			if (retval == PAM_SUCCESS)
				goto success;

		}
		if (!strncmp("tree=", argv[c], 5)) {
			const char *tree;
			const char *contexts;
			const char *group;
			char tbuf[512];

			tree = argv[c] + 5;
			if (strlen(tree) >= sizeof (tbuf)) {
				syslog(LOG_ALERT, "Error in configuration file: tree argument too long!\n");
				continue;
			}
			strcpy(tbuf, tree);
			tree = tbuf;
			group = strchr(tree, '/');
			if (group) {
				tbuf[group - tree] = 0;
				group++;
			}
			contexts = strchr(tree, ':');
			if (contexts) {
				tbuf[contexts - tree] = 0;
				contexts++;
			}
			if (inf.qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "using tree %s ctxs %s group %s", tree, contexts, group);

			retval = nw_attempt_auth_tree(pamh, tree, name, contexts, p, inf.qflag, group, NWCC_NAME_FORMAT_NDS_TREE);
			if (retval == PAM_SUCCESS)
				goto success;

		}
		if (!strncmp("ndsserver=", argv[c], 10)) {
			const char *server;
			const char *contexts;
			const char *group;
			char tbuf[512];

			server = argv[c] + 10;
			if (strlen(server) >= sizeof (tbuf)) {
				syslog(LOG_ALERT, "Error in configuration file: ndsserver argument too long!\n");
				continue;
			}
			strcpy(tbuf, server);
			server = tbuf;
			group = strchr(server, '/');
			if (group) {
				tbuf[group - server] = 0;
				group++;
			}
			contexts = strchr(server, ':');
			if (contexts) {
				tbuf[contexts - server] = 0;
				contexts++;
			}
			if (inf.qflag & QF_DEBUG)
				syslog(LOG_NOTICE, "using server %s ctxs %s group %s", server, contexts, group);

			retval = nw_attempt_auth_tree(pamh, server, name, contexts, p, inf.qflag, group, NWCC_NAME_FORMAT_BIND);
			if (retval == PAM_SUCCESS)
				goto success;
		}

	}

	retval = PAM_AUTH_ERR;
	goto quit;
      success:;
	if (inf.qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "Auth OK\n");
	setpwent();
	pwd_entry = getpwnam(name);
	endpwent();
	retval = PAM_SUCCESS;	//moved here
	if (!inf.isScreenSaverRelogin && inf.qflag & (QF_AUTOCREATE | QF_AUTOMODIFY)) {	// v 1.22
		if ((!pwd_entry && (inf.qflag & QF_AUTOCREATE)) || (pwd_entry && (inf.qflag & QF_AUTOMODIFY))) {
			int err;

			inf.name = strdup(name);
			if (!inf.name) {
				syslog(LOG_WARNING, "Not enough memory for strdup()\n");
				err = -10;	// added PP
			} else {
				// NDS error means NO LOGIN
				err = nw_retrieve_user_info(&inf);
				// remote access denied = NO LOGIN (and no update/creation!)
				if (inf.qflag & QF_DEBUG)
					syslog(LOG_DEBUG, "FOUND ZF %lx in NDS \n", inf.zenFlag);
				if (inf.qflag & QF_DEBUG)
					syslog(LOG_DEBUG, "APPLYING ZF %lx OFF \n", inf.zenFlagOFF);
				mergeZenFlags(&inf);	// rev 1.26  turn off prohibited flags
				if (inf.qflag & QF_DEBUG)
					syslog(LOG_DEBUG, "USING ZF %lx \n", inf.zenFlag);
				if (!err)
					err = process_zenflag_remote(pamh, name, &inf);
				// in this version creation error is lethal, not update
				if (!err)
					err = nw_update_user_info(&inf);
#if 0
				err = 0;	// testing
#endif
			}
			retval = err ? PAM_AUTH_ERR : PAM_SUCCESS;	// added here PP
		}
// feature: if you do not allow autocreation, automodification, NDS is not RED
// so no Zen, no remote access restrictions...
	}

      quit:;
	//PP keep the nw_user_info into PAM for a later use in sm_open_session
	// be Zen, no password saved yet ;-)

	if (retval == PAM_SUCCESS && !inf.isScreenSaverRelogin) {
		void *sav;

		//read again pwd_entry ( was NULL if it is a new account !)
		setpwent();
		pwd_entry = getpwnam(name);
		endpwent();
		mergeZenFlags(&inf);	// rev 1.26  turn off prohibited flags
		retval = process_zenflag_auth(pamh, name, &inf, pwd_entry, p);
		if (retval == PAM_SUCCESS) {	// some ZeN_FLAGS may stop the authentification process !
			sav = malloc(sizeof (inf));
			if (sav) {
				if (inf.qflag & QF_DEBUG)
					syslog(LOG_NOTICE, "saving user_info\n");
				memcpy(sav, &inf, sizeof (inf));
				pam_set_data(pamh, "pam.ncpfs.user_info", sav, cleanup_user_info);
			} else {
				if (inf.qflag & QF_DEBUG)
					syslog(LOG_NOTICE, "Out of memory. NOT saving user_info\n");
				free_nw_user_info(&inf);	// forget about it
				// I feel bad about the pam-session parts, but they should just quit gracefully
			}
		}
	}
	if (inf.qflag & QF_DEBUG)
		syslog(LOG_NOTICE, "final PAM retval %u\n", retval);

	//PP: may be a good spot to close and get rid on the still opened connection ???
	closelog();
	return retval;
}

/*
 * Does nothing. for now
 */

PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
	//return PAM_IGNORE;
	return PAM_SUCCESS;
}

/*
 * Does nothing. for now
 */

PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
	//return PAM_IGNORE;
	return PAM_SUCCESS;
}

/* old code used with server=XXX cmd line argument */
static int
nw_attempt_passwd_prelim_server(pam_handle_t * pamh, 
				const char *server, const char *user, const char *oldpwd,
				int qflag, const char *group, int flags)
{
	struct ncp_conn *conn;
	int err;

	err = nw_create_verify_conn_to_server(&conn, NULL, server, user, oldpwd, qflag, group);
	if (err)
		return err;
	pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
	return 0;
}

/*new code for tree=argument */
static int
nw_attempt_passwd_prelim_tree(pam_handle_t * pamh,
			      const char *tree, const char *user, const char *contexts, const char *oldpwd,
			      int qflag, const char *group, int flags, nuint nameFormat)
{
	struct ncp_conn *conn;
	int err;

	err = nw_create_verify_conn_to_tree(&conn, NULL, tree, user, contexts, oldpwd, qflag, group, nameFormat);
	if (err)
		return err;
	pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
	return 0;
}

static int
nw_attempt_passwd_post(pam_handle_t * pamh, const char *pwd, const char *oldpwd, int qflag, int flags)
{
	struct ncp_conn *conn;
	long err;
	NWObjectID oid;
	char *oldpwdup;
	char *pwdup;

	err = PAM_AUTHTOK_ERR;
	if (!(flags & PAM_UPDATE_AUTHTOK))
		goto bailout_nf;
	err = pam_get_data(pamh, "pam.ncpfs.passwd.conn", (const void **) &conn);
	if (err)
		goto bailout_nf;
	err = nw_get_nwid(conn, &oid, qflag);
	if (err)
		goto bailout_nf;

	oldpwdup = strdup(oldpwd);
	if (!oldpwd) {
		err = ENOMEM;
		goto bailout_nf;
	}
	str_upper(oldpwdup);

	pwdup = strdup(pwd);
	if (!pwd) {
		err = ENOMEM;
		goto bailout_nf2;
	}
	str_upper(pwdup);

	if (qflag & QF_BINDERY) {
		struct ncp_bindery_object uinfo;
		struct ncp_bindery_object u0;
		unsigned char ncp_key[8];

		err = ncp_get_bindery_object_name(conn, oid, &u0);
		if (err) {
			syslog(LOG_WARNING, "%s when trying to get object name\n", strnwerror(err));
			err = PAM_USER_UNKNOWN;
			goto bailout;
		}
		err = ncp_get_encryption_key(conn, ncp_key);
		if (err) {
			syslog(LOG_WARNING, "%s when trying to get encryption key\n", strnwerror(err));
			err = PAM_AUTHTOK_ERR;
			goto bailout;
		} else {
			err = ncp_get_bindery_object_id(conn, u0.object_type, u0.object_name, &uinfo);
			if (err) {
				syslog(LOG_WARNING, "%s when trying to get object ID\n", strnwerror(err));
				err = PAM_USER_UNKNOWN;
				goto bailout;
			}
			err = ncp_change_login_passwd(conn, &uinfo, ncp_key, oldpwdup, pwdup);
			if (err) {
				syslog(LOG_WARNING, "%s when trying to change password\n", strnwerror(err));
				err = PAM_AUTHTOK_ERR;
				goto bailout;
			}
		}
	} else {
		NWDSContextHandle ctx;
		nuint32 c;
		char username[MAX_DN_BYTES];

		err = NWDSCreateContextHandle(&ctx);
		if (err) {
			syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
			err = PAM_SYSTEM_ERR;
			goto bailout;
		}
		c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
		err = NWDSSetContext(ctx, DCK_FLAGS, &c);
		if (err) {
			syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
			err = PAM_SYSTEM_ERR;
			goto bailoutctx;
		}
		NWDSAddConnection(ctx, conn);
		err = NWDSMapIDToName(ctx, conn, oid, username);
		if (err) {
			syslog(LOG_WARNING, "NWDSMapIDToName() failed with %s\n", strnwerror(err));
			err = PAM_USER_UNKNOWN;
			goto bailoutctx;
		}
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "User has DN %s\n", username);
		err = NWDSChangeObjectPassword(ctx, NDS_PASSWORD, username, oldpwdup, pwdup);
		if (err) {
			syslog(LOG_NOTICE, "NWDSChangeObjectPassword() failed with %s\n", strnwerror(err));
			err = PAM_AUTHTOK_ERR;
			goto bailoutctx;
		}
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "User %s has succesfully changed its NW pasword\n", username);
	      bailoutctx:;
		NWDSFreeContext(ctx);
	}
bailout:;
	free(pwdup);
bailout_nf2:;
	free(oldpwdup);
bailout_nf:;
	pam_set_data(pamh, "ncpfs.pam.passwd.conn", NULL, NULL);
	return err;
}

//PP: untested code ..
// one question.
//
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
	int retval;
	const char *name;
	char *p;
	char *oldpasswd;
	int c;

	int qflag = QF_VERBOSE;

	openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
	/* Get options */
	for (c = 0; c < argc; c++) {
		if (argv[c][0] == '-') {
			int i;

			for (i = 1; argv[c][i]; i++) {
				switch (argv[c][i]) {
				case 'v':
					qflag |= QF_VERBOSE;
					break;	/* verbose */
				case 'q':
					qflag &= ~QF_VERBOSE;
					break;	/* quiet */
				case 'd':
					qflag |= QF_DEBUG;
					break;	/* debug */
				case 's':
					qflag |= QF_NOSU;
					break;	/* no supervisor */
				case 'S':
					qflag |= QF_NOSUEQ;
					break;	/* no supervisor equivalent */
				case 'b':
					qflag |= QF_BINDERY;
					break;	/* use bindery access */
				default:;	/* just silently ignore unknown option... */
				}
			}
		}
	}
	//if (qflag &QF_DEBUG)
	//   test_pam_items ("chauthtok",pamh);

	/* Get username */
	if ((retval = pam_get_user(pamh, &name, "passwd: ")) != PAM_SUCCESS)
		goto quit;

	pam_get_item(pamh, PAM_OLDAUTHTOK, (void *) &oldpasswd);
	if (!oldpasswd) {
		retval = _set_oldauth_tok(pamh, flags);
		if (retval != PAM_SUCCESS)
			goto quit;
		pam_get_item(pamh, PAM_OLDAUTHTOK, (void *) &oldpasswd);
	}

	if (flags & PAM_PRELIM_CHECK) {
		/* Find the server name in the configuration. */

		for (c = 0; c < argc; c++) {
			if (!strncmp("server=", argv[c], 7)) {
				const char *server;
				const char *group;
				char sbuf[256];

				server = argv[c] + 7;
				group = strchr(server, '/');
				if (group) {
					if ((size_t) (group - server) < sizeof (sbuf) - 1) {
						memcpy(sbuf, server, group - server);
						sbuf[group - server] = 0;
						server = sbuf;
						group = group + 1;
					} else {
						syslog(LOG_ALERT, "Error in configuration file: server name too long!\n");
						continue;
					}
				}
				retval = nw_attempt_passwd_prelim_server(pamh, server, name, oldpasswd, qflag, group, flags);
				if (retval == PAM_SUCCESS)
					goto quit;
			}
			if (!strncmp("tree=", argv[c], 5)) {
				const char *tree;
				const char *contexts;
				const char *group;
				char tbuf[512];

				tree = argv[c] + 5;
				if (strlen(tree) >= sizeof (tbuf)) {
					syslog(LOG_ALERT, "Error in configuration file: tree argument too long!\n");
					continue;
				}
				strcpy(tbuf, tree);
				tree = tbuf;
				group = strchr(tree, '/');
				if (group) {
					tbuf[group - tree] = 0;
					group++;
				}
				contexts = strchr(tree, ':');
				if (contexts) {
					tbuf[contexts - tree] = 0;
					contexts++;
				}
				if (qflag & QF_DEBUG)
					syslog(LOG_NOTICE, "using tree %s ctxs %s group %s", tree, contexts, group);

				retval = nw_attempt_passwd_prelim_tree(pamh, tree, name, contexts, oldpasswd, qflag, group, flags, NWCC_NAME_FORMAT_NDS_TREE);
				if (retval == PAM_SUCCESS)
					goto quit;

			}
			if (!strncmp("ndsserver=", argv[c], 10)) {
				const char *tree;
				const char *contexts;
				const char *group;
				char tbuf[512];

				tree = argv[c] + 10;
				if (strlen(tree) >= sizeof (tbuf)) {
					syslog(LOG_ALERT, "Error in configuration file: NDS server argument too long!\n");
					continue;
				}
				strcpy(tbuf, tree);
				tree = tbuf;
				group = strchr(tree, '/');
				if (group) {
					tbuf[group - tree] = 0;
					group++;
				}
				contexts = strchr(tree, ':');
				if (contexts) {
					tbuf[contexts - tree] = 0;
					contexts++;
				}
				if (qflag & QF_DEBUG)
					syslog(LOG_NOTICE, "using NDS server %s ctxs %s group %s", tree, contexts, group);

				retval = nw_attempt_passwd_prelim_tree(pamh, tree, name, contexts, oldpasswd, qflag, group, flags, NWCC_NAME_FORMAT_BIND);
				if (retval == PAM_SUCCESS)
					goto quit;

			}
		}
		retval = PAM_AUTHTOK_ERR;
		goto quit;
	}

	if (flags & PAM_UPDATE_AUTHTOK) {
		/* Get password */
		pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);

		if (!p) {
			retval = _read_new_pwd(pamh, flags);
			if (retval != PAM_SUCCESS)
				return retval;
			pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);
		}

		retval = nw_attempt_passwd_post(pamh, p, oldpasswd, qflag, flags);
	} else {
		retval = PAM_SYSTEM_ERR;
	}
quit:;
	closelog();
	return retval;
}

// ajout PP 2000/12/19
// will process session on and session end zenFlag

static int
nw_process_nwinfos_file(const char *uname, const struct nw_user_info *ui, const struct passwd *pwd)
{
// create a ~/.nwinfos file that can be "sourced" in future scripts
	FILE *f;
	char *nwinfos;
#ifdef IMPERSONNATE
	uid_t currentUID = getuid();
#endif
	int perm_err;

	nwinfos = concat3a(pwd->pw_dir, '/', NWINFOS_FILE);
#ifdef IMPERSONNATE
	// v 1.24 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
	if (seteuid(pwd->pw_uid)) {
		syslog(LOG_DEBUG, "Cannot inpersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
		return 1;
	}
#endif

	f = fopen(nwinfos, "w");
	if (f) {
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "writing a new %s file\n", nwinfos);

		// either NDS or local values
		fprintf(f, "NDS_USER=%s\n", ui->name ? ui->name : pwd->pw_name);
		fprintf(f, "NDS_GECOS=\"%s\"\n", ui->gecos ? ui->gecos : pwd->pw_gecos);	//may have space in it
		fprintf(f, "NDS_SHELL=%s\n", ui->shell ? ui->shell : pwd->pw_shell);
		fprintf(f, "NDS_HOME=%s\n", ui->dir ? ui->dir : pwd->pw_dir);
		fprintf(f, "NDS_UID=%u\n", (ui->uid != (uid_t) -1) ? ui->uid : pwd->pw_uid);
		fprintf(f, "NDS_GID=%u\n", (ui->gid != (gid_t) -1) ? ui->gid : pwd->pw_gid);
		// no local alernatives
		fprintf(f, "NDS_QFLAG=%x\n", ui->qflag);
		if (ui->nwhomeServer)
			fprintf(f, "NDS_HOME_SERVER=%s\n", ui->nwhomeServer);
		if (ui->nwhomeVolume)
			fprintf(f, "NDS_HOME_VOLUME=%s\n", ui->nwhomeVolume);
		if (ui->nwhomePath)
			fprintf(f, "NDS_HOME_PATH=%s\n", ui->nwhomePath);
		if (ui->nwhomeMntPnt)
			fprintf(f, "NDS_HOME_MNT_PNT=%s\n", ui->nwhomeMntPnt);
		if (ui->emailSMTP)
			fprintf(f, "NDS_EMAIL=%s\n", ui->emailSMTP);
		if (ui->emailLDAP)
			fprintf(f, "NDS_EMAIL=%s\n", ui->emailLDAP);	// overwrite it
		if (ui->messageServer)
			fprintf(f, "NDS_PREFERRED_SERVER=%s\n", ui->messageServer);
		if (ui->defaultTree)
			fprintf(f, "NDS_PREFERRED_TREE=%s\n", ui->defaultTree);
		if (ui->defaultNameCtx)
			fprintf(f, "NDS_PREFERRED_NAME_CTX=%s\n", ui->defaultNameCtx);

		// v 1.19 added a new account flag to ease zenscript processing
		// is account is new, we can further personnalize config files such as kmailrc, liprefs.js ...
		fprintf(f, "NDS_IS_NEW_USER=%s\n", (ui->isNewUser) ? "1" : "0");

		fprintf(f, "NDS_ZEN_FLAG=0x%lx\n", ui->zenFlag);
		switch (ui->zenFlag && (ZF_BROADCAST_ALL + ZF_BROADCAST_CONSOLE)) {
		case ZF_BROADCAST_ALL:
			fprintf(f, "NDS_BCAST=2\n");
			break;
		case ZF_BROADCAST_CONSOLE:
			fprintf(f, "NDS_BCAST=1\n");
			break;
		default:
			fprintf(f, "NDS_BCAST=0\n");
			break;
		}
		fclose(f);
		//perm_err=chmod(nwinfos,0600) || chown(nwinfos,pwd->pw_uid,pwd->pw_gid);
		perm_err = chmod(nwinfos, 0600);
		seteuid(currentUID);
		if (perm_err)
			syslog(LOG_DEBUG, "problem %d (%s)changing permissions to %s\n", perm_err, strerror(errno), nwinfos);
		if (ui->qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "done writing %s \n", nwinfos);

	} else {
		seteuid(currentUID);
		syslog(LOG_DEBUG, "Cannot open %s: %s\n", nwinfos, strerror(errno));
	}

	return 0;
}

// starting at rev. 1.19 zencripts are launched via su -l user for security reasons !

static int
exechelper2(const char *program,	// script name just to test if the command exist
	    const char *argv[],	// extras arguments to -c program
	    const char *unixname,	// user to su to
	    int verbose)
{
	struct stat stt;

	if (verbose) {
		if (unixname) {
			syslog(LOG_NOTICE, "launching %s %s %s as %s\n", program, argv[1], argv[2], unixname);
		} else {
			syslog(LOG_NOTICE, "launching %s %s %s as root\n", program, argv[1], argv[2]);
		}
	}
	if (stat(program, &stt) != 0) {
		if (verbose)
			syslog(LOG_NOTICE, "%s not found\n", program);
		return -1;
	}
	return exechelper(program, argv, unixname);
}

static int
process_zenflag_session_opening(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{

	int err;
	if (ui->qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "APPLYING ZF %lx\n", ui->zenFlag);

	if (ui->zenFlag & ZF_CREATE_NWINFOS)
		err = nw_process_nwinfos_file(user, ui, pwd);
	if (ui->zenFlag & ZF_OPENING_SCRIPTS) {
		const char *argv[5];

		argv[1] = pwd->pw_dir;	// argument 1 = location of nwinfos file to source in
		argv[2] = NWINFOS_FILE;	// actual file name
		argv[3] = NULL;
		// argument 1 = location of nwinfos file to source in (=user's home dir)
		//argument 2 = actual file name (default=.nwinfos)

		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "running opening scripts.\n");
		if (ui->zenFlag & ZF_0)
			err = exechelper2(ZEN_SCRIPT_0, argv, user, ui->qflag & QF_DEBUG);
		if (ui->zenFlag & ZF_1)
			err = exechelper2(ZEN_SCRIPT_1, argv, user, ui->qflag & QF_DEBUG);
		if (ui->zenFlag & ZF_2)
			err = exechelper2(ZEN_SCRIPT_2, argv, user, ui->qflag & QF_DEBUG);
	}
#if 1
	err = 0;		// ignore errors for now
#endif
	return err;
}

PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
	/* NO WAY to mount netware home here
	   passord is lost, we are in the pam_session now.
	   I could have stored it in nw-user_info struct saved in PAM data
	   but I am not sure this is really safe !!!
	   we only process here some ZENFLAGS red from NDS
	   and stored in the PAM item "pam.ncpfs.user_info"

	 */
	struct nw_user_info *ui;
	const struct passwd *pwd;
	struct stat stt;
	const char *user;
	int err, c;

	int qflag = QF_VERBOSE;

	openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
	/* Get options */
	for (c = 0; c < argc; c++) {
		if (argv[c][0] == '-') {
			int i;

			for (i = 1; argv[c][i]; i++) {
				switch (argv[c][i]) {
				case 'v':
					qflag |= QF_VERBOSE;
					break;	/* verbose */
				case 'q':
					qflag &= ~QF_VERBOSE;
					break;	/* quiet */
				case 'd':
					qflag |= QF_DEBUG;
					break;	/* debug */
				default:;	/* just silently ignore unknown option... */
				}
			}
		}
	}

	/*****************************************************
        if (qflag & QF_DEBUG) {
           test_pam_items ("session closing",pamh);
        }
        *****************************************************/
	if (qflag & QF_DEBUG) {
		syslog(LOG_NOTICE, "start of session \n");
	}
// just in case user has been removed ???
	err = pam_get_item(pamh, PAM_USER, (const void **) &user);
	if (err != PAM_SUCCESS || user == NULL || *user == '\0')
		goto doneok;

	setpwent();
	pwd = getpwnam(user);
	endpwent();
	if (pwd == NULL) {
		syslog(LOG_DEBUG, "%s not found\n", user);
		goto doneok;
	}
	if (stat(pwd->pw_dir, &stt) != 0) {
		syslog(LOG_DEBUG, "Unix home of %s not found !\n", user);
		goto doneok;
	}
	// get back user's info stored in PAM by the authentification section
	// no password there !
	err = pam_get_data(pamh, "pam.ncpfs.user_info", (const void **) &ui);
	if (!err) {
		if (qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got user info back %u", ui->uid);
		process_zenflag_session_opening(user, ui, pwd);
	} else if (qflag & QF_DEBUG)
		syslog(LOG_DEBUG, "failure reading back pam.ncpfs.user_info %u\n", err);
	// no problem, he is authenticated but "zen failed"
doneok:
	pam_set_data(pamh, "pam.ncpfs.passwd.conn", NULL, NULL);
	return PAM_SUCCESS;
}

// starting at rev. 1.19 zencripts are lauched via su -l user for security reasons !
static int
process_zenflag_session_closing(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{
	int err = 0;

// first zenScripts in case they opeate on the mounted nwhome !
	if (ui->zenFlag & ZF_CLOSING_SCRIPTS) {
		const char *argv[5];

		argv[1] = pwd->pw_dir;	// argument 1 = location of nwinfos file to source in
		argv[2] = NWINFOS_FILE;	// actual file name
		argv[3] = NULL;
		// argument 1 = location of nwinfos file to source in (=user's home dir)
		//argument 2 = actual file name (default=.nwinfos)

		if (ui->qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "running closing scripts.\n");
		if (ui->zenFlag & ZF_3)
			err = exechelper2(ZEN_SCRIPT_3, argv, user, ui->qflag & QF_DEBUG);
		if (ui->zenFlag & ZF_4)
			err = exechelper2(ZEN_SCRIPT_4, argv, user, ui->qflag & QF_DEBUG);
		if (ui->zenFlag & ZF_5)
			err = exechelper2(ZEN_SCRIPT_5, argv, user, ui->qflag & QF_DEBUG);
	}

	if (ui->zenFlag & ZF_AUTOMOUNT_NWHOME) {
		err = umount_nwhome(user, ui->nwhomeMntPnt, ui->qflag & QF_DEBUG);
	}
#if 1
	err = 0;		// ignore error
#endif
	return err;
}

static int
pam_do_close_session(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{

	return process_zenflag_session_closing(user, ui, pwd);
}

PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh, int flags, int argc, const char **argv)
// logout fom Netware at session end
// no complains
{
	struct nw_user_info *ui;
	long err;
	const char *user;
	const struct passwd *pwd;
	struct stat stt;

	int qflag = QF_VERBOSE;
	int c;
	openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
	/* Get options */
	for (c = 0; c < argc; c++) {
		if (argv[c][0] == '-') {
			int i;

			for (i = 1; argv[c][i]; i++) {
				switch (argv[c][i]) {
				case 'v':
					qflag |= QF_VERBOSE;
					break;	/* verbose */
				case 'q':
					qflag &= ~QF_VERBOSE;
					break;	/* quiet */
				case 'd':
					qflag |= QF_DEBUG;
					break;	/* debug */
				default:;	/* just silently ignore unknown option... */
				}
			}
		}
	}

	/*****************************************************
        if (qflag & QF_DEBUG) {
           test_pam_items ("session closing",pamh);
        }
        *****************************************************/
	if (qflag & QF_DEBUG) {
		syslog(LOG_NOTICE, "end of session\n");
	}
// just in case user has been removed ???
	err = pam_get_item(pamh, PAM_USER, (const void **) &user);
	if (err != PAM_SUCCESS || user == NULL || *user == '\0')
		goto doneok;

	setpwent();
	pwd = getpwnam(user);
	endpwent();

	if (pwd == NULL) {
		syslog(LOG_NOTICE, "%s not found\n", user);
		goto doneok;
	}

	if (stat(pwd->pw_dir, &stt) != 0) {
		syslog(LOG_NOTICE, "Unix home of %s not found !\n", user);
		goto doneok;
	}
	// read back all user data
	err = pam_get_data(pamh, "pam.ncpfs.user_info", (const void **) &ui);
	if (!err) {
		if (qflag & QF_DEBUG)
			syslog(LOG_NOTICE, "got it back %u", ui->uid);
	} else {
		if (qflag & QF_DEBUG)
			syslog(LOG_DEBUG, "failed reading pam.ncpfs.user_info %lu\n", err);
		goto doneok;
	}
	pam_do_close_session(user, ui, pwd);
	free_nw_user_info(ui);	// forget about it
doneok:
	closelog();

	pam_set_data(pamh, "pam.ncpfs.passwd.conn", NULL, NULL);
	return PAM_SUCCESS;
}

/* static module data */
#ifdef PAM_STATIC
struct pam_module _pam_ncp_auth_modstruct = {
	"pam_ncp_auth",
	pam_sm_authenticate,
	pam_sm_setcred,
	pam_sm_acct_mgmt,
	pam_sm_open_session,	// session IN  was NULL PP
	pam_sm_close_session,	// session OUT was NULL PP
	pam_sm_chauthtok,
};
#endif
