pdp11v/usr/src/cmd/login.c

Compare this file to the similar file:
Show the results in this format:

/*	@(#)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);
}