[alt.sources] nmsg - a 'write' style program to send messages across the net

lm03_cif@troi.cc.rochester.edu (Larry Moss) (03/07/91)

Nmsg was written to send messages to users across the net when a 'talk'
isn't desired or simply doesn't work.  Periodically there are requests
for sample code using sockets.  If nothing else this is an example of
that.  It may be preferred to /bin/write even on the local host since it
also does things like word wrap.

The program works farily well -- usually.  It can basically be expected
to work on a sun4 running sunOS 4.0.3.  It should work on other machines
and other versions of the OS (not sure about other flavors of UNIX) but
for some reason it doesn't.  Before I explain the problems I'd just like
to apologize for posting a program that doesn't have all the bugs worked
out.  I wasn't really sure if it was appropriate to post something like
this, but several people that I asked thought it was a reasonable idea.
I just hope someone reading this can help us out.  I'm posting this now
for two reasons.  1) Gavin and I no longer have any idea what to try to
fix it and would like suggestions.  2) We have now been asked by system
administrators here to not run a daemon on these machines until the bugs
are out.  It's a perfectly reasonable request but one of our problems is
that it doesn't compile on the one machine we do have permission to run it
on (a sun3 running 4.1) so again any suggestions would be appreciated.

Problems:
Sun4 OS4.0.3 - when the client is paused (^Z) the daemon stops also.
I have no idea why this is happening but this only a problem if the
client is then killed before it receives (and sends on to the daemon) a
SIGCONT.  If this happens the daemon just hangs uselessly.

Sun4 OS4.1 - If the client is stopped (^Z), continuing it (fg) does not
send a SIGCONT to the client so it never goes back into cbreak and noecho
modes.

Sun3 OS4.1 - Doesn't compile - but we haven't spent much time on that.

So we're really confused about why it behaves so differently on the
differnet machines.

