/*
 * Copyright (c) 1988 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1988 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)passwd.c	4.35 (Berkeley) 3/16/89";
#endif /* not lint */

#include <sys/param.h>
#include <sys/file.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <ctype.h>
#include <strings.h>

uid_t uid;

main(argc, argv)
	int argc;
	char **argv;
{
	extern int errno;
	struct passwd *pw;
	struct rlimit rlim;
	FILE *temp_fp;
	int fd;
	char *fend, *np, *passwd, *temp, *tend;
	char from[MAXPATHLEN], to[MAXPATHLEN];
	char *getnewpasswd();

	uid = getuid();
	switch(--argc) {
	case 0:
		if (!(pw = getpwuid(uid))) {
			fprintf(stderr, "passwd: unknown user: uid %u\n", uid);
			exit(1);
		}
		break;
	case 1:
		if (!(pw = getpwnam(argv[1]))) {
			fprintf(stderr, "passwd: unknown user %s.\n", argv[1]);
			exit(1);
		}
		if (uid && uid != pw->pw_uid) {
			fprintf(stderr, "passwd: %s\n", strerror(EACCES));
			exit(1);
		}
		break;
	default:
		fprintf(stderr, "usage: passwd [user]\n");
		exit(1);
	}

	(void)signal(SIGHUP, SIG_IGN);
	(void)signal(SIGINT, SIG_IGN);
	(void)signal(SIGQUIT, SIG_IGN);
	(void)signal(SIGTSTP, SIG_IGN);

	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
	(void)setrlimit(RLIMIT_CPU, &rlim);
	(void)setrlimit(RLIMIT_FSIZE, &rlim);

	(void)umask(0);

	temp = _PATH_PTMP;
	if ((fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) {
		if (errno == EEXIST) {
			fprintf(stderr,
			    "passwd: password file busy -- try again later.\n");
			exit(0);
		}
		fprintf(stderr, "passwd: %s: %s", temp, strerror(errno));
		goto bad;
	}
	if (!(temp_fp = fdopen(fd, "w"))) {
		fprintf(stderr, "passwd: can't write %s\n", temp);
		goto bad;
	}
	passwd = _PATH_MASTERPASSWD;
	if (!freopen(passwd, "r", stdin)) {
		fprintf(stderr, "passwd: can't read %s\n", passwd);
		goto bad;
	}

	printf("Changing password for %s.\n", pw->pw_name);
	np = getnewpasswd(pw, temp);

	if (!copy(pw->pw_name, np, temp_fp, pw))
		goto bad;

	(void)fclose(temp_fp);
	(void)fclose(stdin);

	switch(fork()) {
	case 0:
		break;
	case -1:
		fprintf(stderr, "passwd: can't fork");
		goto bad;
		/* NOTREACHED */
	default:
		exit(0);
		/* NOTREACHED */
	}

	if (makedb(temp)) {
		fprintf(stderr, "passwd: mkpasswd failed");
bad:		fprintf(stderr, "; password unchanged.\n");
		(void)unlink(temp);
		exit(1);
	}

	/*
	 * possible race; have to rename four files, and someone could slip
	 * in between them.  LOCK_EX and rename the ``passwd.dir'' file first
	 * so that getpwent(3) can't slip in; the lock should never fail and
	 * it's unclear what to do if it does.  Rename ``ptmp'' last so that
	 * passwd/vipw/chpass can't slip in.
	 */
	(void)setpriority(PRIO_PROCESS, 0, -20);
	fend = strcpy(from, temp) + strlen(temp);
	tend = strcpy(to, _PATH_PASSWD) + strlen(_PATH_PASSWD);
	bcopy(".dir", fend, 5);
	bcopy(".dir", tend, 5);
	if ((fd = open(from, O_RDONLY, 0)) >= 0)
		(void)flock(fd, LOCK_EX);
	/* here we go... */
	(void)rename(from, to);
	bcopy(".pag", fend, 5);
	bcopy(".pag", tend, 5);
	(void)rename(from, to);
	bcopy(".orig", fend, 6);
	(void)rename(from, _PATH_PASSWD);
	(void)rename(temp, passwd);
	/* done! */
	exit(0);
}

copy(name, np, fp, pw)
	char *name, *np;
	FILE *fp;
	struct passwd *pw;
{
	register int done;
	register char *p;
	char buf[1024];

	for (done = 0; fgets(buf, sizeof(buf), stdin);) {
		/* skip lines that are too big */
		if (!index(buf, '\n')) {
			fprintf(stderr, "passwd: line too long.\n");
			return(0);
		}
		if (done) {
			fprintf(fp, "%s", buf);
			continue;
		}
		if (!(p = index(buf, ':'))) {
			fprintf(stderr, "passwd: corrupted entry.\n");
			return(0);
		}
		*p = '\0';
		if (strcmp(buf, name)) {
			*p = ':';
			fprintf(fp, "%s", buf);
			continue;
		}
		if (!(p = index(++p, ':'))) {
			fprintf(stderr, "passwd: corrupted entry.\n");
			return(0);
		}
		/*
		 * reset change time to zero; when classes are implemented,
		 * go and get the "offset" value for this class and reset
		 * the timer.
		 */
		fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
		    pw->pw_name, np, pw->pw_uid, pw->pw_gid,
		    pw->pw_class, 0L, pw->pw_expire, pw->pw_gecos,
		    pw->pw_dir, pw->pw_shell);
		done = 1;
	}
	return(1);
}

