[alt.sources] UNIX Chat Program

john@jwt.UUCP (John Temples) (01/05/91)

This is the chat program developed for the Magpie BBS by Steve Manes.
It supports features such as multiple password-protected channels,
private messages, and logging of chat.  It compiled and ran as-is on
my ESIX SVR3.2 system.  The author states it should run on most
System V and Xenix systems.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	Chat.Doc
#	Makefile
#	chat.c
#	getline.c
#	lockchat.c
#	misc.c
#	chat.h
# This archive created: Wed Jul 12 23:53:13 1989
export PATH; PATH=/bin:$PATH
if test -f 'Chat.Doc'
then
	echo shar: will not over-write existing file "'Chat.Doc'"
else
cat << \SHAR_EOF > 'Chat.Doc'



       Magpie MultiChat           Manual                Version 2.0



                          Magpie Multi-Chat v2.0
                     copyright (c)1989 by Steve Manes
                           Roxy Recorders, Inc.
                    648 Broadway, New York, N.Y. 10012



                         I N T R O D U C T I O N
                         -----------------------


       Magpie Chat was developed as a multiuser chat program for
       use with the Magpie/XENIX BBS program, however it will also
       work as a stand-alone multiple-party chat.  While there are
       several chat programs available for Unix and Xenix, Magpie
       Chat was written to provide a more complete multiuser chat
       environment for dedicated chat call-in applications or for
       sysops of *ix BBSes that need a secure and auditable real-
       time chat program.  This isn't an enhanced "write" or
       "phone" utility but a full-featured, multiuser chat utility.


































       Page 1







       Magpie MultiChat           Manual                Version 2.0



                                 FEATURES



       1.1  Chat Logging

       Chat messages are appended to a shared data file rather than
       written to the users' tty hardware.  This method provides a
       ready log of all chat activity readable by the local honcho
       and eliminates problems with device permissions.  Every 24
       hours, Chat archives the current chatfile for later auditing
       or for disposal and starts a new one.

       1.2  Multiple Channels

       There are 26 chat channels available in Chat.  Channel 'A',
       the default, is a public channel.  Channels 'B' through 'Z'
       may be password protected by the first user to log into the
       channel.  This will allow two or more users to enjoy a
       private chat and to send channel passwords to other users
       via Private chat messages.

       1.3  Word Wrap

       Full word-wrap of text on both input and output for users of
       any terminal width (32-132 columns)is supported.  By
       default, every user is assumed to be on an 80-column screen
       but this may be changed either on the command line or
       interactively through Chat by the user.  The maximum size of
       a chat message is 2k, although you can increase/decrease
       this by changing the value MAXCHAT in 'chat.h'.

       1.4  Editor Control

       The message input editor has cursor control/line editing.
       The following are supported:

           Ctrl-A  back word        Ctrl-F  forward word
           Ctrl-B  back character   Ctrl-D  forward character
           Ctrl-X  cancel line      Ctrl-R  retype line

       1.5  Private Messages

       Users may send private messages to each other regardless of
       what chat channel either is in.









       Page 2







       Magpie MultiChat           Manual                Version 2.0



       1.6  Shell Access

       This feature may be blocked by the local honcho if Chat is
       loaded via an "intelligent" script file.

       1.7  Chat Timer

       By default, all chatters have unlimited time.  However, a
       user's time limit in any one invocation of Chat may be fixed
       by the aforementioned script file.

       1.8  External Pager

       Summons other shell users to Chat.

       1.9  Chat Lock-Out

       Chat can be disabled whenever necessary by the local honcho.




































       Page 3







       Magpie MultiChat           Manual                Version 2.0



                               DISTRIBUTION



       2.1  Copyright Information

       This program is a copyrighted work owned by Steve Manes and
       Roxy Recorders, Inc of New York, NY.  All commercial and
       distribution rights are likewise reserved by Steve Manes.
       You are permitted to compile and use this software free of
       charge under the following conditions:

           1.  Copyright notices and internal prompts and strings
           including the program name and author's credit are NOT
           modified in any way.

           2.  Modifications to the source must NOT delete the
           original code.  Just #ifdef out the old code and add
           your replacement.  Also, please add any comments about
           your modification and, while you're at it, your name.
           You might as well take credit for the improvement.

           3.  That this source file is distributed with all
           included files, including Chat.Doc, in the original
           'shar' format.

           4.  That these files are distributed to others FREE OF
           CHARGE.  Chat source and its executable may NOT be
           included in any package for which a charge of money or
           exchange of services is requested or demanded.


       In addition, if these files are uploaded to any system which
       claims an automatic copyright claim over such uploads this
       will in no way invalidate the author's original copyright or
       legal ownership of his property.


















       Page 4







       Magpie MultiChat           Manual                Version 2.0



                                INTERNALS



       3.1  Chat File

       Chat processes do not communicate with each other directly
       but by means of a common, shared file.  When a chat message
       is entered, it is saved at the bottom of the current
       chatfile, from which all other users are constantly polling
       about once a second.  Besides providing enhanced auditing
       and reducing inter-process messaging, this allows chatfiles
       to be archived, edited or posted to a BBS or similar
       messaging system.  This will also work on systems where
       named pipes, another common Unix communications method, is
       broken or nonexistent.

       Chat automatically restarts a new chatfile at midnight.
       Users who have entered Chat before midnight will be
       automatically moved to the new chatfile.

       Chatfiles are created as 'chatMMDD' where MM=month and
       DD=day.  Since Chat sets all users to the effective user
       superuser has read privileges to chatfiles offline.

       Login/logout information as well as the text of Private
       messages between users is also included in the chatfiles.



       3.2  The Chat Editor

       Chat incorporates the Magpie BBS message editor line input
       function, getline().  Along with instext(), this provides a
       full word-wrapping editor.  Partial text lines are
       automatically joined by reformat() on output to chatfile.
       The formatter can also be forced to insert newlines by
       preceding a line of text with a '.' or a space for tabular
       information.  This also allows a 40-column user to read
       messages entered at 80-columns with formatting for his
       terminal width.

       3.3  Chat Command Line Arguments

       The command line contains arguments to change defaults
       within Chat.  They are mostly effective if Chat is called
       from another program, such as a BBS script file, to force
       the desired environment on selected users.  By default,
       everyone has unlimited Chat time, a username equal to their
       LOGNAME, an 80-column screen and shell privs.




       Page 5







       Magpie MultiChat           Manual                Version 2.0



           -n"name" option

           By default, Chat will use the user's LOGNAME for Chat.
           The -n switch overrides this.

           Example:  chat -n"Mr. Foo"


           -tNN timer option

           The -t argument will set the user's maximum allowable
           time to NN minutes.  If you're running a BBS with a
           limited number of lines and/or an enforced user timer
           this feature will be particularly welcome.  Just pass
           Chat the user's remaining timer on the BBS and Chat will
           automatically abort when the time limit is reached.

           Example:  chat -t30


           -cNN option

           By default, all users will log into Chat with 80 column
           terminal settings.  The -c option will change this to
           any terminal width from 32 to 132 columns, all with full
           word- wrap on input and output.  (Note: the chatfile is
           internally formatted at 78 columns for easier offline
           reading.)


           -x[list] option

           By default, all users have full access to the menu of
           Chat options.  The -x option will allow you to shut off
           most commands from the user (all except except <Q>uit
           and SPACEBAR).  Again, on a BBS you would probably want
           to prohibit users access to the <!>shell command and,
           possibly, the external pager.  Any commands blocked by
           -x will also not appear in the user's help menu.

           Example:  chat -x!s


           -d<path> option

           Overrides the default path to the chat directory
           specified in chat.h.

           Example:  chat -d/usr/magpie





       Page 6







       Magpie MultiChat           Manual                Version 2.0



                          COMPILING INFORMATION


       To allow all users to read and write to the same file and to
       provide general chatfile security Chat set's the user's
       effective ID to 'bin'.  Therefore, Chat must be compiled by
       superuser, or 'root'.

       Set up file defaults in "chat.h".

       type 'make' and then 'make install'











































       Page 7







       Magpie MultiChat           Manual                Version 2.0



                                   BUGS


       Chat is fairly vanilla but it has only been tested under
       Microport Unix and SCO Xenix.  If you encounter any problems
       with Chat under other flavors of *nix, and particularly if
       you've got a fix, I would appreciate it if you would drop me
       a line at any of the below addresses (particularly Magpie-HQ
       or UUCP).

       If you have troubles compiling or running Chat you'll
       probably find your trouble in either the 'lockchat.c' record
       locking/unlocking routines or in the way your system handles
       signal() interrupts.  Unfortunately, the only fix I can
       suggest is to find a guru for your particular flavor of *ix.

       If your users are prolific chatters, keep an eye on your
       Chatfile directory.  A large multiuser system can accumulate
       a megabyte or more of chat per day!


       Enjoy -- Steve Manes

       Magpie-HQ: (212)420-0527
       UUCP:      !{uunet|rutgers|cmcl2}!hombre!magpie!manes
       SmartMail: manes@MASA.COM




























       Page 8











                                 CONTENTS


       FEATURES.................................................  2
       1.1  Chat Logging........................................  2
       1.2  Multiple Channels...................................  2
       1.3  Word Wrap...........................................  2
       1.4  Editor Control......................................  2
       1.5  Private Messages....................................  2
       1.6  Shell Access........................................  3
       1.7  Chat Timer..........................................  3
       1.8  External Pager......................................  3
       1.9  Chat Lock-Out.......................................  3

       DISTRIBUTION.............................................  4
       2.1  Copyright Information...............................  4

       INTERNALS................................................  5
       3.1  Chat File...........................................  5
       3.2  The Chat Editor.....................................  5
       3.3  Chat Command Line Arguments.........................  5

       COMPILING INFORMATION....................................  7

       BUGS.....................................................  8





























                                  - i -




SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# @(#)	makefile for Magpie MultiChat
#	Steve Manes, Roxy Recorders, NYC
#
# Destination directory  -- should be a public directory
DESTDIR=/u/bin

# Owner and Group
OWNER=bbs
GROUP=bin

# Compiler flags
# If your compiler support the 'index' function rather than 'strchr'
# add '-DINDEX' to CFLAGS
# If your compiler doesn't support a 'void' type
# add '-DNOVOID' to CFLAGS
#
# 386 flags
CFLAGS = -O
# 286 flags
#CFLAGS = -Ms2 -O -F 4000

# this compile's for Xenix (comment out next line for SVR2)
OARG = -o chat -lx
# this one's for SVR2
# OARG = -o chat

CHATOBJ =	chat.o getline.o misc.o lockchat.o

CHATSRC = 	chat.c getline.c misc.c lockchat.c

all:	$(CHATOBJ)
	cc $(CFLAGS) $(CHATOBJ) $(OARG)

# must be run by 'root'
install:
	mv chat ${DESTDIR}
	chown ${OWNER} ${DESTDIR}/chat
	chgrp ${GROUP} ${DESTDIR}/chat
	chmod ug+s ${DESTDIR}/chat

sh:
	shar Chat.Doc Makefile chat.c getline.c lockchat.c misc.c chat.h > magchat.sh
	rm -f magchat.sh.Z
	compress magchat.sh
	ln magchat.sh.Z /prog/nuucp
SHAR_EOF
fi # end of overwriting check
if test -f 'chat.c'
then
	echo shar: will not over-write existing file "'chat.c'"
else
cat << \SHAR_EOF > 'chat.c'
/* @(#)	chat.c		-- Magpie Chat
 *
 *	Multi-chat program for Unix
 *	Copyright 1988	All Rights Reserved
 *	Steve Manes, Roxy Recorders, Inc., 648 Broadway, New York, NY 10012
 *	
 *	Originally developed as a multiuser chat facility for
 *	Magpie/UNIX BBS.
 *
 *	Args:
 *	-n"chat name"		chatter's handle (default: system uid)
 *	-tnn			time limit in minutes (default, no limit)
 *	-wnn			terminal width (default: 80 columns)
 *	-x[list]		chat commands to block from user (like !)
 *	-d<path>		path to Chat directory
 *
 */

#include "chat.h"
#include <sys/stat.h>
#include <setjmp.h>

WHOREC	my;

char	*prog = "\nMagpie MultiChannel MultiChat v2.1\n\
  Copyright 1989 by Steve Manes\n\n-- Type ? for help --";