Thanks for help, suggestions, etc...
Larry
--
lm03_cif@uhura.cc.rochester.edu / "I'm so tired... I was up all night
lmo3_ss@db1.cc.rochester.edu   / trying to round off infinity."
lmo3_ss@uordbv.bitnet         / - Stephen Wright
--

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README input.c msgd.c nmsg.c socklib.c utmp.c input.h
#   socket.h socketnum.h msgd.man nmsg.man Makefile TODO
# Wrapped by lm03_cif@troi.cc.rochester.edu on Wed Mar  6 15:03:06 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(2078 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XWHAT IS NMSG?
XIt's basically a program to replace /bin/write that will send messages
Xover the net and do other really nifty things.  Well, kinda nifty anyway.
XIf nothing else it is an example of network communication and stuff.
XThere seem to be requests fairly often for samples like that for people
Xto look at.
X
XBRIEF HISTORY (read: disclaimer) 
XThis program was originally bits of little things that were kind of
Xthrown together.  Most of the stuff in it (handling sockets, handling
Xinput in cbreak mode, etc) was written because neither of us had ever
Xdone it before and thought it might be interesting.  Putting stuff in a
Xuseful program was more or less an afterthought.  In fact the socket
Xstuff was actually sitting around for over a year because after writing
Xit I didn't know what to do with it.  It does do exactly what it was
Xmeant to most of the time.  We both learned a lot from it, but ran into
Xsome problems that we can't solve.  Since this was originally several
Xdiffernet hacked together pieces that two people were working on it may
Xnot seem as clear to someone reading it as we think.  We have tried to
Xleave the code clean and easy to read and tried to do things efficiently
X(except where pure laziness took over).
X
XINSTALLATION
XShould be as simple as editing the Makefile.  Everything that should need
Xto be changed is commented.  If you wish to change the port that the
Xserver runs on, edit socketnum.h.  This is the only way we have right now
Xof restricting who can send messages to particular machines.
X
XTHANKS TO
XMark Sirota who we borrowed some code from.  utmp.c was taken from him.
XThat's one of the examples of pure laziness.
X
XCOPYING
XDo whatever you wish with the code as long as we get credit for what
Xwe've done.  If you find this useful in any way or wish to do something
Xwith parts of the code that it wasn't originally intended for, let us
Xknow.  We're really just wondering if we've managed to do something
Xuseful with our time.  (Consider it an ego thing.)
X
X- Larry
X
XLarry Moss	lm03_cif@cc.rochester.edu
XGavin Stark	gest_ss@cc.rochester.edu
END_OF_FILE
if test 2078 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'input.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'input.c'\"
else
echo shar: Extracting \"'input.c'\" \(9749 characters\)
sed "s/^X//" >'input.c' <<'END_OF_FILE'
X/*
X * input.c: Larry Moss (lm03_cif@cc.rochester.edu)
X *	    Fall, 1990
X *
X * This was written as part of nmsg, a version of write that can be used
X * over the net, by Larry Moss and Gavin Stark.  Feel free to do whatever 
X * you want with this code as long as my name stays on it and I receive
X * credit for what I've done.
X */
X 
X/*
X * Here are the i/o routines to be used with nmsg.  This code deals with 
X * ioctl to set the terminal the way we want it (cbreak mode, no echo).  Some
X * of this may not be done in the cleanest ways, but it is functional.  All
X * normal UNIX line editting stuff (C-U, C-W, etc.) are included.  I think it
X * handles tabs correctly also.  I've included a bunch of things just to learn 
X * how to do it so if anyone reading this has suggestions for doing things 
X * better, I'm listening.
X */
X
X#include <stdio.h>
X#include <string.h>
X#include <signal.h>
X#include <sgtty.h>
X#include <ctype.h>
X#include <pwd.h>
X#include <time.h>
X#include <vfork.h>
X#include <sys/types.h>
X#include <sys/wait.h>
X
X#include "input.h"
X
Xvoid getmessage(message, maxlength)
Xchar	*message;
Xint	maxlength;
X{
X	FILE		*fp;
X	char		savename[1000];
X	int		messagelength;
X	time_t		cur_time;
X	struct tm	*t;
X	char		tempbuf[BUFSIZ];
X
X	/*
X	 * Set up the header for the message. Get the time.
X	 */
X	time(&cur_time);
X	t = localtime(&cur_time);
X
X	get_input(message);
X
X	sprintf(tempbuf, "\007\r\nFrom %s on %s at %d:%02d\r\n%s\r\n",
X		getusername(), getttyname(), t->tm_hour,
X		t->tm_min,message);
X
X	/*
X	 * Save the message
X	 */
X	sprintf(savename, "%s/.oldmsg", getenv("HOME"));
X
X	if((fp = fopen(savename, "w")) == NULL)
X		fprintf(stderr, "error writing to %s", savename);
X	else 
X		fputs(message, fp);
X
X	fclose(fp);
X
X	/*
X	 * The text needs to be in message for the calling procedure.
X	 */
X	strcpy(message,tempbuf);
X}
X
X
Xvoid get_input(message)
Xchar	*message;
X{
X	int		messagelength, c, i, ctrlc=0, cp, column=0;
X	int		linelength;
X	char		temp[80];
X
X	messagelength = strlen(message);
X
X	/*
X	 * If the program is not being run interactively it's real simple
X	 * to get the message.
X	 */
X	if(!isatty(0)) {
X		messagelength = readmessage(message, messagelength, stdin);
X		return;
X	}
X	
X	/*
X	 * If the program is being used interactively we want to set it up
X	 * for things like word wrap and make sure it handles signals
X	 * correctly.
X	 */
X	setterm();
X
X	puts("Enter your message, and terminate with Ctl-D or '.'.");
X	printf("Messages are limited to %d characters.\n\n", MAXLENGTH);
X	fflush(stdout);
X
X	while((c = getchar()) != CTL('d')) {
X		switch (c) {
X			case CTL('c'):
X				if(ctrlc)
X					interrupt();
X				else {
X					ctrlc=1;
X					puts("\n** interrupt -- one more to kill message **");
X				}
X				break;
X			case CTL('h'):
X			case DEL:
X				if(messagelength > 0) {
X					BACKSPACE;
X					message[--messagelength] = NULL;
X					column--;
X				}
X				break;
X			case '\n':
X				putchar(c);
X				message[messagelength++] = '\r';
X				message[messagelength++] = '\n';
X				if ((message[messagelength-3] == '.') && (column ==1)) {
X					message[messagelength-3] = NULL;
X					unsetterm();
X					return;
X				}
X				column = 0;
X				break;
X			case KILL:
X				while ((message[messagelength-1] != '\n') &&
X				       (messagelength > 0)) {
X					message[--messagelength] = NULL;
X					BACKSPACE;
X				}
X				column = 0;
X				break;
X			case WERASE:
X				while ((!isspace(message[messagelength-1])) &&
X				       (messagelength > 0)) {
X					message[--messagelength] = NULL;
X					BACKSPACE;
X					column--;
X				}
X				break;
X			case CTL('r'):
X				putchar('\n');
X				fputs(&message[messagelength - column],stdout);
X				break;
X			case TAB:
X				for(i=0; i <= (column % 8); i++) {
X					putchar(' ');
X					message[messagelength++] = ' ';
X					column++;
X				}
X				break;
X			case '~':
X				if(message[messagelength-1] == '\n' || messagelength ==0) {
X					putchar(c);
X					messagelength = handle_tilde(message,messagelength);
X					break;
X				}
X			default:
X				if(column >= 78) {
X					for(cp=messagelength;!isspace(message[cp]); cp--)
X						BACKSPACE;
X					strcpy(temp, &message[cp+1]);
X					message[cp] = '\r'; message[cp+1]='\n';
X					strcpy(&message[cp+2], temp);
X					messagelength++;
X					fputs(&message[cp],stdout);
X					column = messagelength - cp;
X				}
X				putchar(c);
X				message[messagelength++] = c;
X				column++;
X				break;
X		}
X	}
X	puts("EOF");
X	unsetterm();
X}
X
X/*
X * Set the terminal to cbreak mode, turn off echo and prevent special
X * characters from affecting the input.  We will handle EOF and other fun
X * stuff our own way.  Backup copies of the current setup will be kept
X * to insure the terminal gets returned to its initial state.
X */
Xvoid setterm() {
X	struct sgttyb	termrec;
X	struct tchars	trec;
X
X	signal(SIGINT, interrupt);
X	signal(SIGTSTP, pause);
X	signal(SIGSTOP, pause);
X	signal(SIGCONT, cont);
X
X	gtty(0, &termrec);
X	termrec.sg_flags |= CBREAK;
X	termrec.sg_flags &= ~ECHO;
X	stty(0, &termrec);
X
X	ioctl(0, TIOCGETC, &trec);
X	trec.t_quitc = (char) -1;
X	trec.t_eofc = (char) -1;
X	trec.t_intrc = (char) -1;
X	ioctl(0, TIOCSETC, &trec);
X}
X
X/*
X * Reset terminal settings to the way they were before.  well, that's not
X * really true.  Since I didn't save the original settings, I'll set things
X * to the way they normally are.  I suppose I should do this correctly, but
X * for now, this works.
X */
Xvoid unsetterm() {
X	struct sgttyb	termrec;
X	struct tchars	trec;
X
X	signal(SIGINT, SIG_IGN);
X	signal(SIGTSTP, SIG_IGN);
X	signal(SIGSTOP, SIG_IGN);
X	signal(SIGCONT, SIG_IGN);
X
X	gtty(0, &termrec);
X	termrec.sg_flags &= ~CBREAK;
X	termrec.sg_flags |= ECHO;
X	stty(0, &termrec);
X
X	ioctl(0, TIOCGETC, &trec);
X	trec.t_quitc = CTL('\\');
X	trec.t_eofc = CTL('d');
X	trec.t_intrc = CTL('c');
X	ioctl(0, TIOCSETC, &trec);
X}
X
X/*
X * Interrupt handlers
X */
Xpause() {
X	unsetterm();
X	kill(getpid(), SIGSTOP);
X}
X
Xcont() {
X	setterm();
X}
X
Xinterrupt() {
X	unsetterm();
X	putchar('\n');
X	exit(1);
X}
X
X
X/*
X * Handle tilde commands in messages.
X */
Xint handle_tilde(message, messagelength)
Xchar	*message;
Xint	messagelength;
X{
X	char	command[120], line[MAXLENGTH], *editor, oldmsg[120];
X	static char	tempfile[] = "/tmp/msg.XXXXXX";
X	FILE	*fp;
X	int	i;
X
X	unsetterm();
X	fgets(command, 120, stdin);
X	switch(command[0]) {
X		case 'v':
X			/*
X			 * An editor will be invoked
X			 * First save text to a temp file so the editor can
X			 * deal with it.
X			 */
X			mktemp(tempfile);
X			if((fp=fopen(tempfile, "w")) == NULL) {
X				fprintf(stderr, "error opening %s", tempfile);
X				return;
X			}
X			messagelength=0;
X			fputs(message, fp);
X			fclose(fp);
X
X			/*
X			 * Figure out which editor will be used.
X			 * /usr/ucb/vi will be used unless the editor
X			 * environment variable is present.
X			 */
X			if((editor=getenv("EDITOR")) == NULL)
X				editor="/usr/ucb/vi";
X
X			/*
X			 * Start the new process and ignore interrupts
X			 * until the editing is done so th eprocesses don't
X			 * fight for input.
X			 */
X			if(vfork() == 0) {
X				execl(editor, editor, tempfile, (char *)NULL);
X				exit(0);
X			}
X			wait(0);
X
X			/*
X			 * Read in the modified message and get rid of the
X			 * temp file.
X			 */
X			fp=fopen(tempfile, "r");
X			messagelength=readmessage(message, messagelength, fp);
X			fclose(fp);
X			unlink(tempfile);
X			strcpy(tempfile, "/tmp/msg.XXXXXX");
X			puts("\n-- Continue editing message --");
X			break;
X		case 'r':
X			/*
X			 * read contents of file into message.  If no file
X			 * is specified try to read the last message sent.
X			 */
X			if(strlen(command) == 2) {
X				sprintf(oldmsg, "%s/.oldmsg", getenv("HOME"));
X				if((fp=fopen(oldmsg, "r")) == NULL)
X					fputs("no old message\n");
X				else
X					messagelength=readmessage(message, messagelength, fp);
X			}
X			/*
X			 * read the file specified.  Handle tildes if
X			 * necessary
X			 */
X			else {
X				sprintf(oldmsg, "%s", &command[1]);
X				oldmsg[strlen(oldmsg)-1] = NULL;
X				for(i=0;oldmsg[i]==' ' || oldmsg[i]=='\t'; i++);
X				if(oldmsg[i]=='~')
X				if(strcpy(&oldmsg[i], expand(&oldmsg[i])) == NULL) {
X					fprintf(stderr, "unknown user\n");
X					break;
X				}
X				if((fp=fopen(&oldmsg[i], "r")) == NULL) {
X					fprintf(stderr, "***error opening %s***\n", oldmsg);
X				}
X				else {
X					messagelength=readmessage(message, messagelength, fp);
X				}
X			}
X			fclose(fp);
X			fprintf(stderr, "(Continue.)\n");
X			break;
X		case 'p':
X			/*
X			 * print the current message.
X			 */
X			puts(message);
X			break;
X	}
X	setterm();
X	return messagelength;
X}
X
X/*
X * this is the routine used to read a message from a file or stdin when
X * input is not coming from a tty.
X */
Xint readmessage(message, messagelength, fp)
Xchar	*message;
Xint	messagelength;
XFILE	*fp;
X{
X	char	line[MAXLENGTH], answer[80];
X	int	linelength;
X	
X	while (fgets(line, MAXLENGTH, fp) != (char *) NULL) {
X		linelength = strlen(line);
X		if (messagelength + linelength + 2 > MAXLENGTH - 1) {
X			linelength = (MAXLENGTH - messagelength - 3);
X			fprintf(stderr, "The last line was truncated to %d characters.\nSend anyway? (y/n) ",
X				linelength);
X			fgets(answer, 80, fp);
X			if (answer[0] != 'y')
X				exit(2);
X			line[linelength]= (char) NULL;
X		}
X		strcpy(message + messagelength, line);
X		messagelength += linelength;
X	}
X	return messagelength;
X}
X
X/*
X * Expand tildes in filenames.  The only way I know of to get the home
X * directory of another user is to look up passwd information.  If we want
X * the user's home directory we can just get it from the environment.
X */
Xchar *expand(filename)
Xchar	*filename;
X{
X	struct passwd	*p;
X	char		tempmsg[120];
X
X	if(filename[1] == '/') {
X		sprintf(tempmsg, "%s%s", getenv("HOME"), &filename[1]);
X		return tempmsg;
X	}
X
X	/*
X	 * Try to get user information.  If user information can't be
X	 * obtained return NULL
X	 */
X	if((p = getpwnam(strtok(&filename[1], "/"))) == (struct passwd *)NULL)
X		return (char *)NULL;
X	sprintf(tempmsg, "%s/%s", p->pw_dir, strtok(NULL, " "));
X	return tempmsg;
X}
END_OF_FILE
if test 9749 -ne `wc -c <'input.c'`; then
    echo shar: \"'input.c'\" unpacked with wrong size!
