/*
 * msend.c
 *
 * Copyright (c) 1997 Michael Strates <mstrates@minkirri.apana.org.au>
 * Copyright (c) 1993 Zik Saleeba <zik@zikzak.apana.org.au>
 * Copyright (c) 1993 Andrew Herbert <andrew@werple.apana.org.au>
 * Copyright (c) 1992 Sun Microsystems, Inc. 
 *
 * This is a client implementation of the Message Send protocol
 * defined in RFC1312. This implementation may be freely
 * copied, modified, and redistributed, provided that this
 * comment and the Sun Microsystems copyright are retained.
 * Anyone installing, modifying, or documenting this
 * software is advised to read the section in the RFC which
 * deals with security issues.
 *
 * Author: Geoff.Arnold@east.sun.com
 * Modified by: David Barr <barr@darwin.psu.edu>
 * Modified by: Andrew Herbert <andrew@werple.apana.org.au>
 * Modified by: Zik Saleeba <zik@zikzak.apana.org.au>
 * Modified by: Michael Strates <mstrates@minkirri.apana.org.au>
 * Modified by: Martin Rudat <martin@whoever.com>
 */

/* $Id: msend.c,v 1.7 1993/10/08 18:44:10 zik Exp $ */

#include "config.h"
#include "common.h"
#include "msend.h"


char	*prog;			/* points to program name for messages */
int	debug = 0;
int	verbose = 0;
char	*empty_arg = "";	/* used to encode an empty message part */

int	msg_len = 0;		/* the cumulative length of the message */
char	*msg;			/* message assembly buffer */

#define INTERFACES 32		/* for broadcasting */
int if_n = 0;
struct sockaddr_in if_a[INTERFACES];


void
usage()
{

	fprintf(stderr, "Msend v3.0, Michael Strates <mstrates@minkirri.apana.org.au>\n");
	fprintf(stderr, "Original version by Zik Saleeba, Andrew Herbert, Sun Microsystems.\n");
	fprintf(stderr, "\nTo send a message to another user, start msend with the syntax:\n");
	fprintf(stderr, "%s [-t][-d][-v][-r+][-p+] recipient ['message']\n", prog); 
	fprintf(stderr, "\nOr:\n"); 
	fprintf(stderr, "%s -l+                 read the last + messages (default 5)\n", prog);
	fprintf(stderr, "%s -c                  check (don't read) unread messages\n", prog);
	fprintf(stderr, "%s -u                  display unread messages\n", prog);
	fprintf(stderr, "%s -s+                 shorten buffer to + (default 20)\n", prog);
	fprintf(stderr, "%s -e+                 expire old messages for all users (default 20)\n", prog);
	fprintf(stderr, "\nVarious debugging information can also be presented on the command line:\n");
	fprintf(stderr, "-t                           use TCP connection (ignored for broadcasts)\n");
	fprintf(stderr, "-g                           use UDP connection (default)\n");
	fprintf(stderr, "-b                           broadcast to hosts on local network\n");
	fprintf(stderr, "-v / -d                      be verbose / debug mode\n");
	fprintf(stderr, "-r+ / -p+                    UDP retransmissions (4) / port to use (18)\n");
	fprintf(stderr, "\nRecipient may be user, user@host, user:tty@host, :tty@host, :all@host\n");
	fprintf(stderr, "or @host. Type man msend for more information on these features.\n");
}


void collect_args(options, numargs, argc, argv)
	char *(**options)[];
	int *numargs;
	int argc;
	char *argv[];
{
	int argcount;
	char *pos;
	char *env_args;
	int argccount;

	/* get the environment args */
	env_args = (char *)getenv("MSENDOPTS");

	if (env_args != NULL) {
		*numargs = 0;

		/* do a quick scan to approximately count the arguments */
		argcount = 1;
		for (pos = env_args; *pos != '\0'; pos++) {
			if (*pos == ' ')
				argcount++;
		}
		*options = (char *(*)[])malloc(sizeof(char *) * (argcount+argc));

		/* ok, now do it properly */

		/* skip leading spaces */
		pos = env_args;
		while (*pos == ' ')
			pos++;

		/* collect each arg */
		while (*pos != '\0') {
			(**options)[(*numargs)++] = pos;
			while (*pos != ' ' && *pos != '\0')
				pos++;
			if (*pos == ' ') {
				*pos++ = '\0';

				/* ignore trailing spaces */
				while (*pos == ' ')
					pos++;
			}
		}
		/* add the argv args */
		for (argccount = 0; argccount < argc; argccount++)
			(**options)[(*numargs)++] = argv[argccount];
	}
	else {
		/* just use argv */
		*options = (char *(*)[])argv;
		*numargs = argc;
	}
}


