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