/*
* 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