int
main(argc, argv)
	int	argc;
	char	*argv[];
{
	int	use_tcp = 0;
	int	retries = 4;
	short	port = 0;
	int	broadcasting = 0;
	int	shorten = 20;
	int	doshorten = 0;

	char	*recipient;
	char	*user;
	char	*term;
	char	*host;
	char	*msg_text;

	struct sockaddr_in sin;
	struct	servent *sp;
	struct	hostent *hp;
	char 	local_name[MAXHOSTNAMELEN];
	char	*(*options)[];
	int	numargs;
	int	argcount;


	prog = *argv++;
	argc--;
	collect_args(&options, &numargs, argc, argv);

	/* process options:
	 * -d (debug)
	 * -t (use TCP)
	 * -b (broadcast)
	 * -g (dataGram)
	 * -rN (set retransmission count)
	 * -pN (use port N instead of 18)
	 * -lN (review messages)
	 * -u  (unread messages)
	 * -sN (shorten buffer)
	 * -eN (expire)
	 * -c  (check unread)
	 */

	argcount = 0;
	while(numargs && *(*options)[argcount] == '-') {
		(*options)[argcount]++;
		switch (toupper(*(*options)[argcount])){
			case 'D':
				debug++;
				verbose++;
				break;
			case 'V':
				verbose++;
				break;
			case 'T':
				use_tcp = 1;
				break;
			case 'R':
				(*options)[argcount]++;
				retries = atoi((*options)[argcount]);
				break;
			case 'P':
				(*options)[argcount]++;
				port = atoi((*options)[argcount]);
				break;
			case 'B':
#ifdef NO_BROADCAST
				fprintf(stderr, "Sorry, broadcast not available on this machine - not broadcasting.\n");
#else
				broadcasting = 1;
				use_tcp = 0;
#endif
				break;
			case 'G':
				use_tcp = 0;
				break;
			case 'L':
				(*options)[argcount]++;
				last_message(atoi((*options)[argcount]), shorten);
				exit(0);
				break;
			case 'U':
				unread_message(shorten);
				exit(0);
				break;
			case 'C':
				exit(check_unread());
				break;
			case 'S':
				(*options)[argcount]++;
				shorten = atoi((*options)[argcount]);
				if (shorten <= 0)
					shorten = 20;
				doshorten = 1;
				break;
			case 'E':
				(*options)[argcount]++;
				expire(atoi((*options)[argcount]));
				exit(0);
				break;
			default:
				usage();
				exit(1);
				/*NOTREACHED*/
		}
		argcount++;
		numargs--;
	}

	/* shorten if we need to */
	if (doshorten) {
		makeshort(shorten);
		exit(0);
	}

	if((numargs < 1) || (numargs > 2)) {
		usage();
		exit(1);
		/*NOTREACHED*/
	}

	/*
	 * Rip apart the recipient field and set the user, term,
	 * and host pointers.
	 */
	recipient = (*options)[argcount];

	msg_text = numargs == 2 ? (*options)[argcount+1] : NULL;

	if(debug) printf("recipient is '%s'\n", recipient);

	host = (char *)STRCHR(recipient, '@');
	if(host == NULL)
		host = empty_arg;
	else
		*host++ = '\0';

	term = (char *)STRCHR(recipient, ':');
	if(term == NULL)
		term = empty_arg;
	else
		*term++ = '\0';
	if(!strcmp(term, "all"))	/* external form is "all" */
		strcpy(term, "*");		/* protocol uses "*" */

	user = recipient;

	if(debug) printf("user = '%s', term='%s', host = '%s'\n",
		user, term, host);

	if (host[0] == '\0') {
#ifdef HAVE_GETHOSTNAME
		gethostname(local_name, sizeof(local_name));
#else /* HAVE_GETHOSTNAME */
		sysinfo(SI_HOSTNAME, local_name, sizeof(local_name));
#endif /* HAVE_GETHOSTNAME */
		host = local_name;
	}

	sin.sin_family = AF_INET;

	/*
	 * compute the port to use: consult /etc/services, but if not
	 * found use 18 (from the RFC). the -pN option overrides
	 */

	if(port == 0) {
		sp = getservbyname("message", (use_tcp ? "tcp" : "udp"));
		if(sp)
			sin.sin_port = sp->s_port;
		else
			sin.sin_port = htons(18);	/* from the RFC */
	}
	else
		sin.sin_port = htons(port);

 
	if(debug) printf("using port %d\n", htons(sin.sin_port));

	/*
	 * check to see if we're broadcasting. otherwise build an address for
	 * the designated host
	 */

	if(!broadcasting) {
		hp = gethostbyname(host);
		if(hp == NULL) {
			/* XXX need to add stuff to handle dotted IP addresses */
			fprintf(stderr, "%s: unknown host: %s\n", prog, host);
			exit(2);
		}
		MEMCPY((char *)&sin.sin_addr, (char *)hp->h_addr, hp->h_length);
	}

	/*
	 * now assemble the message. note that this procedure will only
	 * return if the assembly is successful
	 */
	assemble_message(user, term, msg_text);

	/*
	 * if broadcast, invoke broadcast_msg
	 */
	if(broadcasting) {
		udp_msg(&sin, retries, 1);
		exit(0);
		/*NOTREACHED*/
	}


	/*
	 * if requested, attempt to send message via TCP
	 */
	if(use_tcp)
		tcp_msg(&sin);
	/*
	 * If tcp_msg returns, it means that it was unable to bind
	 * to the port on the server. In this case, revert to UDP.
	 */
	udp_msg(&sin, retries, 0);
	exit(0);
}



