/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <crypt.h>
#include <errno.h>
#include <tiuser.h>
#include <netdir.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/yppasswd.h>
#include <netconfig.h>
#include <deflt.h>

/* N2L includes */
#include <ndbm.h>
#include "shim.h"
#include "yptol.h"

/* must match sizes in passwd */
#define	STRSIZE 100

#define	DEFDIR "/etc/"
#define	MYPASSWD "passwd"
#define	MYSHADOW "shadow"
#define	DEFAULT_YPPASSWDD "/etc/default/yppasswdd"
#define	YPPASSWDD_STR "check_restricted_shell_name=1"

/* The guts are in there */
extern void changepasswd(SVCXPRT *);

static void	boilerplate(struct svc_req *rqstp, SVCXPRT *transp);
static void	unlimit(int lim);
bool_t		validloginshell(char *sh, char *arg, int);
int		validstr(char *str, size_t size);

extern char  *getusershell(void);
extern void   setusershell(void);
extern void   endusershell(void);

int  Argc;
char **Argv;
int  mflag;			/* do a make */
int Mstart;
int single = 0;
int nogecos = 0;
int noshell = 0;
int nopw = 0;
int useadjunct = 0;
int useshadow = 0;

static char *defshell = "/bin/sh";

/* These are the various reasons we might exit. */
enum exitstat {
    Esuccess,
    EminusDandfiles,
    Emissingdir,
    Emissingadjunct,
    Eaccesspasswd,
    Eaccessshadow,
    Echdir,
    Egetnetconfigent,
    Et_open,
    Enetdir_rsvdport,
    Et_sync,
    Et_info,
    Esvc_create,
    Esvc_reg,
    Esvcrun_ret,
    ElockFail,
    EparseFail
};

static char err_usage[] =
"Usage:\n"
"        rpc.yppasswdd [-D directory | passwd [passwd.adjunct]]\n"
"                      [-nopw] [-nogecos]\n"
"                      [-noshell] [-m arg1 arg2 ...]\n"
"where\n"
"        directory is the directory where the passwd, shadow and/or\n"
"        passwd.adjunct files are found (/etc by default)\n"
"        It should match the setting of PWDIR in /var/yp/Makefile\n\n"
"        Alternatively, the old 4.1.x syntax is supported where\n"
"        passwd is the path to the passwd file\n"
"        passwd.adjunct is the patch to the passwd.adjunct file\n"
"        NOTES:\n"
"         1. The -D option and the passwd/passwd.adjunct arguments are\n"
"            mutually exclusive\n"
"         2. The old syntax deprecated and will be removed in a future\n"
"            release\n"
"         3. A shadow file found in the same directory as the passwd\n"
"            will be assumed to contain the password information\n\n"
"        arguments after -m are passed to make(1S) after password changes\n"
"        -nopw passwords may not be changed remotely using passwd\n"
"        -nogecos full name may not be changed remotely using passwd or chfn\n"
"        -noshell shell may not be changed remotely using passwd or chsh\n";

char passwd_file[FILENAME_MAX], shadow_file[FILENAME_MAX];
char lockfile[FILENAME_MAX], adjunct_file[FILENAME_MAX];

