#include	<sys/types.h>
#include	<sys/termio.h>
#include	"stdio.h"
#include	"ctype.h"
#include	"hsubs.h"
#include	<sys/stat.h>

#define	PWLOCK	"/etc/ptmp"
#define	PASSWD	"/etc/passwd"
#define	HASHN	"/etc/hpasswd_nm"
#define	HASHU	"/etc/hpasswd_ud"
#define	OFF		0
#define	ON		1
#define	FTL	        4
#define	FSPACE	       31
#define	ERRLINE	       23
#define	MIDCOL	       40
#define	PROMPLINE	5
#define	PROMPCOL	1
#define	NTHINGS 	9
#define	NAME		0
#define	PASSWORD	1
#define	UID		2
#define	GID		3
#define	STATUS		4
#define	NTERMS		5
#define	INFO		6
#define	DIRECTORY	7
#define	SHELL		8

char	*elist[] = {
	"OK", 
	"*** Can't open password file: /etc/passwd ***",
	"*** Bad Password line encountered ***",
	"*** Entry not found. ***",
	"*** Field too long ",
	"*** String must contain only letters and/or digits ***",
	"*** String may not contain a ':' ***",
	"*** String contains a non-printable character ***",
	"*** String must contain only digits ***",
	"*** Character must be one of AVTNM ***",
	"*** Bad character in pathname ***",
	"*** String is too short ***",
	"*** Uid must be in the range 0 - 32767 ***",
	"*** Gid must be in the range 0 - 255 ***",
	"*** Bad status character; must be one of AVTNM ***",
	"*** Name already exists ***",
	"*** Most contain either digits OR a '*' ***",
	"*** Passwords do not match, try again ***",
	"Retype password for verification",
	"Enter password (echoing turned off)",
};

char	bufs[16][128];
struct	msg {
	char *mesg;
	short mlen;
} msg[] = {
	"Name: ", 7,
	"Password: ", 11,
	"Uid: ", 6,
	"Gid: ", 6,
	"Status: ", 9,
	"Terminals: ", 12,
	"Information: ", 14,
	"Directory: ", 12,
	"Shell: ", 8
};

short	maxlen[NTHINGS] = { 8, 8, 5, 3, 1, 2, 32, 32, 32 };

char	*skip(), *getline();
short	dflg = 0;	/* No graphical display */
short	oflg = 0;	/* Override error checking */
long	poff = 0;
char	lckbuf[64];
char	ibuf[512];
short	origlen;
short	errlen = 0;
short	hfd = -1;
char	origname[16];
int	catchi();
char	*strchr();

char	termcb[1024];
char	cmb[64];
char	clb[64];
char	*CM;
char	*CL;
char	*sp = cmb;
char	*cp = clb;
main(c,v)
char **v;
{
	register char *s;
	register short i;
	short e;

	setbuf(stdout, NULL);
	setbuf(stderr, NULL);
	while(--c){
		if(**++v == '-')
			switch(*++*v){
	case 'd':
		dflg++;
		break;

	case 'o':
		oflg++;
		break;

		}
	}

	if(!dflg){
		if(tgetent(termcb, getenv("TERM")) <= 0){
			fprintf(stdout, "No entry in termcap for %s\n", getenv("TERM"));
			exit(3);
		}
		if((CM = tgetstr("cm", &sp)) == NULL){
			fprintf(stdout, "No CM motion for this terminal\n");
			exit(4);
		}
		if((CL = tgetstr("cl", &cp)) == NULL){
			fprintf(stdout, "No CL clear for this terminal\n");
			exit(5);
		}
	}
	clear();
	signal(2, catchi);
	if(lock(0)){
		clear();
		gotorc(PROMPLINE, PROMPCOL);
		fprintf(stdout, "%s\n", lckbuf);
		exit(1);
	}
	disperr(-1, lckbuf);
lp:
	gotorc(PROMPLINE, PROMPCOL);
	fprintf(stdout, "Name: ");
	s = getline(stdin);
	if(*s < 1){
		clear();
		gotorc(1,1);
		lock(1);
		fprintf(stdout, "%s\n", lckbuf);
		exit(0);
	}

	poff = 0;
	if((e = fpent(s)) != 0){
		clear();
		disperr(e);
		goto lp;
	}
	clear();
	s = dflg ? "\n" : "\n\n";
	fprintf(stdout, "%s", s);
	for(i = 0; i < NTHINGS; i++){
		fprintf(stdout, "%s%s%s", msg[i].mesg,
			i == PASSWORD ? "" : bufs[i], s);
	}
	fprintf(stdout, "Poff: %D", poff);

	while(1){
		for(i = 0; i < NTHINGS; i++)
			if(fixit(i))
				break;
		if(i < NTHINGS)break;
	}
	clear();
	writeit();
	goto lp;
}


