[mod.sources] v06i011: A "talk" for System V

sources-request@mirror.UUCP (06/20/86)

Submitted by: cbosgd!ukma!ukecc!edward (Edward C. Bennett)
Mod.sources: Volume 6, Issue 11
Archive-name: sysVtalkB

[ This is the second of two different talk programs.
  The manpage has some lines that start with periods;
  let me know if this causes problems for anyone, and I
  will fix this in future postings.  --r$]

This a version of the Berkeley talk program for System V. Talk allows
conversing parties to type simultaineously while maintaining a neat,
readable screen. It uses the 'messages' Interprocess Communication
Facility of System V to pass data.

#! /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:
:	'README'
:	'talk.1'
:	'Makefile'
:	'talk.h'
:	'talk.c'
:	'talkd.c'
:	'infotalk.c'
:	'stoptalk.c'
: This archive created: 'Fri Jun 13 14:04:24 1986'
: By:	'Edward C. Bennett'
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(2386 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat  >'README' <<\SHAR_EOF
Installing and maintaining 'talk'

	These instructions are intended to be fairly generic. They were
written for a 3B20. Your milage may vary.

	The talk system is composed of three executable pieces.

	talk		the user interface
	talkdemon	the background process that supervises conversations
	stoptalk	a program for removing the talk msgqueue

	(Yes, I know it's usally spelled 'daemon'. But since my 3B20 has
an 'errdemon' I figured System V wanted to be different. C'est la vie.)

	First, read the 'Makefile' and make any nessecary modifications.
You might want to also change 'talk.h'. Running 'make install' will create
and install the three binaries for you. /usr/bin/talk must run setuid.
This is so that independent 'talk' process can exchange signals. Talkdemon
and stoptalk should have 'other' permissions turned off to prevent regular
users from tampering with them. The owner and group ids of the files are
not important so you can set them as you like.

	After installing the binaries, provisions for automatic startup
and shutdown of the talkdemon should be made. In your startup script
(/etc/rc on my 3B20), at the point where /etc/cron is started, insert
these lines:

	echo talkdemon started
	/etc/talkdemon

and in your shutdown script (/etc/shutdown on my 3B20), before all user
processes are killed, insert these lines:

	echo Talk msgqueue being removed
	/etc/stoptalk

The entry in shutdown is needed because taking the system down to single
user mode doesn't remove msgqueues and because /etc/shutdown kills
user processes with a '-9' which prevents the talkdemon from removing
it's own msgqueue.

	The 'infotalk' program provides status on the state of the demon.
It was initally used for debugging purposes and has been included for
completeness.

Known problems with talk

1)	My version of System V has no high-precision sleep call, so the
main loop of 'talk' runs as fast as the machine allows. I don't think
this leads to any problems other than high CPU usage. If your system
has a fractional-second sleep call, I suggest you use it to reduce talk's
CPU time consumption. Let me know how it goes.

2)	The version of `curses` on my machine has a bug which causes
the cursor to jump to and from the beginning of the current line when
a character is added to the screen. This is not harmful, just annoying.  

Edward C. Bennett
	ihnp4!cbosgd!ukma!ukecc!edward
SHAR_EOF
fi
echo shar: extracting "'talk.1'" '(1217 characters)'
if test -f 'talk.1'
then
	echo shar: will not over-write existing file "'talk.1'"
else
cat  >'talk.1' <<\SHAR_EOF
.TH TALK 1
.SH NAME
talk \- inter-terminal screen-oriented communication program
.SH SYNOPSIS
.B talk user
[
.B tty
]
.SH DESCRIPTION
.I Talk
is a inter-terminal conversation program designed
to allow both parties to type at the same time
and still maintain a readable display.
The
.IR curses (3X)
screen-management library is used to handle the
display so you must set your TERM environment variable.
.PP
When you run
.I talk
you give the login name of who you want to converse with.
If they are logged in more than once,
you must also specify which tty of theirs you wish to talk to.
.I Talk
will inform them that you wish to communicate
with them and wait for their response.
If they are slow,
.I talk
will resend its message every 30 seconds until either
they respond or you get bored and hit INTERRUPT.
.PP
A control-L will force the screen to be
redrawn in case it becomes garbled.
.PP
Although the program emulates to Berkeley program of the
same name, the code is 100% original.
.SH AUTHOR
Edward C. Bennett
.SH DIAGNOSTICS
Hopefully self-explanatory.
.SH BUGS
Because of an apparent bug in the SystemV curses package,
if one person backspaces while the other is typing,
text is lost off at least one screen.
SHAR_EOF
fi
echo shar: extracting "'Makefile'" '(1416 characters)'
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat  >'Makefile' <<\SHAR_EOF
#
# Makefile for talk, stoptalk, and the talkdemon
#
# AUTHOR
#	Edward C. Bennett (edward@ukecc)
#
# Copyright 1985 by Edward C. Bennett
#
# Permission is given to alter this code as needed to adapt it to forign
# systems provided that this header is included and that the original
# author's name is preserved.
#

BIN=/usr/bin
DEMONDIR=/etc
#
# /usr/bin/talk needs to run setuid. It doesn't have to be root so make
# this something harmless.
#
OWNER=edward

#
# Use whatever libraries you need to make curses work.
#
LIBS=-lcurses # -lterminfo -ltermcap -ltermlib

all: talk talkdemon infotalk stoptalk

talk: talk.o talk.c
	cc -s talk.o -o talk ${LIBS}