int	mywidth = 80;			/* user's default terminal width */
struct	termio savterm, myterm;		/* terminal structure */
int	Outfid;				/* chat output file handle */
FILE	*Infid;				/* chat input file handle */
long	Chatpos;			/* chat file 'last-read' position */
int	Lockflag;			/* used by reqlock() */
long	TimesUp = -1;			/* user's chat time limit */
int	Today;				/* today's day */
char	workbuf[MAXLINE +1];		/* miscellaneous buffer */
char	ignore[10];			/* command keys to ignore */
char	Chatpath[MAXPATH];		/* chat file directory */
char	*buff, *tail;			/* message input buffer */
jmp_buf	cjmp;
char	*menu[] = {
	"\n",
	"/C   columns",
	"/J   join chat channel",
	"/L   list shell users",
	"/P   private to user",
	"/Q   quit chat",
	"/S   shell pager",
	"/T   time left",		
	"/W   who's in Chat",
	"/H   help",
	"/!   shell command",
	""
};
char	*use = "usage: -n\"user name\" -w[term width] -t[timer] -x[list]\n";

main(argc, argv)
char	*argv[];
{
	char	*cp;
	struct	stat Stat;

	strcpy(my.name, cuserid( (char *)0) );	/* set up CWHO defaults */
	strcpy(my.tty, ttyname(TTYIN));
	time(&my.login);
	ignore[0] = '\0';			/* all commands enabled */
	
	strcpy(Chatpath, CHATPATH);		/* default Chat path */
	
	while (--argc) {			/* get command line args */
		cp = *++argv;
		if (*cp++ == '-') {
			switch (*cp++) {

				case 'n':	/* chat name */
				strcpy(my.name, cp);
				break;
					
				case 'w':	/* user width */
				mywidth = atoi(cp);
				break;
				
				case 't':	/* time limit (seconds) */
				TimesUp = my.login + (long)(atol(cp));
				break;
			
				case 'x':	/* ignore commands */
				strcpy(ignore, cp);
				break;

				case 'd':	/* alternate chat path */
				strcpy(Chatpath, cp);
				break;
			
				default:	/* switch error */
				puts(use);
				exit(1);
			}
		}
	}			

	if (stat( fpath(Chatpath, NOCHATFILE), &Stat) == SUCCESS) {
		puts("\nSorry -- chat unavailable now\n");
		exit(1);
	}

	if (mywidth < 32)	mywidth = 32;	/* get default term width */
	if (mywidth > 132)	mywidth = 132;
	strupr(ignore);
		
	/* allocate buffer space */
	if (!(buff = (char *)malloc(BUFSIZE +100))) {
		puts("malloc error");
		exit(1);
	}

	if (openchat() != SUCCESS) {		/* open/create chat file */
		free(buff);
		exit(1);
	}
	strupr(my.name);
	setterm();
	puts(prog);			/* show the ad */
	whozon();			/* display WHO file */
	my.channel = 'A';		/* default channel: PUBLIC */
	my.pswd[0] = '\0';		/* no password for channel */
	if (addwho(&my) != SUCCESS) 	/* add name to CWHO */
		quit();
	announce(&my, TRUE);
	Chatpos = fseek(Infid, 0L, 2);	/* set read ptr to end-of-file */
	chat();
	quit();
}

/*----------------------------------------------------------------------
 *	chat()
 *	main routine for chat
 *	returns: ERROR (log out) or SUCCESS
 *----------------------------------------------------------------------*/
int	chat()
{
	long	ltime;
	struct	tm *TM;
	static	int warned;
				
	setjmp(cjmp);
	
	while (TRUE) {
		/* if user has a timer, check time left */
		if (TimesUp > -1) {
			time(&ltime);
			if (!warned && (ltime + 120) > TimesUp) {
				puts("\n** 2 minutes left **\n");
				warned = TRUE;
			}
			if (ltime > TimesUp) {
				puts("\n** Sorry, chat timer has expired **\n");
				quit();
			}
		}
		readchat();			/* display any chat waiting */
		if (getcmd() == ERROR)		/* get a chat command */
			break;
	}
}

/*----------------------------------------------------------------------
 *	openchat()
 *	composes chatfile name "CHATmm.dd" where 'mm' and 'dd' are
 *	today's month and day. 
 *	returns: ERROR or SUCCESS
 *----------------------------------------------------------------------*/
int	openchat()
{
	struct	tm *TM;
	char	chatfile[MAXPATH +1];		/* chat filename */
	long	ltime;
	
	time(&ltime);				/* set time now */
	TM = localtime(&ltime);			/* get structure */
	sprintf(chatfile, "%s%02d%02d", fpath(Chatpath, CHATFILE),
	   TM->tm_mon +1, TM->tm_mday);

	/* open chat write handle */
	if ((Outfid = open(chatfile, O_RDWR | O_CREAT, 0x1b0)) == ERROR) {
		printf("Can't open %s for writing\n", chatfile);
		return( ERROR );
	}

	/* open chat read handle */
	if ( !(Infid = fopen(chatfile, "r"))) {
		printf("Can't open %s for reading\n", chatfile);
		return( ERROR );
	}
	Today = TM->tm_mday;
	return( SUCCESS );
}

/*----------------------------------------------------------------------
 *	quit()
 *	branches here to clean up and leave program
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	quit()
{
	long	ltime;
	
	time(&ltime);
	announce(&my, FALSE);
	unlock();
	close(Outfid);
	fclose(Infid);
	ioctl(TTYIN, TCSETAW, &savterm);
	killwho(&my);
	free(buff);
	exit(0);		
} 

/*----------------------------------------------------------------------
 *	setterm()
 *	sets initial terminal and signal() defaults
 *	returns: ERROR OR SUCCESS
 *----------------------------------------------------------------------*/
int	setterm()
{
	ioctl(TTYIN, TCGETA, &savterm);		/* save current terminal */
	ioctl(TTYIN, TCGETA, &myterm);		/* working terminal */
	myterm.c_iflag = (IGNPAR | IGNBRK | ISTRIP | IXON | IXOFF);	
	myterm.c_lflag &= ~(ICANON | ECHO);
	myterm.c_cc[VMIN] = 1;
	myterm.c_cc[VTIME] = 0;
	myterm.c_cc[VINTR] = 255;	/* disable Ctrl-D for the editor */
	myterm.c_cc[VQUIT] = 255;	/* disable Ctrl-C .. use <Q>uit */
	if (ioctl(TTYIN, TCSETAW, &myterm) == ERROR) {
		puts("Can't set terminal");
		return(ERROR);
	}
	setsigs();
	return(SUCCESS);
}

/*----------------------------------------------------------------------
 *	setsigs()
 *	set default signals
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	setsigs()
{
	signal(SIGQUIT, quit);		/* exit */
	signal(SIGINT, 	quit);
	signal(SIGHUP, 	quit);	
	signal(SIGTERM, SIG_IGN);
}

