/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

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

#ident	"@(#)write.c	1.24	99/07/27 SMI"	/* SVr4.0 1.19	*/

/*	Program to communicate with other users of the system.		*/
/*	Usage:	write user_name [terminal]				*/

#include	<ctype.h>
#include	<string.h>
#include	<stdio.h>
#include	<signal.h>
#include	<sys/wait.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/utsname.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<time.h>
#include	<utmpx.h>
#include	<pwd.h>
#include	<fcntl.h>
#include	<stdarg.h>
#include	<locale.h>
#include	<stdlib.h>
#include	<limits.h>
#include	<wctype.h>
#include	<errno.h>
#include	<syslog.h>

#define		TRUE	1
#define		FALSE	0
#define		FAILURE	-1
#define		DATE_FMT	"%a %b %e %H:%M:%S"
#define		UTMP_HACK  /* work around until utmpx is world writable */
/*
 *	DATE-TIME format
 *  %a	abbreviated weekday name
 *  %b  abbreviated month name
 *  %e  day of month
 *  %H  hour - 24 hour clock
 *  %M  minute
 *  %S  second
 *
 */

static int permit1(int);
static int permit(char *);
static int readcsi(int, char *, int);
static void setsignals();
static void shellcmd(char *);
static void openfail();
static void eof();

static struct	utsname utsn;

static FILE	*fp;	/* File pointer for receipient's terminal */
static char *rterm, *receipient; /* Pointer to receipient's terminal & name */
static char *thissys;