fi
# end of 'input.c'
fi
if test -f 'msgd.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'msgd.c'\"
else
echo shar: Extracting \"'msgd.c'\" \(4283 characters\)
sed "s/^X//" >'msgd.c' <<'END_OF_FILE'
X/*
X * msgd: daemon for the msg program.  Allows users to send messages to
X * other users screens across the net.  The daemon listens to a port and
X * waits for reqeuests to send messages.  If the user is logged in and
X * allowing messages, the daemon accepts the message from the sending
X * machine.
X */
X
X/*
X * Written by Larry Moss (lm03_cif@uhura.cc.rochester.edu)
X *	      Gavin Stark (gest_ss@uhura.cc.rocehster.edu)
X * Fall, 1990
X */
X
X#include <signal.h>
X#include <sys/wait.h>
X#include <sys/time.h>
X#include <sys/resource.h>
X#include "socket.h"			/* Socket defs */
X#include "socketnum.h"			/* Socket number */
X
Xvoid deal_with_connection();
Xvoid dummy();
Xvoid restart();
X
Xvoid main()
X{
X	int s;		/* Socket that we are working with */
X	FILE		*logfp;
X
X	/*
X	 * Set things up to handle SIG_CHLDs so that they can die normally.
X	 * Then disconnect the daemon from the shell.  We fork and only let
X	 * the child live so the shell thinks the process is finished.
X	 */
X	signal(SIGCHLD, dummy);
X	close(0); close(1); close(2);
X	if(fork())
X		exit(0);
X	setpgrp(0, 0);
X	
X	s=sockit();	/* Go set the socket stuff */
X
X	listen(s, 5);	/* listen for as many as 5 connections at once */
X	for (;;) {
X		struct sockaddr_in	his_addr;
X		int			his_len = sizeof (his_addr);
X		int			ns;
X
X		/*
X		 * wait for connection to be established.
X		 * If accept returns something less than zero there was an
X		 * error.  For now we'll ignore it since errors will occur
X		 * when a child dies.
X		 */
X		if ((ns = accept(s, &his_addr, &his_len)) > 0) {
X			if(fork() == 0) {
X				close(s);
X				deal_with_connection(ns);  /* Go and deal with a connection */
X				exit();
X			}
X		}
X		close(ns);
X	}
X}
X
X/*
X * Talk to calling program and get some information, like who it is that's
X * calling and the actual message.  In other words, the bulk of the
X * program.  It should probably return some error code if it fails for some
X * unexpected reason.
X */
Xvoid deal_with_connection(ns)
Xint ns;
X{
X	FILE *fp;				/* Input/Output file */
X	char whocalled[200],to[200],tty[200];   /* Info variables */
X	char			s[BUFSIZ];	/* string to get message */
X	char			message[BUFSIZ];
X
X	struct utmp		*u;		/* utmp entry */
X	extern struct utmp	*getutmpline(), /* get utmp programs */
X				*getutmpname(); 
X
X	FILE			*ttyfp;		/* tty file */
X	int			loop;
X
X	fp=fdopen(ns,"w+");	/* Open file to socket */
X	rewind(fp);
X
X	fscanf(fp,"%s %s %s",whocalled,to,tty); 	/* Get info from the remote program */
X
X	rewind(fp);			/* Rewind the file for writing */
X
X	if(strcmp(tty,"NONE")==0) 	/* NONE is sent because I couldn't */
X		tty[0]=0;		/* figure out how to send the NULL */
X					/* pointer it is expecting */
X
X	if (tty[0] != 0) 
X		u = getutmpline(tty);	
X	else 
X		u = getutmpname(to);	/* GET UTMP NAME */
X	
X
X	/*
X	 * Check to see if person is logged in.  It's kind of hard to send
X	 * a message to someone that isn't there.  It should be able to
X	 * check for a user on a particular tty also.  That still needs to
X	 * be implemented.
X	 */
X	if (u == (struct utmp *) NULL || strncmp(u->ut_name, to, 8) != 0) {
X		if (tty[0] != 0) {
X			fprintf(fp,"ERROR\n");
X			fprintf(fp, "TTY_ERROR: %s is not logged on to %s\n",
X				to, tty);
X		}
X		else  {
X			fprintf(fp, "ERROR\n");
X			fprintf(fp, "%s is not logged on to %s\n", to,get_local_hostname());
X		}
X		fclose(fp);
X		return;
X	}
X
X	/*
X	 * Set up a string containing the full pathname to the line.
X	 */
X	sprintf(tty, "/dev/%.8s", u->ut_line);
X
X	/*
X	 * Check to see if the person is refusing messages.  I hate it when
X	 * people do that, but I know some people REALLY don't want to be
X	 * bugged, so....
X	 */
X	if ((ttyfp = fopen(tty,"w")) == (FILE *)NULL ) {
X		fprintf(fp,"ERROR\n");
X		fprintf(fp, "%.8s is not receiving messages on %.8s\n",
X			u->ut_name, u->ut_line);
X		fclose(fp);
X		return;
X	}
X	fprintf(fp,"NOERROR\n");	/* Send that no errors were found */
X
X	rewind(fp); 			/* rewind for reading */
X
X	/*
X	 *  Clear message buffer.  Read message from socket, and send to tty.
X	 */
X
X	for(loop=0;loop<BUFSIZ;loop++) message[loop]=0;	/* Clear message */
X
X	fread(message,BUFSIZ,1,fp);		/* Read message */
X
X	fprintf(ttyfp,message);			/* print message */
X
X        fclose(ttyfp);			/* Close the tty */
X
X/*	rewind(fp);			/* Rewind just in case */
X	fclose(fp);
X	return;				/* Do I need to document this */
X}
X
Xvoid dummy() {
X	wait(0);
X}
END_OF_FILE
if test 4283 -ne `wc -c <'msgd.c'`; then
    echo shar: \"'msgd.c'\" unpacked with wrong size!