/*----------------------------------------------------------------------
 *	append(buff)
 *	write 'buff' to Outfid.
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	append(buff)
char	*buff;
{
	char	spec[3];
		
	if (reqlock() == ERROR)		/* get lock and set lseek to EOF */
		return;
	if (write(Outfid, buff, strlen(buff)) == ERROR)
		puts("\n** File append error");
	write(Outfid, "\n\n", 2);	/* append two newlines */
	unlock();
}

/*----------------------------------------------------------------------
 *	help()
 *	display help
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	help()
{
	int	i;
		
	for (i=0; *menu[i]; i++) {
		if (!strchr(ignore, menu[i][1]))	/* command enabled? */
			printf("%s\n", menu[i]);	/* yes, print it */
	}			
	putchar('\n');
}

/*----------------------------------------------------------------------
 *	chattime(ltime)
 *	returns: pointer to formatted time string for 'ltime'
 *----------------------------------------------------------------------*/
char	*chattime(ltime)
long	*ltime;
{
	struct	tm *TM;
	static	char	tbuff[8];
	int	hour, min, pm;
	
	TM = localtime(ltime);
	hour = TM->tm_hour;
	min = TM->tm_min;
	pm = (hour > 11);
	if (pm)
		hour -= 12;
	if (hour == 0)
		hour = 12;
	sprintf(tbuff, "%d:%02d%s", hour, min, pm ? "pm" : "am");
	return(tbuff);
}

/*----------------------------------------------------------------------
 *	instext( got )
 *	get and insert a line of text into 'buff'
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	instext( got )
char	got;
{
	char	combuf[MAXLINE +1];
	int	i, eol, len, width, bufflen;
	
	bufflen = strlen(buff);
	tail = (char *)(buff + bufflen);
	*tail = '\0';
	width = mywidth -4;
	len = 0;
	strinit(combuf, MAXLINE +1);
	*combuf = got;
	
	/* get text entry */
	while (TRUE) {
		/* check for potential overflow */
		if ((tail - buff) > (MAXCHAT - MAXLINE)) {
			puts("\n**Buffer full**");
			break;
		}
		printf("> ");				/* prompt */
		if (!(len = getline(combuf, width))) {	/* get a line */
			backspace();			/* erase the prompt */
			backspace();
			putchar('\n');
			break;
		}
		eol = len;
		workbuf[0] = '\0';
						
		/* see if line fold needed */
		if (len >= width && combuf[len-1] > ' ') {
			for (; len && combuf[len] != ' '; len--)
				; 
			if (!len)	len = eol;
			strcpy(workbuf, (char *)(combuf+len+1));
		}
		else 	for (; len && combuf[len-1] <= ' '; combuf[len--] = 0)
				;
		combuf[len] = '\n';
		combuf[len+1] = '\0';
		
		/* at this point, trimmed line with terminating '\n' 
		   is in 'combuf' and any overage is in 'workbuf'.
		   'combuf[len]' is pointing to the terminating '\n'
		*/
		strcpy(tail, combuf);		/* copy to buffer */
		tail += (len +1);
		*tail = '\0';
		strinit(combuf, MAXLINE +1);
			
		/* check for line fold on display */
		if (workbuf[0]) {
			for (i=strlen(workbuf); i; i--)	/* erase tail */
				backspace();
			strcpy(combuf, workbuf);
		}
		putchar('\n');
	}
	if ((tail-buff) == bufflen)		/* no text */
		return;
	*(tail -1) = '\0';			/* remove trailing newline */
	reformat(buff, 78);			/* reformat buffer */
	append(buff, FALSE);			/* write to chatfile */
}

/*----------------------------------------------------------------------
 *	getmsg()
 *	grabs a single formatted chat message to 'buff'
 *	returns:  -1  = nothing to read
 *		   0 = message not readable by user
 *		   1 = readable message waiting
 *----------------------------------------------------------------------*/
int	getmsg()
{
	char	*p;
	int	gotesc = FALSE;
	int	rc = FALSE;
	
	buff[0] = '\0';
	Chatpos = ftell(Infid);			/* note file position */

	while (TRUE) {
		if ( fgets(workbuf, MAXLINE, Infid) == NULL) {
			if (gotesc == FALSE)
				rc = ERROR;
			break;
		}
		p = workbuf;
		if (*p == ESC) {		/* beginning of message? */
			if (gotesc) {
				fseek(Infid, Chatpos, 0);	/* reset */
				break;
			}
			gotesc = TRUE;
			p++;			/* offset to message type */
			if (*p == my.channel || *p == ALL) {
				rc = TRUE;
				p += 2;
			}
			else if (*p == PRIVATE &&
			         !strncmp((p+1), my.name, strlen(my.name))) {
			         	rc = TRUE;
					p += strlen(my.name) +1;
			}
		}
		if (rc == TRUE)
			strcat(buff, p);	/* copy to buffer */
		Chatpos = ftell(Infid);
	}
	
	if (rc == TRUE)
		reformat(buff, mywidth -1);	/* format for terminal */
	return( rc );
}

		
/*----------------------------------------------------------------------
 *	readchat()
 *	reads any text present into 'buff' and displays
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	readchat()
{
	long	ltime;
	int	rc;
	char	*p;
	struct	tm *TM;
			
	
	while ((rc = getmsg()) != ERROR) {
		if (rc == TRUE) {
			for (p= buff; *p; p++)
				putchar(*p);
		}
	}
	
	/* Tricky here... check the date to make sure the day hasn't
	 * rolled over.  If so, folks logging on after midnight will be
	 * reading/writing to another chatfile.  If date rollover,
	 * switch to new chat file.
	 */
	time(&ltime);			/* check the day */
	TM = localtime(&ltime);		/* time to switch chatfile? */
	if (TM->tm_mday != Today) {	/* start new chat file */
		puts("\n-- Day rollover: Switching to new chat file");
		puts("-- Some pending messages may be lost");
		close(Outfid);
		fclose(Infid);
		if (openchat() != SUCCESS) {
			puts("\n** Internal error **");
			quit();
		}
		announce(&my, TRUE);
		Chatpos = fseek(Infid, 0L, 2);	/* set end-of-file */
	}
}

/*----------------------------------------------------------------------
 *	getcmd()
 *	gets a command key
 *	waits 1 second for input, then breaks
 *	else, checks for valid command.
 *	returns: ERROR (quit program) or SUCCESS
 *----------------------------------------------------------------------*/
