/*
** chuser v1.8
**
** Add or delete user(s) or change attributes
**
** Copyright (C) 3.5.96 by Andreas Ley <Andreas.Ley@rz.uni-karlsruhe.de>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** This program has been tested on a HP9000/715 with HP-UX A.09.07
** In this environment, neither lint -u nor gcc -Wall -fsyntax-only produce
** any messages. If you encounter any errors or need to make any changes to
** port it to another platform, please contact me.
**
** Version history
**
** Version 1.8 - 24.11.2004
**	Added -N option
**
** Version 1.7.3 - 28.1.2002
**	Fixed passwd and shadow temporary names when alternate files specified
**
** Version 1.7.2 - 12.10.2001
**	Added shadow file option
**
** Version 1.7.1 - 12.6.2001
**	Added DCE handling
**
** Version 1.7 - 9.11.1999
**	Added -S option
**
** Version 1.6.11 - 31.3.1999
**	Added linux signals
**	Added fatal_handler for multiple signal raises
**	Fixed reference after free in del()
**	Fixed removal of required fields in shadow passwd
**
** Version 1.6.10 - 24.11.1998
**	Added setauditid in non-PASSWD-mode
**
** Version 1.6.9 - 19.11.1998
**	Temporarily fixed inplace pw_succhg bug for SHADOW systemtype.
**
** Version 1.6.8 - 2.11.1998
**	Fixed error return codes.
**
** Version 1.6.7 - 11.9.1998
**	Added REMOVEBLIND option.
**
** Version 1.6.6 - 25.8.1998
**	Added attribute delete method.
**	Fixed pw_succhg bug if only ',' supplied but no aging information.
**
** Version 1.6.5 - 24.11.97
**	Fixed checkout_tcb pw_name check for tcb->changed==FALSE.
**
** Version 1.6.4 - 21.10.97
**	Fixed template pw_exp/pw_minchg bug.
**
** Version 1.6.3 - 19.8.97
**	Changed order for checking systemtype since HP-UX 10.20 uses TCB but
**	has a /.secure directory, too.
**
** Version 1.6.2 - 11.7.97
**	Added more verbose error message for invalid or insufficient
**	information.
**
** Version 1.6.1 - 18.11.96
**	Fixed required pw_succhg bug for SHADOW systemtype.
**
** Version 1.6 - 5.11.96
**	Added -U option.
**
** Version 1.5.1 - 4.11.96
**	Fixed template pw_succhg bug.
**
** Version 1.5 - 22.7.96
**	Different exit codes for temporary, permanent and fatal errors.
**
** Version 1.4 - 17.7.96
**	Added ADDBLIND operation.
**
** Version 1.3 - 21.6.96
**	Added YP systemtype.
**
** Version 1.2 - 13.6.96
**	Added AIX systemtype SECURITY.
**	Added CMDOPTS and ATTRIBUTES mode.
**
** Version 1.1 - 12.6.96
**	Added handling of password aging information.
**
** Version 1.0 - 3.5.96
**	Initial version.
*/

static char copyright[] = "@(#)Copyright (C) 1996-2004 by Andreas Ley (Andreas.Ley@rz.uni-karlsruhe.de)";
static char sccsid[] = "@(#)chuser v1.8 - Add or delete user(s) or change attributes";


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>

#ifndef FALSE
#define	FALSE	(0)		/* This is the naked Truth */
#define	TRUE	(1)		/* and this is the Light */
#endif

/* For debugging purposes, an alternate root can be used */
#ifndef PREFIX
#define PREFIX
#endif

/* ASCII Null */
char	zero[]="0";

/* Output null pointers */
char	_null_[]="(null)";
#define	null(x)	((x)?(x):_null_)

/* Output truth value */
char	true[]="true",false[]="false";
#define	bool(x)	((x)?true:false)

/* Return on error */
#define	check(x)			if ((retval=(x))) return(retval);else (void)TRUE

/* read_passwd macro */
#define	pwread(name,type)	if (nptr) {*nptr++='\0'; check(add(list,name,type,ptr,pos==-1?-1:pos+(ptr-buffer),FALSE)); ptr=nptr; nptr=strchr(ptr,':');} else (void)TRUE
#define	pwreaddays(name,type)	if (nptr) {*nptr++='\0'; check(add(list,name,type,mult(ptr,24*60*60),pos==-1?-1:pos+(ptr-buffer),FALSE)); ptr=nptr; nptr=strchr(ptr,':');} else (void)TRUE
#define	pwwrite(name)	(void)strcat(buffer,":"); if (!(value=getval(list,name))) return(name); (void)strcat(buffer,value)
#define	pwwritedays(name)	(void)strcat(buffer,":"); if (!(value=getval(list,name))) return(name); value=divide(value,24*60*60); if (strcmp(value,zero)) (void)strcat(buffer,value); else (void)TRUE

/*
**  Week encoding according to passwd(4)
*/
char	week_chars[]="./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

/*
**  The maximum length a line within the passwd database personal files should
**  have. If a field would override this, a line break will occur. This is
**  mainly for human readability and has no effect on functionality.
*/
#define	MAX_TCB_LEN	80

/*
**  Should auditing be enabled when creating new users?
*/
#define	TEMPLATE_AUDITFLAG	zero

/*
**  In order to create an existing user, a successful login must be faked.
**  Supply an unusual login tty to mark such faked fields.
*/
#define	TEMPLATE_SUCTTY	"(new)"

/*
**  Standard field names used for standard fields. Most of them are the
**  HP-UX 10.x passwd database field names.
*/
char	*pw_name="u_name",*pw_passwd="u_pwd",*pw_uid="u_id",*pw_gid="g_id",
	*pw_gecos="u_gecos",*pw_dir="u_dir",*pw_shell="u_shell",
	*pw_exp="u_exp",*pw_minchg="u_minchg",*pw_succhg="u_succhg",
	*pw_auditid="u_auditid",*pw_auditflag="u_auditflag",*pw_lock="u_lock",
	*pw_suclog="u_suclog",*pw_suctty="u_suctty",*pw_llogin="u_llogin",
	*pw_expire_warning="u_expire_warning",*pw_acct_expire="u_acct_expire",
	*pw_numunsuclog="u_numunsuclog",*pw_flags="flags",*chkent="chkent";
struct sypw_t {
	char	*sy;
	char	*pw;
	} sypw[] = {
		{ "gecos", "u_gecos" },
		{ "home", "u_dir" },
		{ "lastupdate", "u_succhg" },
		{ "password", "u_pwd" },
		{ "shell", "u_shell" },
	};

/*
**  Image name for error messages
*/
char	*image;

/*
**  Command line switches
*/
int	retval,debug=0,verbose=0,ignore=FALSE,resetpwunsuc=FALSE,setauditid=FALSE,changesystemdefault=FALSE,rebuilt=FALSE;
/* NOT YET IMPLEMENTED: ignore: Ignore unknown user, insufficient information */

/*
**  Set or reset password change time
*/
enum	{NONE,RESET,SET} setpwsucchg=NONE;

/*
**  Mode of operation: what to do with existing and non-existing users
*/
enum	{UPDATE,ADD,ADDBLIND,REMOVE,REMOVEBLIND} operation=UPDATE;

/*
**  Input mode: how to parse stdin
*/
enum	{ATTRIBUTES,CMDOPTS,PASSWD} mode=ATTRIBUTES;

/*
**  Type of passwd system, and strings describing these, in the same order.
**  Where the passwd database files live, same order.
**  Dummy password for /etc/passwd when using shadow, same order.
**  One variable: describing the current system type.
**  In case of YP (NIS) passwd, name of the map source file.
*/
typedef enum	{UNIX,YP,SHADOW,SPASSWD,SECURE,SECURITY,TCB} system_t;
char	*system_d[]={
		"Standard Unix " PREFIX "/etc/passwd",
		"YP (NIS) map in " PREFIX "%s",
		"Shadow passwords in " PREFIX "/etc/shadow (Linux, Solaris, UXP/V)",
		"Shadow passwords in " PREFIX "/etc/spasswd",
		"Shadow passwords in " PREFIX "/.secure/etc/passwd (HP-UX 9.x)",
		"Password database in " PREFIX "/etc/security/passwd (AIX)",
		"Password database in " PREFIX "/tcb/files/auth/ (HP-UX 10.x Trusted System)",
	};
char	*passwd_name[]={
		PREFIX "/etc/passwd",
		PREFIX "%s",
		PREFIX "/etc/shadow",
		PREFIX "/etc/spasswd",
		PREFIX "/.secure/etc/passwd",
		PREFIX "/etc/security/passwd",
		PREFIX "/tcb/files/auth",
	};
char	*sysdef_name[]={
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		PREFIX "/tcb/files/auth/system/default",
	};
char	*dummy_passwd[]={
		NULL,
		NULL,
		"x",
		"!",
		"!",
		"!",
		"*",
	};
system_t	systype;
char		*yppasswd=NULL,*altpasswd=NULL,*altshadow=NULL;

/*
**  UID list type; used for rebuild mode.
*/
typedef struct uidlist_t {
	struct uidlist_t	*next;
	uid_t			from,to;
	} uidlist_t;
uidlist_t	*rebuild=NULL;

/*
**  Generic named list type; used for generic list processing routines.
**  No actual variables of this type.
*/
typedef struct list_t {
	struct list_t	*next;
	char		*name;
	} list_t;

/*
**  Types that can be used in a field list
**  Definition of a field: name,type,value triplet, plus file offset and a
**  changed flag for inplace editing (only for file-based data)
*/
typedef enum	{DELETE,BOOL,INT,STRING} type_t;
typedef struct field_t {
	struct field_t	*next;		/* Next field */
	char		*name;		/* Name of field */
	type_t		type;		/* Type of field */
	char		*value;		/* Value of field */
	long		offset;		/* Position in file */
	int		changed;	/* Field must be flushed */
	} field_t;
/*
**  Options specified via the commandline are store in such a structure.
*/
field_t	*cmdopts=NULL;

/*
**  Entry: list of fields, w/ additional parameters
**  In rebuild mode, a keep flag shows wether this entry has been mentioned and
**  must be kept.
*/
typedef enum	{DONT,UID,MENTIONED} keep_t;
typedef struct entry_t {
	struct entry_t	*next;		/* Next entry */
	char		*name;		/* Entry name */
	field_t		*field;		/* Parsed entry */
	keep_t		keep;		/* Keep entry */
	} entry_t;

/*
**  Passwd like structures: built of an entry list, but also containing fields
**  specifying the status of the in-core representation.
**  Changed gets set when the incore representation has been changed and
**  must be flushed to disk. If the change results in a change of length,
**  inplace gets reset, indicating the need to rewrite the file instead of
**  editing it in place. If editing in place can be done, a flag within the
**  structures determines which fields have to be flushed to disk.
*/
typedef struct passwd_t {
	entry_t	*entry;		/* Entry list */
	char	*name;		/* File name */
	FILE	*stream;	/* File stream */
	char	*tmpname;	/* Temporary file name */
	FILE	*tmpstream;	/* Temporary file stream */
	ushort	mode;		/* File mode */
	uid_t	uid;		/* File owner */
	gid_t	gid;		/* File group */
	entry_t	*current;	/* Current entry */
	field_t	*change;	/* Current changes */
	int	changed;	/* Data must be flushed */
	int	inplace;	/* Data can be edited inplace */
	} passwd_t;
/*
**  Passwd and shadow passwd are built like this: list of user entries.
**  So far, only HP-UX 10.x has a user database with personal passwd files,
**  we specify shadow for these and traditional unix systems anyway.
*/
passwd_t	*passwd=NULL,*shadow=NULL;

/*
**  TCB like structures: built of a fields list, but also containing fields
**  specifying the status of the in-core representation.
**  Changed gets set when the incore representation has been changed and
**  must be flushed to disk. If the change results in a change of length,
**  inplace gets reset, indicating the need to rewrite the file instead of
**  editing it in place. If editing in place can be done, a flag within the
**  structures determines which fields have to be flushed to disk.
**  WARNING: currently inplace editing for tcb-like files is not supported.
*/
typedef struct tcb_t {
	struct field_t	*field;			/* Field list */
	char		name[MAXPATHLEN];	/* File name */
	FILE		*stream;		/* File stream */
	char		tmpname[MAXPATHLEN];	/* Temporary file name */
	FILE		*tmpstream;		/* Temporary file stream */
	int		changed;		/* Data must be flushed */
	int		len;			/* Length of current line */
	} tcb_t;
/*
**  HP-UX 10.x user database personal passwd files are built like this.
*/
tcb_t	*tcb=NULL;

/*
**  Generic input buffer in the heap, so we don't overflow our stack when
**  recursing. Also position of buffer start in stream and a flag specifying
**  wether buffer was already filled in a previous read.
*/
char	buffer[8*1024];	
long	pos;
int	filled=FALSE;


/*
**  Verbose messages, so the text shows up only once in the binary...
**  Sigh... this should be the compiler's job, but you can't do a
**  printf((const char *)"...",...); in traditional compilers...
*/
char	v_system[]="System: ",
	v_run[]="%s...",
	v_ok[]="ok\n",
	v_del[]="removed\n",
	v_nouser[]="no such user\n",
	v_nosuser[]="no such user in shadow\n",
	v_info[]="insufficient information\n";
	