fi
# end of 'msgd.c'
fi
if test -f 'nmsg.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'nmsg.c'\"
else
echo shar: Extracting \"'nmsg.c'\" \(3504 characters\)
sed "s/^X//" >'nmsg.c' <<'END_OF_FILE'
X/*
X * nmsg: Client for the network version of msg.  Allows users to send
X * messages to other users screens across the net.  The client parses the
X * command line and gets the message to send.  The message is passed on to
X * the daemon to deliver.
X */
X
X/*
X * Written by Larry Moss (lm03_cif@uhura.cc.rochester.edu)
X *	      Gavin Stark (gest_ss@uhura.cc.rocehster.edu)
X * Fall, 1990
X */
X
X#include <stdio.h>
X#include <string.h>
X#include "socketnum.h"
X#include "socket.h"
X
X
Xextern char *ttyname();
X
Xvoid	usage();
Xvoid	interrupt();
Xvoid	sendit();
Xvoid	getmessage();
Xchar	*getttyname();
Xchar	*getusername();
Xint	daemon_sockit();
X
Xvoid main(argc,argv)
Xint argc;
Xchar **argv;
X{
X	int trc=argc;
X
X	int	daemon_socket;		/* Socket File number */
X	char	toaddress[255],		/* Host to connect to */
X		tty[50],	/* TTY to address */
X		localhost[255],		/* Our host */
X		errorstate[5],
X		s[BUFSIZ],
X		message[BUFSIZ];
X
X	int	i;
X	FILE	*fp;			/* Socket Read/Write file */
X	char	*name, *host;   /* Strings used to parse username and host */	
X
X	/*
X	 * use the local address as the default
X	 */
X	strcpy(localhost,get_local_hostname());
X
X	/*
X	 * If no arguments were given to the program, just exit.  Otherwise
X	 * parse the command line.
X	 */
X	if(argc == 1)
X		usage(argv[0]);
X
X
X	/*
X	 * Parse TTY info
X	 */
X
X	if(argc<3) 
X		strcpy(tty,"NONE");
X	else	
X		strcpy(tty,argv[2]);
X
X
X	if((name = strtok(argv[1], "@")) != (char *)NULL) 
X		host=strtok((char *)NULL,"\0");
X
X	/*
X 	 * if host was parsed, copy that hostname into toaddress
X	 */
X	if(host!=0) strcpy(toaddress,host);
X        	else strcpy(toaddress,localhost);
X
X	/*
X	 * open connection to remote daemon
X	 */
X	daemon_socket=daemon_sockit(toaddress);
X
X
X	fp=fdopen(daemon_socket,"w+");	/* Open socket */
X
X	/*
X	 * Send data to daemon
X	 */
X	fprintf(fp, "\r%s\n%s\n%s\n", getpwuid(getuid())->pw_name,name,tty); 
X
X	rewind(fp);			/* Rewind */
X
X	fscanf(fp,"%s",errorstate);   /* See if any errors were encountered */
X
X	if(strcmp(errorstate,"ERROR")==0) { /* If there was an error get it */
X		fgets(s,BUFSIZ,fp);		
X		fgets(s,BUFSIZ,fp);
X
X		printf("%s\n",s);
X		fclose(fp);
X		exit(0);
X	}
X	rewind(fp);
X
X	/*
X	 * Send the message
X	 */
X	getmessage(message, BUFSIZ);	/* Get the message */
X	fputs(message, fp);			/* Put the message */
X	fclose(fp);	/* Close the socket */
X}
X
X
Xvoid usage(progname)
Xchar	*progname;
X{
X	fprintf(stderr, "Usage: %s <user>[@host] [tty]\n", progname);
X	exit(1);
X}
X
X		/*  
X		 *  This still belongs to Sirota
X		 */
X
X/* 
X * Get the username associated with the current uid.
X */
Xchar *getusername()
X{
X	struct passwd	*p;
X	char		*cp;
X	static char	name[BUFSIZ];
X
X	/*
X	 * Get the passwd file entry associated with the current uid
X	 */
X	if ((p = getpwuid(getuid())) == (struct passwd *) NULL) {
X		fprintf(stderr, "Who the hell are you, anyway?\n");
X		exit(-1);
X	}
X
X	/*
X	 * Pretend that the GECOS field contains only the full name
X	 */
X	if ((cp = index(p->pw_gecos, ',')) != (char *) NULL)
X		*cp = (char) NULL;
X
X	/*
X	 * Create and return a string containing the full name and username
X	 */
X	sprintf(name, "%s <%s@%s>", p->pw_gecos, p->pw_name, get_local_hostname());
X	return name;
X}
X
X/*
X * Get the name of the tty associated with stdout (2).  If unknown, say so.
X */
Xchar *getttyname()
X{
X	char		*tty, *cp;
X
X	/*
X	 * Get the ttyname
X	 */
X	tty = ttyname(2);
X
X	/*
X	 * If it's unknown, say so.
X	 */
X	if (tty == (char *) NULL)
X		return "[tty unknown]";
X
X	/*
X	 * Strip off the pathname
X	 */
X	if ((cp = rindex(tty, '/')) != (char *) NULL)
X		tty = cp + 1;
X
X	/*
X	 * Return it
X	 */
X	return tty;
X}
END_OF_FILE
if test 3504 -ne `wc -c <'nmsg.c'`; then
    echo shar: \"'nmsg.c'\" unpacked with wrong size!
