login(1) replacement

John F. Haugh II jfh at rpp386.Dallas.TX.US
Thu Nov 10 23:40:24 AEST 1988


The recent viral attack on the Internet has caused some of us to question
the need for readable password files.  Short of patching the login binary
to use a different password file, there was no way to make the password
file unreadable.  So, I rewrote login ...

This is a VERY early release.  It supports most of the features I've seen
in login's.  Notably, password aging, subsystem logins, root console-only
logins, environmental variables on the command line, etc.

The code is written to favor simplicity and clarity, at the expense of
efficiency.  If you don't know what you login is doing, you shouldn't
trust it.

I'll be working up a replacement su(1) and passwd(1) to go with this.
Unless you compile with -USHADOW, you shouldn't use this version.  I'm
just sending this out for comments ...  It is only a few days old, so
no serious flames ;-)
--
#! /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:
#	Makefile
#	age.c
#	entry.c
#	env.c
#	login.c
#	main.c
#	password.c
#	pwent.c
#	setup.c
#	shell.c
#	sub.c
#	utmp.c
#	valid.c
# This archive created: Wed Nov  9 21:54:08 1988
# By:	John F. Haugh II (Precision Information, Dallas, TX)
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
CFLAGS = -O
OBJS = main.o login.o env.o password.o entry.o valid.o setup.o shell.o age.o \
	pwent.o utmp.o sub.o

login:	$(OBJS)
	cc -o login $(OBJS)
SHAR_EOF
fi
if test -f 'age.c'
then
	echo shar: "will not over-write existing file 'age.c'"
else
cat << \SHAR_EOF > 'age.c'
#include <stdio.h>
#include <pwd.h>

static	int	c64i (c)
char	c;
{
	if (c == '.')
		return (0);

	if (c == '/')
		return (1);

	if (c >= '0' && c <= '9')
		return (c - '0' + 2);

	if (c >= 'A' && c <= 'Z')
		return (c - 'A' + 12);

	if (c >= 'a' && c <= 'z')
		return (c - 'a' + 38);
	else
		return (-1);
}

long	a64l (s)
char	*s;
{
	int	i;
	long	value;
	long	shift = 0;

	for (i = 0, value = 0L;i < 6 && *s;s++) {
		value += (c64i (*s) << shift);
		shift += 6;
	}
	return (value);
}

expire (age)
char	*age;
{
	long	clock;
	long	week;
	extern	char	name[];
	extern	int	errno;

	time (&clock);
	clock /= (7L * 24L * 60L * 60L);

	if (strlen (age) < 4)
		week = 0L;
	else
		week = a64l (age + 2);

	if (clock >= week + c64i (age[0])) {
		printf ("Your password has expired.");

		if (c64i (age[0]) < c64i (age[1])) {
			puts ("  Contact the system administrator.");
			exit (1);
		}
		puts ("  Choose a new one.");

		execl ("/bin/passwd", "-passwd", name, (char *) 0);
		puts ("Can't execute /bin/passwd");
		exit (errno);
	}
}
SHAR_EOF
fi
if test -f 'entry.c'
then
	echo shar: "will not over-write existing file 'entry.c'"
else
cat << \SHAR_EOF > 'entry.c'
#include <stdio.h>
#include <pwd.h>

#ifdef	SHADOW
#define	PASSWD	"/etc/private/passwd"
#else
#define	PASSWD	"/etc/passwd"
#endif

struct	passwd	*fgetpwent ();

void	entry (name, pwent)
char	*name;
struct	passwd	*pwent;
{
	FILE	*pwd;
	struct	passwd	*passwd;

	if ((pwd = fopen (PASSWD, "r")) == (FILE *) 0) {
		pwent->pw_passwd = (char *) 0;
		return;
	}
	while (passwd = fgetpwent (pwd))
		if (strcmp (name, passwd->pw_name) == 0)
			break;

	fclose (pwd);

	if (passwd == (struct passwd *) 0) {
		pwent->pw_name = (char *) 0;
		pwent->pw_passwd = (char *) 0;
	} else  {
		pwent->pw_name = strdup (passwd->pw_name);
		pwent->pw_passwd = strdup (passwd->pw_passwd);
		pwent->pw_uid = passwd->pw_uid;
		pwent->pw_gid = passwd->pw_gid;
		pwent->pw_age = strdup (passwd->pw_age);
		pwent->pw_comment = (char *) 0;
		pwent->pw_gecos = strdup (passwd->pw_gecos);
		pwent->pw_dir = strdup (passwd->pw_dir);
		pwent->pw_shell = strdup (passwd->pw_shell);
	}
}
SHAR_EOF
fi
if test -f 'env.c'
then
	echo shar: "will not over-write existing file 'env.c'"
