/*
 * Copyright (c) 1984,1998 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)rpc.rusersd.c	1.18	99/03/15 SMI"

#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <memory.h>
#include <netconfig.h>
#include <stropts.h>
#include <syslog.h>
#include <utmpx.h>
#include <rpcsvc/rusers.h>
#include <sys/resource.h>
#include <limits.h>

#ifdef	DEBUG
#define	RPC_SVC_FG
#endif

#define	_RPCSVC_CLOSEDOWN 120

static void rusers_service();
static void closedown();
static void msgout();
static unsigned min();

static int _rpcpmstart;		/* Started by a port monitor ? */
static int _rpcfdtype;		/* Whether Stream or Datagram ? */
static int _rpcsvcdirty;	/* Still serving ? */
static int _rpcsvcrecent;	/* set when we serivce a request; tested */
				/* and cleared by closedown() routine */

#define	DIV60(t)	((t+30)/60)	/* x/60 rounded */

#define	ALL_ENTRIES	1
#define	REAL_USERS	0

utmp_array utmp_array_res;
int used_array_len = 0;
struct utmpidlearr utmpidlearr;

main()
{
	pid_t pid;
	int i;
	int connmaxrec = RPC_MAXDATASIZE;

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

	/*
	 * If stdin looks like a TLI endpoint, we assume
	 * that we were started by a port monitor. If
	 * t_getstate fails with TBADF, this is not a
	 * TLI endpoint.
	 */
	if (t_getstate(0) != -1 || t_errno != TBADF) {
		char *netid;
		struct netconfig *nconf = NULL;
		SVCXPRT *transp;
		int pmclose;
		extern char *getenv();

		_rpcpmstart = 1;
		openlog("rusers", LOG_PID, LOG_DAEMON);
		if ((netid = getenv("NLSPROVIDER")) == NULL) {
#ifdef DEBUG
			msgout("cannot get transport name");
#endif
		} else if ((nconf = getnetconfigent(netid)) == NULL) {
#ifdef DEBUG
			msgout("cannot get transport info");
#endif
		}
		if ((transp = svc_tli_create(0, nconf, NULL, 0, 0)) == NULL) {
			msgout("cannot create server handle");
			exit(1);
		}
		if (nconf)
			freenetconfigent(nconf);
		if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_3, rusers_service,
				0)) {
	msgout("unable to register (RUSERSPROG, RUSERSVERS_3).");
			exit(1);
		}
		if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_IDLE,
				rusers_service, 0)) {
	msgout("unable to register (RUSERSPROG, RUSERSVERS_IDLE).");
			exit(1);
		}
		(void) signal(SIGALRM, closedown);
		(void) alarm(_RPCSVC_CLOSEDOWN);
		svc_run();
		msgout("svc_run returned");
		exit(1);
		/* NOTREACHED */
	}
#ifndef RPC_SVC_FG
	pid = fork();
	if (pid < 0) {
		perror("rpc.rusersd: cannot fork");
		exit(1);
	}
	if (pid)
		exit(0);
	for (i = 0; i < 20; i++)
		(void) close(i);
	setsid();
	openlog("rusers", LOG_PID, LOG_DAEMON);
#endif
	if (!svc_create(rusers_service, RUSERSPROG, RUSERSVERS_3, "netpath")) {
	    msgout("unable to create (RUSERSPROG, RUSERSVERS_3) for netpath");
		exit(1);
	}
	if (!svc_create(rusers_service, RUSERSPROG, RUSERSVERS_IDLE,
			"netpath")) {
	    msgout(
		"unable to create (RUSERSPROG, RUSERSVERS_IDLE) for netpath");
		exit(1);
	}

	svc_run();
	msgout("svc_run returned");
	exit(1);
	/* NOTREACHED */
}


/*
 * This routine gets the user information.
 * "all" specifies whether all listings should be counted, or only those of
 *	type "USER_PROCESS".
 * "version" is either RUSERSVERS_IDLE or RUSERSVERS_3.  If anything else,
 *	just a count is returned.
 * "limit" specifies the maximum number of entries to be processed.
 *
 * For both versions, the results are placed into an external variable.
 * For RUSERSVERS_IDLE, this routine mallocs entries in a vector as it
 * processed each utmpx entry.  These malloc'd entries must be freed after the
 * results are returned.
 * For RUSERSVERS_3, this routine uses array entries that are malloc'd prior
 * to this routine being called. "limit" is the number of elements available.
 */
