/*
 * nwrite: write replacement. 
 *
 * Version 1.9.2 - February 9, 1998
 *
 ****************************************************************************
 * The nwrite software package and all files contained therein are covered
 * by this copyright notice & license:
 * 
 * Copyright (c) 1993 Marco Nicosia <marco@csua.berkeley.edu>
 * Copyright (c) 1994-1995 Aaron C. Smith <aaron@csua.berkeley.edu>
 * Copyright (c) 1995-1998 Alan Coopersmith <alanc@csua.berkeley.edu>
 * 
 * Permission to use, copy, modify, and distribute this software in source
 * and/or binary forms and its documentation, without fee and without a 
 * signed licensing agreement, is hereby granted, provided that the 
 * following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The names of the authors may not be used to endorse or promote
 *     products derived from this software without specific prior written
 *     permission.
 * 
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 * SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE
 * AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * 
 * THE AUTHORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY,
 * PROVIDED HEREUNDER IS PROVIDED "AS IS". THE AUTHORS HAVE NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 * 
 ****************************************************************************
 * 
 */

#ifndef lint
const char sccsid[] = "@(#)nwrite.c	$Revision: 1.9.1 $ $Date: 97/11/15 18:40:38 $ Marco Nicosia, Aaron Smith, Alan Coopersmith";
const char rcsid[] = "$Id$";
#endif

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <sys/types.h>
#ifdef HAVE_SYS_FILE_H
# include <sys/file.h>
#endif
#include <sys/stat.h>

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <utmp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif

#if STDC_HEADERS
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr    index
#  define strrchr  rindex
# endif
#endif

#ifndef TTYS_WORLD_WRITABLE
# define TTYS_WORLD_WRITABLE	TRUE
#endif

#ifdef HAVE_PATHS_H
# include <paths.h>
#endif

#ifndef TTY_DIR
# ifdef _PATH_DEV
#  define TTY_DIR		_PATH_DEV
# else
#  define TTY_DIR               "/dev/"
# endif
#endif

#ifndef TTY_PREFIX
# ifdef PTS_STYLE_TTYS
#  define TTY_PREFIX	      "pts/"
# else
#  define TTY_PREFIX            "tty"
# endif
#endif

#ifndef TTYNAMESZ
# define TTYNAMESZ             24
#endif

#ifndef UT_NAMESIZE
# define UT_NAMESIZE	    sizeof(utmp_entry.ut_name)
#endif

#ifndef UT_LINESIZE
# define UT_LINESIZE	    sizeof(utmp_entry.ut_line)
#endif

#ifndef UTMP_FILE
# ifdef _PATH_UTMP /* newer BSD's */
#  define UTMP_FILE _PATH_UTMP
# else
#  define UTMP_FILE "/etc/utmp"
# endif
#endif

#ifndef PATH_MAX	/* should be in <limits.h> on POSIX platforms */
# define PATH_MAX 1024
#endif

#ifndef HAVE_STRDUP
# define strdup	    mystrdup
  static char *mystrdup(const char *);
#endif

#ifndef SIG_ERR
# define SIG_ERR BADSIG
#endif

#ifndef TRUE
# define FALSE                 0
# define TRUE                  (!FALSE)
#endif

/* errno should be defined by including <errno.h> on ANSI-C platforms */
#ifdef NEED_ERRNO_DECL	
extern int errno;
#endif

/**** End of portability sections ****/

extern char *optarg;
extern int optind, opterr;

#define BOLD_START	    "\033[1m"	    /* vt100-series specific */
#define BOLD_END	    "\033[m"	    /* unfortunately	     */
const char bold_start[]	    = BOLD_START;
const char bold_end[]	    = BOLD_END;
#define BUFSIZE               1024
#define RC_FILENAME           "/.nwriterc"

/* for showtarg directive */
enum { TARG_NEVER = 0, TARG_MULTIPLE, TARG_ALWAYS };
enum { BOLD_NEVER = 0, BOLD_HEADER, BOLD_TAG, BOLD_ALL };

typedef struct pref_t {
  int nodelay;
  int beep;
  int bold;
  int headers;
  int whoeof;
  int showtarg;
  char preftty[TTYNAMESZ];
  char *line_header;
} pref_t; 

