jfh@rpp386.Dallas.TX.US (John F. Haugh II) (11/10/88)
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@rpp386.Dallas.TX.US | "Just say `No, Honey'" UucpNet : <backbone>!killer!rpp386!jfh +--------------------------------------
katzung@laidbak.UUCP (Brian Katzung) (11/12/88)
SECURITY HOLE: Putting ':' at the beginning of the path is just *ABSOLUTELY BEGGING* for Trojan Horses. Always put '.' search at the end if you must put it in at all. This goes for things like exec?p() too. Nit: The login() routine has one formal parameter (login.c) but gets called with two arguments (main.c). -- Brian Katzung