/*
**  Error messages, so the text shows up only once in the binary...
**  Sigh... this should be the compiler's job, but you can't do a
**  printf((const char *)"...",...); in traditional compilers...
*/
char	err_init[]="%s: can't initialize ",
	err_stat[]="%s: can't stat ",
	err_open[]="%s: can't open ",
	err_close[]="%s: can't close ",
	err_dir[]="%s: can't open directory ",
	err_remove[]="%s: can't remove ",
	err_read[]="%s: error reading ",
	err_rename[]="%s: error renaming %s to ",
	err_write[]="%s: error writing ",
	err_handle[]="%s: error handling requests ",
	err_name[]="%s: no username entry for %s\n",
	err_parse[]="%s: invalid attribute syntax: %s\n",
	err_info[]="%s: insufficient information for user %s\n",
	err_invalid[]="%s: invalid or insufficient information\n",
	err_invalidfield[]="%s: invalid or insufficient information (%s)\n",
	err_nouser[]="%s: no such user: %s\n",
	err_nosuser[]="%s: no such user in shadow: %s\n",
	err_notuser[]="%s: no such user in database: %s\n",
	err_busy[]="%s: password file busy\n",
	err_nosysdef[]="%s: no system default for this system\n",
	err_notyetimplemented[]="%s: this mode is not yet implemented\n",
	err_fatal[]="WARNING: Your user database may be corrupt!\n",
	err_termination[]="Interrupt - attempting to leave database in consistent state\n",
	err_error[]="Internal error, program failure - discarding incore data!\n",
	err_error2[]="Multiple internal errors, program terminated abnormally\n",
	err_sig[]="Received SIG%s sig=%d code=%d scp=%d\n";

/*
**  Signal names
*/
#ifdef linux
char	*signals[]={"HUP","INT","QUIT","ILL","TRAP","ABRT","BUS","FPE","KILL","USR1","SEGV","USR2","PIPE","ALRM","TERM","STKFLT","CHLD","CONT","STOP","TSTP","TTIN","TTOU","URG","XCPU","XFSZ","VTALRM","PROF","WINCH","IO","PWR","UNUSED"};
#else
char	*signals[]={"HUP","INT","QUIT","ILL","TRAP","IOT","EMT","FPE","KILL","BUS","SEGV","SYS","PIPE","ALRM","TERM","USR1","USR2","CLD","PWR","VTALRM","PROF","IO","WINCH","STOP","TSTP","CONT","TTIN","TTOU"};
#endif



/*******************************************************************************
**
**  chomp - remove trailing CR, LF and whitespace from a string
**
**  Return value:
**	A pointer to the new end-of-string is returned.
*/
static char *chomp(text)
char	*text;
{
	char	*ptr;

	ptr=strchr(text,'\0');
	if (*(ptr-1)=='\n')
		ptr--;
	if (*(ptr-1)=='\r')
		ptr--;
	while (ptr>text && *(ptr-1)==' ')
		ptr--;
	*ptr='\0';
	return(ptr);
}



/*******************************************************************************
**
**  fill - read a line from a stream into buffer
**
**  Return value:
**	Upon successful completion, 0 is returned. If an end-of-file condition
**	is encountered, a value of -2 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int fill(src)
FILE	*src;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] fill(0x%08lx)\n",__LINE__,(long)src);
	if (!filled) {
		pos=ftell(src);
		if (!(fgets(buffer,sizeof(buffer),src)))
			return(feof(src)?-2:-1);
		(void)chomp(buffer);
		if (debug>1)
			(void)fprintf(stderr,"[%d] fill: \"%s\"\n",__LINE__,buffer);
		filled=TRUE;
	}
	return(0);
}



/*******************************************************************************
**
**  check_system - determine type of password storage
**
**  Return value:
**	UNIX	- Standard Unix /etc/passwd
**	YP	- YP (NIS) map in <yppasswd>
**	SHADOW	- Shadow passwords in /etc/shadow (Solaris 2.x, UXP/V 4.x)
**	SPASSWD	- Shadow passwords in /etc/spasswd
**	SECURE	- Shadow passwords in /.secure/etc/passwd (HP-UX 9.x)
**	SECURITY	- Password database in /etc/security/passwd (AIX)
**	TCB	- Password database in /tcb/files/auth/ (HP-UX 10.x Trusted System)
*/
static system_t check_system()
{
	if (yppasswd)
		return(YP);
	if (!access(passwd_name[SHADOW],F_OK))
		return(SHADOW);
	if (!access(passwd_name[SPASSWD],F_OK))
		return(SPASSWD);
	if (!access(passwd_name[TCB],F_OK))
		return(TCB);
	if (!access(PREFIX "/.secure",F_OK))
		return(SECURE);
	if (!access(PREFIX "/etc/security",F_OK))
		return(SECURITY);
	return(UNIX);
}



/*******************************************************************************
**
**  lock - create a lockfile, returning error status if already exists;
**	set mode and owner and open a stream on the lockfile
**
**  Return value:
**	Upon successful completion, a FILE pointer to the stream is returned.
**	Otherwise, a null pointer is returned and errno is set to indicate the
**	error.
*/
static FILE *lock(name,uid,gid,perm)
char	*name;
uid_t	uid;
gid_t	gid;
ushort	perm;
{
	int	fd;

	if ((fd=open(name,O_WRONLY|O_CREAT|O_EXCL,perm))<0)
		return(NULL);
	if (fchown(fd,uid,gid))
		return(NULL);
	return(fdopen(fd,"w"));
}



/*******************************************************************************
**
**  unlock - close and unlock a lockfile
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int unlock(stream)
FILE	*stream;
{
	return(fclose(stream));
}



/*******************************************************************************
**
**   dump - show memory dump of a field list (for debugging purposes only)
*/
static void dump(list)
field_t	*list;
{
	(void)fprintf(stderr,"[%d] dump(0x%08lx)\n",__LINE__,(long)list);
	while (list) {
		(void)fprintf(stderr,"[%d] 0x%08lx:\n",__LINE__,(long)list);
		(void)fprintf(stderr,"[%d] \tnext=0x%08lx\n",__LINE__,(long)(list->next));
		(void)fprintf(stderr,"[%d] \tname=\"%s\"\n",__LINE__,list->name);
		(void)fprintf(stderr,"[%d] \ttype=%d (%s)\n",__LINE__,list->type,list->type==DELETE?"delete":list->type==BOOL?"bool":list->type==INT?"int":list->type==STRING?"string":"UNKNOWN");
		if (list->type==BOOL)
			(void)fprintf(stderr,"[%d] \tvalue=%s\n",__LINE__,bool(list->value));
		else
			(void)fprintf(stderr,"[%d] \tvalue=\"%s\"\n",__LINE__,list->value);
		list=list->next;
	}
}



/*******************************************************************************
**
**  add_list - add a field or a sublist to the end of a named list
*/
static void add_list(list,sublist)
list_t	**list,*sublist;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] add_list(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)sublist);

	while (*list)
		list=&(*list)->next;
	*list=sublist;
}



/*******************************************************************************
**
**  add - add a name,type,value triplet to a field list. For inplace editing,
**	offset and changed flag get set also. Memory for non-bool values is
**	allocated via malloc and must be freed upon disposal of the list.
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int add(list,name,type,value,offset,changed)
field_t	**list;
char	*name;
type_t	type;
char	*value;
long	offset;
int	changed;
{
	field_t	*field;

	if (debug>2)
		(void)fprintf(stderr,"[%d] add(0x%08lx,\"%s\",%d,\"%s\")\n",__LINE__,(long)list,name,type,value);

	field=(field_t *)malloc(sizeof(field_t));
	if (field==NULL)
		return(-1);
	field->next=NULL;
	field->name=strdup(name);
	if (field->name==NULL)
		return(-1);
	field->type=type;
	if (type==DELETE) {
		field->value=NULL;
	}
	else if (type==BOOL) {
		field->value=(char *)!!value;
	}
	else {
		field->value=strdup(value);
		if (field->value==NULL)
			return(-1);
	}
	field->offset=offset;
	field->changed=changed;
	add_list((list_t **)list,(list_t *)field);
	return(0);
}



/*******************************************************************************
**
**  del - remove a name,type,value triplet from a field list, freeing memory
**	allocated via malloc.
**
**  Return value:
**	If the name specified can't be found, -2 is returned. Otherwise, 0 is
**	returned.
*/
static int del(list,name)
field_t	**list;
char	*name;
{
	field_t	**ptr,*nptr;

	if (debug>2)
		(void)fprintf(stderr,"[%d] del(0x%08lx,\"%s\")\n",__LINE__,(long)list,name);
	if (debug>4) {
		(void)fprintf(stderr,"[%d] del: list before removal\n",__LINE__);
		dump(*list);
	}

	ptr=list;
	while (*ptr&&strcmp((*ptr)->name,name))
		ptr=&(*ptr)->next;
	if (!*ptr) {
		if (debug>2)
			(void)fprintf(stderr,"[%d] del: \"%s\" not found\n",__LINE__,name);
		return(-2);
	}

	free((void *)(*ptr)->name);
	if ((*ptr)->type!=DELETE&&(*ptr)->type!=BOOL)
		free((void *)(*ptr)->value);
	nptr=(*ptr)->next;
	free((void *)(*ptr));
	(*ptr)=nptr;

	if (debug>4) {
		(void)fprintf(stderr,"[%d] del: list after removal\n",__LINE__);
		dump(*list);
	}

	return(0);
}



/*******************************************************************************
**
**  get_list - get a pointer to a named list field
**
**  Return value:
**	If the named field was found, a pointer to the field is returned.
**	Otherwise, a null pointer is returned.
*/
static list_t *get_list(list,name)
list_t	*list;
char	*name;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] get_list(0x%08lx,\"%s\")\n",__LINE__,(long)list,name);
	
	while (list&&strcmp(list->name,name)) {
		if (debug>4)
			(void)fprintf(stderr,"[%d] get_list: 0x%08lx=\"%s\"\n",__LINE__,(long)list,list->name);
		list=list->next;
	}
	if (debug>3) {
		if (list)
			(void)fprintf(stderr,"[%d] get_list: 0x%08lx==\"%s\"\n",__LINE__,(long)list,null(list->name));
		else
			(void)fprintf(stderr,"[%d] get_list: 0x%08lx\n",__LINE__,(long)list);
	}
	return(list);
}



/*******************************************************************************
**
**  getval - get value for a name from a field list
**
**  Return value:
**	Upon successful completion, a pointer to the field's value is returned.
**	Otherwise, a null pointer is returned.
*/
static char *getval(list,name)
field_t	*list;
char	*name;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] getval(0x%08lx,\"%s\")\n",__LINE__,(long)list,name);
	
	if (!(list=(field_t *)get_list((list_t *)list,name)))
		return(NULL);
	if (debug>3)
		(void)fprintf(stderr,"[%d] getval: 0x%08lx=\"%s\",%d,\"%s\"\n",__LINE__,(long)list,list->name,list->type,list->value);
	return(list->value);
}



/*******************************************************************************
**
**  get - get a name,type,value triplet and inplace editing information from a
**	field list
**
**  Return value:
**	If the named field was found, the variables pointed to by type, value,
**	offset and changed are set to the correspondent parts of the field and
**	0 is returned. Otherwise, a value of -2 is returned.
*/
static int get(list,name,type,value,offset,changed)
field_t	*list;
char	*name;
type_t	*type;
char	**value;
long	*offset;
int	*changed;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] get(0x%08lx,\"%s\",0x%08lx,0x%08lx,0x%08lx,0x%08lx)\n",__LINE__,(long)list,name,(long)type,(long)value,(long)offset,(long)changed);
	
	if (!(list=(field_t *)get_list((list_t *)list,name)))
		return(-2);
	if (type)
		*type=list->type;
	if (value)
		*value=list->value;
	if (offset)
		*offset=list->offset;
	if (changed)
		*changed=list->changed;
	if (debug>3)
		(void)fprintf(stderr,"[%d] get: 0x%08lx=\"%s\",%d,\"%s\"\n",__LINE__,(long)list,list->name,list->type,list->value);
	return(0);
}



/*******************************************************************************
**
**  undef - delete a complete field list, freeing memory allocated via malloc.
**	Make sure no references to the list or its entries exist before
**	undef'ing.
*/
static void undef(list)
field_t	**list;
{
	field_t	*next;

	if (debug>2)
		(void)fprintf(stderr,"[%d] undef(0x%08lx)\n",__LINE__,(long)list);

	if (debug)
		*list=NULL;
	while (*list) {
		next=(*list)->next;
		free((void *)(*list)->name);
		if ((*list)->type!=DELETE&&(*list)->type!=BOOL)
			free((void *)(*list)->value);
		free((void *)(*list));
		*list=next;
	}
}



/*******************************************************************************
**
**  uidlist - parse uid list
*/
static uidlist_t *uidlist(list)
char	*list;
{
	char		*comma,*hyphen;
	uidlist_t	*this,*last=NULL;

	while (list) {
		if (comma=strchr(list,','))
			*comma++='\0';
		if (!(this=(uidlist_t *)malloc(sizeof(uidlist_t)))) {
			(void)fprintf(stderr,err_init,image);
			perror("uidlist structure");
			exit(2);
		}
		this->next=last;
		last=this;
		this->from=(uid_t)atol(list);
		if (hyphen=strchr(list,'-')) {
			*hyphen++='\0';
			this->to=(uid_t)atol(hyphen);
		}
		else
			this->to=this->from;
		list=comma;
	}
	return(last);
}