fi
# end of 'nmsg.c'
fi
if test -f 'socklib.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'socklib.c'\"
else
echo shar: Extracting \"'socklib.c'\" \(3211 characters\)
sed "s/^X//" >'socklib.c' <<'END_OF_FILE'
X#include "socket.h"
X#include "socketnum.h"
X
X
X
Xchar *
Xget_local_hostname()
X{
X	static char	local_host_name[128];
X	static int	host_name_is_set = NO;
X
X	if (host_name_is_set == NO) {
X		if (gethostname(local_host_name, sizeof (local_host_name)) == -1) {
X			perror("gethostname:");
X			strcpy(local_host_name, "localhost");
X		}
X		host_name_is_set = YES;
X	}
X
X	return local_host_name;
X}
X
X/* Hostname to address takes a pointer to a internet socket address
X   (struct sockaddr_in) and a hostname, and puts the internet address
X   of that host in the sockaddr.  This particular routine also takes
X   an internet host number (e.g., 128.20.45.56??) and converts that
X   to an address (an address is really just a 32 bit number). */
X
Xhostname_to_address(sin, host_name)
Xstruct sockaddr_in	*sin;
Xchar	*host_name;
X{
X	struct hostent	*hp;
X
X	bzero(sin, sizeof (*sin));
X	if (isdigit(host_name[0])) {
X		unsigned long	addr;
X
X		addr = inet_addr(host_name);
X		if ((int) addr != -1) {
X			bcopy((char *) &addr, (char *) &sin->sin_addr, sizeof (addr));
X			sin->sin_family = htons(AF_INET);
X			return YES;
X		}
X	}
X	if ((hp = gethostbyname(host_name)) == NULL)
X		return NO;
X	bcopy(hp->h_addr, (char *) &sin->sin_addr, hp->h_length);
X	sin->sin_family = hp->h_addrtype;
X
X	return YES;
X}
X
X/* This takes a socket, and address, and the length of the address, which
X   is just the sizeof (struct sockaddr_in), and binds the socket to that
X   address and SOME free port.  It then does a getsockname().  Basically,
X   it sets the port to 0, does a bind system call, which chooses a random
X   port us.  The system chooses the random port BUT it doesn't store that
X   random port back into the ADDR structure.  So, since I might want to
X   know what port got assigned, I do a getsockname() system call, which
X   given a socket, puts the address (which contains the port) in the
X   address structure.  Sorry if that made no sense. */
X
Xbind_and_name(s, addr, len)
Xstruct sockaddr_in	*addr;
X{
X	addr->sin_port = 0;
X	if (bind(s, addr, len) == -1)
X		return -1;
X	if (getsockname(s, addr, &len) == -1)
X		return -1;
X}
X
X
Xset_port(addr, port)
Xstruct sockaddr_in	*addr;
X{
X	addr->sin_port = htons((short) port);
X}
X
Xsockit()
X{
X	int s;
X	struct sockaddr_in	my_addr;
X	int len=sizeof (my_addr);
X
X	s = socket(AF_INET, SOCK_STREAM, 0);
X	if (hostname_to_address(&my_addr, get_local_hostname()) < 0) {
X		perror("reply: hostname_to_address");
X		exit(1);
X	}
X	set_port(&my_addr, SERVER_PORT);
X	if (bind(s, &my_addr, len) < 0)
X		perror("bind:");		/* something's wrong */
X	return s;
X}
X
X
X/*
X * open connection to remote daemon
X */
Xdaemon_sockit(toaddress)
Xchar toaddress[255];
X{
X	int daemon_socket;
X
X	struct sockaddr_in	my_addr,daemon_addr;
X
X	if (hostname_to_address(&my_addr, get_local_hostname()) < 0) {
X		perror("reply: hostname_to_address");
X		exit(1);
X	}
X
X	if (hostname_to_address(&daemon_addr,toaddress) < 0) {
X		perror("reply: hostname_to_address"); 
X		exit(1);
X	}
X
X	daemon_socket = socket(AF_INET, SOCK_STREAM, 0);
X	bind_and_name(daemon_socket, &my_addr, sizeof (my_addr));
X	set_port(&daemon_addr, SERVER_PORT);
X
X	if (connect(daemon_socket, &daemon_addr, sizeof (daemon_addr)) < 0) {
X		perror("Could not connect with remote daemon"); 
X		exit();
X	}
X	return daemon_socket;
X}
END_OF_FILE
if test 3211 -ne `wc -c <'socklib.c'`; then
    echo shar: \"'socklib.c'\" unpacked with wrong size!
