[alt.sources] System V talk with named pipes

mattson@beowulf.ucsd.edu (Jim Mattson) (08/19/89)

Here is a talk program that I wrote for a System V machine a few years ago.
I recently resurrected it for an SCO Xenix machine.  It looks a lot like
UCB talk, but it uses named pipes for communication, so it only works on
the local machine.

Installation notes:
  Everything is set up to run setuid bin.  Actually, it could run as setuid
anybody (except root...that defeats 'mesg n'), but it definitely has to run
setuid.  (This is because the two conversing processes signal each other with
SIGUSR1 when they want the other to read from its pipe.)  You can put a 
line in /etc/rc.local to start up the daemon initially, but if someone runs
'talk' and there is no daemon running, it will try to start one.

If you have problems with it, let me know.

--jim
#! /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:
#	Makefile
#	talk.c
#	talk.h
#	talkd.c
#	win.c
# This archive created: Fri Aug 18 12:18:38 1989
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
# System V version of talk.  Looks and feels like UCB talk, except that
# it only works on the local machine.
# Jim Mattson
# mattson%cs@ucsd.edu
# $Header: Makefile,v 1.3 89/08/18 12:18:21 mattson Exp $

DESTDIR = /local/bin
OWNER = bin

# For SCO Xenix: LIBS = -ltinfo -lx
#                WFLAGS = -DM_TERMINFO
LIBS = -lcurses
WFLAGS=

all:	talk talkd

talk:	talk.o win.o
	$(CC) $(CFLAGS) -o talk talk.o win.o $(LIBS)

talkd:	talkd.c talk.h
	$(CC) $(CFLAGS) -o talkd talkd.c

talk.o: talk.h

win.o: talk.h
	$(CC) $(CFLAGS) -c $(WFLAGS) win.c

install: $(DESTDIR)/talk $(DESTDIR)/talkd

$(DESTDIR)/talk: talk
	cp talk $(DESTDIR)
	chown $(OWNER) $(DESTDIR)/talk
	chmod 4755 $(DESTDIR)/talk

$(DESTDIR)/talkd: talkd
	cp talkd $(DESTDIR)
	chown $(OWNER) $(DESTDIR)/talkd
	chmod 4755 $(DESTDIR)/talkd
SHAR_EOF
fi
if test -f 'talk.c'
then
	echo shar: "will not over-write existing file 'talk.c'"
else
cat << \SHAR_EOF > 'talk.c'
/*
 * System V version of talk.  Looks and feels like UCB talk, except that
 * it only works on the local machine.
 * Jim Mattson
 * mattson%cs@ucsd.edu
 * $Header: talk.c,v 1.3 89/08/18 12:15:33 mattson Exp $
 */
 
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include "talk.h"

#define RETRY_TIME	30

#define INITIAL	0
#define WAITING 1
#define TALKING 2

char state = INITIAL;
char *dpipe = FIFO;
char *inpipe = "/tmp/talkXXXXXX";
int talk_index = -1;
int other_pid;

main(argc, argv)
int argc;
char **argv;
{
	int rc;
	int pdi, pdo;
	int wakeup(), die_quietly();

	if(argc != 2) {
		fprintf(stderr, "Usage is: %s party\n", argv[0]);
		exit(-1);
	}
	if(strlen(argv[1]) > 8) {
		fprintf(stderr, "%s: your party's name is too long.\n", argv[0]);
		exit(-1);
	}
	
	signal(SIGHUP, die_quietly);
	signal(SIGINT, die_quietly);
	signal(SIGQUIT, die_quietly);
	signal(SIGKILL, die_quietly);
	signal(SIGUSR1, SIG_IGN);
	
	make_pipe();
	call(argv[1]);
	connect(&pdi, &pdo);
	talk(pdi, pdo);
	die_quietly();
}

make_pipe()
{
	mktemp(inpipe);	/* Our input pipe */
	if(mknod(inpipe,010622,0)) {
		perror("Cannot create FIFO");
		exit(-1);
	}
	if(chmod(inpipe,0622)) {
		perror("Cannot change mode of FIFO");
		exit(-1);
	}
}