void
main(argc, argv)
int argc;
char **argv;
{
	int i;
	struct utmpx *ubuf;
	static struct utmpx self;
	char ownname[sizeof (self.ut_user) + 1];
	static char rterminal[sizeof ("/dev/") + sizeof (self.ut_line)] =
	    "/dev/";
	extern char *rterm, *receipient;
	char *terminal, *ownterminal, *oterminal;
	short count;
	extern FILE *fp;
	char input[134+MB_LEN_MAX];
	char *ptr;
	time_t tod;
	char time_buf[40];
	struct passwd *passptr;
	char badterm[20][20];
	int bad = 0;
	uid_t	myuid;
	char *bp;
	int n;
	wchar_t wc;
	register c;
	int	newline;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	while ((c = getopt(argc, argv, "")) != EOF)
		switch (c) {
			case '?':
				(void) fprintf(stderr, "Usage: write %s\n",
				gettext("user_name [terminal]"));
				exit(2);
		}
	myuid = geteuid();
	uname(&utsn);
	thissys = utsn.nodename;

/*	Set "rterm" to location where receipient's terminal will go.	*/

	rterm = &rterminal[sizeof ("/dev/") - 1];
	terminal = NULL;

	if (--argc <= 0)
	    {
	    (void) fprintf(stderr, "Usage: write %s\n",
		gettext("user_name [terminal]"));
	    exit(1);
	    }
	else
	    {
	    receipient = *++argv;
	    }

/*	Was a terminal name supplied?  If so, save it.			*/

	if (--argc > 1)
	    {
	    (void) fprintf(stderr, "Usage: write %s\n",
		gettext("user_name [terminal]"));
	    exit(1);
	    }
	else
	    terminal = *++argv;

/*	One of the standard file descriptors must be attached to a	*/
/*	terminal in "/dev".						*/

	if ((ownterminal = ttyname(fileno(stdin))) == NULL &&
	    (ownterminal = ttyname(fileno(stdout))) == NULL &&
	    (ownterminal = ttyname(fileno(stderr))) == NULL)
	    {
	    (void) fprintf(stderr,
			gettext("I cannot determine your terminal name."
					" No reply possible.\n"));
	    ownterminal = "/dev/???";
	    }

	/*
	 * Set "ownterminal" past the "/dev/" at the beginning of
	 * the device name.
	 */
	oterminal = ownterminal + sizeof ("/dev/")-1;

	/*
	 * Scan through the "utmpx" file for your own entry and the
	 * entry for the person we want to send to.
	 */
	for (self.ut_pid = 0, count = 0; (ubuf = getutxent()) != NULL; )
	    {
	/* Is this a USER_PROCESS entry? */

	    if (ubuf->ut_type == USER_PROCESS)
	    {
/*	Is it our entry?  (ie.  The line matches ours?)			*/

		if (strncmp(&ubuf->ut_line[0], oterminal,
		    sizeof (ubuf->ut_line)) == 0) self = *ubuf;

/*	Is this the person we want to send to?				*/

		if (strncmp(receipient, &ubuf->ut_user[0],
		    sizeof (ubuf->ut_user)) == 0)
		    {
/*	If a terminal name was supplied, is this login at the correct	*/
/*	terminal?  If not, ignore.  If it is right place, copy over the	*/
/*	name.								*/

		    if (terminal != NULL)
		    {
			if (strncmp(terminal, &ubuf->ut_line[0],
			    sizeof (ubuf->ut_line)) == 0)
			    {
			    strlcpy(rterm, &ubuf->ut_line[0],
				sizeof (rterminal) - (rterm - rterminal));
			    if (myuid && !permit(rterminal)) {
				bad++;
				rterm[0] = '\0';
			    }
			    }
		    }

/*	If no terminal was supplied, then take this terminal if no	*/
/*	other terminal has been encountered already.			*/

		    else
		    {
/*	If this is the first encounter, copy the string into		*/
/*	"rterminal".							*/

			if (*rterm == '\0')
			    {
			    strlcpy(rterm, &ubuf->ut_line[0],
				sizeof (rterminal) - (rterm - rterminal));
			    if (myuid && !permit(rterminal)) {
				if (bad < 20) {
					strlcpy(badterm[bad++], rterm,
					    sizeof (badterm[bad++]));
				}
				rterm[0] = '\0';
			    } else if (bad > 0) {
				(void) fprintf(stderr,
				gettext(
				"%s is logged on more than one place.\n"
	"You are connected to \"%s\".\nOther locations are:\n"),
				    receipient, rterm);
				for (i = 0; i < bad; i++)
				    (void) fprintf(stderr, "%s\n", badterm[i]);
			    }
			}

/*	If this is the second terminal, print out the first.  In all	*/
/*	cases of multiple terminals, list out all the other terminals	*/
/*	so the user can restart knowing what her/his choices are.	*/

			else if (terminal == NULL)
			    {
			    if (count == 1 && bad == 0)
			    {
				(void) fprintf(stderr,
				gettext(
				"%s is logged on more than one place.\n"
	"You are connected to \"%s\".\nOther locations are:\n"),
				    receipient, rterm);
			    }
			    fwrite(&ubuf->ut_line[0], sizeof (ubuf->ut_line),
				1, stderr);
			    (void) fprintf(stderr, "\n");
			    }

			count++;
		    }			/* End of "else" */
		    }			/* End of "else if (strncmp" */
	    }			/* End of "if (USER_PROCESS" */
	    }		/* End of "for(count=0" */

/*	Did we find a place to talk to?  If we were looking for a	*/
/*	specific spot and didn't find it, complain and quit.		*/

	if (terminal != NULL && *rterm == '\0')
	    {
	    if (bad > 0) {
		(void) fprintf(stderr, gettext("Permission denied.\n"));
		exit(1);
		} else {
#ifdef UTMP_HACK
		if (strlcat(rterminal, terminal, sizeof (rterminal)) >=
		    sizeof (rterminal)) {
			(void) fprintf(stderr,
			    gettext("Terminal name too long.\n"));
			exit(1);
		}
		if (self.ut_pid == 0) {
	            if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL) {
			(void) fprintf(stderr,
			gettext("Cannot determine who you are.\n"));
			exit(1);
		    }
		    strncpy(&ownname[0], &passptr->pw_name[0], sizeof (ownname));
		} else {
            	    strncpy(&ownname[0], self.ut_user, sizeof (self.ut_user));
	        }
		if (!permit(rterminal)) {
			(void) fprintf(stderr,
				gettext("%s permission denied\n"), terminal);
			exit(1);
		}
#else
		(void) fprintf(stderr, gettext("%s is not at \"%s\".\n"),
			receipient, terminal);
		exit(1);
#endif	/* UTMP_HACK */
	    }
	    }

/*	If we were just looking for anyplace to talk and didn't find	*/
/*	one, complain and quit.						*/
/*	If permissions prevent us from sending to this person - exit	*/

	else if (*rterm == '\0')
	    {
	    if (bad > 0)
		(void) fprintf(stderr, gettext("Permission denied.\n"));
	    else
		(void) fprintf(stderr,
			gettext("%s is not logged on.\n"), receipient);
	    exit(1);
	    }