tester: ntalk.c
	cc -DSCHIZO ntalk.c -o tester ${LIBS}

talkdemon: talkd.o talkd.c
	 cc -s talkd.o -o talkdemon

infotalk: infotalk.o infotalk.c
	 cc -s infotalk.o -o infotalk

stoptalk: stoptalk.o stoptalk.c
	 cc -s stoptalk.o -o stoptalk

talk.o: talk.h
	cc -c -O talk.c

talkd.o: talk.h
	cc -c -O talkd.c

infotalk.o: talk.h
	cc -c -O infotalk.c

stoptalk.o: talk.h
	cc -c -O stoptalk.c

install: all
	/etc/install -f ${BIN} talk
	/etc/install -f ${DEMONDIR} talkdemon
	/etc/install -f ${DEMONDIR} stoptalk
	chown ${OWNER} ${BIN}/talk
	chmod 4755 ${BIN}/talk
	chmod 750 ${DEMONDIR}/talkdemon ${DEMONDIR}/stoptalk

clean:
	rm -f *.o talk talkd talkdemon stoptalk

shar:
	shar -v README talk.1 Makefile talk.h talk.c talkd.c infotalk.c stoptalk.c > talk.shar
SHAR_EOF
fi
echo shar: extracting "'talk.h'" '(2629 characters)'
if test -f 'talk.h'
then
	echo shar: will not over-write existing file "'talk.h'"
else
cat  >'talk.h' <<\SHAR_EOF
/*
 * Definitions for talk and the talkdemon
 *
 * AUTHOR
 *	Edward C. Bennett (edward@ukecc)
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <utmp.h>
#include <string.h>

/*
 * If your system has a high resolutiuon sleep call, define SLEEP to
 * be the name of the function and define SLEEP_TIME to be whatever number
 * gives you about a .1 second delay. The defines are used like this:
 *	SLEEP(SLEEP_TIME);
 */
/* #define	SLEEP	sleep		/* Name of high resolution sleep call */
/* #define	SLEEP_TIME	100	/* Number to give ~.1 second delay */

/*
 * If your system has the old utmp file format, i.e. without USER_PROCESS,
 * define OLDUTMP
 */
/* #define	OLDUTMP		/* If you use the old utmp format */

/*
 * If you have a version of curses that is more Berkeley than System V,
 * you'll probably need at least one of these.
 */
/* #define	cbreak	crmode		/* to turn off canonical input */

/* #define	CLRONEXIT	/* turn on if you want the screen cleared on exit */
/*
 * Defines for using msgget()
 * If for some reason the msgkey that talk generates is already used on
 * your system, change MAGIC_ID to another character.
 */
#define	TALK_PATH	"/usr/bin/talk"
#define	MAGIC_ID	'E' 		/* This can be any char. Be creative. */
#define	MSGQ_PERMS	0666		/* You may want to change these */
					/* to increase security */

#define	NAMESIZ	8	/* from <utmp.h> */
#define	LINESIZ	12	/* from <utmp.h> */
#define	TTYLOC	16	/* ttys always start in this location in 'mtext' */
#define	SEND	0	/* Locations in the 'lines' array for these msgtypes */
#define	RECEIVE	1
#define	CTL	2
#define	PIDLOC	7	/* pids always ride in this spot in the 'lines' array */
#define	STATUS	3	/* mtype for status messages */

/*
 * Stuff for the Deamon MeSsaGes
 * All message involving the demon use this structure
 *
 * Here's how the buffer is laid out:
 *					1			2
 *	0		8		6			8
 *     | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
 *     |  name         |  name         | tty line              | pid
 *     | send  |receive| ctl   |       |       |       |       | pid   |
 */
typedef	struct	{
	long	mtype;
	union	{
		char	mtext[32];
		long	lines[8];
	} msgval;
} DMSG;

DMSG	dmsgbuf;
#define	MSGSIZ	sizeof(dmsgbuf.msgval.mtext)

/*
 * 'Find' return codes
 */
#define	TALKABLE	 1
#define	NOTLOGGEDON	-1
#define	NOTWRITE	-2
#define	LOGGEDMORE	-3
#define	NOTONLINE	-4
SHAR_EOF
fi
echo shar: extracting "'talk.c'" '(9418 characters)'
if test -f 'talk.c'
then
	echo shar: will not over-write existing file "'talk.c'"
else
cat  >'talk.c' <<\SHAR_EOF
/*
 * talk - a two-way, screen-oriented communication program
 *
 *	Talk, which emulates the Berkeley program of the same name, allows
 * both parties in a conversation to type at will, without fear of garbling
 * each others messages. Input from either user is immediatly written to both
 * screens so that there is no waiting like there is with write(1).
 *
 *	Although this program resembles, to the user, the Berkeley program
 * 'talk', the code is entirely original by the author.
 *
 * AUTHOR
 *	Edward C. Bennett (edward@ukecc)
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <curses.h>
#include <signal.h>
#include <fcntl.h>
#include "talk.h"

struct	msgbuf	sndbuf, rcvbuf;

int	msqid;
int	avail;		/* The availabity of a perspective partner */
int	parpid = 0;	/* The pid of the partner */
char	*ctime(), *getlogin();
time_t	tloc, otime, time();