typedef struct tty_info_t {
    char     name [TTYNAMESZ];	/* name of this tty */
    FILE    *file;		/* file descriptor open & writing to it */
    int	     found;
    int	     specified;		/* was it specifically asked for by name? */
    struct tty_info_t *next;
} tty_info_t;

typedef struct local_user_t {
  char		*name;
  tty_info_t	*ttys;
  pref_t	 pref;
  int		 header_sent;
  int		 ttys_requested, ttys_found;
} local_user_t;

typedef struct user_t {
  struct user_t		    *next;
  enum { LOCAL, REMOTE }     type;
  union {
    local_user_t    *local;
  }			     info;
} user_t;

/* prototypes */
char *		make_idle(int);
RETSIGTYPE   	sigint_handler();
void   		get_prefs(local_user_t *, char *);
void   		read_prefs(char *, pref_t *);
void 		send_header(user_t *);

/* globals */
FILE	*utmpd;
/* user_t  my; */
char	*myname;
char	mytty[TTYNAMESZ];
char    *myrcname;
user_t  *user_list = NULL;
int     local_req_count = 0;
int	local_req_found = 0;
struct utmp   utmp_entry;
struct stat   stat_buf;
struct passwd pwd_stack;
struct passwd *pwd = &pwd_stack;
time_t	now;
int	debugmode = 0;

struct pref_t default_pref = {
  /* nodelay */   FALSE,
  /* beep    */   TRUE,
  /* bold    */   BOLD_NEVER,
  /* headers */   TRUE,
  /* whoeof  */   FALSE,
  /* showtarg */  TARG_MULTIPLE,
  /* preftty */	  "",
  /* line_header */ NULL

};

tty_info_t *new_tty_info_t (void)
{
    tty_info_t	*new_tit;

    new_tit = calloc(1, sizeof(tty_info_t));

    if (new_tit == NULL) {
	perror("nwrite: fatal memory allocation error");
	exit(errno);
    }
    return (new_tit);
}

void init_globals() 
{
  char    buf[BUFSIZE];
  int     myttyfd;	/* file descriptor of input tty */
  char *  myttyname;

#ifdef sequent
  extern char *ttyname();
#endif

  time(&now);	/* store the current time in now */

  pwd = getpwuid(getuid());
  if (pwd == NULL) {
    fprintf(stderr, "nwrite: couldn't determine your login name.\n");
    exit(1);
  }
    myname = strdup(pwd->pw_name);

  /* determine controlling terminal */
  if (isatty(fileno(stdin))) 
    myttyfd = fileno(stdin);
  else if (isatty(fileno(stdout))) 
    myttyfd = fileno(stdout);
  else if (isatty(fileno(stderr))) 
    myttyfd = fileno(stderr);
  else {
    fprintf(stderr, "nwrite: could not find your tty.\n");
    exit(2);
  }

  if (fstat(myttyfd, &stat_buf) == -1) {
    fprintf(stderr, "nwrite: could not stat your tty.\n");
    exit(2);
  }

#if TTYS_WORLD_WRITABLE
  if (!(stat_buf.st_mode & (S_IWRITE >> 6)))
      fprintf(stderr, "nwrite: your tty is not%s writable.\n",
	      debugmode ? " world" : "");
#else
  if (!(stat_buf.st_mode & (S_IWRITE >> 3)))
      fprintf(stderr, "nwrite: your tty is not%s writable.\n",
	      debugmode ? " group" : "");
#endif

  myttyname = ttyname(myttyfd);
  if (myttyname == NULL) {
    fprintf(stderr, "nwrite: could not determine your tty.\n");
    exit(3);
  }
  else
    strncpy(buf, myttyname, TTYNAMESZ);

  myttyname = strchr(buf + 1, '/');
  if (myttyname != NULL)
  {
    strcpy(mytty, myttyname + 1);
  }
  else
    strcpy(mytty, buf);

#ifdef SYSTEM_NWRITERC
  read_prefs(SYSTEM_NWRITERC, &default_pref);
#endif
}