int
getutmpx_3(all, version, limit)
	int all;		/* give all listings? */
	int version;		/* version 2 or 3 */
	int limit;		/* limits users returned, 0 means no limit */
{
	struct utmpx *utent;
	struct utmpidle **q = utmpidlearr.uia_arr;
	int minidle;
	int cnt = 0;
	time_t now;
	extern char *s_malodup();

	time(&now);		/* only one call to time() for this rpc call */
	setutxent();		/* reset the utmpx file */
	while ((utent = getutxent()) != NULL && (limit == 0 || cnt < limit)) {
		if (utent->ut_line[0] == '\0' || utent->ut_user[0] == '\0')
			continue;
		/*
		 * List only user processes.
		 * XXX modified to exclude cmdtool style window entries.
		 */
		if ((all == REAL_USERS) && ((utent->ut_type != USER_PROCESS) ||
		    nonuserx(*utent)))
			continue;

		if (version == RUSERSVERS_IDLE) {
			/*
			 * need to free this; done after svc_sendreply.
			 */
			*q = (struct utmpidle *)
				malloc(sizeof (struct utmpidle));
			(*q)->ui_idle = findidle(utent->ut_line,
						sizeof (utent->ut_line), now);
			if (strncmp(utent->ut_line, "console",
				strlen("console")) == 0) {
				(*q)->ui_idle = min((*q)->ui_idle,
					console_idle(now));
			}
			usys5to_ru(utent, &((*q)->ui_utmp));
#ifdef DEBUG
			printf("%-*s %-*s  %s; idle %d",
			    sizeof (utent->ut_line),
			    utent->ut_line,
			    sizeof (utent->ut_name),
			    utent->ut_name,
			    ctime(&utent->ut_xtime),
			    (*q)->ui_idle);
#endif
			q++;
		} else if (version == RUSERSVERS_3) {
#define	uav	utmp_array_res.utmp_array_val

			uav[cnt].ut_host =
				s_malodup(utent->ut_host, utent->ut_syslen);
			uav[cnt].ut_user = s_malodup(utent->ut_user,
				sizeof (utent->ut_user));
			uav[cnt].ut_line = s_malodup(utent->ut_line,
				sizeof (utent->ut_line));
			uav[cnt].ut_type = utent->ut_type;
			uav[cnt].ut_time = utent->ut_xtime;
			uav[cnt].ut_idle = findidle(utent->ut_line,
						sizeof (utent->ut_line), now);
			if (strncmp(utent->ut_line, "console",
				strlen("console")) == 0) {
				uav[cnt].ut_idle =
					min(uav[cnt].ut_idle,
							console_idle(now));
			}
#ifdef DEBUG
			printf("user: %-10s line: %-10s  %s; idle %d (%s)\n",
					uav[cnt].ut_line, uav[cnt].ut_user,
					ctime((time_t *)&uav[cnt].ut_time),
					uav[cnt].ut_idle, uav[cnt].ut_host);
#endif
#undef	uav
		}
		cnt++;
	}
	return (cnt);
}

/*
 * "string" is a character array with maximum size "size".  Return a
 * malloc'd string that's a duplicate of the string.
 */
char *
s_malodup(string, size)
char *string;
int size;
{
	char *tmp;

	tmp = (char *)malloc(size+1);
	if (tmp == NULL) {
		msgout("rpc.rusersd: malloc failed (2)");
		return (NULL);
	}
	strncpy(tmp, string, size);
	tmp[size] = '\0';
	return (tmp);
}


int
console_idle(now)
	time_t now;
{
	/*
	 * On the console, the user may be running a window system; if so,
	 * their activity will show up in the last-access times of
	 * "/dev/kbd" and "/dev/mouse", so take the minimum of the idle
	 * times on those two devices and "/dev/console" and treat that as
	 * the idle time.
	 */
	return (min((unsigned)findidle("kbd", strlen("kbd"), now),
		(unsigned)findidle("mouse", strlen("mouse"), now)));
}