main(argc, argv)
int argc;
char *argv[];
{
	key_t		msgkey, ftok();
	long		fromtype;	/* The msgtype where we look for characters */
					/* from the partner */
#ifdef FIONREAD
	long		waiting;
#endif
	void		exit();
	int		c, Finish();
	int		MIDDLE;
	short		orgy = 0, orgx = 0;	/* That's ORGinator's Y you pervert */
	short		resy, resx = 0;


	msgkey = ftok(TALK_PATH, MAGIC_ID);

	if ((msqid = msgget(msgkey, MSGQ_PERMS)) == -1) {
		fprintf(stderr, "%s: Nonexistant talk msgqueue\n", argv[0]);
		exit(1);
	}

#ifndef SCHIZO
	if (!isatty(0)) {
		fprintf(stderr, "%s: must have a terminal for input\n", argv[0]);
		exit(1);
	}
#endif

#ifndef SCHIZO
	if (argc == 1) {
		fprintf(stderr, "%s: No user specified\n", argv[0]);
		exit(1);
	}

	/*
	 * We need to tell the demon who we are, who we want to talk to,
	 * the tty of who we want (if it was given) and our process id.
	 */
	strncpy(dmsgbuf.msgval.mtext, getlogin(), NAMESIZ);
	strncpy(&dmsgbuf.msgval.mtext[NAMESIZ], argv[1], NAMESIZ);
	if (argv[2])
		strncpy(&dmsgbuf.msgval.mtext[TTYLOC], argv[2], LINESIZ);
	dmsgbuf.msgval.lines[PIDLOC] = getpid();
	dmsgbuf.mtype = 1;

	/*
	 * Tell the demon we are here.
	 */
	if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
		fprintf(stderr, "%s: msgsnd failure(%d)\n", argv[0], errno);
		exit(1);
	}

	/*
	 * Wait for a response from the demon.
	 */
	if (msgrcv(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, (long)2, 0) == -1) {
		fprintf(stderr, "%s: msgrcv failure(%d)\n", argv[0], errno);
		exit(1);
	}

	avail = dmsgbuf.msgval.lines[0];
	/*
	 * A negative availability is untalkable.
	 */
	if (avail < 0) {
		Error(avail, argv);
		exit(1);
	}

	sndbuf.mtype = dmsgbuf.msgval.lines[SEND];
#endif

	/*
	 * Now that it's OK to talk, go ahead and
	 * initialize the terminal and screen
	 */
	if (initscr() == (WINDOW *)NULL) {
		fprintf(stderr, "%s: TERM variable not set\n", argv[0]);
		Finish(SIGINT);
	}

	/*
	 * Since curses in now ineffect, we need to watch for
	 * ALL signals so that we can properly terminate
	 */
	for (c = 0; c < NSIG; c++)
		signal(c, Finish);

	cbreak();
	noecho();
#ifndef SCHIZO
#ifndef FIONREAD
	nodelay(stdscr, TRUE);
#endif
#endif
	MIDDLE = LINES / 2 - 1;
	time(&tloc);
	mvprintw(MIDDLE, 0, "--------------------------------------------------------------- %12.12s --", ctime(&tloc) + 4);
	move(orgy, orgx);
	refresh();

	fromtype = dmsgbuf.msgval.lines[RECEIVE];

#ifndef SCHIZO
	/*
	 * If we are given a ctl type, wait for a ctl message.
	 */
	if (dmsgbuf.msgval.lines[CTL] > 0) {
		mvprintw(0, 0, "[Waiting for %s to answer]\n", argv[1]);
		refresh();
		Ring();
		while (msgrcv(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, dmsgbuf.msgval.lines[2], 0) == -1) {
			/*
			 * The 'talkee' may have turned off his messages
			 */
			if (avail < 0) {
				mvprintw(orgy++, orgx, "[%s no longer writable]\n", argv[1]);
				refresh();
			}
			/*
			 * EINTR is an interrupted system call
			 */
			else if (errno == EINTR) {
				mvprintw(orgy++, orgx, "[Ringing %s again]\n", argv[1]);
				refresh();
			}
			else {
				fprintf(stderr, "%s: msgrcv failure(%d)\n", argv[0], errno);
				Finish(SIGINT);
			}
		}
		if (orgy) {
			while (orgy > 1) {
				move(--orgy, orgx);
				clrtoeol();
			}
		} else
			orgy = 1;

		alarm(0);	/* Turn off the ringer */
		beep();
		mvaddstr(0, 0, "[Connection established]\n");
		refresh();
	}
	parpid = dmsgbuf.msgval.lines[PIDLOC];