else
cat << \SHAR_EOF > 'env.c'
#include <stdio.h>
#include <string.h>

extern	char	**environ;
extern	char	*newenvp[];
extern	int	newenvc;
extern	int	maxenv;

static	char	*forbid[] = {
	"HOME",
	"PATH",
	"SHELL",
	(char *) 0
};

char	*strdup (s)
char	*s;
{
	int	len;
	char	*cp;
	char	*malloc ();

	if (s == (char *) 0)
		return ((char *) 0);

	len = strlen (s);
	if (! (cp = malloc (len + 1)))
		return ((char *) 0);

	return (strcpy (cp, s));
}

addenv (entry)
char	*entry;
{
	char	*cp;
	int	i;
	int	len;

	if (cp = strchr (entry, '='))
		len = cp - entry;
	else
		len = strlen (entry);

	for (i = 0;i < newenvc;i++)
		if (strncmp (entry, newenvp[i], len) == 0 &&
			(newenvp[i][len] == '=' || newenvp[i][len] == '\0'))
			break;

	if (i == maxenv) {
		puts ("Environment overflow");
		return;
	}
	if (i == newenvc)
		newenvp[newenvc++] = strdup (entry);
	else {
		free (newenvp[i]);
		newenvp[i] = strdup (entry);
	}
}

setenv (argc, argv)
int	argc;
char	**argv;
{
	int	i;
	int	n;
	char	variable[BUFSIZ];
	char	*cp;

	for (i = 0;i < argc;i++) {
		if ((n = strlen (argv[i])) >= BUFSIZ)
			continue;	/* ignore long entries */

		if (! (cp = strchr (argv[i], "="))) {
			strcpy (variable, argv[i]);
		} else {
			strncpy (variable, argv[i], cp - argv[i]);
			variable[cp - argv[i]] = '\0';
		}
		for (n = 0;forbid[n] != (char *) 0;n++)
			if (strcmp (variable, forbid[n]) == 0)
				break;

		if (forbid[n] != (char *) 0) {
			printf ("You may not change $%s\n", forbid[n]);
			continue;
		}
		addenv (argv[i]);
	}
}
SHAR_EOF
fi
if test -f 'login.c'
then
	echo shar: "will not over-write existing file 'login.c'"
else
cat << \SHAR_EOF > 'login.c'
#include <stdio.h>
#include <ctype.h>
#include <string.h>

login (name)
char	*name;
{
	char	buf[BUFSIZ];
	char	*envp[32];
	int	envc;
	char	*cp;
	int	i;

	memset (buf, 0, BUFSIZ);

	fputs ("login: ", stdout);

	if (fgets (buf, BUFSIZ, stdin) != buf)
		exit (1);

	buf[strlen (buf) - 1] = '\0';	/* remove \n [ must be there ] */

	for (cp = buf;*cp == ' ' || *cp == '\t';cp++)
		;

	for (i = 0;i < BUFSIZ - 1 && isgraph (*cp);name[i++] = *cp++)
		;

	if (*cp)
		cp++;

	name[i] = '\0';

	if (*cp != '\0') {		/* process new variables */
		for (envc = 0;envc < 32;envc++) {
			envp[envc] = strtok (envc == 0 ? cp:(char *) 0, " \t,");

			if (envp[envc] == (char *) 0)
				break;
		}
		setenv (envc, envp);
	}
}
SHAR_EOF
fi
if test -f 'main.c'
then
	echo shar: "will not over-write existing file 'main.c'"
else
cat << \SHAR_EOF > 'main.c'
#include <sys/types.h>
#include <stdio.h>
#include <pwd.h>
#include <utmp.h>

char	name[BUFSIZ];
char	pass[BUFSIZ];
char	home[BUFSIZ];
char	prog[BUFSIZ];

struct	passwd	pwent;
struct	utmp	utent;

#ifndef	MAXENV
#define	MAXENV	64
#endif

char	*newenvp[MAXENV];
int	newenvc = 0;
int	maxenv = MAXENV;

#ifndef	ALARM
#define	ALARM	60
#endif

#ifndef	RETRIES
#define	RETRIES	3
#endif

#ifndef	PATH
#define	PATH	":/bin:/usr/bin"
#endif

