[alt.sources] Phone program re-visited

markk@censor.UUCP (Mark Keating) (03/28/89)

Some time ago the program 'Phone' was posted on the Net, following
that some requests were make because of some problems with it.
I decided to play with this to better understand IPC's and ended
up fixing the major problems with it, and so am posting it back.

    Things fixed:

        Phone now negotiates with each end to determine whether
        the callee is already in a conversation, if so a message
        is returned to the caller saying that the line is busy,
        also a message is put up of the callee's status line who
        last tried calling (sort of a call waiting). This eliminated
        the problems with phone going out to lunch when a third
        caller tried to get in on the conversation.

        Added in an option to specify the tty port for when a user
        is logged on more than once.

        Added in one of those silly little clocks that keeps reminding
        you what time it is while your busy yakkity-yakkin on the phone.

        Fixed up some minor annoyances where phone would leave the tty
        in a somewhat insane state and also didn't cleanly disconnect
        when the connection couldn't be made.

I still find this program nice in the fact that it doesn't require a
server, and only requires that you run it.

    Hope you find it useful, seems like an excellent lil Unix comm program.


------------------------------cut here------------------------------
#! /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:
#	phone.1
#	phone.c
#	Makefile
# This archive created: Tue Mar 28 08:45:55 1989
export PATH; PATH=/bin:$PATH
if test -f 'phone.1'
then
	echo shar: will not over-write existing file "'phone.1'"
else
cat << \SHAR_EOF > 'phone.1'
.TH PHONE 1 "July 17, 1988"
.UC 4
.SH NAME
phone \- phone another user, typing screen to screen.
.SH SYNOPSIS
phone [ user ] / [ -t ttyXXX ]
.SH DESCRIPTION
.I Phone 
causes a message to appear on the terminal being used by
.I user
that indicates that someone is trying to phone him.  
If the other user also executes phone, then both screens are cleared
and the users are set up in a two way conversation.
.PP
Each user's input is displayed at the bottom of their own
screen and at the top of the other user's screen.  The
following control characters are handled:
.TP 20
.BI "ERASE\ (Control\ H)"
Erase the character before the cursor.
.TP
.BI "KILL\ (Control\ U)"
Erase the line the cursor is on and move the cursor to the beginning 
of the (now blank) line.
.TP
.BI "Control\ L"
Refresh your own screen.
.TP
.BI "Control\ G"
Either flash (preferably) or ring the bell on other user's terminal.
.TP
.BI "EOF\ (Control\ D)"
Discontinue execution.  The other user is informed.
.PP
(The characters in parenthesis above are typical assignments
for these control characters.  Those assigned by the user
will be used.  See STTY(1)).
.PP
.I Phone
handles only two users.  If a third user attempts entry into
a conversation, they are notified that the line is busy, also
the person on the line is notified by a status message that
indicates who was trying to call. 
.PP
Works only when each user is logged in once.  An error
message is indicated if phone cannot determine the proper
port. In this case using the '-t tty' option will
allow proper access.
.PP
Requires System V, since it uses Sys V IPC.
.SH AUTHOR
Jack Bonn
.br
Software Labs, Ltd.
.br
Box 451
.br
Easton, CT  06612
.PP
jack@swlabs.UUCP
.br
uunet!swlabs!jack
SHAR_EOF
fi # end of overwriting check
if test -f 'phone.c'
then
	echo shar: will not over-write existing file "'phone.c'"
else
cat << \SHAR_EOF > 'phone.c'
/* opt: -O -o phone -lcurses

	Nice program but missing some things:
	
		- added in call negotiation to stop from flaking when a third
			user requests access
		- added in notification of who was trying to call
			(sort of like call waiting)
		- added in a silly little time clock in corner to let you
			know how much time you have been waisting here

	One of the nice thing about phone is the fact that a server is not
	required and just the single point to point connections do all the work

*/

#include <sys/param.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <utmp.h>
#include <signal.h>
#include <curses.h>
#include <stdio.h>
#include <string.h>

#define SAME		0	/* For calls to strcmp */
#define CTRL_G		('G'-'A'+1)
#define CTRL_K		('K'-'A'+1)
#define CTRL_L		('L'-'A'+1)
#define DELETE		0x7F