call(party)
char *party;
{
	struct treq r;
	char *tty;
	
	r.req = TALK_NEW;
	strcpy(r.d.new.caller, cuserid(NULL));
	strcpy(r.d.new.pipe, inpipe);
	strcpy(r.d.new.party, party);
	strcpy(r.d.new.port, strrchr(ttyname(0),'/')+1);
	r.d.new.pid = getpid();
	send_daemon(&r);
}

connect(pdi, pdo)
int *pdi, *pdo;
{
	struct treq r;
	int rc;
	
	if((*pdi = open(inpipe,O_RDONLY | O_TRUNC)) == -1) {
		perror("Cannot open your FIFO");
		die_quietly();
	}

	signal(SIGALRM, wakeup);
	alarm(RETRY_TIME);
	while(state != TALKING) {
		do {
			rc = read(*pdi, &r, sizeof(struct treq));
		} while(rc == -1 && errno == EINTR);
		if(rc == sizeof(struct treq)) {
			switch(r.req) {
				case TALK_RING:
					fputs("[Ringing your party", stdout);
					if(state == WAITING) 
						fputs(" again", stdout);
					fputs("...]\n", stdout);
					fflush(stdout);
					talk_index = r.d.ring.ind;
					state = WAITING;
					break;
				case TALK_ERR:
					fprintf(stderr,"[%s]\n",r.d.err.msg);
					die_quietly();
					break;
				case TALK_ACK:
					signal(SIGALRM, SIG_IGN);
					state = TALKING;
					other_pid = r.d.ack.pid;
					do {
						*pdo = open(r.d.ack.pipe,O_WRONLY);
					} while(*pdo == -1 && errno == EINTR);
					if(*pdo == -1) {
						perror("Cannot open party's FIFO");
						die_quietly();
					break;
					}
			}
		}
	}
}

send_daemon(r)
struct treq *r;
{
	int pdd;
	int rc;

	do {
		pdd = open(dpipe,O_WRONLY | O_NDELAY);
	} while(pdd == -1 && errno == EINTR);
	if(pdd == -1) {
		perror("Cannot connect to talk daemon");
		if(state == INITIAL) {
			/* Try to crank up a new daemon */
			restart_daemon();
			sleep(3);
			if((pdd = open(dpipe,O_WRONLY | O_NDELAY)) == -1) {
				perror("Still cannot connect to talk daemon");
				die_quietly();
			}
		} else
			die_quietly();
	}
	do {
		rc = write(pdd, r, sizeof(struct treq));
	} while(rc == -1 && errno == EINTR);
	if(rc == -1) {
		perror("Cannot write to daemon's pipe");
		die_quietly();
	}
}

restart_daemon()
{
	int pid;
	
	fputs("Attempting to restart daemon...\n", stdout);
	if((pid=fork()) == -1) {
		perror("Cannot fork");
		die_quietly();
	} else if(pid == 0) {
		execlp("talkd","talkd",0);
		_exit(0);
	}
}

wakeup()
{
	struct treq r;

	if(state == WAITING) {
		r.req = TALK_RING;
		r.d.ring.ind = talk_index;
		send_daemon(&r);
		signal(SIGALRM, wakeup);
		alarm(RETRY_TIME);
	}
	else {
		signal(SIGALRM, SIG_DFL);
	}
}

die_quietly()
{
	struct treq r;
	static dying = 0;

	switch(state) {
		case WAITING:
			if(!dying) {
				dying = 1;
				r.req = TALK_HUP;
				r.d.hup.ind = talk_index;
				send_daemon(&r);
			}
		default:
			unlink(inpipe);
	}
	exit(0);
}
SHAR_EOF
fi
if test -f 'talk.h'
then
	echo shar: "will not over-write existing file 'talk.h'"
else
cat << \SHAR_EOF > 'talk.h'
/*
 * System V version of talk.  Looks and feels like UCB talk, except that
 * it only works on the local machine.
 * Jim Mattson
 * mattson%cs@ucsd.edu
 * $Header: talk.h,v 1.3 89/08/18 12:15:37 mattson Exp $
 */
 

