4.3BSD-Reno/src/usr.bin/passwd/passwd.c

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

/*
 * 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: (1) source distributions retain this entire copyright notice and
 * comment, and (2) distributions including binaries display the following
 * acknowledgement:  ``This product includes software developed by the
 * University of California, Berkeley and its contributors'' in the
 * documentation or other materials provided with the distribution and in
 * all advertising materials mentioning features or use of this software.
 * Neither the name of the University nor the names of its contributors may
 * 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.42 (Berkeley) 6/19/90";
#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/wait.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#ifdef	KERBEROS
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <kerberosIV/des.h>
#include <kerberosIV/krb.h>
#include "kpasswd_proto.h"
int use_kerberos = 1;
#define ARGSTR "l"
#else
#define ARGSTR ""
#endif

uid_t uid;

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

	uid = getuid();
	uname = getlogin();

#ifdef	KERBEROS
	while ((ch = getopt(argc, argv, ARGSTR)) != EOF)
		switch (ch) {
		/* change local password file */
		case 'l':
			use_kerberos = 0;
			break;
		default:
		case '?':
			usage();
			exit(1);
		}

	argc -= optind;
	argv += optind;
#endif

	switch(argc) {
	case 0:
		break;
	case 1:
#ifdef	KERBEROS
		if (use_kerberos && (strcmp(argv[1],uname) != 0)) { 
			fprintf(stderr,
				"must kinit to change another's password\n");
			exit(1);
		}
#endif
		uname = argv[0];
		break;
	default:
		usage();
		exit(1);
	}

#ifdef	KERBEROS
	if (use_kerberos) {
		exit(do_krb_passwd());
		/* NOTREACHED */
	}
#endif

	if (!(pw = getpwnam(uname))) {
		fprintf(stderr, "passwd: unknown user %s.\n", uname);
		exit(1);
	}
	if (uid && uid != pw->pw_uid) {
		fprintf(stderr, "passwd: %s\n", strerror(EACCES));
		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", temp);
		goto bad;
	}
	passwd = _PATH_MASTERPASSWD;
	if (!freopen(passwd, "r", stdin)) {
		fprintf(stderr, "passwd: can't read %s", passwd);
		goto bad;
	}

	printf("Changing local 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, *t;
	char buf[_PASSWORD_LEN+1], salt[2], *crypt(), *getpass();
	int tries = 0;
	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:");
		if (!*p) {
			(void)printf("Password unchanged.\n");
			(void)unlink(temp);
			exit(0);
		}
		if (strlen(p) <= 5 && (uid != 0 || tries++ < 2)) {
			printf("Please enter a longer password.\n");
			continue;
		}
		for (t = p; *t && islower(*t); ++t);
		if (!*t && (uid != 0 || tries++ < 2)) {
			printf("Please don't use an all-lower case password.\nUnusual capitalization, control characters or digits are suggested.\n");
			continue;
		}
		(void)strcpy(buf, p);
		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));
#ifdef NEWSALT
	salt[0] = '_';
	to64(&salt[1], (long)(29*25), 4);
	to64(&salt[5], (long)random(), 4);
#else
	to64(&salt[0], (long)random(), 2);
#endif
	return(crypt(buf, salt));
}

static unsigned char itoa64[] =		/* 0..63 => ascii-64 */
	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

to64(s, v, n)
	register char *s;
	register long v;
	register int n;
{
	while (--n >= 0) {
		*s++ = itoa64[v&0x3f];
		v >>= 6;
	}
}

makedb(file)
	char *file;
{
	union wait pstat;
	pid_t pid, waitpid();

	if (!(pid = vfork())) {
		execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL);
		_exit(127);
	}
	return(waitpid(pid, &pstat, 0) == -1 ? -1 : pstat.w_status);
}

usage()
{
#ifdef	KERBEROS
	fprintf(stderr, "usage: passwd [-l] user\n");
#else
	fprintf(stderr, "usage: passwd user\n");
#endif
}


#ifdef	KERBEROS 
KTEXT_ST	ticket;
long		authopts = 0L;
Key_schedule	random_schedule;
char		realm[REALM_SZ], krbhst[MAX_HSTNM];
static		struct	kpasswd_data	proto_data;
static		des_cblock		okey;
static		Key_schedule	osched;
int		sock;
int		finish();
#define		PROTO	"tcp"

static struct timeval	timeout = { CLIENT_KRB_TIMEOUT, 0 };