/*
	packet.type is one of these --
*/

#define	HOWDY		1	/* Initiate/Start  connection	*/
#define	ADIOS		2	/* Hang up/disolve connection	*/
#define	DISPLAY		3	/* Display character on screen	*/
#define	FLASH		4	/* Ring bell/flash screen		*/
#define	REFRESH		5	/* Redraw screen on CTL-L		*/
#define	ERASE		6	/* Erase previous character		*/
#define	KILL		7	/* Erase line cursor is on		*/

/*
	packet.origin is one of these --
*/

#define	LOCAL		0
#define	REMOTE		1

#define	IPC_ID		231				/* Upper part of key (for uniqueness) */
#define RING_TIME	10				/* Ring cadence in seconds */

int		pid, busy, exit_flag;		/* pid for offspring, if non-zero */

int		loc_q, rem_q;				/* message queue id's */
key_t	my_key, other_key;			/* keys for the two queues */
key_t	ftok();						/* convert a file to a key */

char	*my_name;					/* My User Name */
char	*my_tty;					/* Path of my Device */

char	other_name[24];				/* Other User Name */
char	other_tty[24];				/* Path of other Device */

int		w_size, w_next;				/* window size and pointer */
int		erase_ch, eof_ch;			/* Special control chars for user */

int 	screen_set;					/* flag to indicate curses init'ed */
WINDOW 	*topwin, *botwin;			/* curses windows */

struct 	utmp *u_elem, *getutent();
struct 	termio new_ioargs, old_ioargs;

struct packet {						/* This is the packet that we ipc */
		long type;
		int origin;
		char keypress;
		char buff[31];
	} s_pkt, r_pkt;

void wrapup();
char *getenv();
char *ttyname();

main(argc,argv)
int  argc;
char     *argv[];
{
	if (argc == 2)
		strcpy(other_name, argv[1]);	/* get by other user's name */
	else if (argc == 3 && (strcmp(argv[1], "-t") == SAME)) {
		strcpy(other_tty, "/dev/");
		strcat(other_tty, argv[2]);		/* or  by other tty's  name */
	}
	else {
		puts("usage: phone [ user ] / [ -t ttyXXX ]");
		exit(1);
	}

	my_name = getenv("LOGNAME");		/* Get our name		*/
	my_tty  = ttyname(0);				/* and Device		*/

	ioctl(0, TCGETA, &old_ioargs);
	erase_ch = old_ioargs.c_cc[VERASE];
	eof_ch   = old_ioargs.c_cc[VEOF];
	signal(SIGINT, wrapup);

	if (get_tty(argc == 2) == TRUE) {	/* Get other User's Name / Device */

		/* Get unique keys for naming message queues */
		my_key    = ftok(my_tty,    IPC_ID);
		other_key = ftok(other_tty, IPC_ID);

		if (open_queues()) {
			if (pid=fork()) scrn_proc(); /* Returns when ADIOS packet received */
			else			key_proc();	 /* Never returns, must be killed */
		}
		else exit_flag = 2;
	}
	else exit_flag = 3;
	wrapup();
}

void wrapup()
{
	int ret_code;					/* Wait insists on an argument */
	struct msqid_ds buf;

	if (pid) {						/* Kill sibbling if present */
		kill(pid, SIGQUIT);
		wait(&ret_code);
	}

	if (screen_set == TRUE) endwin();	/* Clean up curses windows */

	msgctl(loc_q, IPC_RMID, &buf);		/* Remove our message queue */
	if (!busy) send_remote(ADIOS, 0);	/* In case other side is still up */
	ioctl(0, TCSETA, &old_ioargs);
	exit(exit_flag);
}

void wrapup_child()
{
	exit(0);
}