#endif

	/*
	 * This loop just continually checks both users for input.
	 * It actually runs TOO FAST, but since SysV has no fractional
	 * second sleep we do the best we can.
	 */
	resy = MIDDLE + 1;
	for (;;) {
		/*
		 * This is the owner's half
		 */
#ifdef FIONREAD
		ioctl(0, FIONREAD, &waiting);
		if (waiting) {
			c = getch();
#else
		if ((c = getch()) != EOF) {
#endif
			if (c == erasechar()) {
				c = '\b';
				if (--orgx < 0)
					orgx = 0;
				else
					mvaddstr(orgy, orgx, " \b");
			}
			else if (c == '\004')
				Finish(SIGINT);	
			else if (c == '\f') {
				clearok(curscr, TRUE);
				refresh();
				continue;
			}
			else if (c == '\n') {
				orgy = ++orgy % MIDDLE;
				orgx = 0;
				move(orgy, orgx);
				clrtoeol();
				move((orgy + 1) % MIDDLE, orgx);
				clrtoeol();
				move(orgy, orgx);
			}
			/*
			 * Regular characters
			 */
			else {
				/*
				 * Check for wrap around
				 */
				if (orgx >= 79) {
					orgy = ++orgy % MIDDLE;
					orgx = 0;
					move(orgy, orgx);
					clrtoeol();
					move((orgy + 1) % MIDDLE, orgx);
					clrtoeol();
				}
				mvaddch(orgy, orgx++, c);
			}

			refresh();
#ifndef SCHIZO
			*sndbuf.mtext = c;
			if (msgsnd(msqid, (struct msgbuf *)&sndbuf, 1, 0) == -1) {
				fprintf(stderr, "%s: msgsnd failure(%d)\n", argv[0], errno);
				Finish(SIGINT);
			}
#endif
		}

#ifndef SCHIZO
		/*
		 * This is the partner's half
		 */
		if (msgrcv(msqid, (struct msgbuf *)&rcvbuf, 1, fromtype, IPC_NOWAIT) != -1) {
			switch (*rcvbuf.mtext) {
			case '\b':
				if (--resx < 0)
					resx = 0;
				else
					mvaddstr(resy, resx, " \b");
				break;

			case '\n':
				if (++resy >= LINES - 1)
					resy = MIDDLE + 1;
				resx = 0;
				mvaddch(resy, resx, '\n');
				mvaddch(((resy - MIDDLE) % (LINES - MIDDLE - 2)) + MIDDLE + 1, resx, '\n');
				move(resy, resx);
				break;

			default:
				/*
				 * Check for wrap around
				 */
				if (resx >= 79) {
					if (++resy >= LINES - 1)
						resy = MIDDLE + 1;
					resx = 0;
					move(resy, resx);
					clrtoeol();
					move(((resy - MIDDLE) % (LINES - MIDDLE - 2)) + MIDDLE + 1, resx);
					clrtoeol();
					move(resy, resx);
				}
				mvaddch(resy, resx++, *rcvbuf.mtext);
				break;
			}

			refresh();
		}
#endif
		/*
		 * Update the time
		 */
		time(&tloc);
		if (tloc >= otime + 60) {
			otime = tloc;
			mvprintw(MIDDLE, COLS - 16, "%12.12s", ctime(&tloc) + 4);
			refresh();
		}
#ifdef SLEEP
		SLEEP(SLEEP_TIME);
#endif
	}
}

/*
 * Ring - ring the perspective partner
 *
 * Ring();
 *
 * A request message is sent to the partner's terminal.
 */
Ring()
{
	FILE		*fp, *fopen();
	char		*ttyname();
	int		Ring();

	tloc = time((time_t *)0);
	if ((fp = fopen(&dmsgbuf.msgval.mtext[TTYLOC], "w")) != NULL) {
		fprintf(fp, "\r\n%c%cTalk request from %s on %s at %5.5s\r\n",
			'\007', '\011', getlogin(), ttyname(0)+5, ctime(&tloc)+11);
		fprintf(fp, "%cRespond with 'talk %s'\r\n", '\007', getlogin());
		fclose(fp);
	}
	/*
	 * If the person being rung turns of his messages, set avail to
	 * indicate his new unavailablity
	 */
	else 
		avail = -1;

	signal(SIGALRM, Ring);
	alarm((unsigned)20);
}

/*
 * Error - print an error message
 *
 * Error(code);
 *	code	is the availability of who we want to talk to
 *
 * A message regarding why we can't talk is printed.
 */
Error(code, argv)
int code;
char **argv;
{
	switch (code) {

	case NOTLOGGEDON:
		fprintf(stderr, "%s: %s not logged on\n", argv[0], argv[1]);
		break;

	case NOTWRITE:
		fprintf(stderr, "%s: %s not writeable\n", argv[0], argv[1]);
		break;

	case LOGGEDMORE:
		fprintf(stderr, "%s: %s logged on more than once\n", argv[0], argv[1]);
		break;

	case NOTONLINE:
		fprintf(stderr, "%s: %s not on line %s\n", argv[0], argv[1], argv[2]);
		break;
	}
}

/*
 * Finish - reset and exit
 *
 * Finish();
 *
 * Finish is called upon receipt of SIGINT or SIGUSR1. Finish resets the
 * user's terminal and if called by SIGINT, sends SIGUSR1 signal to his
 * partner's talk process so that both terminate simultaineously
 */
Finish(sig)
int sig;
{
	FILE		*fp, *fopen();
	int		i;

	/*
	 * Prevent from being interrupted while finishing
	 */
	for (i = 0; i < NSIG; i++)
		signal(i, SIG_IGN);
#ifndef SCHIZO
	if (sig == SIGINT) {
		/*
		 * If a conversation was in process, tell the partner's
		 * process to stop also. Otherwise, tell the partner
		 * that the request is no longer pending.
		 */
		if (parpid)
			kill(parpid, SIGUSR1);
		else if ((fp = fopen(&dmsgbuf.msgval.mtext[TTYLOC], "w")) != NULL) {
			tloc = time((long *)0);
			fprintf(fp, "\r\n%cTalk request from %s cancelled at %5.5s\r\n", '\011', getlogin(), ctime(&tloc)+11);
			fclose(fp);
		}
	}
#endif

#ifdef CLRONEXIT
	clear();
#endif
	mvaddstr(0, 0, "[Connection closed]\n");
	refresh();
	nodelay(stdscr, FALSE);
	endwin();

#ifndef SCHIZO
	dmsgbuf.msgval.lines[PIDLOC] = -getpid();
	dmsgbuf.mtype = 1;
	if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
		fprintf(stderr, "talk: msgsnd failure(%d)\n", errno);
		exit(1);
	}