/*******************************************************************************
**
**  check_uid - test wether uid is in list
*/
static int check_uid(list,uid)
uidlist_t	*list;
uid_t		uid;
{
	if (debug>2)
		(void)fprintf(stderr,"[%d] check_uid(0x%08lx,%ld)\n",__LINE__,(long)list,(long)uid);
	while (list) {
		if (list->from <= uid && uid <= list->to) {
			if (debug>3)
				(void)fprintf(stderr,"[%d] check_uid = %d\n",__LINE__,TRUE);
			return(TRUE);
		}
		list=list->next;
	}
	if (debug>3)
		(void)fprintf(stderr,"[%d] check_uid = %d\n",__LINE__,FALSE);
	return(FALSE);
}



/*******************************************************************************
**
**  attr_name - canonicalize attribute name
**
**  Return value:
**	Generic attribute name
*/
static char *attr_name(attr)
char	*attr;
{
	int	i;

	for (i=0;i<sizeof(sypw)/sizeof(struct sypw_t);i++)
		if (!strcmp(attr,sypw[i].sy))
			return(sypw[i].pw);
	return(attr);
}



/*******************************************************************************
**
**  sy_name - Security attribute name
**
**  Return value:
**	Security attribute name
*/
static char *sy_name(attr)
char	*attr;
{
	int	i;

	for (i=0;i<sizeof(sypw)/sizeof(struct sypw_t);i++)
		if (!strcmp(attr,sypw[i].pw))
			return(sypw[i].sy);
	return(attr);
}



/*******************************************************************************
**
**  parse_tcb - parse text representation of a trusted system database field to
**	a name,type,value triplet for a field list. Pointers to the original
**	string are returned, no memory is allocated.
**
**  Return value:
**	If the text representation is a valid trusted system database field,
**	the variables pointed to by name, type and value are set and 0 is
**	returned. Otherwise, a value of -2 is returned.
*/
static int parse_tcb(text,name,type,value)
char	*text;
char	**name;
type_t	*type;
char	**value;
{
	char	*ptr;

	if (debug)
		(void)fprintf(stderr,"[%d] parse_tcb(\"%s\",0x%08lx,0x%08lx,0x%08lx)\n",__LINE__,text,(long)name,(long)type,(long)value);

	ptr=text;
	while(*ptr&&strchr("_abcdefghijklmnopqrstuvwxyz0123456789",*ptr))
		ptr++;
	switch (*ptr) {
	case '\0':
		*type=BOOL;
		*value=(char *)TRUE;
		break;
	case '@':
		*type=BOOL;
		*value=(char *)FALSE;
		break;
	case '!':
		*type=DELETE;
		*value=NULL;
		break;
	case '#':
		*type=INT;
		*value=ptr+1;
		break;
	case '=':
		*type=STRING;
		*value=ptr+1;
		break;
	default:
		return(-2);
	}
	*ptr='\0';
	*name=text;
	return(0);
}



/*******************************************************************************
**
**  read_tcb - read a complete trusted system database field into a field list.
**	Memory for the list is allocated within the add function.
**
**  Return value:
**	Upon successful completion, 0 is returned. If a complete trusted system
**	database field can't be read or the field read is invalid or incomplete,
**	a value of -2 is returned. Otherwise, a value of -1 is returned and
**	errno is set to indicate the error.
*/
static int read_tcb(list,src)
field_t	**list;
FILE	*src;
{
	char	*ptr,*nptr,*name,*value;
	type_t	type;

	if (debug)
		(void)fprintf(stderr,"[%d] read_tcb(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)src);
	while (pos=ftell(src),fgets(buffer,sizeof(buffer),src)) {
		(void)chomp(buffer);
		if (debug)
			(void)fprintf(stderr,"[%d] read_tcb: \"%s\"\n",__LINE__,buffer);
		if ((ptr=strchr(buffer,':'))) {
			*ptr++='\0';
			while ((nptr=strchr(ptr,':'))) {
				*nptr++='\0';
				if (!strcmp(ptr,chkent))
					return(0);
				check(parse_tcb(ptr,&name,&type,&value));
				check(add(list,name,type,value,pos+(value-buffer),FALSE));
				ptr=nptr;
			}
			if (strcmp(ptr,"\\"))
				return(-2);
		}
		else
			return(-2);
	}
	return(-2);
}



/*******************************************************************************
**
**  out_tcb - convert a name,type,value triplet to the text representation of a
**	trusted system database field and write to dest
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int out_tcb(name,type,value)
char	*name;
type_t	type;
char	*value;
{
	char	*ptr;

	if (debug)
		(void)fprintf(stderr,"[%d] out_tcb(\"%s\",%d,\"%s\")\n",__LINE__,name,type,value);

	(void)sprintf(buffer,"%s",name);
	ptr=strchr(buffer,'\0');
	switch (type) {
	case BOOL:
		(void)sprintf(ptr,value?"":"@");
		break;
	case INT:
		(void)sprintf(ptr,"#%s",value);
		break;
	case STRING:
		(void)sprintf(ptr,"=%s",value);
		break;
	default:	/* Should not occur */
		(void)fprintf(stderr,err_error,image);
		(void)fprintf(stderr,"out_tcb: invalid type (%d)\n",type);
		return(-2);
	}
	if (debug)
		(void)fprintf(stderr,"[%d] out_tcb: buffer=\"%s\"\n",__LINE__,buffer);
	if (tcb->len+strlen(buffer)+2>MAX_TCB_LEN) {
		if (fprintf(tcb->tmpstream,"\\\n\t:")<0)
			return(-1);
		tcb->len=9;
	}
	tcb->len+=strlen(buffer)+1;
	return(fprintf(tcb->tmpstream,"%s:",buffer)<0?-1:0);
}



/*******************************************************************************
**
**  write_tcb - write a field list to a trusted system database field
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int write_tcb(name)
char	*name;
{
	field_t	*field;

	if (debug)
		(void)fprintf(stderr,"[%d] write_tcb(\"%s\")\n",__LINE__,name);

	tcb->len=0;
	if (out_tcb(name,BOOL,(char *)TRUE))
		return(-1);
	field=tcb->field;
	while (field) {
		/* This should not be required since no delete entries should ever show
		 * up in the output queue.
		if (field->type!=DELETE)
		 */
			if (out_tcb(field->name,field->type,field->value))
				return(-1);
		field=field->next;
	}
	if (out_tcb(chkent,BOOL,(char *)TRUE))
		return(-1);
	return(fprintf(tcb->tmpstream,"\n")<0?-1:0);
}



/*******************************************************************************
**
**  mult - multiply ascii representation of an integer
**
**  Return value:
**	A pointer to a static area containing the ascii representation of the
**	computed product is returned. Data will be overwritten in subsequent
**	calls to mult.
*/
static char *mult(c,factor)
char	*c;
int	factor;
{
	static char	mbuf[20];

	if (debug>3)
		(void)fprintf(stderr,"[%d] mult(\"%s\",%d)\n",__LINE__,c,factor);

	(void)sprintf(mbuf,"%ld",atol(c)*factor);
	return(mbuf);
}



/*******************************************************************************
**
**  divide - divide ascii representation by an integer
**
**  Return value:
**	A pointer to a static area containing the ascii representation of the
**	computed quotient is returned. Data will be overwritten in subsequent
**	calls to divide.
*/
static char *divide(c,denom)
char	*c;
int	denom;
{
	static char	dbuf[20];

	if (debug>3)
		(void)fprintf(stderr,"[%d] divide(\"%s\",%d)\n",__LINE__,c,denom);

	(void)sprintf(dbuf,"%ld",atol(c)/denom);
	return(dbuf);
}



/*******************************************************************************
**
**  weeks - convert passwd date representation to time value
**
**  Return value:
**	A pointer to a static area containing the ascii representation of the
**	computed time value is returned. Data will be overwritten in subsequent
**	calls to weeks.
*/
static char *weeks(c,cnt)
char	*c;
int	cnt;
{
	static char	wbuf[20];
	char		*ptr;
	time_t	tmp;

	if (debug>1)
		(void)fprintf(stderr,"[%d] weeks(\"%s\",%d)\n",__LINE__,c,cnt);

	tmp=0;
	while (cnt--) {
		tmp*=64;
		tmp+=(c[cnt]&&(ptr=strchr(week_chars,c[cnt])))?(ptr-week_chars)*7*24*60*60:0;
	}
	(void)sprintf(wbuf,"%ld",(long)tmp);
	return(wbuf);
}



/*******************************************************************************
**
**  week_char - convert ascii representation of a time value to a passwd date
**	representation
**
**  Return value:
**	A pointer to a static area containing the passwd representation of the
**	given time value is returned.
*/
static char *week_char(w)
char	*w;
{
	static char	wbuf[20];
	char	*ptr;
	time_t	tmp;

	if (debug>1)
		(void)fprintf(stderr,"[%d] week_char(\"%s\")\n",__LINE__,w);

	ptr=wbuf;
	tmp=atol(w)/(7*24*60*60);
	while(tmp) {
		*ptr++=week_chars[tmp%64];
		tmp/=64;
	}
	*ptr='\0';
	return(wbuf);
}