main (argc, argv, envp)
int	argc;
char	**argv;
char	**envp;
{
	int	retries = RETRIES;

	checkutmp ();			/* must be lowest level shell */

	if (! isatty (0))		/* must be a terminal */
		exit (1);

	while (*envp)			/* add inherited environment, */
		addenv (*envp++);	/* some variables change later */

	addenv (PATH);			/* set the default $PATH */

#ifdef	TZ
	addenv (TZ);			/* set the default $TZ, if one */
#endif
#ifdef	HZ
	addenv (HZ);			/* set the default $HZ, if one */
#endif
	if (argc >= 2) {		/* now set command line variables */
		setenv (argc - 2, &argv[2]);
		strncpy (name, argv[1], sizeof name);
	}
	alarm (60);			/* only allow ALARM sec. for login */

	while (1) {		/* repeatedly get login/password pairs */
		if (! name[0]) {	/* need to get a login id */
			login (name, sizeof name);
			continue;
		}
		entry (name, &pwent);	/* get entry from password file */

	/*
	 * Here we have a sticky situation.  Some accounts may have no
	 * password entry in the password file.  So, we don't ask for a
	 * password.  Others, have a blank password entered - you be the
	 * judge.  The conditional compilation NOBLANK requires even
	 * blank passwords to be prompted for.  This may well break
	 * quite a few systems.  Use with discretion.
	 */

#ifdef	NOBLANK
		if (! password (pass))	/* get a password from user */
			continue;
#else
		if ((pwent.pw_name == (char *) 0 || pwent.pw_passwd)
				&& ! password (pass))
			continue;
#endif
		if (valid (pass, &pwent)) /* check encrypted passwords ... */
			break;		/* ... encrypted passwords matched */

		if (--retries <= 0)	/* only allow so many failures */
			exit (1);
	}
	alarm (0);			/* turn off alarm clock */

	setutmp ();			/* make entry in utmp & wtmp files */
#ifdef	CONSOLE
	if (pwent.pw_uid == 0 &&	/* root no logging in on console ? */
			strncmp (CONSOLE, utent.ut_line, sizeof utent.ut_line))
		exit (1);		/* then exit! */
#endif
	if (pwent.pw_shell[0] == '*')	/* subsystem root required */
		subsystem ();		/* figure out what to execute */

	setup (&pwent);			/* set UID, GID, HOME, etc ... */

	if (pwent.pw_age)		/* check for age of password ... */
		expire (pwent.pw_age);	/* ... ask for new one if expired */

	shell (pwent.pw_shell);		/* exec the shell finally. */
}
SHAR_EOF
fi
if test -f 'password.c'
then
	echo shar: "will not over-write existing file 'password.c'"
else
cat << \SHAR_EOF > 'password.c'
#include <stdio.h>
#include <string.h>

char	*getpass ();

int	password (pass)
char	*pass;
{
	char	*cp;

	if ((cp = getpass ("Password:")) == (char *) 0)
		return (0);

	strcpy (pass, cp);
	return (1);
}
SHAR_EOF
fi
if test -f 'pwent.c'
then
	echo shar: "will not over-write existing file 'pwent.c'"
else
cat << \SHAR_EOF > 'pwent.c'
#include <stdio.h>
#include <pwd.h>
#include <string.h>

#define	SBUFSIZ	64

struct	passwd	*fgetpwent (fp)
FILE	*fp;
{
	static	struct	passwd	pwent;
	static	char	name[SBUFSIZ];
	static	char	password[SBUFSIZ];
	static	char	gecos[SBUFSIZ];
	static	char	home[SBUFSIZ];
	static	char	shell[SBUFSIZ];
	static	char	age[SBUFSIZ];
	char	buf[BUFSIZ];
	char	*cp;

	pwent.pw_name = name;
	pwent.pw_passwd = password;
	pwent.pw_uid = -1;
	pwent.pw_gid = -1;
	pwent.pw_age = age;
	pwent.pw_comment = (char *) 0;
	pwent.pw_gecos = gecos;
	pwent.pw_dir = home;
	pwent.pw_shell = shell;

	while (fgets (buf, BUFSIZ, fp) == buf) {
		if (buf[0] == '#')
			continue;

		buf[strlen (buf) - 1] = 0;

		if ((cp = strtok (buf, ":")) && *cp)
			strcpy (name, cp);
		else
			continue;

		if (cp = strtok ((char *) 0, ":"))
			strcpy (password, cp);
		else
			continue;

		if ((cp = strtok ((char *) 0, ":")) && *cp)
			pwent.pw_uid = atoi (cp);
		else
			continue;

		if ((cp = strtok ((char *) 0, ":")) && *cp)
			pwent.pw_gid = atoi (cp);
		else
			continue;

		if (cp = strchr (password, ',')) {
			strcpy (age, cp + 1);
			*cp = '\0';
		} else
			pwent.pw_age = (char *) 0;

		if (cp = strtok ((char *) 0, ":"))
			strcpy (gecos, cp);
		else
			continue;

		if ((cp = strtok ((char *) 0, ":")) && *cp)
			strcpy (home, cp);
		else
			continue;

		if ((cp = strtok ((char *) 0, ":")) && *cp)
			strcpy (shell, cp);
		else
			pwent.pw_shell = (char *) 0;

		return (&pwent);
	}
	return ((struct passwd *) 0);
}
SHAR_EOF
fi
if test -f 'setup.c'
then
	echo shar: "will not over-write existing file 'setup.c'"