#endif

	exit(0);
}
SHAR_EOF
fi
echo shar: extracting "'talkd.c'" '(13950 characters)'
if test -f 'talkd.c'
then
	echo shar: will not over-write existing file "'talkd.c'"
else
cat  >'talkd.c' <<\SHAR_EOF
/*
 * talkdemon - the demon for the talk program
 *
 * The talkdemon maintains a linked list of TALK structures, one for each
 * person engaged in a conversation. The demon sits in the background and
 * 'listens' on msgtype 1 for messages from talk programs. Whenever a talk
 * process starts up or exits, it sends a message to the demon. The demon
 * examines the message to see if it is and startup or an exit message.
 * If it is the former, the demon compares the new structure with the existing
 * list and manages to determine a set of send/receive msgtypes. (Each
 * conversation gets its own set) Now, ctl messages. When the first half
 * of a conversation (the originator) starts up, it has to wait for the	
 * other process to start. If the demon determines that a talk process is
 * an originator, in addition to the send/receive pair, the demon sends back
 * a ctltype. The originating talk process waits on this ctltype for a demon
 * message. This is sent when the second half of the conversation starts
 * up (the responder). When either talk process exits, it sends a message
 * to the demon as well as SIGUSR1 to the other process. When the demon
 * receives an exit message, it removes that process's TALK structure
 * from the linked list.
 *
 * AUTHOR
 *	Edward C. Bennett (edward@ukecc)
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include <signal.h>
#include "talk.h"
#include <sys/stat.h>

#define	CONSOLE	"/dev/console"	/* Where to write errors if talkd exits */

/*
 * Each conversationalist gets one of these
 */
typedef	struct	talk	{
	char		user[NAMESIZ];		/* The user */
	char		other[NAMESIZ];		/* Who they are talking to */
	char		tty[LINESIZ];		/* The line of the PARTNER */
	long		types;			/* Msgtype index */
	int		pid;	/* pid of the user's talk process (what else?)*/
	struct	talk	*next;
} TALK;

int	msqid;
char	*program, errbuf[BUFSIZ];
void	exit(), free();

main(argc, argv)
int argc;
char **argv;
{
	TALK		*Head, *Addper(), *Delper();
	key_t		msgkey, ftok();
	long		ctl, Link();
	int		i, avail, Finish();

	program = *argv;

	if (fork())
		exit(0);

	setpgrp();
	for (i = 0; i < NSIG; i++)
		signal(i, Finish);

	msgkey = ftok(TALK_PATH, MAGIC_ID);

	if ((msqid = msgget(msgkey, MSGQ_PERMS|IPC_CREAT|IPC_EXCL)) == -1) {
		sprintf(errbuf, "%s: Unable to create talk msgqueue(%d)", program, errno);
		Finish(NSIG);
	}

	Head = (TALK *)NULL;
	/*
	 * The demon sits in this loop, waiting for a message
	 * from a potential user.
	 */
	for (;;) {
#ifdef DEBUG
		fflush(stdout);
#endif
		if (msgrcv(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, (long)1, 0) == -1) {
			sprintf(errbuf, "%s: msgrcv failure(%d)", program, errno);
			Finish(NSIG);
		}

		/*
		 * If a message comes in with the pid '0', the talkdemon
		 * will remove its msgqueue and exit. This is needed for
		 * for reboot procedures. Note that process '0' is ALWAYS
		 * the swapper so there is no danger of a user accidently
		 * killing the demon.
		 */
		if (dmsgbuf.msgval.lines[PIDLOC] == 0) {
#ifdef DEBUG
			printf("\nStoptalk message received\n");
#endif
			sprintf(errbuf, "stopped by a command from stoptalk");
			Finish(NSIG);
		}

		/*
		 * A message with a pid of '1' is a request for status
		 * message. Status info is returned on msgtype 3. Note
		 * that process '1' is ALWAYS /etc/init so it is safe to
		 * assume that a message with a pid of '1' is an info
		 * request.
		 */
		if (dmsgbuf.msgval.lines[PIDLOC] == 1) {
#ifdef DEBUG
			printf("\nInfotalk status request message received\n");
#endif
			Status(Head);
			continue;
		}

		/*
		 * When a users exits talk, he sends a removal message to
		 * the demon. Removal messages are identified by a negative
		 * pid.
		 */
		if (dmsgbuf.msgval.lines[PIDLOC] < 0) {
#ifdef DEBUG
			printf("\nRemoval message received\n");
#endif
			Head = Delper(Head, dmsgbuf.msgval.lines[PIDLOC]);
			continue;
		}

#ifdef DEBUG
		printf("\nInitiate message from: %s pid: %d to: %s\n",
			dmsgbuf.msgval.mtext,
			dmsgbuf.msgval.lines[PIDLOC],
			&dmsgbuf.msgval.mtext[NAMESIZ]);
#endif

		/*
		 * Determine the availability of the potential partner.
		 */
		avail = Find(&dmsgbuf.msgval.mtext[8], &dmsgbuf.msgval.mtext[TTYLOC]);
#ifdef DEBUG
		printf("Availability of %s on %s is %d\n", &dmsgbuf.msgval.mtext[8], &dmsgbuf.msgval.mtext[TTYLOC], avail);
#endif
		if (avail == TALKABLE) {
			if ((Head = Addper(Head, &dmsgbuf)) == NULL) {
				sprintf(errbuf, "%s: Unable to add user, malloc failure(%d)", program, errno);
				Finish(NSIG);
			}

			/*
			 * Determine if the new user is an 'originator' or
			 * a 'responder' and assign them send and receive
			 * msgtypes.
			 */
			ctl = Link(Head, &dmsgbuf);

#ifdef DEBUG
			printf("Sending %d %d %d to type 2\n",
				dmsgbuf.msgval.lines[SEND],
				dmsgbuf.msgval.lines[RECEIVE],
				dmsgbuf.msgval.lines[CTL]);
#endif
		}
		/*
		 * We do this if the requested partner is unavailable.
		 */
		else {
			dmsgbuf.msgval.lines[SEND] = avail;
			ctl = -1;
		}

		/*
		 * Send an acknowledgement to the user informing him of the
		 * availability of his partner and the msgtypes to use for
		 * sending and receiving.
		 */
		dmsgbuf.mtype = 2;
		if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
			sprintf(errbuf, "%s: msgsnd failure(%d)", program, errno);
			Finish(NSIG);
		}

		/*
		 * If the most recent message was a 'responder', send
		 * a ctl message to the 'originator' telling him that
		 * his partner has answered. The ctl message includes
		 * the pid of the respondent.
		 */
		if (ctl > 0) {
#ifdef DEBUG
			printf("Sending ctl message to %d\n", ctl);
#endif
			dmsgbuf.mtype = ctl;
			dmsgbuf.msgval.lines[PIDLOC] = Head->pid;
			if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
				sprintf(errbuf, "%s: msgsnd failure(%d)", program, errno);
				Finish(NSIG);
			}
		}
	}
}