void get_prefs(local_user_t *usr, char *target) 
{
  char prefsFileName[PATH_MAX];

  /* defaults */
  usr->pref = default_pref;

  if (target == NULL) 
    return;

  snprintf(prefsFileName, sizeof(prefsFileName), "%s%s", target, RC_FILENAME);
  read_prefs(prefsFileName, &(usr->pref));
}

/* parse pref file */
void read_prefs(char *prefsFileName, pref_t *prefTarget) {
  char buf[BUFSIZE];
  char option[BUFSIZE];
  char pref_str[BUFSIZE];
  char scratch_buf[BUFSIZE];
  int  value;
  FILE *prefs = NULL;

  *scratch_buf = '\0';

  if (debugmode)
    fprintf(stderr, "Opening %s...", prefsFileName);
  prefs = fopen(prefsFileName, "r");
  if (prefs == NULL) {
    if (debugmode)
      perror(prefsFileName);
    return;
  }

  if (debugmode)
    printf("succeeded\n");

  while (fgets(buf, BUFSIZE, prefs) != NULL) {
    sscanf(buf, "%s%s", option, pref_str);
    if (debugmode)
	printf("option: '%s'\tpref_str: '%s'\n", option, pref_str);
    value = atoi(pref_str);
    if (strncmp(option, "beep", sizeof("beep") - 1) == 0)
      prefTarget->beep = value;
    else if (strncmp(option, "nodelay", sizeof("nodelay") -1 ) == 0)
      prefTarget->nodelay = value;
    else if (strncmp(option, "boldhdr", sizeof("boldhdr") -  1) == 0)
      prefTarget->bold = BOLD_TAG;
    else if (strncmp(option, "bold", sizeof("bold") - 1) == 0) {
      strcpy(scratch_buf, pref_str);
      if (strncmp(scratch_buf, "never", sizeof("never") - 1) == 0)
        prefTarget->bold = BOLD_NEVER;
      else if (strncmp(scratch_buf, "header", sizeof("header") - 1) == 0)
        prefTarget->bold = BOLD_HEADER;
      else if (strncmp(scratch_buf, "tag", sizeof("tag") - 1) == 0)
        prefTarget->bold = BOLD_TAG;
      else if (strncmp(scratch_buf, "all", sizeof("all") - 1) == 0)
	prefTarget->bold = BOLD_ALL;
      else if (strlen(scratch_buf) == 1) /* backwards compat */
	prefTarget->bold = atoi(scratch_buf);
    }
    else if (strncmp(option, "headers", sizeof("headers") - 1) == 0)
      prefTarget->headers = value;
    else if (strncmp(option, "whoeof", sizeof("whoeof") - 1) == 0)
      prefTarget->whoeof = value;
    else if (strncmp(option, "showtarg", sizeof("showtarg") - 1) == 0) {
      strcpy(scratch_buf, pref_str);
      if (strncmp(scratch_buf, "never", sizeof("never") - 1) == 0)
	prefTarget->showtarg = TARG_NEVER;
      else if (strncmp(scratch_buf, "multi", sizeof("multi") - 1) == 0)
	prefTarget->showtarg = TARG_MULTIPLE;
      else if (strncmp(scratch_buf, "always", sizeof("always") - 1) == 0)
	prefTarget->showtarg = TARG_ALWAYS;
    }
    else if (strncmp(option, "preftty", sizeof("preftty") - 1) == 0)
      strcpy(prefTarget->preftty, pref_str);
  }
  if (debugmode) {
    printf("Options are:\n"
	   "Nodelay: %d\n"
	   "Beep: %d\n"
	   "Bold: %d\n"
	   "Headers: %d\n"
	   "WhoEOF: %d\n"
	   "ShowTarg: %d\n"
	   "PrefTTY: %s\n",
	   prefTarget->nodelay, prefTarget->beep, prefTarget->bold,
	   prefTarget->headers, prefTarget->whoeof, prefTarget->showtarg,
	   prefTarget->preftty);
  }
}