gotorc(x, y)
{
	if(!dflg)
		fprintf(stdout, "%s", tgoto(CM, y-1, x-1));
}

clear()
{
	if(!dflg)
		fprintf(stdout, "%s", CL);
	errlen = 0;
}


fpent(n)
char *n;
{
	static char pbuf[256];
	register char *s, *t, *l;
	short pfd, r, len;

	if((pfd = open(PASSWD, 0)) == -1)
		return(1);

	poff = 0;
	t = pbuf;
	while((r = read(pfd, ibuf, 512)) > 0){
		l = ibuf+r;
		s = ibuf;
		while(s < l){
			if((*t++ = *s++) != '\n')
				continue;
			len = t - pbuf;
			*t = 0;
			origlen = strlen(pbuf);
			*--t = 0;
			if((t = skip(pbuf)) == NULL)
				return(2);
			*--t = 0;
			if(strcmp(n, pbuf)){
				t = pbuf;
				continue;
			}
			close(pfd);
			poff += (s-ibuf-len);
			*t = ':';
			return(breakdown(pbuf));
		}
		poff += 512;
	}
	close(pfd);
	return(3);
}

breakdown(b)
char *b;
{
	register char *s, *t;
	register short i;

	s = t = b;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[NAME], s);
	strcpy(origname, s);
	s = t;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[PASSWORD], s);
	s = t;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[UID], s);
	s = t;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[GID], s);
	s = t;
	bufs[STATUS][0] = *s++;
	bufs[STATUS][1] = 0;
	t = s;
	while(*t != ' ' && *t)t++;
	if(*t == 0)
		return(2);
	*t++ = 0;
	strcpy(bufs[NTERMS], s);
	s = t;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[INFO], s);
	s = t;
	if((t = skip(t)) == NULL)
		return(2);
	strcpy(bufs[DIRECTORY], s);
	s = t;
	if((t = skip(t)) != NULL)
		return(2);
	strcpy(bufs[SHELL], s);
	return(0);
}

char *
skip(s)
register char *s;
{
	while(*s && *s != ':')
		s++;
	if(*s == 0)
		return(0);
	*s++ = 0;
	return(s);
}

char *
getline(fp)
FILE *fp;
{
	static char iline[256];
	register char c;
	register char *s;

	for(s = iline; (*s = getc(fp)) > 0 && *s != '\n'; s++);
	if(*s < 1)
		return(EOF);
	*s = 0;
	return(iline);
}


fixit(n)
register short n;
{

	register short nspaces, line;
	char *s;
	long	salt;
	char	saltc[16];
	char	c;
	short	i;

	line = 2*n + 3;
retry:
	if(n == PASSWORD){
		nspaces = 0;
		disperr(19);
	}
	else	{
		disperr(-1, "");
		nspaces = strlen(bufs[n]);
	}
	if(!dflg)
		gotorc(line, msg[n].mlen + nspaces + 1);
	else	fprintf(stdout, "%s%s ", msg[n].mesg,
			n == PASSWORD ? "" : bufs[n]);
	if(n == PASSWORD)
		echo(OFF);
	if((s = getline(stdin)) == EOF)
		return(1);
	if(n == PASSWORD){
		putc('\n', stdout);
		echo(ON);
	}
	if(*s == 0)
		return(0);
	trim(s);
	if(!oflg && (i = checkout(n, s)) != 0){
error:
		disperr(i);
		if(i == FTL){
			gotorc(ERRLINE, 35);
			fprintf(stdout, "(Maximum of %d characters) ***\n", 
				maxlen[n]);
		}
		if(n != PASSWORD){
			gotorc(line, 1);
			nspaces = strlen(s) + msg[n].mlen + nspaces + 2;
			while(nspaces--)
				putc(' ', stdout);
		}
		gotorc(line, 1);
		if(n != PASSWORD)
			fprintf(stdout, "%s%s", msg[n].mesg, bufs[n]);
		goto retry;
	}
	if(n == PASSWORD){
		time(&salt);
		salt += getpid();
		saltc[0] = salt & 077;
		saltc[1] = (salt>>6) & 077;
		for(i = 0; i < 2; i++){
			c = saltc[i] + '.';
			if(c > '9') c += 7;
			if(c > 'Z') c += 6;
			saltc[i] = c;
		}
		saltc[posit(0)] = saltc[0];
		saltc[posit(1)] = saltc[1];
		strcpy(bufs[n], crypt(s, saltc));
		echo(OFF);
		strcpy(ibuf, s);
		disperr(18);
		gotorc(line, msg[n].mlen + 1);
		s = getline(stdin);
		echo(ON);
		if(s == EOF)
			return(1);
		if(strcmp(s, ibuf)){
			i = 17;
			goto error;
		}
		return(0);
	}
	else	strcpy(bufs[n], s);
	nspaces += strlen(bufs[n]) + 1;
	if(!dflg){
		gotorc(line, msg[n].mlen);
		while(nspaces--)
			putc(' ', stdout);
		gotorc(line, msg[n].mlen);
		fprintf(stdout, "%s", bufs[n]);
	} else {
		fprintf(stdout, "%s%s\n", msg[n].mesg, bufs[n]);
	}
	return(0);
}