/*******************************************************************************
**
**  read_passwd - read a complete passwd field into a field list. Memory for
**	the list is allocated within the add function.
**
**  Return value:
**	Upon successful completion, 0 is returned. If an end-of-file condition
**	is encountered, a value of -2 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int read_passwd(list,src)
field_t	**list;
FILE	*src;
{
	char	*ptr,*nptr,*pptr;

	if (debug>1)
		(void)fprintf(stderr,"[%d] read_passwd(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)src);

	check(fill(src));

	(void)strcat(buffer,":");
	ptr=buffer;
	nptr=strchr(ptr,':');
	pwread(pw_name,STRING);
	if (nptr) {
		*nptr++='\0';
		if ((pptr=strchr(ptr,','))) {
			*pptr++='\0';
			if (*pptr) {
				check(add(list,pw_exp,INT,weeks(pptr,1),pos==-1?-1:pos+(pptr-buffer),FALSE));
				if (*++pptr) {
					check(add(list,pw_minchg,INT,weeks(pptr,1),pos==-1?-1:pos+(pptr-buffer),FALSE));
					if (*++pptr) {
						check(add(list,pw_succhg,INT,weeks(pptr,2),pos==-1?-1:pos+(pptr-buffer),FALSE));
					}
				}
			}
		}
		check(add(list,pw_passwd,STRING,ptr,pos==-1?-1:pos+(ptr-buffer),FALSE));
		ptr=nptr;
		nptr=strchr(ptr,':');
	}
	pwread(pw_uid,INT);
	pwread(pw_gid,INT);
	pwread(pw_gecos,STRING);
	pwread(pw_dir,STRING);
	pwread(pw_shell,STRING);
	filled=FALSE;
	if (debug>4)
		dump(*list);
	return(0);
}



/*******************************************************************************
**
**  read_shadow - read a complete shadow field into a field list. Memory for
**	the list is allocated within the add function.
**
**  Return value:
**	Upon successful completion, 0 is returned. A parsing error returns a
**	value of -3. If an end-of-file condition is encountered, a value of -2
**	is returned. Otherwise, a value of -1 is returned and errno is set to
**	indicate the error.
*/
static int read_shadow(list,src)
field_t	**list;
FILE	*src;
{
	char	*ptr,*nptr,*pptr;

	if (debug>1)
		(void)fprintf(stderr,"[%d] read_shadow(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)src);

	check(fill(src));

	switch (systype) {
	case SHADOW:
		(void)strcat(buffer,":");
		ptr=buffer;
		nptr=strchr(ptr,':');
		pwread(pw_name,STRING);
		pwread(pw_passwd,STRING);
		pwreaddays(pw_succhg,INT);
		pwreaddays(pw_minchg,INT);
		pwreaddays(pw_exp,INT);
		pwreaddays(pw_expire_warning,INT);
		pwreaddays(pw_llogin,INT);
		pwreaddays(pw_acct_expire,INT);
		pwread(pw_flags,STRING);
		filled=FALSE;
		break;
	case SPASSWD:
		(void)fprintf(stderr,err_notyetimplemented,image);
		exit(2);
		break;
	case SECURE:
		(void)strcat(buffer,":");
		ptr=buffer;
		nptr=strchr(ptr,':');
		pwread(pw_name,STRING);
		if (nptr) {
			*nptr++='\0';
			if ((pptr=strchr(ptr,','))) {
				*pptr++='\0';
				if (*pptr) {
					check(add(list,pw_exp,INT,weeks(pptr,1),pos==-1?-1:pos+(pptr-buffer),FALSE));
				}
				if (*++pptr) {
					check(add(list,pw_minchg,INT,weeks(pptr,1),pos==-1?-1:pos+(pptr-buffer),FALSE));
				}
				if (*++pptr) {
					check(add(list,pw_succhg,INT,weeks(pptr,2),pos==-1?-1:pos+(pptr-buffer),FALSE));
				}
			}
			check(add(list,pw_passwd,STRING,ptr,pos==-1?-1:pos+(ptr-buffer),FALSE));
			ptr=nptr;
			nptr=strchr(ptr,':');
		}
		pwread(pw_uid,INT);
		pwread(pw_auditflag,INT);
		filled=FALSE;
		break;
	case SECURITY:
		/* Skip empty lines; they're documented as mandatory,
		   but not really required */
		while (!buffer[0]) {
			filled=FALSE;
			check(fill(src));
		}
		if (strchr(" \t\r",buffer[0])||!(nptr=strchr(buffer,':'))) {
			(void)fprintf(stderr,err_parse,image,buffer);
			return(-3);
		}
		*nptr='\0';
		if (debug>1)
			(void)fprintf(stderr,"[%d] read_shadow: \"%s\" = \"%s\"\n",__LINE__,pw_name,buffer);
		check(add(list,pw_name,STRING,buffer,pos,FALSE));
		filled=FALSE;
		for(;;) {
			check(fill(src));
			ptr=buffer;
			while (*ptr&&strchr(" \t\r",*ptr))
				ptr++;
			if (ptr==buffer)
				break;
			if (!(nptr=strchr(ptr+1,'='))) {
				(void)fprintf(stderr,err_parse,image,buffer);
				return(-3);
			}
			pptr=nptr+1;
			while (*pptr&&strchr(" \t\r",*pptr))
				pptr++;
			while (strchr(" \t\r",*(nptr-1)))
				nptr--;
			*nptr='\0';
			if (debug>1)
				(void)fprintf(stderr,"[%d] read_shadow: \"%s\" = \"%s\"\n",__LINE__,attr_name(ptr),pptr);
			check(add(list,attr_name(ptr),STRING,pptr,pos==-1?-1:pos+(pptr-buffer),FALSE));
			filled=FALSE;
		}
		break;
	default:	/* Should not occur */
		(void)fprintf(stderr,err_error,image);
		(void)fprintf(stderr,"read_shadow: invalid type (%d)\n",systype);
		return(-3);
	}
	if (debug>4)
		dump(*list);
	return(0);
}



/*******************************************************************************
**
**  write_passwd - write a field list to a passwd file
**
**  Return value:
**	Upon successful completion, NULL is returned. If the passwd entry is
**	invalid or incomplete, a pointer to the name of the field in question
***	is returned. Otherwise, a value of -1 is returned and errno is set to
***	indicate the error.
*/
static char *write_passwd(dest,list)
FILE	*dest;
field_t	*list;
{
	char	*value;

	if (debug>1)
		(void)fprintf(stderr,"[%d] write_passwd(0x%08lx,0x%08lx)\n",__LINE__,(long)dest,(long)list);

	if (!(value=getval(list,pw_name)))
		return(pw_name);
	(void)strcpy(buffer,value);
	pwwrite(pw_passwd);
	if (systype==UNIX&&(value=getval(list,pw_exp))) {
		(void)strcat(buffer,",");
		(void)strcat(buffer,week_char(value));
		if ((value=getval(list,pw_minchg))) {
			(void)strcat(buffer,week_char(value));
			if ((value=getval(list,pw_succhg)))
				(void)strcat(buffer,week_char(value));
		}
	}
	pwwrite(pw_uid);
	pwwrite(pw_gid);
	pwwrite(pw_gecos);
	pwwrite(pw_dir);
	pwwrite(pw_shell);
	if (debug>1)
		(void)fprintf(stderr,"[%d] write_passwd: \"%s\"\n",__LINE__,buffer);
	return((char *)(fprintf(dest,"%s\n",buffer)<0?-1:0));
}



/*******************************************************************************
**
**  write_shadow - write a field list to a shadow file
**
**  Return value:
**	Upon successful completion, NULL is returned. If the shadow entry is
**	invalid or incomplete, a pointer to the name of the field in question
***	is returned. Otherwise, a value of -1 is returned and errno is set to
***	indicate the error.
*/
static char *write_shadow(dest,list)
FILE	*dest;
field_t	*list;
{
	char	*value;

	if (debug>1)
		(void)fprintf(stderr,"[%d] write_shadow(0x%08lx,0x%08lx)\n",__LINE__,(long)dest,(long)list);

	if (!(value=getval(list,pw_name))) {
		if (debug>2)
			dump(list);
		return(pw_name);
	}
	switch (systype) {
	case SHADOW:
		(void)strcpy(buffer,value);
		pwwrite(pw_passwd);
		pwwritedays(pw_succhg);
		pwwritedays(pw_minchg);
		pwwritedays(pw_exp);
		pwwritedays(pw_expire_warning);
		pwwritedays(pw_llogin);
		pwwritedays(pw_acct_expire);
		pwwrite(pw_flags);
		break;
	case SPASSWD:
		(void)fprintf(stderr,err_notyetimplemented,image);
		exit(2);
		break;
	case SECURE:
		(void)strcpy(buffer,value);
		(void)strcat(buffer,":");
		if (!(value=getval(list,pw_passwd)))
			return(pw_passwd);
		(void)strcat(buffer,value);
		if ((value=getval(list,pw_exp))) {
			(void)strcat(buffer,",");
			(void)strcat(buffer,week_char(value));
			if ((value=getval(list,pw_minchg))) {
				(void)strcat(buffer,week_char(value));
				if ((value=getval(list,pw_succhg)))
					(void)strcat(buffer,week_char(value));
			}
		}
		(void)strcat(buffer,":");
		if (!(value=getval(list,pw_uid)))
			return(pw_uid);
		(void)strcat(buffer,value);
		(void)strcat(buffer,":");
		if (!(value=getval(list,pw_auditflag)))
			return(pw_auditflag);
		(void)strcat(buffer,value);
		break;
	case SECURITY:
		if (fprintf(dest,"%s:\n",value)<0)
			return((char *)-1);
		if ((value=getval(list,pw_passwd)))
			if (fprintf(dest,"\t%s = %s\n",sy_name(pw_passwd),value)<0)
				return((char *)-1);
		if ((value=getval(list,pw_succhg)))
			if (fprintf(dest,"\t%s = %s\n",sy_name(pw_succhg),value)<0)
				return((char *)-1);
		if ((value=getval(list,pw_flags))) {
			if (debug>1)
				(void)fprintf(stderr,"[%d] write_shadow: \"\t%s = %s\"\n",__LINE__,sy_name(pw_flags),value);
			if (fprintf(dest,"\t%s = %s\n",sy_name(pw_flags),value)<0)
				return((char *)-1);
		}
		buffer[0]='\0';
		break;
	default:	/* Should not occur */
		(void)fprintf(stderr,err_error,image);
		(void)fprintf(stderr,"write_shadow: invalid type (%d)\n",systype);
		return("none");
	}
	if (debug>1)
		(void)fprintf(stderr,"[%d] write_shadow: \"%s\"\n",__LINE__,buffer);
	return((char *)(fprintf(dest,"%s\n",buffer)<0?-1:0));
}



/*******************************************************************************
**
**  input_passwd - read a complete passwd file into a list of field lists.
**	Memory for the entries and the entry names is allocated via malloc.
**
**  Return value:
**	Upon successful completion, 0 is returned. If a passwd field has no
**	login name, a value of -2 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int input_passwd(list,cmd,uidlist)
passwd_t	*list;
int		(*cmd)();
uidlist_t	*uidlist;
{
	entry_t	**entryp;
	field_t	*field;
	char	*value;

	if (debug)
		(void)fprintf(stderr,"[%d] input_passwd(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)cmd);

	entryp=&list->entry;
	for (;;) {
		field=NULL;
		retval=(*cmd)(&field,list->stream);
		if (retval==-1)
			return(-1);
		if (retval)
			break;
		*entryp=(entry_t *)malloc(sizeof(entry_t));
		if (*entryp==NULL)
			return(-1);
		(*entryp)->field=field;
		/* Must dup in case the field list gets undef'ed upon user removal */
		if (!((*entryp)->name=strdup(getval((*entryp)->field,pw_name))))
			return(-2);
		(*entryp)->keep=(!uidlist || !(value=getval((*entryp)->field,pw_uid)) || check_uid(uidlist,(uid_t)atol(value)))?UID:DONT;
		if (debug>4)
			(void)fprintf(stderr,"[%d] keep=%d (rebuild=\"%s\", uid=%s)\n",__LINE__, (*entryp)->keep,rebuild,getval((*entryp)->field,pw_uid));
		entryp=&(*entryp)->next;
	}
	*entryp=NULL;
	return(0);
}