struct treq {
	int	req;
	union {
		struct {
			char caller[9];	/* Name of caller */
			char pipe[21];	/* Caller's pipe */
			char party[9];	/* Party we are trying to reach */
			int pid;	/* caller's pid */
			char port[9];	/* Caller's port */
		} new;
		struct {
			int ind;	/* Index */
		} ring;
		struct {
			char pipe[21];	/* Party's pipe */
			int pid;		/* party's pid */
		} ack;
		struct {
			char msg[81];
		} err;
		struct {
			int ind;	/* Index */
		} hup;
	} d;
};

struct talk {
	char caller[9];
	char party[9];
	char cpipe[21];
	char ppipe[21];
	int cpid;
	int ppid;
	char port[8];
	char inuse;
};

#define TALK_NEW	0
#define TALK_RING	1
#define TALK_ACK	2
#define TALK_HUP	3
#define TALK_ERR	4

#define FIFO "/usr/tmp/talkd.fifo"
SHAR_EOF
fi
if test -f 'talkd.c'
then
	echo shar: "will not over-write existing file 'talkd.c'"
else
cat << \SHAR_EOF > 'talkd.c'
/*
 * System V version of talk.  Looks and feels like UCB talk, except that
 * it only works on the local machine.  This is the daemon.  It can be
 * started in /etc/rc.local (or the equivalent) with a line like:
 * [ -x /local/bin/talkd ] && /local/bin/talkd
 *
 * Jim Mattson
 * mattson%cs@ucsd.edu
 * $Header: talkd.c,v 1.3 89/08/18 12:15:40 mattson Exp $
 */
 
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <utmp.h>
#include "talk.h"

#define TALK_MAX 20

struct talk pend[TALK_MAX];

main()
{
	int pd;
	int rc;
	char *pipe = FIFO;
	struct treq request;
	
	if(daemon_exists(pipe)) {
		fprintf(stderr,"Daemon already exists.\n");
		exit(-1);
	}
	if((rc = fork()) == -1) {
		perror("Cannot fork");
		exit(-1);
	}
	if(rc)
		_exit(0);	/* parent */

	setpgrp();		/* Detach from parent's process group */

	pd = makepipe(pipe);
	fclose(stdin);
	fclose(stdout);
	
	for(;;) {
		if((rc = read(pd, &request, sizeof(struct treq)) == sizeof(struct treq))) {
			switch(request.req) {
				case TALK_NEW:
					new_talk(&request);
					break;
				case TALK_RING:
					if(!ring(request.d.ring.ind))
						pend[request.d.ring.ind].inuse = 0;
					break;
				case TALK_HUP:
					pend[request.d.hup.ind].inuse = 0;
					break;
			}
		} else if(!rc) {
			close(pd);
			pd = makepipe(pipe);
		}
	}
}

new_talk(r)
struct treq *r;
{
	int pendno;
	int pindex;
	struct treq l;
	
	if((pendno = pending(r)) == -1) {
		if((pindex = next_pend()) == -1) {
			l.req = TALK_ERR;
			strcpy(l.d.err.msg, "Too many pending connections.");
			send(r->d.new.pipe, &l);
		} else {
			strcpy(pend[pindex].caller, r->d.new.caller);
			strcpy(pend[pindex].cpipe, r->d.new.pipe);
			strcpy(pend[pindex].party, r->d.new.party);
			strcpy(pend[pindex].port, r->d.new.port);
			pend[pindex].cpid = r->d.new.pid;
			if(ring(pindex)) {
				pend[pindex].inuse = 1;
			}
		}
	} else {
		strcpy(pend[pendno].ppipe, r->d.new.pipe);
		pend[pendno].ppid = r->d.new.pid;
		r->req = TALK_ACK;
		strcpy(r->d.ack.pipe, pend[pendno].ppipe);
		r->d.ack.pid = pend[pendno].ppid;
		send(pend[pendno].cpipe, r);
		strcpy(r->d.ack.pipe, pend[pendno].cpipe);
		r->d.ack.pid = pend[pendno].cpid;
		send(pend[pendno].ppipe, r);
		pend[pendno].inuse = 0;
	}
}