lock(flg)
int flg;
{
	register char u , *e , *s;
	struct stat dir;
	int i;

	s = 0;
	u = getuid();
	i = stat(PWLOCK,&dir);
	if(flg){
		if(i)e = "not locked";
		else    if(u != dir.st_uid)e = "not locked by you";
			else    {
				unlink(PWLOCK);
				e = "unlocked";
			}
	}
	else    {
		if(i){
			if(close(creat(PWLOCK,0644)) == -1)
				e = s = "can not be locked by you";
			else    e = "now locked by you";
		}
		else    if(u == dir.st_uid) s = e = "already locked by you";
			else    {
				s = e = "is locked by UID    ";
				while(*s++);
				--s;
				i = dir.st_uid;
				if(i == 0)*--s = '0';
				while(i){
					*--s = i%10 + '0';
					i =/ 10;
				}
			}
		}

		sprintf(lckbuf, "/etc/passwd %s.", e);
		if(s)return(-1);
		else    return(0);
}

disperr(e, parm)
register short e;
char *parm;
{
	register char *m;
	register char *p;

	m = (e == -1) ? parm : elist[e];
	if(errlen){
		gotorc(ERRLINE, MIDCOL - (errlen / 2));
		while(errlen--)
			putc(' ', stdout);
	}
	errlen = strlen(m);
	if(e == FTL)
		errlen += FSPACE;
	gotorc(ERRLINE, MIDCOL - (errlen / 2));
	fprintf(stdout, "%s%s", m, dflg ? "\n" : "");
}


checkout(n, s)
register short n;
char *s;
{
	register char *t;
	register short l;

	t = s;
	l = strlen(t);
	if(l > maxlen[n])
		return(4);
	if(l < 1)
		return(11);
	while(*t){
		if(*t == ':')
			return(6);
		switch(n){

	case NAME:
		if(!isalnum(*t))
			return(5);
		break;

	case PASSWORD:
	case INFO:
		if(!isprint(*t) && *t != ' ' && *t != '\t')
			return(7);
		break;

	case UID:
	case GID:
		if(!isdigit(*t))
			return(8);
		break;

	case NTERMS:
		if(!isdigit(*t) && *t != '*')
			return(16);
		break;

	case STATUS:
		if(!isupper(*t))
			return(9);
		break;

	case DIRECTORY:
	case SHELL:
		if(!isalnum(*t) && *t != '/')
			return(10);
		break;

	default:
		fprintf(stderr, "This is a bug; this line can not be reached!\n");
		break;

		}
		t++;
	}
	t = s;
	if(n == UID || n == GID){
		if(n == UID){
			if(l == 5 && *t > '5')
				return(12);
			l = atoi(t);
			if(l < 0)
				return(12);
		}
		else	{
			l = atoi(t);
			if(l < 0 || l > 255)
				return(13);
		}
		return(0);
	}
	if(n == STATUS)
		if(strchr("AVNTMR", *t) == NULL)
			return(14);
		else	return(0);
	if(n == NTERMS){
		if(*t == '*' && t[1])
			return(16);
		else 	return(0);
	}
	if(n == NAME){
		if(strcmp(t, origname) == 0)
			return(0);
		if(ngetent(HASHN, t, ibuf) == 0)
			return(15);
	}
	return(0);
}

