Shadow Login Suite, version 3 (part 7 of 8)

John F Haugh II jfh at rpp386.cactus.org
Fri May 17 02:32:17 AEST 1991


#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	groupio.c
#	shadowio.c
#	sgroupio.c
# This archive created: Sun Mar  3 13:27:37 1991
# By:	John F Haugh II (River Parishes Programming, Austin TX)
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'groupio.c'" '(10707 characters)'
if test -f 'groupio.c'
then
	echo shar: "will not over-write existing file 'groupio.c'"
else
sed 's/^X//' << \SHAR_EOF > 'groupio.c'
X/*
X * Copyright 1990, John F. Haugh II
X * An unpublished work.
X * All rights reserved.
X *
X * Use, duplication, and disclosure prohibited without
X * the express written permission of the author.
X *
X *	This file implements a transaction oriented group database
X *	library.  The group file is updated one entry at a time.
X *	After each transaction the file must be logically closed and
X *	transferred to the existing group file.  The sequence of
X *	events is
X *
X *	gr_lock				-- lock group file
X *	gr_open				-- logically open group file
X *	while transaction to process
X *		gr_(locate,update,remove) -- perform transaction
X *	done
X *	gr_close			-- commit transactions
X *	gr_unlock			-- remove group lock
X */
X
X#include <sys/stat.h>
X#include <fcntl.h>
X#include <errno.h>
X#include <grp.h>
X#include <stdio.h>
X#ifdef	BSD
X#include <strings.h>
X#else
X#include <string.h>
X#endif
X
X#ifndef	lint
Xstatic	char	sccsid[] = "@(#)groupio.c	3.6 15:59:12 12/9/90";
X#endif
X
Xstatic	int	islocked;
Xstatic	int	isopen;
Xstatic	int	open_modes;
Xstatic	FILE	*grfp;
X
Xstruct	gr_file_entry {
X	char	*grf_line;
X	int	grf_changed;
X	struct	group	*grf_entry;
X	struct	gr_file_entry *grf_next;
X};
X
Xstatic	struct	gr_file_entry	*grf_head;
Xstatic	struct	gr_file_entry	*grf_tail;
Xstatic	struct	gr_file_entry	*grf_cursor;
Xstatic	int	gr_changed;
Xstatic	int	lock_pid;
X
X#define	GR_LOCK	"/etc/group.lock"
X#define	GR_TEMP "/etc/grp.%d"
X#define	GROUP	"/etc/group"
X
Xstatic	char	gr_filename[BUFSIZ] = GROUP;
X
Xextern	char	*strdup();
Xextern	struct	group	*sgetgrent();
X
X/*
X * gr_dup - duplicate a group file entry
X *
X *	gr_dup() accepts a pointer to a group file entry and
X *	returns a pointer to a group file entry in allocated
X *	memory.
X */
X
Xstatic struct group *
Xgr_dup (grent)
Xstruct	group	*grent;
X{
X	struct	group	*gr;
X	int	i;
X
X	if (! (gr = (struct group *) malloc (sizeof *gr)))
X		return 0;
X
X	if ((gr->gr_name = strdup (grent->gr_name)) == 0 ||
X			(gr->gr_passwd = strdup (grent->gr_passwd)) == 0)
X		return 0;
X
X	for (i = 0;grent->gr_mem[i];i++)
X		;
X
X	gr->gr_mem = (char **) malloc (sizeof (char *) * (i + 1));
X	for (i = 0;grent->gr_mem[i];i++)
X		if (! (gr->gr_mem[i] = strdup (grent->gr_mem[i])))
X			return 0;
X
X	gr->gr_mem[i] = 0;
X	gr->gr_gid = grent->gr_gid;
X
X	return gr;
X}
X
X/*
X * gr_free - free a dynamically allocated group file entry
X *
X *	gr_free() frees up the memory which was allocated for the
X *	pointed to entry.
X */
X
Xstatic void
Xgr_free (grent)
Xstruct	group	*grent;
X{
X	int	i;
X
X	free (grent->gr_name);
X	free (grent->gr_passwd);
X
X	for (i = 0;grent->gr_mem[i];i++)
X		free (grent->gr_mem[i]);
X
X	free (grent->gr_mem);
X}
X
X/*
X * gr_name - change the name of the group file
X */
X
Xint
Xgr_name (name)
Xchar	*name;
X{
X	if (isopen || strlen (name) > (BUFSIZ-10))
X		return -1;
X
X	strcpy (gr_filename, name);
X	return 0;
X}
X
X/*
X * gr_lock - lock a group file
X *
X *	gr_lock() encapsulates the lock operation.  it returns
X *	TRUE or FALSE depending on the group file being
X *	properly locked.  the lock is set by creating a semaphore
X *	file, GR_LOCK.
X */
X
Xint
Xgr_lock ()
X{
X	int	fd;
X	int	pid;
X	int	len;
X	char	file[BUFSIZ];
X	char	buf[32];
X	struct	stat	sb;
X
X	if (islocked)
X		return 1;
X
X	if (strcmp (gr_filename, GROUP) != 0)
X		return 0;
X
X	/*
X	 * Create a lock file which can be switched into place
X	 */
X
X	sprintf (file, GR_TEMP, lock_pid = getpid ());
X	if ((fd = open (file, O_CREAT|O_EXCL|O_WRONLY, 0600)) == -1)
X		return 0;
X
X	sprintf (buf, "%d", lock_pid);
X	if (write (fd, buf, strlen (buf) + 1) != strlen (buf) + 1) {
X		(void) close (fd);
X		(void) unlink (file);
X		return 0;
X	}
X	close (fd);
X
X	/*
X	 * Simple case first -
X	 *	Link fails (in a sane environment ...) if the target
X	 *	exists already.  So we try to switch in a new lock
X	 *	file.  If that succeeds, we assume we have the only
X	 *	valid lock.  Needs work for NFS where this assumption
X	 *	may not hold.  The simple hack is to check the link
X	 *	count on the source file, which should be 2 iff the
X	 *	link =really= worked.
X	 */
X
X	if (link (file, GR_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X
X	/*
X	 * Invalid lock test -
X	 *	Open the lock file and see if the lock is valid.
X	 *	The PID of the lock file is checked, and if the PID
X	 *	is not valid, the lock file is removed.  If the unlink
X	 *	of the lock file fails, it should mean that someone
X	 *	else is executing this code.  They will get success,
X	 *	and we will fail.
X	 */
X
X	if ((fd = open (GR_LOCK, O_RDWR)) == -1 ||
X			(len = read (fd, buf, BUFSIZ)) <= 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	buf[len] = '\0';
X	if ((pid = strtol (buf, (char **) 0, 10)) == 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (kill (pid, 0) == 0)  {
X		errno = EEXIST;
X		return 0;
X	}
X	if (unlink (GR_LOCK)) {
X		(void) close (fd);
X		(void) unlink (file);
X
X		return 0;
X	}
X
X	/*
X	 * Re-try lock -
X	 *	The invalid lock has now been removed and I should
X	 *	be able to acquire a lock for myself just fine.  If
X	 *	this fails there will be no retry.  The link count
X	 *	test here makes certain someone executing the previous
X	 *	block of code didn't just remove the lock we just
X	 *	linked to.
X	 */
X
X	if (link (file, GR_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X	(void) unlink (file);
X	return 0;
X}
X
X/*
X * gr_unlock - logically unlock a group file
X *
X *	gr_unlock() removes the lock which was set by an earlier
X *	invocation of gr_lock().
X */
X
Xint
Xgr_unlock ()
X{
X	if (isopen) {
X		open_modes = O_RDONLY;
X		if (! gr_close ())
X			return 0;
X	}
X	if (islocked) {
X		islocked = 0;
X		if (lock_pid != getpid ())
X			return 0;
X
X		(void) unlink (GR_LOCK);
X		return 1;
X	}
X	return 0;
X}
X
X/*
X * gr_open - open a group file
X *
X *	gr_open() encapsulates the open operation.  it returns
X *	TRUE or FALSE depending on the group file being
X *	properly opened.
X */
X
Xint
Xgr_open (mode)
Xint	mode;
X{
X	char	buf[8192];
X	char	*cp;
X	struct	gr_file_entry	*grf;
X	struct	group	*grent;
X
X	if (isopen || (mode != O_RDONLY && mode != O_RDWR))
X		return 0;
X
X	if (mode != O_RDONLY && ! islocked &&
X			strcmp (gr_filename, GROUP) == 0)
X		return 0;
X
X	if ((grfp = fopen (gr_filename, mode == O_RDONLY ? "r":"r+")) == 0)
X		return 0;
X
X	grf_head = grf_tail = grf_cursor = 0;
X	gr_changed = 0;
X
X	while (fgetsx (buf, sizeof buf, grfp) != (char *) 0) {
X		if (cp = strrchr (buf, '\n'))
X			*cp = '\0';
X
X		if (! (grf = (struct gr_file_entry *) malloc (sizeof *grf)))
X			return 0;
X
X		grf->grf_changed = 0;
X		grf->grf_line = strdup (buf);
X		if ((grent = sgetgrent (buf)) && ! (grent = gr_dup (grent)))
X			return 0;
X
X		grf->grf_entry = grent;
X
X		if (grf_head == 0) {
X			grf_head = grf_tail = grf;
X			grf->grf_next = 0;
X		} else {
X			grf_tail->grf_next = grf;
X			grf->grf_next = 0;
X			grf_tail = grf;
X		}
X	}
X	isopen++;
X	open_modes = mode;
X
X	return 1;
X}
X
X/*
X * gr_close - close the group file
X *
X *	gr_close() outputs any modified group file entries and
X *	frees any allocated memory.
X */
X
Xint
Xgr_close ()
X{
X	char	backup[BUFSIZ];
X	int	fd;
X	int	mask;
X	int	c;
X	int	i;
X	int	errors = 0;
X	FILE	*bkfp;
X	struct	gr_file_entry *grf;
X	struct	gr_file_entry *ogrf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (islocked && lock_pid != getpid ()) {
X		isopen = 0;
X		islocked = 0;
X		errno = EACCES;
X		return 0;
X	}
X	strcpy (backup, gr_filename);
X	strcat (backup, "-");
X
X	if (open_modes == O_RDWR && gr_changed) {
X		mask = umask (0222);
X		if ((bkfp = fopen (backup, "w")) == 0) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		rewind (grfp);
X		while ((c = getc (grfp)) != EOF) {
X			if (putc (c, bkfp) == EOF) {
X				fclose (bkfp);
X				return 0;
X			}
X		}
X		if (fclose (bkfp))
X			return 0;
X
X		isopen = 0;
X		(void) fclose (grfp);
X
X		mask = umask (0222);
X		if (! (grfp = fopen (gr_filename, "w"))) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		for (grf = grf_head;! errors && grf;grf = grf->grf_next) {
X			if (grf->grf_changed) {
X				if (putgrent (grf->grf_entry, grfp))
X					errors++;
X			} else {
X				if (fputsx (grf->grf_line, grfp))
X					errors++;
X
X				if (putc ('\n', grfp) == EOF)
X					errors++;
X			}
X		}
X		if (fflush (grfp))
X			errors++;
X
X		if (errors) {
X			unlink (gr_filename);
X			link (backup, gr_filename);
X			unlink (backup);
X			return 0;
X		}
X	}
X	if (fclose (grfp))
X		return 0;
X
X	grfp = 0;
X
X	while (grf_head != 0) {
X		grf = grf_head;
X		grf_head = grf->grf_next;
X
X		if (grf->grf_entry) {
X			gr_free (grf->grf_entry);
X			free (grf->grf_entry);
X		}
X		if (grf->grf_line)
X			free (grf->grf_line);
X
X		free (grf);
X	}
X	grf_tail = 0;
X	return 1;
X}
X
Xint
Xgr_update (grent)
Xstruct	group	*grent;
X{
X	struct	gr_file_entry	*grf;
X	struct	group	*ngr;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (grf = grf_head;grf != 0;grf = grf->grf_next) {
X		if (grf->grf_entry == 0)
X			continue;
X
X		if (strcmp (grent->gr_name, grf->grf_entry->gr_name) != 0)
X			continue;
X
X		if (! (ngr = gr_dup (grent)))
X			return 0;
X		else {
X			gr_free (grf->grf_entry);
X			*(grf->grf_entry) = *ngr;
X		}
X		grf->grf_changed = 1;
X		grf_cursor = grf;
X		return gr_changed = 1;
X	}
X	grf = (struct gr_file_entry *) malloc (sizeof *grf);
X	if (! (grf->grf_entry = gr_dup (grent)))
X		return 0;
X
X	grf->grf_changed = 1;
X	grf->grf_next = 0;
X	grf->grf_line = 0;
X
X	if (grf_tail)
X		grf_tail->grf_next = grf;
X
X	if (! grf_head)
X		grf_head = grf;
X
X	grf_tail = grf;
X
X	return gr_changed = 1;
X}
X
Xint
Xgr_remove (name)
Xchar	*name;
X{
X	struct	gr_file_entry	*grf;
X	struct	gr_file_entry	*ogrf;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (ogrf = 0, grf = grf_head;grf != 0;
X			ogrf = grf, grf = grf->grf_next) {
X		if (! grf->grf_entry)
X			continue;
X
X		if (strcmp (name, grf->grf_entry->gr_name) != 0)
X			continue;
X
X		if (grf == grf_cursor)
X			grf_cursor = ogrf;
X
X		if (ogrf != 0)
X			ogrf->grf_next = grf->grf_next;
X		else
X			grf_head = grf->grf_next;
X
X		if (grf == grf_tail)
X			grf_tail = ogrf;
X
X		return gr_changed = 1;
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xstruct group *
Xgr_locate (name)
Xchar	*name;
X{
X	struct	gr_file_entry	*grf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (grf = grf_head;grf != 0;grf = grf->grf_next) {
X		if (grf->grf_entry == 0)
X			continue;
X
X		if (strcmp (name, grf->grf_entry->gr_name) == 0) {
X			grf_cursor = grf;
X			return grf->grf_entry;
X		}
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xint
Xgr_rewind ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	grf_cursor = 0;
X	return 1;
X}
X
Xstruct group *
Xgr_next ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (grf_cursor == 0)
X		grf_cursor = grf_head;
X	else
X		grf_cursor = grf_cursor->grf_next;
X
X	while (grf_cursor) {
X		if (grf_cursor->grf_entry)
X			return grf_cursor->grf_entry;
X
X		grf_cursor = grf_cursor->grf_next;
X	}
X	return 0;
X}
SHAR_EOF
if test 10707 -ne "`wc -c < 'groupio.c'`"
then
	echo shar: "error transmitting 'groupio.c'" '(should have been 10707 characters)'
fi
fi
echo shar: "extracting 'shadowio.c'" '(10637 characters)'
if test -f 'shadowio.c'
then
	echo shar: "will not over-write existing file 'shadowio.c'"
else
sed 's/^X//' << \SHAR_EOF > 'shadowio.c'
X/*
X * Copyright 1990, John F. Haugh II
X * All rights reserved.
X *
X * Use, duplication, and disclosure prohibited without
X * the express written permission of the author.
X *
X *	This file implements a transaction oriented password database
X *	library.  The password file is updated one entry at a time.
X *	After each transaction the file must be logically closed and
X *	transferred to the existing password file.  The sequence of
X *	events is
X *
X *	spw_lock			-- lock shadow file
X *	spw_open			-- logically open shadow file
X *	while transaction to process
X *		spw_(locate,update,remove) -- perform transaction
X *	done
X *	spw_close			-- commit transactions
X *	spw_unlock			-- remove shadow lock
X */
X
X#ifndef	lint
Xstatic	char	sccsid[] = "@(#)shadowio.c	3.4	07:54:20	12/1/90";
X#endif
X
X#include <sys/stat.h>
X#include <fcntl.h>
X#include <errno.h>
X#include <stdio.h>
X#ifdef	BSD
X#include <strings.h>
X#else
X#include <string.h>
X#endif
X#include "shadow.h"
X
Xstatic	int	islocked;
Xstatic	int	isopen;
Xstatic	int	open_modes;
Xstatic	FILE	*spwfp;
X
Xstruct	spw_file_entry {
X	char	*spwf_line;
X	int	spwf_changed;
X	struct	spwd	*spwf_entry;
X	struct	spw_file_entry *spwf_next;
X};
X
Xstatic	struct	spw_file_entry	*spwf_head;
Xstatic	struct	spw_file_entry	*spwf_tail;
Xstatic	struct	spw_file_entry	*spwf_cursor;
Xstatic	int	sp_changed;
Xstatic	int	lock_pid;
X
X#define	SPW_LOCK	"/etc/shadow.lock"
X#define	SPW_TEMP	"/etc/spwd.%d"
X#define	SHADOW		"/etc/shadow"
X
Xstatic	char	spw_filename[BUFSIZ] = SHADOW;
X
Xextern	char	*strdup();
Xextern	struct	spwd	*sgetspent();
X
X/*
X * spw_dup - duplicate a shadow file entry
X *
X *	spw_dup() accepts a pointer to a shadow file entry and
X *	returns a pointer to a shadow file entry in allocated
X *	memory.
X */
X
Xstatic struct spwd *
Xspw_dup (spwd)
Xstruct	spwd	*spwd;
X{
X	struct	spwd	*spw;
X
X	if (! (spw = (struct spwd *) malloc (sizeof *spw)))
X		return 0;
X
X	*spw = *spwd;
X	if ((spw->sp_namp = strdup (spwd->sp_namp)) == 0 ||
X			(spw->sp_pwdp = strdup (spwd->sp_pwdp)) == 0)
X		return 0;
X
X	return spw;
X}
X
X/*
X * spw_free - free a dynamically allocated shadow file entry
X *
X *	spw_free() frees up the memory which was allocated for the
X *	pointed to entry.
X */
X
Xstatic void
Xspw_free (spwd)
Xstruct	spwd	*spwd;
X{
X	free (spwd->sp_namp);
X	free (spwd->sp_pwdp);
X}
X
X/*
X * spw_name - change the name of the shadow password file
X */
X
Xint
Xspw_name (name)
Xchar	*name;
X{
X	if (isopen || strlen (name) > (BUFSIZ-10))
X		return -1;
X
X	strcpy (spw_filename, name);
X	return 0;
X}
X
X/*
X * spw_lock - lock a password file
X *
X *	spw_lock() encapsulates the lock operation.  it returns
X *	TRUE or FALSE depending on the password file being
X *	properly locked.  the lock is set by creating a semaphore
X *	file, SPW_LOCK.
X */
X
Xint
Xspw_lock ()
X{
X	int	fd;
X	int	pid;
X	int	len;
X	char	file[BUFSIZ];
X	char	buf[32];
X	struct	stat	sb;
X
X	if (islocked)
X		return 1;
X
X	if (strcmp (spw_filename, SHADOW) != 0)
X		return 0;
X
X	/*
X	 * Create a lock file which can be switched into place
X	 */
X
X	sprintf (file, SPW_TEMP, lock_pid = getpid ());
X	if ((fd = open (file, O_CREAT|O_EXCL|O_WRONLY, 0600)) == -1)
X		return 0;
X
X	sprintf (buf, "%d", lock_pid);
X	if (write (fd, buf, strlen (buf) + 1) != strlen (buf) + 1) {
X		(void) close (fd);
X		(void) unlink (file);
X		return 0;
X	}
X	close (fd);
X
X	/*
X	 * Simple case first -
X	 *	Link fails (in a sane environment ...) if the target
X	 *	exists already.  So we try to switch in a new lock
X	 *	file.  If that succeeds, we assume we have the only
X	 *	valid lock.  Needs work for NFS where this assumption
X	 *	may not hold.  The simple hack is to check the link
X	 *	count on the source file, which should be 2 iff the
X	 *	link =really= worked.
X	 */
X
X	if (link (file, SPW_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X
X	/*
X	 * Invalid lock test -
X	 *	Open the lock file and see if the lock is valid.
X	 *	The PID of the lock file is checked, and if the PID
X	 *	is not valid, the lock file is removed.  If the unlink
X	 *	of the lock file fails, it should mean that someone
X	 *	else is executing this code.  They will get success,
X	 *	and we will fail.
X	 */
X
X	if ((fd = open (SPW_LOCK, O_RDWR)) == -1 ||
X			(len = read (fd, buf, BUFSIZ)) <= 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	buf[len] = '\0';
X	if ((pid = strtol (buf, (char **) 0, 10)) == 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (kill (pid, 0) == 0)  {
X		errno = EEXIST;
X		return 0;
X	}
X	if (unlink (SPW_LOCK)) {
X		(void) close (fd);
X		(void) unlink (file);
X
X		return 0;
X	}
X
X	/*
X	 * Re-try lock -
X	 *	The invalid lock has now been removed and I should
X	 *	be able to acquire a lock for myself just fine.  If
X	 *	this fails there will be no retry.  The link count
X	 *	test here makes certain someone executing the previous
X	 *	block of code didn't just remove the lock we just
X	 *	linked to.
X	 */
X
X	if (link (file, SPW_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X	(void) unlink (file);
X	return 0;
X}
X
X/*
X * spw_unlock - logically unlock a shadow file
X *
X *	spw_unlock() removes the lock which was set by an earlier
X *	invocation of spw_lock().
X */
X
Xint
Xspw_unlock ()
X{
X	if (isopen) {
X		open_modes = O_RDONLY;
X		if (! spw_close ())
X			return 0;
X	}
X  	if (islocked) {
X  		islocked = 0;
X		if (lock_pid != getpid ())
X			return 0;
X
X		(void) unlink (SPW_LOCK);
X		return 1;
X	}
X	return 0;
X}
X
X/*
X * spw_open - open a password file
X *
X *	spw_open() encapsulates the open operation.  it returns
X *	TRUE or FALSE depending on the shadow file being
X *	properly opened.
X */
X
Xint
Xspw_open (mode)
Xint	mode;
X{
X	char	buf[BUFSIZ];
X	char	*cp;
X	struct	spw_file_entry	*spwf;
X	struct	spwd	*spwd;
X
X	if (isopen || (mode != O_RDONLY && mode != O_RDWR))
X		return 0;
X
X	if (mode != O_RDONLY && ! islocked &&
X			strcmp (spw_filename, SHADOW) == 0)
X		return 0;
X
X	if ((spwfp = fopen (spw_filename, mode == O_RDONLY ? "r":"r+")) == 0)
X		return 0;
X
X	spwf_head = spwf_tail = spwf_cursor = 0;
X	sp_changed = 0;
X
X	while (fgets (buf, sizeof buf, spwfp) != (char *) 0) {
X		if (cp = strrchr (buf, '\n'))
X			*cp = '\0';
X
X		if (! (spwf = (struct spw_file_entry *) malloc (sizeof *spwf)))
X			return 0;
X
X		spwf->spwf_changed = 0;
X		spwf->spwf_line = strdup (buf);
X		if ((spwd = sgetspent (buf)) && ! (spwd = spw_dup (spwd)))
X			return 0;
X
X		spwf->spwf_entry = spwd;
X
X		if (spwf_head == 0) {
X			spwf_head = spwf_tail = spwf;
X			spwf->spwf_next = 0;
X		} else {
X			spwf_tail->spwf_next = spwf;
X			spwf->spwf_next = 0;
X			spwf_tail = spwf;
X		}
X	}
X	isopen++;
X	open_modes = mode;
X
X	return 1;
X}
X
X/*
X * spw_close - close the password file
X *
X *	spw_close() outputs any modified password file entries and
X *	frees any allocated memory.
X */
X
Xint
Xspw_close ()
X{
X	char	backup[BUFSIZ];
X	int	fd;
X	int	mask;
X	int	c;
X	int	i;
X	int	errors = 0;
X	FILE	*bkfp;
X	struct	spw_file_entry *spwf;
X	struct	spw_file_entry *ospwf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (islocked && lock_pid != getpid ()) {
X		isopen = 0;
X		islocked = 0;
X		errno = EACCES;
X		return 0;
X	}
X	strcpy (backup, spw_filename);
X	strcat (backup, "-");
X
X	if (open_modes == O_RDWR && sp_changed) {
X		mask = umask (077);
X		if ((bkfp = fopen (backup, "w")) == 0) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		rewind (spwfp);
X		while ((c = getc (spwfp)) != EOF) {
X			if (putc (c, bkfp) == EOF) {
X				fclose (bkfp);
X				return 0;
X			}
X		}
X		if (fclose (bkfp))
X			return 0;
X
X		isopen = 0;
X		(void) fclose (spwfp);
X
X		mask = umask (077);
X		if (! (spwfp = fopen (spw_filename, "w"))) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		for (spwf = spwf_head;errors == 0 && spwf;
X						spwf = spwf->spwf_next) {
X			if (spwf->spwf_changed) {
X				if (putspent (spwf->spwf_entry, spwfp))
X					errors++;
X			} else {
X				if (fputs (spwf->spwf_line, spwfp) == EOF)
X					errors++;
X				if (putc ('\n', spwfp) == EOF)
X					errors++;
X			}
X		}
X		if (fflush (spwfp))
X			errors++;
X
X		if (errors) {
X			unlink (spw_filename);
X			link (backup, spw_filename);
X			unlink (backup);
X			return 0;
X		}
X	}
X	if (fclose (spwfp))
X		return 0;
X
X	spwfp = 0;
X
X	while (spwf_head != 0) {
X		spwf = spwf_head;
X		spwf_head = spwf->spwf_next;
X
X		if (spwf->spwf_entry) {
X			spw_free (spwf->spwf_entry);
X			free (spwf->spwf_entry);
X		}
X		if (spwf->spwf_line)
X			free (spwf->spwf_line);
X
X		free (spwf);
X	}
X	spwf_tail = 0;
X	return 1;
X}
X
Xint
Xspw_update (spwd)
Xstruct	spwd	*spwd;
X{
X	struct	spw_file_entry	*spwf;
X	struct	spwd	*nspwd;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (spwf = spwf_head;spwf != 0;spwf = spwf->spwf_next) {
X		if (spwf->spwf_entry == 0)
X			continue;
X
X		if (strcmp (spwd->sp_namp, spwf->spwf_entry->sp_namp) != 0)
X			continue;
X
X		if (! (nspwd = spw_dup (spwd)))
X			return 0;
X		else {
X			spw_free (spwf->spwf_entry);
X			*(spwf->spwf_entry) = *nspwd;
X		}
X		spwf->spwf_changed = 1;
X		spwf_cursor = spwf;
X		return sp_changed = 1;
X	}
X	spwf = (struct spw_file_entry *) malloc (sizeof *spwf);
X	if (! (spwf->spwf_entry = spw_dup (spwd)))
X		return 0;
X
X	spwf->spwf_changed = 1;
X	spwf->spwf_next = 0;
X	spwf->spwf_line = 0;
X
X	if (spwf_tail)
X		spwf_tail->spwf_next = spwf;
X
X	if (! spwf_head)
X		spwf_head = spwf;
X
X	spwf_tail = spwf;
X
X	return sp_changed = 1;
X}
X
Xint
Xspw_remove (name)
Xchar	*name;
X{
X	struct	spw_file_entry	*spwf;
X	struct	spw_file_entry	*ospwf;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (ospwf = 0, spwf = spwf_head;spwf != 0;
X			ospwf = spwf, spwf = spwf->spwf_next) {
X		if (! spwf->spwf_entry)
X			continue;
X
X		if (strcmp (name, spwf->spwf_entry->sp_namp) != 0)
X			continue;
X
X		if (spwf == spwf_cursor)
X			spwf_cursor = ospwf;
X
X		if (ospwf != 0)
X			ospwf->spwf_next = spwf->spwf_next;
X		else
X			spwf_head = spwf->spwf_next;
X
X		if (spwf == spwf_tail)
X			spwf_tail = ospwf;
X
X		return sp_changed = 1;
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xstruct spwd *
Xspw_locate (name)
Xchar	*name;
X{
X	struct	spw_file_entry	*spwf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (spwf = spwf_head;spwf != 0;spwf = spwf->spwf_next) {
X		if (spwf->spwf_entry == 0)
X			continue;
X
X		if (strcmp (name, spwf->spwf_entry->sp_namp) == 0) {
X			spwf_cursor = spwf;
X			return spwf->spwf_entry;
X		}
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xint
Xspw_rewind ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	spwf_cursor = 0;
X	return 1;
X}
X
Xstruct spwd *
Xspw_next ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (spwf_cursor == 0)
X		spwf_cursor = spwf_head;
X	else
X		spwf_cursor = spwf_cursor->spwf_next;
X
X	while (spwf_cursor) {
X		if (spwf_cursor->spwf_entry)
X			return spwf_cursor->spwf_entry;
X
X		spwf_cursor = spwf_cursor->spwf_next;
X	}
X	return 0;
X}
SHAR_EOF
if test 10637 -ne "`wc -c < 'shadowio.c'`"
then
	echo shar: "error transmitting 'shadowio.c'" '(should have been 10637 characters)'
fi
fi
echo shar: "extracting 'sgroupio.c'" '(11482 characters)'
if test -f 'sgroupio.c'
then
	echo shar: "will not over-write existing file 'sgroupio.c'"
else
sed 's/^X//' << \SHAR_EOF > 'sgroupio.c'
X/*
X * Copyright 1990, John F. Haugh II
X * All rights reserved.
X *
X * Permission is granted to copy and create derivative works for any
X * non-commercial purpose, provided this copyright notice is preserved
X * in all copies of source code, or included in human readable form
X * and conspicuously displayed on all copies of object code or
X * distribution media.
X *
X *	This file implements a transaction oriented shadow group
X *	database library.  The shadow group file is updated one
X *	entry at a time.  After each transaction the file must be
X *	logically closed and transferred to the existing shadow
X *	group file.  The sequence of events is
X *
X *	sgr_lock			-- lock shadow group file
X *	sgr_open			-- logically open shadow group file
X *	while transaction to process
X *		sgr_(locate,update,remove) -- perform transaction
X *	done
X *	sgr_close			-- commit transactions
X *	sgr_unlock			-- remove shadow group lock
X */
X
X#include <sys/stat.h>
X#include <fcntl.h>
X#include <errno.h>
X#include <stdio.h>
X#ifdef	BSD
X#include <strings.h>
X#define	strchr	index
X#define	strrchr	rindex
X#else
X#include <string.h>
X#endif
X#include "shadow.h"
X
X#ifndef	lint
Xstatic	char	sccsid[] = "@(#)sgroupio.c	3.1	08:13:51	12/14/90";
X#endif
X
Xstatic	int	islocked;
Xstatic	int	isopen;
Xstatic	int	open_modes;
Xstatic	FILE	*sgrfp;
X
Xstruct	sg_file_entry {
X	char	*sgr_line;
X	int	sgr_changed;
X	struct	sgrp	*sgr_entry;
X	struct	sg_file_entry *sgr_next;
X};
X
Xstatic	struct	sg_file_entry	*sgr_head;
Xstatic	struct	sg_file_entry	*sgr_tail;
Xstatic	struct	sg_file_entry	*sgr_cursor;
Xstatic	int	sgr_changed;
Xstatic	int	lock_pid;
X
X#define	SG_LOCK	"/etc/gshadow.lock"
X#define	GR_TEMP "/etc/gshadow.%d"
X#define	SGROUP	"/etc/gshadow"
X
Xstatic	char	sg_filename[BUFSIZ] = SGROUP;
X
Xextern	char	*strdup();
Xextern	struct	sgrp	*sgetgsent();
Xextern	char	*fgetsx();
X
X/*
X * sgr_dup - duplicate a shadow group file entry
X *
X *	sgr_dup() accepts a pointer to a shadow group file entry and
X *	returns a pointer to a shadow group file entry in allocated memory.
X */
X
Xstatic struct sgrp *
Xsgr_dup (sgrent)
Xstruct	sgrp	*sgrent;
X{
X	struct	sgrp	*sgr;
X	int	i;
X
X	if (! (sgr = (struct sgrp *) malloc (sizeof *sgr)))
X		return 0;
X
X	if ((sgr->sg_name = strdup (sgrent->sg_name)) == 0 ||
X			(sgr->sg_passwd = strdup (sgrent->sg_passwd)) == 0)
X		return 0;
X
X	for (i = 0;sgrent->sg_mem[i];i++)
X		;
X
X	sgr->sg_mem = (char **) malloc (sizeof (char *) * (i + 1));
X	for (i = 0;sgrent->sg_mem[i];i++)
X		if (! (sgr->sg_mem[i] = strdup (sgrent->sg_mem[i])))
X			return 0;
X
X	sgr->sg_mem[i] = 0;
X
X	sgr->sg_adm = (char **) malloc (sizeof (char *) * (i + 1));
X	for (i = 0;sgrent->sg_adm[i];i++)
X		if (! (sgr->sg_adm[i] = strdup (sgrent->sg_adm[i])))
X			return 0;
X
X	sgr->sg_adm[i] = 0;
X
X	return sgr;
X}
X
X/*
X * sgr_free - free a dynamically allocated shadow group file entry
X *
X *	sgr_free() frees up the memory which was allocated for the
X *	pointed to entry.
X */
X
Xstatic void
Xsgr_free (sgrent)
Xstruct	sgrp	*sgrent;
X{
X	int	i;
X
X	free (sgrent->sg_name);
X	free (sgrent->sg_passwd);
X
X	for (i = 0;sgrent->sg_mem[i];i++)
X		free (sgrent->sg_mem[i]);
X
X	free (sgrent->sg_mem);
X
X	for (i = 0;sgrent->sg_adm[i];i++)
X		free (sgrent->sg_adm[i]);
X
X	free (sgrent->sg_adm);
X}
X
X/*
X * sgr_name - change the name of the shadow group file
X */
X
Xint
Xsgr_name (name)
Xchar	*name;
X{
X	if (isopen || strlen (name) > (BUFSIZ-10))
X		return -1;
X
X	strcpy (sg_filename, name);
X	return 0;
X}
X
X/*
X * sgr_lock - lock a shadow group file
X *
X *	sgr_lock() encapsulates the lock operation.  it returns
X *	TRUE or FALSE depending on the shadow group file being
X *	properly locked.  the lock is set by creating a semaphore
X *	file, SG_LOCK.
X */
X
Xint
Xsgr_lock ()
X{
X	int	fd;
X	int	pid;
X	int	len;
X	char	file[BUFSIZ];
X	char	buf[32];
X	struct	stat	sb;
X
X	if (islocked)
X		return 1;
X
X	if (strcmp (sg_filename, SGROUP) != 0)
X		return 0;
X
X	/*
X	 * Create a lock file which can be switched into place
X	 */
X
X	sprintf (file, GR_TEMP, lock_pid = getpid ());
X	if ((fd = open (file, O_CREAT|O_EXCL|O_WRONLY, 0600)) == -1)
X		return 0;
X
X	sprintf (buf, "%d", lock_pid);
X	if (write (fd, buf, strlen (buf) + 1) != strlen (buf) + 1) {
X		(void) close (fd);
X		(void) unlink (file);
X		return 0;
X	}
X	close (fd);
X
X	/*
X	 * Simple case first -
X	 *	Link fails (in a sane environment ...) if the target
X	 *	exists already.  So we try to switch in a new lock
X	 *	file.  If that succeeds, we assume we have the only
X	 *	valid lock.  Needs work for NFS where this assumption
X	 *	may not hold.  The simple hack is to check the link
X	 *	count on the source file, which should be 2 iff the
X	 *	link =really= worked.
X	 */
X
X	if (link (file, SG_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X
X	/*
X	 * Invalid lock test -
X	 *	Open the lock file and see if the lock is valid.
X	 *	The PID of the lock file is checked, and if the PID
X	 *	is not valid, the lock file is removed.  If the unlink
X	 *	of the lock file fails, it should mean that someone
X	 *	else is executing this code.  They will get success,
X	 *	and we will fail.
X	 */
X
X	if ((fd = open (SG_LOCK, O_RDWR)) == -1 ||
X			(len = read (fd, buf, BUFSIZ)) <= 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	buf[len] = '\0';
X	if ((pid = strtol (buf, (char **) 0, 10)) == 0) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (kill (pid, 0) == 0)  {
X		errno = EEXIST;
X		return 0;
X	}
X	if (unlink (SG_LOCK)) {
X		(void) close (fd);
X		(void) unlink (file);
X
X		return 0;
X	}
X
X	/*
X	 * Re-try lock -
X	 *	The invalid lock has now been removed and I should
X	 *	be able to acquire a lock for myself just fine.  If
X	 *	this fails there will be no retry.  The link count
X	 *	test here makes certain someone executing the previous
X	 *	block of code didn't just remove the lock we just
X	 *	linked to.
X	 */
X
X	if (link (file, SG_LOCK) == 0) {
X		if (stat (file, &sb) != 0)
X			return 0;
X
X		if (sb.st_nlink != 2)
X			return 0;
X
X		(void) unlink (file);
X		islocked = 1;
X		return 1;
X	}
X	(void) unlink (file);
X	return 0;
X}
X
X/*
X * sgr_unlock - logically unlock a shadow group file
X *
X *	sgr_unlock() removes the lock which was set by an earlier
X *	invocation of sgr_lock().
X */
X
Xint
Xsgr_unlock ()
X{
X	if (isopen) {
X		open_modes = O_RDONLY;
X		if (! sgr_close ())
X			return 0;
X	}
X	if (islocked) {
X		islocked = 0;
X		if (lock_pid != getpid ())
X			return 0;
X
X		(void) unlink (SG_LOCK);
X		return 1;
X	}
X	return 0;
X}
X
X/*
X * sgr_open - open a shadow group file
X *
X *	sgr_open() encapsulates the open operation.  it returns
X *	TRUE or FALSE depending on the shadow group file being
X *	properly opened.
X */
X
Xint
Xsgr_open (mode)
Xint	mode;
X{
X	char	buf[8192];
X	char	*cp;
X	struct	sg_file_entry	*sgrf;
X	struct	sgrp	*sgrent;
X
X	if (isopen || (mode != O_RDONLY && mode != O_RDWR))
X		return 0;
X
X	if (mode != O_RDONLY && ! islocked &&
X			strcmp (sg_filename, SGROUP) == 0)
X		return 0;
X
X	if ((sgrfp = fopen (sg_filename, mode == O_RDONLY ? "r":"r+")) == 0)
X		return 0;
X
X	sgr_head = sgr_tail = sgr_cursor = 0;
X	sgr_changed = 0;
X
X	while (fgetsx (buf, sizeof buf, sgrfp) != (char *) 0) {
X		if (cp = strrchr (buf, '\n'))
X			*cp = '\0';
X
X		if (! (sgrf = (struct sg_file_entry *) malloc (sizeof *sgrf)))
X			return 0;
X
X		sgrf->sgr_changed = 0;
X		sgrf->sgr_line = strdup (buf);
X		if ((sgrent = sgetgsent (buf)) && ! (sgrent = sgr_dup (sgrent)))
X			return 0;
X
X		sgrf->sgr_entry = sgrent;
X
X		if (sgr_head == 0) {
X			sgr_head = sgr_tail = sgrf;
X			sgrf->sgr_next = 0;
X		} else {
X			sgr_tail->sgr_next = sgrf;
X			sgrf->sgr_next = 0;
X			sgr_tail = sgrf;
X		}
X	}
X	isopen++;
X	open_modes = mode;
X
X	return 1;
X}
X
X/*
X * sgr_close - close the shadow group file
X *
X *	sgr_close() outputs any modified shadow group file entries and
X *	frees any allocated memory.
X */
X
Xint
Xsgr_close ()
X{
X	char	backup[BUFSIZ];
X	int	fd;
X	int	mask;
X	int	c;
X	int	i;
X	int	errors = 0;
X	FILE	*bkfp;
X	struct	sg_file_entry *sgrf;
X	struct	sg_file_entry *osgrf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (islocked && lock_pid != getpid ()) {
X		isopen = 0;
X		islocked = 0;
X		errno = EACCES;
X		return 0;
X	}
X	strcpy (backup, sg_filename);
X	strcat (backup, "-");
X
X	if (open_modes == O_RDWR && sgr_changed) {
X		mask = umask (0222);
X		if ((bkfp = fopen (backup, "w")) == 0) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		rewind (sgrfp);
X		while ((c = getc (sgrfp)) != EOF) {
X			if (putc (c, bkfp) == EOF) {
X				fclose (bkfp);
X				return 0;
X			}
X		}
X		if (fclose (bkfp))
X			return 0;
X
X		isopen = 0;
X		(void) fclose (sgrfp);
X
X		mask = umask (0222);
X		if (! (sgrfp = fopen (sg_filename, "w"))) {
X			umask (mask);
X			return 0;
X		}
X		umask (mask);
X
X		for (sgrf = sgr_head;! errors && sgrf;sgrf = sgrf->sgr_next) {
X			if (sgrf->sgr_changed) {
X				if (putgsent (sgrf->sgr_entry, sgrfp))
X					errors++;
X			} else {
X				if (fputsx (sgrf->sgr_line, sgrfp))
X					errors++;
X
X				if (putc ('\n', sgrfp) == EOF)
X					errors++;
X			}
X		}
X		if (fflush (sgrfp))
X			errors++;
X
X		if (errors) {
X			unlink (sg_filename);
X			link (backup, sg_filename);
X			unlink (backup);
X			return 0;
X		}
X	}
X	if (fclose (sgrfp))
X		return 0;
X
X	sgrfp = 0;
X
X	while (sgr_head != 0) {
X		sgrf = sgr_head;
X		sgr_head = sgrf->sgr_next;
X
X		if (sgrf->sgr_entry) {
X			sgr_free (sgrf->sgr_entry);
X			free (sgrf->sgr_entry);
X		}
X		if (sgrf->sgr_line)
X			free (sgrf->sgr_line);
X
X		free (sgrf);
X	}
X	sgr_tail = 0;
X	return 1;
X}
X
Xint
Xsgr_update (sgrent)
Xstruct	sgrp	*sgrent;
X{
X	struct	sg_file_entry	*sgrf;
X	struct	sgrp	*nsgr;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (sgrf = sgr_head;sgrf != 0;sgrf = sgrf->sgr_next) {
X		if (sgrf->sgr_entry == 0)
X			continue;
X
X		if (strcmp (sgrent->sg_name, sgrf->sgr_entry->sg_name) != 0)
X			continue;
X
X		if (! (nsgr = sgr_dup (sgrent)))
X			return 0;
X		else {
X			sgr_free (sgrf->sgr_entry);
X			*(sgrf->sgr_entry) = *nsgr;
X		}
X		sgrf->sgr_changed = 1;
X		sgr_cursor = sgrf;
X		return sgr_changed = 1;
X	}
X	sgrf = (struct sg_file_entry *) malloc (sizeof *sgrf);
X	if (! (sgrf->sgr_entry = sgr_dup (sgrent)))
X		return 0;
X
X	sgrf->sgr_changed = 1;
X	sgrf->sgr_next = 0;
X	sgrf->sgr_line = 0;
X
X	if (sgr_tail)
X		sgr_tail->sgr_next = sgrf;
X
X	if (! sgr_head)
X		sgr_head = sgrf;
X
X	sgr_tail = sgrf;
X
X	return sgr_changed = 1;
X}
X
Xint
Xsgr_remove (name)
Xchar	*name;
X{
X	struct	sg_file_entry	*sgrf;
X	struct	sg_file_entry	*osgrf;
X
X	if (! isopen || open_modes == O_RDONLY) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (osgrf = 0, sgrf = sgr_head;sgrf != 0;
X			osgrf = sgrf, sgrf = sgrf->sgr_next) {
X		if (! sgrf->sgr_entry)
X			continue;
X
X		if (strcmp (name, sgrf->sgr_entry->sg_name) != 0)
X			continue;
X
X		if (sgrf == sgr_cursor)
X			sgr_cursor = osgrf;
X
X		if (osgrf != 0)
X			osgrf->sgr_next = sgrf->sgr_next;
X		else
X			sgr_head = sgrf->sgr_next;
X
X		if (sgrf == sgr_tail)
X			sgr_tail = osgrf;
X
X		return sgr_changed = 1;
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xstruct sgrp *
Xsgr_locate (name)
Xchar	*name;
X{
X	struct	sg_file_entry	*sgrf;
X
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	for (sgrf = sgr_head;sgrf != 0;sgrf = sgrf->sgr_next) {
X		if (sgrf->sgr_entry == 0)
X			continue;
X
X		if (strcmp (name, sgrf->sgr_entry->sg_name) == 0) {
X			sgr_cursor = sgrf;
X			return sgrf->sgr_entry;
X		}
X	}
X	errno = ENOENT;
X	return 0;
X}
X
Xint
Xsgr_rewind ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	sgr_cursor = 0;
X	return 1;
X}
X
Xstruct sgrp *
Xsgr_next ()
X{
X	if (! isopen) {
X		errno = EINVAL;
X		return 0;
X	}
X	if (sgr_cursor == 0)
X		sgr_cursor = sgr_head;
X	else
X		sgr_cursor = sgr_cursor->sgr_next;
X
X	while (sgr_cursor) {
X		if (sgr_cursor->sgr_entry)
X			return sgr_cursor->sgr_entry;
X
X		sgr_cursor = sgr_cursor->sgr_next;
X	}
X	return 0;
X}
SHAR_EOF
if test 11482 -ne "`wc -c < 'sgroupio.c'`"
then
	echo shar: "error transmitting 'sgroupio.c'" '(should have been 11482 characters)'
fi
fi
exit 0
#	End of shell archive
-- 
John F. Haugh II        | Distribution to  | UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 255-8251 | GEnie PROHIBITED :-) |  Domain: jfh at rpp386.cactus.org
"If liberals interpreted the 2nd Amendment the same way they interpret the
 rest of the Constitution, gun ownership would be mandatory."



More information about the Alt.sources mailing list