do_krb_passwd()
{
	struct servent *se;
	struct hostent *host;
	struct sockaddr_in sin;
	int rval;
	char pass[_PASSWORD_LEN], password[_PASSWORD_LEN];
	fd_set readfds;
	CREDENTIALS	cred;

	static struct rlimit rl = { 0, 0 };

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

	if (setrlimit(RLIMIT_CORE, &rl) < 0) {
		perror("setrlimit");
		return(1);
	}

	if ((se = getservbyname(SERVICE, PROTO)) == NULL) {
		fprintf(stderr, "couldn't find entry for service %s/%s\n",
			SERVICE, PROTO);
		return(1);
	}

	if ((rval = krb_get_lrealm(realm,1)) != KSUCCESS) {
		fprintf(stderr, "couldn't get local Kerberos realm: %s\n",
			krb_err_txt[rval]);
		return(1);
	}

	if ((rval = krb_get_krbhst(krbhst, realm, 1)) != KSUCCESS) {
		fprintf(stderr, "couldn't get Kerberos host: %s\n",
			krb_err_txt[rval]);
		return(1);
	}

	if ((host = gethostbyname(krbhst)) == NULL) {
		fprintf(stderr, "couldn't get host entry for krb host %s\n",
			krbhst);
		return(1);
	}

	sin.sin_family = host->h_addrtype;
	bcopy(host->h_addr, (char *) &sin.sin_addr, host->h_length);
	sin.sin_port = se->s_port;

	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
		perror("socket");
		return(1);
	}

	if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		perror("connect");
		close(sock);
		return(1);
	}

	rval = krb_sendauth(
		authopts,		/* NOT mutual */
		sock,
		&ticket,		/* (filled in) */
		SERVICE,
		krbhst,			/* instance (krbhst) */
		realm,			/* dest realm */
		(u_long) getpid(),	/* checksum */
		NULL,			/* msg data */
		NULL,			/* credentials */ 
		NULL,			/* schedule */
		NULL,			/* local addr */
		NULL,			/* foreign addr */
		"KPWDV0.1"
	);


	if (rval != KSUCCESS) {
		fprintf(stderr, "Kerberos sendauth error: %s\n",
			krb_err_txt[rval]);
		return(1);
	}

	krb_get_cred("krbtgt", realm, realm, &cred);

	printf("Changing Kerberos password for %s.%s@%s.\n",
		cred.pname, cred.pinst, realm);

	if (des_read_pw_string(pass,
	    sizeof(pass)-1, "Old Kerberos password:", 0)) {
		fprintf(stderr,
			"error reading old Kerberos password\n");
		return(1);
	}

	(void)des_string_to_key(pass, okey);
	(void)des_key_sched(okey, osched);
	(void)des_set_key(okey, osched);

	/* wait on the verification string */

	FD_ZERO(&readfds);
	FD_SET(sock, &readfds);

	rval =
	  select(sock + 1, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout);

	if ((rval < 1) || !FD_ISSET(sock, &readfds)) {
		if(rval == 0) {
			fprintf(stderr, "timed out (aborted)\n");
			cleanup();
			return(1);
		}
		fprintf(stderr, "select failed (aborted)\n");
		cleanup();
		return(1);
	}

	/* read verification string */

	if (des_read(sock, &proto_data, sizeof(proto_data)) !=
	    sizeof(proto_data)) {
		fprintf(stderr,
		    "couldn't read verification string (aborted)\n");
		cleanup();
		return(1);
	}

	(void)signal(SIGHUP, finish);
	(void)signal(SIGINT, finish);

	if (strcmp(SECURE_STRING, proto_data.secure_msg) != 0) {
		cleanup();
		/* don't complain loud if user just hit return */
		if (pass == NULL || (!*pass))
			return(0);
		fprintf(stderr, "Sorry\n");
		return(1);
	}

	(void)des_key_sched(proto_data.random_key, random_schedule);
	(void)des_set_key(proto_data.random_key, random_schedule);
	(void)bzero(pass, sizeof(pass));

	if (des_read_pw_string(pass,
	    sizeof(pass)-1, "New Kerberos password:", 0)) {
		fprintf(stderr,
			"error reading new Kerberos password (aborted)\n");
		cleanup();
		return(1);
	}

	if (des_read_pw_string(password,
	    sizeof(password)-1, "Retype new Kerberos password:", 0)) {
		fprintf(stderr,
			"error reading new Kerberos password (aborted)\n");
		cleanup();
		return(1);
	}

	if (strcmp(password, pass) != 0) {
		fprintf(stderr, "password mismatch (aborted)\n");
		cleanup();
		return(1);
	}

	if (strlen(pass) == 0)
		printf("using NULL password\n");

	send_update(sock, password, SECURE_STRING);

	/* wait for ACK */

	FD_ZERO(&readfds);
	FD_SET(sock, &readfds);

	rval =
	  select(sock + 1, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout);
	if ((rval < 1) || !FD_ISSET(sock, &readfds)) {
		if(rval == 0) {
			fprintf(stderr, "timed out reading ACK (aborted)\n");
			cleanup();
			exit(1);
		}
		fprintf(stderr, "select failed (aborted)\n");
		cleanup();
		exit(1);
	}

	recv_ack(sock);
	cleanup();
	exit(0);
}

send_update(dest, pwd, str)
	int	dest;
	char	*pwd, *str;
{
	static struct	update_data	ud;
	strncpy(ud.secure_msg, str, _PASSWORD_LEN);
	strncpy(ud.pw, pwd, sizeof(ud.pw));
	if (des_write(dest, &ud, sizeof(ud)) != sizeof(ud)) {
		fprintf(stderr, "couldn't write pw update (abort)\n");
		bzero(ud, sizeof(ud));
		cleanup();
		exit(1);
	}
}

recv_ack(remote)
	int	remote;
{
	int	cc;
	char	buf[BUFSIZ];
	cc = des_read(remote, buf, sizeof(buf));
	if (cc <= 0) {
		fprintf(stderr, "error reading acknowledgement (aborted)\n");
		cleanup();
		exit(1);
	}
	printf("%s", buf);
}

cleanup()
{
	(void)bzero(&proto_data, sizeof(proto_data));
	(void)bzero(okey, sizeof(okey));
	(void)bzero(osched, sizeof(osched));
	(void)bzero(random_schedule, sizeof(random_schedule));
}

finish()
{
	(void)close(sock);
	exit(1);
}

#endif /* KERBEROS */