[mod.sources] rsend - BSD communications program

sources-request@panda.UUCP (12/31/85)

Mod.sources:  Volume 3, Issue 73
Submitted by: seismo!enea!ttds!zap (Svante Lindahl)


	Here is a program that we use all the time, instead of
	write and talk. It can be used for one-way communication
	(like write) and over a local network (like talk).
	It is very BSD:ish, sockets and all.

	We have it running on three VAXen and several SUNs on our
	ethernet, and communication between VAX and SUN works fine
	(unlike talk :-(  (ours does work now!)).

	Please, no flames, this was my first UNIX-hack, my first
	C-hack and my first communications program. Accurate criticism
	is of course welcome.

: This is a shar archive.  Extract with sh, not csh.
: This archive ends with exit, so do not worry about trailing junk.
echo 'Extracting README'
sed 's/^X//' > README << '+ END-OF-FILE README'
XThis is the README file for rsend.
XThe other files you need are:
X	rsend.h		- the headerfile for rsend.c and rsendd.c
X	rsend.c		- the user interface
X	rsendd.c	- the rsend daemon
X	rsend.1		- the manual entry for rsend
X	Makefile	- makes rsend and rsendd
X
Xrsend is a program for sending messages to other users on a local machine
Xor other machines in a local area network. (cf. SEND on TOPS-10). It is
Ximplemented using sockets and will thus only run on 4.xBSD (for x>=2).
X
XBefore typing make, take a look at the makefile and see which compile-
Xtime flags you want to use.
X
XWhen installing it you need to do the following:
X
X	make an entry for rsend in /etc/services looking something
X	like this:
X		rsend		777/udp
X	(Where 777 is an arbitrary address less than 1024)
X
X	put the following in /etc/rc.local to start the daemon when
X	the machine boots:
X		if [ -f /usr/net/rsendd ]; then
X			/usr/net/rsendd & echo -n ' rsendd' > /dev/console
X		fi
X	(or at least something looking remotely like this, depending
X	 on whether you echo the names of local daemons to the console,
X	 and where you prefer to install rsendd)
X
XNote that the daemon has to be started by root.
+ END-OF-FILE README
chmod 'u=rw,g=rw,o=r' 'README'
echo '	-rw-rw-r--  1 zap          1154 Dec 30 23:04 README        (as sent)'
echo -n '	'
/bin/ls -l README
echo 'Extracting Makefile'
sed 's/^X//' > Makefile << '+ END-OF-FILE Makefile'
XDAEMONDIR	= /usr/net
XDESTDIR		= /usr/local
X
XOBJECTS	= rsendd.o rsend.o
XSOURCE	= rsendd.c rsend.c
XHEADRS	= rsend.h
X
X#
X# Possible defines in CFLAGS are:
X#
X# -DDEBUG	when debugging the daemon, it will run attached to a terminal
X#		compile and load with -g instead of -O, and debug it
X# -DTEST 	when testing the daemon, doesn't need to be root, but can't
X#		recieve from other machines (Entry in /etc/services not needed)
X# -DINET_D	if you let the daemon be invoked by inetd (the internet daemon)
X#		when its service is required
X# -DUNIQUE	if you want the user program to check rwho database for
X#		reciever on other hosts when not logged in on the local machine
X#		(i e one username is always the same person on all machines in
X#		the local area network)
X
XCFLAGS	= -O -DUNIQUE
X# LDFLAGS = -O	# when debugging
X
Xall:	rsend rsendd
X	
Xrsend:	rsend.o
X	cc ${LDFLAGS} -o rsend rsend.o
X
Xrsendd:	rsendd.o
X	cc ${LDFLAGS} -o rsendd rsendd.o
X
Xrsend.o:	 ${HEADRS}
X
Xrsendd.o:	 ${HEADRS}
X
Xinstall:	all
X	strip rsend rsendd
X	cp rsend ${DESTDIR}/rsend
X	cp rsendd ${DAEMONDIR}/rsendd
X	chmod 755 ${DESTDIR}/rsend
X	chmod 700 ${DAEMONDIR/rsendd
X
Xclean:
X	rm -f *.o
+ END-OF-FILE Makefile
chmod 'u=rw,g=r,o=r' 'Makefile'
echo '	-rw-r--r--  1 zap          1138 Dec 30 23:11 Makefile        (as sent)'
echo -n '	'
/bin/ls -l Makefile
echo 'Extracting rsend.1'
sed 's/^X//' > rsend.1 << '+ END-OF-FILE rsend.1'
X.TH RSEND Local "30 December 1985"
X.SH NAME
X.B rsend
X\- send a message to another (possibly remote) user
X.SH SYNOPSIS
X.B rsend
Xperson [ \-ttyname ] [ message ]
X.SH DESCRIPTION
X.I Rsend
Xsends a message to another user on your machine,
Xor to a user on another machine in a
Xlocal area network.
X.PP 
XIf you wish to send to someone on you own machine, then
X.I person
Xis just the person's login name. If you wish to send to
Xa user on another host, then
X.I person
Xis of the form:
X.sp
X.in +1.5i
X.I user@host
X\ or\ 
X.I host!user
X\ or
X.br
X.I host:user
X\ or\ 
X.I host.user
X.br
X.in -1.5i
X.sp
XIf you don't specify a host or the username isn't logged in on your
Xmachine (or doesn't even exist on your machine), the rwho\-database
Xis searched for that user on the other hosts in the local area network.
XIf a user is logged in several times 
Xthe tty with the least idletime is choosen to recieve
Xthe message.
X.PP 
X.I Rsend
Xcan be used either with a message on the commandline
Xor interactively. Be aware that the shell will interpret
Xmetacharacters on the commandline, so it may be wise to
Xquote the message, or use
X.I rsend
Xinteractively.
X.PP
XIf a message, for example ``Hello'', is supplied on the commandline
X.I rsend
Xwill send a message, that looks like this:
X.PP
X    <11:47 From yourname@yourhost (yourttyname): Hello>
X.PP
Xand then exit.
XThe message is preceded by, and followed by a newline so that the
Xreciever's screen doesn't get too messed up.
XThere is also a bell (Control\-G) included in the message
Xto alert the recipient.
X.PP
XTo run
X.I rsend
Xinteractively omit the message from the commandline.
X.I Rsend
Xwill prompt ``rsend>'' and at this time the message can
Xbe typed. Nothing will be sent until the first line of
Xthe message is completed (unlike write(1)).
XThe first line sent looks exactly as if
X.I rsend
Xhad been used with the message on the commandline.
XThe rsend\-prompt will return and subsequent non\-empty
Xlines will be sent, looking like this:
X.PP
X    <yourname: text being sent>
X.PP
Xpreceded and followed by a newline.
XTo exit
X.I rsend
Xtype either the EOF\-character, or `.' as the first
Xand only character on a line.
XIf the character `!' is typed first on a line a subshell
Xwill be invoked (as specified by the environment\-variable
XSHELL, or /bin/sh if SHELL is undefined).
XIf the `!' is the only character on the line, the shell
Xwill be interactive, otherwise the rest of the line
Xwill be interpreted as a command to the shell,
Xand the shell will exit after executing the command.
X.PP
XIf the reciever is logged in more than once, the
X.I \-ttyname
Xargument can be used to specify the appropriate
Xterminal, e.g.
X.I ``rsend foo@bar \-ttyd3''
Xor
X.I ``rsend bar!foo \-d3''.
XIt is also possible to replace
X.I ``user''
Xwith a specification of a terminal when specifying
X.I ``person'',
Xe.g.
X.I ``rsend \-d3''
Xor
X.I ``rsend \-ttyd3@bar''.
X.SH DIAGNOSTICS
XIf a message couldn't be sent an appropriate
Xwarning is written to the senders terminal.
X.SH FILES
X/etc/utmp		searched for reciever
X/usr/spool/rwho/whod.*	searched for reciever
X/bin/sh		subshell if SHELL is not found
X.SH "SEE ALSO"
Xmesg(1), who(1), rwho(1C), talk(1), mail(1), write(1)
X.SH AUTHOR
XSvante Lindahl, Royal Institute of Technology, Stockholm
X.SH BUGS
XPossibly!
+ END-OF-FILE rsend.1
chmod 'u=rw,g=r,o=r' 'rsend.1'
echo '	-rw-r--r--  1 zap          3230 Dec 30 22:35 rsend.1        (as sent)'
echo -n '	'
/bin/ls -l rsend.1
echo 'Extracting rsend.h'
sed 's/^X//' > rsend.h << '+ END-OF-FILE rsend.h'
X/*
X * $Header: rsend.h,v 1.7 85/09/12 15:07:15 zap Exp $
X */
X
X/*
X * Revision 1.2  85/08/17  01:44:22  zap
X * New format for msg - request type field added, request types can be
X * CHECK (check if reciver is logged in and tty writeable) and SEND
X * (go ahead and send message).
X * 
X * Revision 1.1  85/08/16  21:42:05  zap
X * Initial revision
X * 
X *    rsend.h    1.0    zap@bogart	1985-07-02 03:39:48
X */
X
X/*
X** Copyright (C) 1985 by Svante Lindahl.
X**
X** rsend - send a message to a local or remote user
X**
X*/
X
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <pwd.h>
X#include <utmp.h>
X#include <netdb.h>
X#include <netinet/in.h>
X#include <stdio.h>
X#include <errno.h>
X#include <signal.h>
X#include <sys/wait.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X#include <sys/time.h>
X#include "/usr/src/net/rwhod/rwhod.h"
X
X#define TRUE	1
X#define	FALSE	0
X#define ever	;;
X
X#ifdef	TEST
X#define RENDEZ_VOUS	11147
X#endif
X
X#define NAME_SIZE	9
X#define TTY_SIZE	8
X#define	LINE_LENGTH	80
X#define BUF_SIZE	400
X#define HOST_NAME_SIZE	32
X
X#define WHDRSIZE   (sizeof(wd) - sizeof (wd.wd_we))
X
X/* request values */
X#define	CHECK		0
X#define	SEND		1
X
X/* response values */
X#define SUCCESS		0
X#define NO_USER		1
X#define NOT_IN		2
X#define NOT_TTY		3
X#define	WRNG_TTY	4
X#define NOT_USED	5
X#define	DENIED		6
X#define OP_UTMP		7
X#define OP_TTY		8
X#define WR_FAIL		9
X#define CONFUSED	10
X#define NO_ANSW		11
X#define CHDIR		12
X
X/* Seems like nr of bytes must be divisible by 4 to work between Sun and VAX */
Xstruct rsend_msg {	
X	char rcv_name[NAME_SIZE];   /*   9 */
X	char rcv_tty[TTY_SIZE];     /*   8 */
X	char rqst_type;		    /*   1 */
X	char fill[2];		    /*   2 */
X	char text[BUF_SIZE];        /* 400 */
X} msg;
X
X/* Seems like nr of bytes must be divisible by 4 to work between Sun and VAX */
Xstruct rsend_rsp {
X        char code;                  /*   1 */
X	char fill[2];               /*   2 */
X        char rcv_name[NAME_SIZE];   /*   9 */
X        char rcv_tty[TTY_SIZE];     /*   8 */
X} rsp;
X
Xextern int errno;
+ END-OF-FILE rsend.h
chmod 'u=r,g=r,o=r' 'rsend.h'
echo '	-r--r--r--  1 zap          1990 Sep 12 15:07 rsend.h        (as sent)'
echo -n '	'
/bin/ls -l rsend.h
echo 'Extracting rsendd.c'
sed 's/^X//' > rsendd.c << '+ END-OF-FILE rsendd.c'
X#ifndef lint
Xstatic char *RCSid = "$Header: rsendd.c,v 1.8 85/12/30 22:56:16 zap Exp $";
Xstatic char *Copyright = "rsendd.c  Copyright (C) 1985 Svante Lindahl.";
X#endif
X
X/*
X * $Log:	rsendd.c,v $
X * Revision 1.8  85/12/30  22:56:16  zap
X * Made lint happier.
X * 
X * Revision 1.7  85/10/06  19:50:46  zap
X * Problem: daemon would occasionally get hung, fix(?): timeout after 3
X * seconds in wait and send a SIGKILL to the child.
X * 
X * Revision 1.6  85/10/06  19:04:30  zap
X * Bugfix; didn't recognize that a tty was unused, badly placed '!' removed.
X * 
X * Revision 1.5  85/08/21  18:27:59  zap
X * Better way of finding tty with least idletime.
X * 
X * Revision 1.4  85/08/19  23:09:44  zap
X * The daemon now tries to find reciver's job with least idletime if no
X * particular tty is requested.
X * 
X * Revision 1.3  85/08/19  21:10:34  zap
X * Some small fixes.
X * 
X * Revision 1.2  85/08/17  01:31:28  zap
X * Checks if person is logged in and tty is writeable when called with
X * request type CHECK, only writes on tty with request is set to SEND.
X * 
X * Revision 1.1  85/08/16  21:40:20  zap
X * Initial revision
X * 
X * rsendd.c    1.0    zap@bogart	1985-07-05 18:00:30
X *
X */
X
X/*
X** rsendd - daemon for rsend a message sending program 
X**
X** This program, and any documentation for it, is copyrighted by Svante
X** Lindahl. It may be copied for non-commercial use only, provided that
X** any and all copyright notices are preserved.
X**
X** Please report any bugs and/or fixes to:
X**
X** UUCP: {seismo,mcvax,cernvax,diku,ircam,prlb2,tut,ukc,unido}!enea!ttds!zap
X** ARPA: enea!ttds!zap@seismo.ARPA
X**   or  Svante_Lindahl_NADA%QZCOM.MAILNET@MIT-MULTICS.ARPA
X**
X*/
X
X#include "rsend.h"
X
X#include <fcntl.h>
X#include <sys/ioctl.h>
X#include <sys/resource.h>
X
X#ifdef	INET_D
X#define TIMEOUT	30        /* let the daemon time out and exit if.. */
X#define MAXIDLE	120       /* ..it is invoked by the inet-daemon    */
X#endif
X
Xstruct	sockaddr_in sin, from ;
Xint	fromlen = sizeof(from) ;
Xextern	int doit() ;
X
X#ifndef DEBUG
Xint	pid ;
Xlong	lastmsgtime ;
X#endif
X
Xmain ()
X{
X    int s, f, cc, w;
X    union wait status;
X    int (*saveintr)(), (*savequit)();
X    struct passwd *getpwnam() ;
X    struct servent *sp ;
X#ifndef	DEBUG
X    extern	void reaper(), chaser() ;
X#endif
X#ifdef	INET_D
X    extern	void timeout() ;
X#endif
X    
X    sin.sin_addr.s_addr = INADDR_ANY ;
X    sin.sin_family = AF_INET ;
X#ifndef TEST
X    if (getuid()) {
X        fprintf (stderr, "rsendd: not super user\n") ;
X        exit(1) ;
X    }
X    sp = getservbyname("rsend", "udp") ;
X    if (sp == NULL) {
X        fprintf (stderr, "rsendd: udp/rsend: unknown service\n") ;
X        exit(1) ;
X    }
X    sin.sin_port = sp->s_port ;
X#else
X    sin.sin_port = htons(RENDEZ_VOUS) ;
X#endif
X#ifndef	DEBUG
X     /* fork, parent quits, child dissociates itself from the tty */
X    if (fork())
X        _exit(0) ;
X    for (f = 0 ; f < 5 ; f++)
X        (void) close(f) ;
X    (void) open("/dev/null", O_RDONLY) ;
X    (void) open("/dev/null", O_WRONLY) ;
X    (void) dup(1) ;
X    if ((f = open("/dev/tty", O_RDWR)) > 0) {
X        (void) ioctl(f, (int) TIOCNOTTY, (char *) 0) ;
X        (void) close(f) ;
X    }
X    s = socket(AF_INET, SOCK_DGRAM, 0) ;
X    if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
X        perror("rsendd: bind") ;
X        exit(1) ;
X    }
X    (void) signal(SIGCHLD, reaper) ;    /* try not to leave zombies around */
X    for(ever) {
X        cc = recvfrom(s, (char *)&msg, sizeof(msg),
X                      0, (struct sockaddr *) &from, &fromlen);
X        if ((pid = vfork()) == 0)
X            exit(doit(s, cc)) ;
X        (void) time(&lastmsgtime) ;
X        saveintr = signal(SIGINT, SIG_IGN) ;
X        savequit = signal(SIGQUIT, SIG_IGN) ;
X	(void) signal(SIGALRM, chaser) ;
X        (void) alarm(3);
X        while ((w = wait(&status)) != pid && w != -1)
X            ;
X        (void) signal(SIGQUIT, saveintr) ;
X        (void) signal(SIGQUIT, savequit) ;
X#ifdef	INET_D
X        (void) signal(SIGALRM, timeout) ;
X        (void) alarm(TIMEOUT) ;
X#else
X	(void) alarm(0) ;
X#endif
X    }
X#else
X    s = socket(AF_INET, SOCK_DGRAM, 0) ;
X    if (bind(s, &sin, sizeof(sin)) < 0) {
X        perror("rsendd: bind") ;
X        exit(1) ;
X    }
X    for(ever) {
X        cc = recvfrom(s, (char *)&msg, sizeof(msg), 0, &from, &fromlen) ;
X        doit(s, cc) ;
X    }
X#endif
X}
X
Xint
Xdoit(s, cc)
X    int s, cc ;
X{
X    int  f, i, idle = -1 ;
X    struct stat st ;
X    struct utmp ut ;
X    struct passwd *pwp ;
X    long   now ;
X
X    if (cc != sizeof(msg)) {
X        if (cc < 0 && errno != EINTR)
X            perror("recvfrom") ;
X        return(1) ;
X    }
X    if (chdir("/dev") < 0)
X        rsp.code = CHDIR ;
X    else if (*msg.rcv_tty != '\0') {
X        (void) strcpy(rsp.rcv_tty, msg.rcv_tty) ;
X        if (stat(msg.rcv_tty, &st) < 0)
X            rsp.code = NOT_TTY ;
X        else if (((st.st_uid == 0) && (strcmp(msg.rcv_name, "root")))
X          || (pwp = getpwuid(st.st_uid)) == NULL)
X            rsp.code = NOT_USED ;
X        else if (*msg.rcv_name != '\0') {
X            (void) strcpy(rsp.rcv_name, msg.rcv_name) ;
X            if (getpwnam(msg.rcv_name) == NULL)
X                rsp.code = NO_USER ;
X            else if (strcmp(msg.rcv_name, pwp->pw_name) != 0)
X                rsp.code = WRNG_TTY ;
X            else if (!(st.st_mode & 2))
X                rsp.code = DENIED ;
X            else
X                rsp.code = SUCCESS ;
X        } else {
X            if (!(st.st_mode & 2))
X                rsp.code = DENIED ;
X            else {
X                rsp.code = SUCCESS ;
X                (void) strcpy(rsp.rcv_name, pwp->pw_name) ;
X            }
X        }
X    } else if (*msg.rcv_name != '\0') {
X        (void) strcpy(rsp.rcv_name, msg.rcv_name) ;
X        if (getpwnam(msg.rcv_name) == NULL)
X            rsp.code = NO_USER ;
X	else if ((f = open ("/etc/utmp", 0)) < 0)
X	    rsp.code = OP_UTMP ;
X	else {        /* search for reciver in /etc/utmp */
X	    rsp.code = NOT_IN ;
X	    (void) time(&now) ;
X	    while (read (f, (char *)&ut, sizeof(ut)) > 0) {
X		if (strcmp(msg.rcv_name, ut.ut_name) == 0) {
X		    if ((stat(ut.ut_line, &st) >= 0) && (st.st_mode & 2) 
X                      && (((i = now - st.st_mtime) < idle) || (idle = -1))) {
X                        (void) strcpy(rsp.rcv_tty, ut.ut_line) ;
X			rsp.code = SUCCESS ;
X                        idle = i ;
X		    } else if (rsp.code != SUCCESS) {
X                        (void) strcpy(rsp.rcv_tty, ut.ut_line) ;
X			rsp.code = DENIED ;
X		    }
X		}
X	    }
X	    (void) close(f) ;
X        }
X    } else
X        rsp.code = CONFUSED ;        /* hopefully not reached... */
X
X    if (rsp.code == SUCCESS) {       /* found somewhere to send it */
X        if ((f = open(rsp.rcv_tty, O_WRONLY)) < 0)
X            rsp.code = OP_TTY ;
X        else if (msg.rqst_type == SEND) {
X            if (write(f, msg.text, strlen(msg.text)) != strlen(msg.text))
X                rsp.code = WR_FAIL ;
X            (void) close(f) ;
X        }
X    }
X    /* send status report to the sender */
X    cc = sendto(s, (char *)&rsp, sizeof(rsp),
X                0, (struct sockaddr *) &from, sizeof(from)) ;
X    return(cc == sizeof(rsp) ? 0 : -1) ;
X}
X
X#ifdef	INET_D
Xvoid
Xtimeout()
X{
X    long now ;
X
X    (void) time(&now) ;
X    if (now - lastmsgtime >= MAXIDLE)
X        exit(0) ;
X    (void) alarm(TIMEOUT) ;
X}
X#endif
X
X#ifndef DEBUG
Xvoid
Xreaper()            /* takes care of getting rid of zombie-children */
X{
X    union wait status ;
X
X    while (wait3(&status, WNOHANG, (struct rusage *) 0) > 0) ;
X}
X
Xvoid
Xchaser()	    /* chase down children that refuse to die */
X{
X    (void) kill(pid, SIGKILL) ;
X}
X#endif
+ END-OF-FILE rsendd.c
chmod 'u=r,g=r,o=r' 'rsendd.c'
echo '	-r--r--r--  1 zap          7552 Dec 30 22:56 rsendd.c        (as sent)'
echo -n '	'
/bin/ls -l rsendd.c
echo 'Extracting rsend.c'
sed 's/^X//' > rsend.c << '+ END-OF-FILE rsend.c'
X#ifndef lint
Xstatic char *RCSid = "$Header: rsend.c,v 1.7 85/12/30 22:54:02 zap Exp $";
Xstatic char *Copyright = "rsend.c  Copyright (C) 1985 Svante Lindahl.";
X#endif
X
X/*
X * $Log:	rsend.c,v $
X * Revision 1.7  85/12/30  22:54:02  zap
X * Made lint happier. Fixed bug, replacing strcmp with strncmp.
X * 
X * Revision 1.6  85/10/06  19:01:35  zap
X * Bugfix; working directory was "wrong" when shellescape invoked, now
X * does a chdir back after checking things in /dev & /usr/spool/rwho.
X * 
X * Revision 1.5  85/08/21  17:37:12  zap
X * Now chooses to send to writeable tty with least idletime if reciever
X * is logged in more than once on the local host.
X * 
X * Revision 1.4  85/08/19  23:08:56  zap
X * Small change.
X * 
X * Revision 1.3  85/08/19  21:27:51  zap
X * When no host is specified and the reciever isn't logged in on the local
X * host the rwho database is searched to find reciever on an other machine
X * in the local area network. If logged in several times on other hosts the
X * tty (pty) with the least idletime is selected.
X * 
X * Revision 1.2  85/08/17  01:40:41  zap
X * Now checks if reciever is logged in and tty is writeable before entering
X * interactive mode.
X * 
X * Revision 1.1  85/08/16  21:41:09  zap
X * Initial revision
X * 
X * rsend.c    1.0    zap@bogart    1985-07-23 21:29:27
X *
X */
X
X/*
X** rsend - send a message to a local or remote user
X**
X** This program, and any documentation for it, is copyrighted by Svante
X** Lindahl. It may be copied for non-commercial use only, provided that
X** any and all copyright notices are preserved.
X**
X** Please report any bugs and/or fixes to:
X**
X** UUCP: {seismo,mcvax,cernvax,diku,ircam,prlb2,tut,ukc,unido}!enea!ttds!zap
X** ARPA: enea!ttds!zap@seismo.ARPA
X**   or  Svante_Lindahl_NADA%QZCOM.MAILNET@MIT-MULTICS.ARPA
X**
X*/
X
X#include "rsend.h"
X
X#define	TIMEOUT    2	                /* seconds */
X
Xint	Argc, remotesend, interactive = FALSE ;
Xchar	**Argv, *my_name, *my_tty ;
Xchar    my_cpu[HOST_NAME_SIZE] ;
Xchar	rcv_cpu[HOST_NAME_SIZE] ;
Xstruct	sockaddr_in sin;
X
Xchar	*getlogin(), *ttyname(), *rindex() ;
Xstruct	hostent *gethostbyname() ;
Xstruct	servent *getservbyname() ;
X
Xextern	void err_check () ;
Xextern	void who_am_i () ;
Xextern	void who_is_rcvr () ;
Xextern	void set_up_msg() ;
Xextern	void set_up_tty() ;
Xextern	int  where_is_rcvr () ;
Xextern	void where_to () ;
Xextern	void check_it () ;
Xextern	void be_interactive () ;
Xextern	void do_shell ();
Xextern	void build_msg () ;
Xextern	int  build_header () ;
Xextern	void send_it () ;
Xextern	void timeout() ;
Xextern	void error_handle() ;
X
Xmain (argc, argv)
X    int argc ;
X    char **argv ;
X{
X    Argc = argc ;
X    Argv = argv ;
X    err_check () ;
X    who_am_i () ;
X    who_is_rcvr () ;
X    where_to () ;
X    if (interactive) {
X        check_it () ;
X        be_interactive() ;
X    } else {
X        build_msg () ;
X        send_it () ;
X    }
X}
X
Xvoid
Xerr_check ()
X{
X    if (Argc < 2 ) {
X        fprintf(stderr,
X            "Usage: rsend user[@host] [-tty] [message...]\n") ;
X        exit(1) ;
X    }
X    if (!isatty(0)) {
X        fprintf(stderr,
X               "rsend: Sorry, standard input must be a tty!\n") ;
X        exit(1) ;
X    }
X}
X
Xvoid
Xwho_am_i ()
X{
X    struct passwd *pwp ;
X
X    my_name = getlogin() ;
X    if (*my_name == NULL)
X        if ((pwp = getpwuid(getuid())) == NULL) {
X            fprintf(stderr, "You are not for real!.\n") ;
X            exit(1) ;
X        }
X        else
X            my_name = (char *)pwp->pw_name ;
X    my_tty = rindex(ttyname(0), '/') + 1 ;
X    (void) gethostname(my_cpu, sizeof(my_cpu)) ;
X}
X
Xvoid
Xwho_is_rcvr ()
X{
X    char *ptr ;
X    int  code ;
X
X    Argc-- ;  Argv++ ;
X    *msg.rcv_tty = '\0' ;
X    *msg.rcv_name = '\0' ;
X    for (ptr = *Argv ; *ptr != '\0' && *ptr != '@' && *ptr != ':' 
X        && *ptr != '!' && *ptr != '.' ; ptr++)
X            ;
X    if (*ptr == '\0') {               /* no host specified */
X        set_up_msg(*Argv, my_cpu) ;
X        if (*msg.rcv_tty == '\0') {
X            if ((code = where_is_rcvr()) != SUCCESS)
X                error_handle(code) ;
X	}
X    } else {
X        if (*ptr == '@') {            /* user@host || -tty@host */
X            *ptr++ = '\0' ;
X            set_up_msg(*Argv, ptr) ;
X        } else {                      /* host[.!:]user || host[.!:]-tty */
X            *ptr++ = '\0' ;     
X            set_up_msg(ptr, *Argv) ;
X        }
X    }
X    remotesend = strcmp(rcv_cpu, my_cpu) ;
X    Argc-- ;  Argv++ ;
X    if (Argc == 0)                    /* anything left on the commandline? */
X        interactive = TRUE ;
X    else if (**Argv == '-') {         /* tty-specification? */
X        set_up_tty(&Argv[0][1]) ;
X        Argc-- ;  Argv++ ;
X        if (Argc == 0)                /* still more? */
X            interactive = TRUE ;
X    }
X}
X
Xvoid
Xset_up_msg(ptr, cpu)
X    char *ptr, *cpu;
X{
X    if (*ptr != '-')                  /* reciever's username or tty? */
X        (void) strcpy(msg.rcv_name, ptr) ;
X    else
X        set_up_tty(++ptr) ;
X    (void) strcpy(rcv_cpu, cpu) ;
X}
X
Xvoid
Xset_up_tty(ptr)
X    char *ptr ;
X{
X    if (!strcmp("console", ptr))
X        (void) strcpy(msg.rcv_tty, ptr) ;
X    else {
X        if (strncmp("tty", ptr, 3)) 
X            (void) strcpy(msg.rcv_tty, "tty") ;
X        (void) strcat(msg.rcv_tty, ptr) ;
X    }
X    msg.rcv_tty[TTY_SIZE-1] = '\0' ;
X}
X
Xint
Xwhere_is_rcvr()
X{
X    int    code, f, i, idle = -1, nowd = 0 ;
X    char   path[1024], *getwd() ;
X    struct stat st ;
X    struct utmp ut ;
X    long   now ;
X
X    if (getwd(path) == '\0')
X        nowd++ ;
X    if (getpwnam(msg.rcv_name) == NULL)
X        code = NO_USER ;
X    else if ((f = open ("/etc/utmp", 0)) < 0)
X        code = OP_UTMP ;
X    else if (chdir("/dev") < 0)
X        code = CHDIR ;
X    else {
X        code = NOT_IN ;       /* search for reciver in /etc/utmp */
X        (void) time(&now) ;
X	while (read(f, (char *)&ut, sizeof(ut)) > 0) {
X	    if (strncmp (msg.rcv_name, ut.ut_name, 8) == 0) {
X		if ((stat(ut.ut_line, &st) >= 0) && (st.st_mode & 2)
X		  && (((i = now - st.st_mtime) < idle) || (idle = -1))) {
X		    (void) strcpy(msg.rcv_tty, ut.ut_line) ;
X		    code = SUCCESS ;
X                    idle = i ;
X		} else if (code != SUCCESS) { 
X                    (void) strcpy(msg.rcv_tty, ut.ut_line) ;
X		    code = DENIED ;
X		}
X	    }
X	}
X        (void) close(f) ;
X    }
X#ifdef UNIQUE
X    {
X        int    cc, n ;
X        DIR   *dirp ;
X        struct direct *dp ;
X        struct whoent *we ;
X        struct whod wd ;
X        register struct whod *wp = &wd ;
X
X        if (   ((code == NO_USER) || (code == NOT_IN) || (code == CHDIR))
X            && (chdir("/usr/spool/rwho") >= 0)
X            && ((dirp = opendir(".")) != NULL)) {
X            /* check other machines in local network (use rwho database) */
X	    (void) time(&now) ;
X	    while (dp = readdir (dirp)) {
X		if ((dp->d_ino == 0) || (strncmp(dp->d_name, "whod.", 5)))
X		    continue ;
X		if ((f = open(dp->d_name, 0)) < 0)
X		    continue ;
X		cc = read(f, (char *)&wd, sizeof(struct whod)) ;
X		if ((cc < WHDRSIZE) ||  (now - wp->wd_recvtime > 5 * 60) 
X		  || (!strcmp (my_cpu, wp->wd_hostname))) {
X		    (void) close (f) ;
X		    continue ;
X		}
X		for(we = wp->wd_we,
X                  n = (cc - WHDRSIZE) / sizeof(struct whoent);
X		  n > 0; n--, we++) {
X		    if (strncmp(msg.rcv_name, we->we_utmp.out_name, 8))
X			continue ;
X		    if ((i = we->we_idle) < idle || idle == -1) {
X			code = SUCCESS ;
X			idle = i ;
X			(void) strcpy(rcv_cpu, wp->wd_hostname) ;
X			(void) strcpy(msg.rcv_tty, we->we_utmp.out_line) ;
X		    }
X		}
X		(void) close (f) ;
X	    }
X	    if (code == SUCCESS)
X		fprintf(stderr, "%s not logged in on %s, sending to %s@%s\n",
X		  msg.rcv_name, my_cpu, msg.rcv_name, rcv_cpu) ;
X	}
X    }
X#endif 
X    if (!nowd)
X        (void) chdir(path) ;
X    return(code) ;
X}
X
Xvoid
Xwhere_to()
X{
X    struct    hostent *hp ;
X    struct    servent *sp ;
X
X    hp = gethostbyname(rcv_cpu) ;      /* get address of reciever's machine */
X    if (hp == (struct hostent *) 0 ) {
X        fprintf(stderr, "rsend: I don't know a machine named %s\n", rcv_cpu) ;
X        exit(1) ;
X    }
X    bzero(&sin, sizeof (sin)) ;
X    bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length) ;
X    sin.sin_family = hp->h_addrtype ;
X#ifndef	TEST
X    sp = getservbyname("rsend", "udp") ;     /* find daemon's port */
X    if (sp == NULL) {
X        fprintf(stderr, "rsend isn't supported by this machine\n") ;
X        exit(1) ;
X    }
X    sin.sin_port = sp->s_port ;
X#else
X    if (remotesend) {    /* remote sends not supported when testing */
X        fprintf(stderr,
X            "Sorry, only local sends are supported at this time\n") ;
X        exit(1) ;
X    }
X    sin.sin_port = htons(RENDEZ_VOUS) ;      /* find daemon's port */
X#endif
X}
X
Xvoid
Xcheck_it()
X{
X	msg.rqst_type = CHECK ;
X	send_it() ;
X	msg.rqst_type = SEND ;
X}
X
Xvoid
Xbe_interactive()
X{
X    int hdr_siz, i ;
X    char buf[BUF_SIZE] ;
X
X    hdr_siz = build_header () ;   /* standard header for first message */
X    msg.text[hdr_siz++] = ' ' ;
X    msg.text[hdr_siz] = '\0' ;
X    for(ever) {
X        printf("rsend>") ;        /* prompt for input */
X        (void) fflush(stdout) ;
X        if (fgets(buf, sizeof(buf), stdin) != NULL) {
X            if ((i = strlen(buf)) > 1) {
X                buf[--i] = '\0' ;             /* nullify the \n from fgets */
X                if (i == 1 && *buf == '.')    /* '.' to exit pgm */
X                    break ;
X                else if (*buf == '!') 
X                    do_shell(i, buf) ;
X                else {
X                    if (hdr_siz + i > LINE_LENGTH)  /* separate header if .. */
X                        (void) strcat(msg.text, "\r\n") ; /* .. long message */
X                    if (hdr_siz + i + 8 > BUF_SIZE) {     /* too much... */
X                        (void) strncat(msg.text, buf, BUF_SIZE-hdr_siz-i-8) ;
X                        (void) strcat(msg.text, "...\r\n") ;
X                        fprintf(stderr, 
X                            "rsend: long message, %d word(s) truncated\n",
X                                Argc + 1) ;
X                    } else {
X                        (void) strcat(msg.text, buf) ;
X                        (void) strcat(msg.text, ">\r\n") ;
X                    }
X                    msg.text[BUF_SIZE-1] = '\0' ;
X                    send_it() ;
X                    (void) sprintf(msg.text, "\r\n<%s: ", my_name) ; 
X                    hdr_siz = strlen(msg.text) ;
X                }
X            }
X        } else {
X            printf("\r\n") ;
X            break ;
X        }
X    } 
X}
X
Xvoid
Xdo_shell (i, cmd)
X    int i ;
X    char *cmd ;
X{
X    int pid, w ;
X    union wait status ;
X    char *sh, *getenv() ;
X    int (*saveintr)(), (*savequit)() ;
X
X    if ((sh = getenv("SHELL")) == NULL)
X        sh = "/bin/sh" ;
X    if (i == 1) {                             /* interactive shell */
X        if ((pid = vfork()) == 0)
X            execl(sh, sh, (char *) 0) ;
X    }
X    else if ((pid = vfork ()) == 0)            /* do one commandline */
X        execl(sh, sh, "-c", ++cmd, (char *) 0) ;
X    saveintr = signal(SIGINT, SIG_IGN) ;
X    savequit = signal(SIGQUIT, SIG_IGN) ;
X    while ((w = wait(&status)) != pid && w != -1)
X        ;
X    if (i == 1)
X        printf("\n") ;
X    (void) signal(SIGINT, saveintr) ;
X    (void) signal(SIGQUIT, savequit) ;
X}
X
Xvoid
Xbuild_msg ()
X{
X    int     hdr_siz, too_much = FALSE ;
X    char    buf[BUF_SIZE] ;
X
X    msg.rqst_type = SEND ;
X    hdr_siz = build_header() ;
X    while (Argc--) {            /* copy everything from the commandline */
X        (void) strcat(buf, " ") ;
X        if (hdr_siz + strlen(buf) + 8 + strlen(*Argv) > BUF_SIZE ) {
X            too_much = TRUE ;
X            break ;
X        }
X        (void) strcat(buf, *Argv++) ;
X    }
X    if (hdr_siz + strlen(buf) > LINE_LENGTH)  /* separate heading from .. */
X        (void) strcat(msg.text, "\r\n") ;     /*  ..message if it's long  */
X    (void) strcat(msg.text, buf) ;
X    if (too_much) {
X        (void) strcat(msg.text, "...\r\n") ;
X        fprintf(stderr, 
X            "rsend: long message, %d word(s) truncated\n", Argc + 1) ;
X    }
X    else
X        (void) strcat(msg.text, ">\r\n") ;
X    msg.text[BUF_SIZE-1] = '\0' ;        /* just to make sure...*/
X}
X
Xint
Xbuild_header ()
X{
X    long    clock ;
X    struct  tm *tp, *localtime() ;
X
X    (void) time(&clock) ;  /* message header: "<11:47 From foo@bar (ttyxx):" */
X    tp = localtime(&clock) ;
X    (void) sprintf(msg.text,"\r\n\007<%d:%02d From %s@%s (%s):", 
X        tp->tm_hour, tp->tm_min, my_name, my_cpu, my_tty) ;
X    return(strlen(msg.text)) ;
X}
X
Xvoid
Xsend_it ()
X{
X    struct sockaddr from ;
X    int s, cc, fromlen = sizeof(from) ;
X
X    s = socket(AF_INET, SOCK_DGRAM, 0) ;    /* get a socket */
X    if (s < 0) {
X        perror("rsend: socket") ;
X        exit(1) ;
X    }
X    /* send to address in 'sin' through socket to (possibly remote) daemon */
X    cc = sendto(s, (char *)&msg, sizeof(msg),
X                0, (struct sockaddr *)&sin, sizeof (sin)) ;
X    if (cc != sizeof(msg)) {
X        perror("sendto") ;
X        exit(1) ;
X    }
X    rsp.code = NO_ANSW ;
X    (void) signal(SIGALRM, timeout) ;/* will not wait forever for a response */
X    (void) alarm(TIMEOUT) ;
X    (void) recvfrom(s, (char *)&rsp, sizeof(rsp), 0, &from, &fromlen) ;
X    (void) close(s) ;
X    (void) alarm(0) ;               /* don't time out on an interactive user */
X    if (*msg.rcv_name == '\0')
X        (void) strcpy(msg.rcv_name, rsp.rcv_name) ;
X    if (*msg.rcv_tty == '\0')
X        (void) strcpy(msg.rcv_tty, rsp.rcv_tty) ;
X    if (rsp.code != SUCCESS)
X        error_handle(rsp.code) ;
X}
X
Xvoid
Xtimeout ()
X{
X    fprintf(stderr, "rsend: timeout before acknowledged by rsend-daemon@%s\n",
X        rcv_cpu) ;
X    exit(0) ;
X}
X
Xvoid
Xerror_handle (errcod)
X    int errcod ;
X{
X    char errmsg[BUF_SIZE] ;
X    char recvr[NAME_SIZE + HOST_NAME_SIZE + 1] ;
X    char daemon_at[HOST_NAME_SIZE + 15] ;
X
X    if (remotesend) {
X        (void) sprintf(recvr, "%s@%s", msg.rcv_name, rcv_cpu) ;
X        (void) sprintf(daemon_at, "rsend-daemon@%s", rcv_cpu) ;
X        (void) strcat(rcv_cpu, ":") ;
X    } else {
X        (void) strcpy(recvr, msg.rcv_name) ;
X        (void) strcpy(daemon_at, "rsend-daemon") ;
X        *rcv_cpu = '\0' ;
X    }
X
X    switch (errcod) {
X    case NO_USER :
X        (void) sprintf(errmsg, "%s: no such user", recvr) ;
X        break ;
X    case NOT_IN :
X        (void) sprintf(errmsg, "%s not logged in", recvr) ;
X        break ;
X    case NOT_TTY :
X        (void) sprintf(errmsg, "%s/dev/%s no such device",
X            rcv_cpu, msg.rcv_tty) ;
X        break ;
X    case WRNG_TTY :
X        (void) sprintf(errmsg, "%s is not on %s", recvr, msg.rcv_tty) ;
X        break ;
X    case NOT_USED :
X        (void) sprintf(errmsg, "%s/dev/%s is not in use",
X            rcv_cpu, msg.rcv_tty) ;
X        break ;
X    case DENIED :
X        (void) sprintf(errmsg,
X            "%s/dev/%s: permission denied, try mail instead", 
X             rcv_cpu, msg.rcv_tty) ;
X        break ;
X    case OP_UTMP :
X        (void) sprintf(errmsg, "cannot open %s/etc/utmp", rcv_cpu) ;
X        break ;
X    case OP_TTY :
X        (void) sprintf(errmsg, "cannot open %s/dev/%s", rcv_cpu, msg.rcv_tty) ;
X        break ;
X    case WR_FAIL :
X        (void) sprintf(errmsg, "couldn't write on %s/dev/%s", 
X            rcv_cpu, msg.rcv_tty) ;
X        break ;
X    case CONFUSED :
X        (void) sprintf(errmsg, "%s is confused", daemon_at) ;
X        break ;
X    case NO_ANSW :
X        (void) sprintf(errmsg, "no answer from %s", daemon_at) ;
X        break ;
X    case CHDIR :
X        (void) sprintf(errmsg, "couldn't chdir to /dev") ;
X        break ;
X    default :
X        (void) sprintf(errmsg,
X            "this cannot happen! (unknown error, no:%d)", errcod) ;
X    }
X    fprintf(stderr, "rsend: %s\n", errmsg) ;
X    exit(1) ;
X}
+ END-OF-FILE rsend.c
chmod 'u=r,g=r,o=r' 'rsend.c'
echo '	-r--r--r--  1 zap         15648 Dec 30 22:55 rsend.c        (as sent)'
echo -n '	'
/bin/ls -l rsend.c
exit 0

--
Svante Lindahl, NADA, KTH            Numerical Analysis & Computer Science 
Phone: +46-8-787-7146                Royal Institute of Technology         
UUCP: {seismo,mcvax}!enea!ttds!zap   ARPA: enea!ttds!zap@seismo.CSS.GOV