int main(int argc, char *argv[])
{
  char   buf[BUFSIZE];
  char   *bufptr = buf;

  int    IFLAG = FALSE;  /* perform idle checking? */
#ifdef CLEAR_PROCS
  int    AFLAG = FALSE; 
#endif
  time_t idle_limit = 0;
  char  *idle_string = NULL;
  tty_info_t  *the_tty;

  user_t *usr;
  user_t *prev;

  /* install signal handlers */
  if (signal(SIGINT, sigint_handler) == SIG_ERR) {
    fprintf(stderr, "nwrite: Error installing SIGINT handler.\n");
  }
  if (signal(SIGHUP, sigint_handler) == SIG_ERR) {
    fprintf(stderr, "nwrite: Error installing SIGHUP handler.\n");
  }

  if (argc < 2) {
  usage:
    fprintf(stderr, "Usage: nwrite [flags] user[.tty] [user[.tty] ...]\n"
#ifdef CLEAR_PROCS
	    "\t-a <time>\t clear arguments in process table\n"
#endif
	    "\t-i <time>\t do not write to ttys more than <time> minutes idle\n");
    exit(1);
  }
  else {
    int c;
#ifdef CLEAR_PROCS
    while ((c = getopt(argc, argv, "ai:D")) != -1)
#else
    while ((c = getopt(argc, argv, "i:D")) != -1)
#endif
      switch (c) {
      case 'i':
	IFLAG = TRUE;
	idle_limit = atoi(optarg)*60;
	break;
#ifdef CLEAR_PROCS
      case 'a':
	AFLAG = TRUE;
	break;
#endif
      case 'D':
	debugmode = TRUE;
	break;
      default:
	goto usage;
	break;
      }
  }
  if (optind == argc)
    goto usage;

  init_globals(); /* obtain utmp file descriptor, etc. */

  while (optind < argc) {
    char  *ttyptr;
    int	   new_usr = 0;

    if (strchr(argv[optind], '@'))
    {
	/* eventually handle remote writes here */
	fprintf(stderr, 
		"nwrite: writing to remote hosts not supported (yet)\n"
		"        skipping %s\n", argv[optind]);
    } 
    else 
    { /* write to local user */
	tty_info_t  *new_tty = NULL;

	new_usr = 1;
	ttyptr = strrchr(argv[optind], '.');
	if (ttyptr != NULL)
	    *(ttyptr++) = '\0';

	for (usr = user_list; (usr != NULL) && new_usr; )
	{
	    if ( (usr->type == LOCAL) &&
		 !(strcmp(usr->info.local->name, argv[optind])))
	    {
		new_usr = 0;
	    }
	    else
	    {
		usr = usr->next;
	    }
	}

	if (new_usr) {
	    usr = (user_t *)malloc(sizeof(user_t));

	    usr->type = LOCAL;
	    usr->info.local = (local_user_t *) malloc(sizeof(local_user_t));
	    usr->info.local->name = strdup(argv[optind]);

	    pwd = getpwnam(usr->info.local->name);
	    if (pwd) {
		get_prefs(usr->info.local, pwd->pw_dir);
	    } else {
/*		fprintf(stderr, "nwrite: getpwnam(%s) failed", */
		fprintf(stderr, "nwrite: could not find user named %s", 
			usr->info.local->name);
		free(usr);
		usr = user_list;

		if ( (usr == NULL) || (ttyptr != NULL) 
		    || (usr->info.local->ttys->specified == TRUE) ) {
		    fprintf(stderr, " - skipping that user.\n");
		}
		else if (strncmp(argv[optind], TTY_PREFIX,
                         sizeof(TTY_PREFIX) - 1) == 0) 
		{
		    fprintf(stderr, " - assuming it's a tty name.\n");

		    strcpy(usr->info.local->ttys->name, TTY_DIR);
		    strcat(usr->info.local->ttys->name, argv[optind]);
		    usr->info.local->ttys->specified = TRUE;
		}
		else if (strncmp(argv[optind], TTY_DIR,
                         sizeof(TTY_DIR) - 1) == 0) 
		{
		    fprintf(stderr, " - assuming it's a tty name.\n");

                    strcat(usr->info.local->ttys->name, argv[optind]);
                    usr->info.local->ttys->specified = TRUE;
		} else { 
		    fprintf(stderr, " - skipping that user.\n");
		}
	    
		optind++;
		continue;
	    }

	    usr->info.local->ttys = NULL;
	    usr->info.local->ttys_found = 0;
	    usr->info.local->ttys_requested = 0;
	    usr->info.local->header_sent = 0;

	    usr->next = user_list;
	    user_list = usr;
	}

	/* Make new tty and put it on the front of the tty list */
	new_tty		    = new_tty_info_t();
        new_tty->file       = NULL;
        new_tty->next       = usr->info.local->ttys;
	usr->info.local->ttys = new_tty;
	usr->info.local->ttys_requested++;
        local_req_count++;

	if (ttyptr == NULL) {
	    new_tty->specified	= FALSE;
	}
	else 
	{    /* tty specified */
	    new_tty->specified	= TRUE;

	    if (strncmp(ttyptr, TTY_DIR, sizeof(TTY_DIR) - 1) != 0)
	    {
		/* add /dev/ prefix if user didn't supply it */
		strncpy(new_tty->name, TTY_DIR, sizeof(new_tty->name));

		if (strncmp(ttyptr, TTY_PREFIX, sizeof(TTY_PREFIX) - 1) != 0)
		{
		    /* add tty prefix if user didn't supply it */
		    strcat(new_tty->name, TTY_PREFIX);
		}
	    }
	    strcat(new_tty->name, ttyptr);
	}
    }

    optind++;
  }

#ifdef CLEAR_PROCS
  if (AFLAG) {
    int i;
    /* clear argv */
    for (i = 1; i < argc; i++) {
      int j;
      for (j = 0; argv[i][j]; j++)
	argv[i][j] = '\0';
    }
  }
#endif

  /* open the utmp */
  if ((utmpd = fopen(UTMP_FILE, "r")) == NULL) {
    /* don't use argv here - it might've been cleared */
    perror("nwrite (" UTMP_FILE "):");
    exit(1);
  }

  /* scan utmp for logins matching any of our targets */
  while ( (local_req_found < local_req_count) && 
	 (fread((char *) &utmp_entry, sizeof(utmp_entry), 1, utmpd) == 1)) {

#ifdef non_user
    if (non_user(utmp_entry))
	continue;
#endif

#ifdef USER_PROCESS
    if (utmp_entry.ut_type != USER_PROCESS)
	continue;
#endif

    /* skip blank lines in utmp */
    if ( (utmp_entry.ut_line[0] == '\0') || (utmp_entry.ut_name[0] == '\0') )
	continue;

    for (usr = user_list; usr != NULL; usr = usr->next ) {

      while ( (usr != NULL) && (usr->type != LOCAL) && 
           (usr->info.local->ttys_requested >= usr->info.local->ttys_found)) {
	usr = usr->next;
      }

      if (usr == NULL)
	  break;

      if (!(strncmp(utmp_entry.ut_name, usr->info.local->name,
		    sizeof(utmp_entry.ut_name)))) {
	time_t	     idle_time   = 0;
	int	     tty_fd;
	int	     matched	 = 0;

	for (the_tty = usr->info.local->ttys; 
	     (the_tty != NULL) && !(matched);
	     the_tty = the_tty->next)
	{
	    if (!(the_tty->found)) {
		if (the_tty->specified) {
		    if (!strncmp(utmp_entry.ut_line, 
				 the_tty->name + sizeof(TTY_DIR) - 1,
				 UT_LINESIZE)) 
		    {
			the_tty->found = TRUE;
		    }
		} else { /* tty not specified */
		    the_tty->found = TRUE;
		    matched++;
		    strcpy(the_tty->name, TTY_DIR);
		    strncat(the_tty->name, utmp_entry.ut_line, 
			    sizeof(utmp_entry.ut_line));
		}
	    
		if (the_tty->found == TRUE) {
		    if(stat(the_tty->name, &stat_buf) != 0)
			fprintf(stderr, "nwrite: couldn't stat %s.\n", 
				the_tty->name);
		    else {
			if (stat_buf.st_mtime < stat_buf.st_atime)
			    idle_time = now < stat_buf.st_mtime ? 0 : 
			                now - stat_buf.st_mtime;
			else
			    idle_time = now < stat_buf.st_atime ? 0 : 
       			                now - stat_buf.st_atime;
			
			idle_string = make_idle(idle_time);
			if (IFLAG && (the_tty->specified == FALSE)) {
			    if (idle_time > idle_limit) {
				printf("ignoring %s on %s (idle %s)\n", 
				       usr->info.local->name, 
				       the_tty->name + sizeof(TTY_DIR) - 1, 
				       idle_string);
				the_tty->found = FALSE;
				continue;
			    }
			}
		    }

/* tell POSIX-ish systems not to make the target's tty the controlling	*/
/* tty for this process							*/
#ifdef O_NOCTTY		
#define TTY_FLAGS O_WRONLY | O_NOCTTY
#else
#define TTY_FLAGS O_WRONLY
#endif

		    tty_fd = open(the_tty->name, TTY_FLAGS);

		    /* Paranoia - make sure we're writing to a tty, not
		       appending to a file someone has stuck there */

		    if ( (tty_fd < 0) || !(isatty(tty_fd)) || 
			(the_tty->file = fdopen(tty_fd, "w")) == NULL ) {
			if (buf == bufptr) {
			    strcpy(buf, "nwrite: could not open ");
			    bufptr += sizeof("nwrite: could not open ") - 1;
			} else {
			    strcat(bufptr, ", ");
			}
			strncat(bufptr, utmp_entry.ut_name, UT_NAMESIZE);
			strcat(bufptr, ".");
			strncat(bufptr, utmp_entry.ut_line, UT_LINESIZE);

			bufptr += strlen(bufptr);
			the_tty->found = FALSE;
			continue;
		    }

		    if (idle_time > 60)
			printf("writing to %s on %s (idle %s)\n", 
			       usr->info.local->name, 
			       the_tty->name + sizeof(TTY_DIR) - 1, 
			       idle_string);
		    else 
			printf("writing to %s on %s\n", 
			       usr->info.local->name,
			       the_tty->name + sizeof(TTY_DIR) - 1);

		    usr->info.local->ttys_found++;
		    local_req_found++;
		    if (debugmode)
			printf("local_req_count: %d\tlocal_req_found: %d\n", 
			        local_req_count, local_req_found);
		    break;
		}
	    }
	}
      }
    }
  }
  if (bufptr != buf)
	fprintf(stderr, "%s\n", buf);
    
  fclose(utmpd);

  if (IFLAG && idle_limit) {
    idle_string = (char *) malloc(64);
    snprintf(idle_string, 64, " that were less than %s idle", 
	    make_idle(idle_limit));
  } else {
    idle_string = "";
  }

  usr = user_list;
  prev = NULL;

  while (usr) {
    if (usr->type == LOCAL) {
	if (usr->info.local->ttys_found < usr->info.local->ttys_requested) {

	    for (the_tty = usr->info.local->ttys; (the_tty != NULL);
		 the_tty = the_tty->next)
	    {
		if (!(the_tty->found) && the_tty->specified) {
		    fprintf(stderr, 
			    "nwrite: utmp says %s is not logged onto tty %s.\n",
			    usr->info.local->name, the_tty->name);
		}
	    }

	    if (usr->info.local->ttys_found == 0)
	    {
		user_t	*next_usr;

		fprintf(stderr, "nwrite: no writable ttys found for %s%s.\n",
			usr->info.local->name, idle_string);

		if (prev) {
		    prev->next = usr->next;
		} else {
		    user_list = usr->next;
		}
		next_usr = usr->next;
		local_req_count -= usr->info.local->ttys_requested;
		free(usr); /* should free all associated structures as well */
		usr = next_usr;
		continue;
	    }
	    else
	    { /* some, but not all ttys found */
		tty_info_t  *prev_tty, *cur_tty, *next_tty;

		fprintf(stderr, 
			"nwrite: only found %d writable tty%s for %s%s.\n",
			usr->info.local->ttys_found, 
			(usr->info.local->ttys_found > 1) ? "s" : "",
			usr->info.local->name,
			idle_string);

		prev_tty = NULL;
		cur_tty = usr->info.local->ttys;
		while (cur_tty != NULL) {
		    if (cur_tty->found == FALSE) {
			next_tty = cur_tty->next;

			if (prev_tty == NULL)
			    usr->info.local->ttys = next_tty;
			else
			    prev_tty->next = next_tty;

			free(cur_tty);
			cur_tty = next_tty;
		    }
		    else
		    { 
			prev_tty = cur_tty;
			cur_tty = prev_tty->next;
		    }
		}		
	    }
	}
    }
    prev = usr;
    usr = usr->next;
  }


  if (debugmode) {
    usr = user_list;
    while (usr) {
	if ( (usr->type == LOCAL) && (usr->info.local->ttys_found == 0) )
	    fprintf(stderr, "messed up: %s\n", usr->info.local->name);
	usr = usr->next;
    }
  }
  fflush(stderr);
  if (local_req_found == 0) {	/* REMOTE TODO: add test for remote conns */
    exit(1);
  }

  for (usr = user_list; usr != NULL ; usr = usr->next) {
    if (usr->info.local->pref.nodelay) {
	send_header(usr);
    }
  }

  while (fgets(buf, BUFSIZE, stdin) != NULL) {
    char	*p;

    /* strip newline */
    if ((p = strrchr(buf, '\n'))) *p = '\0';
    
    usr = user_list;
    while(usr) {
      int ok = 0;

      if (!usr->info.local->header_sent) {
	send_header(usr);
      }

      for (the_tty = usr->info.local->ttys; 
	   the_tty != NULL; the_tty = the_tty->next) {

	if (usr->info.local->pref.bold == BOLD_ALL)
	  ok = fprintf(the_tty->file, "%s%s" BOLD_END " \r\n", 
		       usr->info.local->pref.line_header, buf);
	else
	  ok = fprintf(the_tty->file, "%s%s\r\n", 
		       usr->info.local->pref.line_header, buf);

	if ( (ok < 0) || (fflush(the_tty->file) == EOF) ) {
	  fprintf(stderr, 
		  "\007nwrite: write to %s on %s failed, user logged out?\n",
		  usr->info.local->name, the_tty->name);
	  fclose(the_tty->file);
	  if (usr->info.local->ttys_found > 1) {
	    if (usr->info.local->ttys == the_tty)
	      usr->info.local->ttys = the_tty->next;
	    else {
	      tty_info_t  *tmp_tty = usr->info.local->ttys;

	      while ( (tmp_tty != NULL) && (tmp_tty->next != the_tty) )
		tmp_tty = tmp_tty->next;

	      if (tmp_tty != NULL) {
		tmp_tty->next = the_tty->next;
		free(the_tty);
	      }
	    }
	  } else { /* last tty - nuke the whole user entry */
	    if (usr == user_list) {
		if (usr->next != NULL) {
		  user_list = usr->next;
		  free (usr);
		  local_req_count--;
	      }
	      else    /* no one left to write to */
		exit(1);	  
	    } else {
	      user_t  *temp, *temp_prev;

	      temp_prev = user_list;
	      temp = user_list-> next;

	      while ( (temp != usr) && (temp != NULL) )
	      {
		temp_prev = temp;
		temp = temp->next;
	      }

	      temp_prev->next = usr->next;
	      free(usr);
	      local_req_count--;
	    }
	  }
	}
      }
      usr = usr->next;
    }
  }
  sigint_handler(); /* why reduplicate code? */
  return 0; /* not reached */
}

