[alt.sources] Shadow Login Suite, patch 4

jfh@rpp386.cactus.org (John F Haugh II) (06/11/91)

[ Either this didn't make it off this site or I'm in really bad shape. ]

This is patch #4 for the current beta test release of the shadow login
suite.  It adds three new commands, useradd, userdel and usermod.  The
documentation that I promised in patch #3 hasn't been written.  I've been
busy planning a wedding (mine) and I've not had the time.  You may refer
either to the source code or your nearest SVR4 documentation for the
behavior of these commands.

It is known that there are bugs in these three new commands.  Since they
are the newest code in the entire suite, that's to be expected.  Have
patience and I'll get it fixed.  Some of the bugs have been fixed already,
and other fixes are due out in patch #6 (patch #5 is currently being
verified by Chip).

Also added was a "patchlevel.h" file.  So many of you asked for it, so
I've provided it ...
--
*** /dev/null	Fri Jun  7 11:02:59 1991
--- patchlevel.h	Fri Jun  7 11:13:28 1991
***************
*** 0 ****
--- 1,13 ----
+ /*
+  * Copyright 1991, John F. Haugh II
+  * All rights reserved.
+  *
+  * Permission is granted to copy and create derivative works for any
+  * non-commercial purpose, provided this copyright notice is preserved
+  * in all copies of source code, or included in human readable form
+  * and conspicuously displayed on all copies of object code or
+  * distribution media.
+  */
+ 
+ #define	RELEASE		3
+ #define	PATCHLEVEL	4
*** rel3/config.h	Thu Jun  6 09:35:02 1991
--- config.h	Fri Jun  7 11:11:26 1991
***************
*** 12,18 ****
  /*
   * Configuration file for login.
   *
!  *	@(#)config.h	3.7	08:57:09	5/30/91
   */
  
  /*
--- 12,18 ----
  /*
   * Configuration file for login.
   *
!  *	@(#)config.h	3.8	11:11:17	6/7/91
   */
  
  /*
***************
*** 31,40 ****
--- 31,44 ----
   * for getpwuid() and getpwnam().  This provides compatibility for
   * privileged applications which are shadow-ignorant.  YOU ARE
   * ENCOURAGED TO NOT USE THIS OPTION UNLESS ABSOLUTELY NECESSARY.
+  *
+  * Define SHADOWGRP to user shadowed group files.  This feature adds
+  * the concept of a group administrator.
   */
  
  #define	SHADOWPWD
  #undef	AUTOSHADOW
+ #define	SHADOWGRP
  
  /*
   * Define DOUBLESIZE to use 16 character passwords
***************
*** 270,275 ****
--- 274,282 ----
  #define	GETPWENT	/* Define if you want my GETPWENT(3) routines */
  #define	GETGRENT	/* Define if you want my GETGRENT(3) routines */
  #define	NEED_AL64	/* Define if library does not include a64l() */
