/*
 * enter a password in the password file
 * this program should be suid with owner
 * with an owner with write permission on /etc/passwd
 */
#include <stdio.h>
#include <signal.h>
#include <pwd.h>
#include <hsubs.h>

char	passwd[] = "/etc/passwd";
char	temp[]	 = "/etc/ptmp";
char	temp1[]	 = "/etc/ptmp1";
char	*hname = 0;
struct	passwd *pwd;
struct	passwd *getpwent();
int	endpwent();
char	*strcpy();
char	*crypt();
char	*getpass();
char	*logname();
char	*pw;
char	pwbuf[10];
char	buf[512];
int	cuid = -1;

main(argc, argv)
char *argv[];
{
	long la;
	char *p, *t, *s;
	int i;
	char saltc[16];
	long salt;
	int u,g,fi,fo;
	int insist;
	int ok, flags;
	int c;
	int pwlen;
	FILE *tf;
	char *uname;

	insist = 0;
	if(argc < 2) {
		uname = logname();
		printf("Changing password for %s\n", uname);
	} else {
		uname = argv[1];
	}
	while(((pwd=getpwent()) != NULL)&&(strcmp(pwd->pw_name,uname)!=0));
	g = getgid();
	u = getuid();
	if((pwd==NULL) || (u!=0 && g != 0 && u != pwd->pw_uid))
		{
		printf("Permission denied.\n");
		goto bex;
		}
	endpwent();
	if (pwd->pw_passwd[0] && g != 0 && u != 0) {
		strcpy(pwbuf, getpass("Old password:"));
		pw = crypt(pwbuf, pwd->pw_passwd);
		if(strcmp(pw, pwd->pw_passwd) != 0) {
			printf("Sorry.\n");
			goto bex;
		}
	}
tryagn:
	strcpy(pwbuf, getpass("New password:"));
	pwlen = strlen(pwbuf);
	if (pwlen == 0) {
		printf("Password unchanged.\n");
		goto bex;
	}
	ok = 0;
	flags = 0;
	p = pwbuf;
	while(c = *p++){
		if(c>='a' && c<='z') flags |= 2;
		else if(c>='A' && c<='Z') flags |= 4;
		else if(c>='0' && c<='9') flags |= 1;
		else flags |= 8;
	}
	if(flags >=7 && pwlen>= 4) ok = 1;
	if(((flags==2)||(flags==4)) && pwlen>=6) ok = 1;
	if(((flags==3)||(flags==5)||(flags==6))&&pwlen>=5) ok = 1;

	if((ok==0) && (insist<2)){
		if(flags==1)
		printf("Please use at least one non-numeric character.\n");
		else
		printf("Please use a longer password.\n");
		insist++;
		goto tryagn;
		}

	if (strcmp(pwbuf,getpass("Retype new password:")) != 0) {
		printf ("Mismatch - password unchanged.\n");
		goto bex;
	}

	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;
	}
	/* MUST BE CHANGED IF CRYPT ALGORITHM CHANGES */
	saltc[posit(0)] = saltc[0];
	saltc[posit(1)] = saltc[1];
	pw = crypt(pwbuf, saltc);
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);

	setuid(3);	/* so i cant be killed by some asshole, unless
				hes prived */
	setpgrp(-1);
	umask(0);
	if(access(temp, 0) >= 0) {
busy:
		printf("Temporary file busy -- try again\n");
		goto bex;
	}
	close(creat(temp,0600));
	if(link(temp, temp1) == -1)
		goto busy;
	if((tf=fopen(temp,"w")) == NULL) {
		printf("Cannot create temporary file\n");
		goto bex;
	}

/*
 *	copy passwd to temp, replacing matching lines
 *	with new password.
 */

	while((pwd=getpwent()) != NULL) {
		if(strcmp(pwd->pw_name,uname) == 0) {
			if(g != 0 && u != 0 && u != pwd->pw_uid) {
				printf("Permission denied.\n");
				goto out;
			}
			pwd->pw_passwd = pw;
			cuid = pwd->pw_uid;
		}
		fprintf(tf,"%s:%s:%d:%d:%s:%s:%s\n",
			pwd->pw_name,
			pwd->pw_passwd,
			pwd->pw_uid,
			pwd->pw_gid,
			pwd->pw_gecos,
			pwd->pw_dir,
			pwd->pw_shell);
	}
	endpwent();
	fclose(tf);

/*
 *	copy temp back to passwd file
 */

/*
	if((fi=open(temp,0)) < 0) {
		printf("Temp file disappeared!\n");
		goto out;
	}
	if((fo=creat(passwd, 0644)) < 0) {
		printf("Cannot recreat passwd file.\n");
		goto out;
	}
	while((u=read(fi,buf,sizeof(buf))) > 0) write(fo,buf,u);
	close(fi);
	close(fo);
*/
/* lets just use link to move the ptmp file, it wont spend
so much cpu time */
	chmod(temp, 0644);
	chown(temp, 3, 3);
	unlink(passwd);
	if( link(temp, passwd)){
		sleep(4);
		if(link(temp, passwd)){
			fprintf(stderr, "Can't link ptmp to passwd");
			exit(1);
		}
	}
	unlink(temp);
	unlink(temp1);
	la = (long)(hkey(uname)) << 7;
	hname = HFILE_NM;
redo:
	if((fi = open(hname, 2)) == -1){
		printf("Can't enter in %s -- see systems personel\n", hname);
		goto out;
	}
	lseek(fi, la, 0);
	while(read(fi, buf, 128) == 128 && buf[0]){
		for(p = buf; p < &buf[128]; p++){
			if(*p == ':')break;
		}
		if(*p != ':'){
badh:
			printf("Hashed file in bad format near %s -- see systems personel\n", buf);
			goto out;
		}
		*p = 0;
		if(strcmp(buf, uname) == 0){
			lseek(fi, -128L, 1);
			*p++ = ':';
			t = p;
			while(*t && *t != ':')t++;
			if(*t != ':')goto badh;
			pwlen = strlen(pw);
			if((i = pwlen - t + p) > 0){
				if(i > 13)goto badh;
				s = t;
				while(*s)s++;
				while(s >= t){
					s[i] = *s;
					s--;
				}
			} else if(i){
				if(i < -13)goto badh;
				do {
					t[i] = *t;
				} while(*++t);
			}
			t = pw;
			while(*t)
				*p++ = *t++;
			write(fi, buf, 128);
			goto out;
		}
	}
	if(strcmp(hname, HFILE_NM) == 0)
		printf("Name %s not found in %s -- see systems personel\n", uname, hname);

out:
	close(fi);
	if(strcmp(hname, HFILE_NM) == 0){
		hname = HFILE_UD;
		la = (long)(ukey(cuid)) << 7;
		goto redo;
	}
	unlink(temp);
	unlink(temp1);

bex:
	exit(1);
}