int	getcmd()
{
	char	c, *user;
	long	ltime;
		
	signal(SIGALRM, gotit);			/* set up timeout */
	alarm(1);
	c = getchar() & 0x7f;			/* grab 7-bit character */
	alarm(0);

	if (c == '/') {
		putchar('/');
		c = toupper(getchar() & 0x7F);	/* get command */
		if (c == 'Q' || c == '/') {
			puts("Quit\n");			
			return(ERROR);
		}
		if (strchr(ignore, c)) {	/* blocked command? */
			puts("\nInvalid command\n");
			return(SUCCESS);	/* yes, ignore it */
		}
		/* do command */
		switch (c) {
			case 'J':	/* change chat channel */
			puts("Join channel");
			channel(&my);
			break;
			
			case 'C':	/* set terminal width */
			puts("Columns");
			getwidth();
			break;
		
			case 'W':	/* users in chat */
			puts("Who's On");
			whozon();
			break;
		
			case 'P':	/* User->user private chat message */
			puts("Private");
			if (!(user = finduser()))
				break;
			sprintf(buff, "\033%c%s%c**Private from %s:\n",
			   PRIVATE, user, 1, my.name);
			instext(0);
			break;
		
			case '?':	/* help */
			case 'H':
			puts("Help");
			help();
			break;

			case 'T':	/* timer */
			puts("Time");
			time(&ltime);
			printf("\nTime now:   %s\n", chattime(&ltime));
			printf("Time on:    %ld:%02ld\n", (ltime-my.login) /60,
				(ltime-my.login) %60);
			if (TimesUp > -1) 
				printf("Time left:  %ld:%02ld\n",
				   (TimesUp-ltime) /60, (TimesUp-ltime) %60);
			break;
		
			case 'L':	/* list logged-in users */
			puts("List shell users\n");
			shpager(FALSE);
			break;
		
			case 'S':	/* page logged in user via /dev/? */
			puts("Shell pager\n");
			shpager(TRUE);
			break;
		
			case '!':
			strinit(workbuf, 81);
			printf("sh command: ");
			if (getline(workbuf, 80)) {
				putchar('\n');
				if (chexec(workbuf) == 127)
					puts("\nsh error");
				else {
					printf("\nHit any key. . .");
					getchar();
				}
			}
			putchar('\n');
			break;

			case '\b':
			backspace();
			break;

			default:
			puts("(Invalid command)");
			break;
		}
		return(SUCCESS);
	}
	else if (c == '?')
		help();
	/* get a line of chat text */
	else if (isalnum(c)) {
		sprintf(buff, "\033%c %s:  ", my.channel, my.name);
		instext(c);
	}
	else if (isspace(c)) {
		sprintf(buff, "\033%c %s:  ", my.channel, my.name);
		instext(0);
	}
	return( SUCCESS );
}

/*----------------------------------------------------------------------
 *	gotit()
 *	branches here on timeout above
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	gotit()
{
	alarm(0);
	longjmp(cjmp, 0);
}

/*----------------------------------------------------------------------
 *	chexec(arg)
 *	execute a shell command
 *	returns:  execl status
 *----------------------------------------------------------------------*/
int	chexec(arg)
char	*arg;
{
	int	pid, rc, wrc;

	if ((pid = fork()) == 0) {
		setuid(getuid());
		execl("/bin/sh", "sh", "-c", arg, 0);
		_exit(127);
	}
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	while ((wrc = wait(&rc)) != pid && wrc != -1)
		;
	setsigs();
	return(rc);
}
	
SHAR_EOF
fi # end of overwriting check
if test -f 'getline.c'
then
	echo shar: will not over-write existing file "'getline.c'"
else
cat << \SHAR_EOF > 'getline.c'
#include <stdio.h>
#include <ctype.h>

#define EOL     0
#define FALSE   0
#define TRUE    1
#define BEL     7
#define BS      '\b'
#define TAB     9
#define CR      13
#define LF      10
#define ESC     27
#define SPACE   ' '
#define DEL     127
#define TABLEN  8
#define ONESEC  18L

void	backspace();

/*--------------------------------------------------------------------------
 *  getline( buff, maxlen )
 *
 *  buff:       input buffer
 *  maxlen:     maximum characters
 *-------------------------------------------------------------------------*/
int	getline(buff, maxlen)
char	buff[];
int	maxlen;
{
	int	curs, i;
	char	c;
	
	if (*buff)
		printf("%s", buff);	/* display anything in buffer */
	curs = strlen(buff);		/*  and advance 'curs' */

	while (curs <= maxlen) {
		c = getchar() & 0x7f;
		if (c >= SPACE && c != DEL) {	/* printable character */
			buff[curs++] = c;	/* overwrite/insert character */
			putchar(c);
			continue;
		}

		switch (c) {			/* special character received */

		case CR:			/* done */
		return(strlen(buff));		/* return buffer length */

		case 'H'-64:		/* backspace */
		case DEL:		/* or delete */
		if (!curs)     
			break;		/* beginning of line... ignore */
		if (!buff[curs]) {	/* backspace at end of line */
			backspace();	/* display destructive BS */
			buff[--curs] = '\0';	/* zap the buffer character */
			break;
		}
		putchar(BS);		/* cursor left */
		curs--;			/*.. then fall through to next
                                                   'case'.. */

		case 'G'-64:	/* delete in-line */
		if (buff[curs]) {			/* inside a line? */
			strcpy(buff, buff+1);		/* shift buffer left */
			for (i=curs; buff[i]; i++)	/* display it */
				putchar(buff[i]);
			putchar(SPACE);			/* zap trailing char */
			for (; i >= curs; i--)		/* return cursor */
				putchar(BS);
		}
		break;

		case 'D'-64:	/* cursor right */
		if (buff[curs])			/* if not at right column... */
			putchar(buff[curs++]);	/* move to character-right */
		break;

		case 'B'-64:	/* cursor left */
		if (curs) {			/* if not at left column... */
			putchar(BS);		/* cursor left */
			curs--;			/* mark postion */
		}
		break;

		case 'A'-64:		/* word left */
		if (curs) {			/* if not at left column... */
			do			/* move left through any */
				putchar(BS);	/*  whitespace */
			while (curs && buff[--curs] <= SPACE)
				;
			while (curs && buff[curs-1] > SPACE) {
				putchar(BS);  /* move left through text */
				curs--;
			}      
		}
		break;

		case 'F'-64:	/* word right */
		if (buff[curs]) {		/* if not at right column... */
			while (buff[curs] > SPACE)
				putchar(buff[curs++]);	/* show char-right */
			while (buff[curs] == SPACE)	/* move through SPACEs */
				putchar(buff[curs++]);
		}
		break;

		case 'X'-64:	/* delete line */
		while (buff[curs]) {	/* delete characters-right */
			putchar(SPACE);
			curs++;
		}
		while (curs) {		/* delete to beginning of line */
			backspace();
			buff[curs--] = '\0';
		}
		curs = 0;
		buff[curs] = '\0';
		break;

		case 'R'-64:	/* retype line */
		printf("\n> ");
		for (curs=0; buff[curs]; curs++)	/* retype the buffer */
			putchar(buff[curs]);
		break;

		case TAB:	/* expand tab to SPACEs */
		while (curs < maxlen) {		/* don't expand beyond maxlen */
			buff[curs++] = SPACE;	/* insert a space */
			putchar(SPACE);		/* show it */
			if ( !(curs % TABLEN) ) /* and break if at tabstop */
				break;
		}
		break;
	}
	}
	return (strlen(buff));
}