+ #define	NEED_MKDIR	/* Define if system does not have mkdir() */
+ #define	NEED_RMDIR	/* Define if system does not have rmdir() */
+ #define	NEED_RENAME	/* Define if system does not have rename() */
  #undef	NO_STRSTR	/* Define if library does not include strstr() */
  
  /*
*** rel3/Makefile	Thu Jun  6 09:35:02 1991
--- Makefile	Fri Jun  7 11:12:38 1991
***************
*** 8,16 ****
  # and conspicuously displayed on all copies of object code or
  # distribution media.
  #
! #	@(#)Makefile	3.10	09:05:50  - Shadow password system
  #
! #	@(#)Makefile	3.10	09:05:50	5/30/91
  #
  SHELL = /bin/sh
  
--- 8,16 ----
  # and conspicuously displayed on all copies of object code or
  # distribution media.
  #
! #	@(#)Makefile	3.11	11:11:42  - Shadow password system
  #
! #	@(#)Makefile	3.11	11:11:42	6/7/91
  #
  SHELL = /bin/sh
  
***************
*** 127,135 ****
  	utmp.c valid.c port.c newgrp.c gpmain.c grent.c mkpasswd.c pwpack.c \
  	chfn.c chsh.c chage.c rad64.c encrypt.c chpasswd.c shadowio.c pwio.c \
  	newusers.c groupio.c fields.c pwdbm.c grpack.c grdbm.c sppack.c \
! 	spdbm.c dpmain.c gshadow.c gsdbm.c gspack.c sgroupio.c
  
! FILES1 = README newgrp.c Makefile config.h pwunconv.c obscure.c age.c id.c
  
  FILES2 = passwd.c port.c lmain.c mkpasswd.c sulogin.c pwpack.c dialup.c \
  	sulog.c getpass.c
--- 127,137 ----
  	utmp.c valid.c port.c newgrp.c gpmain.c grent.c mkpasswd.c pwpack.c \
  	chfn.c chsh.c chage.c rad64.c encrypt.c chpasswd.c shadowio.c pwio.c \
  	newusers.c groupio.c fields.c pwdbm.c grpack.c grdbm.c sppack.c \
! 	spdbm.c dpmain.c gshadow.c gsdbm.c gspack.c sgroupio.c useradd.c \
! 	userdel.c patchlevel.h usermod.c
  
! FILES1 = README newgrp.c Makefile config.h pwunconv.c obscure.c age.c id.c \
! 	patchlevel.h
  
  FILES2 = passwd.c port.c lmain.c mkpasswd.c sulogin.c pwpack.c dialup.c \
  	sulog.c getpass.c
***************
*** 147,152 ****
--- 149,156 ----
  
  FILES7 = groupio.c shadowio.c sgroupio.c
  
+ FILES8 = useradd.c userdel.c usermod.c
+ 
  MAN_1 = chage.1 chfn.1 chsh.1 login.1 passwd.1 su.1
  MAN_3 = shadow.3
  MAN_4 = faillog.4 passwd.4 porttime.4 shadow.4
***************
*** 155,161 ****
  DOCS = $(MAN_1) $(MAN_3) $(MAN_4) $(MAN_8)
  
  BINS = su login pwconv pwunconv passwd sulogin faillog newgrp gpasswd \
! 	mkpasswd chfn chsh chage chpasswd newusers dpasswd id
  
  all:	$(BINS) $(DOCS)
  
--- 159,166 ----
  DOCS = $(MAN_1) $(MAN_3) $(MAN_4) $(MAN_8)
  
  BINS = su login pwconv pwunconv passwd sulogin faillog newgrp gpasswd \
! 	mkpasswd chfn chsh chage chpasswd newusers dpasswd id useradd \
! 	userdel usermod
  
  all:	$(BINS) $(DOCS)
  
***************
*** 214,220 ****
  
  lint:	su.lint login.lint pwconv.lint pwunconv.lint passwd.lint sulogin.lint \
  	faillog.lint newgrp.lint gpasswd.lint mkpasswd.lint chfn.lint \
! 	chsh.lint chage.lint dpasswd.lint $(ALLSRCS:.c=.L)
  
  tags:	$(ALLSRCS)
  	$(TAGS) $(ALLSRCS)
--- 219,226 ----
  
  lint:	su.lint login.lint pwconv.lint pwunconv.lint passwd.lint sulogin.lint \
  	faillog.lint newgrp.lint gpasswd.lint mkpasswd.lint chfn.lint \
! 	chsh.lint chage.lint dpasswd.lint id.lint useradd.lint userdel.lint \
! 	usermod.lint $(ALLSRCS:.c=.L)
  
  tags:	$(ALLSRCS)
  	$(TAGS) $(ALLSRCS)
***************
*** 327,332 ****
--- 333,356 ----
  id.lint: id.c
  	$(LINT) $(LINTFLAGS) id.c > id.lint
  
+ useradd: useradd.o libshadow.a
+ 	$(CC) -o useradd $(LDFLAGS) useradd.o libshadow.a $(LIBS)
+ 
+ useradd.lint: useradd.c
+ 	$(LINT) $(LINTFLAGS) useradd.c > useradd.lint
+ 
+ userdel: userdel.o libshadow.a
+ 	$(CC) -o userdel $(LDFLAGS) userdel.o libshadow.a $(LIBS)
+ 
+ userdel.lint: userdel.c
+ 	$(LINT) $(LINTFLAGS) userdel.c > userdel.lint
+ 
+ usermod: usermod.o libshadow.a
+ 	$(CC) -o usermod $(LDFLAGS) usermod.o libshadow.a $(LIBS)
+ 
+ usermod.lint: usermod.c
+ 	$(LINT) $(LINTFLAGS) usermod.c > usermod.lint
+ 
  sulog.o: config.h
  
  susetup.c: setup.c
***************
*** 395,400 ****
--- 419,426 ----
  id.o: pwd.h
  newusers.o: config.h shadow.h pwd.h
  dpmain.o: dialup.h
+ useradd.o: config.h shadow.h pwd.h
+ userdel.o: config.h shadow.h pwd.h
  
  clean:
  	-rm -f *.o a.out core npasswd nshadow *.pag *.dir
***************
*** 410,416 ****
  	done
  
  shar:	login.sh.1 login.sh.2 login.sh.3 login.sh.4 login.sh.5 login.sh.6 \
! 	login.sh.7 login.sh.8
  
  login.sh.1: $(FILES1) Makefile
  	shar -a $(FILES1) > login.sh.1
--- 436,442 ----
  	done
  
  shar:	login.sh.1 login.sh.2 login.sh.3 login.sh.4 login.sh.5 login.sh.6 \
! 	login.sh.7 login.sh.8 login.sh.9
  
  login.sh.1: $(FILES1) Makefile
  	shar -a $(FILES1) > login.sh.1
***************
*** 433,437 ****
  login.sh.7: $(FILES7) Makefile
  	shar -a $(FILES7) > login.sh.7
  
! login.sh.8: $(DOCS) Makefile
! 	shar -a $(DOCS) > login.sh.8
--- 459,466 ----
  login.sh.7: $(FILES7) Makefile
  	shar -a $(FILES7) > login.sh.7
  
! login.sh.8: $(FILES8) Makefile
! 	shar -a $(FILES8) > login.sh.8
! 
! login.sh.9: $(DOCS) Makefile
! 	shar -a $(DOCS) > login.sh.9
*** /dev/null	Fri Jun  7 11:02:59 1991
--- useradd.c	Fri Jun  7 11:10:07 1991
***************
*** 0 ****
--- 1,1245 ----
+ /*
+  * Copyright 1991, John F. Haugh II
+  * All rights reserved.
+  *
+  * Permission is granted to copy and create derivative works for any
+  * non-commercial purpose, provided this copyright notice is preserved
+  * in all copies of source code, or included in human readable form
+  * and conspicuously displayed on all copies of object code or
+  * distribution media.
+  */
+ 
+ #ifndef lint
+ static	char	sccsid[] = "@(#)useradd.c	3.1	11:08:18	6/7/91";
+ #endif
+ 
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <stdio.h>
+ #include <errno.h>
+ #include <pwd.h>
+ #include <grp.h>
+ #include <ctype.h>
+ #include <fcntl.h>
+ #include <time.h>
+ 
+ #ifdef	BSD
+ #include <strings.h>
+ #else
+ #include <string.h>
+ #endif
+ 
+ #include "config.h"
+ #include "shadow.h"
+ 
+ #ifdef	USE_SYSLOG
+ #include <syslog.h>
+ 
+ #ifndef	LOG_WARN
+ #define	LOG_WARN LOG_WARNING
+ #endif
+ #endif
+ 
+ gid_t	def_group;
+ char	def_home[BUFSIZ];
+ char	def_shell[BUFSIZ];
+ long	def_inactive;
+ long	def_expire;
+ char	def_file[] = "/etc/default/useradd";
+ 
+ #ifndef	NGROUPS_MAX
+ #define	NGROUPS_MAX	64
+ #endif
+ 
+ char	user_name[BUFSIZ];
+ uid_t	user_id;
+ gid_t	user_gid;
+ char	user_comment[BUFSIZ];
+ char	user_home[BUFSIZ];
+ char	user_shell[BUFSIZ];
+ long	user_expire;
+ int	user_ngroups;
+ gid_t	user_groups[NGROUPS_MAX];
+ 
+ char	*Prog;
+ 
+ int	uflg;	/* specify user ID for new account                            */
+ int	oflg;	/* permit non-unique user ID to be specified with -u          */
+ int	gflg;	/* primary group ID  for new account                          */
+ int	Gflg;	/* secondary group set for new account                        */
+ int	dflg;	/* home directory for new account                             */
+ int	bflg;	/* new default root of home directory                         */
+ int	sflg;	/* shell program for new account                              */
+ int	cflg;	/* comment (GECOS) field for new account                      */
+ int	mflg;	/* create user's home directory if it doesn't exist           */
+ int	kflg;	/* specify a directory to fill new user directory             */
+ int	fflg;	/* days until account with expired password is locked         */
+ int	eflg;	/* days after password changed before it becomes expired      */
+ int	Dflg;	/* set/show new user default values                           */
+ 
+ #if defined(DBM) || defined(NDBM)
+ extern	int	pw_dbm_mode;
+ #endif
+ #ifdef	NDBM
+ extern	int	sp_dbm_mode;
+ extern	int	gr_dbm_mode;
+ extern	int	sg_dbm_mode;
+ #endif
+ extern	FILE	*fopen();
+ extern	int	fclose();
+ extern	char	*malloc();
+ extern	char	*mktemp();
+ 
+ extern	struct	group	*getgrnam();
+ extern	struct	group	*getgrgid();
+ extern	struct	group	*gr_next();
+ extern	struct	group	*gr_locate();
+ extern	int	gr_lock();
+ extern	int	gr_unlock();
+ extern	int	gr_rewind();
+ extern	int	gr_open();
+ 
+ #ifdef	SHADOWGRP
+ extern	struct	sgrp	*sgr_next();
+ extern	int	sgr_lock();
+ extern	int	sgr_unlock();
+ extern	int	sgr_rewind();
+ extern	int	sgr_open();
+ #endif
+ 
+ extern	struct	passwd	*getpwnam();
+ extern	struct	passwd	*pw_next();
+ extern	int	pw_lock();
+ extern	int	pw_unlock();
+ extern	int	pw_rewind();
+ extern	int	pw_open();
+ 
+ extern	int	spw_lock();
+ extern	int	spw_unlock();
+ extern	int	spw_open();
+ 
+ #define	DAY	(24L*3600L)
+ #define	WEEK	(7*DAY)
+ 
+ #ifdef	ITI_AGING
+ #define	SCALE	(1)
+ #else
+ #define	SCALE	(DAY)
+ #endif
+ 
+ /*
+  * days and juldays are used to compute the number of days in the
+  * current month, and the cummulative number of days in the preceding
+  * months.  they are declared so that january is 1, not 0.
+  */
+ 
+ static	short	days[13] = { 0,
+ 	31,	28,	31,	30,	31,	30,	/* JAN - JUN */
+ 	31,	31,	30,	31,	30,	31 };	/* JUL - DEC */
+ 
+ static	short	juldays[13] = { 0,
+ 	0,	31,	59,	90,	120,	151,	/* JAN - JUN */
+ 	181,	212,	243,	273,	304,	334 };	/* JUL - DEC */
+ 
+ #ifdef	NEED_MKDIR
+ /*
+  * mkdir - create a directory
+  *
+  *	mkdir is provided for systems which do not include the mkdir()
+  *	system call.
+  */
+ 
+ int
+ mkdir (dir, mode)
+ char	*dir;
+ int	mode;
+ {
+ 	int	status;
+ 
+ 	if (fork ()) {
+ 		while (wait (&status) != -1)
+ 			;
+ 
+ 		return status >> 8;
+ 	}
+ #ifdef	USE_SYSLOG
+ 	closelog ();
+ #endif
+ 	close (2);
+ 	open ("/dev/null", O_WRONLY);
+ 	umask (0777 & ~ mode);
+ 	execl ("/bin/mkdir", "mkdir", dir, 0);
+ 	_exit (128);
+ }
+ #endif
+ #ifdef	NEED_RENAME
+ /*
+  * rename - rename a file to another name
+  *
+  *	rename is provided for systems which do not include the rename()
+  *	system call.
+  */
+ 
+ int
+ rename (begin, end)
+ char	*begin;
+ char	*end;
+ {
+ 	struct	stat	s1, s2;
+ 	extern	int	errno;
+ 	int	orig_err = errno;
+ 
+ 	if (stat (begin, &s1))
+ 		return -1;
+ 
+ 	if (stat (end, &s2)) {
+ 		errno = orig_err;
+ 	} else {
+ 
+ 		/*
+ 		 * See if this is a cross-device link.  We do this to
+ 		 * insure that the link below has a chance of working.
+ 		 */
+ 
+ 		if (s1.st_dev != s2.st_dev) {
+ 			errno = EXDEV;
+ 			return -1;
+ 		}
+ 
+ 		/*
+ 		 * See if we can unlink the existing destination
+ 		 * file.  If the unlink works the directory is writable,
+ 		 * so there is no need here to figure that out.
+ 		 */
+ 
+ 		if (unlink (end))
+ 			return -1;
+ 	}
+ 
+ 	/*
+ 	 * Now just link the original name to the final name.  If there
+ 	 * was no file previously, this link will fail if the target
+ 	 * directory isn't writable.  The unlink will fail if the source
+ 	 * directory isn't writable, but life stinks ...
+ 	 */
+ 
+ 	if (link (begin, end) || unlink (begin))
+ 		return -1;
+ 
+ 	return 0;
+ }
+ #endif
+ 
+ /*
+  * strtoday - compute the number of days since 1970.
+  *
+  * the total number of days prior to the current date is
+  * computed.  january 1, 1970 is used as the origin with
+  * it having a day number of 0.
+  */
+ 
+ long
+ strtoday (str)
+ char	*str;
+ {
+ 	char	slop[2];
+ 	int	month;
+ 	int	day;
+ 	int	year;
+ 	long	total;
+ 
+ 	/*
+ 	 * start by separating the month, day and year.  this is
+ 	 * a chauvanistic program - it only takes date input in
+ 	 * the standard USA format.
+ 	 */
+ 
+ 	if (sscanf (str, "%d/%d/%d%c", &month, &day, &year, slop) != 3)
+ 		return -1;
+ 
+ 	/*
+ 	 * the month, day of the month, and year are checked for
+ 	 * correctness and the year adjusted so it falls between
+ 	 * 1970 and 2069.
+ 	 */
+ 
+ 	if (month < 1 || month > 12)
+ 		return -1;
+ 
+ 	if (day < 1)
+ 		return -1;
+ 
+ 	if ((month != 2 || (year % 4) != 0) && day > days[month])
+ 		return -1;
+ 	else if ((month == 2 && (year % 4) == 0) && day > 29)
+ 		return -1;
+ 
+ 	if (year < 0)
+ 		return -1;
+ 	else if (year < 69)
+ 		year += 2000;
+ 	else if (year < 99)
+ 		year += 1900;
+ 
+ 	if (year < 1970 || year > 2069)
+ 		return -1;
+ 
+ 	/*
+ 	 * the total number of days is the total number of days in all
+ 	 * the whole years, plus the number of leap days, plus the
+ 	 * number of days in the whole months preceding, plus the number
+ 	 * of days so far in the month.
+ 	 */
+ 
+ 	total = (long) ((year - 1970) * 365L) + (((year + 1) - 1970) / 4);
+ 	total += (long) juldays[month] + (month > 2 && (year % 4) == 0 ? 1:0);
+ 	total += (long) day - 1;
+ 
+ 	return total;
+ }
+ 
+ /*
+  * add_list - add a member to a list of group members
+  *
+  *	the array of member names is searched for the new member
+  *	name, and if not present it is added to a freshly allocated
+  *	list of users.
+  */
+ 
+ char **
+ add_list (list, member)
+ char	**list;
+ char	*member;
+ {
+ 	int	i;
+ 	char	**tmp;
+ 
+ 	/*
+ 	 * Scan the list for the new name.  Return the original list
+ 	 * pointer if it is present.
+ 	 */
+ 
+ 	for (i = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member) == 0)
+ 			return list;
+ 
+ 	/*
+ 	 * Allocate a new list pointer large enough to hold all the
+ 	 * old entries, and the new entries as well.
+ 	 */
+ 
+ 	if (! (tmp = (char **) malloc ((i + 2) * sizeof member)))
+ 		return 0;
+ 
+ 	/*
+ 	 * Copy the original list to the new list, then append the
+ 	 * new member and NULL terminate the result.  This new list
+ 	 * is returned to the invoker.
+ 	 */
+ 
+ 	for (i = 0;list[i] != (char *) 0;i++)
+ 		tmp[i] = list[i];
+ 
+ 	tmp[i++] = strdup (member);
+ 	tmp[i] = (char *) 0;
+ 
+ 	return tmp;
+ }
+ 
+ /*
+  * get_defaults - read the defaults file
+  *
+  *	get_defaults() reads the defaults file for this command.  It sets
+  *	the various values from the file, or uses built-in default values
+  *	if the file does not exist.
+  */
+ 
+ void
+ get_defaults ()
+ {
+ 	FILE	*fp;
+ 	char	buf[BUFSIZ];
+ 	char	*cp;
+ 	struct	group	*grp;
+ 
+ 	/*
+ 	 * Open the defaults file for reading.
+ 	 */
+ 
+ 	if (! (fp = fopen (def_file, "r"))) {
+ 
+ 		/*
+ 		 * No defaults file - set up the defaults that are given
+ 		 * in the documentation.
+ 		 */
+ 
+ 		def_group = 1;
+ 		strcpy (def_home, "/home");
+ 		def_inactive = 0;
+ 		def_expire = 0;
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * Read the file a line at a time.  Only the lines that have
+ 	 * relevant values are used, everything else can be ignored.
+ 	 */
+ 
+ 	while (fgets (buf, BUFSIZ, fp)) {
+ 		if (cp = strrchr (buf, '\n'))
+ 			*cp = '\0';
+ 
+ 		/*
+ 		 * Primary GROUP identifier
+ 		 */
+ 
+ 		if (strncmp ("GROUP=", buf, 6) == 0) {
+ 			cp = buf + 6;
+ 			if (isdigit (*cp))
+ 				def_group = atoi (cp);
+ 			else if (grp = getgrnam (cp))
+ 				def_group = grp->gr_gid;
+ 			else
+ 				fprintf (stderr, "%s: unknown group %s\n", cp);
+ 		}
+ 		
+ 		/*
+ 		 * Default HOME filesystem
+ 		 */
+ 		 
+ 		else if (strncmp ("HOME=", buf, 5) == 0) {
+ 			strncpy (def_home, buf + 5, BUFSIZ);
+ 		}
+ 
+ 		/*
+ 		 * Default Login Shell command
+ 		 */
+ 
+ 		else if (strncmp ("SHELL=", buf, 6) == 0) {
+ 			strncpy (def_shell, buf + 6, BUFSIZ);
+ 		}
+ 
+ 		/*
+ 		 * Default Password Inactive value
+ 		 */
+ 
+ 		else if (strncmp ("INACTIVE=", buf, 9) == 0) {
+ 			def_inactive = atoi (buf + 9);
+ 		}
+ 		
+ 		/*
+ 		 * Default Password Expiration value
+ 		 */
+ 
+ 		else if (strncmp ("EXPIRE=", buf, 7) == 0) {
+ 			def_expire = atoi (buf + 7);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * show_defaults - show the contents of the defaults file
+  *
+  *	show_defaults() displays the values that are used from the default
+  *	file and the built-in values.
+  */
+ 
+ void
+ show_defaults ()
+ {
+ 	printf ("GROUP=%d\n", def_group);
+ 	printf ("HOME=%s\n", def_home);
+ 	printf ("INACTIVE=%d\n", def_inactive);
+ 	printf ("EXPIRE=%d\n", def_expire);
+ }
+ 
+ /*
+  * set_defaults - write new defaults file
+  *
+  *	set_defaults() re-writes the defaults file using the values that
+  *	are currently set.  Duplicated lines are pruned, missing lines are
+  *	added, and unrecognized lines are copied as is.
+  */
+ 
+ int
+ set_defaults ()
+ {
+ 	FILE	*ifp;
+ 	FILE	*ofp;
+ 	char	buf[BUFSIZ];
+ 	static	char	new_file[] = "/etc/default/nuaddXXXXXX";
+ 	char	*cp;
+ 	int	out_group = 0;
+ 	int	out_home = 0;
+ 	int	out_inactive = 0;
+ 	int	out_expire = 0;
+ 
+ 	/*
+ 	 * Create a temporary file to copy the new output to.
+ 	 */
+ 
+ 	mktemp (new_file);
+ 	if (! (ofp = fopen (new_file, "w"))) {
+ 		fprintf (stderr, "%s: cannot create new defaults file\n", Prog);
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Open the existing defaults file and copy the lines to the
+ 	 * temporary files, using any new values.  Each line is checked
+ 	 * to insure that it is not output more than once.
+ 	 */
+ 
+ 	if (ifp = fopen (def_file, "r")) {
+ 		while (fgets (buf, BUFSIZ, ifp)) {
+ 			if (cp = strrchr (buf, '\n'))
+ 				*cp = '\0';
+ 
+ 			if (strncmp ("GROUP=", buf, 6) == 0) {
+ 				if (! out_group)
+ 					fprintf (ofp, "GROUP=%d\n", def_group);
+ 
+ 				out_group++;
+ 			} else if (strncmp ("HOME=", buf, 5) == 0) {
+ 				if (! out_home)
+ 					fprintf (ofp, "HOME=%s\n", def_home);
+ 
+ 				out_home++;
+ 			} else if (strncmp ("INACTIVE=", buf, 9) == 0) {
+ 				if (! out_inactive)
+ 					fprintf (ofp, "INACTIVE=%d\n",
+ 						def_inactive);
+ 
+ 				out_inactive++;
+ 			} else if (strncmp ("EXPIRE=", buf, 7) == 0) {
+ 				if (! out_expire)
+ 					fprintf (ofp, "EXPIRE=%d\n",
+ 						def_expire);
+ 
+ 				out_expire++;
+ 			} else
+ 				fprintf (ofp, "%s\n", buf);
+ 		}
+ 		fclose ((FILE *) ifp);
+ 	}
+ 
+ 	/*
+ 	 * Check each line to insure that every line was output.  This
+ 	 * causes new values to be added to a file which did not previously
+ 	 * have an entry for that value.
+ 	 */
+ 
+ 	if (! out_group)
+ 		fprintf (ofp, "GROUP=%d\n", def_group);
+ 
+ 	if (! out_home)
+ 		fprintf (ofp, "HOME=%s\n", def_home);
+ 
+ 	if (! out_inactive)
+ 		fprintf (ofp, "INACTIVE=%d\n", def_inactive);
+ 
+ 	if (! out_expire)
+ 		fprintf (ofp, "EXPIRE=%d\n", def_expire);
+ 
+ 	/*
+ 	 * Flush and close the file.  Check for errors to make certain
+ 	 * the new file is intact.
+ 	 */
+ 
+ 	(void) fflush (ofp);
+ 	if (ferror (ofp) || fclose ((FILE *) ofp)) {
+ 		unlink (new_file);
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Rename the current default file to its backup name.
+ 	 */
+ 
+ 	sprintf (buf, "%s-", def_file);
+ 	if (rename (def_file, buf) && errno != ENOENT) {
+ 		sprintf (buf, "%s: rename: %s", Prog, def_file);
+ 		perror (buf);
+ 		unlink (new_file);
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Rename the new default file to its correct name.
+ 	 */
+ 
+ 	if (rename (new_file, def_file)) {
+ 		sprintf (buf, "%s: rename: %s", Prog, new_file);
+ 		perror (buf);
+ 		return -1;
+ 	}
+ #ifdef	USE_SYSLOG
+ 	syslog (LOG_INFO, "defaults: group=%d, home=%s, inactive=%d, expire=%d",
+ 		def_group, def_home, def_inactive, def_expire);
+ #endif
+ 	return 0;
+ }
+ 
+ /*
+  * get_groups - convert a list of group names to an array of group IDs
+  *
+  *	get_groups() takes a comma-separated list of group names and
+  *	converts it to an array of group ID values.  Any unknown group
+  *	names are reported as errors.
+  */
+ 
+ int
+ get_groups (list)
+ char	*list;
+ {
+ 	char	*cp;
+ 	struct	group	*grp;
+ 	int	errors = 0;
+ 
+ 	/*
+ 	 * Initialize the list to be empty
+ 	 */
+ 
+ 	user_ngroups = 0;
+ 
+ 	if (! *list)
+ 		return 0;
+ 
+ 	/*
+ 	 * So long as there is some data to be converted, strip off
+ 	 * each name and look it up.  A mix of numerical and string
+ 	 * values for group identifiers is permitted.
+ 	 */
+ 
+ 	do {
+ 		/*
+ 		 * Strip off a single name from the list
+ 		 */
+ 
+ 		if (cp = strchr (list, ','))
+ 			*cp++ = '\0';
+ 
+ 		/*
+ 		 * Names starting with digits are treated as numerical
+ 		 * GID values, otherwise the string is looked up as is.
+ 		 */
+ 
+ 		if (isdigit (*list))
+ 			grp = getgrgid (atoi (list));
+ 		else
+ 			grp = getgrnam (list);
+ 
+ 		/*
+ 		 * There must be a match, either by GID value or by
+ 		 * string name.
+ 		 */
+ 
+ 		if (! grp) {
+ 			fprintf (stderr, "%s: unknown group %s\n", Prog, list);
+ 			errors++;
+ 		}
+ 
+ 		/*
+ 		 * Add the GID value from the group file to the user's
+ 		 * list of groups.
+ 		 */
+ 
+ 		user_groups[user_ngroups++] = grp->gr_gid;
+ 
+ 		list = cp;
+ 	} while (list);
+ 
+ 	/*
+ 	 * Any errors in finding group names are fatal
+ 	 */
+ 
+ 	if (errors)
+ 		return -1;
+ 
+ 	return 0;
+ }
+ 
+ /*
+  * usage - display usage message and exit
+  */
+ 
+ usage ()
+ {
+ 	fprintf (stderr,
+ 		"usage:\tuseradd [-u uid [-o]] [-g group] [-G group,...] \n");
+ 	fprintf (stderr,
+ 		"\t\t[-d home] [-s shell] [-c comment] [-m [-k template]]\n");
+ 	fprintf (stderr,
+ 		"\t\t[-f inactive] [-e expire] name\n");
+ 
+ 	fprintf (stderr,
+ 		"\tuseradd -D [-g group] [-b base] [-f inactive] [-e expire]\n");
+ 
+ 	exit (1);
+ }
+ 
+ /*
+  * new_pwent - initialize the values in a password file entry
+  *
+  *	new_pwent() takes all of the values that have been entered and
+  *	fills in a (struct passwd) with them.
+  */
+ 
+ void
+ new_pwent (pwent)
+ struct	passwd	*pwent;
+ {
+ 	memset (pwent, 0, sizeof *pwent);
+ 	pwent->pw_name = user_name;
+ 	pwent->pw_passwd = "*";
+ 	pwent->pw_age = "";
+ 	pwent->pw_uid = user_id;
+ 	pwent->pw_gid = user_gid;
+ 	pwent->pw_gecos = user_comment;
+ 	pwent->pw_comment = "";
+ 	pwent->pw_dir = user_home;
+ 	pwent->pw_shell = user_shell;
+ }
+ 
+ /*
+  * new_spent - initialize the values in a shadow password file entry
+  *
+  *	new_spent() takes all of the values that have been entered and
+  *	fills in a (struct spwd) with them.
+  */
+ 
+ void
+ new_spent (spent)
+ struct	spwd	*spent;
+ {
+ 	memset (spent, 0, sizeof *spent);
+ 	spent->sp_namp = user_name;
+ 	spent->sp_pwdp = "!";
+ 	spent->sp_lstchg = 0;
+ 	spent->sp_min = 0;
+ 	spent->sp_max = def_expire;
+ 	spent->sp_warn = 0;
+ 	spent->sp_inact = def_inactive;
+ 	spent->sp_expire = user_expire;
+ }
+ 
+ /*
+  * grp_update - add user to secondary group set
+  *
+  *	grp_update() takes the secondary group set given in user_groups
+  *	and adds the user to each group given by that set.
+  */
+ 
+ void
+ grp_update ()
+ {
+ 	int	i;
+ 	struct	group	*grp;
+ 	struct	sgrp	*sgrp;
+ 
+ 	/*
+ 	 * Lock and open the group file.  This will load all of the group
+ 	 * entries.
+ 	 */
+ 
+ 	if (! gr_lock ()) {
+ 		fprintf (stderr, "%s: error locking group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! gr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: error opening group file\n", Prog);
+ 		exit (1);
+ 	}
+ #ifdef	SHADOWGRP
+ 	if (! sgr_lock ()) {
+ 		fprintf (stderr, "%s: error locking shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! sgr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: error opening shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ #endif
+ 
+ 	/*
+ 	 * Scan through the entire group file looking for the groups that
+ 	 * the user is a member of.
+ 	 */
+ 
+ 	for (gr_rewind (), grp = gr_next ();grp;grp = gr_next ()) {
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;i < user_ngroups;i++)
+ 			if (grp->gr_gid == user_groups[i])
+ 				break;
+ 
+ 		if (i == user_ngroups)
+ 			continue;
+ 
+ 		/* 
+ 		 * Add the username to the list of group members and
+ 		 * update the group entry to reflect the change.
+ 		 */
+ 
+ 		grp->gr_mem = add_list (grp->gr_mem, user_name);
+ 		if (! gr_update (grp)) {
+ 			fprintf (stderr, "%s: error adding new group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! gr_dbm_update (grp)) {
+ 			fprintf (stderr, "%s: cannot add new dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ #ifdef	USE_SYSLOG
+ 		syslog (LOG_INFO, "add `%s' to group `%s'\n",
+ 			user_name, grp->gr_name);
+ #endif
+ 	}
+ 
+ #ifdef	SHADOWGRP
+ 	/*
+ 	 * Scan through the entire shadow group file looking for the groups
+ 	 * that the user is a member of.  The administrative list isn't
+ 	 * modified.
+ 	 */
+ 
+ 	for (sgr_rewind (), sgrp = sgr_next ();sgrp;sgrp = sgr_next ()) {
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;i < user_ngroups;i++) {
+ 			if (! (grp = gr_locate (sgrp->sg_name)))
+ 				continue;
+ 
+ 			if (grp->gr_gid == user_groups[i])
+ 				break;
+ 		}
+ 		if (i == user_ngroups)
+ 			continue;
+ 
+ 		/* 
+ 		 * Add the username to the list of group members and
+ 		 * update the group entry to reflect the change.
+ 		 */
+ 
+ 		sgrp->sg_mem = add_list (sgrp->sg_mem, user_name);
+ 		if (! sgr_update (sgrp)) {
+ 			fprintf (stderr, "%s: error adding new group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! sgr_dbm_update (sgrp)) {
+ 			fprintf (stderr, "%s: cannot add new dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ #ifdef	USE_SYSLOG
+ 		syslog (LOG_INFO, "add `%s' to shadow group `%s'\n",
+ 			user_name, sgrp->sg_name);
+ #endif
+ 	}
+ #endif
+ }
+ 
+ /*
+  * find_new_uid - find the next available UID
+  *
+  *	find_new_uid() locates the next highest unused UID in the password
+  *	file, or checks the given user ID against the existing ones for
+  *	uniqueness.
+  */
+ 
+ int
+ find_new_uid ()
+ {
+ 	struct	passwd	*pwd;
+ 
+ 	/*
+ 	 * Start with some UID value if the user didn't provide us with
+ 	 * one already.
+ 	 */
+ 
+ 	if (! uflg)
+ 		user_id = 100;
+ 
+ 	/*
+ 	 * Search the entire password file, either looking for this
+ 	 * UID (if the user specified one with -u) or looking for the
+ 	 * largest unused value.
+ 	 */
+ 
+ 	for (pw_rewind (), pwd = pw_next ();pwd;pwd = pw_next ()) {
+ 		if (strcmp (user_name, pwd->pw_name) == 0) {
+ 			fprintf (stderr, "%s: name %s is not unique\n",
+ 				Prog, user_name);
+ 			exit (1);
+ 		}
+ 		if (uflg && user_id == pwd->pw_uid) {
+ 			fprintf (stderr, "%s: uid %d is not unique\n",
+ 				Prog, user_id);
+ 			exit (1);
+ 		}
+ 		if (! uflg && pwd->pw_uid >= user_id)
+ 			user_id = pwd->pw_uid + 1;
+ 	}
+ }
+ 
+ /*
+  * process_flags - perform command line argument setting
+  *
+  *	process_flags() interprets the command line arguments and sets
+  *	the values that the user will be created with accordingly.  The
+  *	values are checked for sanity.
+  */
+ 
+ void
+ process_flags (argc, argv)
+ int	argc;
+ char	**argv;
+ {
+ 	extern	int	optind;
+ 	extern	char	*optarg;
+ 	struct	group	*grp;
+ 	int	anyflag = 0;
+ 	int	arg;
+ 
+ 	while ((arg = getopt (argc, argv, "Du:og:G:d:s:c:mk:f:e:b:")) != EOF) {
+ 		switch (arg) {
+ 			case 'b':
+ 				bflg++;
+ 				if (! Dflg)
+ 					usage ();
+ 
+ 				strncpy (def_home, optarg, BUFSIZ);
+ 				break;
+ 			case 'c':
+ 				cflg++;
+ 				strncpy (user_comment, optarg, BUFSIZ);
+ 				break;
+ 			case 'd':
+ 				dflg++;
+ 				strncpy (user_home, optarg, BUFSIZ);
+ 				break;
+ 			case 'D':
+ 				if (anyflag)
+ 					usage ();
+ 
+ 				Dflg++;
+ 				break;
+ 			case 'e':
+ 				eflg++;
+ 				if (Dflg)
+ 					def_expire = atoi (optarg);
+ 				else {
+ 					user_expire = strtoday (optarg);
+ #ifdef	ITI_AGING
+ 					user_expire *= DAY;
+ #endif
+ 				}
+ 				break;
+ 			case 'f':
+ 				fflg++;
+ 				def_inactive = atoi (optarg);
+ 				break;
+ 			case 'g':
+ 				gflg++;
+ 				if (isdigit (optarg[0]))
+ 					grp = getgrgid (atoi (optarg));
+ 				else
+ 					grp = getgrnam (optarg);
+ 
+ 				if (! grp) {
+ 					fprintf (stderr,
+ 						"%s: unknown group %s\n",
+ 						optarg);
+ 					exit (1);
+ 				}
+ 				def_group = grp->gr_gid;
+ 				break;
+ 			case 'G':
+ 				Gflg++;
+ 				if (get_groups (optarg))
+ 					exit (1);
+ 
+ 				break;
+ 			case 'k':
+ 				if (! mflg)
+ 					usage ();
+ 
+ 				kflg++;
+ 				break;
+ 			case 'm':
+ 				mflg++;
+ 				break;
+ 			case 'o':
+ 				if (! uflg)
+ 					usage ();
+ 
+ 				oflg++;
+ 				break;
+ 			case 's':
+ 				sflg++;
+ 				strncpy (user_shell, optarg, BUFSIZ);
+ 				break;
+ 			case 'u':
+ 				uflg++;
+ 				user_id = atoi (optarg);
+ 				break;
+ 			default:
+ 				usage ();
+ 		}
+ 		anyflag++;
+ 	}
+ 	if (! Dflg && optind == argc - 1)
+ 		strcpy (user_name, argv[argc - 1]);
+ 
+ 	if (! dflg)
+ 		sprintf (user_home, "%s/%s", def_home, user_name);
+ 
+ 	if (! gflg)
+ 		user_gid = def_group;
+ 
+ 	if (Dflg) {
+ 		if (optind != argc)
+ 			usage ();
+ 
+ 		if (uflg || oflg || Gflg || dflg ||
+ 				sflg || cflg || mflg || kflg)
+ 			usage ();
+ 	} else {
+ 		if (optind != argc - 1)
+ 			usage ();
+ 	}
+ }
+ 
+ /*
+  * close_files - close all of the files that were opened
+  *
+  *	close_files() closes all of the files that were opened for this
+  *	new user.  This causes any modified entries to be written out.
+  */
+ 
+ close_files ()
+ {
+ 	if (! pw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite shadow password file\n",	
+ 			Prog);
+ 		exit (1);
+ 	}
+ 	if (user_ngroups > 0) {
+ 		if (! gr_close ()) {
+ 			fprintf (stderr, "%s: cannot rewrite group file\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 		(void) gr_unlock ();
+ #ifdef	SHADOWGRP
+ 		if (! sgr_close ()) {
+ 			fprintf (stderr, "%s: cannot rewrite shadow group file\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ 	(void) pw_unlock ();
+ }
+ 
+ /*
+  * open_files - lock and open the password files
+  *
+  *	open_files() opens the two password files.
+  */
+ 
+ open_files ()
+ {
+ 	if (! pw_lock ()) {
+ 		fprintf (stderr, "%s: unable to lock password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! pw_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: unable to open password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_lock ()) {
+ 		fprintf ("%s: cannot lock shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_open (O_RDWR)) {
+ 		fprintf ("%s: cannot open shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ }
+ 
+ /*
+  * usr_update - create the user entries
+  *
+  *	usr_update() creates the password file entries for this user
+  *	and will update the group entries if required.
+  */
+ 
+ usr_update ()
+ {
+ 	struct	passwd	pwent;
+ 	struct	spwd	spent;
+ 
+ 	if (! oflg)
+ 		find_new_uid ();
+ 
+ 	new_pwent (&pwent);
+ 	if (! pw_update (&pwent)) {
+ 		fprintf (stderr, "%s: error adding new password entry\n", Prog);
+ 		exit (1);
+ 	}
+ 	new_spent (&spent);
+ 	if (! spw_update (&spent)) {
+ 		fprintf (stderr, "%s: error adding new shadow password entry\n",
+ 			Prog);
+ 		exit (1);
+ 	}
+ #if defined(DBM) || defined(NDBM)
+ 	if (access ("/etc/passwd.pag", 0) == 0 && ! pw_dbm_update (&pwent)) {
+ 		fprintf (stderr, "%s: error updating password dbm entry\n",
+ 			Prog);
+ 		exit (1);
+ 	}
+ #endif
+ #ifdef	NDBM
+ 	if (access ("/etc/shadow.pag", 0) == 0 && ! sp_dbm_update (&spent)) {
+ 		fprintf (stderr, "%s: error updating shadow passwd dbm entry\n",
+ 			Prog);
+ 		exit (1);
+ 	}
+ #endif
+ #ifdef	USE_SYSLOG
+ 	syslog (LOG_INFO,
+ 		"new user: name=%s, uid=%d, gid=%d, home=%s, shell=%s\n",
+ 		user_name, user_id, user_gid, user_home, user_shell);
+ #endif
+ 	if (user_ngroups > 0)
+ 		grp_update ();
+ }
+ 
+ /*
+  * create_home - create the user's home directory
+  *
+  *	create_home() creates the user's home directory if it does not
+  *	already exist.  It will be created mode 755 owned by the user
+  *	with the user's default group.
+  */
+ 
+ create_home ()
+ {
+ 	if (access (user_home, 0)) {
+ 		if (mkdir (user_home, 0755)) {
+ 			fprintf (stderr, "%s: cannot create directory %s\n",
+ 				Prog, user_home);
+ 			exit (1);
+ 		}
+ 		chown (user_home, user_id, user_gid);
+ 		chmod (user_home, 0755);
+ 	}
+ }
+ 
+ /*
+  * main - useradd command
+  */
+ 
+ main (argc, argv)
+ int	argc;
+ char	**argv;
+ {
+ 	/*
+ 	 * Get my name so that I can use it to report errors.
+ 	 */
+ 
+ 	if (Prog = strrchr (argv[0], '/'))
+ 		Prog++;
+ 	else
+ 		Prog = argv[0];
+ 
+ #ifdef	USE_SYSLOG
+ 	openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
+ #endif
+ 
+ 	/*
+ 	 * The open routines for the DBM files don't use read-write
+ 	 * as the mode, so we have to clue them in.
+ 	 */
+ 
+ #if defined(DBM) || defined(NDBM)
+ 	pw_dbm_mode = O_RDWR;
+ #endif
+ #ifdef	NDBM
+ 	sp_dbm_mode = O_RDWR;
+ 	gr_dbm_mode = O_RDWR;
+ #ifdef	SHADOWGRP
+ 	sg_dbm_mode = O_RDWR;
+ #endif
+ #endif
+ 	get_defaults ();
+ 
+ 	process_flags (argc, argv);
+ 
+ 	/*
+ 	 * See if we are messing with the defaults file, or creating
+ 	 * a new user.
+ 	 */
+ 
+ 	if (Dflg) {
+ 		if (gflg || bflg || fflg || eflg)
+ 			exit (set_defaults () ? 1:0);
+ 
+ 		show_defaults ();
+ 		exit (0);
+ 	}
+ 
+ 	/*
+ 	 * Start with a quick check to see if the user exists.
+ 	 */
+ 
+ 	if (getpwnam (user_name)) {
+ 		fprintf (stderr, "%s: user %s exists\n", Prog, user_name);
+ 		exit (1);
+ 	}
+ 
+ 	/*
+ 	 * Do the hard stuff - open the files, create the user entries,
+ 	 * create the home directory, then close and update the files.
+ 	 */
+ 
+ 	open_files ();
+ 
+ 	usr_update ();
+ 
+ 	if (mflg)
+ 		create_home ();
+ 
+ 	close_files ();
+ 	exit (0);
+ 	/*NOTREACHED*/
+ }
*** /dev/null	Fri Jun  7 11:02:59 1991
--- userdel.c	Fri Jun  7 11:10:08 1991
***************
*** 0 ****
--- 1,499 ----
+ /*
+  * Copyright 1991, John F. Haugh II
+  * All rights reserved.
+  *
+  * Permission is granted to copy and create derivative works for any
+  * non-commercial purpose, provided this copyright notice is preserved
+  * in all copies of source code, or included in human readable form
+  * and conspicuously displayed on all copies of object code or
+  * distribution media.
+  */
+ 
+ #ifndef lint
+ static	char	sccsid[] = "@(#)userdel.c	3.1	11:08:47	6/7/91";
+ #endif
+ 
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <stdio.h>
+ #include <errno.h>
+ #include <pwd.h>
+ #include <grp.h>
+ #include <ctype.h>
+ #include <fcntl.h>
+ #include <time.h>
+ 
+ #ifdef	BSD
+ #include <strings.h>
+ #else
+ #include <string.h>
+ #endif
+ 
+ #include "config.h"
+ #include "shadow.h"
+ 
+ #ifdef	USE_SYSLOG
+ #include <syslog.h>
+ 
+ #ifndef	LOG_WARN
+ #define	LOG_WARN LOG_WARNING
+ #endif
+ #endif
+ 
+ gid_t	default_group;
+ char	default_home[BUFSIZ];
+ char	default_shell[BUFSIZ];
+ long	default_inactive;
+ long	default_expire;
+ char	default_file[] = "/etc/default/useradd";
+ 
+ #ifndef	NGROUPS_MAX
+ #define	NGROUPS_MAX	64
+ #endif
+ 
+ char	user_name[BUFSIZ];
+ uid_t	user_id;
+ gid_t	user_group;
+ char	user_comment[BUFSIZ];
+ char	user_home[BUFSIZ];
+ char	user_shell[BUFSIZ];
+ int	user_ngroups;
+ int	user_expire;
+ gid_t	user_groups[NGROUPS_MAX];
+ 
+ char	*Prog;
+ int	rflg;
+ 
+ #if defined(DBM) || defined(NDBM)
+ extern	int	pw_dbm_mode;
+ #endif
+ #ifdef	NDBM
+ extern	int	sp_dbm_mode;
+ extern	int	gr_dbm_mode;
+ #ifdef	SHADOWGRP
+ extern	int	sg_dbm_mode;
+ #endif
+ #endif
+ extern	struct	group	*getgrnam();
+ extern	struct	group	*getgrgid();
+ extern	struct	group	*gr_next();
+ extern	struct	passwd	*getpwnam();
+ extern	struct	passwd	*pw_next();
+ 
+ #ifdef	SHADOWGRP
+ extern	int	sgr_lock();
+ extern	int	sgr_unlock();
+ extern	int	sgr_open();
+ extern	int	sgr_close();
+ extern	struct	sgrp	*sgr_next();
+ #endif
+ 
+ extern	char	*malloc();
+ 
+ #ifdef	NEED_RMDIR
+ /*
+  * rmdir - remove a directory
+  *
+  *	rmdir is provided for systems which do not include the rmdir()
+  *	system call.
+  */
+ 
+ int
+ rmdir (dir)
+ char	*dir;
+ {
+ 	int	status;
+ 
+ 	if (fork ()) {
+ 		while (wait (&status) != -1)
+ 			;
+ 
+ 		return status >> 8;
+ 	}
+ 	close (2);
+ 	open ("/dev/null", O_WRONLY);
+ 	execl ("/bin/rmdir", "rmdir", dir, 0);
+ 	_exit (128);
+ }
+ #endif
+ 
+ /*
+  * del_list - delete a member from a list of group members
+  *
+  *	the array of member names is searched for the old member
+  *	name, and if present it is deleted from a freshly allocated
+  *	list of users.
+  */
+ 
+ char **
+ del_list (list, member)
+ char	**list;
+ char	*member;
+ {
+ 	int	i, j;
+ 	char	**tmp;
+ 
+ 	/*
+ 	 * Scan the list for the new name.  Return the original list
+ 	 * pointer if it is present.
+ 	 */
+ 
+ 	for (i = j = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member))
+ 			j++;
+ 
+ 	if (j == i)
+ 		return list;
+ 
+ 	/*
+ 	 * Allocate a new list pointer large enough to hold all the
+ 	 * old entries, and the new entries as well.
+ 	 */
+ 
+ 	if (! (tmp = (char **) malloc ((j + 2) * sizeof member)))
+ 		return 0;
+ 
+ 	/*
+ 	 * Copy the original list to the new list, then append the
+ 	 * new member and NULL terminate the result.  This new list
+ 	 * is returned to the invoker.
+ 	 */
+ 
+ 	for (i = j = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member))
+ 			tmp[j++] = list[i];
+ 
+ 	tmp[j] = (char *) 0;
+ 
+ 	return tmp;
+ }
+ 
+ /*
+  * usage - display usage message and exit
+  */
+ 
+ usage ()
+ {
+ 	fprintf (stderr, "usage: userdel [-r] name\n");
+ 	exit (1);
+ }
+ 
+ /*
+  * update_groups - delete user from secondary group set
+  *
+  *	update_groups() takes the user name that was given and searches
+  *	the group files for membership in any group.
+  */
+ 
+ void
+ update_groups ()
+ {
+ 	int	i;
+ 	struct	group	*grp;
+ #ifdef	SHADOWGRP
+ 	struct	sgrp	*sgrp;
+ #endif
+ 
+ 	/*
+ 	 * Scan through the entire group file looking for the groups that
+ 	 * the user is a member of.
+ 	 */
+ 
+ 	for (gr_rewind (), grp = gr_next ();grp;grp = gr_next ()) {
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;grp->gr_mem[i];i++)
+ 			if (strcmp (grp->gr_mem[i], user_name) == 0)
+ 				break;
+ 
+ 		if (grp->gr_mem[i] == (char *) 0)
+ 			continue;
+ 
+ 		/* 
+ 		 * Delete the username from the list of group members and
+ 		 * update the group entry to reflect the change.
+ 		 */
+ 
+ 		grp->gr_mem = del_list (grp->gr_mem, user_name);
+ 		if (! gr_update (grp)) {
+ 			fprintf (stderr, "%s: error updating group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! gr_dbm_update (grp)) {
+ 			fprintf (stderr, "%s: cannot update dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ 
+ #ifdef	SHADOWGRP
+ 	/*
+ 	 * Scan through the entire shadow group file looking for the groups
+ 	 * that the user is a member of.  Both the administrative list and
+ 	 * the ordinary membership list is checked.
+ 	 */
+ 
+ 	for (sgr_rewind (), sgrp = sgr_next ();sgrp;sgrp = sgr_next ()) {
+ 		int	group_changed = 0;
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;sgrp->sg_mem[i];i++)
+ 			if (strcmp (sgrp->sg_mem[i], user_name) == 0)
+ 				break;
+ 
+ 		if (sgrp->sg_mem[i]) {
+ 			sgrp->sg_mem = del_list (sgrp->sg_mem, user_name);
+ 			group_changed = 1;
+ 		}
+ 		for (i = 0;sgrp->sg_adm[i];i++)
+ 			if (strcmp (sgrp->sg_adm[i], user_name) == 0)
+ 				break;
+ 
+ 		if (sgrp->sg_adm[i]) {
+ 			sgrp->sg_adm = del_list (sgrp->sg_adm, user_name);
+ 			group_changed = 1;
+ 		}
+ 		if (! group_changed)
+ 			continue;
+ 
+ 		if (! sgr_update (sgrp)) {
+ 			fprintf (stderr, "%s: error updating group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! sgr_dbm_update (sgrp)) {
+ 			fprintf (stderr, "%s: cannot update dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ #endif
+ }
+ 
+ /*
+  * close_files - close all of the files that were opened
+  *
+  *	close_files() closes all of the files that were opened for this
+  *	new user.  This causes any modified entries to be written out.
+  */
+ 
+ close_files ()
+ {
+ 	if (! pw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite shadow password file\n",	
+ 			Prog);
+ 		exit (1);
+ 	}
+ 	if (! gr_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite group file\n",
+ 			Prog);
+ 		exit (1);
+ 	}
+ 	(void) gr_unlock ();
+ #ifdef	SHADOWGRP
+ 	if (! sgr_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite shadow group file\n",
+ 			Prog);
+ 		exit (1);
+ 	}
+ 	(void) sgr_unlock ();
+ #endif
+ 	(void) pw_unlock ();
+ }
+ 
+ /*
+  * open_files - lock and open the password files
+  *
+  *	open_files() opens the two password files.
+  */
+ 
+ open_files ()
+ {
+ 	if (! pw_lock ()) {
+ 		fprintf (stderr, "%s: unable to lock password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! pw_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: unable to open password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_lock ()) {
+ 		fprintf ("%s: cannot lock shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_open (O_RDWR)) {
+ 		fprintf ("%s: cannot open shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! gr_lock ()) {
+ 		fprintf (stderr, "%s: unable to lock group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! gr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: cannot open group file\n", Prog);
+ 		exit (1);
+ 	}
+ #ifdef	SHADOWGRP
+ 	if (! sgr_lock ()) {
+ 		fprintf (stderr, "%s: unable to lock shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! sgr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: cannot open shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ #endif
+ }
+ 
+ /*
+  * update_user - delete the user entries
+  *
+  *	update_user() deletes the password file entries for this user
+  *	and will update the group entries as required.
+  */
+ 
+ update_user ()
+ {
+ 	struct	passwd	*pwd;
+ 
+ 	if (! pw_remove (user_name))
+ 		fprintf (stderr, "%s: error deleting password entry\n", Prog);
+ 
+ 	if (! spw_remove (user_name))
+ 		fprintf (stderr, "%s: error deleting shadow password entry\n",
+ 			Prog);
+ 
+ #if defined(DBM) || defined(NDBM)
+ 	if (access ("/etc/passwd.pag", 0) == 0) {
+ 		if ((pwd = getpwnam (user_name)) && ! pw_dbm_remove (pwd))
+ 			fprintf (stderr,
+ 				"%s: error deleting password dbm entry\n",
+ 				Prog);
+ 	}
+ 
+ 	/*
+ 	 * If the user's UID is a duplicate the duplicated entry needs
+ 	 * to be updated so that a UID match can be found in the DBM
+ 	 * files.
+ 	 */
+ 
+ 	for (pw_rewind (), pwd = pw_next ();pwd;pwd = pw_next ()) {
+ 		if (pwd->pw_uid == user_id) {
+ 			pw_dbm_update (pwd);
+ 			break;
+ 		}
+ 	}
+ #endif
+ #ifdef	NDBM
+ 	if (access ("/etc/shadow.pag", 0) == 0 && ! sp_dbm_remove (user_name))
+ 		fprintf (stderr, "%s: error deleting shadow passwd dbm entry\n",
+ 			Prog);
+ #endif
+ }
+ 
+ /*
+  * main - useradd command
+  */
+ 
+ main (argc, argv)
+ int	argc;
+ char	**argv;
+ {
+ 	struct	passwd	*pwd;
+ 	int	arg;
+ 	extern	int	optind;
+ 	extern	char	*optarg;
+ 
+ 	/*
+ 	 * Get my name so that I can use it to report errors.
+ 	 */
+ 
+ 	if (Prog = strrchr (argv[0], '/'))
+ 		Prog++;
+ 	else
+ 		Prog = argv[0];
+ 
+ #ifdef	USE_SYSLOG
+ 	openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
+ #endif
+ 
+ 	/*
+ 	 * The open routines for the DBM files don't use read-write
+ 	 * as the mode, so we have to clue them in.
+ 	 */
+ 
+ #if defined(DBM) || defined(NDBM)
+ 	pw_dbm_mode = O_RDWR;
+ #endif
+ #ifdef	NDBM
+ 	sp_dbm_mode = O_RDWR;
+ 	gr_dbm_mode = O_RDWR;
+ #ifdef	SHADOWGRP
+ 	sg_dbm_mode = O_RDWR;
+ #endif
+ #endif
+ 	while ((arg = getopt (argc, argv, "r")) != EOF)
+ 		if (arg != 'r')
+ 			usage ();
+ 		else
+ 			rflg++;
+ 	
+ 	if (optind == argc)
+ 		usage ();
+ 
+ 	/*
+ 	 * Start with a quick check to see if the user exists.
+ 	 */
+ 
+ 	strncpy (user_name, argv[argc - 1], BUFSIZ);
+ 
+ 	if (! (pwd = getpwnam (user_name))) {
+ 		fprintf (stderr, "%s: user %s does not exist\n",
+ 			Prog, user_name);
+ 		exit (1);
+ 	}
+ 	user_id = pwd->pw_uid;
+ 	strcpy (user_home, pwd->pw_dir);
+ 
+ 	/*
+ 	 * Do the hard stuff - open the files, create the user entries,
+ 	 * create the home directory, then close and update the files.
+ 	 */
+ 
+ 	open_files ();
+ 
+ 	update_user ();
+ 	update_groups ();
+ 
+ 	if (rflg)
+ 		rmdir (user_home);
+ 
+ 	close_files ();
+ 	exit (0);
+ 	/*NOTREACHED*/
+ }
*** /dev/null	Fri Jun  7 11:02:59 1991
--- usermod.c	Fri Jun  7 11:10:08 1991
***************
*** 0 ****
--- 1,1149 ----
+ /*
+  * Copyright 1991, John F. Haugh II
+  * All rights reserved.
+  *
+  * Permission is granted to copy and create derivative works for any
+  * non-commercial purpose, provided this copyright notice is preserved
+  * in all copies of source code, or included in human readable form
+  * and conspicuously displayed on all copies of object code or
+  * distribution media.
+  */
+ 
+ #ifndef lint
+ static	char	sccsid[] = "@(#)usermod.c	3.1	11:08:35	6/7/91";
+ #endif
+ 
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <stdio.h>
+ #include <errno.h>
+ #include <pwd.h>
+ #include <grp.h>
+ #include <ctype.h>
+ #include <fcntl.h>
+ #include <time.h>
+ 
+ #ifdef	BSD
+ #include <strings.h>
+ #else
+ #include <string.h>
+ #endif
+ 
+ #include "config.h"
+ #include "shadow.h"
+ 
+ #ifdef	USE_SYSLOG
+ #include <syslog.h>
+ 
+ #ifndef	LOG_WARN
+ #define	LOG_WARN LOG_WARNING
+ #endif
+ #endif
+ 
+ #ifndef	NGROUPS_MAX
+ #define	NGROUPS_MAX	64
+ #endif
+ 
+ char	user_name[BUFSIZ];
+ char	user_newname[BUFSIZ];
+ uid_t	user_id;
+ uid_t	user_newid;
+ gid_t	user_gid;
+ char	user_comment[BUFSIZ];
+ char	user_home[BUFSIZ];
+ char	user_newhome[BUFSIZ];
+ char	user_shell[BUFSIZ];
+ long	user_expire;
+ long	user_inactive;
+ int	user_ngroups;
+ gid_t	user_groups[NGROUPS_MAX];
+ struct	passwd	user_pwd;
+ struct	spwd	user_spwd;
+ 
+ char	*Prog;
+ 
+ int	uflg;	/* specify user ID for new account                            */
+ int	oflg;	/* permit non-unique user ID to be specified with -u          */
+ int	gflg;	/* primary group ID  for new account                          */
+ int	Gflg;	/* secondary group set for new account                        */
+ int	dflg;	/* home directory for new account                             */
+ int	sflg;	/* shell program for new account                              */
+ int	cflg;	/* comment (GECOS) field for new account                      */
+ int	mflg;	/* create user's home directory if it doesn't exist           */
+ int	fflg;	/* days until account with expired password is locked         */
+ int	eflg;	/* days after password changed before it becomes expired      */
+ int	lflg;	/* new user name for user                                     */
+ 
+ #if defined(DBM) || defined(NDBM)
+ extern	int	pw_dbm_mode;
+ #endif
+ #ifdef	NDBM
+ extern	int	sp_dbm_mode;
+ extern	int	gr_dbm_mode;
+ extern	int	sg_dbm_mode;
+ #endif
+ extern	FILE	*fopen();
+ extern	int	fclose();
+ extern	char	*malloc();
+ extern	char	*mktemp();
+ 
+ extern	struct	group	*getgrnam();
+ extern	struct	group	*getgrgid();
+ extern	struct	group	*gr_next();
+ extern	struct	group	*gr_locate();
+ extern	int	gr_lock();
+ extern	int	gr_unlock();
+ extern	int	gr_rewind();
+ extern	int	gr_open();
+ 
+ #ifdef	SHADOWGRP
+ extern	struct	sgrp	*sgr_next();
+ extern	int	sgr_lock();
+ extern	int	sgr_unlock();
+ extern	int	sgr_rewind();
+ extern	int	sgr_open();
+ #endif
+ 
+ extern	struct	passwd	*getpwnam();
+ extern	struct	passwd	*pw_next();
+ extern	struct	passwd	*pw_locate();
+ extern	int	pw_lock();
+ extern	int	pw_unlock();
+ extern	int	pw_rewind();
+ extern	int	pw_open();
+ 
+ extern	int	spw_lock();
+ extern	int	spw_unlock();
+ extern	int	spw_open();
+ extern	int	spw_locate();
+ 
+ #define	DAY	(24L*3600L)
+ #define	WEEK	(7*DAY)
+ 
+ #ifdef	ITI_AGING
+ #define	SCALE	(1)
+ #else
+ #define	SCALE	(DAY)
+ #endif
+ 
+ /*
+  * days and juldays are used to compute the number of days in the
+  * current month, and the cummulative number of days in the preceding
+  * months.  they are declared so that january is 1, not 0.
+  */
+ 
+ static	short	days[13] = { 0,
+ 	31,	28,	31,	30,	31,	30,	/* JAN - JUN */
+ 	31,	31,	30,	31,	30,	31 };	/* JUL - DEC */
+ 
+ static	short	juldays[13] = { 0,
+ 	0,	31,	59,	90,	120,	151,	/* JAN - JUN */
+ 	181,	212,	243,	273,	304,	334 };	/* JUL - DEC */
+ 
+ #ifdef	NEED_MKDIR
+ /*
+  * mkdir - create a directory
+  *
+  *	mkdir is provided for systems which do not include the mkdir()
+  *	system call.
+  */
+ 
+ int
+ mkdir (dir, mode)
+ char	*dir;
+ int	mode;
+ {
+ 	int	status;
+ 
+ 	if (fork ()) {
+ 		while (wait (&status) != -1)
+ 			;
+ 
+ 		return status >> 8;
+ 	}
+ #ifdef	USE_SYSLOG
+ 	closelog ();
+ #endif
+ 	close (2);
+ 	open ("/dev/null", O_WRONLY);
+ 	umask (0777 & ~ mode);
+ 	execl ("/bin/mkdir", "mkdir", dir, 0);
+ 	_exit (128);
+ }
+ #endif
+ #ifdef	NEED_RENAME
+ /*
+  * rename - rename a file to another name
+  *
+  *	rename is provided for systems which do not include the rename()
+  *	system call.
+  */
+ 
+ int
+ rename (begin, end)
+ char	*begin;
+ char	*end;
+ {
+ 	struct	stat	s1, s2;
+ 	extern	int	errno;
+ 	int	orig_err = errno;
+ 
+ 	if (stat (begin, &s1))
+ 		return -1;
+ 
+ 	if (stat (end, &s2)) {
+ 		errno = orig_err;
+ 	} else {
+ 
+ 		/*
+ 		 * See if this is a cross-device link.  We do this to
+ 		 * insure that the link below has a chance of working.
+ 		 */
+ 
+ 		if (s1.st_dev != s2.st_dev) {
+ 			errno = EXDEV;
+ 			return -1;
+ 		}
+ 
+ 		/*
+ 		 * See if we can unlink the existing destination
+ 		 * file.  If the unlink works the directory is writable,
+ 		 * so there is no need here to figure that out.
+ 		 */
+ 
+ 		if (unlink (end))
+ 			return -1;
+ 	}
+ 
+ 	/*
+ 	 * Now just link the original name to the final name.  If there
+ 	 * was no file previously, this link will fail if the target
+ 	 * directory isn't writable.  The unlink will fail if the source
+ 	 * directory isn't writable, but life stinks ...
+ 	 */
+ 
+ 	if (link (begin, end) || unlink (begin))
+ 		return -1;
+ 
+ 	return 0;
+ }
+ #endif
+ 
+ /*
+  * strtoday - compute the number of days since 1970.
+  *
+  * the total number of days prior to the current date is
+  * computed.  january 1, 1970 is used as the origin with
+  * it having a day number of 0.
+  */
+ 
+ long
+ strtoday (str)
+ char	*str;
+ {
+ 	char	slop[2];
+ 	int	month;
+ 	int	day;
+ 	int	year;
+ 	long	total;
+ 
+ 	/*
+ 	 * start by separating the month, day and year.  this is
+ 	 * a chauvanistic program - it only takes date input in
+ 	 * the standard USA format.
+ 	 */
+ 
+ 	if (sscanf (str, "%d/%d/%d%c", &month, &day, &year, slop) != 3)
+ 		return -1;
+ 
+ 	/*
+ 	 * the month, day of the month, and year are checked for
+ 	 * correctness and the year adjusted so it falls between
+ 	 * 1970 and 2069.
+ 	 */
+ 
+ 	if (month < 1 || month > 12)
+ 		return -1;
+ 
+ 	if (day < 1)
+ 		return -1;
+ 
+ 	if ((month != 2 || (year % 4) != 0) && day > days[month])
+ 		return -1;
+ 	else if ((month == 2 && (year % 4) == 0) && day > 29)
+ 		return -1;
+ 
+ 	if (year < 0)
+ 		return -1;
+ 	else if (year < 69)
+ 		year += 2000;
+ 	else if (year < 99)
+ 		year += 1900;
+ 
+ 	if (year < 1970 || year > 2069)
+ 		return -1;
+ 
+ 	/*
+ 	 * the total number of days is the total number of days in all
+ 	 * the whole years, plus the number of leap days, plus the
+ 	 * number of days in the whole months preceding, plus the number
+ 	 * of days so far in the month.
+ 	 */
+ 
+ 	total = (long) ((year - 1970) * 365L) + (((year + 1) - 1970) / 4);
+ 	total += (long) juldays[month] + (month > 2 && (year % 4) == 0 ? 1:0);
+ 	total += (long) day - 1;
+ 
+ 	return total;
+ }
+ 
+ /*
+  * add_list - add a member to a list of group members
+  *
+  *	the array of member names is searched for the new member
+  *	name, and if not present it is added to a freshly allocated
+  *	list of users.
+  */
+ 
+ char **
+ add_list (list, member)
+ char	**list;
+ char	*member;
+ {
+ 	int	i;
+ 	char	**tmp;
+ 
+ 	/*
+ 	 * Scan the list for the new name.  Return the original list
+ 	 * pointer if it is present.
+ 	 */
+ 
+ 	for (i = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member) == 0)
+ 			return list;
+ 
+ 	/*
+ 	 * Allocate a new list pointer large enough to hold all the
+ 	 * old entries, and the new entries as well.
+ 	 */
+ 
+ 	if (! (tmp = (char **) malloc ((i + 2) * sizeof member)))
+ 		return 0;
+ 
+ 	/*
+ 	 * Copy the original list to the new list, then append the
+ 	 * new member and NULL terminate the result.  This new list
+ 	 * is returned to the invoker.
+ 	 */
+ 
+ 	for (i = 0;list[i] != (char *) 0;i++)
+ 		tmp[i] = list[i];
+ 
+ 	tmp[i++] = strdup (member);
+ 	tmp[i] = (char *) 0;
+ 
+ 	return tmp;
+ }
+ 
+ /*
+  * del_list - delete a member from a list of group members
+  *
+  *	the array of member names is searched for the old member
+  *	name, and if present it is deleted from a freshly allocated
+  *	list of users.
+  */
+ 
+ char **
+ del_list (list, member)
+ char	**list;
+ char	*member;
+ {
+ 	int	i, j;
+ 	char	**tmp;
+ 
+ 	/*
+ 	 * Scan the list for the new name.  Return the original list
+ 	 * pointer if it is present.
+ 	 */
+ 
+ 	for (i = j = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member))
+ 			j++;
+ 
+ 	if (j == i)
+ 		return list;
+ 
+ 	/*
+ 	 * Allocate a new list pointer large enough to hold all the
+ 	 * old entries, and the new entries as well.
+ 	 */
+ 
+ 	if (! (tmp = (char **) malloc ((j + 2) * sizeof member)))
+ 		return 0;
+ 
+ 	/*
+ 	 * Copy the original list to the new list, then append the
+ 	 * new member and NULL terminate the result.  This new list
+ 	 * is returned to the invoker.
+ 	 */
+ 
+ 	for (i = j = 0;list[i] != (char *) 0;i++)
+ 		if (strcmp (list[i], member))
+ 			tmp[j++] = list[i];
+ 
+ 	tmp[j] = (char *) 0;
+ 
+ 	return tmp;
+ }
+ 
+ /*
+  * get_groups - convert a list of group names to an array of group IDs
+  *
+  *	get_groups() takes a comma-separated list of group names and
+  *	converts it to an array of group ID values.  Any unknown group
+  *	names are reported as errors.
+  */
+ 
+ int
+ get_groups (list)
+ char	*list;
+ {
+ 	char	*cp;
+ 	struct	group	*grp;
+ 	int	errors = 0;
+ 
+ 	/*
+ 	 * Initialize the list to be empty
+ 	 */
+ 
+ 	user_ngroups = 0;
+ 
+ 	if (! *list)
+ 		return 0;
+ 
+ 	/*
+ 	 * So long as there is some data to be converted, strip off
+ 	 * each name and look it up.  A mix of numerical and string
+ 	 * values for group identifiers is permitted.
+ 	 */
+ 
+ 	do {
+ 		/*
+ 		 * Strip off a single name from the list
+ 		 */
+ 
+ 		if (cp = strchr (list, ','))
+ 			*cp++ = '\0';
+ 
+ 		/*
+ 		 * Names starting with digits are treated as numerical
+ 		 * GID values, otherwise the string is looked up as is.
+ 		 */
+ 
+ 		if (isdigit (*list))
+ 			grp = getgrgid (atoi (list));
+ 		else
+ 			grp = getgrnam (list);
+ 
+ 		/*
+ 		 * There must be a match, either by GID value or by
+ 		 * string name.
+ 		 */
+ 
+ 		if (! grp) {
+ 			fprintf (stderr, "%s: unknown group %s\n", Prog, list);
+ 			errors++;
+ 		}
+ 
+ 		/*
+ 		 * Add the GID value from the group file to the user's
+ 		 * list of groups.
+ 		 */
+ 
+ 		user_groups[user_ngroups++] = grp->gr_gid;
+ 
+ 		list = cp;
+ 	} while (list);
+ 
+ 	/*
+ 	 * Any errors in finding group names are fatal
+ 	 */
+ 
+ 	if (errors)
+ 		return -1;
+ 
+ 	return 0;
+ }
+ 
+ /*
+  * usage - display usage message and exit
+  */
+ 
+ usage ()
+ {
+ 	fprintf (stderr,
+ 		"usage: %s [-u uid [-o]] [-g group] [-G group,...] \n", Prog);
+ 	fprintf (stderr,
+ 		"\t\t[-d home [-m]] [-s shell] [-c comment] [-l new_name]\n");
+ 	fprintf (stderr,
+ 		"\t\t[-f inactive] [-e expire] name\n");
+ 
+ 	exit (2);
+ }
+ 
+ /*
+  * new_pwent - initialize the values in a password file entry
+  *
+  *	new_pwent() takes all of the values that have been entered and
+  *	fills in a (struct passwd) with them.
+  */
+ 
+ void
+ new_pwent (pwent)
+ struct	passwd	*pwent;
+ {
+ 	if (lflg)
+ 		pwent->pw_name = strdup (user_newname);
+ 
+ 	if (uflg)
+ 		pwent->pw_uid = user_newid;
+ 
+ 	if (gflg)
+ 		pwent->pw_gid = user_gid;
+ 
+ 	if (cflg)
+ 		pwent->pw_gecos = strdup (user_comment);
+ 
+ 	if (dflg)
+ 		pwent->pw_dir = strdup (user_newhome);
+ 
+ 	if (sflg)
+ 		pwent->pw_shell = strdup (user_shell);
+ }
+ 
+ /*
+  * new_spent - initialize the values in a shadow password file entry
+  *
+  *	new_spent() takes all of the values that have been entered and
+  *	fills in a (struct spwd) with them.
+  */
+ 
+ void
+ new_spent (spent)
+ struct	spwd	*spent;
+ {
+ 	if (lflg)
+ 		spent->sp_namp = strdup (user_newname);
+ 
+ 	spent->sp_inact = user_inactive;
+ 	spent->sp_expire = user_expire;
+ }
+ 
+ /*
+  * grp_update - add user to secondary group set
+  *
+  *	grp_update() takes the secondary group set given in user_groups
+  *	and adds the user to each group given by that set.
+  */
+ 
+ void
+ grp_update ()
+ {
+ 	int	i;
+ 	int	is_member;
+ 	int	was_member;
+ 	int	was_admin;
+ 	struct	group	*grp;
+ 	struct	sgrp	*sgrp;
+ 
+ 	/*
+ 	 * Lock and open the group file.  This will load all of the group
+ 	 * entries.
+ 	 */
+ 
+ 	if (! gr_lock ()) {
+ 		fprintf (stderr, "%s: error locking group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! gr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: error opening group file\n", Prog);
+ 		exit (1);
+ 	}
+ #ifdef	SHADOWGRP
+ 	if (! sgr_lock ()) {
+ 		fprintf (stderr, "%s: error locking shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! sgr_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: error opening shadow group file\n", Prog);
+ 		exit (1);
+ 	}
+ #endif
+ 
+ 	/*
+ 	 * Scan through the entire group file looking for the groups that
+ 	 * the user is a member of.
+ 	 */
+ 
+ 	for (gr_rewind (), grp = gr_next ();grp;grp = gr_next ()) {
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;i < user_ngroups;i++)
+ 			if (grp->gr_gid == user_groups[i])
+ 				break;
+ 
+ 		is_member = i == user_ngroups ? -1:i;
+ 
+ 		for (i = 0;grp->gr_mem[i];i++)
+ 			if (strcmp (user_name, grp->gr_mem[i]) == 0)
+ 				break;
+ 
+ 		was_member = grp->gr_mem[i] ? i:-1;
+ 
+ 		if (is_member == -1 && was_member == -1)
+ 			continue;
+ 
+ 		if (was_member >= 0 && (! Gflg || is_member >= 0)) {
+ 			if (lflg) {
+ 				grp->gr_mem = del_list (grp->gr_mem,
+ 					user_name);
+ 				grp->gr_mem = add_list (grp->gr_mem,
+ 					user_newname);
+ #ifdef	USE_SYSLOG
+ 				syslog (LOG_INFO,
+ 					"change `%s' to `%s' in group `%s'\n",
+ 					user_name, user_newname, grp->gr_name);
+ #endif
+ 			}
+ 		} else if (was_member >= 0 && Gflg && is_member < 0) {
+ 			grp->gr_mem = del_list (grp->gr_mem, user_name);
+ #ifdef	USE_SYSLOG
+ 			syslog (LOG_INFO, "delete `%s' from group `%s'\n",
+ 				user_name, grp->gr_name);
+ #endif
+ 		} else if (was_member < 0 && Gflg && is_member >= 0) {
+ 			grp->gr_mem = add_list (grp->gr_mem,
+ 				lflg ? user_newname:user_name);
+ #ifdef	USE_SYSLOG
+ 			syslog (LOG_INFO, "add `%s' to group `%s'\n",
+ 				lflg ? user_newname:user_name, grp->gr_name);
+ #endif
+ 		} else
+ 			continue;
+ 
+ 		if (! gr_update (grp)) {
+ 			fprintf (stderr, "%s: error adding new group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! gr_dbm_update (grp)) {
+ 			fprintf (stderr, "%s: cannot add new dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ 
+ #ifdef	SHADOWGRP
+ 	/*
+ 	 * Scan through the entire shadow group file looking for the groups
+ 	 * that the user is a member of.
+ 	 */
+ 
+ 	for (sgr_rewind (), sgrp = sgr_next ();sgrp;sgrp = sgr_next ()) {
+ 
+ 		/*
+ 		 * See if the user was a member of this group
+ 		 */
+ 
+ 		for (i = 0;sgrp->sg_mem[i];i++)
+ 			if (strcmp (sgrp->sg_mem[i], user_name) == 0)
+ 				break;
+ 
+ 		was_member = sgrp->sg_mem[i] ? i:-1;
+ 
+ 		/*
+ 		 * See if the user was an administrator of this group
+ 		 */
+ 
+ 		for (i = 0;sgrp->sg_adm[i];i++)
+ 			if (strcmp (sgrp->sg_adm[i], user_name) == 0)
+ 				break;
+ 
+ 		was_admin = sgrp->sg_adm[i] ? i:-1;
+ 
+ 		/*
+ 		 * See if the user specified this group as one of their
+ 		 * concurrent groups.
+ 		 */
+ 
+ 		for (i = 0;i < user_ngroups;i++) {
+ 			if (! (grp = gr_locate (sgrp->sg_name)))
+ 				continue;
+ 
+ 			if (grp->gr_gid == user_groups[i])
+ 				break;
+ 		}
+ 		is_member = i == user_ngroups ? -1:i;
+ 
+ 		if (is_member == -1 && was_admin == -1 && was_member == -1)
+ 			continue;
+ 
+ 		if (was_admin >= 0 && lflg) {
+ 			sgrp->sg_adm = del_list (sgrp->sg_adm, user_name);
+ 			sgrp->sg_adm = add_list (sgrp->sg_adm, user_newname);
+ #ifdef	USE_SYSLOG
+ 			syslog (LOG_INFO, "change admin `%s' to `%s' in shadow group `%s'\n",
+ 				user_name, user_newname, sgrp->sg_name);
+ #endif
+ 		}
+ 		if (was_member >= 0 && (! Gflg || is_member >= 0)) {
+ 			if (lflg) {
+ 				sgrp->sg_mem = del_list (sgrp->sg_mem,
+ 					user_name);
+ 				sgrp->sg_mem = add_list (sgrp->sg_mem,
+ 					user_newname);
+ #ifdef	USE_SYSLOG
+ 				syslog (LOG_INFO, "change `%s' to `%s' in shadow group `%s'\n",
+ 					user_name, user_newname, sgrp->sg_name);
+ #endif
+ 			}
+ 		} else if (was_member >= 0 && Gflg && is_member < 0) {
+ 			sgrp->sg_mem = del_list (sgrp->sg_mem, user_name);
+ #ifdef	USE_SYSLOG
+ 			syslog (LOG_INFO, "delete `%s' from shadow group `%s'\n",
+ 				user_name, sgrp->sg_name);
+ #endif
+ 		} else if (was_member < 0 && Gflg && is_member >= 0) {
+ 			sgrp->sg_mem = add_list (sgrp->sg_mem,
+ 				lflg ? user_newname:user_name);
+ #ifdef	USE_SYSLOG
+ 			syslog (LOG_INFO, "add `%s' to shadow group `%s'\n",
+ 				lflg ? user_newname:user_name, grp->gr_name);
+ #endif
+ 		} else
+ 			continue;
+ 
+ 		/* 
+ 		 * Update the group entry to reflect the changes.
+ 		 */
+ 
+ 		if (! sgr_update (sgrp)) {
+ 			fprintf (stderr, "%s: error adding new group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #ifdef	NDBM
+ 		/*
+ 		 * Update the DBM group file with the new entry as well.
+ 		 */
+ 
+ 		if (! sgr_dbm_update (sgrp)) {
+ 			fprintf (stderr, "%s: cannot add new dbm group entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ #endif
+ }
+ 
+ /*
+  * check_new_id - verify the new UID for uniqueness
+  *
+  *	check_new_id() insures that the new UID does not exist already.
+  *	It does this by checking that the UID has changed and that there
+  *	is no current entry for this user ID.
+  */
+ 
+ int
+ check_new_id ()
+ {
+ 	/*
+ 	 * First, the easy stuff.  If the ID can be duplicated, or if
+ 	 * the ID didn't really change, just return.  If the ID didn't
+ 	 * change, turn off those flags.  No sense doing needless work.
+ 	 */
+ 
+ 	if (oflg)
+ 		return 0;
+ 
+ 	if (user_id == user_newid) {
+ 		uflg = lflg = 0;
+ 		return 0;
+ 	}
+ 	if (getpwuid (user_newid))
+ 		return -1;
+ 
+ 	return 0;
+ }
+ 
+ /*
+  * process_flags - perform command line argument setting
+  *
+  *	process_flags() interprets the command line arguments and sets
+  *	the values that the user will be created with accordingly.  The
+  *	values are checked for sanity.
+  */
+ 
+ void
+ process_flags (argc, argv)
+ int	argc;
+ char	**argv;
+ {
+ 	extern	int	optind;
+ 	extern	char	*optarg;
+ 	struct	group	*grp;
+ 	struct	passwd	*pwd;
+ 	struct	spwd	*spwd;
+ 	long	l;
+ 	int	anyflag = 0;
+ 	int	arg;
+ 
+ 	if (argc == 1 || argv[argc - 1][0] == '-')
+ 		usage ();
+ 
+ 	if (! (pwd = getpwnam (argv[argc - 1]))) {
+ 		fprintf (stderr, "%s: user %s does not exist\n",
+ 			Prog, argv[argc - 1]);
+ 		exit (6);
+ 	}
+ 	strcpy (user_name, pwd->pw_name);
+ 	user_id = pwd->pw_uid;
+ 	user_gid = pwd->pw_gid;
+ 	strcpy (user_comment, pwd->pw_gecos);
+ 	strcpy (user_home, pwd->pw_dir);
+ 	strcpy (user_shell, pwd->pw_shell);
+ 
+ 	if (spwd = getspnam (user_name)) {
+ 		user_expire = spwd->sp_expire;
+ 		user_inactive = spwd->sp_inact;
+ 	}
+ 	while ((arg = getopt (argc, argv, "u:og:G:d:s:c:mf:e:l:")) != EOF) {
+ 		switch (arg) {
+ 			case 'c':
+ 				if (strcmp (optarg, user_comment)) {
+ 					cflg++;
+ 					strncpy (user_comment, optarg, BUFSIZ);
+ 				}
+ 				break;
+ 			case 'd':
+ 				dflg++;
+ 				strncpy (user_newhome, optarg, BUFSIZ);
+ 				break;
+ 			case 'e':
+ 				l = strtoday (optarg);
+ #ifdef	ITI_AGING
+ 				l *= DAY;
+ #endif
+ 				if (l != user_expire) {
+ 					eflg++;
+ 					user_expire = l;
+ 				}
+ 				break;
+ 			case 'f':
+ 				if (user_inactive != atoi (optarg)) {
+ 					fflg++;
+ 					user_inactive = atoi (optarg);
+ 				}
+ 				break;
+ 			case 'g':
+ 				if (isdigit (optarg[0]))
+ 					grp = getgrgid (atoi (optarg));
+ 				else
+ 					grp = getgrnam (optarg);
+ 
+ 				if (! grp) {
+ 					fprintf (stderr,
+ 						"%s: unknown group %s\n",
+ 						optarg);
+ 					exit (1);
+ 				}
+ 				if (grp->gr_gid != user_gid) {
+ 					gflg++;
+ 					user_gid = grp->gr_gid;
+ 				}
+ 				break;
+ 			case 'G':
+ 				Gflg++;
+ 				if (get_groups (optarg))
+ 					exit (1);
+ 
+ 				break;
+ 			case 'l':
+ 				if (strcmp (user_name, optarg)) {
+ 					lflg++;
+ 					strcpy (user_newname, optarg);
+ 				}
+ 				break;
+ 			case 'm':
+ 				if (! dflg)
+ 					usage ();
+ 
+ 				mflg++;
+ 				break;
+ 			case 'o':
+ 				if (! uflg)
+ 					usage ();
+ 
+ 				oflg++;
+ 				break;
+ 			case 's':
+ 				if (strcmp (user_shell, optarg)) {
+ 					strncpy (user_shell, optarg, BUFSIZ);
+ 					sflg++;
+ 				}
+ 				break;
+ 			case 'u':
+ 				uflg++;
+ 				user_newid = atoi (optarg);
+ 				break;
+ 			default:
+ 				usage ();
+ 		}
+ 		anyflag++;
+ 	}
+ 	if (optind != argc - 1)
+ 		usage ();
+ 
+ 	if (dflg && strcmp (user_home, user_newhome) == 0)
+ 		dflg = mflg = 0;
+ 
+ 	if (uflg && user_id == user_newid)
+ 		uflg = oflg = 0;
+ 
+ 	if (lflg && getpwnam (user_newname)) {
+ 		fprintf (stderr, "%s: user %s exists\n", user_newname);
+ 		exit (9);
+ 	}
+ }
+ 
+ /*
+  * close_files - close all of the files that were opened
+  *
+  *	close_files() closes all of the files that were opened for this
+  *	new user.  This causes any modified entries to be written out.
+  */
+ 
+ close_files ()
+ {
+ 	if (! pw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_close ()) {
+ 		fprintf (stderr, "%s: cannot rewrite shadow password file\n",	
+ 			Prog);
+ 		exit (1);
+ 	}
+ 	if (user_ngroups > 0) {
+ 		if (! gr_close ()) {
+ 			fprintf (stderr, "%s: cannot rewrite group file\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 		(void) gr_unlock ();
+ #ifdef	SHADOWGRP
+ 		if (! sgr_close ()) {
+ 			fprintf (stderr, "%s: cannot rewrite shadow group file\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ #endif
+ 	}
+ 	(void) pw_unlock ();
+ }
+ 
+ /*
+  * open_files - lock and open the password files
+  *
+  *	open_files() opens the two password files.
+  */
+ 
+ open_files ()
+ {
+ 	if (! pw_lock ()) {
+ 		fprintf (stderr, "%s: unable to lock password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! pw_open (O_RDWR)) {
+ 		fprintf (stderr, "%s: unable to open password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_lock ()) {
+ 		fprintf ("%s: cannot lock shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ 	if (! spw_open (O_RDWR)) {
+ 		fprintf ("%s: cannot open shadow password file\n", Prog);
+ 		exit (1);
+ 	}
+ }
+ 
+ /*
+  * usr_update - create the user entries
+  *
+  *	usr_update() creates the password file entries for this user
+  *	and will update the group entries if required.
+  */
+ 
+ usr_update ()
+ {
+ 	struct	passwd	pwent;
+ 	struct	spwd	spent;
+ 	struct	passwd	*pwd;
+ 	struct	spwd	*spwd;
+ 
+ 	pwd = pw_locate (user_name);
+ 	pwent = *pwd;
+ 
+ 	spwd = spw_locate (user_name);
+ 	spent = *spwd;
+ 
+ 	new_pwent (&pwent);
+ 	new_spent (&spent);
+ 
+ 	if (lflg || uflg || gflg || cflg || dflg || sflg) {
+ 		if (! pw_update (&pwent)) {
+ 			fprintf (stderr, "%s: error changing password entry\n");
+ 			exit (1);
+ 		}
+ 		if (lflg && ! pw_remove (user_name)) {
+ 			fprintf (stderr, "%s: error removing password entry\n");
+ 			exit (1);
+ 		}
+ #if defined(DBM) || defined(NDBM)
+ 		if (access ("/etc/passwd.pag", 0) == 0) {
+ 			if (! pw_dbm_update (&pwent)) {
+ 				fprintf (stderr, "%s: error adding password dbm entry\n",
+ 					Prog);
+ 				exit (1);
+ 			}
+ 			if (lflg && (pwd = getpwnam (user_name)) && ! pw_dbm_remove (pwd)) {
+ 				fprintf (stderr, "%s: error removing passwd dbm entry\n",
+ 					Prog);
+ 				exit (1);
+ 			}
+ 		}
+ #endif
+ 	}
+ 	if (lflg || eflg || fflg) {
+ 		if (! spw_update (&spent)) {
+ 			fprintf (stderr, "%s: error adding new shadow password entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 		if (lflg && ! spw_remove (user_name)) {
+ 			fprintf (stderr, "%s: error removing shadow password entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 	}
+ #ifdef	NDBM
+ 	if (access ("/etc/shadow.pag", 0) == 0) {
+ 		if (! sp_dbm_update (&spent)) {
+ 			fprintf (stderr, "%s: error updating shadow passwd dbm entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 		if (lflg && ! sp_dbm_remove (user_name)) {
+ 			fprintf (stderr, "%s: error removing shadow passwd db entry\n",
+ 				Prog);
+ 			exit (1);
+ 		}
+ 	}
+ #endif
+ 	if (Gflg || lflg)
+ 		grp_update ();
+ }
+ 
+ /*
+  * move_home - move the user's home directory
+  *
+  *	move_home() creates the user's home directory if it does not
+  *	already exist.  It will be created mode 755 owned by the user
+  *	with the user's default group.
+  */
+ 
+ move_home ()
+ {
+ 	if (mflg && access (user_home, 0) == 0) {
+ 		if (access (user_newhome, 0) == 0) {
+ 			fprintf (stderr, "%s: directory %s exists\n",
+ 				Prog, user_newhome);
+ 			exit (12);
+ 		} else if (rename (user_home, user_newhome)) {
+ 			fprintf (stderr,
+ 				"%s: cannot rename directory %s to %s\n",
+ 				Prog, user_home, user_newhome);
+ 			exit (12);
+ 		}
+ 	}
+ 	if (uflg || gflg)
+ 		chown (dflg ? user_newhome:user_home, user_id, user_gid);
+ }
+ 
+ /*
+  * main - useradd command
+  */
+ 
+ main (argc, argv)
+ int	argc;
+ char	**argv;
+ {
+ 	/*
+ 	 * Get my name so that I can use it to report errors.
+ 	 */
+ 
+ 	if (Prog = strrchr (argv[0], '/'))
+ 		Prog++;
+ 	else
+ 		Prog = argv[0];
+ 
+ #ifdef	USE_SYSLOG
+ 	openlog (Prog, LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
+ #endif
+ 
+ 	/*
+ 	 * The open routines for the DBM files don't use read-write
+ 	 * as the mode, so we have to clue them in.
+ 	 */
+ 
+ #if defined(DBM) || defined(NDBM)
+ 	pw_dbm_mode = O_RDWR;
+ #endif
+ #ifdef	NDBM
+ 	sp_dbm_mode = O_RDWR;
+ 	gr_dbm_mode = O_RDWR;
+ #ifdef	SHADOWGRP
+ 	sg_dbm_mode = O_RDWR;
+ #endif
+ #endif
+ 	process_flags (argc, argv);
+ 
+ 	/*
+ 	 * Do the hard stuff - open the files, change the user entries,
+ 	 * change the home directory, then close and update the files.
+ 	 */
+ 
+ 	open_files ();
+ 
+ 	usr_update ();
+ 
+ 	close_files ();
+ 
+ 	if (mflg)
+ 		move_home ();
+ 
+ 	exit (0);
+ 	/*NOTREACHED*/
+ }
-- 
John F. Haugh II        | Distribution to  | UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 255-8251 | GEnie PROHIBITED :-) |  Domain: jfh@rpp386.cactus.org
"If liberals interpreted the 2nd Amendment the same way they interpret the
 rest of the Constitution, gun ownership would be mandatory."