/*
 * append a string to a message buffer, extra = 1 if we want
 * to add a trailing null-terminator into the message as well.
 * expands the buffer as necessary.
 */

void 
append_buffer(buffer, bufsize, msglen, addstr, extra)
	char **buffer;
	int *bufsize;
	int *msglen;
	char *addstr;
	int extra;
{
	int addlen;

	/* expand the buffer */
	addlen = strlen(addstr) + extra; 
	if (*msglen + addlen > *bufsize) {
		/* get more space */
		*bufsize *= 2;
		*buffer = realloc(*buffer, *bufsize);
		if (*buffer == NULL) {
			fprintf(stderr, "Out of memory.\n");
			exit(1);
		}
	}

	strcpy(*buffer + *msglen, addstr); 
	*msglen += addlen;
}


/*
 * assemble_message assembles a complete message in the buffer
 * msg and stores the length in msg_len. Note that, as defined
 * by the RFC, the message buffer includes embedded nulls.
 */

void
assemble_message(user, term, msg_text)
	char	*user;
	char	*term;
	char	*msg_text;
{
	char	*r;
	char	linebuff[256];
	char	*dp;
	int	buflen;
	FILE	*sigfile;
	char	*signature;
	char	*homedir;
	struct passwd *pwd;

	/* make a buffer */
	buflen = 1024;
	msg = malloc(buflen);
	if (msg == NULL) {
		fprintf(stderr, "Out of memory.\n");
		exit(1);
	}

	/* make the message */
	*msg = 'B';	/* per RFC */
	msg_len = 1;

	filter(user);
	append_buffer(&msg, &buflen, &msg_len, user, 1);
	filter(term);
	append_buffer(&msg, &buflen, &msg_len, term, 1);

	if (msg_text)
	    do_line(&msg, &buflen, &msg_len, msg_text);
	else {
	    int is_tty = isatty(0);

	    linebuff[sizeof(linebuff)-1] = '\0';
	    if (is_tty) {
                puts("Please begin typing your message now, line by line. When you are finished,");
		puts("send your message by pressing Control-D or pressing return on a blank");
		puts("line. To cancel your message, press Control-C.");
                puts("");
#ifdef USE_READLINE
		while ((r = readline("| ")) != NULL && *r != '\0') {
		    add_history(r);
	    	    do_line(&msg, &buflen, &msg_len, r);
		}
#else
		putchar('|');
		putchar(' ');
		/* eof or empty line terminates */
		while(((r = fgets(linebuff, sizeof(linebuff)-1, stdin)) != NULL) &&
			linebuff[1]) {
	    	    do_line(&msg, &buflen, &msg_len, linebuff);
		    putchar('|'); putchar(' ');
		}
#endif
		if (r == NULL)
		    /* we must have finished with ctrl-d */
		    putchar('\n');	/* newline for style */
	    }
	    else
		while(fgets(linebuff, sizeof(linebuff)-1, stdin) != NULL)
	    	    do_line(&msg, &buflen, &msg_len, linebuff);
	}
	
	append_buffer(&msg, &buflen, &msg_len, "", 1);

#ifndef BUGGY_LOGNAME
	dp = (char *)getlogin();
	if (dp == NULL || *dp == '\0')
#endif
	    {
	    struct passwd *me;
	    me = getpwuid(getuid());
	    if (me == NULL) {
		fprintf(stderr, "You don't exist -- Terminating...\n");
		exit(1);
	    }
	    dp = me->pw_name;
	}

	append_buffer(&msg, &buflen, &msg_len, dp, 1);
	if(debug) printf("sender username is '%s'\n", (dp ? dp : empty_arg));
	dp = (char *)ttyname(2); /* check standard error */
	if (dp == NULL) {
		append_buffer(&msg, &buflen, &msg_len, empty_arg, 1);
	} else {
		append_buffer(&msg, &buflen, &msg_len, dp, 1);
	}
	if(debug) printf("sender terminal is '%s'\n", (dp ? dp : empty_arg));
	
	sprintf(linebuff, "%ld", time(NULL));
	append_buffer(&msg, &buflen, &msg_len, linebuff, 1);
	if(debug) printf("cookie is '%s'\n", linebuff);

	/*
	 * Generate a signature from $HOME/.msgsig
	 */

	homedir = (char *)getenv("HOME");
	if (homedir == NULL) {
		pwd = (struct passwd *)getpwnam(dp);
		homedir = pwd->pw_dir;
	}
	sprintf(linebuff, "%s/.msgsig", homedir);
	signature = empty_arg;
	sigfile = fopen(linebuff, "r");
	if (sigfile != NULL) {
		linebuff[sizeof(linebuff)-1] = '\0';
		if (fgets(linebuff, sizeof(linebuff)-1, sigfile) != NULL) {
			if (linebuff[strlen(linebuff)-1] == '\n')
				linebuff[strlen(linebuff)-1] = '\0';
			filter(linebuff);
			signature = linebuff;
		}
		fclose(sigfile);
	}

	if(debug) printf("signature is '%s'\n", signature);
	append_buffer(&msg, &buflen, &msg_len, signature, 1);

	if(debug) printf("total message length is %d\n", msg_len);
}