/*----------------------------------------------------------------------
 *	backspace()
 *	prints destructive backspace
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	backspace()
{
	putchar('\b');
	putchar(' ');
	putchar('\b');
}
SHAR_EOF
fi # end of overwriting check
if test -f 'lockchat.c'
then
	echo shar: will not over-write existing file "'lockchat.c'"
else
cat << \SHAR_EOF > 'lockchat.c'
/*	lockchat.c	-- record locking functions for Magpie chat */

#include "chat.h"
#include <sys/locking.h>

#define	MAXTRY	10

/*----------------------------------------------------------------------
 *	lockf(fid, func, size)
 *
 *	Replacement for SVR2 lockf(), which doesn't work correctly
 *	in some versions of Unix and in Xenix.
 *----------------------------------------------------------------------*/
int	lockf(fid, func, size) 
int 	fid, func; 
long	size;
{ 
       switch (func) { 
               case F_ULOCK:   return( locking(fid, LK_UNLCK, size) ); 
               case F_LOCK:    return( locking(fid, LK_LOCK,  size) ); 
               case F_TLOCK:   return( locking(fid, LK_NBLCK, size) ); 
               default:        return(ERROR); 
       } 
} 

/*----------------------------------------------------------------------
 *	reqlock()
 *	locks chat file for end-of-file write
 *	returns: ERROR or SUCCESS
 *----------------------------------------------------------------------*/
int	reqlock()
{
	long	fpos;
	int	tries;
	
	fpos = lseek(Outfid, 0L, 2);	/* mark current file pos */
	lseek(Outfid, 0L, 0);		/* move to BOF and lock */
	signal(SIGHUP,	unlock);	/* if hang up, assure unlock */
	
	Lockflag = TRUE;
	tries = 0;
	while (lockf(Outfid, F_TLOCK, 0L) == ERROR) { 
		if (tries++ == MAXTRY) {
			puts("Can't acquire file lock");
			Lockflag = FALSE;
			break;
		}
		sleep(2);
		puts("Awaiting lock");
	}
	lseek(Outfid, fpos, 0);		/* restore file pos */
	setsigs();
	return( (Lockflag == TRUE) ? SUCCESS : ERROR);
}

/*----------------------------------------------------------------------
 *	unlock()
 *	unlock chatfile
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	unlock()
{
	long	fpos;
	
	if (Lockflag == TRUE) {
		fpos = lseek(Outfid, 0L, 2);	/* save current file pos */
		lseek(Outfid, 0L, 0);		/* set to BOF for unlock */
		signal(SIGHUP,	SIG_IGN);
		lockf(Outfid, F_ULOCK, 0L);
		lseek(Outfid, fpos, 0);		/* restore file pos */
		Lockflag = FALSE;
	}
	setsigs();
}

SHAR_EOF
fi # end of overwriting check
if test -f 'misc.c'
then
	echo shar: will not over-write existing file "'misc.c'"
else
cat << \SHAR_EOF > 'misc.c'
/* misc.c	-- miscellaneous routines for Magpie 'chat' */

#include "chat.h"
#include <utmp.h>

static	int WhoFid = 0;

/*----------------------------------------------------------------------
 *	finduser()
 *	asks for username and checks WHO for match
 *	returns: username or NULL
 *----------------------------------------------------------------------*/
char	*finduser()
{
	int	fid;
	WHOREC	who;
	
	printf("\nPrivate message to: ");	/* get username */
	strinit(workbuf, MAXNAME +1);		/* for getline() */
	if (!getline(workbuf, MAXNAME))	{
		putchar('\n');
		return( NULL );
	}
	putchar('\n');
	strupr(workbuf);			/* name -> uppercase */
	
	if ((fid = open( fpath(Chatpath, WHOFILE), O_RDONLY)) == ERROR)
		return( (char *)0 );
	while (read(fid, &who, sizeof(WHOREC)) == sizeof(WHOREC)) {
		if (!strcmp(who.name, workbuf))
			return(workbuf);
	}
	puts("\nUser not in Chat");
	return( NULL );
}

/*----------------------------------------------------------------------
 *	strupr(s)
 *	converts string to uppercase
 *	returns: pointer to string
 *----------------------------------------------------------------------*/
char	*strupr(s)
char	*s;
{
	char	*p;
	
	for (p=s; *p; p++)
		*p = toupper(*p);
	return( s );
}

/*----------------------------------------------------------------------
 *	strinit(s, len)
 *	null out a string, s', to length, 'len'.
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	strinit(s, len)
char	*s;
int	len;
{
	memset(s, '\0', len);
}

/*----------------------------------------------------------------------
 *	getwidth()
 *	sets user's terminal width
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	getwidth()
{
	int	n;
	
	printf("\nNew terminal width: ");
	strinit(workbuf, 5);
	if (getline(workbuf, 4)) {
		n = atoi(workbuf);
		if (n >= 32 && n <= 132)
			mywidth = n;
	}
	putchar('\n');
}

/*----------------------------------------------------------------------
 *	reformat(buff, winsize)
 *	reformats 'buff' for user's window width
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	reformat(buff, width)
char	*buff;
int	width;
{       
	extern  char *tail;
	char  	*c, *lastbrk, *lastspace;

	c = lastspace = lastbrk = buff;
	while (*c == '\n')		/* preserve leading newlines */
		c++;
	while (*c) {
		if (*c == '\n') {	/* check for newline */
			if (*(c+1)=='\n' || *(c+1)=='.' || *(c-1)=='\n'||
			    *(c+1)==' ') {
				lastbrk = c++;
				continue;
			}
			*c = ' ';
		}
		if (*c == ' ')		/* check for line break */
			lastspace = c;
					/* check for wrap... */
					/* if no break space, chop up line */
		if (c >= (lastbrk + width -1)) {    
			if (lastspace <= lastbrk)  
				lastspace = c;
			*lastspace = '\n';	/* add a newline */
			lastbrk = ++lastspace;
			c++;
			continue;
		}
		c++;
	}
	tail = c;				/* reset end-of-buffer addr */
}