/*
 * Finish - clean up
 *
 * Finish();
 *
 * Finish remove the msgqueue and exits. It is normally not called
 * but is here in case the demon encounters an error.
 */
Finish(sig)
int sig;
{
	FILE		*fp, *fopen();
	time_t		tloc, time();
	char		*ctime();
	int		rmv;

	if (sig != NSIG)
		sprintf(errbuf, "caught signal #%d", sig);

	rmv = msgctl(msqid, IPC_RMID, (struct msqid_ds *)NULL);

	if ((fp = fopen(CONSOLE, "w")) != NULL) {
		tloc = time((long *)0);
		fprintf(fp, "talkdemon exiting: %s", ctime(&tloc));
		fprintf(fp, "%s\n", errbuf);
		if (rmv == 0) {
			fprintf(fp, "talk msgqueue removed\n");
#ifdef DEBUG
			printf("%s: msgqueue removed. demon exiting\n", program);
#endif
		}
		else {
			fprintf(fp, "talk msgqueue not removed\n");
#ifdef DEBUG
			printf("%s: msgqueue not removed. demon exiting\n", program);
#endif
		}
	}
	exit(1);
}

/*
 * Status - report the status of the talkdemon
 *
 * Status(Head);
 *	Head	is a pointer to the first TALK structure in the user list
 *
 * Status runs through the current list of talk users and sends out one status
 * msgbuf for each user. All messages are sent to msgtype 3 which is reserved
 * for this use.
 *
 */
Status(Head)
TALK *Head;
{
	dmsgbuf.mtype = STATUS;

	for (; Head; Head = Head->next) {
		strncpy(dmsgbuf.msgval.mtext, Head->user, NAMESIZ);
		strncpy(&dmsgbuf.msgval.mtext[NAMESIZ], Head->other, NAMESIZ);
		strncpy(&dmsgbuf.msgval.mtext[TTYLOC], Head->tty, LINESIZ);
		/*
		 * Do a little bit play to squeeze in some extra infomation.
		 * No entirely kosher, but it will only screw up in already
		 * hazardous situations. (types > 2^16)
		 */
		dmsgbuf.msgval.lines[PIDLOC] = Head->types;
		dmsgbuf.msgval.lines[PIDLOC] <<= 16;
		dmsgbuf.msgval.lines[PIDLOC] += Head->pid;

		if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
			sprintf(errbuf, "%s: msgsnd failure(%d)", program, errno);
			Finish(NSIG);
		}
	}
	/*
	 * A message containing a pid of '0' indicates the end of the status
	 * information.
	 */
	dmsgbuf.msgval.lines[PIDLOC] = 0;
	if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
		sprintf(errbuf, "%s: msgsnd failure(%d)", program, errno);
		Finish(NSIG);
	}
#ifdef DEBUG
	printf("End-of-status message written\n");
#endif
}

/*
 * Link - determine send and receive msgtypes for a TALK structure
 *
 * ctl = Link(Head, bufptr)
 *	ctl	is a ctl type for the TALK structure
 *	Head	is a pointer to the first TALK structure in the list,
 *		which is ALWAYS the most recently added.
 *	bufptr	is a pointer to a DMSG buffer.
 *
 * The newest TALK structure is compared against the rest of the list to
 * determine if the structure belongs to an 'originator' or a 'responder'.
 * If it's the former, a send/receive/ctl msgtype triple is selected, if
 * the latter, a send/receive pair is taken from the matched initiator's
 * structure.
 *
 * If the TALK structure belongs to a 'responder', the ctl msgtype for the
 * corresponding 'originator' is returned, otherwise a zero is returned.
 */