char *make_idle(int diff)
{
  /* this function lifted from WHO, by Marco Nicosia */

  char *idle = (char *) malloc(20 * sizeof(char));
  
  if (diff == 0) 
    idle[0] = '\0';
  else if (diff < 60) 
    snprintf(idle, 20, "%ds", diff);
  else if (diff < 3600) 
    snprintf(idle, 20, "%dm", diff / 60);
  else if (diff < 86400)
    snprintf(idle, 20, "%d:%.2d", diff / 3600, (diff % 3600) / 60);
  else 
    snprintf(idle, 20, "%dd", diff / 86400);
  return(idle);
}

RETSIGTYPE sigint_handler() {
    user_t	*usr;
    tty_info_t	*tty;

    for (usr = user_list; usr != NULL; usr = usr->next) {
	switch (usr->type)
	{
	    case LOCAL:
		if (usr->info.local != NULL) {
		    for (tty = usr->info.local->ttys; 
			 tty != NULL; tty = tty->next ) {
		    
			if (usr->info.local->header_sent) {
			    if (usr->info.local->pref.bold)
				fprintf(tty->file, BOLD_START "EOF");
			    else
				fprintf(tty->file, "EOF");
			    if (usr->info.local->pref.whoeof && myname)
				fprintf(tty->file, "(%s)", myname);
			    if (usr->info.local->pref.bold)
				fprintf(tty->file, BOLD_END "\n");
			    else
				fprintf(tty->file, "\n");
			}
			fclose(tty->file);
		    }
		}
		break;

	    case REMOTE:
	    default:
		fprintf(stderr, "Non-local ttys not implemented yet");
		break;
	}
    }

  putchar('\n');
  exit(0);
}