/*----------------------------------------------------------------------
 *	getwhoent()
 *	returns next entry in MagChat 'cwho' file
 *	returns: WHOREC *
 *----------------------------------------------------------------------*/
WHOREC	*getwhoent()
{
	static	WHOREC	who;

	if (WhoFid == 0) {			/* open if not */
		if ((WhoFid = open( fpath(Chatpath, WHOFILE), O_RDWR)) == ERROR) {
			WhoFid = 0;
			return( (WHOREC *) 0 );
		}
	}
	if (read(WhoFid, &who, sizeof(WHOREC)) == sizeof(WHOREC))
		return( &who );

	endwhoent();
	return( (WHOREC *) 0 );
}

/*----------------------------------------------------------------------
 *	endwhoent()
 *	close WHO file
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	endwhoent()
{
	if (WhoFid > 0) {
		close(WhoFid);			/* end of file */
		WhoFid = 0;			/* close and exit */
	}
}

/*----------------------------------------------------------------------
 *	addwho(rec)
 *	add CWHO record
 *	file is self-maintaining
 *	returns: ERROR or SUCCESS
 *----------------------------------------------------------------------*/
int	addwho(rec)
WHOREC	*rec;
{
	WHOREC	who;
	long	offset;
	int	fid, rc;
	
	if ((fid = open( fpath(Chatpath, WHOFILE), O_RDWR | O_CREAT,
	     0x1b4)) == ERROR) {
		perror(workbuf);
		return( ERROR );
	}

	/* find a deleted WHOREC slot and re-use it, else append new record */
	offset = 0;
	while (read(fid, &who, sizeof(WHOREC)) == sizeof(WHOREC)) {	
		if (who.name[0] == 0)
			break;
		offset += (long)sizeof(WHOREC);
	}
	lseek(fid, offset, 0);
	rc = write(fid, rec, sizeof(WHOREC));
	close(fid);
	if (rc == ERROR) {
		printf("\nError adding name to %s", WHOFILE);
		return( ERROR );
	}
	return( SUCCESS );
}

/*----------------------------------------------------------------------
 *	killwho(rec)
 *	remove CWHO record
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	killwho(rec)
WHOREC	*rec;
{
	WHOREC	*who;
	long	offset = 0;

	endwhoent();				/* assure Whofile closed */
	while ((who = getwhoent()) > 0) {
		if (who->name[0] &&			/* active record? */
		    !strcmp(who->tty, rec->tty)) {	/* our tty? */
			strinit(who, sizeof(WHOREC));	/* null it out */
			lseek(WhoFid, offset, 0);	/* rewind top of rec */
			write(WhoFid, who, sizeof(WHOREC));	/* rewrite */
			break;
		}
		offset += (long)sizeof(WHOREC);
	}
	endwhoent();
}

/*----------------------------------------------------------------------
 *	whozon()
 *	display CWHO file
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	whozon()
{
	int	users = 0;
	WHOREC	*who;

	putchar('\n');
	while ((who = getwhoent()) > 0) {	
		if (who->name[0]) {
			printf("Channel %c:  %s on %s at %s\n", who->channel,
			   who->name, who->tty, chattime(&who->login));
			users++;
		}
	}
	if (!users)
		puts("[Nobody in Chat]");
	putchar('\n');
}

/*----------------------------------------------------------------------
 *	channel(rec)
 *	change Chat channel
 *	B-Z channels may be secured private with a pswd if not already
 *	secured.  If secured, ask for a pswd.  If not, allow one to be
 *	set.
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	channel(rec)
WHOREC	*rec;
{
	char	newchan;
	WHOREC	*who;
	int	occupied = 0;
	
	printf("\nYou are currently on Channel %c", my.channel);
	printf("\nEnter new Chat channel (A-Z) or RETURN to abort: ");
	newchan = getchar() & 0x7f;
	newchan = toupper(newchan);
	printf("%c\n", newchan);

	if (newchan == '\r' || newchan == my.channel)
		return;
	if ( !isalpha(newchan) ) {
		printf("\nInvalid channel '%c'\n", newchan);
		return;
	}

	if (newchan == 'A') {			/* join public channel */
		join(rec, newchan, FALSE);	/* with no password */
		return;
	}
	endwhoent();			/* assure Whofile closed */

	/* check to see if we're the first to use this channel */
	while ((who = getwhoent()) > 0) {
		if (who->channel == newchan) {		/* someone here */
			occupied++;
			if (strlen(who->pswd) > 0) {	/* and it's got a pwd */
				printf("\nChannel %c requires password\n",
				   who->channel);
				printf("Enter password: ");
				strinit(workbuf, MAXPWD +1);
				if (getline(workbuf, MAXPWD) &&
			    	   !strcmp(workbuf, who->pswd)) {
					endwhoent();
					join(rec, newchan, FALSE);
					return;
				}
				else {			/* password failed */
					puts("\nSorry!");
					endutent();
					return;
				}
			}
		}
	}
	endutent();
	
	/* if channel unoccupied, join it as emperor */
	if (!occupied)
		join(rec, newchan, TRUE);
}

/*----------------------------------------------------------------------
 *	join(rec, chan, getpswd)
 *	Switch to new Chat channel 'chan'.
 *	If 'getpswd', allow user to set one for channel
 *	returns: FALSE
 *----------------------------------------------------------------------*/