/*	Did we find our own entry?					*/

	else if (self.ut_pid == 0)
	    {
/*	Use the user id instead of utmp name if the entry in the	*/
/*	utmp file couldn't be found.					*/

	    if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL)
	    {
		(void) fprintf(stderr,
			gettext("Cannot determine who you are.\n"));
		exit(1);
	    }
	    strncpy(&ownname[0], &passptr->pw_name[0], sizeof (ownname));
	    }
	else
	    {
	    strncpy(&ownname[0], self.ut_user, sizeof (self.ut_user));
	    }
	ownname[sizeof (ownname)-1] = '\0';

	if (!permit1(1))
		(void) fprintf(stderr,
		gettext("Warning: You have your terminal set to \"mesg -n\"."
		    " No reply possible.\n"));
/*	Close the utmpx files.						*/

	endutxent();

/*	Try to open up the line to the receipient's terminal.		*/

	signal(SIGALRM, openfail);
	alarm(5);
	fp = fopen(&rterminal[0], "w");
	alarm(0);

/*	Make sure executed subshell doesn't inherit this fd - close-on-exec */

	if (fcntl(fileno(fp), F_SETFD, FD_CLOEXEC) < 0)  {
		perror("fcntl(F_SETFD)");
		exit(1);
	}

/*	Catch signals SIGHUP, SIGINT, SIGQUIT, and SIGTERM, and send	*/
/*	<EOT> message to receipient before dying away.			*/

	setsignals(eof);

/*	Get the time of day, convert it to a string and throw away the	*/
/*	year information at the end of the string.			*/

	time(&tod);
	cftime(time_buf, dcgettext(NULL, DATE_FMT, LC_TIME), &tod);
	(void) fprintf(fp,
	gettext("\n\007\007\007\tMessage from %s on %s (%s) [ %s ] ...\n"),
	    &ownname[0], thissys, oterminal, time_buf);
	fflush(fp);
	(void) fprintf(stderr, "\007\007");

/*	Get input from user and send to receipient unless it begins	*/
/*	with a !, when it is to be a shell command.			*/
	newline = 1;
	while ((i = readcsi(0, &input[0], sizeof (input))) > 0) {
		ptr = &input[0];
/*	Is this a shell command?					*/

		if ((newline) && (*ptr == '!'))
			shellcmd(++ptr);

/*	Send line to the receipient.					*/

		else {
			if (myuid && !permit1(fileno(fp))) {
				(void) fprintf(stderr,
			gettext("Can no longer write to %s\n"), rterminal);
				break;
			}

/*
 * All non-printable characters are displayed using a special notation:
 * Control characters  shall be displayed using the two character
 * sequence of ^ (carat) and the ASCII character - decimal 64 greater
 * that the character being encoded - eg., a \003 is displayed ^C.
 * Characters with the eighth bit set shall be displayed using
 * the three or four character meta notation - e.g., \372 is
 * displayed M-z and \203 is displayed M-^C.
 */

			newline = 0;
			for (bp = &input[0]; --i >= 0; bp++) {
			if (*bp == '\n') {
				newline = 1;
				putc('\r', fp);
			}
			if (*bp == ' ' ||
				 *bp == '\t' || *bp == '\n' ||
				 *bp == '\r' || *bp == '\013' ||
				 *bp == '\007') {
					putc(*bp, fp);
			} else if (((n = mbtowc(&wc, bp, MB_CUR_MAX)) > 0) &&
				iswprint(wc)) {
				for (; n > 0; --n, --i, ++bp)
					putc(*bp, fp);
				bp--, ++i;
			} else {
				if (!isascii(*bp)) {
					fputs("M-", fp);
					*bp = toascii(*bp);
				}
				if (iscntrl(*bp)) {
					putc('^', fp);
/*	add decimal 64 to the control character			*/
					putc(*bp + 0100, fp);
				}
				else
					putc(*bp, fp);
			}
			if (*bp == '\n')
				fflush(fp);
			if (ferror(fp) || feof(fp)) {
				printf(gettext(
				"\n\007Write failed (%s logged out?)\n"),
				receipient);
				exit(1);
			}
			} /* for */
			fflush(fp);
	} /* else */
	} /* while */

/*	Since "end of file" received, send <EOT> message to receipient.	*/

	eof();
}


static void
setsignals(catch)
void (*catch)();
{
	signal(SIGHUP, catch);
	signal(SIGINT, catch);
	signal(SIGQUIT, catch);
	signal(SIGTERM, catch);
}