long
Link(Head, bufptr)
TALK *Head;
DMSG *bufptr;
{
	TALK		*ptr;
	int		i = 4;

	/*
	 * See if there is a 'partner' for the new person.
	 * This is done by looking for a TALK structure with the same
	 * names, but reversed.
	 */
	for (ptr = Head->next; ptr; ptr = ptr->next) {
		/*
		 * Perform a sanity check on the list. Occasionally
		 * talk process die without removing their TALK
		 * structures. Dunno why.
		 *
		 * If a TALK structure represents a dead process, remove
		 * it. Notice that the return value of Delper() is ignored.
		 * We can do this because ptr->pid will never be Head->pid.
		 */
		if (kill(ptr->pid, 0) == -1)
			(void) Delper(Head, ptr->pid);

		if (!strncmp(ptr->user, Head->other, NAMESIZ) &&
			!strncmp(ptr->other, Head->user, NAMESIZ)) {
			bufptr->msgval.lines[RECEIVE] = ptr->types;
			bufptr->msgval.lines[SEND] = ptr->types + 1;
			bufptr->msgval.lines[CTL] = 0;
			bufptr->msgval.lines[PIDLOC] = ptr->pid;
			/*
			 * NULL the tty pointers of the structures
			 * to show that they're connected.
			 */
			*Head->tty = NULL;
			*ptr->tty = NULL;
			Head->types = ptr->types;

			return(ptr->types + 2);
		}
	}

	/*
	 * If we got this far, the new person is an 'originator'.
	 *
	 * First, find an unused send/receive/ctl triple.
	 */
	do {
		for (ptr = Head->next; ptr; ptr = ptr->next) {
			if (i == ptr->types) {
				i += 3;
				break;
			}
		}
	} while (ptr);
	Head->types = i;
	bufptr->msgval.lines[SEND] = i;
	bufptr->msgval.lines[RECEIVE] = i + 1;
	bufptr->msgval.lines[CTL] = i + 2;

	return(0);
}

/*
 * Find - locate the partner and determine their availability
 *
 * avail = Find(p1, p2);
 *	avail	is the availability of the requested partner
 *	p1	is a pointer to the partner's name
 *	p2	is a pointer to the name of the partner's tty
 *
 * Find reads UTMP_FILE to get the tty line of the initiator's requested
 * partner. If no tty was given, Find() fills it into p2.
 */
Find(partner, line)
char *partner, *line;
{
	struct	stat	lstat;
	struct	utmp	utmp;
	char		tmpbuf[18];
	int		fd, flag;

	flag = 0;

	if ((fd = open(UTMP_FILE, 0)) >= 0) {
		while (read(fd, (char *)&utmp, sizeof(utmp)) == sizeof(utmp)) {
#ifndef OLDUTMP
			if (utmp.ut_type != USER_PROCESS)
				continue;
#endif

			if (!strncmp(utmp.ut_line, line, LINESIZ) &&
				strncmp(utmp.ut_name, partner, NAMESIZ)) {
				flag = -1;
				break;
			}
			else if (!strncmp(utmp.ut_name, partner, NAMESIZ)) {
				flag++;
				/* if line was speced and found him on this
				   line, make flag one even if he was already
				   found on another line */
				if (*line != NULL &&
				    !strncmp(utmp.ut_line, line, LINESIZ)) {
					flag = 1;
					break;
				}

				/* safe to copy line if not speced since it
				   cant match again */
				if (*line == NULL)
					strcpy(line, utmp.ut_line);
#ifdef DEBUG
				printf("%s found on %s\n", partner, line);
#endif
			}
		}
		close(fd);
	}
	else {
		sprintf(errbuf, "%s: cannot open %s(%d)", program, UTMP_FILE, errno);
		Finish(NSIG);
	}

	if (flag == 0)
		return(NOTLOGGEDON);
	if (flag < 0)
		return(NOTONLINE);
	if (flag > 1)
		return(LOGGEDMORE);

	strcpy(tmpbuf, line);
	strcpy(line, "/dev/");
	strcat(line, tmpbuf);
	stat(line, &lstat);
	if (!(lstat.st_mode & 0002))
		return(NOTWRITE);

	return(TALKABLE);
}

/*
 * Addper - add a new user to the list
 *
 * tp = Addper(hp, mp);
 *	tp	is a pointer to new TALK structure
 *	hp	is a pointer to the Head of the TALK list
 *	mp	is a pointer to the dmsgbuf
 *
 * A new TALK structure is allocated and linked in at the head of the list.
 * Data from the just-received dmsg is copied into the new structure.
 * A pointer to the new structure is returned unless malloc() was unable
 * to allocate space, in which case NULL is returned.
 */
TALK *
Addper(ptr, mptr)
TALK *ptr;
DMSG *mptr;
{
	TALK		*p;
	char		*malloc();

	if ((p = (TALK *)malloc(sizeof(TALK))) == NULL)
		return(NULL);

	p->next = ptr;

	strncpy(p->user, mptr->msgval.mtext, NAMESIZ);
	strncpy(p->other, &mptr->msgval.mtext[NAMESIZ], NAMESIZ);
	strncpy(p->tty, &mptr->msgval.mtext[TTYLOC], LINESIZ);
	p->pid = mptr->msgval.lines[PIDLOC];

	return(p);
}

/*
 * Delper - remove somebody
 *
 * hp = Delper(p, pid);
 *	hp	is a pointer to the (possibly new) head of the list
 *	p	is a pointer to the head of the list
 *	pid	is the pid of the structure to remove
 *
 * The person whose talk pid is given is removed from the list.
 * The, possibly new, head of the list is returned.
 */