void	join(rec, chan, getpswd)
WHOREC	*rec;
char	chan;
int	getpswd;
{
	WHOREC	*who;
	long	offset = 0;

	printf("\n\nJoining Channel %c\n", chan);

	/* find me in the Whofile and change to new channel */
	while ((who = getwhoent()) > 0) {
		if (who->name[0] && !strcmp(who->tty, rec->tty)) {
			if (getpswd) {
				puts("\nYou may set a password for this channel");
				printf("Enter password or RETURN for none: ");
				strinit(workbuf, MAXPWD +1);
				if (getline(workbuf, MAXPWD))
					strcpy(who->pswd, workbuf);
				else	who->pswd[0] = '\0';
				putchar('\n');
			}
			who->channel = chan;
			lseek(WhoFid, offset, 0);	/* rewind top of rec */
			write(WhoFid, who, sizeof(WHOREC));	/* rewrite */
			announce(rec, FALSE);		/* logout */
			rec->channel = chan;
			announce(rec, TRUE);		/* login */
			break;
		}
		offset += (long)sizeof(WHOREC);
	}
	endwhoent();
}

/*----------------------------------------------------------------------
 *	announce(rec, login)
 *	announce login or logout to chat channel
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	announce(rec, login)
WHOREC	*rec;		/* user record */
int	login;		/* entering chat channel */
{
	sprintf(workbuf, "\033%c %s ==> %s at %s", rec->channel,
	   login ? "In" : "Out", rec->name, chattime(&rec->login));
	append(workbuf, TRUE);
}

/*----------------------------------------------------------------------
 *	shpager(call)
 *	shows system on-line
 *	if 'call', send chat summons
 *	returns: nothing
 *----------------------------------------------------------------------*/
void	shpager(call)
int	call;
{
	struct	utmp UT;
	char	dev[20][12];		/* device address */
	int	fid, i=0, n;
	FILE	*fp;
		
	if ((fid = open(UTMPFILE, O_RDONLY)) == ERROR) {
		printf("\nCan't open %s", UTMPFILE);
		return;
	}

	while (i < 20 && read(fid, &UT, sizeof(UT)) == sizeof(UT)) {
		if (UT.ut_line[0] && UT.ut_name[0] &&
		    strncmp(UT.ut_name, "LOGIN", 5)) {
		    	strcpy(dev[i], UT.ut_line);	/* copy device */
		    	printf("#%-4d%-8s %-12s %s\n", i, UT.ut_name, UT.ut_line,
		    	   chattime(&UT.ut_time));
			i++;
		}
	}
	putchar('\n');
	close(fid);
	if (!call)			/* send chat request? */
		return;			/* nope, exit */
		
	/* send a message via /dev */
	strinit(workbuf, 5);
	printf("\nSend chat page to # ");
	if (!getline(workbuf, 4)) {	/* get user */
		putchar('\n');
		return;
	}
	if ((n = atoi(workbuf)) > i)	/* out of range */
		return;

	sprintf(workbuf, "/dev/%s", dev[n]);
	if (!(fp = fopen(workbuf, "w"))) {
		puts("\nCan't open device");
		return;
	}
	fprintf(fp, "\07\n\n** Chat Request from %s on %s", my.name, my.tty);
	fprintf(fp, "\n** From shell:  type 'chat'");
	fprintf(fp, "\n** From Magpie: type 'OC'\n\07");
	fclose(fp);
	puts("\nSummons sent");
}

/*----------------------------------------------------------------------
 *	fpath(path, name)
 *	returns a full filename for path/name
 *----------------------------------------------------------------------*/
char	*fpath(path, name)
char	*path, *name;
{
	static	char myfile[MAXPATH + MAXNAME +1];
	
	if (path[ strlen(path) -1 ] != '/')
		sprintf(myfile, "%s/%s", path, name);
	else	sprintf(myfile, "%s%s", path, name);
	return( myfile );
}
SHAR_EOF
fi # end of overwriting check
if test -f 'chat.h'
then
	echo shar: will not over-write existing file "'chat.h'"
else
cat << \SHAR_EOF > 'chat.h'
/* chat.h */

#include <stdio.h>		
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>
#include <termio.h>
#include <errno.h>

#undef	toupper		/* use the function version */

#define	XENIX		/* this one's for Xenix */

/* path to chatfile (overridable with argument -d) */
#define	CHATPATH	"/u/local/chatfiles"

/* file prefix for chat text file */
#define	CHATFILE	"chat"

/* filename for chat user file */
#define WHOFILE		"CWHO"

/* filename for 'chat-closed' file */
#define NOCHATFILE	"Closed"

#define	UTMPFILE	"/etc/utmp"

#define	MAXPATH		60		/* maximum path length */
#define MAXNAME		25		/* maximum username length */
#define MAXTTYNAME	15		/* maximum ttyname string length */
#define MAXCHAT		2000		/* buffer size for chat message */
#define	MAXPWD		8		/* password length */
#define BUFSIZE		(MAXCHAT *2)	/* maximum output buffer size */
#define MAXLINE		132		/* maximum chat line length */

#define	TRUE		1
#define FALSE		0
#define SUCCESS		0
#define ERROR		-1

#define TTYIN		1		/* terminal handle */
#define	ESC		'\033'

/*----------------------------------------------------------------------
 *	Special chat message types
 *----------------------------------------------------------------------*/
#define	ALL		'0'			/* message to ALL */
#define PRIVATE		'1'			/* private message */

/*----------------------------------------------------------------------
 *	WHO structure
 *----------------------------------------------------------------------*/
typedef	struct {
	char		name[MAXNAME +1];	/* user's name */
	char		tty[MAXTTYNAME +1];	/* device name */
	long		login;			/* login time */
	char		channel;		/* chat channel */
	char		pswd[MAXPWD +1];	/* password for channel */
} WHOREC;

/*----------------------------------------------------------------------
 *	strchr/index and 'void' dependencies
 *----------------------------------------------------------------------*/
#ifdef INDEX
#define	strchr	index
#endif

#ifdef NOVOID
#define	void	int
#endif

/*----------------------------------------------------------------------
 *	extern
 *----------------------------------------------------------------------*/
char	*ttyname(), *getlogin(), *chattime(), *strupr(), *finduser(),
	*fpath();
long	atol();
void	quit(), unlock(), setsigs(), append(), timeout(), help(), 
	whozon(), killwho(), instext(), gotit(), strinit(), signin(),
	endwhoent(), channel(), join(), announce(), readchat();
WHOREC	*getwhoent();

extern	char	workbuf[], *buff, *tail, Chatpath[];
extern	int	mywidth, Outfid, Lockflag, errno;
extern	long	TimesUp, Chatpos;
extern	WHOREC	my;
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
John W. Temples -- john@jwt.UUCP (uunet!jwt!john)