fi
# end of 'socklib.c'
fi
if test -f 'utmp.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'utmp.c'\"
else
echo shar: Extracting \"'utmp.c'\" \(1383 characters\)
sed "s/^X//" >'utmp.c' <<'END_OF_FILE'
X/*
X * This program is Copyright (c) 1988, 1989 by Mark Sirota.
X * It is provided to you without charge, and with no warranty.
X * You may give away copies of libutmp, including sources, provided that this
X * notice is included in all the files.
X */
X
X#include <utmp.h>
X#include <sys/file.h>
X#include <stdio.h>
X
Xstatic int	utmpfd = -1;
X
X/*
X * Get the next entry in /etc/utmp
X */
Xstruct utmp *
Xgetutmpent()
X{
X	static struct utmp	u;
X
X	if (utmpfd == -1)
X		if ((utmpfd = open("/etc/utmp", O_RDONLY)) == -1)
X			return (struct utmp *) NULL;
X
X	if (read(utmpfd, &u, sizeof (struct utmp)) != sizeof (struct utmp))
X		return (struct utmp *) NULL;
X
X	return &u;
X}
X
X/*
X * Get a utmp entry according to ut_line.
X */
Xstruct utmp *
Xgetutmpline(line)
Xregister char	*line;
X{
X	register struct utmp	*u;
X
X	setutmpent();
X	while ((u = getutmpent()) != (struct utmp *) NULL) {
X		printf("testline in utmp.c   %s\n",u->ut_line);
X		if (strncmp(u->ut_line, line, 8) == 0)
X			break;
X	}
X	return u;
X}
X
X/*
X * Get a utmp entry according to ut_name.
X */
Xstruct utmp *
Xgetutmpname(name)
Xregister char	*name;
X{
X	register struct utmp	*u;
X
X	setutmpent();
X	while ((u = getutmpent()) != (struct utmp *) NULL)
X		if (strncmp(u->ut_name, name, 8) == 0)
X			break;
X	return u;
X}
X
X/*
X * Rewind /etc/utmp
X */
Xint
Xsetutmpent()
X{
X	lseek(utmpfd, 0L, L_SET);
X}
X
X/*
X * Close /etc/utmp
X */
Xint
Xendutmpent()
X{
X	close(utmpfd);
X	utmpfd = -1;
X}
END_OF_FILE
if test 1383 -ne `wc -c <'utmp.c'`; then
    echo shar: \"'utmp.c'\" unpacked with wrong size!
