/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2006 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 <stropts.h>
#include <signal.h>
#include <fcntl.h>
#include <door.h>
#include <thread.h>
#include <priv_utils.h>
#include <locale.h>
#include <strings.h>
#include <syslog.h>
#include <unistd.h>
#include <nfs/nfs4.h>
#include <nfs/nfsid_map.h>
#include <rpcsvc/daemon_utils.h>
#include <arpa/nameser.h>
#include <nfs/nfssys.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>

extern struct group *_uncached_getgrgid_r(gid_t, struct group *, char *, int);
extern struct group *_uncached_getgrnam_r(const char *, struct group *,
    char *, int);
extern struct passwd *_uncached_getpwuid_r(uid_t, struct passwd *, char *, int);
extern struct passwd *_uncached_getpwnam_r(const char *, struct passwd *,
    char *, int);

/*
 * seconds to cache nfsmapid domain info
 */
#define	NFSCFG_DEFAULT_DOMAIN_TMOUT	(5 * 60)
#define	NFSMAPID_DOOR   "/var/run/nfsmapid_door"

extern void	nfsmapid_func(void *, char *, size_t, door_desc_t *, uint_t);

extern void	check_domain(int);
extern void	idmap_kcall(int);
extern void	open_diag_file(void);

size_t		pwd_buflen = 0;
size_t		grp_buflen = 0;
thread_t	sig_thread;
static char	*MyName;

/*
 * nfscfg_domain_tmout is used by nfsv4-test scripts to query
 * the nfsmapid daemon for the proper timeout. Don't delete !
 */
time_t		 nfscfg_domain_tmout = NFSCFG_DEFAULT_DOMAIN_TMOUT;

/*
 * Processing for daemonization
 */
static void
daemonize(void)
{
	switch (fork()) {
		case -1:
			perror("nfsmapid: can't fork");
			exit(2);
			/* NOTREACHED */
		case 0:		/* child */
			break;

		default:	/* parent */
			_exit(0);
	}

	if (chdir("/") < 0)
		syslog(LOG_ERR, gettext("chdir /: %m"));

	/*
	 * Close stdin, stdout, and stderr.
	 * Open again to redirect input+output
	 */
	(void) close(0);
	(void) close(1);
	(void) close(2);
	(void) open("/dev/null", O_RDONLY);
	(void) open("/dev/null", O_WRONLY);
	(void) dup(1);
	(void) setsid();
}

/* ARGSUSED */
static void *
sig_handler(void *arg)
{
	siginfo_t	si;
	sigset_t	sigset;
	struct timespec	tmout;
	int		ret;

	tmout.tv_nsec = 0;
	(void) sigemptyset(&sigset);
	(void) sigaddset(&sigset, SIGHUP);
	(void) sigaddset(&sigset, SIGTERM);
#ifdef	DEBUG
	(void) sigaddset(&sigset, SIGINT);
#endif

	/*CONSTCOND*/
	while (1) {
		tmout.tv_sec = nfscfg_domain_tmout;
		if ((ret = sigtimedwait(&sigset, &si, &tmout)) != 0) {
			/*
			 * EAGAIN: no signals arrived during timeout.
			 * check/update config files and continue.
			 */
			if (ret == -1 && errno == EAGAIN) {
				check_domain(0);
				continue;
			}

			switch (si.si_signo) {
				case SIGHUP:
					check_domain(1);
					break;
#ifdef DEBUG
				case SIGINT:
					exit(0);
#endif
				case SIGTERM:
				default:
					exit(si.si_signo);
			}
		}
	}
	/*NOTREACHED*/
	return (NULL);
}

/*
 * Thread initialization. Mask out all signals we want our
 * signal handler to handle for us from any other threads.
 */
static void
thr_init(void)
{
	sigset_t sigset;
	long	 thr_flags = (THR_NEW_LWP|THR_DAEMON|THR_SUSPENDED);

	/*
	 * Before we kick off any other threads, mask out desired
	 * signals from main thread so that any subsequent threads
	 * don't receive said signals.
	 */
	(void) thr_sigsetmask(NULL, NULL, &sigset);
	(void) sigaddset(&sigset, SIGHUP);
	(void) sigaddset(&sigset, SIGTERM);
#ifdef	DEBUG
	(void) sigaddset(&sigset, SIGINT);
#endif
	(void) thr_sigsetmask(SIG_SETMASK, &sigset, NULL);

	/*
	 * Create the signal handler thread suspended ! We do things
	 * this way at setup time to minimize the probability of
	 * introducing any race conditions _if_ the process were to
	 * get a SIGHUP signal while creating a new DNS query thread
	 * in get_dns_txt_domain().
	 */
	if (thr_create(NULL, 0, sig_handler, 0, thr_flags, &sig_thread)) {
		syslog(LOG_ERR,
			gettext("Failed to create signal handling thread"));
		exit(4);
	}
}