next_pend()
{
	int i;

	for(i = 0; i < TALK_MAX; i++) 
		if(!pend[i].inuse) 
			return(i);
	return(-1);
}

ring(i)
int i;
{
	int utd;
	struct utmp ut_entry;
	FILE *fp;
	char dev[20];
	struct treq resp;
	char found, ringing;
	
	if((utd = open(UTMP_FILE,O_RDONLY)) == EOF) {
		perror("Cannot open utmp\n");
		exit(-1);
	}
	found = ringing = 0; 
	while(read(utd, &ut_entry, sizeof(struct utmp)) == sizeof(struct utmp)) {
		if(ut_entry.ut_type == USER_PROCESS &&
			strncmp(ut_entry.ut_line, pend[i].port,8) &&
			!strncmp(ut_entry.ut_user, pend[i].party,8)) {
				if(!found) {
					resp.req = TALK_RING;
					resp.d.ring.ind = i;
					send(pend[i].cpipe, &resp);
					found = 1;
				}
				sprintf(dev,"/dev/%s",ut_entry.ut_line);
				if((fp = fopen(dev, "w")) != NULL) {
					fprintf(fp,"\nMessage from Talk Daemon:\007\n");
					fprintf(fp,"\tTalk connection requested by %s\n", 
						pend[i].caller);
					fprintf(fp,"\tPlease respond with \"talk %s\"\n",
						pend[i].caller);
					fclose(fp);
					ringing = 1;
				} 
			}
	}
	if(!found) {
		resp.req = TALK_ERR;
		strcpy(resp.d.err.msg,"Your party is not logged on.");
		send(pend[i].cpipe, &resp);
	} else if(!ringing) {
		resp.req = TALK_ERR;
		strcpy(resp.d.err.msg,"Your party is not accepting messages.");
		send(pend[i].cpipe, &resp);
	}
	return(found && ringing);
}

pending(r)
struct treq *r;
{
	int i;

	for(i = 0; i < TALK_MAX; i++) 
		if(pend[i].inuse && !strcmp(pend[i].caller, r->d.new.party) &&
			!strcmp(pend[i].party, r->d.new.caller))
				return(i);
	return(-1);
}

makepipe(p)
char *p;
{
	int rc;
	int pd;
	
	unlink(p);
	if((rc = mknod(p,010622,0)) && errno != EEXIST) {
		perror("Cannot create FIFO");
		exit(-1);
	}
	if(chmod(p,0622)) {
		perror("Cannot change mode of FIFO");
		exit(-1);
	}
	if((pd = open(p,O_RDONLY | O_TRUNC)) == EOF) {
		perror("Cannot open FIFO");
		exit(-1);
	}
	return(pd);
}

daemon_exists(p)
char *p;
{
	int pd;

	if((pd = open(p,O_WRONLY | O_NDELAY)) == EOF)
		return(0);
	else {
		close(pd);
		return(1);
	}
}

send(p, r)
char *p;
struct treq *r;
{
	int pd;

	if((pd = open(p,O_WRONLY)) != EOF) {
		write(pd, r, sizeof(struct treq));
		close(pd);
	}
}
	
SHAR_EOF
fi
if test -f 'win.c'
then
	echo shar: "will not over-write existing file 'win.c'"
else
cat << \SHAR_EOF > 'win.c'
/*
 * System V version of talk.  Looks and feels like UCB talk, except that
 * it only works on the local machine.
 * Jim Mattson
 * mattson%cs@ucsd.edu
 */
 
#include <curses.h>
#include <fcntl.h>
#include <signal.h>
#include "talk.h"

/* Define some special keys */
#define CTRL_D	4
#define CTRL_H	8
#define CTRL_L	12
#define CTRL_M	13
#define CTRL_R	18
#define CTRL_W	23
#define CTRL_X	24
#define DEL		127

int midway;
int ox, oy;
int pipe;
int other_pid;