fi
# end of 'utmp.c'
fi
if test -f 'input.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'input.h'\"
else
echo shar: Extracting \"'input.h'\" \(709 characters\)
sed "s/^X//" >'input.h' <<'END_OF_FILE'
X/*
X * header file for:
X * input.c: Larry Moss (lm03_cif@cc.rochester.edu)
X *	    Fall, 1990
X *
X * This was written as part of nmsg, a version of write that can be used
X * over the net, by Larry Moss and Gavin Stark.  Feel free to do whatever 
X * you want with this code as long as my name stays on it and I receive
X * credit for what I've done.
X */
X
X#define	LINELENGTH	78
X#define BACKSPACE	fputs("\b \b", stdout)
X#define DEL		127
X#define WERASE		23
X#define KILL		21
X#define TAB		9
X#define CTL(c)		(c & 037)
X#define MAXLENGTH	1024
X
Xvoid get_input();
Xvoid setterm();
Xvoid unsetterm();
Xint handle_tilde();
Xint interrupt();
Xint pause();
Xint cont();
Xint child();
Xint readmessage();
Xchar *getenv();
Xchar *expand();
END_OF_FILE
if test 709 -ne `wc -c <'input.h'`; then
    echo shar: \"'input.h'\" unpacked with wrong size!
fi
# end of 'input.h'
fi
if test -f 'socket.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'socket.h'\"
else
echo shar: Extracting \"'socket.h'\" \(253 characters\)
sed "s/^X//" >'socket.h' <<'END_OF_FILE'
X#define NO 0
X#define YES 1
X#include <ctype.h>
X#include <fcntl.h>
X#include <netdb.h>
X#include <pwd.h>
X#include <stdio.h>
X#include <strings.h>
X#include <sys/param.h>
X#include <sys/socket.h>
X#include <sys/types.h>
X#include <utmp.h>
X#include <netinet/in.h>
END_OF_FILE
if test 253 -ne `wc -c <'socket.h'`; then
    echo shar: \"'socket.h'\" unpacked with wrong size!
fi
# end of 'socket.h'
fi
if test -f 'socketnum.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'socketnum.h'\"
else
echo shar: Extracting \"'socketnum.h'\" \(25 characters\)
sed "s/^X//" >'socketnum.h' <<'END_OF_FILE'
X#define SERVER_PORT 2834
END_OF_FILE
if test 25 -ne `wc -c <'socketnum.h'`; then
    echo shar: \"'socketnum.h'\" unpacked with wrong size!
fi
# end of 'socketnum.h'
fi
if test -f 'msgd.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'msgd.man'\"
else
echo shar: Extracting \"'msgd.man'\" \(465 characters\)
sed "s/^X//" >'msgd.man' <<'END_OF_FILE'
X.TH MSGD 8 "23 JAN 1991"
X.SH NAME
Xmsgd \- Delivers messages sent with nmsg(1).
X.SH SYNOPSIS
X\fBmsgd\fP
X.SH DESCRIPTION
X\fBmsgd\fP is the daemon for nmsg(1).  It receives messages sent over the
Xinternet and delivers them to the appropriate user.  It will
Xautomagically disconnect itself from the shell and run in the background.
X.RE
X.SH FILES
X/etc/utmp
X.SH "SEE ALSO"
Xnmsg(1)
X.SH AUTHORS
XGavin Stark gest_ss@cc.rochester.edu
X.br
XLarry Moss lm03_cif@cc.rochester.edu
END_OF_FILE
if test 465 -ne `wc -c <'msgd.man'`; then
    echo shar: \"'msgd.man'\" unpacked with wrong size!