void send_header(user_t *usr)
{
  user_t	*tmp	      = user_list;
  static char	 timebuf[32]  = "";
  tty_info_t    *the_tty;

  if (timebuf[0] == '\0')     /* initialize time the first time through */
  {
    char   *ttime;
    
#ifdef HAVE_STRFTIME
    struct tm *the_time	= localtime(&now);

    if (the_time != NULL)
      strftime(timebuf, sizeof(timebuf), "%X", the_time);

    if (timebuf[0] == '\0')   /* just in case strftime fails */
#endif
    {
      ttime = ctime(&now);
      strncat(timebuf, ttime + 11, 8);
    }
  }
  for (the_tty = usr->info.local->ttys; the_tty != NULL; 
       the_tty = the_tty->next)
  {
    if (the_tty->file != NULL)
    {
	fprintf(the_tty->file, "\r\n");
	if (usr->info.local->pref.beep)
	    fprintf(the_tty->file, "\007\007\007");
	if (usr->info.local->pref.bold)
	    fprintf(the_tty->file, "%s", bold_start);

	fprintf(the_tty->file, "Message from %s on %s ", myname, mytty);
	if ((usr->info.local->pref.showtarg == TARG_MULTIPLE &&
	     user_list->next)
	    || usr->info.local->pref.showtarg == TARG_ALWAYS) {
	    fprintf(the_tty->file, "to ");
	    for (tmp = user_list; tmp != NULL; tmp = tmp->next) {
		fprintf(the_tty->file, "%s ", tmp->info.local->name);
	    }
	}
	fprintf(the_tty->file, "at %s ... \r\n", timebuf);
	if (usr->info.local->pref.bold)
	    fprintf(the_tty->file, "%s", bold_end);
	fflush(the_tty->file);
    }
  }
  /* Build header tag */
  if (usr->info.local->pref.headers) {
    char *temptr, *endptr;
    size_t tempsize;

    tempsize = sizeof(BOLD_START) + sizeof(BOLD_END) + 3 + strlen(myname);

    usr->info.local->pref.line_header = malloc(tempsize);

    temptr = usr->info.local->pref.line_header;
    *temptr = '\0';
    endptr= temptr + tempsize;

    if (usr->info.local->pref.bold > BOLD_HEADER) {
      strcpy(temptr, bold_start);
      temptr += sizeof(BOLD_START) - 1;
    }

    snprintf(temptr, endptr - temptr, " %s> ", myname);

    if (usr->info.local->pref.bold == BOLD_TAG)
      strcat(temptr, bold_end);
    if (debugmode)
	printf("line header for writing to %s: %s\n",
	        usr->info.local->name, usr->info.local->pref.line_header);
  }
  else if (usr->info.local->pref.bold == BOLD_ALL) {
    usr->info.local->pref.line_header = strdup(bold_start);
  }
  usr->info.local->header_sent = TRUE;
}


#ifndef HAVE_STRDUP
static char *mystrdup(const char *input)
{
    char *output = malloc(strlen(input) + 1);

    if (output != NULL)
	strcpy(output, input);

    return output;
}
#endif

#ifndef HAVE_SNPRINTF
# if __STDC__
#  include <stdarg.h>
# else
#  include <varargs.h>
# endif

#if __STDC__
int
snprintf(char *str, size_t n, char const *fmt, ...)
#else
int
snprintf(str, n, fmt, va_alist)
        char *str;
        size_t n;
        char *fmt;
        va_dcl
#endif
{
  va_list ap;
  int result;

#if __STDC__
  va_start(ap, fmt);
#else
  va_start(ap);
#endif
  result = vsprintf(str, fmt, ap);
  va_end(ap);
  return result;
}
#endif /* don't HAVE_SNPRINTF */