talk(pdi, pdo)
int pdi, pdo;
{
	int x=0, y=0;		/* Cursor location */
	char c;				/* Input character */
	int ic;				/* Input character as integer */
	int i;
	struct termio ttystuff;
	int die(), update_other();
	int flags;
		
	signal(SIGPIPE, die);
	signal(SIGUSR1, update_other);
	pipe = pdi;

	flags = fcntl(pdi, F_GETFL);
	fcntl(pdi, F_SETFL, flags | O_NDELAY);
	ioctl(0, TCGETA ,&ttystuff);
	
	initscr();		/* Must call this first for curses */
	nonl(); raw(); noecho();	/* Set up raw input w/out echo */

	midway = (LINES + 1) / 2;
	for(i = 0; i < COLS; i++)
		mvaddch(midway, i, '-');
	ox = 0; oy = midway + 1;
	
	move(y,x);
	refresh();		/* Draw first screen */
	
	while((c = ic = getch()) != CTRL_D && c != ttystuff.c_cc[VINTR]) {
	signal(SIGUSR1, SIG_IGN);
		switch(ic) {
			default:
				if(c == ttystuff.c_cc[VERASE])
					c = CTRL_H;
				else if(c == ttystuff.c_cc[VKILL])
					c = CTRL_X;
				else if(c < ' ' || c == DEL) {	/* Non-printing char not in switch */
					beep();
					break;
				} else {
					mvaddch(y,x,c);
					write(pdo, &c, sizeof(c));
					if(kill(other_pid, SIGUSR1) == -1)
						die();
					if(++x == COLS) {
						x = 0;
						if(++y == midway) 	/* Go down one line on wrap */
							y = 0;
						move(y,x);
						clrtoeol();
					}
					break;
				}

			case EOF :		/* Read was interrupted -- Pyramid only? */
				break;
				
			case CTRL_H :	/* Move back */
				if(x == 0) 
					beep();
				else {
					write(pdo, &c, sizeof(c));
					if(kill(other_pid, SIGUSR1) == -1)
						die();
					--x;
					mvaddch(y,x,' ');
				}
				break;

			case CTRL_M :	/* Carriage return -- go to col 1 of next line */
				if(++y == midway)
					y = 0;

			case CTRL_X :	/* Kill line */
				x = 0;		
				move(y,x);
				clrtoeol();
				write(pdo, &c, sizeof(c));
				if(kill(other_pid, SIGUSR1) == -1)
					die();
				break;

			case CTRL_L :
			case CTRL_R :	/* Redraw screen */
				touchwin(stdscr);	/* Tell curses the whole window changed */
				clearok(stdscr, 1);	/* Clear screen before refresh */
				refresh();		/* Okay, redraw it */
				clearok(stdscr, 0);	/* Reset clear-before-redraw flag */
				break;
		}
		move(y, x);		/* Update cursor location */
		refresh();		/* Draw new screen */
		signal(SIGUSR1, update_other);
	}
	c = CTRL_D;
	write(pdo, &c, sizeof(c));
	kill(other_pid, SIGUSR1);
	die();
}

die()
{
	int flags;
	
	flags = fcntl(0, F_GETFL);
	fcntl(0, F_SETFL, flags & ~O_NDELAY);
	move(LINES - 1, 0);
	refresh();
	endwin();
	puts("\n[Connection closed...exiting]");
	die_quietly();
}

update_other()
{
	char c;

	signal(SIGUSR1, SIG_IGN);
	move(oy, ox);
	while(read(pipe, &c, sizeof(c)) > 0) {
		switch(c) {
			default:
				addch(c);
				if(++ox == COLS) {
					ox = 0;
					if(++oy == LINES) 	/* Go down one line on wrap */
						oy = midway + 1;
					move(oy,ox);
					clrtoeol();
				}
				break;

			case CTRL_H :	/* Move back */
				--ox;
					mvaddch(oy,ox,' ');
				break;
				
			case CTRL_M :	/* Carriage return -- go to col 1 of next line */
				if(++oy == LINES)
					oy = midway + 1;

			case CTRL_X :	/* Kill line */
				ox = 0;		
				move(oy,ox);
				clrtoeol();
				break;
			case CTRL_D :
				die();
				break;
		}
		move(oy, ox);		/* Update cursor location */
	}
	refresh();
	signal(SIGUSR1, update_other);
	return(0);
}
SHAR_EOF
fi
exit 0
#	End of shell archive