else
cat << \SHAR_EOF > 'setup.c'
#include <pwd.h>
#include <string.h>

extern	char	home[];
extern	char	prog[];
extern	char	name[];

long	strtol ();

setup (info)
struct	passwd	*info;
{
	extern	int	errno;
	char	logname[30];
	char	mail[30];
	char	*cp;
	int	i;
	long	l;

	if (chdir (info->pw_dir) == -1) {
		printf ("Unable to change directory to \"%s\"\n", info->pw_dir);
		exit (errno);
	}
	if (setgid (info->pw_gid) == -1) {
		puts ("Bad group id");
		exit (errno);
	}
	if (setuid (info->pw_uid) == -1) {
		puts ("Bad user id");
		exit (errno);
	}
	strcat (strcpy (home, "HOME="), info->pw_dir);
	addenv (home);

	if (info->pw_shell == (char *) 0)
		info->pw_shell = "/bin/sh";

	strcat (strcpy (prog, "SHELL="), info->pw_shell);
	addenv (prog);

	strcat (strcpy (logname, "LOGNAME="), name);
	addenv (logname);

	strcat (strcpy (mail, "MAIL=/usr/mail/"), name);
	addenv (mail);

	for (cp = info->pw_gecos;cp != (char *) 0;cp = strchr (cp, ',')) {
		if (*cp == ',')
			cp++;

		if (strncmp (cp, "pri=", 4) == 0) {
			i = atoi (cp + 4);
			if (i >= -20 && i <= 20)
				nice (i);

			continue;
		}
		if (strncmp (cp, "limit=", 6) == 0) {
			l = atol (cp + 6);
			ulimit (2, l);

			continue;
		}
		if (strncmp (cp, "mask=", 5) == 0) {
			i = strtol (cp + 5, (char **) 0, 8) & 0777;
			umask (i);

			continue;
		}
	}
}
SHAR_EOF
fi
if test -f 'shell.c'
then
	echo shar: "will not over-write existing file 'shell.c'"
else
cat << \SHAR_EOF > 'shell.c'
#include <stdio.h>

extern	char	*newenvp[];

shell (file)
char	*file;
{
	char	arg0[BUFSIZ];
	char	*path;
	char	*strrchr ();
	extern	int	errno;

	if (file == (char *) 0)
		exit (1);

	path = strrchr (file, '/');

	strcpy (arg0 + 1, path);
	arg0[0] = '-';

	execle (file, arg0, (char *) 0, newenvp);
	execle ("/bin/sh", arg0 + 1, "-c", file, (char *) 0, newenvp);

	printf ("Can't execute %s\n", file);
	exit (errno);
}
SHAR_EOF
fi
if test -f 'sub.c'
then
	echo shar: "will not over-write existing file 'sub.c'"
else
cat << \SHAR_EOF > 'sub.c'
#include <pwd.h>

extern	struct	passwd	pwent;

/*
 * I have heard of two different types of behavior with subsystem roots.
 * One has you execute login no matter what.  The other has you execute
 * the command [ if one exists ] after the '*' in the shell name.  The
 * macro SUBLOGIN says to execute /bin/login [ followed by /etc/login ]
 * regardless.  Otherwise, pwent.pw_shell is fixed up and that command
 * is executed [ by returning to the caller ].  I prefer the latter since
 * it doesn't require having a "login" on the new root filesystem.
 */