static void
daemon_init(void)
{
	struct passwd pwd;
	struct group grp;
	char *pwd_buf;
	char *grp_buf;

	/*
	 * passwd/group reentrant interfaces limits
	 */
	pwd_buflen = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX);
	grp_buflen = (size_t)sysconf(_SC_GETGR_R_SIZE_MAX);

	/*
	 * MT initialization is done first so that if there is the
	 * need to fire an additional thread to continue to query
	 * DNS, that thread is started off with the main thread's
	 * sigmask.
	 */
	thr_init();

	/*
	 * Determine nfsmapid domain.
	 */
	check_domain(0);

	/*
	 * In the case of nfsmapid running diskless, it is important
	 * to get the initial connections to the nameservices
	 * established to prevent problems like opening a devfs
	 * node to contact a nameservice being blocked by the
	 * resolution of an active devfs lookup.
	 * First issue a set*ent to "open" the databases and then
	 * get an entry and finally lookup a bogus entry to trigger
	 * any lazy opens.
	 */
	setpwent();
	setgrent();
	(void) getpwent();
	(void) getgrent();
	if ((pwd_buf = malloc(pwd_buflen)) == NULL)
		return;

	(void) _uncached_getpwnam_r("NF21dmvP", &pwd, pwd_buf, pwd_buflen);
	(void) _uncached_getpwuid_r(1181794, &pwd, pwd_buf, pwd_buflen);

	if ((grp_buf = realloc(pwd_buf, grp_buflen)) == NULL) {
		free(pwd_buf);
		return;
	}

	(void) _uncached_getgrnam_r("NF21dmvP", &grp, grp_buf, grp_buflen);
	(void) _uncached_getgrgid_r(1181794, &grp, grp_buf, grp_buflen);
	free(grp_buf);
}

static int
start_svcs(void)
{
	int doorfd = -1;
#ifdef DEBUG
	int dfd;
#endif

	if ((doorfd = door_create(nfsmapid_func, NULL,
	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
		syslog(LOG_ERR, "Unable to create door: %m\n");
		return (1);
	}

#ifdef DEBUG
	/*
	 * Create a file system path for the door
	 */
	if ((dfd = open(NFSMAPID_DOOR, O_RDWR|O_CREAT|O_TRUNC,
				S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) {
		syslog(LOG_ERR, "Unable to open %s: %m\n", NFSMAPID_DOOR);
		(void) close(doorfd);
		return (1);
	}

	/*
	 * Clean up any stale associations
	 */
	(void) fdetach(NFSMAPID_DOOR);

	/*
	 * Register in namespace to pass to the kernel to door_ki_open
	 */
	if (fattach(doorfd, NFSMAPID_DOOR) == -1) {
		syslog(LOG_ERR, "Unable to fattach door: %m\n");
		(void) close(dfd);
		(void) close(doorfd);
		return (1);
	}
	(void) close(dfd);
#endif

	/*
	 * Now that we're actually running, go
	 * ahead and flush the kernel flushes
	 * Pass door name to kernel for door_ki_open
	 */
	idmap_kcall(doorfd);

	/*
	 * Wait for incoming calls
	 */
	/*CONSTCOND*/
	while (1)
		(void) pause();

	syslog(LOG_ERR, gettext("Door server exited"));
	return (10);
}

/* ARGSUSED */
int
main(int argc, char **argv)
{
	MyName = argv[0];

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	/* _check_services() framework setup */
	(void) _create_daemon_lock(NFSMAPID, DAEMON_UID, DAEMON_GID);

	/*
	 * Open diag file in /var/run while we've got the perms
	 */
	open_diag_file();

	/*
	 * Initialize the daemon to basic + sys_nfs
	 */
#ifndef	DEBUG
	if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET,
	    DAEMON_UID, DAEMON_GID, PRIV_SYS_NFS, (char *)NULL) == -1) {
		(void) fprintf(stderr, gettext("%s PRIV_SYS_NFS privilege "
			"missing\n"), MyName);
		exit(1);
	}
#endif

	/*
	 * Take away a subset of basic, while this is not the absolute
	 * minimum, it is important that it is unique among other
	 * daemons to insure that we get a unique cred that will
	 * result in a unique open_owner.  If not, we run the risk
	 * of a diskless client deadlocking with a thread holding
	 * the open_owner seqid lock while upcalling the daemon.
	 * XXX This restriction will go away once we stop holding
	 * XXX open_owner lock across rfscalls!
	 */
	(void) priv_set(PRIV_OFF, PRIV_PERMITTED,
		PRIV_FILE_LINK_ANY, PRIV_PROC_SESSION,
		(char *)NULL);

#ifndef DEBUG
	daemonize();
	switch (_enter_daemon_lock(NFSMAPID)) {
		case 0:
			break;

		case -1:
			syslog(LOG_ERR, "error locking for %s: %s", NFSMAPID,
			    strerror(errno));
			exit(3);

		default:
			/* daemon was already running */
			exit(0);
	}
#endif
	openlog(MyName, LOG_PID | LOG_NDELAY, LOG_DAEMON);

	/* Initialize daemon subsystems */
	daemon_init();

	/* start services */
	return (start_svcs());
}