
/* --------------------------------------------------------------------  */
/*                                                                       */
/*                         Author: Clyde Hoover                          */
/*                          Computation Center                           */
/*                   The University of Texas at Austin                   */
/*                          Austin, Texas 78712                          */
/*                         clyde@emx.utexas.edu                          */
/*                   uunet!cs.utexas.edu!ut-emx!clyde                    */
/*                                                                       */
/*This code may be distributed freely, provided this notice is retained. */
/*                                                                       */
/* --------------------------------------------------------------------  */
/*
 *	pw_yp - Routines for dealing with SUN Yellow Pages password files
 *
 *	This code can update local password file, can cause rebuilding of
 *	local YP maps and can use yppasswdd(8) to change YP passwords.
 *
 *	Must be linked with -lrpcsvc
 */
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include <fcntl.h>
#include <rpc/rpc.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/yppasswd.h>
#include <sys/socket.h>

#ifdef	SECURE_RPC
#include <rpc/key_prot.h>
#endif

#ifdef	SYSV
#define	index	strchr
#endif

#ifdef	SYSLOG
#include <syslog.h>
#endif

#ifndef lint
static char sccsid[] = "@(#)pw_yp.c	1.15 1/25/91 (cc.utexas.edu)";
#endif

#define	NONE	-1	/* YP not active */
#define	NOT	0	/* YP active - we are not master */
#define	IS	1	/* YP active - we have the password file */

#define	SLOP	128	/* Size difference tolerated old <> new passwd file */

typedef struct passwd	passwd;
typedef	struct passwd	*passwdp;

static passwd	theUser,	/* User being changed */
		Me;		/* User invoking passwd */
static int	myuid,		/* Uid of invoker */
		mytempfile = 0;	/* Does PASSWD_TEMP belong to me? */
static char	*ypmaster,	/* Name of the YP master */
		*ypdomain;	/* YP domain name */

#define	PASSWD_MAP	"passwd.byname"		/* Name of YP passwd map */

/*
 *	File names
 */
#ifndef	PASSWD_FILE
#define	PASSWD_FILE	"/etc/passwd"
#endif
 
#ifndef	PASSWD_SAVE
#define	PASSWD_SAVE	"/etc/opasswd"
#endif

#ifndef	PASSWD_TEMP
#define	PASSWD_TEMP	"/etc/ptmp"
#endif

#define	PASSWD_MODE	0644
 
#ifdef	DEBUG
static char	*passwdtemp = "./etc_ptmp",		/* Temp file */
		*passwdfile = "./etc_passwd",		/* Password file */
		*savefile = "./etc_opasswd";		/* Save file */
#else
static char	*passwdtemp = PASSWD_TEMP,
		*passwdfile = PASSWD_FILE,
		*savefile = PASSWD_SAVE;
#endif
static char	auxlockfile[MAXPATHLEN];		/* Aux lock file */

extern int	errno;

char	*getlogin(),
	*crypt();

/*
 *	pw_initialize - set up
 */
pw_initialize()
{
	passwdp	me;			/* Passwd for invoker */
	char	*myname = getlogin();	/* Invoker login name */

#ifdef	DEBUG
	setpwfile(passwdfile);
#endif
	myuid = getuid();
	if (myname && *myname) {
		if ((me = getpwnam(myname)) == NULL)
			quit(1, "Cannot get user identification from name.\n");
	} else {
		if ((me = getpwuid(myuid)) == NULL)
			quit(1, "Cannot get user identification from uid.\n");
	}

	_cppasswd(me, &Me);
	return(1);
}

/*
 *	pw_getuserbyname - get password
 *
 *	Returns 1 if passwd found for <name>
 *		0 otherwise
 */
pw_getuserbyname(name, passwdb)
char	*name,		/* User name */
	*passwdb;	/* Where to stuff password */
{
	passwdp	p;	/* Temp */

	if ((p = getpwnam(name)) == NULL)
		return(0);
	_cppasswd(p, &theUser);
	(void) strcpy(passwdb, p->pw_passwd);
	return(1);
}