fi
# end of 'msgd.man'
fi
if test -f 'nmsg.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'nmsg.man'\"
else
echo shar: Extracting \"'nmsg.man'\" \(1870 characters\)
sed "s/^X//" >'nmsg.man' <<'END_OF_FILE'
X.TH NMSG 1 "23 JAN 1991"
X.SH NAME
Xnmsg \- Send a message to a user
X.SH SYNOPSIS
X\fBnmsg\fP <\fIuser\fP>[\fI@host\fP] [\fItty\fP]
X.SH DESCRIPTION
X\fBnmsg\fP sends a message to the specified \fIuser\fP.  The message is
Xread from the standard input until a CTL-D or a single '.' on one line is
Xencountered.  In normal usage the program works interactively to provide
Xword wrap and to allow tilde commands to be used.  If the input is coming
Xfrom a pipe it will be read in straight without word wrapping, and tildes
Xwill be ignored.  In all cases a copy of the message is saved in
X$HOME/.oldmsg.
X.PP
XThe optional \fBhost\fP argument allows a message to be sent to someone on
Xa remote machine assuming \fBhost\fP has msgd(8) running on the same port.
XThe \fItty\fP argument specifies which tty to write to, in the case that
Xa user is logged in more than once.  If \fItty\fP is not supplied, the
Xterminal chosen is arbitrary.
X.PP
X.RS
X.B Tilde commands:
X.IP
X~p	Print contents of current message.
X.IP
X~r\fIfile\fP	include \fIfile\fP in message.  If no argument is given
X\fBnmsg\fP attempts to use the file $HOME/.oldmsg as input.  Tilde
Xexpansion of file names does work however there are no wildcards.
X.IP
X~v	This will place you in an editor.  If you have the variable
XEDITOR set in your environment it will use that.  Otherwise it will
Xdefault to /usr/ucb/vi.  This is definitely the silliest feature of the
Xprogram.
X.RE
X.SH FILES
X/etc/utmp
X.SH BUGS
XWhen running interactively the program assumes you are using an 80 column
Xterminal window.
X.LP
XOften entering the editor twice in one session causes problems.  Also you
Xcan't pause out of the editor.
X.LP
XThe program isn't very smart about picking the tty to write to.
X.SH "SEE ALSO"
Xmail(1), mesg(1), talk(1), who(1), write(1), msgd(8)
X.SH AUTHORS
XLarry Moss lm03_cif@cc.rochester.edu
X.br
XGavin Stark gest_ss@cc.rochester.edu
END_OF_FILE
if test 1870 -ne `wc -c <'nmsg.man'`; then
    echo shar: \"'nmsg.man'\" unpacked with wrong size!
fi
# end of 'nmsg.man'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(1368 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#
X# Makefile for msg - a program to splash text on someone's screen
X# over the network.
X#
X
X#
X# This is the only section that should need to be changed.
X# for some reason, gcc won't compile this correctly on a sun3.  There's a
X# problem with bcopy() that needs to be looked into.
X#
XDESTDIR = /u/cif/lm03_cif
XBINDIR = $(DESTDIR)/bin
XETCDIR = $(DESTDIR)/etc
XMANDIR = $(DESTDIR)/man
X#CC = gcc
XCC = cc
X
X#
X# Comment out the next line if you don't want to use the nameserver.
X#
XLDFLAGS = -lresolv
X
X#
X# the NORESOLV definition doesn't do anything now.  Some #include lines may
X# need to be commented out in socket.h.  Nothing else should need to be 
X# changed.
X#
X#CFLAGS = -O -D"NORESOLV"
X#CFLAGS = -g
XCFLAGS = -O
XDOBJS = msgd.o socklib.o utmp.o
XMOBJS = nmsg.o socklib.o input.o
X
X#
X# .c.o is being changed so gcc won't complain about the options sun's make
X# supplies to $(CC)
X#
X.c.o:
X	$(CC) $(CFLAGS) -c $<
X
Xall: nmsg msgd
X
Xnmsg: $(MOBJS)
X	$(CC) -o nmsg $(MOBJS) $(LDLIBS) $(LDFLAGS)
X
Xmsgd: $(DOBJS)
X	$(CC) -o msgd $(DOBJS) $(LDLIBS) $(LDFLAGS)
X
Xinstall: nmsg msgd
X	install -s nmsg $(BINDIR)
X	install -s msgd $(ETCDIR)
X	install -c -m 0644 nmsg.man $(MANDIR)/man1/nmsg.1
X	install -c -m 0644 msgd.man $(MANDIR)/man8/msgd.8
X
Xtar:
X	tar -cvf nmsg.tar README *.c *.h *.man Makefile TODO
X
Xshar:
X	shar -onmsg.shar README *.c *.h *.man Makefile TODO
X
Xclean:
X	rm -f *.o msgd nmsg core
END_OF_FILE
if test 1368 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'TODO' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'TODO'\"
else
echo shar: Extracting \"'TODO'\" \(1100 characters\)
sed "s/^X//" >'TODO' <<'END_OF_FILE'
XIf no tty is specified it should be smarter.  It would be nice if it could
Xselect the 'best' tty rather than grabbing a random one.
X
XIt won't let the editor pause. (low priority item)
X
Xsometimes chokes when you edit something a second time.
X
Xwill try to deliver message even if user logs out.
X
XIt should be able to handle multiple users.  Probably notify after who the
Xmessage was delivered to rather than try to find out which people are
Xlogged in ahead of time.  Input format would be changed to user@host:tty
X
XMark's suggestion:
X> Of course, a neat extra-credit technique would be to batch messages to the
X> same host, so that if I send to "lm03_cif@troi.cc.rochester.edu
X> jdic@troi.cc.rochester.edu", it would only contact troi's msgd once instead of
X> twice.  But that's extra credit. :-)
X
XAnother idea would be to keep a record of users that received messages in
Xthe last hour or so so you can check to see who sent you a message if you
Xjust cleared your screen.  Something like 'nmsg -w'.
X
XIf a record is kept of who sent the last message, then 'nmsg -r' could
Xjust reply to the last person.
END_OF_FILE
if test 1100 -ne `wc -c <'TODO'`; then
    echo shar: \"'TODO'\" unpacked with wrong size!
fi
# end of 'TODO'
fi
echo shar: End of shell archive.
exit 0
--
lm03_cif@uhura.cc.rochester.edu / "I'm so tired... I was up all night
lmo3_ss@db1.cc.rochester.edu   / trying to round off infinity."
lmo3_ss@uordbv.bitnet         / - Stephen Wright