trim(s)
register char *s;
{
	register char *t;
	register char *p;

	p = s;
	t = s;
	while(*p == ' ')p++;
	if(p != s){
		while(*t++ = *p++);
		p = s;
	}
	while(*p)p++;
	p--;
	while(*p == ' ')p--;
	*++p = 0;
}

echo(how)
{
	static struct termio ttybuf;
	register short i;

	if(how == OFF){
		ioctl(0, TCGETA, &ttybuf);
		i = ttybuf.c_lflag;
		ttybuf.c_lflag &= ~ECHO;
		ioctl(0, TCSETA, &ttybuf);
		ttybuf.c_lflag = i;
	} else	if(ttybuf.c_cflag)
			ioctl(0, TCSETA, &ttybuf);
}

writeit()
{
	static char obuf[512];
	register char *s;
	short fd, r;

	sprintf(obuf, "%s:%s:%s:%s:%s%s %s:%s:%s\n",
		bufs[NAME], bufs[PASSWORD], bufs[UID], bufs[GID],
		bufs[STATUS], bufs[NTERMS], bufs[INFO], bufs[DIRECTORY],
		bufs[SHELL]);
	gotorc(PROMPLINE, PROMPCOL);
	fprintf(stdout, "%s", obuf);
	gotorc(PROMPLINE+2, PROMPCOL);
	fprintf(stdout, "Write? ");
	s = getline(stdin);
	if(strncmp("yes", s, strlen(s))){
		clear();
		return;
	}
	signal(2, 1);
	if((fd = open(PASSWD, 2)) == -1){
		clear();
		disperr(1);
		goto out;
	}
	lseek(fd, poff, 1);
	if(strlen(obuf) != origlen){
		printf("Line changed length -- sorry\n");
		sleep(2);
		clear();
		close(fd);
		goto out;
	}
	write(fd, obuf, strlen(obuf));
	close(fd);
	fixent(origname, bufs[NAME], obuf);
	printf("\n\nWritten.\n");
	sleep(2);
	clear();
out:
	signal(2, catchi);
}

static char hbuf[512];

fixent(oldn, newn, lne)
char *oldn, *newn, *lne;
{
	int fd, r;
	char err;
	long h1;
	register char *l, *s, *p;

	err = 0;
	s = lne;
	while(*s++);
	p = lne+128;
	while(s < p)
		*s++ = 0;
	if(hfd == -1){
		if((hfd = open(HASHN, 2)) == -1){
			fprintf(stdout, "Fixent: can't open %s\n", HASHN);
			return(-1);
		}
	}

	if(strcmp(oldn, newn)){
		h1 = (long)(hkey(oldn)) << 7;
lp1:
		if(lseek(hfd, h1, 0) == -1L){
			fprintf(stdout, "Bad seek in old seek\n");
			return(-1);
		}
		while((r = read(hfd, hbuf, 128)) == 128){
			s = hbuf;
			if(*s == 0){
				fprintf(stdout, "Old name not found in hashed file\n");
				goto out;
			}
			p = s;
			while(*p && *p != ':')
				p++;
			*p = 0;
			if(strcmp(oldn, s)){
				h1 += 128L;
				continue;
			}
			lseek(hfd, h1, 0);
			write(hfd, "xyzabcgh:xxxxxxxxxxxxx:255:255:x:/xx:\n",39);
			goto out;
		}
		if(err++ == 0){
			h1 = 0L;
			goto lp1;
		}
	}
out:
	err = 0;
	h1 = (long)(hkey(newn)) << 7;

lp:
	if(lseek(hfd, h1, 0) == -1){
		fprintf(stdout, "Fixent: Bad seek\n");
		return(-1);
	}

	while((r = read(hfd, hbuf, 128)) == 128){
		s = hbuf;
		if(*s == 0){
			fprintf("New name: %s\n", newn);
			goto writit;
		}
		p = s;
		while(*p != ':')p++;
		*p = 0;
		if(strcmp(newn, s)){
			h1 += 128L;
			continue;
		}
writit:
		lseek(hfd, h1, 0);
		write(hfd, lne, 128);
		return(0);
	}
	if(err++ == 0){
		h1 = 0;
		goto lp;
	}
	fprintf(stdout, "Read error in %s\n", HASHN);
	return(-1);
}

catchi()
{
	signal(2, 1);
	lock(1);
	clear();
	fprintf(stdout, "Interrupt -- %s\n", lckbuf);
	exit(1);
}