int
main(int argc, char **argv)
{
	SVCXPRT *transp4, *transp6, *transpl;
	struct netconfig *nconf4, *nconf6, *nconfl;
	int i, tli4, tli6, stat;
	int errorflag;
	int dfexcl; /* -D or files, not both flag */
	enum exitstat exitstatus = Esuccess;
	int connmaxrec = RPC_MAXDATASIZE;

	strcpy(passwd_file, DEFDIR MYPASSWD);
	strcpy(shadow_file, DEFDIR MYSHADOW);
	strcpy(lockfile, DEFDIR ".pwd.lock");
	strcpy(adjunct_file, DEFDIR "security/passwd.adjunct");

	Argc = argc;
	Argv = argv;

	for (i = 1, errorflag = 0, dfexcl = 0; i < argc; i++) {
		if (argv[i][0] == '-' && argv[i][1] == 'm') {
		    if (access("/usr/ccs/bin/make", X_OK) < 0)
			fprintf(stderr,
				"%s: /usr/ccs/bin/make is not available, "
				"ignoring -m option",
				argv[0]);
		    else {
			mflag++;
			Mstart = i;
			break;
		    }
		} else if (argv[i][0] == '-' && argv[i][1] == 'D') {
		    switch (dfexcl) {
		    case 0:
			if (++i < argc) {
			    strcpy(passwd_file, argv[i]);
			    strcpy(shadow_file, argv[i]);
			    strcpy(adjunct_file, argv[i]);
			    strcpy(lockfile, argv[i]);
			    if (argv[i][strlen(argv[i]) - 1] == '/') {
				strcat(passwd_file, MYPASSWD);
				strcat(shadow_file, MYSHADOW);
				strcat(lockfile, ".pwd.lock");
				strcat(adjunct_file, "security/passwd.adjunct");
			    } else {
				strcat(passwd_file, "/" MYPASSWD);
				strcat(shadow_file, "/" MYSHADOW);
				strcat(lockfile, "/.pwd.lock");
				strcat(adjunct_file,
					"/security/passwd.adjunct");
			    }
			    dfexcl++;
			} else {
			    fprintf(stderr,
				"rpc.yppasswdd: -D option requires a "
				"directory argument\n");
			    errorflag++;
			    exitstatus = Emissingdir;
			}
			break;
		    case 1:
			fprintf(stderr,
				"rpc.yppasswdd: cannot specify passwd/"
				"passwd.adjunct pathnames AND use -D\n");
			errorflag++;
			dfexcl++;
			exitstatus = EminusDandfiles;
			break;
		    default:
			break;
		    }
	/* -single: Allow user to change only one of password,  */
	/*		shell, or full name at a time.  (WHY?)	*/
	/*	else if (strcmp(argv[i], "-single") == 0)	*/
	/*	    single = 1;					*/
	/*	else if (strcmp(argv[i], "-nosingle") == 0)	*/
	/*	    single = 0;					*/
		} else if (strcmp(argv[i], "-nogecos") == 0)
		    nogecos = 1;
		else if (strcmp(argv[i], "-nopw") == 0)
		    nopw = 1;
		else if (strcmp(argv[i], "-noshell") == 0)
		    noshell = 1;
		else if (argv[i][0] != '-') {
			/*
			 * If we find a shadow file, we warn that we're
			 * using it in addition to warning that the user
			 * it using a deprecated syntax.
			 */
		    errorflag++;
		    switch (dfexcl) {
		    case 0:
			strcpy(passwd_file, argv[i]);
			memset(shadow_file, 0, sizeof (shadow_file));
			strncpy(shadow_file, argv[i],
				strrchr(argv[i], '/') - argv[i] + 1);
			strcat(shadow_file, MYSHADOW);
			fprintf(stderr,
				"rpc.yppasswdd: specifying the password file"
				" on the command line is \n"
				"               obsolete, "
				"consider using the -D option instead.\n");
			if (access(shadow_file, F_OK) == 0) {
			    fprintf(stderr,
				    "rpc.yppasswdd: found a shadow file in "
				    "the same directory as %s\n"
				    "               It will be used.\n",
				    passwd_file);
			}
			if (i + 1 < argc && argv[i+1][0] != '-') {
			    strcpy(adjunct_file, argv[++i]);
			    if (access(adjunct_file, F_OK) != 0) {
				fprintf(stderr,
					"rpc.yppasswdd: adjunct file %s "
					"not found\n",
					adjunct_file);
				exitstatus = Emissingadjunct;
			    }
			}
			dfexcl++;
			break;
		    case 1:
			fprintf(stderr,
				"rpc.yppasswdd: cannot specify passwd/"
				"passwd.adjunct pathnames AND use -D\n");
			dfexcl++;
			exitstatus = EminusDandfiles;
			break;
		    default:
			break;
		    }
		} else {
		    errorflag++;
		    fprintf(stderr,
			    "rpc.yppasswdd: unrecognized option %s ignored\n",
			    argv[i]);
		}
	}

	if (errorflag)
		fprintf(stderr, err_usage);

	if (exitstatus)
		exit(exitstatus);

	if (access(passwd_file, W_OK) < 0) {
		fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
			passwd_file);
		exitstatus = Eaccesspasswd;
	}
	if (access(shadow_file, W_OK) == 0) {
		useshadow = 1;
	} else {
		/* We don't demand a shadow file unless we're looking at /etc */
		if (strcmp(DEFDIR MYSHADOW, shadow_file) == 0) {
		    fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
				shadow_file);
		    exitstatus = Eaccessshadow;
		}
	}
	if (access(adjunct_file, W_OK) == 0) {
		/* using an adjunct file */
		useadjunct = 1;
	}

	if (chdir("/var/yp") < 0) {
		fprintf(stderr, "rpc.yppasswdd: can't chdir to /var/yp\n");
		exitstatus = Echdir;
	}

	if (exitstatus)
		exit(exitstatus);

	if (errorflag)
		fprintf(stderr, "\nProceeding.\n");


	/*
	 * Initialize locking system.
	 * This is required for N2L version which accesses the DBM files.
	 * For the non N2L version this sets up some locking which, since non
	 * N2L mode does not access the DBM files, will be unused.
	 *
	 * This also sets up yptol_mode.
	 */
	if (!init_lock_system(TRUE)) {
		fprintf(stderr,
			"rpc.yppasswdd: Cant initialize locking system\n");
		exit(ElockFail);
	}