void
do_line(buffer, bufsize, msglen, linebuff)
	char **buffer;
	int *bufsize;
	int *msglen;
	char *linebuff;
{
	int	addlen;

	filter(linebuff);
	addlen = strlen(linebuff);
	if (addlen > 0 && linebuff[addlen-1] == '\n')
		linebuff[addlen-1] = '\0'; /* minus the terminating LF */
	append_buffer(buffer, bufsize, msglen, linebuff, 0);
	append_buffer(buffer, bufsize, msglen, "\r\n", 0);
}

/*
 * tcp_msg(sp)
 *      sp points at a sockaddr_in which contains the
 *      port to be used.
 *
 * Send the assembled message using TCP. If the attempt is
 * successful, the program exits with a status of 0. If a client-
 * side error occurs (e.g. unable to open a socket) the program
 * exits with a positive non-zero status. If it proves impossible
 * to connect to the server, an error message is displayed and
 * the procedure returns. This allows the caller to retry using
 * UDP.
 */
	 
void
tcp_msg (sp)
	struct sockaddr_in *sp;
{
	int	s;
	struct	sockaddr_in sin;
	char	rcvbuf[256];
	int	rval;

	if(debug) printf("invoked tcp_msg()\n");

	if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		fprintf(stderr, "%s: unable to open socket.\n", prog);
		perror("Reason");
		exit(3);
	}

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(0);

	if(BIND(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
		fprintf(stderr, "%s: unable to set local socket address.\n", prog);
		perror("Reason");
		exit(3);
	}

	if(connect(s, (struct sockaddr *)sp, sizeof (*sp)) < 0) {
		fprintf(stderr, "%s: unable to connect to TCP server.\n", 
			prog);
		perror("Reason");
		return;
		/*NOTREACHED*/
	}

	if(verbose)
		printf("sending message...\n");
	if(write(s, msg, msg_len) < 0) {
		fprintf(stderr, "%s: unable to send message.\n", prog);
		perror("Reason");
		if (close(s))
			perror("close");
		exit(3);
	}
		/*
		 * wait for reply
		 */
	rval = read(s, rcvbuf, sizeof rcvbuf);
	if (rval < 1) {
		fprintf(stderr, "%s: no reply received.\n", prog);
		perror("Reason");
		if (close(s))
			perror("close");
		exit(3);
	}
	rcvbuf[(rval < sizeof(rcvbuf)) ? rval : sizeof(rcvbuf)-1] = 0;
	if (debug) printf("reply:'%s'\n", rcvbuf);
	if (rcvbuf[0] == '+') {
		if (verbose) printf("message delivered to recipient (%s)\n",
		    rcvbuf+1);
		exit(0);
		/*NOTREACHED*/
	}
	else if (rcvbuf[0] == '-') {
		printf("Message wasn't delivered - %s.\n", rcvbuf+1);
		exit(1);
		/*NOTREACHED*/
	}
	else {
		printf("Message wasn't delivered.\n");
		exit(2);
		/*NOTREACHED*/
	}
}