static void
rusers_service(rqstp, transp)
	register struct svc_req *rqstp;
	register SVCXPRT *transp;
{
	int i;
	int cnt;
	char *replyerr = "rpc.rusersd: error replying to request";

	_rpcsvcrecent = _rpcsvcdirty = 1;
	switch (rqstp->rq_proc) {
	case 0:
		if (svc_sendreply(transp, xdr_void, 0) == FALSE) {
			msgout(replyerr);
		}
		break;
	case RUSERSPROC_NUM:
		cnt = getutmpx_3(REAL_USERS, 0, 0);
		if (!svc_sendreply(transp, xdr_u_long, (caddr_t)&cnt))
			msgout(replyerr);
		break;
	case RUSERSPROC_NAMES:
	case RUSERSPROC_ALLNAMES:
		if (rqstp->rq_vers == RUSERSVERS_IDLE) {
			utmpidlearr.uia_arr = (struct utmpidle **)
				malloc(MAXUSERS*sizeof (struct utmpidle *));
			utmpidlearr.uia_cnt = getutmpx_3(rqstp->rq_proc ==
				RUSERSPROC_ALLNAMES,
				RUSERSVERS_IDLE, MAXUSERS);
			if (!svc_sendreply(transp, xdr_utmpidlearr,
					(caddr_t)&utmpidlearr))
				msgout(replyerr);
			for (i = 0; i < utmpidlearr.uia_cnt; i++) {
				free(utmpidlearr.uia_arr[i]);
			}
			free(utmpidlearr.uia_arr);
		} else if (rqstp->rq_vers == RUSERSVERS_3) {
			int entries, alloc_array_len;

			/*
			 * Always free strings from previous results array
			 */
			for (i = 0; i < used_array_len; i++) {
			free_ua_entry(&utmp_array_res.utmp_array_val[i]);
			}
			entries = (rqstp->rq_proc == RUSERSPROC_ALLNAMES);
			cnt = getutmpx_3(entries, 0, 0);	/* get cnt */
			if (cnt > utmp_array_res.utmp_array_len) {
				free(utmp_array_res.utmp_array_val);
				utmp_array_res.utmp_array_len = 0;
				utmp_array_res.utmp_array_val = (rusers_utmp *)
					malloc(cnt * sizeof (rusers_utmp));
				if (utmp_array_res.utmp_array_val == NULL) {
				    msgout("rpc.rusersd: malloc failed (1)");
				    break;
				}
				alloc_array_len = cnt;
			} else {
				alloc_array_len = utmp_array_res.utmp_array_len;
			}
			cnt = getutmpx_3(entries, RUSERSVERS_3, cnt);
			utmp_array_res.utmp_array_len = used_array_len = cnt;
			if (!svc_sendreply(transp, xdr_utmp_array,
					(caddr_t)&utmp_array_res))
				msgout(replyerr);
			utmp_array_res.utmp_array_len = alloc_array_len;
		}
		break;
	default:
		svcerr_noproc(transp);
		break;
	}
	_rpcsvcdirty = 0;

}

free_ua_entry(uap)
rusers_utmp *uap;
{
	if (uap == NULL)
		return;
	if (uap->ut_user)
		free(uap->ut_user);
	if (uap->ut_line)
		free(uap->ut_line);
	if (uap->ut_host)
		free(uap->ut_host);
}



/* find & return number of minutes current tty has been idle */
findidle(name, ln, now)
	char *name;
	int ln;
	time_t	now;
{
	struct stat stbuf;
	long lastaction, diff;
	char ttyname[32];

	strcpy(ttyname, "/dev/");
	strncat(ttyname, name, ln);
	if (stat(ttyname, &stbuf) < 0)
		return (INT_MAX);
	lastaction = stbuf.st_atime;
	diff = now - lastaction;
	diff = DIV60(diff);
	if (diff < 0) diff = 0;
	return (diff);
}

static
usys5to_ru(s5, bss)
	struct utmpx *s5;
	struct ru_utmp *bss;
{
	int i;

#ifdef DEBUG
	printf("sizeof (bss->ut_host) == %d\n", sizeof (bss->ut_host));
#endif
	strncpy(bss->ut_name, s5->ut_name, sizeof (bss->ut_name));
	strncpy(bss->ut_line, s5->ut_line, sizeof (bss->ut_line));
	strncpy(bss->ut_host, s5->ut_host, sizeof (bss->ut_host));
	bss->ut_time = s5->ut_xtime;
}

static void
msgout(msg)
	char *msg;
{
#ifdef RPC_SVC_FG
	if (_rpcpmstart)
		syslog(LOG_ERR, msg);
	else
		(void) fprintf(stderr, "%s\n", msg);
#else
	syslog(LOG_ERR, msg);
#endif
}

static void
closedown(sig)
int sig;
{
	if (_rpcsvcrecent) {
		_rpcsvcrecent = 0;
	} else {
		if (_rpcsvcdirty == 0) {
			int i, openfd;
			struct t_info tinfo;

			if (t_getinfo(0, &tinfo) || (tinfo.servtype == T_CLTS))
				exit(0);

			for (i = 0, openfd = 0;
					i < svc_max_pollfd && openfd < 2;
					i++) {
				if (svc_pollfd[i].fd >= 0)
					openfd++;
			}

			if (openfd <= 1)
				exit(0);
		}
	}
	(void) signal(SIGALRM, closedown);
	(void) alarm(_RPCSVC_CLOSEDOWN);
}

unsigned
min(a, b)
unsigned a;
unsigned b;
{
	if (a < b)
		return (a);
	else
		return (b);
}