/*
 *	pw_permission - check password change permission
 *
 *	Returns 1 if password can be changed
 *		0 if not
 */
pw_permission()
{
	/*
	 * Is this my password or someone elses?
	 */
	if (strcmp(Me.pw_name, theUser.pw_name) && myuid)
		return(0);

	/*
	 * If on a YP client, root cannot change another
	 * users' password via yppasswd(), since we can't
	 * get a plain text password to pass to yppasswdd().
	 */
	if (myuid == 0 && is_yp_master() == NOT) {
		FILE	*pf;
		passwdp px,		/* Password file traversal */
			fgetpwent();
		int	rc = 0;

		/* What if passwdfile != /etc/passwd?  */
		if ((pf = fopen(passwdfile, "r")) == NULL)
			quit(1, "Cannot open password file \"%s\".\n", passwdfile);
		/*
		 * Scan local password file, looking for user
		 * Cannot use getpwnam() because it will use YP - I want to know
		 * if the user's password file entry is >>local<<
		 */
		while ((px = fgetpwent(pf)) != NULL) {
			if (strcmp(px->pw_name, theUser.pw_name) == 0) {
				rc = 1;
				break;
			}
		}
		fclose(pf);
		if (rc) {
			if (strncmp(px->pw_passwd, "##", 2) == 0)
			    quit(0,
				"Changing of adjunct passwords not supported.\n");
			return(1);
		}
		else
			quit(0, "Password for %s can only be changed on YP server %s.\n",
				theUser.pw_name, ypmaster);
	}
	/* Check if passwd is '##username' - can't do that yet */

	/*
	 * Other checks can be put here to determine if the invoker should
	 * be allowed to change this password.
	 */
	return(1);
}

/*
 *      pw_compare - compare old and new passwords
 *
 *      Returns 1 if check = new, 0 if not
 */
pw_compare(current, new)
char	*current,		/* Current pw (encrypted) */
	*new;			/* check pw (plain) */
{
	if (!*current)		/* Is current password null? */
		return(0);
	/* Put other administrative checks here */
	return(!strcmp(current, crypt(new, current)));
}

/*
 *      pw_check - sanity check password.  Right now just calls
 *              the password check code
 *
 *      Returns 1 if password is ok to use, 0 otherwise
 */
pw_check(pwd)
char	*pwd;
{
	int	rc = checkpasswd(theUser.pw_uid, pwd);

#ifdef	PASSWORD_HISTORY
	if (rc)
		return(rc);
	/* Call password history checker to prevent password reuse */
	rc = passwd_history(theUser.pw_uid, pwd);
#endif
	return(rc);
}

/*	Error message for when yppasswdd fails with error code 1.  */
static char *yperrmsg =
"Password change failed: Problem with yppasswdd.\n\n\
This is probably because the YP maps are out of sync\n\
with the YP passwd file for %s on %s.\n\n\
Please try again later.\n";

/*
 *      pw_replace - replace password in passwd file 
 */