TALK *
Delper(Head, pid)
TALK *Head;
int pid;
{
	TALK		*ptr, *lastptr;

	pid = abs(pid);	/* Make sure pid is positive */
	ptr = Head;
	lastptr = (TALK *)NULL;

	while (ptr->pid != pid) {
		lastptr = ptr;
		ptr = ptr->next;

		if (ptr == (TALK *)NULL)	/* Just to be safe */
			return(Head);
	}

	if (lastptr)
		lastptr->next = ptr->next;
	else
		Head = ptr->next;

#ifdef DEBUG
	printf("Removing: %s %d from list\n", ptr->user, pid);
#endif

	free((char *)ptr);

	return(Head);
}
SHAR_EOF
fi
echo shar: extracting "'infotalk.c'" '(1673 characters)'
if test -f 'infotalk.c'
then
	echo shar: will not over-write existing file "'infotalk.c'"
else
cat  >'infotalk.c' <<\SHAR_EOF
/*
 * infotalk - provide current status of the talk system
 *
 * AUTHOR
 *	Edward C. Bennett (edward@ukecc)
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include "talk.h"

int	msqid;

main(argc, argv)
int argc;
char *argv[];
{
	key_t		msgkey, ftok();
	void		exit();
	int		busy;

	msgkey = ftok(TALK_PATH, MAGIC_ID);

	if ((msqid = msgget(msgkey, MSGQ_PERMS)) == -1) {
		fprintf(stderr, "%s: Nonexistant talk msgqueue\n", argv[0]);
		exit(1);
	}

	dmsgbuf.msgval.lines[PIDLOC] = 1;
	dmsgbuf.mtype = 1;

	/*
	 * Send the request to the demon
	 */
	if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, 0) == -1) {
		fprintf(stderr, "%s: msgsnd failure(%d)\n", argv[0], errno);
		exit(1);
	}
#ifdef DEBUG
	printf("Info request sent to talkdemon\n");
#endif

	printf("Talker      Talkee      TTY             PID    MTYPE\n");
	do {
		if (msgrcv(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, (long)STATUS, 0) == -1) {
			fprintf(stderr, "%s: msgrcv failure(%d)\n", argv[0], errno);
			exit(1);
		}
#ifdef DEBUG
	printf("Info message received from talkdemon\n");
#endif
		if (dmsgbuf.msgval.lines[PIDLOC]) {
			printf("%-12s", dmsgbuf.msgval.mtext);
			printf("%-12s", &dmsgbuf.msgval.mtext[NAMESIZ]);
			printf("%-16s", &dmsgbuf.msgval.mtext[TTYLOC]);
			printf("%5d", dmsgbuf.msgval.lines[PIDLOC] & 0177777);
			printf("  %5d\n", dmsgbuf.msgval.lines[PIDLOC] >> 16);
			busy++;
		}
	} while (dmsgbuf.msgval.lines[PIDLOC]);
	if (!busy)
		printf("No one is currently using talk\n");
}
SHAR_EOF
fi
echo shar: extracting "'stoptalk.c'" '(1814 characters)'
if test -f 'stoptalk.c'
then
	echo shar: will not over-write existing file "'stoptalk.c'"
else
cat  >'stoptalk.c' <<\SHAR_EOF
/*
 * stoptalk - shut down the talk facility
 *
 * SYNOPSIS
 *	stoptalk
 *
 * Stoptalk is used to stop the talk facility. Prior to a reboot the
 * current talk msgqueue needs to be removed manually because the
 * reboot procedure does not clear system msgqueues. The talkdemon
 * is incapable of removing its own msgqueue automatically because
 * /etc/kill stops process with SIGKILL which cannot be caught.
 *
 * Stoptalk sends a message to the talkdemon instructing it to exit.
 * If this fails, stoptalk tries to remove the msgsqueue itself.
 *
 * AUTHOR
 *	Edward C. Bennett (edward@ukecc)
 *
 * Copyright 1985 by Edward C. Bennett
 *
 * Permission is given to alter this code as needed to adapt it to forign
 * systems provided that this header is included and that the original
 * author's name is preserved.
 */
#include <stdio.h>
#include "talk.h"

main(argc, argv)
int argc;
char **argv;
{
	key_t		msgkey, ftok();
	void		exit();
	int		msqid;

	msgkey = ftok(TALK_PATH, MAGIC_ID);

	if ((msqid = msgget(msgkey, MSGQ_PERMS)) == -1) {
		fprintf(stderr, "%s: Nonexistant talk msgqueue\n", *argv);
		exit(1);
	}

	/*
	 * Set up a msg containing the pid '0'. This will signal
	 * the talkdemon to exit gracefully.
	 */
	dmsgbuf.msgval.lines[PIDLOC] = 0;
	dmsgbuf.mtype = 1;

	/*
	 * Send the 'kill' msg to the demon.
	 */
	if (msgsnd(msqid, (struct msgbuf *)&dmsgbuf, MSGSIZ, IPC_NOWAIT) == 0) {
		/*
		 * Wait while the demon prints its exit message
		 */
		sleep(2);
		exit(0);
	}
	/*
	 * The graceful method didn't work, get brutal
	 */
	fprintf(stderr, "%s: msgsnd failure(%d)\n", *argv, errno);
	if (msgctl(msqid, IPC_RMID, (struct msqid_ds *)NULL) == 0) {
		fprintf(stderr, "%s: Talk msgqueue removed\n", *argv);
		exit(0);
	}

	fprintf(stderr, "%s: Unable to remove talk msgqueue\n", *argv);
	exit(1);
}
SHAR_EOF
fi
:	End of shell archive
exit 0