subsystem ()
{
	char	*strdup ();

	if (chdir (pwent.pw_dir) || chroot (pwent.pw_dir)) {
		puts ("Can't change to \"%s\"\n", pwent.pw_dir);
		exit (1);
	}
	strcpy (utent.ut_line, "<!sublogin>");

	setutmp ();
#ifdef	SUBLOGIN

	/*
	 * Here I use the original environment because I felt like it.
	 * You may want to let the user modify the environment, I didn't.
	 */

	execl ("/bin/login", "login", name, (char *) 0);
	execl ("/etc/login", "login", name, (char *) 0);
	puts ("No /bin/login or /etc/login on root");
	exit (1);
#else
	if (pwent.pw_shell[1] == '\0')
		pwent.pw_shell = "/bin/sh";
	else
		pwent.pw_shell++;
#endif
}
SHAR_EOF
fi
if test -f 'utmp.c'
then
	echo shar: "will not over-write existing file 'utmp.c'"
else
cat << \SHAR_EOF > 'utmp.c'
#include <sys/types.h>
#include <utmp.h>
#include <string.h>
#include <stdio.h>

extern	struct	utmp	utent;
extern	char	name[];

checkutmp ()
{
	struct	utmp	*ut;
	struct	utmp	*getutent ();
#ifndef	NDEBUG
	int	pid = getppid ();
#else
	int	pid = getpid ();
#endif

	setutent ();
	while (ut = getutent ())
		if (ut->ut_pid == pid)
			break;

	if (ut)				/* save for future calls ... */
		utent = *ut;

	endutent ();

	if (ut && utent.ut_pid == pid)
		return;

	puts ("No utmp entry.  You must exec \"login\" from the lowest level \"sh\"");
	exit (1);
}

setutmp ()
{
	FILE	*wtmp;
	char	tty[sizeof utent.ut_line + 1];
	char	*line;

	setutent ();

	strncpy (utent.ut_user, name, sizeof utent.ut_user);

	utent.ut_type = USER_PROCESS;

	if (line = strrchr (utent.ut_line, "/")) {
		strcpy (tty, line + 1);
		memset (utent.ut_line, '\0', sizeof utent.ut_line);
		strcpy (utent.ut_line, tty);
	}
	time (&utent.ut_time);

	pututline (&utent);

	if ((wtmp = fopen (WTMP_FILE, "a+"))) {
		fwrite (&utent, sizeof utent, 1, wtmp);
		fclose (wtmp);
	}
}
SHAR_EOF
fi
if test -f 'valid.c'
then
	echo shar: "will not over-write existing file 'valid.c'"
else
cat << \SHAR_EOF > 'valid.c'
#include <pwd.h>

/*
 * valid - compare encrypted passwords
 *
 *	Valid() compares the DES encrypted password from the password file
 *	against the password which the user has entered after it has been
 *	encrypted using the same salt as the original.
 */

int	valid (password, entry)
char	*password;
struct	passwd	*entry;
{
	char	*encrypt;
	char	*salt;
	char	*crypt ();

	/*
	 * Start with blank or empty password entries.  Always encrypt
	 * a password if no such user exists.  Only if the ID exists and
	 * the password is really empty do you return quickly.  This
	 * routine is meant to waste CPU time.
	 */

	if (entry->pw_name &&
			(entry->pw_passwd == (char *) 0 ||
			 strlen (entry->pw_passwd == 0)) {
		if (strlen (password) == 0)
			return (1);	/* user entered nothing */
		else
			return (0);	/* user entered something! */
	}

	/*
	 * If there is no entry then we need a salt to use.
	 */

	if (entry->pw_passwd == (char *) 0 || entry->pw_passwd[0] == '\0')
		salt = "xx";
	else
		salt = entry->pw_passwd;

	/*
	 * Now, perform the encryption using the salt from before on
	 * the users input.  Since we always encrypt the string, it
	 * should be very difficult to determine if the user exists by
	 * looking at execution time.
	 */

	encrypt = crypt (password, salt);

	/*
	 * One last time we must deal with there being no password file
	 * entry for the user.  We use the pw_passwd == NULL idiom to
	 * cause non-existent users to not be validated.  Even still,
	 * we are safe because if the string were == "", any encrypted
	 * string is not going to match - the output of crypt() begins
	 * with the salt, which is "xx", not "".
	 */

	if (entry->pw_passwd && strcmp (encrypt, entry->pw_passwd) == 0)
		return (1);
	else
		return (0);
}
SHAR_EOF
fi
exit 0
#	End of shell archive
-- 
John F. Haugh II                        +----Make believe quote of the week----
VoiceNet: (214) 250-3311   Data: -6272  | Nancy Reagan on Artifical Trish:
InterNet: jfh at rpp386.Dallas.TX.US       |      "Just say `No, Honey'"
UucpNet : <backbone>!killer!rpp386!jfh  +--------------------------------------



More information about the Alt.sources mailing list