char *
getnewpasswd(pw, temp)
	register struct passwd *pw;
	char *temp;
{
	register char *p;
	char buf[10], salt[2], *crypt(), *getpass();
	time_t time();

	if (uid && pw->pw_passwd &&
	    strcmp(crypt(getpass("Old password:"), pw->pw_passwd),
	    pw->pw_passwd)) {
		(void)printf("passwd: %s.\n", strerror(EACCES));
		(void)unlink(temp);
		exit(1);
	}

	for (buf[0] = '\0';;) {
		p = getpass("New password:");
		(void)strcpy(buf, p);
		if (checkpass(pw, temp, p))
			continue;
		if (!strcmp(buf, getpass("Retype new password:")))
			break;
		printf("Mismatch; try again, EOF to quit.\n");
	}
	/* grab a random printable character that isn't a colon */
	(void)srandom((int)time((time_t *)NULL));
	while ((salt[0] = random() % 93 + 33) == ':');
	while ((salt[1] = random() % 93 + 33) == ':');
	return(crypt(buf, salt));
}

makedb(file)
	char *file;
{
	int status, pid, w;

	if (!(pid = vfork())) {
		execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL);
		_exit(127);
	}
	while ((w = wait(&status)) != pid && w != -1);
	return(w == -1 || status);
}

/*
 * checkpass - check for the most common weak passwords:
 *
 *	passwords the same as the login name or login name reversed,
 *	passwords the same as names and numbers in the GECOS field,
 *	passwords matching words found in the dictionary.
 *
 *	Written by Paul Pomes, University of Illinois, CSO
 */