pw_replace(newpwd, curpwd)
char	*newpwd,		/* New password (plain) */
	*curpwd;		/* Old password (plain) */
{
	passwdp px,		/* Password file traversal */
		fgetpwent();
	long    oldsigs,	/* Old signal mask */
		blocksigs = sigmask(SIGINT) |	/* Sigs to block */
			    sigmask(SIGQUIT) |
			    sigmask(SIGTSTP);
	FILE	*tf,		/* New password file output */
		*pf;		/* Current password file input */
	int	fd,		/* Temp file create fd */
		islocal = 0;	/* Is user in local password file */
	struct stat	oldstat,	/* Old password file stat */
			newstat;	/* New password file stat */

	if ((pf = fopen(passwdfile, "r")) == NULL)
		quit(1, "Cannot open password file \"%s\".\n", passwdfile);
	/*
	 * Scan local password file, looking for user
	 * Cannot use getpwnam() because it will use YP - I want to know
	 * if I have to change a >>local<< password file.
	 */
	while ((px = fgetpwent(pf)) != NULL) {
		if (*px->pw_name == '+' || *px->pw_name == '-')
			continue;
		if (strcmp(px->pw_name, theUser.pw_name) == 0) {
			if (strncmp(px->pw_passwd, "##", 2) == 0)
			    quit(0,
				"Changing of adjunct passwords not supported.\n");
			islocal++;
			break;
		}
	}
	rewind(pf);

	/*
	 * If the user was not in the local password file, use RPC
	 * to update the Yellow Pages (NIS) password file.
	 */
	if (islocal == 0) {
		if (is_yp_master() == NOT) {
			int	rc;		/* Return code from ypasswdd */
			int	why;		/* RPC call return code */
			int	ypport;		/* Port for RPC call */
			struct yppasswd yppasswd; /* YP passwd change block */
			char	salt[4];	/* Password encryption salt */

			if (curpwd[0] == 0)
				quit(0, "Cannot change YP password without old password.\n");
			randomstring(salt, sizeof(salt));
			theUser.pw_passwd = crypt(newpwd, salt);
			yppasswd.oldpass = curpwd;
			_cppasswd(&theUser, &yppasswd.newpw);
#ifdef	DEBUG
			printf("yppasswd(%s, %s)\n", curpwd, theUser.pw_passwd);
#else
			if ((ypport = getrpcport(ypmaster, YPPASSWDPROG,
			     YPPASSWDPROC_UPDATE, IPPROTO_UDP)) == 0)
				quit(1, "%s is not running ypassswdd.\n",
				     ypmaster);

			if (ypport >= IPPORT_RESERVED)
				quit(1, "yppasswdd on %s not privleged.\n",
					ypmaster);
			rc = callrpc(ypmaster, YPPASSWDPROG, YPPASSWDVERS,
				YPPASSWDPROC_UPDATE, xdr_yppasswd, &yppasswd,
				xdr_int, &why);

			/* RPC call error */
			if (rc)
#if	NO_CLNT_SPERRNO
				clnt_perrno(rc);
				quit(1, "Password change failed (%s)\n",
					ypmaster);
#else
				quit(1, "Password change failed (%s): %s\n",
					ypmaster, clnt_sperrno(rc));
#endif

			/* Error returned from yppasswdd */
			if (why) {
#ifdef	SYSLOG
				syslog(LOG_ERR,
					"yppasswdd error %d on %s for %s",
					why, ypmaster, theUser.pw_name);
#endif
				if (why == 1)
				   quit(0, yperrmsg,  ypdomain, ypmaster);
				else
				   quit(1, "Password change failed.\n");
			}
# ifdef	SECURE_RPC
			reset_secret_key(curpwd);
# endif /* SECURE_RPC */
#endif	/* DEBUG */
			return;
		}
		else	/* User not in local passwd, and not in YP passwd */
			quit(1, "User %s missing from password file.\n",
				theUser.pw_name);
	}

	/*
	 * There is a local password file to change
	 */
	(void) umask(0);
	(void) fstat(fileno(pf), &oldstat);
	/*
	 * Use different temp file if on YP master.
	 * This deals with the SunOS 4.0.3 yppasswdd which creates temp files
	 * named "passwd-file.ptmp", rather than the traditional "/etc/ptmp".
	 * But there are still a lot of applications which use /etc/ptmp,
	 * so is it used as the passwd temp file and the 'auxlockfile' is
	 * also made --- >>>GROAN<<<.
	 */
	auxlockfile[0] = 0;
	if (is_yp_master() == IS) {
		(void) sprintf(auxlockfile, "%s.ptmp", passwdfile);
		close(mklocktemp(auxlockfile));
	}
	mytempfile = 1;
	fd = mklocktemp(passwdtemp);
	if ((tf = fdopen(fd, "w")) == NULL)
		quit(1, "Cannot fdopen temp file.\n");

	oldsigs = sigblock(blocksigs);
	while ((px = fgetpwent(pf)) != NULL) {
		if (px->pw_name == 0 || px->pw_name[0] == 0) /* Sanity check */
			continue;
		if (strcmp(px->pw_name, theUser.pw_name) == 0) {
			char	salt[4];	/* Password encryption salt */

			randomstring(salt, sizeof(salt));
			theUser.pw_passwd = crypt(newpwd, salt);
			px = &theUser;
		}
		(void) putpwent(px, tf);
	}
	(void) fflush(tf);			/* Force buffers empty */
	(void) fstat(fileno(tf), &newstat);	/* Get size */
	(void) fclose(tf);
	(void) fclose(pf);

	/*
	 * Check if the new password file is complete.  Since the encrypted
	 * password is of a fixed length, the new file should be roughly
	 * the same size as the old one.
	 *
	 * This assumption will FAIL when this program does chfn and chsh!!! -
	 * use line counts.
	 */
	if (newstat.st_size < (oldstat.st_size - SLOP))
		quit(1,
		"New password file appears to be incomplete - aborting.\n");

	if (rename(passwdfile, savefile) < 0) {
		perror("Password file save");
		(void) unlink(passwdtemp);
		quit(1, "Can't save password file.\n");
	}
	if (rename(passwdtemp, passwdfile) < 0) {
		perror("Password file replace");
		(void) unlink(passwdtemp);
		(void) link(savefile, passwdfile);
		quit(1, "Can't replace password file.\n");
	}
	if (is_yp_master() == IS)
		updateyp();
	(void) sigsetmask(oldsigs);
}

