pdp11v/usr/src/cmd/login.c
/* @(#)login.c 1.2 */
/*
* login [ name ] [ environment args ]
*
* Conditional assemblies:
*
* PASSREQ causes password to be required
* NO_MAIL causes the MAIL environment variable not to be set
* NOSHELL does not put non-standard shell names into environment
* CONSOLE, if defined, only allows root logins on the device
* specified by CONSOLE. CONSOLE MUST NOT be defined as
* either "/dev/syscon" or "/dev/systty"!!
*/
#include <sys/types.h>
#include <utmp.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/dir.h>
#define TRUE 1
#define FALSE 0
#define FAILURE -1
#define SCPYN(a, b) strncpy(a, b, sizeof(a))
#define DIAL_FILE "/etc/dialups"
#define DPASS_FILE "/etc/d_passwd"
#define SHELL "/bin/sh"
#define PATH "PATH=:/bin:/usr/bin"
#define ROOTPATH "PATH=:/bin:/usr/bin:/etc"
#define SUBLOGIN "<!sublogin>"
#define ROOTUID 0
#define MAXARGS 63
#define MAXENV 1024
#define MAXLINE 256
struct passwd nouser = {"", "nope"};
struct utmp utmp, ubuf;
char minusnam[16] = "-";
char shell[64] = { "SHELL=" } ;
char home[64] = { "HOME=" } ;
char logname[30] = { "LOGNAME=" } ;
#ifndef NO_MAIL
char mail[30] = { "MAIL=/usr/mail/" } ;
#endif
char *envinit[5+MAXARGS] =
{
home,PATH,logname,0,0
} ;
int basicenv ;
char envblk[MAXENV] ;
struct passwd *pwd;
struct passwd *getpwnam();
int setpwent();
char *crypt();
char *getpass();
char *strrchr(),*strchr(),*strcat() ;
extern char **environ;
#define WEEK (24L * 7 * 60 * 60) /* 1 week in seconds */
time_t when;
time_t maxweeks;
time_t minweeks;
time_t now;
long a64l();
main(argc, argv ,renvp)
char **argv,**renvp;
{
register char *namep;
int c,j,k,l_index,length ;
char *ttyn,*ttyntail;
int nopassword ;
register int i ;
register struct utmp *u ;
struct utmp *getutent() ;
FILE *fp ;
struct stat statbuf ;
char **envp,*ptr,*endptr ;
int sublogin ;
extern char **getargs() ;
extern char *terminal() ;
/* Set flag to disable the pid check if you find that you are */
/* a subsystem login. */
sublogin = FALSE;
if (*renvp && strcmp(*renvp,SUBLOGIN) == 0)
sublogin = TRUE;
umask(0);
alarm(60);
signal(SIGQUIT, 1);
signal(SIGINT, 1);
nice(0);
ttyn = terminal(0);
if (ttyn==0)
ttyn = "/dev/tty??";
if (ttyntail = strrchr(ttyn,'/')) ttyntail++ ;
else ttyntail = ttyn ;
if (--argc) {
SCPYN(utmp.ut_user, *++argv);
for(namep = &utmp.ut_user[0]; *namep; namep++);
envp = ++argv ;
goto first ;
}
loop:
SCPYN(utmp.ut_user, "");
first:
while (utmp.ut_user[0] == '\0') {
namep = &utmp.ut_user[0];
printf("login: ");
if ((envp = getargs()) != (char**)NULL) {
SCPYN(utmp.ut_user,*envp++) ;
for (namep = &utmp.ut_user[0] ; *namep &&
namep < &utmp.ut_user[sizeof(utmp.ut_user)];)
namep++ ;
}
}
setpwent();
if ((pwd = getpwnam(&utmp.ut_user[0])) == NULL)
pwd = &nouser;
endpwent();
if (strncmp("pri=", pwd->pw_gecos, 4) == 0) {
int i, mflg, pri;
pri = 0;
mflg = 0;
i = 4;
if (pwd->pw_gecos[i] == '-') {
mflg++;
i++;
}
while(pwd->pw_gecos[i] >= '0' && pwd->pw_gecos[i] <= '9')
pri = (pri * 10) + pwd->pw_gecos[i++] - '0';
if (mflg)
pri = -pri;
nice(pri);
}
while (namep < &utmp.ut_user[sizeof(utmp.ut_user)])
*namep++ = '\0';
nopassword = 1;
if (*pwd->pw_passwd != '\0') {
nopassword = 0;
if(gpass("Password:", pwd->pw_passwd))
goto loop;
}
/*
* get dialup password, if necessary
*/
if(dialpass(ttyn))
goto loop;
if(chdir(pwd->pw_dir) < 0) {
printf("Unable to change directory to \"%s\"\n",pwd->pw_dir);
if(chdir("/") < 0) {
printf("Unable to change directory to \"/\"\n");
goto loop;
}
printf("You are in \"/\" instead.\n");
}
time(&utmp.ut_time);
i = utmp.ut_pid = getpid();
/* Find the entry for this pid (or line if we are a sublogin) in */
/* the utmp file. */
while ((u = getutent()) != NULL) {
if ((u->ut_type == INIT_PROCESS || u->ut_type ==
LOGIN_PROCESS || u->ut_type == USER_PROCESS)
&& ( (sublogin && strncmp(u->ut_line,ttyntail,
sizeof(u->ut_line)) == 0) || u->ut_pid == i) ) {
/* Copy in the name of the tty minus the "/dev/", the id, and set */
/* the type of entry to USER_PROCESS. */
SCPYN(utmp.ut_line,(ttyn+sizeof("/dev/")-1)) ;
utmp.ut_id[0] = u->ut_id[0] ;
utmp.ut_id[1] = u->ut_id[1] ;
utmp.ut_id[2] = u->ut_id[2] ;
utmp.ut_id[3] = u->ut_id[3] ;
utmp.ut_type = USER_PROCESS ;
/* Return the new updated utmp file entry. */
pututline(&utmp) ;
break ;
}
}
endutent() ; /* Close utmp file */
if (u == (struct utmp *)NULL){
printf("No utmp entry. You must exec \"login\" from\
the lowest level \"sh\".\n") ;
exit(1) ;
}
/* Now attempt to write out this entry to the wtmp file if we */
/* were successful in getting it from the utmp file and the */
/* wtmp file exists. */
if (u != NULL && (fp = fopen(WTMP_FILE,"r+")) != NULL) {
fseek(fp,0L,2) ; /* Seek to end of file. */
fwrite(&utmp,sizeof(utmp),1,fp) ;
fclose(fp) ;
}
chown(ttyn, pwd->pw_uid, pwd->pw_gid);
/* If the shell field starts with a '*', do a chroot to the home */
/* directory and perform a new login. */
if(*pwd->pw_shell == '*')
{
if(chroot(pwd->pw_dir) < 0)
{
printf("No Root Directory\n");
goto loop;
}
/* Set the environment flag <!sublogin> so that the next login */
/* knows that it is a sublogin. */
envinit[0] = SUBLOGIN ;
envinit[1] = (char*)NULL ;
printf("Subsystem root: %s\n",pwd->pw_dir);
execle("/bin/login", "login", 0,&envinit[0]);
execle("/etc/login", "login", 0,&envinit[0]);
printf("No /bin/login or /etc/login on root\n");
goto loop;
}
if (setgid(pwd->pw_gid) == FAILURE)
{
printf("Bad group id.\n") ;
exit(1) ;
}
if (setuid(pwd->pw_uid) == FAILURE)
{
printf("Bad user id.\n") ;
exit(1) ;
}
/* Set up the basic environment for the exec. This includes */
/* HOME, PATH, LOGNAME, SHELL, and MAIL. */
strcat(home, pwd->pw_dir);
strcat(logname, pwd->pw_name);
if(pwd->pw_uid == ROOTUID)
{
#ifdef CONSOLE
if(strcmp(ttyn, CONSOLE))
{
printf("Not on system console\n");
exit(10);
}
#endif
envinit[1] = ROOTPATH ;
}
if (*pwd->pw_shell == '\0') {
pwd->pw_shell = SHELL;
strcat(shell, pwd->pw_shell);
}
#ifndef NOSHELL
else {
envinit[3] = shell;
strcat(shell, pwd->pw_shell);
}
#endif
/* Find the name of the shell. */
if ((namep = strrchr(pwd->pw_shell, '/')) == NULL)
namep = pwd->pw_shell;
else
namep++;
/* Generate the name of the shell with a '-' sign in front of it. */
/* This causes .profile processing when a shell is exec'ed. */
strcat(minusnam, namep);
#ifndef NO_MAIL
if (envinit[3] == (char*)NULL) envinit[3] = mail ;
else envinit[4] = mail ;
strcat(mail,pwd->pw_name) ;
#endif
/* Find the end of the basic environment. */
for (basicenv=3; envinit[basicenv] ;basicenv++) ;
/* Add in all the environment variables picked up from the */
/* argument list to "login" or from the user response to the */
/* "login" request. */
for (j=0,k=0,l_index=0,ptr= &envblk[0]; *envp && j < MAXARGS-1
;j++,envp++) {
/* Scan each string provided. If it doesn't have the format */
/* xxx=yyy, then add the string "Ln=" to the beginning. */
if ((endptr = strchr(*envp,'=')) == (char*)NULL) {
envinit[basicenv+k] = ptr ;
sprintf(ptr,"L%d=%s",l_index,*envp) ;
/* Advance "ptr" to the beginning of the next argument. */
while (*ptr++) ;
k++ ;
l_index++ ;
} else {
/* Check to see whether this string replaces any previously */
/* defined string. */
for (i=0,length= endptr+1-*envp; i < basicenv+k ;i++) {
if (strncmp(*envp,envinit[i],length) == 0) {
/* If the variable to be changed is "SHELL=" or "PATH=", don't */
/* allow the substitution. */
if (strncmp(*envp,"SHELL=",6) != 0
&& strncmp(*envp,"PATH=",5) != 0)
envinit[i] = *envp ;
break ;
}
}
/* If it doesn't, place it at the end of environment array. */
if (i == basicenv+k) {
envinit[basicenv+k] = *envp ;
k++ ;
}
}
}
/* Switch to the new environment. */
environ = envinit;
alarm(0);
#ifdef PASSREQ
if (nopassword) {
printf("You don't have a password. Choose one.\n");
for(namep = utmp.ut_user; *namep != ' '; namep++)
if(namep == &utmp.ut_user[sizeof(utmp.ut_user)])
break;
*namep = '\0';
utmp.ut_user[0] &= ~0200;
printf("passwd %s\n", utmp.ut_user);
execl("/bin/passwd", "passwd", utmp.ut_user, 0);
printf("Can not execute /bin/passwd\n");
exit(1);
}
#endif
/* is the age of the password to be checked? */
if (*pwd->pw_age != NULL) {
/* retrieve (a) week of previous change;
(b) maximum number of valid weeks */
when = (long) a64l (pwd->pw_age);
/* max, min and weeks since last change are packed radix 64 */
maxweeks = when & 077;
minweeks = (when >> 6) & 077;
when >>= 12;
now = time(0)/WEEK;
if ((now > when + maxweeks) && (maxweeks >= minweeks)) {
printf ("Your password has expired. Choose\
a new one\n");
execl("/bin/passwd", "passwd", utmp.ut_user, 0);
exit(9);
}
}
signal(SIGQUIT, 0);
signal(SIGINT, 0);
execlp(pwd->pw_shell, minusnam, 0);
printf("No shell\n");
exit(1);
}
dialpass(ttyn)
char *ttyn;
{
register FILE *fp;
char defpass[30];
char line[80];
register char *p1, *p2;
if((fp=fopen(DIAL_FILE, "r")) == NULL)
return(0);
while((p1 = fgets(line, sizeof(line), fp)) != NULL) {
while(*p1 != '\n' && *p1 != ' ' && *p1 != '\t')
p1++;
*p1 = '\0';
if(strcmp(line, ttyn) == 0)
break;
}
fclose(fp);
if(p1 == NULL || (fp = fopen(DPASS_FILE, "r")) == NULL)
return(0);
while((p1 = fgets(line, sizeof(line)-1, fp)) != NULL) {
while(*p1 && *p1 != ':')
p1++;
*p1++ = '\0';
p2 = p1;
while(*p1 && *p1 != ':')
p1++;
*p1 = '\0';
if(strcmp(pwd->pw_shell, line) == 0) {
fclose(fp);
if (*p2 == '\0')
return(0);
return(gpass("Dialup Password:", p2));
}
if(strcmp(SHELL, line) == 0)
SCPYN(defpass, p2);
}
fclose(fp);
return(gpass("Dialup Password:", defpass));
}
gpass(prmt, pswd)
char *prmt, *pswd;
{
register char *p1;
p1 = crypt(getpass(prmt), pswd);
if(strcmp(p1, pswd)) {
printf("Login incorrect\n");
return(1);
}
return(0);
}
#define WHITESPACE 0
#define ARGUMENT 1
char **getargs()
{
static char envblk[MAXLINE] ;
static char *args[MAXARGS] ;
register char *ptr,**answer ;
register int c ;
int state ;
extern int quotec() ;
for (ptr= &envblk[0]; ptr < &envblk[sizeof(envblk)] ;) *ptr++ = '\0' ;
for (answer= &args[0]; answer < &args[MAXARGS] ;)
*answer++ = (char *)NULL ;
for (ptr= &envblk[0],answer= &args[0],state = WHITESPACE
; (c = getc(stdin)) != EOF;) {
switch (c) {
case '\n' :
if (ptr == &envblk[0]) return((char **)NULL) ;
else return(&args[0]) ;
case ' ' :
case '\t' :
if (state == ARGUMENT) {
*ptr++ = '\0' ;
state = WHITESPACE ;
}
break ;
case '\\' :
c = quotec() ;
default :
if (state == WHITESPACE) {
*answer++ = ptr ;
state = ARGUMENT ;
}
*ptr++ = c ;
}
/* If the buffer is full, force the next character to be read to */
/* be a <newline>. */
if (ptr == &envblk[sizeof(envblk)-1]) {
ungetc('\n',stdin) ;
putc('\n',stdout) ;
}
}
/* If we left loop because an EOF was received, exit immediately. */
exit(0) ;
}
int quotec()
{
register int c,i,num ;
switch(c = getc(stdin)) {
case 'n' :
c = '\n' ;
break ;
case 'r' :
c = '\r' ;
break ;
case 'v' :
c = 013 ;
break ;
case 'b' :
c = '\b' ;
break ;
case 't' :
c = '\t' ;
break ;
case 'f' :
c = '\f' ;
break ;
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
for (num=0,i=0; i < 3 ;i++) {
num = num * 8 + (c - '0') ;
if ((c = getc(stdin)) < '0' || c > '7')
break ;
}
ungetc(c,stdin) ;
c = num & 0377 ;
break ;
default :
break ;
}
return (c) ;
}
/*
* termainal(f): return "/dev/ttyXX" which the the name of the
* tty belonging to file f. This routine is the same as ttyname()
* except that it rejects /dev/syscon and /dev/systty, which are
* links to other device names.
*
* This program works in two passes: the first pass tries to
* find the device by matching device and inode numbers; if
* that doesn't work, it tries a second time, this time doing a
* stat on every file in /dev and trying to match device numbers
* only. If that fails too, NULL is returned.
*/
char *
terminal(f)
int f;
{
struct stat fsb, tsb;
struct direct db;
register int df, pass1;
extern char *strcpy(), *strcat();
extern long lseek();
static char rbuf[32], dev[]="/dev/";
if(isatty(f) == 0)
return(NULL);
if(fstat(f, &fsb) < 0)
return(NULL);
if((fsb.st_mode & S_IFMT) != S_IFCHR)
return(NULL);
if((df = open(dev, 0)) < 0)
return(NULL);
pass1 = 1;
do {
while(read(df, (char *)&db, sizeof(db)) == sizeof(db)) {
if(db.d_ino == 0)
continue;
if(pass1 && db.d_ino != fsb.st_ino)
continue;
if (strncmp(&db.d_name[0],"syscon") == 0 ||
strncmp(&db.d_name[0],"systty") == 0)
continue ;
(void) strcpy(rbuf, dev);
(void) strcat(rbuf, db.d_name);
if(stat(rbuf, &tsb) < 0)
continue;
if(tsb.st_rdev == fsb.st_rdev &&
(tsb.st_mode&S_IFMT) == S_IFCHR &&
(!pass1 || tsb.st_ino == fsb.st_ino)) {
(void) close(df);
return(rbuf);
}
}
(void) lseek(df, 0L, 0);
} while(pass1--);
(void) close(df);
return(NULL);
}