key_proc()
{
	register int ch;

	/* Keyboard process */

	signal(SIGQUIT, wrapup_child);
	new_ioargs = old_ioargs;
	new_ioargs.c_lflag &= ~(ISIG | ICANON | ECHO);
	new_ioargs.c_cc[VMIN] = (char)1;
	ioctl(0, TCSETA, &new_ioargs);

	for (;;) {
		ch = getchar();
		if		(ch >= DELETE)   send_both (ERASE,   0);
		else if (ch >= ' ')		 send_both (DISPLAY, ch);
		else if	(ch == erase_ch) send_both (ERASE,   0);
		else if (ch == CTRL_K)	 send_both (KILL,    0);
		else if (ch == CTRL_L)	 send_local(REFRESH, 0);
		else if (ch == CTRL_G)	 send_both (FLASH,   0);
		else if (ch == eof_ch)	 send_local(ADIOS,   0);
		else if (ch == '\r' || ch == '\n')
								 send_both (DISPLAY, '\n');
		else 					 send_local(FLASH,   0);
	}
}

scrn_proc()
{
	register int	y, x;
	register int	ch;
	register WINDOW	*window;			/* temp curses windows */

	/* Screen process */

	init_screen();

	do {
		if (msgrcv(loc_q, &r_pkt, sizeof(r_pkt)-sizeof(r_pkt.type), 0L, 0)
					== -1) continue;

		window = (r_pkt.origin == LOCAL) ? botwin : topwin;

		ch = r_pkt.keypress;

		switch ((int)r_pkt.type) {
			case HOWDY:		disable_third();
							break;

			case ERASE:		waddch(window, '\b'); wdelch(window);
							break;

			case KILL:		getyx(window, y, x); wmove(window, y, 0);
							wdeleteln(window); break;

			case DISPLAY:	waddch(window, (char)(ch));
							break;

			case ADIOS:		if (r_pkt.origin == REMOTE)
								wprintw(botwin, "\n*** %s has hung up. ***\n",
										 other_name);
							break;

			case REFRESH:	clearok(curscr, TRUE);
							break;

			case FLASH:		flash();
							break;
		}
		if (r_pkt.origin == REMOTE) wnoutrefresh(topwin);
		wnoutrefresh(botwin);
		doupdate();
	} while (r_pkt.type != ADIOS);
}

/* third entry not allowed so disconnect it */

disable_third()
{
	int		bad_q;				/* message queue id's */
	key_t	third_key;			/* keys for the two queues */
	char	*dev_ptr;
	char	buffer[40];

	if ((dev_ptr=strchr(r_pkt.buff, ' ')) == NULL) return 0;
	*dev_ptr++ = '\0'; 
	if (strncmp(dev_ptr, "/dev/tty", 8) != SAME)   return 0;
	third_key = ftok(dev_ptr, IPC_ID);
	if ((bad_q = msgget(third_key, 0)) == -1)      return 0;

	s_pkt.origin = REMOTE;	 s_pkt.type = ADIOS;
	s_pkt.keypress = 'd';	 strcpy(s_pkt.buff, other_name);
	strcat(s_pkt.buff, " "); strcat(s_pkt.buff, other_tty);
	msgsnd(bad_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
						+ strlen(s_pkt.buff) + 1, 0);
	sprintf(buffer, "`%s  [%s]'  Tried Calling", r_pkt.buff, dev_ptr+5);
	mvaddstr(w_next, COLS - strlen(buffer), buffer);
	flash(); refresh(); return 1;
}

init_screen()
{
	char	buffer[40];

	initscr();	screen_set = TRUE;

	w_size = (w_next = LINES/2) - 2;

	topwin = newwin(w_size,0,1,0);	botwin = newwin(w_size,0,w_next+1,0);
	idlok(topwin,TRUE);				idlok(botwin,TRUE);
	scrollok(topwin,TRUE);			scrollok(botwin,TRUE);

	attron(A_REVERSE | A_BOLD);
	sprintf(buffer, "%s  [%s]:", other_name, other_tty+5);
	mvprintw(0, 0, "%-*s", COLS-16, buffer);

	sprintf(buffer, "%s  [%s]:", my_name, my_tty+5);
	mvprintw(w_next, 0, "%-*s", COLS, buffer);

	put_time();
}

put_time()
{
	int		clock, i;
	char	*ct_ptr, *ctime();

	clock = time((long *) 0); ct_ptr = ctime(&clock);
	move(0, COLS-16); for (i=0; i<16; i++) addch(*ct_ptr++);
	refresh(); wrefresh(botwin);
	signal(SIGALRM, put_time); alarm(60 - (clock % 60));	
}


