/* * msend.c * * Copyright (c) 1997 Michael Strates * Copyright (c) 1993 Zik Saleeba * Copyright (c) 1993 Andrew Herbert * 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 * Modified by: Andrew Herbert * Modified by: Zik Saleeba * Modified by: Michael Strates * Modified by: Martin Rudat */ /* $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 \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++; } }