/*
 *      pw_cleanup - clean up after myself
 */
pw_cleanup(code)
int	code;		/* 0 for normal, 1 for abort */ /*NOTUSED*/
{
	if (mytempfile) {
		(void) unlink(passwdtemp);
		if (auxlockfile[0])
			(void) unlink(auxlockfile);
	}
}

/*
 *      _newstr - copy string into new storage
 */
static char *
_newstr(s)
char	*s;		/* String to copy */
{
	register char	*t;	/* Temp */
	char	*malloc();

	if (s == NULL)
		return(0);
	t = malloc(strlen(s) + 1);
	if (t == NULL)
		quit(1, "No memory.\n");
	(void) strcpy(t, s);
	return(t);
}

/*
 *	mklocktemp - Make temp file with exclusive use checking
 *
 *	Returns file descriptor of created file, else exits with error
 */
static int
mklocktemp(name)
char	*name;
{
	int	fd;

	fd = open(name, O_WRONLY|O_CREAT|O_EXCL, PASSWD_MODE);
	if (fd < 0) {
		if (errno == EEXIST)
			quit(0, "Password file busy - try again.\n");
		perror("Tempfile create");
		quit(1, "Cannot create temp file.\n");
	}
	return(fd);
}

/*
 *	 _cppasswd - copy a passwd structure
 */
static
_cppasswd(f,t)
passwdp	f,		/* From */
	t;		/* To */
{
	*t = *f;
	t->pw_name = _newstr(f->pw_name);
	t->pw_passwd = _newstr(f->pw_passwd);
	t->pw_comment = _newstr(f->pw_comment);
	t->pw_gecos = _newstr(f->pw_gecos);
	t->pw_dir = _newstr(f->pw_dir);
	t->pw_shell = _newstr(f->pw_shell);
}

/*
 *	is_yp_master - Figure out whether we are running on the Yellow Pages
 *		master for the password file maps.
 *
 *	Returns:
 *		IS if we are the master
 *		NOT if we are not the master
 *		NONE if there is no master
 */
#include <netdb.h>
#ifndef	MAXHOSTNAMLEN
#define	MAXHOSTNAMLEN 32
#endif