/*
 * udp_msg(sp, r)
 *      sp points at a sockaddr_in which contains the
 *      port to be used.
 *      r is the retry count to be used.
 *	broad is set if it's to be broadcast
 *
 * Send the assembled message to a specific destination using UDP.
 * This procedure will never return - it always exits with an
 * appropriate status code.
 */

void
udp_msg(sp, r, broad)
	struct	sockaddr_in *sp;
	int	r;
	int	broad;
{
	int	s;
	int	delivered = 0;
	struct	sockaddr_in sin;
	fd_set	ready;
	struct	timeval to;
	int	rval;
	int	toutwait;
	int	i;

if(debug) printf("invoked udp_msg(...,%d)\n", r);

	if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		fprintf(stderr, "%s: unable to open socket.\n", prog);
		perror("Reason");
		exit(3);
	}

#ifndef NO_BROADCAST
	if (broad) {
		i = 1;
		if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof i) < 0) {
			fprintf(stderr, "%s: unable to configure socket for broadcast.\n", prog);
			perror("Reason");
			exit(3);
		}

		find_broadcast_addresses(s);
	}
#endif

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(0);

	if(bind(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
		fprintf(stderr, "%s: unable to set local socket address.\n", prog);
		perror("Reason");
		exit(3);
	}
	
	/*
	 * the timeout wait starts at three seconds and backs off
	 * by two seconds each time we retry
	 */
	toutwait = 3;

	/* send the message */
	while(r--) {
		if(verbose)
			printf("sending message...\n");
		if (broad) {
#ifndef NO_BROADCAST
			for (i = 0; i < if_n; i++){
				if_a[i].sin_port = sp->sin_port;
				if(verbose) printf("sending message to %s\n",
					inet_ntoa(if_a[i].sin_addr));
				if(sendto(s, msg, msg_len, 0, (struct sockaddr *)&(if_a[i]),
			   	sizeof if_a[i]) < 0) {
					fprintf(stderr, "%s: unable to send message.\n", prog);
					perror("Reason");
					exit(3);
				}
			}
#endif
		}
		else {
			if(sendto(s, msg, msg_len, 0, (struct sockaddr *)sp, sizeof(*sp)) < 0) {
				fprintf(stderr, "%s: unable to send message.\n", prog);
				perror("Reason");
				exit(3);
			}
		}
		if(r) {
			/*
			 * wait for reply or timeout
			 */
			to.tv_sec = toutwait;
			to.tv_usec = 0;
			FD_ZERO(&ready);
			FD_SET(s, &ready);
			rval = select(20, &ready, (fd_set *)0, (fd_set *)0, &to);
			if(rval < 0)
			 	fprintf(stderr, "%s: interrupt\n", prog);
			if(rval == 1) {
				delivered = 1;
				udp_decode_ack(s);
				break;
			}
			/*
			 * falling through, must be interrupt or timeout
			 */
			toutwait += 2;
		}
	}
	if (!delivered)
		printf("Message unacknowledged - may not have been received.\n");
	if(debug) printf("closing and exiting\n");
	close(s);
	exit(0);
	/*NOTREACHED*/
}


#ifndef NO_BROADCAST
/*
 * find_broadcast_addresses
 *
 * This procedure is used by broadcast_msg to determine all of the
 * network interfaces configred on the local system, so that the
 * message can be broadcast over each of them. This logic is derived
 * from the SunOS documentation, and has also been tested on SVR4:
 * anyone porting this program to significantly different systems
 * should check this area carefully.
 *
 * The procedure uses the SIOCGIFCONF ioctl to retrieve the
 * interfaces. For each one, it retrieves the flags via SIOCGIFFLAGS.
 * For a point-to-point interface, the peer address is fetched using
 * SIOCGIFDSTADDR. For a broadcast inteface, the broadcast address
 * is obtained using SIOCGIFBRDADDR. The addresses are accumulated
 * in the array if_a, and if_n holds the count of addresses found.
 */