checkpass(pwd, TempFile, newpass)
	struct passwd *pwd;
	char *TempFile, *newpass;
{
	char	*t;
	char	*gecos, *gp, *gp2;
	char	entry[100], *ep;
	struct stat	sbuf;
	long	top, middle, bottom;
	int	c;
	FILE	*dict;
	char	*malloc(), *indexm(), *reverse();

	/* was anything entered?  */
	if (!*newpass) {
		(void)printf("Password unchanged.\n");
		(void)unlink(TempFile);
		exit(0);
	}

	/* demand at least 6 characters to prevent exhaustive searches */
	if (strlen(newpass) <= 5) {
		(void)printf("Please enter a longer password.\n");
		return(1);
	}

	/* don't allow all lower case passwords regardless of length */
	for (t = newpass; *t && islower(*t); ++t)
		;
	if (!*t) {
		(void)printf("Please don't use an all-lower case password.\nUnusual capitalization, control characters or digits are suggested.\n");
		return(1);
	}

	/* check for password == name, possibly repeated, regardless of case */
	*entry = '\0';
	do {
		(void)strcat(entry, pwd->pw_name);
		if (! strncasecmp(newpass, entry, 8) ||
		    ! strncasecmp(newpass, reverse(entry), 8)) {
			(void)printf("Please don't use your account name as a password.\n");
			return(1);
		}
	} while (strlen(entry) < 8);

	/* check for password == GECOS tokens */
	if ((gecos = malloc((unsigned) (strlen(pwd->pw_gecos)+1))) == NULL) {
		perror("passwd: ");
		(void)unlink(TempFile);
		exit(1);
	}
	(void)strcpy(gecos, pwd->pw_gecos);
	for (gp = gecos; *gp;) {
		if ((gp2 = indexm(gp, " ,;")) != NULL)
			*gp2 = '\0';
		else
			gp2 = gecos + strlen(gecos) - 1;
		if (! strncasecmp(newpass, gp, 8) ||
		    ! strncasecmp(newpass, reverse(gp), 8)) {
			(void)printf("Please don't use your name, office or phone number as a password.\n");
			free(gecos);
			return(1);
		}
		gp = ++gp2;
	}
	free(gecos);

	/* check against /usr/dict/words.  return silently if open fails */
	if ((dict = fopen("/usr/dict/words", "r")) == (FILE *)NULL)
		return(0);
	if (fstat(fileno(dict), &sbuf)) {
		perror("passwd: fstat of /usr/dict/words");
		(void)fclose(dict);
		return(0);
	}
	for (bottom = 0, top = sbuf.st_size;;) {
		middle = (top + bottom) / 2;
		(void)fseek(dict, middle, 0);

		for (++middle; (c = getc(dict)) != EOF && c != '\n'; ++middle)
			;
		for (ep = entry;;) {
			if ((c = getc(dict)) == EOF)
				break;
			if (c == '\n')
				break;
			*ep++ = c;
		}
		*ep = '\0';
		if (*entry == '\0')
			break;
		if (strncasecmp(newpass, entry, 8) <= 0) {
			if (top <= middle)
				break;
			top = middle;
		}
		else
			bottom = middle;
	}
	(void)fseek(dict, bottom, 0);
	while (ftell(dict) <= top) {
		for (ep = entry;;) {
			if ((c = getc(dict)) == EOF)
				break;
			if (c == '\n')
				break;
			*ep++ = c;
		}
		*ep = '\0';
		if (*entry == '\0') {
			(void)fclose(dict);
			return(0);
		}
		if (! strncasecmp(newpass, entry, 8)) {
			(void)printf("Please don't use words that can be found in the dictionary.\n");
			(void)fclose(dict);
			return(1);
		}
	}
	(void)fclose(dict);
	return(0);
}

/*
 * Written by Jacob Gore, March 12, 1987.
 *
 * Finds the pointer of the leftmost occurance within the character string
 * 'string' of any character found within the character string 'chars'.
 *
 * If none of the characters in 'chars' appear in 'string', NULL is returned.
 *
 */
char *
indexm (string, chars)
	char *string, *chars;
{
	while (*string) {
		if (index(chars, *string) != NULL)
			return(string);
		string++;
	}
	return(NULL);
}

/* reverse a string */
char *
reverse(str)
	char *str;
{
	static char rbuf[100];
	int	len = strlen(str);
	char	*ptr = rbuf + len;

	*ptr = '\0';
	while (*str && (*--ptr = *str++))
		;
	return(rbuf);
}

#ifdef	STRNCASECMP
/*
 * Copyright (c) 1987 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)strcasecmp.c	5.6 (Berkeley) 6/27/88";
#endif /* LIBC_SCCS and not lint */

#include <sys/types.h>

/*
 * This array is designed for mapping upper and lower case letter
 * together for a case independent comparison.  The mappings are
 * based upon ascii character sequences.
 */
static u_char charmap[] = {
	'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
	'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
	'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
	'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
	'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
	'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
	'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
	'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
	'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
	'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
	'\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
	'\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
	'\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
	'\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
	'\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
	'\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
	'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
	'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
	'\300', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\333', '\334', '\335', '\336', '\337',
	'\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};

strcasecmp(s1, s2)
	char *s1, *s2;
{
	register u_char	*cm = charmap,
			*us1 = (u_char *)s1,
			*us2 = (u_char *)s2;

	while (cm[*us1] == cm[*us2++])
		if (*us1++ == '\0')
			return(0);
	return(cm[*us1] - cm[*--us2]);
}

strncasecmp(s1, s2, n)
	char *s1, *s2;
	register int n;
{
	register u_char	*cm = charmap,
			*us1 = (u_char *)s1,
			*us2 = (u_char *)s2;

	while (--n >= 0 && cm[*us1] == cm[*us2++])
		if (*us1++ == '\0')
			return(0);
	return(n < 0 ? 0 : cm[*us1] - cm[*--us2]);
}
#endif /* STRNCASECMP */