/*******************************************************************************
**
**  output_passwd - write a list of field lists to a passwd temporary file
**
**  Return value:
**	Upon successful completion, NULL is returned. If a passwd field is
**	invalid or incomplete, output is interrupted and a pointer to the name
***	of the entry in question is returned. Otherwise, a value of -1 is
***	returned and errno is set to indicate the error.
*/
static char *output_passwd(list,cmd)
passwd_t	*list;
char	*(*cmd)();
{
	entry_t	*entry;
	char	*retname;

	if (debug)
		(void)fprintf(stderr,"[%d] output_passwd(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)cmd);

	entry=list->entry;
	while (entry) {
		if (entry->field && (!rebuilt || entry->keep)) {
			if ((retname=(*cmd)(list->tmpstream,entry->field))) {
				if (retname==(char *)-1)
					return(retname);
				else {
					(void)sprintf(buffer,"user %s, field %s",entry->name,retname);
					return(buffer);
				}
			}
		}
		entry=entry->next;
	}
	return(NULL);
}



/*******************************************************************************
**
**  inplace_passwd - inplace editing of a passwd type file using
**
**  Return value:
**	Upon successful completion, 0 is returned. If full inplace editing
**	information isn't available, output is interrupted and a value of -2 is
**	returned. Otherwise, a value of -1 is returned and errno is set to
**	indicate the error.
*/
static int inplace_passwd(list)
passwd_t	*list;
{
	entry_t	*entry;
	field_t	*field;

	if (debug)
		(void)fprintf(stderr,"[%d] inplace_passwd(0x%08lx)\n",__LINE__,(long)list);

	entry=list->entry;
	while (entry) {
		field=entry->field;
		while (field) {
			if (field->changed) {
				if (debug)
					(void)fprintf(stderr,"[%d] inplace_passwd: %s -> %s -> %s -> \"%s\"\n",__LINE__,list->name,entry->name,field->name,field->value);
				if (field->offset==-1)
					return(-2);
				check(fseek(list->stream,field->offset,SEEK_SET));
				check(fprintf(list->stream,"%s",field->value)<0?-1:0);
			}
			field=field->next;
		}
		entry=entry->next;
	}
	return(0);
}



/*******************************************************************************
**
**  bail_out - bails out with fatal error message
*/
static void bail_out()
{
	if (debug)
		(void)fprintf(stderr,"[%d] bail_out()\n",__LINE__);

	(void)fprintf(stderr,err_fatal);
	(void)unlock(shadow->tmpstream);
	(void)remove(shadow->tmpname);
	(void)unlock(passwd->tmpstream);
	(void)remove(passwd->tmpname);
	exit(3);
}



/*******************************************************************************
**
**  checkout_passwd - flush passwd list to disk, remove lockfile
**
**  Returns if everything is fine, bails out with appropriate error message
**  otherwise.
*/
static void checkout_passwd(list,cmd)
passwd_t	*list;
char	*(*cmd)();
{
	char	*retname;

	if (debug)
		(void)fprintf(stderr,"[%d] checkout_passwd(0x%08lx,0x%08lx)\n",__LINE__,(long)list,(long)cmd);
	if (debug>1)
		(void)fprintf(stderr,"[%d] checkout_passwd: changed=%d inplace=%d tmpstream=0x%08lx tmpname=\"%s\"\n",__LINE__,list->changed,list->inplace,(long)list->tmpstream,list->tmpname);

	if (list->changed) {
		if (list->inplace) {
			list->inplace=FALSE;	/* For subsequent checkout_passwd calls */
			if ((retval=inplace_passwd(list))) {
				(void)fprintf(stderr,err_write,image);
					if (retval==-1)
						perror(list->name);
					else
						(void)fprintf(stderr,err_invalid,list->name);
				checkout_passwd(list,cmd);
				exit(2);
			}
			if (fclose(list->stream)) {
				(void)fprintf(stderr,err_close,image);
				perror(list->name);
				checkout_passwd(list,cmd);
				exit(2);
			}
			if (list->tmpstream)
				(void)unlock(list->tmpstream);
			if (list->tmpname)
				(void)remove(list->tmpname);
		}
		else {
			if ((retname=output_passwd(list,cmd))) {
				(void)fprintf(stderr,err_write,image);
				if (retname==(char *)-1)
					perror(list->tmpname);
				else
					(void)fprintf(stderr,err_invalidfield,list->tmpname,retname);
				bail_out();
			}
			if (unlock(list->tmpstream)) {
				(void)fprintf(stderr,err_close,image);
				perror(list->tmpname);
				bail_out();
			}
			if (rename(list->tmpname,list->name)) {
				(void)fprintf(stderr,err_rename,image,list->tmpname);
				perror(list->name);
				bail_out();
			}
		}
	}
	else {
		if (debug)
			(void)fprintf(stderr,"[%d] checkout_passwd: no change\n",__LINE__);
		if (list->tmpstream)
			(void)unlock(list->tmpstream);
		if (list->tmpname)
			(void)remove(list->tmpname);
	}
}



/*******************************************************************************
**
**  init_passwd - Initialize passwd structure. Memory for the structure is
**	allocated via malloc.
**
**  Returns if everything is fine, bails out otherwise.
*/
static void init_passwd()
{
	struct stat	buf;

	if (debug)
		(void)fprintf(stderr,"[%d] init_passwd()\n",__LINE__);

	if (!(passwd=(passwd_t *)malloc(sizeof(passwd_t)))) {
		(void)fprintf(stderr,err_init,image);
		perror("passwd structure");
		exit(2);
	}

	passwd->entry=NULL;
	switch (systype) {
	case YP:
		if (!(passwd->name=malloc(strlen(yppasswd)+strlen(PREFIX "")+1))) {
			(void)fprintf(stderr,err_init,image);
			perror("YP passwd name");
			exit(2);
		}
		(void)sprintf(passwd->name,PREFIX "%s",yppasswd);
		break;
	case TCB:
		passwd->name=altpasswd?altpasswd:passwd_name[UNIX];
		if (!altpasswd)
			passwd->tmpname=PREFIX "/etc/passwd.tmp";
		break;
	default:
		passwd->name=altpasswd?altpasswd:passwd_name[UNIX];
		if (!altpasswd)
			passwd->tmpname=PREFIX "/etc/ptmp";
		break;
	}
	if (systype == YP || altpasswd) {
		if (!(passwd->tmpname=malloc(strlen(passwd->name)+5))) {
			(void)fprintf(stderr,err_init,image);
			perror("passwd temporary name");
			exit(2);
		}
		(void)strcpy(passwd->tmpname,passwd->name);
		(void)strcat(passwd->tmpname,".tmp");
	}
	if (debug>2)
		(void)fprintf(stderr,"[%d] init_passwd: name=\"%s\" tmpname=\"%s\"\n",__LINE__,passwd->name,passwd->tmpname);
	if (stat(passwd->name,&buf)) {
		(void)fprintf(stderr,err_stat,image);
		perror(passwd->name);
		exit(2);
	}
	passwd->mode=buf.st_mode;
	passwd->uid=buf.st_uid;
	passwd->gid=buf.st_gid;
	passwd->stream=NULL;
	passwd->tmpstream=NULL;
	passwd->changed=FALSE;
	passwd->inplace=TRUE;
}



/*******************************************************************************
**
**  checkin_passwd - lock passwd file, read passwd list from disk
**
**  Returns if everything is fine, bails out otherwise.
*/
static void checkin_passwd()
{
	if (debug)
		(void)fprintf(stderr,"[%d] checkin_passwd()\n",__LINE__);

	/*
	**  Lock passwd file (by exclusively opening the temporary file)
	*/

	if (!(passwd->tmpstream=lock(passwd->tmpname,passwd->uid,passwd->gid,passwd->mode))) {
		if (errno==EEXIST) {
			(void)fprintf(stderr,err_busy,image);
			exit(1);
		}
		else {
			(void)fprintf(stderr,err_open,image);
			perror(passwd->tmpname);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
	}
	if (debug>1)
		(void)fprintf(stderr,"[%d] checkin_passwd: passwd->tmpstream=0x%08lx\n",__LINE__,(long)passwd->tmpstream);

	/*
	**  Open and read password file
	*/

	if (!(passwd->stream=fopen(passwd->name,"r+"))) {
		(void)fprintf(stderr,err_open,image);
		perror(passwd->name);
		checkout_passwd(passwd,write_passwd);
		exit(2);
	}
	if ((retval=input_passwd(passwd,read_passwd,rebuild))) {
		(void)fprintf(stderr,err_read,image);
			if (retval==-1)
				perror(passwd->name);
			else
				(void)fprintf(stderr,err_invalid,passwd->name);
		checkout_passwd(passwd,write_passwd);
		exit(2);
	}
}


/*******************************************************************************
**
**  checkout_shadow - flush shadow list to disk, remove lockfile
**
**  Returns if everything is fine, bails out with appropriate error message
**  otherwise.
*/
static void checkout_shadow()
{
	if (debug)
		(void)fprintf(stderr,"[%d] checkout_shadow()\n",__LINE__);

	if (shadow->changed) {
		if (shadow->inplace) {
			shadow->inplace=FALSE;	/* For subsequent checkout_shadow calls */
			if ((retval=inplace_passwd(shadow))) {
				(void)fprintf(stderr,err_write,image);
					if (retval==-1)
						perror(shadow->name);
					else
						(void)fprintf(stderr,err_invalid,shadow->name);
				checkout_shadow();
			}
			else
				if (fclose(shadow->stream)) {
					(void)fprintf(stderr,err_close,image);
					perror(shadow->name);
					checkout_shadow();
					exit(2);
				}
		}
		else {
			if (output_passwd(shadow,write_shadow)) {
				(void)fprintf(stderr,err_write,image);
				perror(shadow->tmpname);
				bail_out();
			}
			if (unlock(shadow->tmpstream)) {
				(void)fprintf(stderr,err_close,image);
				perror(shadow->tmpname);
				bail_out();
			}
			if (rename(shadow->tmpname,shadow->name)) {
				(void)fprintf(stderr,err_rename,image,shadow->tmpname);
				perror(shadow->name);
				bail_out();
			}
		}
	}
	else {
		if (debug)
			(void)fprintf(stderr,"[%d] checkout_shadow: no change\n",__LINE__);
		if (shadow->tmpstream)
			(void)unlock(shadow->tmpstream);
		if (shadow->tmpname)
			(void)remove(shadow->tmpname);
	}
}



/*******************************************************************************
**
**  init_shadow - Initialize shadow structure. Memory for the structure is
**	allocated via malloc.
**
**  Returns if everything is fine, bails out otherwise.
*/
static void init_shadow()
{
	struct stat	buf;

	if (debug)
		(void)fprintf(stderr,"[%d] init_shadow()\n",__LINE__);

	if (!(shadow=(passwd_t *)malloc(sizeof(passwd_t)))) {
		(void)fprintf(stderr,err_init,image);
		perror("shadow structure");
		exit(2);
	}

	shadow->entry=NULL;
	switch (systype) {
	case SHADOW:
	case SPASSWD:
	case SECURE:
	case SECURITY:
		shadow->name=altshadow?altshadow:passwd_name[systype];
		break;
	default:
		shadow->name=NULL;
		break;
	}
	shadow->stream=NULL;
	if (shadow->name) {
		switch (systype) {
		case SHADOW:
			if (!altshadow) {
				shadow->tmpname=PREFIX "/etc/stmp";
				break;
			}
			/*FALLTHROUGH*/
		default:
			if (!(shadow->tmpname=malloc(strlen(shadow->name)+5))) {
				(void)fprintf(stderr,err_init,image);
				perror("shadow temporary name");
				exit(2);
			}
			(void)strcpy(shadow->tmpname,shadow->name);
			(void)strcat(shadow->tmpname,".tmp");
			break;
		}
		if (debug>2)
			(void)fprintf(stderr,"[%d] init_shadow: name=\"%s\" tmpname=\"%s\"\n",__LINE__,shadow->name,shadow->tmpname);
		if (stat(shadow->name,&buf)) {
			(void)fprintf(stderr,err_stat,image);
			perror(shadow->name);
			exit(2);
		}
		shadow->mode=buf.st_mode;
		shadow->uid=buf.st_uid;
		shadow->gid=buf.st_gid;
	}
	else
		shadow->tmpname=NULL;
	shadow->tmpstream=NULL;
	shadow->changed=FALSE;
	shadow->inplace=TRUE;
	/* Initialized shadow structure is unchanged - if it is actually read, the
		 changed flag in rebuild mode is set in input_passwd */
}



/*******************************************************************************
**
**  checkin_shadow - lock shadow file, read shadow list from disk
**
**  Returns if everything is fine, bails out otherwise.
*/
static void checkin_shadow()
{
	if (debug)
		(void)fprintf(stderr,"[%d] checkin_shadow()\n",__LINE__);

	/*
	**  Lock shadow file (by exclusively opening the temporary file)
	*/

	if (!(shadow->tmpstream=lock(shadow->tmpname,shadow->uid,shadow->gid,shadow->mode))) {
		if (errno==EEXIST) {
			(void)fprintf(stderr,err_busy,image);
			checkout_passwd(passwd,write_passwd);
			exit(1);
		}
		else {
			(void)fprintf(stderr,err_open,image);
			perror(shadow->tmpname);
			checkout_passwd(shadow,write_shadow);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
	}

	/*
	**  Open and read shadow file
	*/

	if (!(shadow->stream=fopen(shadow->name,"r+"))) {
		(void)fprintf(stderr,err_open,image);
		perror(shadow->name);
		checkout_passwd(shadow,write_shadow);
		checkout_passwd(passwd,write_passwd);
		exit(2);
	}
	if ((retval=input_passwd(shadow,read_shadow,NULL))) {
		(void)fprintf(stderr,err_read,image);
			if (retval==-1)
				perror(shadow->name);
			else
				(void)fprintf(stderr,err_invalid,shadow->name);
		checkout_passwd(shadow,write_shadow);
		checkout_passwd(passwd,write_passwd);
		exit(2);
	}
}



/*******************************************************************************
**
**  checkout_tcb - flush tcb field to disk
**
**  Returns if everything is fine, bails out otherwise, writing current passwd
*/
static void checkout_tcb(name)
char	*name;
{
	char	*new;

	if (debug)
		(void)fprintf(stderr,"[%d] checkout_tcb(\"%s\")\n",__LINE__,name);

	if (tcb->changed) {
		if (changesystemdefault) {
			new=name;
			(void)sprintf(tcb->tmpname,"%s-t",tcb->name);
		}
		else {
			if (!(new=getval(tcb->field,pw_name))) {
				(void)fprintf(stderr,err_name,image,name);
				checkout_passwd(passwd,write_passwd);
				exit(2);
			}
			if (strcmp(name,new)) {
				(void)sprintf(tcb->tmpname,"%s/%c/%s",passwd_name[TCB],*new,new);
				if (rename(tcb->name,tcb->tmpname)) {
					(void)fprintf(stderr,err_rename,image,tcb->name);
					perror(tcb->tmpname);
					checkout_passwd(passwd,write_passwd);
					exit(2);
				}
				(void)strcpy(tcb->name,tcb->tmpname);
			}
			(void)sprintf(tcb->tmpname,"%s/%c/%s-t",passwd_name[TCB],*new,new);
		}
		if (!(tcb->tmpstream=lock(tcb->tmpname,0,0,0644))) {
			(void)fprintf(stderr,err_open,image);
			perror(tcb->tmpname);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if (write_tcb(new)) {
			(void)fprintf(stderr,err_write,image);
			perror(tcb->tmpname);
			(void)unlock(tcb->tmpstream);
			(void)remove(tcb->tmpname);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if (unlock(tcb->tmpstream)) {
			(void)fprintf(stderr,err_close,image);
			perror(tcb->tmpname);
			(void)remove(tcb->tmpname);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if (rename(tcb->tmpname,tcb->name)) {
			(void)fprintf(stderr,err_rename,image,tcb->tmpname);
			perror(tcb->name);
			(void)remove(tcb->tmpname);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		tcb->tmpname[0]='\0';
	}
	else
		if (debug)
			(void)fprintf(stderr,"[%d] checkout_tcb: no change\n",__LINE__);
}



/*******************************************************************************
**
**  init_tcb - Initialize TCB structure. Memory for the structure is allocated
**	via malloc.
**
**  Returns if everything is fine, bails out otherwise
*/
static void init_tcb(name)
char	*name;
{
	if (debug)
		(void)fprintf(stderr,"[%d] init_tcb(\"%s\")\n",__LINE__,name);

	if (!tcb)
		if (!(tcb=(tcb_t *)malloc(sizeof(tcb_t)))) {
			(void)fprintf(stderr,err_init,image);
			perror("tcb structure");
			exit(2);
		}

	tcb->field=NULL;
	if (name)
		(void)sprintf(tcb->name,"%s/%c/%s",passwd_name[TCB],*name,name);
	else
		tcb->name[0]='\0';
	tcb->stream=NULL;
	tcb->tmpname[0]='\0';
	tcb->tmpstream=NULL;
	tcb->changed=FALSE;
}



/*******************************************************************************
**
**  checkin_tcb - read tcb field from disk
**
**  Returns if everything is fine, bails out otherwise, writing current passwd
*/
static void checkin_tcb()
{
	if (debug)
		(void)fprintf(stderr,"[%d] checkin_tcb()\n",__LINE__);

	if (!tcb->field) {
		if (!(tcb->stream=fopen(tcb->name,"r"))) {
			(void)fprintf(stderr,err_open,image);
			perror(tcb->name);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if ((retval=read_tcb(&tcb->field,tcb->stream))) {
			(void)fprintf(stderr,err_read,image);
			if (retval==-1)
				perror(tcb->name);
			else
				(void)fprintf(stderr,err_invalid,tcb->name);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if (fclose(tcb->stream)) {
			(void)fprintf(stderr,err_close,image);
			perror(tcb->name);
			checkout_passwd(passwd,write_passwd);
			exit(2);
		}
		if (debug>4)
			dump(tcb->field);
	}
}



/*******************************************************************************
**
**  template_passwd - adds a new user template to the passwd list according to
**	the attributes list. Memory for the structure is allocated via malloc.
**	The current passwd pointer is set to the new structure.
**
**  Return value:
**	Upon successful completion, 0 is returned. If passwd information has
**	been insufficient, a value of -2 is returned. Otherwise, a value of -1
**	is returned and errno is set to indicate the error.
*/
static int template_passwd(name,list)
char	*name;
field_t	*list;
{
	if (debug)
		(void)fprintf(stderr,"[%d] template_passwd(\"%s\",0x%08lx)\n",__LINE__,name,(long)list);

	/*
	**  Check for sufficient information
	*/

	if (!get_list((list_t *)list,pw_name)
		||!get_list((list_t *)list,pw_passwd)
		||!get_list((list_t *)list,pw_uid)
		||!get_list((list_t *)list,pw_gid)
		||!get_list((list_t *)list,pw_gecos)
		||!get_list((list_t *)list,pw_dir)
		||!get_list((list_t *)list,pw_shell))
		return(-2);

	/*
	**  Generate template passwd entry
	*/

	if (!(passwd->current=(entry_t *)malloc(sizeof(entry_t))))
		return(-1);
	passwd->current->next=NULL;
	passwd->current->name=strdup(getval(list,pw_name));
	passwd->current->field=NULL;
	passwd->current->keep=MENTIONED;
	if (debug)
		(void)fprintf(stderr,"[%d] template_passwd: generated passwd template\n",__LINE__);

	add_list((list_t **)&passwd->entry,(list_t *)passwd->current);

	return(0);
}



/*******************************************************************************
**
**  template_shadow - adds a new user template to the shadow list according to
**	the attributes list. Memory for the structure is allocated via malloc.
**	The current shadow pointer is set to the new structure.
**
**  Return value:
**	Upon successful completion, 0 is returned. If shadow information has
**	been insufficient, a value of -2 is returned. Otherwise, a value of -1
**	is returned and errno is set to indicate the error.
*/
static int template_shadow(name,list)
char	*name;
field_t	*list;
{
	char		*value;

	if (debug)
		(void)fprintf(stderr,"[%d] template_shadow(\"%s\",0x%08lx)\n",__LINE__,name,(long)list);

	/*
	**  Check for sufficient information
	*/

	if (!get_list((list_t *)list,pw_name)
		||!get_list((list_t *)list,pw_passwd)
		||!get_list((list_t *)list,pw_uid))
		return(-2);

	/*
	**  Generate template shadow / database entry
	*/

	(void)sprintf(buffer,"%d",(int)time((time_t *)NULL));
	value=getval(list,pw_uid);
	switch (systype) {
	case SHADOW:
	case SPASSWD:
	case SECURE:
	case SECURITY:
		if (!(shadow->current=(entry_t *)malloc(sizeof(entry_t))))
			return(-1);
		shadow->current->next=NULL;
		shadow->current->name=strdup(getval(list,pw_name));
		shadow->current->field=NULL;
		check(add(&shadow->current->field,pw_exp,INT,zero,-1,TRUE));
		check(add(&shadow->current->field,pw_minchg,INT,zero,-1,TRUE));
		if (systype==SHADOW) {	/* This is a required field */
			check(add(&shadow->current->field,pw_succhg,INT,zero,-1,TRUE));
		}
		check(add(&shadow->current->field,pw_auditid,INT,value,-1,TRUE));
		check(add(&shadow->current->field,pw_auditflag,INT,TEMPLATE_AUDITFLAG,-1,TRUE));
		check(add(&shadow->current->field,pw_expire_warning,INT,zero,-1,TRUE));
		check(add(&shadow->current->field,pw_llogin,INT,zero,-1,TRUE));
		check(add(&shadow->current->field,pw_acct_expire,INT,zero,-1,TRUE));
		check(add(&shadow->current->field,pw_flags,STRING,"",-1,TRUE));
		if (debug)
			(void)fprintf(stderr,"[%d] template_shadow: generated shadow template\n",__LINE__);
		if (debug>4)
			dump((field_t *)shadow->current);
		add_list((list_t **)&shadow->entry,(list_t *)shadow->current);
		break;
	case TCB:
		/* No template for these, use system default **
		check(add(&tcb->field,pw_exp,INT,zero,-1,TRUE));
		check(add(&tcb->field,pw_minchg,INT,zero,-1,TRUE));
		** No template for these, use system default */
		/* No template for this, will be provided via change list **
		check(add(&tcb->field,pw_succhg,INT,buffer,-1,TRUE));
		** No template for this, will be provided via change list */
		check(add(&tcb->field,pw_auditid,INT,value,-1,TRUE));
		check(add(&tcb->field,pw_auditflag,INT,TEMPLATE_AUDITFLAG,-1,TRUE));
		check(add(&tcb->field,pw_suclog,INT,buffer,-1,TRUE));
		check(add(&tcb->field,pw_suctty,STRING,TEMPLATE_SUCTTY,-1,TRUE));
		check(add(&tcb->field,pw_lock,BOOL,(char *)FALSE,-1,TRUE));
		if (debug)
			(void)fprintf(stderr,"[%d] template_shadow: generated tcb template\n",__LINE__);
		if (debug>4)
			dump(tcb->field);
	default:
		break;
	}

	return(0);
}



/*******************************************************************************
**
**  chpasswd - change user attributes in passwd-like list. Changes are added
**	to a change list, memory is allocated in the add function.
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int chpasswd(list,name,type,value)
passwd_t	*list;
char	*name,*value;
type_t	type;
{
	field_t		*field;
	char	*oldvalue=NULL;
	long	offset;

	if (debug)
		(void)fprintf(stderr,"[%d] chpasswd(0x%08lx,\"%s\",%d,\"%s\")\n",__LINE__,(long)list,name,type,value);

	field=list->current->field;
	if (type==DELETE) {
		if (!del(&list->current->field,name)) {
			if (debug)
				(void)fprintf(stderr,"[%d] chpasswd: %s: deleted\n",__LINE__,name);
			list->changed=TRUE;
			list->inplace=FALSE;
		}
	}
	else if (!field||get(field,name,(type_t *)NULL,&oldvalue,&offset,(int *)NULL)||strcmp(value,oldvalue)) {
		if (debug)
			(void)fprintf(stderr,"[%d] chpasswd: %s: %s -> %s\n",__LINE__,name,null(oldvalue),null(value));
		check(add(&list->change,name,type,value,offset,TRUE));
		list->changed=TRUE;
		list->inplace&=oldvalue&&(strlen(value)==strlen(oldvalue));
		/*
		**  DIRTY HACK: if fields get changed that get recalculated and
		**  may change size upon output (in write_passwd:week_char or
		**  write_shadow:pwwritedays) inplace gets reset. A better
		**  solution would be to add either a size or an original_value
		**  field struct entry and to compare the recalculated value.
		*/
		switch (systype) {
		case UNIX:
			if (!strcmp(name,pw_exp))
				list->inplace=FALSE;
			break;
		case SHADOW:
			if (!strcmp(name,pw_succhg)
				||!strcmp(name,pw_minchg)
				||!strcmp(name,pw_exp)
				||!strcmp(name,pw_expire_warning)
				||!strcmp(name,pw_llogin)
				||!strcmp(name,pw_acct_expire))
				list->inplace=FALSE;
			break;
		default:
			break;
		}
		if (debug>1)
			(void)fprintf(stderr,"[%d] chpasswd: changed=%s inplace=%s\n",__LINE__,bool(list->changed),bool(list->inplace));
	}
	return(0);
}


/*******************************************************************************
**
**  chtcb - change user attributes in tcb field. Memory for new entries or
**	changed non-bool values is allocated via malloc.
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int chtcb(name,type,value)
char	*name,*value;
type_t	type;
{
	field_t	*field;

	if (debug)
		(void)fprintf(stderr,"[%d] chtcb(\"%s\",%d,\"%s\")\n",__LINE__,name,type,value);

	if (!tcb->field)
		checkin_tcb();
	if (type==DELETE) {
		if (!del(&tcb->field,name)) {
			if (debug)
				(void)fprintf(stderr,"[%d] chtcb: %s: deleted\n",__LINE__,name);
			tcb->changed=TRUE;
		}
	}
	else if ((field=(field_t *)get_list((list_t *)tcb->field,name))) {
		if (type==BOOL) {
			if (!value!=!field->value) {
				if (debug)
					(void)fprintf(stderr,"[%d] chtcb: %s: %s -> %s\n",__LINE__,name,bool(field->value),bool(value));
				field->value=(char *)!!value;
				tcb->changed=TRUE;
			}
		}
		else {
			if (strcmp(value,field->value)) {
				if (debug)
					(void)fprintf(stderr,"[%d] chtcb: %s: %s -> %s\n",__LINE__,name,null(field->value),value);
				free((void *)field->value);
				field->value=strdup(value);
				if (field->value==NULL)
					return(-1);
				tcb->changed=TRUE;
			}
		}
	}
	else {
		if (debug)
			(void)fprintf(stderr,"[%d] chtcb: %s: -> %s\n",__LINE__,name,value);
		check(add(&tcb->field,name,type,value,-1,TRUE));
		tcb->changed=TRUE;
	}
	return(0);
}



/***
void list_entry(list,text)
entry_t	*list;
char	*text;
{
	fprintf(stderr,"[%d] %s:",__LINE__,text);
	for (;list;list=list->next)
		fprintf(stderr," %s",list->name);
	fprintf(stderr,"\n");
}
***/



/*******************************************************************************
**
**  chuser - change user attributes according to the change list
**
**  Bails out if a fatal error occurs, writing current passwd.
**
**  Return value:
**	Upon successful completion, 0 is returned. If a passwd field is invalid
**	or incomplete or a user for an update operation does not exist, a value
**	of -2 is returned. Otherwise, a value of -1 is returned and errno is
**	set to indicate the error.
*/
static int chuser(name,list)
char	*name;
field_t	*list;
{
	if (debug)
		(void)fprintf(stderr,"[%d] chuser(\"%s\",0x%08lx)\n",__LINE__,name,(long)list);
	/***
	if (debug)
		list_entry(passwd->entry,"begin chuser");
	***/
	if (verbose) {
		(void)printf(v_run,name);
		(void)fflush(stdout);
	}

	/*
	**  Initialize change lists
	*/
	passwd->change=NULL;
	shadow->change=NULL;

	/*
	**  Generate database filenames
	*/

	if (systype==TCB) {
		init_tcb(name);
		if (debug)
			(void)fprintf(stderr,"[%d] tcb->name=\"%s\"\n",__LINE__,tcb->name);
		/* Should check for existance here and give err_notuser if not,
			but there's too much overhead stat'ing many files */
	}

	/*
	**  Get fields from passwd lists
	*/

	if (debug>2)
		(void)fprintf(stderr,"[%d] getting passwd entry\n",__LINE__);
	passwd->current=(entry_t *)get_list((list_t *)passwd->entry,name);
	switch (systype) {
	case SHADOW:
	case SPASSWD:
	case SECURE:
	case SECURITY:
		if (debug>2)
			(void)fprintf(stderr,"[%d] getting shadow entry\n",__LINE__);
		shadow->current=(entry_t *)get_list((list_t *)shadow->entry,name);
		break;
	default:
		if (debug>2)
			(void)fprintf(stderr,"[%d] no shadow entry (systype=%d)\n",__LINE__,systype);
		break;
	}

	/*
	**  Check prerequisites unless blind remove was specified
	*/

	if (operation!=REMOVEBLIND)
		if (passwd->current) {
			switch (systype) {
			case SHADOW:
			case SPASSWD:
			case SECURE:
			case SECURITY:
			case TCB:
				if (systype==TCB?access(tcb->name,F_OK):!shadow->current) {
					if (operation==ADDBLIND) {
						if ((retval=template_shadow(name,list))) {
							if (retval==-2) {
								if (verbose)
									(void)printf(v_info);
								(void)fprintf(stderr,err_info,image,name);
							}
							return(retval);
						}
					}
					else {
						if (verbose)
							(void)printf(v_nosuser);
						(void)fprintf(stderr,err_nosuser,image,name);
						return(-2);
					}
				}
				break;
			default:
				break;
			}
		}
		else if (operation==ADD||operation==ADDBLIND) {
			if ((retval=template_passwd(name,list))) {
				if (retval==-2) {
					if (verbose)
						(void)printf(v_info);
					(void)fprintf(stderr,err_info,image,name);
				}
				return(retval);
			}
			if ((retval=template_shadow(name,list))) {
				if (retval==-2) {
					if (verbose)
						(void)printf(v_info);
					(void)fprintf(stderr,err_info,image,name);
				}
				return(retval);
			}
		}
		else {
			if (verbose)
				(void)printf(v_nouser);
			(void)fprintf(stderr,err_nouser,image,name);
			return(-2);
		}

	/*
	**  If operation is remove, delete database and passwd fields
	*/

	if (operation==REMOVE||operation==REMOVEBLIND) {
		switch (systype) {
		case SHADOW:
		case SPASSWD:
		case SECURE:
		case SECURITY:
			if (shadow->current) {
				undef(&shadow->current->field);
				shadow->changed=TRUE;
				shadow->inplace=FALSE;
			}
			break;
		case TCB:
			if (remove(tcb->name)&&operation!=REMOVEBLIND) {
				(void)fprintf(stderr,err_remove,image);
				perror(tcb->name);
				checkout_passwd(passwd,write_passwd);
				exit(2);
			}
			break;
		default:
			break;
		}
		if (passwd->current) {
			undef(&passwd->current->field);
			passwd->changed=TRUE;
			passwd->inplace=FALSE;
		}
		if (verbose)
			(void)printf(v_del);
		return(0);
	}

	/*
	**  Potential list removals
	*/

	if (setpwsucchg==RESET) {
		switch (systype) {
		case UNIX:
			if (!del(&passwd->current->field,pw_succhg)) {
				passwd->changed=TRUE;
				passwd->inplace=FALSE;
			}
			break;
		case SHADOW:	/* This is a required field, will be set to zero */
			check(add(&list,pw_succhg,INT,zero,-1,TRUE));
			break;
		case SPASSWD:
		case SECURE:
		case SECURITY:
			if (!del(&shadow->current->field,pw_succhg)) {
				shadow->changed=TRUE;
				shadow->inplace=FALSE;
			}
			break;
		case TCB:
			if (!tcb->field)
				checkin_tcb();
			if (!del(&tcb->field,pw_succhg))
				tcb->changed=TRUE;
			break;
		case YP:
			break;
		}
	}

	if (resetpwunsuc&&systype==TCB) {
		if (!tcb->field)
			checkin_tcb();
		if (!del(&tcb->field,pw_numunsuclog))
			tcb->changed=TRUE;
	}

	/*
	**  Processing loop
	*/

	while (list) {
		if (debug)
			(void)fprintf(stderr,"[%d] chuser: %s=\"%s\"\n",__LINE__,list->name,list->value);
		/*
		**  Name and UID information is both in passwd and database
		**  files
		*/
		if (!strcmp(list->name,pw_name)
			||!strcmp(list->name,pw_uid)) {
			check(chpasswd(passwd,list->name,list->type,list->value));
			switch (systype) {
			case SHADOW:
			case SPASSWD:
			case SECURE:
			case SECURITY:
				check(chpasswd(shadow,list->name,list->type,list->value));
				break;
			case TCB:
				check(chtcb(list->name,list->type,list->value));
				break;
			default:
				break;
			}
		}
		/*
		**  If passwd information is in database files, don't add it to
		**  passwd, unless it's a DCE flag
		**  FIXME: Avoid adding a template shadow entry for DCE users
		**  (they get an empty flags entry now)
		*/
		else if (!strcmp(list->name,pw_passwd)) {
			switch (systype) {
			case SHADOW:
			case SPASSWD:
			case SECURE:
			case SECURITY:
				if (strcmp(list->value,"DCE")) {
					check(chpasswd(passwd,list->name,list->type,dummy_passwd[systype]));
					check(chpasswd(shadow,list->name,list->type,list->value));
				}
				else
					check(chpasswd(passwd,list->name,list->type,list->value));
				break;
			case TCB:
				if (strcmp(list->value,"DCE")) {
					check(chpasswd(passwd,list->name,list->type,dummy_passwd[systype]));
					check(chtcb(list->name,list->type,list->value));
				}
				else
					check(chpasswd(passwd,list->name,list->type,list->value));
				break;
			default:
				check(chpasswd(passwd,list->name,list->type,list->value));
				break;
			}
		}
		/*
		**  If passwd information is in database files, don't add
		**  expiration information to passwd. YP (NIS) has no expiration
		**  information at all. Expiration information must not be
		**  deleted from shadow passwd, replace with 0 in this case.
		*/
		else if (!strcmp(list->name,pw_exp)
			||!strcmp(list->name,pw_minchg)
			||!strcmp(list->name,pw_succhg)) {
			switch (systype) {
			case UNIX:
				check(chpasswd(passwd,list->name,list->type,list->value));
				break;
			case SHADOW:
				if (list->type==DELETE)
					check(chpasswd(shadow,list->name,INT,"0"));
				else
					check(chpasswd(shadow,list->name,list->type,list->value));
				break;
			case SPASSWD:
			case SECURE:
			case SECURITY:
				check(chpasswd(shadow,list->name,list->type,list->value));
				break;
			case TCB:
				check(chtcb(list->name,list->type,list->value));
				break;
			case YP:
				break;
			}
		}
		/*
		**  GID, Gecos, Dir and Shell are only in passwd
		*/
		else if (!strcmp(list->name,pw_gid)
			||!strcmp(list->name,pw_gecos)
			||!strcmp(list->name,pw_dir)
			||!strcmp(list->name,pw_shell)) {
			check(chpasswd(passwd,list->name,list->type,list->value));
		}
		/*
		**  All other information is in database files (?)
		*/
		else {
			switch (systype) {
			case SHADOW:
			case SPASSWD:
			case SECURE:
			case SECURITY:
				check(chpasswd(shadow,list->name,list->type,list->value));
				break;
			case TCB:
				check(chtcb(list->name,list->type,list->value));
			default:
				break;
			}
		}
		list=list->next;
	}

	/*
	**  Try to update individual database files
	*/

	if (systype==TCB&&tcb) {
		checkout_tcb(name);
		undef(&tcb->field);
	}

	/*
	**  Now add passwd/shadow changes to the lists
	*/

	if (passwd->change) {
		add_list((list_t **)&passwd->change,(list_t *)passwd->current->field);
		passwd->current->field=passwd->change;
	}
	if (shadow->change) {
		add_list((list_t **)&shadow->change,(list_t *)shadow->current->field);
		shadow->current->field=shadow->change;
	}
	passwd->current->keep=MENTIONED;

	if (verbose)
		(void)printf(v_ok);
	return(0);
}



/*******************************************************************************
**
**  chsysdef - change system default attributes according to the change list
**
**  Bails out if a fatal error occurs
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int chsysdef(list)
field_t	*list;
{
	if (debug)
		(void)fprintf(stderr,"[%d] chsysdef(0x%08lx)\n",__LINE__,(long)list);
	if (verbose) {
		(void)printf(v_run,"system default");
		(void)fflush(stdout);
	}

	/*
	**  Generate database filenames
	*/

	if (systype==TCB) {
		init_tcb((char *)NULL);
		(void)strcpy(tcb->name,sysdef_name[systype]);
		if (debug)
			(void)fprintf(stderr,"[%d] tcb->name=\"%s\"\n",__LINE__,tcb->name);
		/* Should check for existance here and give err_notuser if not,
			but there's too much overhead stat'ing many files */
	}

	/*
	**  Potential list removals
	*/

	if (setpwsucchg==RESET) {
		switch (systype) {
		case TCB:
			if (!tcb->field)
				checkin_tcb();
			if (!del(&tcb->field,pw_succhg))
				tcb->changed=TRUE;
			break;
		default:	/* Should not occur */
			(void)fprintf(stderr,err_error,image);
			(void)fprintf(stderr,"chsysdef: invalid systype (%d)\n",systype);
			exit(2);
		}
	}

	if (resetpwunsuc&&systype==TCB) {
		if (!tcb->field)
			checkin_tcb();
		if (!del(&tcb->field,pw_numunsuclog))
			tcb->changed=TRUE;
	}

	/*
	**  Processing loop
	*/

	while (list) {
		if (debug)
			(void)fprintf(stderr,"[%d] chsysdef: %s=\"%s\"\n",__LINE__,list->name,list->value);
		/*
		**  GID, Gecos, Dir and Shell are only in passwd
		*/
		if (strcmp(list->name,pw_gid)
			&&strcmp(list->name,pw_gecos)
			&&strcmp(list->name,pw_dir)
			&&strcmp(list->name,pw_shell)) {
			switch (systype) {
			case TCB:
				check(chtcb(list->name,list->type,list->value));
				break;
			default:
				(void)fprintf(stderr,err_error,image);
				(void)fprintf(stderr,"chsysdef: invalid systype (%d)\n",systype);
				exit(2);
			}
		}
		list=list->next;
	}

	/*
	**  Try to update individual database files
	*/

	if (systype==TCB&&tcb) {
		checkout_tcb("default");
		undef(&tcb->field);
	}

	if (verbose)
		(void)printf(v_ok);
	return(0);
}



/*******************************************************************************
**
**  process_passwd - read a passwd style input file and process each field
**
**  Return value:
**	Upon successful completion, 0 is returned. If a passwd field is invalid
**	or incomplete or a user for an update operation does not exist, a value
**	of -2 is returned. Otherwise, a value of -1 is returned and errno is
**	set to indicate the error.
*/
static int process_passwd(src)
FILE	*src;
{
	field_t	*list=NULL;
	char	*value;

	if (debug)
		(void)fprintf(stderr,"[%d] process_passwd(0x%08lx)\n",__LINE__,(long)src);

	for (;;) {
		if ((retval=read_passwd(&list,src))) {
			if (retval==-1)
				return(retval);
			else
				break;
		}
		if (setpwsucchg==SET) {
			(void)sprintf(buffer,"%d",(int)time((time_t *)NULL));
			check(add(&list,pw_succhg,INT,buffer,-1,TRUE));
		}
		if (setauditid&&(value=getval(list,pw_uid))) {
			check(add(&list,pw_auditid,INT,value,-1,TRUE));
		}
		check(!(value=getval(list,pw_name)));
		switch (chuser(value,list)) {
		case -1:
			return(-1);
		case -2:
			if (!ignore)
				return(-2);
		}
		undef(&list);
	}
	return(0);
}



/*******************************************************************************
**
**  keep_changed - propagate keep information to changed flag
*/
static void keep_changed(dst)
passwd_t	*dst;
{
	entry_t	*entry;

	if (debug)
		(void)fprintf(stderr,"[%d] keep_changed(0x%08lx)\n",__LINE__,(long)dst);

	/* Check for any unkept entries - if so, must be checked out */
	if (!dst->changed) {
		entry=dst->entry;
		while (entry) {
			if (!entry->keep) {
				dst->changed=TRUE;
				dst->inplace=FALSE;
				break;
			}
			entry=entry->next;
		}
	}
}



/*******************************************************************************
**
**  propagate_keep - propagate keep information 
*/
static void propagate_keep(src,dst)
passwd_t	*src,*dst;
{
	entry_t	*entry;
	char	*value;

	if (debug)
		(void)fprintf(stderr,"[%d] propagate_keep(0x%08lx,0x%08lx)\n",__LINE__,(long)src,(long)dst);

	entry=src->entry;
	while (entry) {
		switch (entry->keep) {
		case DONT:
			src->changed=TRUE;
			src->inplace=FALSE;
			break;
		case UID:
			((entry_t *)get_list((list_t *)dst->entry,entry->name))->keep=UID;
			break;
		default:
			break;
		}
		entry=entry->next;
	}

	keep_changed(dst);
}



/*******************************************************************************
**
**  tcb_keep - sync tcb database with keep information 
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
static int tcb_keep(src)
passwd_t	*src;
{
	DIR		*auth,*subdir;
	struct dirent	*authp,*subdirp;
	char		tmpname[MAXPATHLEN],*fpos;

	if (debug)
		(void)fprintf(stderr,"[%d] tcb_keep(0x%08lx)\n",__LINE__,(long)src);

	strcpy(tmpname,passwd_name[TCB]);
	fpos=strchr(tmpname,'\0');
	*fpos++='/';

	if (!(auth=opendir(passwd_name[TCB]))) {
		(void)fprintf(stderr,err_dir,image);
		perror(passwd_name[TCB]);
		return(-1);
	}
	while (authp=readdir(auth)) {
		if (authp->d_name[0]!='.'&&authp->d_name[1]=='\0') {
			strcpy(fpos,authp->d_name);
			if (!(subdir=opendir(tmpname))) {
				(void)fprintf(stderr,err_dir,image);
				perror(tmpname);
				return(-1);
			}
			fpos[1]='/';
			while (subdirp=readdir(subdir)) {
				if (strcmp(subdirp->d_name,".")&&strcmp(subdirp->d_name,"..")) {
					if (!((entry_t *)get_list((list_t *)src->entry,subdirp->d_name))->keep) {
						strcpy(fpos+2,subdirp->d_name);
						if (debug)
							(void)fprintf(stderr,"[%d] tcb_keep: Removing %s\n",__LINE__,tmpname);
						if (remove(tmpname)) {
							(void)fprintf(stderr,err_remove,image);
							perror(tmpname);
							return(-1);
						}
					}
					else {
						if (debug)
							(void)fprintf(stderr,"[%d] tcb_keep: Keeping %s\n",__LINE__,subdirp->d_name);
					}
				}
			}
			(void)closedir(subdir);
		}
	}
	(void)closedir(auth);
	return(0);
}



/*******************************************************************************
**
**  termination_handler - Signal handler, cleans up and exits
*/
static void termination_handler(sig,code,scp)
int	sig,code,scp;
{
	(void)signal(SIGHUP,SIG_IGN);
	(void)signal(SIGINT,SIG_IGN);
	(void)signal(SIGQUIT,SIG_IGN);
	(void)signal(SIGPIPE,SIG_IGN);
	(void)signal(SIGALRM,SIG_IGN);
	(void)signal(SIGTERM,SIG_IGN);
	(void)signal(SIGPWR,SIG_IGN);

	(void)fprintf(stderr,err_termination);
	if (debug)
		(void)fprintf(stderr,err_sig,signals[sig-1],sig,code,scp);
	if (debug>2)
		(void)fprintf(stderr,"[%d] termination_handler: ignoring signals %d, %d, %d, %d, %d, %d and %d\n",__LINE__,SIGHUP,SIGINT,SIGQUIT,SIGPIPE,SIGALRM,SIGTERM,SIGPWR);
	if (*tcb->tmpname)
		(void)remove(tcb->tmpname);
	checkout_passwd(shadow,write_shadow);
	checkout_passwd(passwd,write_passwd);
	exit(1);
}



/*******************************************************************************
**
**  fatal_handler - Signal handler, exits after multiple errors, assuming incore
**	data is corrupt and cannot be saved anymore
*/
static void fatal_handler(sig,code,scp)
int	sig,code,scp;
{
	(void)signal(SIGILL,SIG_IGN);
	(void)signal(SIGFPE,SIG_IGN);
	(void)signal(SIGBUS,SIG_IGN);
	(void)signal(SIGSEGV,SIG_IGN);

	(void)fprintf(stderr,err_error2);
	if (debug)
		(void)fprintf(stderr,err_sig,signals[sig-1],sig,code,scp);
	if (debug>2)
		(void)fprintf(stderr,"[%d] fatal_handler: ignoring signals %d, %d, %d and %d\n",__LINE__,SIGILL,SIGFPE,SIGBUS,SIGSEGV);
	exit(3);
}



/*******************************************************************************
**
**  error_handler - Signal handler, cleans up and exits after error, assuming
**	incore data is corrupt
*/
static void error_handler(sig,code,scp)
int	sig,code,scp;
{
	(void)signal(SIGILL,fatal_handler);
	(void)signal(SIGFPE,fatal_handler);
	(void)signal(SIGBUS,fatal_handler);
	(void)signal(SIGSEGV,fatal_handler);

	(void)fprintf(stderr,err_error);
	if (debug)
		(void)fprintf(stderr,err_sig,signals[sig-1],sig,code,scp);
	(void)fprintf(stderr,err_fatal);
	if (debug>2)
		(void)fprintf(stderr,"[%d] error_handler: setting fatal_handler for signals %d, %d, %d and %d\n",__LINE__,SIGILL,SIGFPE,SIGBUS,SIGSEGV);
	if (tcb&&*tcb->tmpname)
		(void)remove(tcb->tmpname);
	if (shadow->tmpstream)
		(void)unlock(shadow->tmpstream);
	if (shadow->tmpname)
		(void)remove(shadow->tmpname);
	if (passwd->tmpstream) {
		(void)unlock(passwd->tmpstream);
		(void)remove(passwd->tmpname);
	}
	exit(3);
}



/*******************************************************************************
**
**  usage - show usage
*/
static void usage()
{
	(void)fprintf(stderr,"Usage: %s [options] input-mode\n",image);
	(void)fprintf(stderr,"or:    %s [options] attribute[=value|#value|@] ... name\n",image);
	(void)fprintf(stderr,"or:    %s [options] option value ... name ...\n",image);
	(void)fprintf(stderr,"Options:\n");
	(void)fprintf(stderr,"-A         Add user if it doesn't exist\n");
	(void)fprintf(stderr,"-AA        Add user even if it exists only partially\n");
	(void)fprintf(stderr,"-R         Remove user\n");
	(void)fprintf(stderr,"-RR        Remove user even if it exists only partially\n");
	(void)fprintf(stderr,"-N uidlist Rebuild mode\n           remove all users not mentioned and with a uid not in the list given\n           (uidlist is a comma-separated list of uids or uid-ranges)\n");
	(void)fprintf(stderr,"-C         Reset password change\n");
	(void)fprintf(stderr,"-CC        Set password change time equal to now\n");
	(void)fprintf(stderr,"-U         Reset unsuccessful logins\n");
	(void)fprintf(stderr,"-I         Set auditid equal to userid\n");
	(void)fprintf(stderr,"-S         Change system default entry (incompatible with -P)\n");
	(void)fprintf(stderr,"-Y passwd  Use YP (NIS) passwd file\n");
	(void)fprintf(stderr,"-F passwd  Use alternate passwd file\n           (but normal shadow/database unless -T specified)\n");
	(void)fprintf(stderr,"-T shadow  Use alternate shadow file/database directory\n           (but normal passwd unless -F specified)\n");
	(void)fprintf(stderr,"Valid input modes:\n");
	(void)fprintf(stderr,"-P         passwd mode, read passwd(4) lines from stdin\n");
	(void)fprintf(stderr,"Valid attributes:\n");
	(void)fprintf(stderr,"  HP-UX style:\n");
	(void)fprintf(stderr,"    u_name u_id u_pwd u_owner u_bootauth u_auditid u_auditflag u_minchg\n    u_maxlen u_exp u_life u_succhg u_unsucchg u_llogin u_pw_expire_warning\n    u_acct_expire u_pswduser u_pickpw u_genpwd u_restrict u_nullpw u_pwchanger\n    u_pw_admin_num u_genchars u_genletters u_tod u_suclog u_suctty u_unsuclog\n    u_unsuctty u_numunsuclog u_maxtries u_lock\n");
	(void)fprintf(stderr,"  AIX style:\n");
	(void)fprintf(stderr,"    admin admgroups auditclasses auth1 auth2 core cpu daemon data expires fsize\n    gecos groups home id lastupdate login password pgrp rlogin rss shell stack\n    su sugroups sysenv tpath ttys umask usrenv\n");
	(void)fprintf(stderr,"Valid options:\n");
	(void)fprintf(stderr,"-n  login name\n");
	(void)fprintf(stderr,"-p  encrypted password\n");
	(void)fprintf(stderr,"-u  numerical user ID\n");
	(void)fprintf(stderr,"-g  numerical group ID\n");
	(void)fprintf(stderr,"-c  full gecos field\n");
	(void)fprintf(stderr,"-d  initial working directory \n");
	(void)fprintf(stderr,"-s  program to use as shell\n");
	/*
	(void)fprintf(stderr,"-f  full name in gecos field\n");
	(void)fprintf(stderr,"-o  office location in gecos field\n");
	(void)fprintf(stderr,"-e  extension in gecos field\n");
	(void)fprintf(stderr,"-h  home phone in gecos field\n");
	*/
	(void)fprintf(stderr,"Exit codes: 1=temporary error, 2=permanent error, 3=fatal error\n");
	exit(2);
}



/*******************************************************************************
**
**  main - parse options, call processing routines
**
**  Return value:
**	Upon successful completion, 0 is returned. Otherwise, a value of -1 is
**	returned and errno is set to indicate the error.
*/
int main(argc,argv)
int	argc;
char	*argv[];
{
	int		c;
	extern char	*optarg;
	extern int	optind;
	char		*name;
	type_t		type;
	char		*value;

	if ((image=strrchr(argv[0],'/')))
		image++;
	else
		image=argv[0];

	/* BEGJKLMOQWXZabijklmqrtwxyz */
	while ((c=getopt(argc,argv,"DARCUISF:Y:T:PN:n:p:u:g:c:d:s:f:o:e:h:vVH?"))!=EOF)
		switch ((char)c) {
		case 'D':
			debug++;
			break;
		case 'A':
			operation=operation==ADD?ADDBLIND:ADD;
			break;
		case 'R':
			operation=operation==REMOVE?REMOVEBLIND:REMOVE;
			break;
		case 'N':
			rebuild=uidlist(optarg);
			break;
		case 'C':
			setpwsucchg=setpwsucchg==RESET?SET:RESET;
			break;
		case 'U':
			resetpwunsuc=TRUE;
			break;
		case 'I':
			setauditid=TRUE;
			break;
		case 'S':
			changesystemdefault=TRUE;
			break;
		case 'F':
			altpasswd=optarg;
			break;
		case 'Y':
			yppasswd=optarg;
			break;
		case 'T':
			altshadow=optarg;
			break;
		case 'P':
			mode=PASSWD;
			break;
		case 'n':
			check(add(&cmdopts,pw_name,STRING,optarg,-1,TRUE));
			break;
		case 'p':
			check(add(&cmdopts,pw_passwd,STRING,optarg,-1,TRUE));
			break;
		case 'u':
			check(add(&cmdopts,pw_uid,INT,optarg,-1,TRUE));
			break;
		case 'g':
			check(add(&cmdopts,pw_gid,INT,optarg,-1,TRUE));
			break;
		case 'c':
			check(add(&cmdopts,pw_gecos,STRING,optarg,-1,TRUE));
			break;
		case 'd':
			check(add(&cmdopts,pw_dir,STRING,optarg,-1,TRUE));
			break;
		case 's':
			check(add(&cmdopts,pw_shell,STRING,optarg,-1,TRUE));
			break;
		case 'f':
			check(add(&cmdopts,"u_fullname",STRING,optarg,-1,TRUE));
			break;
		case 'o':
			check(add(&cmdopts,"u_office",STRING,optarg,-1,TRUE));
			break;
		case 'e':
			check(add(&cmdopts,"u_extension",STRING,optarg,-1,TRUE));
			break;
		case 'h':
			check(add(&cmdopts,"u_homephone",STRING,optarg,-1,TRUE));
			break;
		case 'v':
			verbose++;
			break;
		case 'V':
			(void)fprintf(stderr,"%s\n",sccsid+4);
			(void)fprintf(stderr,"%s\n",copyright+4);
			exit(0);
		case 'H':
			(void)fprintf(stderr,"%s\n",sccsid+4);
			(void)fprintf(stderr,"%s\n",copyright+4);
		case '?':
			usage();
		}

	/*
	**  Check mode, parse attributes
	*/

	if ((optind<argc)==(mode==PASSWD)	/* Can't process stdin and args */
		||(changesystemdefault&&(mode==PASSWD||rebuild))	/* chsysdef can't read from stdin and can't rebuild */
		||((setpwsucchg||resetpwunsuc||setauditid||changesystemdefault||rebuild)&&(operation==REMOVE||operation==REMOVEBLIND))	/* Won't set fields if entry will be removed, can't remove system default, can't rebuild and remove */
		||(altpasswd&&yppasswd))	/* YP-Passwd-File already is an alternate file */
		usage();

	if (cmdopts)
		mode=CMDOPTS;
	if (debug>2)
		(void)fprintf(stderr,"[%d] mode=%d\n",__LINE__,mode);

	if (mode==ATTRIBUTES&&operation!=REMOVE&&operation!=REMOVEBLIND)
		for (;optind+(changesystemdefault?0:1)<argc;optind++) {
			if (parse_tcb(argv[optind],&name,&type,&value)) {
				(void)fprintf(stderr,err_parse,image,argv[optind]);
				return(-1);
			}
			check(add(&cmdopts,attr_name(name),type,value,-1,TRUE));
		}

	/*
	**  Determine system type, initialize data structures
	*/

	systype=check_system();
	if (verbose||debug) {
		(void)fprintf(stderr,v_system);
		(void)fprintf(stderr,system_d[systype],yppasswd);
		if (altpasswd||altshadow)
			(void)fprintf(stderr,", using");
		if (altpasswd)
			(void)fprintf(stderr," %s",altpasswd);
		if (altpasswd&&altshadow)
			(void)fprintf(stderr," and");
		if (altshadow)
			(void)fprintf(stderr," shadow %s",altshadow);
		(void)fprintf(stderr,"\n");
	}
	if (changesystemdefault&&!sysdef_name[systype]) {
		(void)fprintf(stderr,err_nosysdef,image);
		return(-1);
	}

	init_passwd();
	init_shadow();
	if (systype==TCB)
		init_tcb((char *)NULL);

	(void)umask((mode_t)0);

	/*
	**  Setup signal handler
	*/

	if (debug>2)
		(void)fprintf(stderr,"[%d] setting termination_handler for signals %d, %d, %d, %d, %d, %d and %d\n",__LINE__,SIGHUP,SIGINT,SIGQUIT,SIGPIPE,SIGALRM,SIGTERM,SIGPWR);
	(void)signal(SIGHUP,termination_handler);
	(void)signal(SIGINT,termination_handler);
	(void)signal(SIGQUIT,termination_handler);
	(void)signal(SIGPIPE,termination_handler);
	(void)signal(SIGALRM,termination_handler);
	(void)signal(SIGTERM,termination_handler);
	(void)signal(SIGPWR,termination_handler);

	if (debug>2)
		(void)fprintf(stderr,"[%d] setting error_handler for signals %d, %d, %d and %d\n",__LINE__,SIGILL,SIGFPE,SIGBUS,SIGSEGV);
	(void)signal(SIGILL,error_handler);
	(void)signal(SIGFPE,error_handler);
	(void)signal(SIGBUS,error_handler);
	(void)signal(SIGSEGV,error_handler);

	/*
	**  Read password files
	*/

	if (!changesystemdefault) {
		checkin_passwd();
		switch (systype) {
		case SHADOW:
		case SPASSWD:
		case SECURE:
		case SECURITY:
			checkin_shadow();
			break;
		default:
			break;
		}
	}

	/*
	**  Process input data
	*/

	switch (mode) {
	case ATTRIBUTES:
	case CMDOPTS:
		if (setpwsucchg==SET) {
			(void)sprintf(buffer,"%d",(int)time((time_t *)NULL));
			check(add(&cmdopts,pw_succhg,INT,buffer,-1,TRUE));
		}
		if (setauditid&&(value=getval(cmdopts,pw_uid))) {
			check(add(&cmdopts,pw_auditid,INT,value,-1,TRUE));
		}
		if (changesystemdefault)
			switch(chsysdef(cmdopts)) {
			case -1:
				(void)fprintf(stderr,err_handle,image);
				perror("");
				exit(2);
			default:
				break;
			}
		else {
			for (;optind<argc;optind++)
				switch (chuser(argv[optind],cmdopts)) {
				case -1:
					(void)fprintf(stderr,err_handle,image);
					perror("");
					checkout_passwd(shadow,write_shadow);
					checkout_passwd(passwd,write_passwd);
					exit(2);
				case -2:
					if (!ignore) {
						checkout_passwd(shadow,write_shadow);
						checkout_passwd(passwd,write_passwd);
						exit(2);
					}
					break;
				default:
					break;
				}
		}
		break;
	case PASSWD:
		switch (process_passwd(stdin)) {
			case -1:
				(void)fprintf(stderr,err_read,image);
				perror("<stdin>");
			case -2:
				checkout_passwd(shadow,write_shadow);
				checkout_passwd(passwd,write_passwd);
				exit(2);
			default:
				break;
			}
		break;
	default:	/* Should not occur */
		(void)fprintf(stderr,err_error,image);
		(void)fprintf(stderr,"main: invalid mode (%d)\n",mode);
		return(-2);
	}

	/*
	**  Process keep information
	*/

	if (rebuild) {
		switch (systype) {
		case SHADOW:
		case SPASSWD:
		case SECURE:
		case SECURITY:
			propagate_keep(passwd,shadow);
			break;
		case TCB:
			keep_changed(passwd);
			if (tcb_keep(passwd)) {
				checkout_passwd(shadow,write_shadow);
				checkout_passwd(passwd,write_passwd);
				exit(2);
			}
			break;
		default:
			break;
		}
		/* Security feature: don't evaluate keep information in
		   checkout unless all unkept entries have been verified */
		rebuilt=TRUE;
	}

	/*
	**  Write password files
	*/

	checkout_passwd(shadow,write_shadow);
	checkout_passwd(passwd,write_passwd);

	return(0);
}