void
find_broadcast_addresses(s)
	int s;
{
	struct ifconf ifc;
	struct ifreq *ifr;
	char buf[4096], *cp, *cplim;
	int n;

	if_n = 0;

	ifc.ifc_len = sizeof buf;
	ifc.ifc_buf = buf;

	if(ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0) {
		fprintf(stderr, "%s: SIOCGIFCONF failed.\n", prog);
		perror("Reason");
		exit(3);
	}
	ifr = ifc.ifc_req;
	n = ifc.ifc_len/sizeof(struct ifreq);
	if(debug)
		printf("checking %d interfaces returned by SIOCGIFCONF...\n",
			n);

#ifdef RTM_ADD
#define max(a, b) (a > b ? a : b)
#define size(p) max((p).sa_len, sizeof(p))
#else
#define size(p) (sizeof (p))
#endif

	cplim = buf + ifc.ifc_len; /*skip over if's with big ifr_addr's */
	for (cp = buf; cp < cplim;
			cp += sizeof (ifr->ifr_name) + size(ifr->ifr_addr)) {
		ifr = (struct ifreq *)cp;
		if(ifr->ifr_addr.sa_family != AF_INET) {
			continue;
		}
		if(ioctl(s, SIOCGIFFLAGS, (char *)ifr) < 0) {
			perror("SIOCGIFFLAGS");
			continue;
		}
		if((ifr->ifr_flags & IFF_UP) == 0 ||
		   (ifr->ifr_flags & IFF_LOOPBACK) ||
		   (ifr->ifr_flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0)
			continue;
		if(ifr->ifr_flags & IFF_POINTOPOINT) {
			if(ioctl(s, SIOCGIFDSTADDR, (char *)ifr) < 0) {
				perror("SIOCGIFDSTADDR");
				continue;
			}
			MEMCPY((char *)&if_a[if_n++], (char *)&ifr->ifr_dstaddr,
				sizeof ifr->ifr_dstaddr);
		} else if(ifr->ifr_flags & IFF_BROADCAST) {
			if(ioctl(s, SIOCGIFBRDADDR, (char *)ifr) < 0) {
				perror("SIOCGIFBRDADDR");
				continue;
			}
			MEMCPY((char *)&if_a[if_n++], (char *)&ifr->ifr_broadaddr,
				sizeof ifr->ifr_broadaddr);
		}
	}

	if(debug)
		printf("found %d interfaces\n", if_n);	
	if(if_n == 0) {
		fprintf(stderr, "%s: no applicable network interfaces\n", prog);
		exit(3);
		/*NOTREACHED*/
	} 
}
#endif /* NO_BROADCAST */


/* read & decode the server's acknowledgement */
void
udp_decode_ack(s)
	int	s;
{
	struct	sockaddr_in from;
	int	fromlen, rval;
	char	rcvbuf[256];

	fromlen = sizeof(fromlen);
	rval = recvfrom(s, rcvbuf, sizeof rcvbuf, 0,
			(struct sockaddr *)&from, &fromlen);

	if (rval < 0) {
		perror("recvfrom");
		return;
	}
	rcvbuf[(rval < sizeof(rcvbuf)) ? rval : sizeof(rcvbuf)-1] = 0;

	if (rcvbuf[0] == '+') {
		if (verbose)
			printf("message delivered to recipient (%s)\n",
			    rcvbuf+1);
	}
	else if (rcvbuf[0] == '-')
		printf("Message wasn't delivered - %s.\n", rcvbuf+1);
	else
		printf("Unknown message acknowledgement (%s)\n", rcvbuf); 
}


/*
 * As noted in the RFC, it is important to filter out control
 * chracters and suchlike, since there may exist terminals
 * which will behave in bizarre and security-violating ways
 * if presented with certain control code sequences. The
 * server will also be filtering messages, but it is incumbent
 * upon a well-written client implementation to send only "clean"
 * messages. After all, there may exist servers which will reject
 * any message which does not filter cleanly.
 *
 * It is an open question as to how the filtering should be done.
 * One approach might be to squeeze out any invalid characters
 * silently. This would make debugging difficult. This implementation
 * replaces all non-printable characters with '?' characters.
 * 
 * mstrates@minkirri.apana.org.au: I've changed it to a | ..
 * seems to look a lot cleaner than a ? :-)
 */

void
filter(text)
char *text;
{
	while (*text) {
		if(!isprint(*text) && !isspace(*text))
			*text = '|';
		text++;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1