/* write - write to a logged in user	Authors: N. Andrew and F. van Kempen */

/*
 * Usage:	write [-c] [-v] user [tty]
 *  			-c Read & write one character at a time (cbreak mode)
 *			-v Verbose
 *
 * Version:	1.6	10/24/92
 *
 * NOTES:	Write requires 1.4a (or higher) libraries,
 *		for getopt(), strchr().
 *
 * Authors:	Nick Andrew  (nick@nswitgould.oz)  - Public Domain
 *		Fred van Kempen (minixug!waltje@kyber.uucp)
 */

#include <sys/types.h>
#include <fcntl.h>
#include <pwd.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmp.h>
#include <time.h>
#include <stdio.h>

static char *Version = "@(#) WRITE 1.6 (10/24/92)";

int otty;			/* file desc of callee's terminal */
short int cbreak = 0;		/* are we in CBREAK (-c) mode? */
short int verbose = 0;		/* are we in VERBOSE (-v) mode? */
short int writing = 0;		/* is there a connection? */
char *user = NULL;	/* callee's user name */
char *tty = NULL;	/* callee's terminal if given */
char *ourtty = NULL;	/* our terminal name */
struct termios ttyold, ttynew;	/* our tty controlling structs */

extern int optind;

_PROTOTYPE(int main, (int argc, char **argv));
_PROTOTYPE(char *finduser, (void));
_PROTOTYPE(void settty, (char *utty));
_PROTOTYPE(void sayhello, (void));
_PROTOTYPE(void escape, (char *cmd));
_PROTOTYPE(void writetty, (void));
_PROTOTYPE(void usage, (void));
_PROTOTYPE(void intr, (int dummy));

char *finduser()
{
/* Search the UTMP database for the user we want. */

  static char utmptty[16];
  struct utmp utmp;
  struct passwd *userptr;
  int utmpfd;

  ourtty = ttyname(0);
  if (ourtty == NULL) ourtty = "/dev/console";

  if (user == NULL) exit(-1);
  if ((userptr = getpwnam(user)) == NULL) {
	fprintf(stderr, "No such user: %s\n", user);
	return(NULL);
  }
  if (verbose) fprintf(stderr, "Trying to write to %s\n",
		userptr->pw_gecos);

  if ((utmpfd = open("/etc/utmp", O_RDONLY)) < 0) {
	fprintf(stderr, "Cannot open utmp file\n");
	return(NULL);
  }
  utmptty[0] = '\0';

  /* We want to find if 'user' is logged on, and return in utmptty[]
   * 'user' `s terminal, and if 'user' is logged onto the tty the
   * caller specified, return that tty name. */
  while (read(utmpfd, (char *) &utmp, sizeof(utmp)) == sizeof(utmp)) {
	/* is this the user we are looking for? */
	if (strncmp(utmp.ut_name, user, sizeof(utmp.ut_name))) continue;

	strcpy(utmptty, utmp.ut_line);
	/* is he on the terminal we want to write to? */
	if (tty == NULL || !strcmp(utmptty, tty)) {
		break;
	}
  }

  if (utmptty[0] == '\0') {
	fprintf(stderr, "%s is not logged on\n", user);
	return( NULL);
  }
  if (tty != NULL && strcmp(utmptty, tty)) {
	fprintf(stderr, "%s is logged onto %s, not %s\n", user, utmptty, tty);
	return( NULL);
  }
  close(utmpfd);

  if (verbose) fprintf(stderr, "Writing to %s on %s\n", user, utmptty);
  return(utmptty);
}


void settty(utty)
char *utty;			/* name of terminal found in utmp */
{
/* Open other person's terminal and setup our own terminal. */

  char buff[48];

  sprintf(buff, "/dev/%s", utty);
  if ((otty = open(buff, O_WRONLY)) < 0) {
	fprintf(stderr, "Cannot open %s to write to %s\n", utty, user);
	fprintf(stderr, "It may have write permission turned off\n");
	exit(-1);
  }
  tcgetattr(0, &ttyold);
  tcgetattr(0, &ttynew);
  ttynew.c_lflag &= ~(ICANON|ECHO);
  signal(SIGINT, intr);
  if (cbreak) tcsetattr(0, TCSANOW, &ttynew);
}


void sayhello()
{
  struct passwd *pw;
  char buff[128];
  long now;
  char *sp;

  time(&now);

  pw = getpwuid(getuid());
  if (pw == NULL) {
	fprintf(stderr, "unknown user\n");
	exit(-1);
  }
  if ((sp = strrchr(ourtty, '/')) != NULL)
	++sp;
  else
	sp = ourtty;

  sprintf(buff, "\nMessage from %s (%s) %-24.24s...\n",
	pw->pw_name, sp, ctime(&now));

  write(otty, buff, strlen(buff));
  printf("\007\007");
  fflush(stdout);
}


void escape(cmd)
char *cmd;
{
/* Shell escape. */

  register char *x;

  write(1, "!\n", 2);
  for (x = cmd; *x; ++x)
	if (*x == '\n') *x = '\0';

  system(cmd);
  write(1, "!\n", 2);
}


void writetty()
{
/* The write loop. */

  char line[80];
  int n, cb_esc;

  writing = 1;
  cb_esc = 0;

  while ((n = read(0, line, 79)) > 0) {
	if (line[0] == '\004') break;	/* EOT */

	if (cbreak && line[0] == '\n') cb_esc = 1;

	if (cbreak) write(1, line, n);

	if (line[0] == '!') {
		if (cbreak && cb_esc) {
			cb_esc = 0;
			tcsetattr(0, TCSANOW, &ttyold);
			read(0, line, 79);
			escape(line);
			tcsetattr(0, TCSANOW, &ttynew);
		} else if (cbreak)
			write(otty, line, n);
		else
			escape(&line[1]);
		continue;
	}
	write(otty, line, n);
  }
  write(1, "\nEOT\n", 5);
  write(otty, "\nEOT\n", 5);
}


void usage()
{
  fprintf(stderr, "usage: write [-c] [-v] user [tty]\n");
  fprintf(stderr, "\t-c : cbreak mode\n\t-v : verbose\n");
  exit(-1);
}


int main(argc, argv)
int argc;
char *argv[];
{
  register int c;
  char *sp;

  setbuf(stdout, (char *) NULL);

  /* Parse options. */
  while ((c = getopt(argc, argv, "cv")) != EOF) switch (c) {
	        case 'c':	cbreak = 1;	break;
	        case 'v':	verbose = 1;	break;
	    default:
		usage();
	}

  /* Parse user and tty arguments */
  if (optind < argc) {
	user = argv[optind++];

	/* WTMP usernames are 1-8 chars */
	if (strlen(user) > 8) *(user + 8) = '\0';

	if (optind < argc) {
		tty = argv[optind++];
		if (optind < argc) usage();
	}
  } else
	usage();

  sp = finduser();		/* find which tty to write onto */
  if (sp != NULL) {	/* did we find one? */
	settty(sp);		/* setup our terminal */
	sayhello();		/* print the initial message */
	writetty();		/* the write loop */
	tcsetattr(0, TCSANOW, &ttyold);
	exit(0);
  }
  return(-1);
}

void intr(dummy)
int dummy;			/* to satisfy the prototype */
{
/* The interrupt key has been hit. exit cleanly. */

  signal(SIGINT, SIG_IGN);
  fprintf(stderr, "\nInterrupt. Exiting write\n");
  tcsetattr(0, TCSANOW, &ttyold);
  if (writing) write(otty, "\nEOT\n", 5);
  exit(0);
}