#ifndef	DEBUG
	/* Close everything, but stdin/stdout/stderr */
	closefrom(3);
#endif

	if (yptol_mode) {
		stat = parseConfig(NULL, NTOL_MAP_FILE);
		if (stat == 1) {
			fprintf(stderr, "yppasswdd : NIS to LDAP mapping"
							" inactive.\n");
		} else if (stat != 0) {
			fprintf(stderr, "yppasswdd : Aborting after NIS to LDAP"
							" mapping error.\n");
			exit(EparseFail);
		}
	}

#ifndef	DEBUG
	/* Wack umask that we inherited from parent */
	umask(0);

	/* Be a midwife to ourselves */
	if (fork())
		exit(Esuccess);

	/* Disassociation is hard to do, la la la */
	setpgrp();
	setsid();

	/* Ignore stuff */
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGWINCH, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);

	/*
	 * Just in case that wasn't enough, let's fork
	 * again.  (per Stevens).
	 */
	if (fork())
		exit(Esuccess);

	/*
	 * We need stdin, stdout, and stderr later when we
	 * fork a make(1).
	 */
	freopen("/dev/null", "r+", stdin);
	freopen("/dev/null", "r+", stdout);
	freopen("/dev/null", "r+", stderr);
#endif

	openlog("yppasswdd", LOG_CONS | LOG_PID, LOG_AUTH);
	unlimit(RLIMIT_CPU);
	unlimit(RLIMIT_FSIZE);

	/*
	 * Set non-blocking mode and maximum record size for
	 * connection oriented RPC transports.
	 */
	if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) {
		syslog(LOG_INFO, "unable to set maximum RPC record size");
	}

	nconf4 = getnetconfigent("udp");
	nconf6 = getnetconfigent("udp6");
	if (nconf4 == 0 && nconf6 == 0) {
		syslog(LOG_ERR, "udp/udp6 transport not supported\n");
		exit(Egetnetconfigent);
	}

	tli4 = (nconf4 != 0) ? t_open(nconf4->nc_device, O_RDWR, NULL) : -1;
	tli6 = (nconf6 != 0) ? t_open(nconf6->nc_device, O_RDWR, NULL) : -1;

	if (tli4 == -1 && tli6 == -1) {
		syslog(LOG_ERR, "can\'t open TLI endpoint(s)\n");
		exit(Et_open);
	}

	if (tli4 != -1) {
		if (netdir_options(nconf4, ND_SET_RESERVEDPORT, tli4, NULL)) {
			syslog(LOG_ERR, "could not set reserved port: %s\n",
				netdir_sperror());
			exit(Enetdir_rsvdport);
		}
	}
	if (tli6 != -1) {
		if (netdir_options(nconf6, ND_SET_RESERVEDPORT, tli6, NULL)) {
			syslog(LOG_ERR, "could not set reserved port: %s\n",
				netdir_sperror());
			exit(Enetdir_rsvdport);
		}
	}
#ifdef	DEBUG
	{
		int i, tli[2];
		char *label[2] = {"udp", "udp6"};
		int state;
		struct t_info tinfo;

		tli[0] = tli4;
		tli[1] = tli6;

		for (i = 0; i < sizeof (tli)/sizeof (tli[0]); i++) {
			fprintf(stderr, "transport %s, fd = %d\n",
				tli[i], label[i]);
			if ((state = t_sync(tli[i])) < 0) {
				fprintf(stderr, "t_sync failed: %s\n",
					t_errlist[t_errno]);
				exit(Et_sync);
			}
			if (t_getinfo(tli[i], &tinfo) < 0) {
				fprintf(stderr, "t_getinfo failed: %s\n",
					t_errlist[t_errno]);
				exit(Et_info);
			}

			switch (state) {
			case T_UNBND:
				fprintf(stderr, "TLI is unbound\n");
				break;
			case T_IDLE:
				fprintf(stderr, "TLI is idle\n");
				break;
			case T_INREL:
				fprintf(stderr,
					"other side wants to release\n");
				break;
			case T_INCON:
				fprintf(stderr, "T_INCON\n");
				break;
			case T_DATAXFER:
				fprintf(stderr, "T_DATAXFER\n");
				break;
			default:
				fprintf(stderr, "no state info, state = %d\n",
					state);
			}
		}
	}