int open_queues()
{
	int	i_am_phoning=0;
	register int seconds;

	if ((loc_q = msgget(my_key, 0666 | IPC_CREAT)) == -1) {
		printf("Unable to create my queue\n");
		return(FALSE);
	}

	/*	Stand on our head to test creation of rem_q more often 
		than when we ring the other user.   */

	for (seconds=RING_TIME; ; seconds++) {

		if ((rem_q = msgget(other_key, 0)) != -1) break;
		else i_am_phoning = 1;

		if (seconds == RING_TIME) {
			seconds = 0;
			if (!ring(other_tty)) {
				printf("%s's phone is off the hook (mesg -n).\n\n", other_name);
				return(FALSE);
			}
			printf("Ringing %s on %.14s\n\n", other_name, other_tty);
		}
		if (sleep(1))	/* != 0 if interrupted by other signal */
			return(FALSE);
	}
	
	if (i_am_phoning) {
		msgrcv(loc_q, &r_pkt, sizeof(r_pkt) - sizeof(r_pkt.type), 0L, 0);
		if (r_pkt.type == HOWDY) {
			s_pkt.origin = REMOTE;   s_pkt.type = HOWDY;
			s_pkt.keypress = 'y';	 strcpy(s_pkt.buff, my_name);
			strcat(s_pkt.buff, " "); strcat(s_pkt.buff, my_tty);
			msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
							+ strlen(s_pkt.buff) + 1, 0);
		}
		else return(FALSE);
	}
	else {
		while (msgrcv(loc_q, &r_pkt, 32, 0L, IPC_NOWAIT) != -1) ;
		s_pkt.origin = REMOTE;   s_pkt.type = HOWDY;
		s_pkt.keypress = 'n';	 strcpy(s_pkt.buff, my_name);
		strcat(s_pkt.buff, " "); strcat(s_pkt.buff, my_tty);
		msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
							+ strlen(s_pkt.buff) + 1, 0);
		msgrcv(loc_q, &r_pkt, sizeof(r_pkt) - sizeof(r_pkt.type), 0L, 0);
		if (r_pkt.type != HOWDY) {
			printf("%s's line is busy, try again later.\n\n", other_name);
			busy = 1; return(FALSE);
		}
	}
	return(TRUE);
}

int get_tty(by_name)
int			by_name;
{
	int found=0;

	while(u_elem=getutent()) {
		if (by_name) {
			if (strcmp(u_elem->ut_user, other_name) == SAME) {
				++found; strcpy(other_tty, "/dev/");
				strncpy(&other_tty[5], u_elem->ut_line, sizeof(u_elem->ut_line));
			}
		}
		else {
			if ((u_elem->ut_type == USER_PROCESS)
			 && (strcmp(&other_tty[5], u_elem->ut_line) == SAME)) {
				++found;
				strncpy(other_name, u_elem->ut_name, sizeof(u_elem->ut_name));
			}
		}
	}
	if (found == 1) return(TRUE);
	if (!by_name)   printf("%s  User is not logged in.\n", other_tty);
	else {
		if (found == 0) 
			 printf("User %s is not logged in.\n", other_name);
		else printf("User %s is logged in more than once.\n", other_name);
	}
	return(FALSE);
}

int ring(device)
char *device;
{
	FILE *out_s;

	if (!(out_s = fopen(device, "w"))) return(FALSE);

	fprintf(out_s, "\n%s is phoning you from %s. Type `phone %s' to answer.\007\n\n",
					my_name, my_tty, my_name);
	fclose(out_s);
	return(TRUE);
}

send_both(type, ch)
register int type;
register int ch;
{
	send_local (type, ch);
	send_remote(type, ch);
}

send_local(type, ch)
register int type;
register int ch;
{
	s_pkt.origin = LOCAL; s_pkt.type = type; s_pkt.keypress = ch;
	msgsnd(loc_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin), 0);
}

send_remote(type, ch)
register int type;
register int ch;
{
	s_pkt.origin = REMOTE; s_pkt.type = type; s_pkt.keypress = ch;
	msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin), 0);
}
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'
phone:	phone.c
	cc -O phone.c -o phone -lcurses

lint:
	lint phone.c -lcurses

install:
	cp phone /usr/lbin

shar:
	shar phone.1 phone.c Makefile >phone.sh
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0