static void
shellcmd(command)
char *command;
{
	register pid_t child;
	extern void eof();

	if ((child = fork()) == (pid_t)FAILURE)
	    {
	    (void) fprintf(stderr,
	    gettext("Unable to fork.  Try again later.\n"));
	    return;
	    } else if (child == (pid_t)0) {
/*	Reset the signals to the default actions and exec a shell.	*/

	    if (setgid(getgid()) < 0)
		exit(1);
	    execl("/usr/bin/sh", "sh", "-c", command, 0);
	    exit(0);
	    }
	else
	    {
/*	Allow user to type <del> and <quit> without dying during	*/
/*	commands.							*/

	    signal(SIGINT, SIG_IGN);
	    signal(SIGQUIT, SIG_IGN);

/*	As parent wait around for user to finish spunoff command.	*/

	    while (wait(NULL) != child);

/*	Reset the signals to their normal state.			*/

	    setsignals(eof);
	    }
	(void) fprintf(stdout, "!\n");
}

static void
openfail()
{
	extern char *rterm, *receipient;

	(void) fprintf(stderr,
		gettext("Timeout trying to open %s's line(%s).\n"),
	    receipient, rterm);
	exit(1);
}

static void
eof()
{
	extern FILE *fp;

	(void) fprintf(fp, "%s\n", gettext("<EOT>"));
	exit(0);
}

/*
 * permit: check mode of terminal - if not writable by all disallow writing to
 * (even the user him/herself cannot therefore write to their own tty)
 */

static int
permit(term)
char *term;
{
	struct stat buf;
	int fildes;

	if ((fildes = open(term, O_WRONLY|O_NOCTTY)) < 0)
		return (0);
	/* check if the device really is a tty */
	if (!isatty(fildes)) {
		(void) fprintf(stderr,
		    gettext("%s in utmpx is not a tty\n"), term);
		openlog("write", 0, LOG_AUTH);
		syslog(LOG_CRIT, "%s in utmpx is not a tty\n", term);
		closelog();
		close(fildes);
		return (0);
	}
	fstat(fildes, &buf);
	close(fildes);
	return (buf.st_mode & (S_IWGRP|S_IWOTH));
}



/*
 * permit1: check mode of terminal - if not writable by all disallow writing
 * to (even the user him/herself cannot therefore write to their own tty)
 */

/* this is used with fstat (which is faster than stat) where possible */

static int
permit1(fildes)
int fildes;
{
	struct stat buf;

	fstat(fildes, &buf);
	return (buf.st_mode & (S_IWGRP|S_IWOTH));
}


/*
    Read a string of multi-byte characters from specified file.
 * The requested # of bytes are attempted to read.
 * readcsi() tries to complete the last multibyte character
 * by calling mbtowc(), if the leftovers form mbtowc(),
 * left the last char imcomplete, moves into delta_spool to use later,
 * next called. The caller must reserve
 * nbytereq+MB_LEN_MAX bytes for the buffer.  When the attempt
 * is failed, it truncate the last char.
 * Returns the number of bytes that constitutes the valid multi-byte characters.
 */


static int readcsi(d, buf, nbytereq)
int	d;
char	*buf;
int	nbytereq;
{
	static char	delta_pool[MB_LEN_MAX * 2];
	static char	delta_size;
	char	*cp, *nextp, *lastp;
	int	n;
	int	r_size;

	if (delta_size) {
		memcpy(buf, delta_pool, delta_size);
		cp = buf + delta_size;
		r_size = nbytereq - delta_size;
	} else {
		cp = buf;
		r_size = nbytereq;
	}

	if ((r_size = read(d, cp, r_size)) < 0)
		r_size = 0;
	if ((n = delta_size + r_size) <= 0)
		return (n);

	/* Scan the result to test the completeness of each EUC characters. */
	nextp = buf;
	lastp = buf + n; /* Lastp points to the first junk byte. */
	while (nextp < lastp) {
		if ((n = (lastp - nextp)) > (unsigned int)MB_CUR_MAX)
			n = (unsigned int)MB_CUR_MAX;
		if ((n = mbtowc((wchar_t *)0, nextp, n)) <= 0) {
			if ((lastp - nextp) < (unsigned int)MB_CUR_MAX)
				break;
			n = 1;
		}
		nextp += n;
	}
	/* How many bytes needed to complete the last char? */
	delta_size = lastp - nextp;
	if (delta_size > 0) {
		if (nextp[delta_size - 1] != '\n') {
			/* the remnants store into delta_pool */
			memcpy(delta_pool, nextp, delta_size);
		} else
			nextp = lastp;
	}
	*nextp = '\0';
	return (nextp-buf); /* Return # of bytes. */
}