#endif
	if (tli4 != -1) {
		rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
			nconf4);
		transp4 = svc_tli_create(tli4, nconf4, NULL, 0, 0);
	} else {
		transp4 = 0;
	}
	if (tli6 != -1) {
		rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
			nconf6);
		transp6 = svc_tli_create(tli6, nconf6, NULL, 0, 0);
	} else {
		transp6 = 0;
	}
	if (transp4 == 0 && transp6 == 0) {
		syslog(LOG_ERR, "yppasswdd: couldn't create an RPC server\n");
		exit(Esvc_create);
	}
	if (transp4 && !svc_reg(transp4, (ulong_t)YPPASSWDPROG,
			(ulong_t)YPPASSWDVERS, boilerplate, nconf4)) {
		syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
		exit(Esvc_reg);
	}
	if (transp6 && !svc_reg(transp6, (ulong_t)YPPASSWDPROG,
			(ulong_t)YPPASSWDVERS, boilerplate, nconf6)) {
		syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
		exit(Esvc_reg);
	}

	/*
	 * Create a loopback RPC service for secure authentication of local
	 * principals -- we need this for accepting passwd updates from
	 * root on the master server.
	 */
	if ((nconfl = getnetconfigent("ticlts")) == NULL) {
	    syslog(LOG_ERR, "transport ticlts not supported\n");
	    exit(Egetnetconfigent);
	}
	rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS, nconfl);
	transpl = svc_tli_create(RPC_ANYFD, nconfl, NULL, 0, 0);
	if (transpl == NULL) {
	    syslog(LOG_ERR,
		"yppasswdd: couldn't create an loopback RPC server\n");
	    exit(Esvc_create);
	}
	if (!svc_reg(transpl, (ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
			boilerplate, nconfl)) {
	    syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
	    exit(Esvc_reg);
	}
	__rpc_negotiate_uid(transpl->xp_fd);
	freenetconfigent(nconf4);
	freenetconfigent(nconf6);
	freenetconfigent(nconfl);
	svc_run();
	syslog(LOG_ERR, "yppasswdd: svc_run shouldn't have returned\n");

	return (Esvcrun_ret);
	/* NOTREACHED */
}

static void
boilerplate(struct svc_req *rqstp, SVCXPRT *transp)
{
	switch (rqstp->rq_proc) {
	case NULLPROC:
		if (!svc_sendreply(transp, xdr_void, (char *)0))
		    syslog(LOG_WARNING,
			"yppasswdd: couldn't reply to RPC call\n");
		break;
	case YPPASSWDPROC_UPDATE:
		if (yptol_mode)
			shim_changepasswd(transp);
		else
			changepasswd(transp);
		break;
	}
}

int
validstr(char *str, size_t size)
{
	char c;

	if (str == NULL || strlen(str) > size || strchr(str, ':'))
		return (0);
	while (c = *str++) {
		if (iscntrl(c))
		    return (0);
	}
	return (1);
}

bool_t
validloginshell(char *pw_shell, char *arg, int privileged)
{
	static char newshell[STRSIZE];
	char *cp, *valid;

	if (pw_shell == 0 || *pw_shell == '\0')
		pw_shell = defshell;

	if ((defopen(DEFAULT_YPPASSWDD)) == 0) {
		if ((defread(YPPASSWDD_STR)) != NULL) {
			cp = strrchr(pw_shell, '/');
			if (cp)
				cp++;
			else
				cp = pw_shell;

			if (*cp == 'r') {
				syslog(LOG_ERR,
					"yppasswdd: cannot change "
					"from restricted shell %s\n",
					pw_shell);
				return (0);
			}
		}
		(void) defopen((char *)NULL);
	}

	for (valid = getusershell(); valid; valid = getusershell())
		if (strcmp(pw_shell, valid) == 0)
		    break;

	if (valid == NULL && !privileged) {
		syslog(LOG_ERR, "yppasswdd: Current shell is not valid: %s\n",
			pw_shell);
		endusershell();
		return (0);
	}

	if (arg != 0) {
		strncpy(newshell, arg, sizeof (newshell) - 1);
		newshell[sizeof (newshell) - 1] = 0;
	} else {
		endusershell();
		return (0);
	}

	/*
	 * Allow user to give shell name w/o preceding pathname.
	 */
	setusershell();
	for (valid = getusershell(); valid; valid = getusershell()) {
		if (newshell[0] == '/') {
		    cp = valid;
		} else {
		    cp = strrchr(valid, '/');
		    if (cp == 0)
			cp = valid;
		    else
			cp++;
		}
		if (strcmp(newshell, cp) == 0)
		    break;
	}

	if (valid == 0) {
		if (!privileged || newshell[0] != '/') {
			syslog(LOG_WARNING,
				"%s is unacceptable as a new shell.\n",
				newshell);
			endusershell();
			return (0);
		}
		valid = newshell;
	}

	if (access(valid, X_OK) < 0) {
		syslog(LOG_WARNING, "%s is unavailable.\n", valid);
		endusershell();
		return (0);
	}

	strncpy(newshell, valid, sizeof (newshell));
	pw_shell =  newshell;
	endusershell();
	return (1);
}

static void
unlimit(int lim)
{
	struct rlimit rlim;
	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
	setrlimit(lim, &rlim);
}