home *** CD-ROM | disk | FTP | other *** search
- /* RE_SID: @(%)/usr/re/builds/pcnfs/unix/rfcmsg/SCCS/s.msgclnt.c 1.7 93/08/20 16:12:35 SMI */
- /* @(#)msgclnt.c 1.7 8/20/93 */
- /*
- * msgclnt.c
- *
- * Copyright (c) 1991-1993 Sun Microsystems, Inc.
- *
- * This is a client implementation of the Message Send protocol
- * defined in RFCxxxx. 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.
- */
- #include <sys/types.h>
- #include <sys/time.h>
- #ifdef SVR4
- #include <sys/select.h>
- #include <memory.h>
- #define bcopy(from, to, howmuch) memcpy(to, from, howmuch)
- #endif
- #include <sys/socket.h>
- #include <sys/sockio.h>
- #include <netinet/in.h>
- #include <net/if.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- #include <time.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[1024]; /* message assembly buffer */
-
- /*
- * types for all procedures. for ANSI standard C, these should
- * be edited into full-blown prototypes.
- */
- void usage();
- int main();
- void assemble_message();
- void do_line();
- void tcp_msg();
- void udp_msg();
- void broadcast_msg();
- void find_broadcast_addresses();
- void filter();
-
- extern char *getlogin();
- extern char *ttyname();
-
-
- void
- usage()
- {
- fprintf(stderr, "usage: %s [-t][-d][-v][-rN][-pN] recipient ['message']\n", prog);
- fprintf(stderr, " -t - use TCP; default is UDP. (ignored for broadcasts)\n");
- fprintf(stderr, " -d - turn on debugging (implies -v)\n");
- fprintf(stderr, " -v - verbose mode\n");
- fprintf(stderr, " -rN - UDP retransmissions (default: 3)\n");
- fprintf(stderr, " -pN - use port N (default: 18)\n");
- fprintf(stderr, " recipient may have any of the following forms:\n");
- fprintf(stderr, " user - broadcast for user at any host\n");
- fprintf(stderr, " user@host - directed to user at given host\n");
- fprintf(stderr, " @host - default dest. at given host\n");
- fprintf(stderr, " @ - default dest. on all hosts\n");
- fprintf(stderr, " user:term@host - user logged in to term on host\n");
- fprintf(stderr, " :term@host - specified terminal on host\n");
- fprintf(stderr, " :all@host - all terminals on given host\n");
- fprintf(stderr, " :all - all terminals on all hosts\n");
- fprintf(stderr, "If there is no message argument, ");
- fprintf(stderr, "the message is read from standard input.\n");
- }
-
- int
- main(argc, argv)
- int argc;
- char *argv[];
- {
-
- int use_tcp = 0;
- int retries = 3;
- short port = 0;
- int broadcasting = 0;
-
- char *recipient;
- char *user;
- char *term;
- char *host;
- char *msg_text;
-
- struct sockaddr_in sin;
- struct servent *sp;
- struct hostent *hp;
- struct netent *np;
-
-
- prog = *argv++;
- argc--;
-
- /* process options:
- * -d (debug)
- * -t (use TCP)
- * -rN (set retransmission count)
- * -pN (use port N instead of 18)
- */
-
- while(argc && *argv[0] == '-') {
- (*argv)++;
- switch (toupper(*argv[0])){
- case 'D':
- debug++;
- verbose++;
- break;
- case 'V':
- verbose++;
- break;
- case 'T':
- use_tcp++;
- break;
- case 'R':
- (*argv)++;
- retries = atoi(*argv);
- break;
- case 'P':
- (*argv)++;
- port = atoi(*argv);
- break;
- default:
- usage();
- exit(1);
- /*NOTREACHED*/
- }
- argv++;
- argc--;
- }
- if((argc < 1) || (argc > 2)) {
- usage();
- exit(1);
- /*NOTREACHED*/
- }
-
- /*
- * Rip apart the recipient field and set the user, term,
- * and host pointers.
- */
- recipient = argv[0];
-
- msg_text = argc == 2 ? argv[1] : NULL;
-
- if(debug) printf("recipient is '%s'\n", recipient);
-
- host = strchr(recipient, '@');
- if(host == NULL)
- host = empty_arg;
- else
- *host++ = '\0';
-
- term = 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);
-
- broadcasting = (host[0] == '\0');
-
- 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; if so, ignore use_tcp and
- * build a broadcast address. otherwise build an address for
- * the designated host
- */
-
- if(broadcasting) {
- if(use_tcp)
- fprintf(stderr, "%s: broadcast requested, so -t is ignored\n", prog);
- use_tcp = 0;
- }
- else {
- 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) {
- broadcast_msg(&sin, retries);
- 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);
- exit(0);
- }
-
- /*
- * 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 *cp;
- int i;
- int j;
- char linebuff[256];
- struct utmp *utp;
- char *dp;
-
- cp = msg;
-
- *cp++ = 'B'; /* per RFC */
- msg_len = 1;
-
- #define APPEND(s) {strcpy(cp, s); j = strlen(s); cp += j; *cp++ = '\0'; msg_len += j + 1; }
-
- filter(user);
- APPEND(user);
- filter(term);
- APPEND(term);
-
- if (msg_text)
- do_line(msg_text, &cp, &msg_len);
- else {
- while(gets(linebuff) != NULL) {
- do_line(linebuff, &cp, &msg_len);
- }
- }
-
- *cp++ = '\0';
- msg_len++;
-
- dp = getlogin();
- if (dp == NULL) {
- APPEND(empty_arg);
- } else {
- APPEND(dp);
- }
- if(debug) printf("sender username is '%s'\n", (dp ? dp : empty_arg));
- dp = ttyname(2); /* check standard error */
- if (dp == NULL) {
- APPEND(empty_arg);
- } else {
- APPEND(dp);
- }
- if(debug) printf("sender terminal is '%s'\n", (dp ? dp : empty_arg));
-
- sprintf(linebuff, "%ld", time(NULL));
- APPEND(linebuff);
- if(debug) printf("cookie is '%s'\n", linebuff);
-
- /*
- * In this version, we always generate an empty signature part
- */
- if(debug) printf("signature is '%s'\n", empty_arg);
- APPEND(empty_arg); /* null signature */
-
-
- if(debug) printf("total message length is %d\n", msg_len);
- if(msg_len > 512) {
- fprintf(stderr, "%s: message (inc. control fields) exceeds 512 byte limit\n", prog);
- exit(2);
- }
- }
-
- void
- do_line(linebuff, cpp, msg_lenp)
- char *linebuff;
- char **cpp;
- int *msg_lenp;
- {
- int j;
- char *cp = *cpp;
- int msg_len = *msg_lenp;
-
- filter(linebuff);
- j = strlen(linebuff);
- if((msg_len + j) > (512-28-3)) {
- fprintf(stderr, "%s: message (inc. control fields) exceeds 512 byte limit\n", prog);
- exit(2);
- /*NOTREACHED*/
- }
- strcpy(cp, linebuff);
- cp += j;
- *cp++ = '\015';
- *cp++ = '\012';
- msg_len += j + 2;
-
- *cpp = cp;
- *msg_lenp = msg_len;
- }
-
- /*
- * 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;
- int on = 1;
- 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");
- exit(3);
- }
- /*
- * wait for reply
- */
- rval = read(s, rcvbuf, 256);
- if(rval < 1) {
- fprintf(stderr, "%s: no reply received.\n", prog);
- perror("Reason");
- exit(3);
- }
- rcvbuf[rval] = '\0';
- if(debug) printf("reply:'%s'\n", rcvbuf);
- if(rcvbuf[0] == '+') {
- if(verbose) printf("message delivered to recipient\n");
- exit(0);
- /*NOTREACHED*/
- } else {
- if(verbose) printf("message not delivered\n");
- exit(5);
- /*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.
- *
- * 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)
- struct sockaddr_in *sp;
- int r;
-
- {
- int s;
- int on = 1;
- struct sockaddr_in sin;
- fd_set ready;
- struct timeval to;
- char rcvbuf[256];
- int rval;
-
- 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);
- }
-
- 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);
- }
- while(r--) {
- if(verbose)
- printf("sending message...\n");
- 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 = 2;
- 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) {
- /* XXX to do : read and decode result */
- if(verbose)
- printf("%s: message receipt acknowledgement received\n", prog);
- break;
- }
- /*
- * falling through, must be interrupt or timeout
- */
- }
- }
- if(debug) printf("closing and exiting\n");
- close(s);
- exit(0);
- /*NOTREACHED*/
- }
-
- /*
- * 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.
- */
-
- #define INTERFACES 32
- int if_n;
- struct sockaddr_in if_a[INTERFACES];
-
- void
- find_broadcast_addresses(s)
- int s;
- {
- struct ifconf ifc;
- struct ifreq *ifr;
- char buf[4096];
- 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);
-
- for (; --n >= 0; ifr++) {
- 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;
- }
- bcopy((char *)&ifr->ifr_dstaddr, (char *)&if_a[if_n++],
- sizeof ifr->ifr_dstaddr);
- } else if(ifr->ifr_flags & IFF_BROADCAST) {
- if(ioctl(s, SIOCGIFBRDADDR, (char *)ifr) < 0) {
- perror("SIOCGIFBRDADDR");
- continue;
- }
- bcopy((char *)&ifr->ifr_broadaddr, (char *)&if_a[if_n++],
- 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*/
- }
- }
-
- /*
- * broadcast_msg(sp, r)
- * sp points at a sockaddr_in which contains the
- * port to be used.
- * r is the retry count to be used.
- *
- * Broadcast the message 'r' times over all network interfaces.
- * This procedure never returns: it will eventually exit with a suitable
- * status code. Broadcasts are sent at intervals of 2 seconds.
- * (It might be advisable to make this parameter adjustable.)
- *
- * Normally a broadcast message will not elicit a reply. However
- * if it does, we assume that we've reached the right destination
- * and we quit without further ado.
- */
-
- void
- broadcast_msg (sp, r)
- struct sockaddr_in *sp;
- int r;
-
- {
- int s;
- int on = 1;
- struct sockaddr_in sin;
- fd_set ready;
- struct timeval to;
- char rcvbuf[256];
- int rval;
- int i;
-
- if(debug) printf("invoked broadcast_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);
- }
-
- i = 1;
- if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof i) < 0) {
- fprintf(stderr, "%s: unable to configure socket for broadcast.\n", prog);
- perror("Reason");
- exit(3);
- }
-
- find_broadcast_addresses(s);
-
- 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);
- }
- while(r--) {
- 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);
- }
- }
- if(r) {
- /*
- * wait for reply or timeout
- */
- to.tv_sec = 2;
- 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) {
- /* XXX to do : read and decode result */
- if(verbose)
- printf("%s: message receipt acknowledgement received\n", prog);
- break;
- }
- /*
- * falling through, must be interrupt or timeout
- */
- }
- }
- if(debug) printf("closing and exiting\n");
- close(s);
- exit(0);
- /*NOTREACHED*/
- }
-
- /*
- * 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.
- */
-
- void
- filter(text)
- char *text;
- {
- while (*text) {
- if(!isprint(*text) && !isspace(*text))
- *text = '?';
- text++;
- }
- }
-
-