is_yp_master()
{
	static char	known = 0,	/* We've been here */
			answer = NOT;	/* ...and this is the answer */
	char	hostname[MAXHOSTNAMLEN];	/* Our host name */
	char	*index();
	struct hostent	*hinfo;

	if (known)
		return(answer);
	(void) gethostname(hostname, sizeof(hostname));
	if (yp_get_default_domain(&ypdomain)) {
/* 		quit(1, "Cannot get YP domain.\n"); */
		known++;
		return(answer = NONE);		/* Assume no YP running */
	}

	if (yp_master(ypdomain, PASSWD_MAP, &ypmaster)) {
		known++;
		return(answer = NONE);		/* Assume no YP running */
	}

	known++;
#ifdef	FASTCHECK
	/*
	 * Stupid (but fast) hostname check (first component only)
	 */
	{
		char	*p;			/* Scratch */

		if (p = index(ypmaster, '.')) *p = 0;
		if (p = index(hostname, '.')) *p = 0;
	}
#else
	/*
	 * Compare my host name and the YP master's host name.
	 * Use gethostbyname() to return the fully qualified form so that
	 * a string compare can be done.
	 */
	if (index(hostname, '.') == 0) {
		if ((hinfo = gethostbyname(hostname)) == 0)
			quit(1, "Cannot get hostinfo for self.\n");
		(void) strcpy(hostname, hinfo->h_name);
	}
	if (index(ypmaster, '.') == 0) {
		static char	ypmaster_f[MAXHOSTNAMLEN];

		if ((hinfo = gethostbyname(ypmaster)) == 0)
			quit(1, "Cannot get hostinfo for ypmaster.\n");
		(void) strcpy(ypmaster_f, hinfo->h_name);
		ypmaster = ypmaster_f;
	}
#endif
	if (strcmp(ypmaster, hostname) == 0)
		return(answer = IS);
	return(answer);
}

/*
 *	An example sh(1) script to update YP password map
 */
char	*ypcmd =
	"(PATH=/bin:/usr/bin; export PATH; ypdirs='/var/yp /etc/yp'\n\
	for d in $ypdirs; do\n\
		if [ -d $d ]; then\n\
			cd $d; exec make passwd\n\
		fi\n\
	done\n\
	echo 'passwd: Cannot rebuild YP maps!' 1>&2\n\
	false) >/dev/null &\n";

/*
 *	updateyp - update local YP maps
 */
updateyp()
{
	(void) setuid(geteuid()); 	/* Get all privs */
			/* (This assumes that we are setuid root) */
	/*
	 * This machine is the YP master for passwd - invoke something
	 * to update the YP maps.
	 * Super-user can override the default YP updater by setting
	 * env "YP_UPDATE_PROC" to a command to be run instead.
	 * The name of the user being changed is piped to stdin of the command.
	 */
	if (myuid == 0) {
		char	*getenv();
		char	*proc = getenv("YP_UPDATE_PROC");

		if (proc && *proc) {
			char	cmdbuf[BUFSIZ];

			(void) sprintf(cmdbuf, "/bin/echo '%s' | ( %s )",
				theUser.pw_name, proc);
#ifdef	DEBUG
			printf("updateyp (proc): %s", cmdbuf);
#endif
			(void) system(cmdbuf);
			return;
		}
	}
#ifdef	DEBUG
	printf("updateyp: %s", ypcmd);
#endif
	(void) system(ypcmd);
}

#ifdef	SECURE_RPC
/*
 *	reset_secret_key - Reset secret key for secure RPC
 */
reset_secret_key(curpwd)
char	*curpwd;
{
	char	mynet[MAXNETNAMELEN+1],
		key[HEXKEYBYTES+1];

	getnetname(mynet);
	if (!getsecretkey(mynet, key, curpwd))
		return;		/* Secure RPC not running */
	if (key[0] = 0)
		return;		/* No secret key */
	fprintf(stderr, "Cannot change secure RPC key\n");
	/*
	 * Actually I could, but I'd have to steal from Sun source
	 * code to do it.  Sun wouldn't like that very much, so I won't.
	 */
}
#endif
/*		End pw_yp.c 		*/
