569 lines
14 KiB
C

/* This file is part of the Project Athena Zephyr Notification System.
* It contains code for the "zwrite" command.
*
* Created by: Robert French
*
* $Id$
*
* Copyright (c) 1987,1988 by the Massachusetts Institute of Technology.
* For copying and distribution information, see the file
* "mit-copyright.h".
*/
#include <sysdep.h>
#include <zephyr/mit-copyright.h>
#include <zephyr/zephyr.h>
#include <netdb.h>
#include <pwd.h>
#ifndef lint
static const char rcsid_zwrite_c[] = "$Id$";
#endif /* lint */
#define DEFAULT_CLASS "MESSAGE"
#define DEFAULT_INSTANCE "PERSONAL"
#define URGENT_INSTANCE "URGENT"
#define DEFAULT_OPCODE ""
#define FILSRV_CLASS "FILSRV"
#define MAXRECIPS 100
int nrecips, msgarg, verbose, quiet, nodot, cc;
char *whoami, *inst, *class, *opcode, *realm, *recips[MAXRECIPS], *sender = 0;
Z_AuthProc auth;
ZNotice_Kind_t kind = ACKED;
void un_tabify(char **, int *);
char *fix_filsrv_inst(char *);
void usage(char *);
void send_off(ZNotice_t *, int);
int
main(int argc, char *argv[])
{
int retval, arg, nocheck, nchars, msgsize, filsys, tabexpand;
char *message, *signature = NULL, *format = NULL;
static char bfr[BUFSIZ], classbfr[BUFSIZ], instbfr[BUFSIZ], sigbfr[BUFSIZ];
static char opbfr[BUFSIZ];
static ZNotice_t notice;
char *charset = NULL;
whoami = argv[0];
if ((retval = ZInitialize()) != ZERR_NONE) {
com_err(whoami, retval, "while initializing");
exit(1);
}
if (argc < 2)
usage(whoami);
auth = ZAUTH;
verbose = quiet = msgarg = nrecips = nocheck = filsys = nodot = 0;
tabexpand = 1;
class = ZGetVariable("zwrite-class");
if (class) {
(void) strcpy(classbfr, class);
class = classbfr;
}
else
class = DEFAULT_CLASS;
inst = ZGetVariable("zwrite-inst");
if (inst) {
(void) strcpy(instbfr, inst);
inst = instbfr;
}
else
inst = DEFAULT_INSTANCE;
opcode = ZGetVariable("zwrite-opcode");
if (opcode)
opcode = strcpy(opbfr, opcode);
else
opcode = DEFAULT_OPCODE;
signature = ZGetVariable("zwrite-signature");
if (signature) {
(void) strcpy(sigbfr, signature);
signature = sigbfr;
}
arg = 1;
for (;arg<argc && !msgarg; arg++) {
if (*argv[arg] != '-') {
recips[nrecips++] = argv[arg];
continue;
}
if (strlen(argv[arg]) > 2)
usage(whoami);
switch (argv[arg][1]) {
case 'a': /* Backwards compatibility */
auth = ZAUTH;
break;
case 'o':
class = DEFAULT_CLASS;
inst = DEFAULT_INSTANCE;
opcode = DEFAULT_OPCODE;
break;
case 'd':
auth = ZNOAUTH;
break;
case 'v':
verbose = 1;
break;
case 'q':
quiet = 1;
break;
case 'n':
nocheck = 1;
break;
case 't':
tabexpand = 0;
break;
case 'u':
inst = URGENT_INSTANCE;
break;
case 'O':
if (arg == argc-1)
usage(whoami);
arg++;
opcode = argv[arg];
break;
case 'i':
if (arg == argc-1 || filsys == 1)
usage(whoami);
arg++;
inst = argv[arg];
filsys = -1;
break;
case 'c':
if (arg == argc-1 || filsys == 1)
usage(whoami);
arg++;
class = argv[arg];
filsys = -1;
break;
case 'f':
if (arg == argc-1 || filsys == -1)
usage(whoami);
arg++;
class = FILSRV_CLASS;
inst = fix_filsrv_inst(argv[arg]);
filsys = 1;
nocheck = 1; /* implied -n (no ping) */
break;
case 's':
if (arg == argc-1)
usage(whoami);
arg++;
signature = argv[arg];
break;
case 'm':
if (arg == argc-1)
usage(whoami);
nocheck = 1; /* implied -n (no ping) */
msgarg = arg+1;
break;
case 'l': /* literal */
nodot = 1;
break;
case 'F':
if (arg == argc-1)
usage(whoami);
arg++;
format = argv[arg];
break;
case 'r':
if (arg == argc-1)
usage(whoami);
arg++;
realm = argv[arg];
break;
case 'C':
cc = 1;
break;
case 'x':
if (arg == argc-1)
usage(whoami);
arg++;
charset = argv[arg];
break;
case 'S':
if (arg == argc-1)
usage(whoami);
arg++;
sender = argv[arg];
auth = ZNOAUTH;
break;
case 'U':
kind = UNACKED;
nocheck = 1; /* implied -n (no ping) */
break;
default:
usage(whoami);
}
}
if (!nrecips && !(strcmp(class, DEFAULT_CLASS) ||
(strcmp(inst, DEFAULT_INSTANCE) &&
strcmp(inst, URGENT_INSTANCE)))) {
/* must specify recipient if using default class and
(default instance or urgent instance) */
fprintf(stderr, "No recipients specified.\n");
usage(whoami);
}
if (!signature) {
/* try to find name in the password file */
register struct passwd *pwd;
register char *cp = sigbfr;
register char *cp2, *pp;
pwd = getpwuid(getuid());
if (pwd) {
cp2 = pwd->pw_gecos;
for (; *cp2 && *cp2 != ',' ; cp2++) {
if (*cp2 == '&') {
pp = pwd->pw_name;
*cp++ = islower(*pp) ? toupper(*pp) : *pp;
pp++;
while (*pp)
*cp++ = *pp++;
} else
*cp++ = *cp2;
}
signature = sigbfr;
}
}
memset(&notice, 0, sizeof(ZNotice_t));
notice.z_kind = kind;
notice.z_port = 0;
notice.z_class = class;
notice.z_class_inst = inst;
notice.z_opcode = "PING";
notice.z_sender = sender;
notice.z_message_len = 0;
notice.z_recipient = "";
notice.z_charset = ZGetCharset(charset);
if (format)
notice.z_default_format = format;
else
notice.z_default_format = "http://zephyr.1ts.org/wiki/df";
if (!nocheck && nrecips)
send_off(&notice, 0);
if (!msgarg && isatty(0)) {
if (nodot)
printf("Type your message now. End with the end-of-file character.\n");
else
printf("Type your message now. End with control-D or a dot on a line by itself.\n");
}
message = NULL;
msgsize = 0;
if (signature) {
message = malloc((unsigned)(strlen(signature)+2));
(void) strcpy(message, signature);
msgsize = strlen(message);
message[msgsize++] = '\0';
} else {
message = malloc(1);
message[msgsize++] = '\0';
}
if (cc && nrecips > 1) {
int size = msgsize;
for (arg=0;arg<nrecips;arg++)
size += (strlen(recips[arg]) + 2);
size += 6; /* for the newlines and "cc: " */
message = realloc(message, (unsigned) size);
(void) strcpy(message+msgsize, "CC: ");
msgsize += 4;
for (arg=0;arg<nrecips;arg++) {
(void) strcpy(message+msgsize, recips[arg]);
msgsize += strlen(recips[arg]);
if (arg != nrecips-1) {
message[msgsize] = ' ';
msgsize++;
}
}
message[msgsize] = '\n';
msgsize += 1;
}
if (msgarg) {
int size = msgsize;
for (arg=msgarg;arg<argc;arg++)
size += (strlen(argv[arg]) + 1);
size++; /* for the newline */
message = realloc(message, (unsigned) size);
for (arg=msgarg;arg<argc;arg++) {
(void) strcpy(message+msgsize, argv[arg]);
msgsize += strlen(argv[arg]);
if (arg != argc-1) {
message[msgsize] = ' ';
msgsize++;
}
}
message[msgsize] = '\n';
msgsize += 1;
} else {
if (isatty(0)) {
for (;;) {
unsigned int l;
if (!fgets(bfr, sizeof bfr, stdin))
break;
if (!nodot && bfr[0] == '.' &&
(bfr[1] == '\n' || bfr[1] == '\0'))
break;
l = strlen(bfr);
message = realloc(message, msgsize+l+1);
(void) strcpy(message+msgsize, bfr);
msgsize += l;
}
if (ferror(stdin)) /* drop the message */
exit(1);
message = realloc(message, (unsigned)(msgsize+1));
}
else { /* Use read so you can send binary messages... */
while ((nchars = read(fileno(stdin), bfr, sizeof bfr))) {
if (nchars == -1) {
fprintf(stderr, "Read error from stdin! Can't continue!\n");
exit(1);
}
message = realloc(message, (unsigned)(msgsize+nchars));
(void) memcpy(message+msgsize, bfr, nchars);
msgsize += nchars;
}
/* end of msg */
message = realloc(message, (unsigned)(msgsize+1));
}
}
notice.z_opcode = opcode;
if (tabexpand)
un_tabify(&message, &msgsize);
notice.z_message = message;
notice.z_message_len = msgsize;
send_off(&notice, 1);
exit(0);
}
void
send_off(ZNotice_t *notice, int real)
{
int i, success, retval;
char realm_recip[BUFSIZ], dest[3 * BUFSIZ];
ZNotice_t retnotice;
success = 0;
for (i=0;i<nrecips || i==0;i++) {
if (realm) {
sprintf(realm_recip, "%s@%s", (nrecips) ? recips[i] : "", realm);
notice->z_recipient = realm_recip;
} else {
notice->z_recipient = (nrecips) ? recips[i] : "";
}
if (nrecips)
strcpy(dest, recips[i]);
else if (!strcmp(class, DEFAULT_CLASS))
sprintf(dest, "instance \"%s\"", inst);
else if (!strcmp(inst, DEFAULT_INSTANCE))
sprintf(dest, "class \"%s\"", class);
else
sprintf(dest, "class \"%s\", instance \"%s\"", class, inst);
if (verbose && real)
printf("Sending %smessage, class %s, instance %s, to %s\n",
auth?"authenticated ":"",
class, inst,
nrecips?notice->z_recipient:"everyone");
if ((retval = ZSendNotice(notice, auth)) != ZERR_NONE) {
com_err(whoami, retval, "while sending notice to %s", dest);
break;
}
if (kind == UNACKED)
continue;
if (real && !quiet) {
if (verbose)
printf("Queued... ");
else
printf("Message queued for %s... ", dest);
fflush(stdout);
}
if ((retval = ZIfNotice(&retnotice, (struct sockaddr_in *) 0,
ZCompareUIDPred,
(char *)&notice->z_uid)) != ZERR_NONE) {
com_err(whoami, retval, "while waiting for acknowledgement for %s",
dest);
continue;
}
if (retnotice.z_kind == SERVNAK) {
if (!quiet) {
printf("Received authorization failure while sending to %s\n",
dest);
}
ZFreeNotice(&retnotice);
break; /* if auth fails, punt */
}
if (retnotice.z_kind != SERVACK || !retnotice.z_message_len) {
if (!quiet) {
printf("Detected server failure while receiving acknowledgement for %s\n",
dest);
}
ZFreeNotice(&retnotice);
continue;
}
if (!strcmp(retnotice.z_message, ZSRVACK_SENT)) {
success = 1;
if (real && !quiet)
printf("sent\n");
} else if (!strcmp(retnotice.z_message, ZSRVACK_NOTSENT)) {
if (verbose && real && !quiet) {
if (strcmp(class, DEFAULT_CLASS)) {
fprintf(stderr, "Not logged in or not subscribing to class %s, instance %s\n",
class, inst);
} else {
fprintf(stderr,
"Not logged in or not subscribing to messages\n");
}
}
else if (!quiet) {
if (!nrecips) {
fprintf(stderr,
"No one subscribing to class %s, instance %s\n",
class, inst);
} else {
if (strcmp(class, DEFAULT_CLASS)) {
fprintf(stderr, "%s: Not logged in or not subscribing to class %s, instance %s\n",
notice->z_recipient, class, inst);
} else {
fprintf(stderr, "%s: Not logged in or not subscribing to messages\n",
notice->z_recipient);
}
}
}
}
else
printf("Internal failure - illegal message field in server response\n");
ZFreeNotice(&retnotice);
}
if (!success)
exit(1);
}
void
usage(char *s)
{
fprintf(stderr,
"Usage: %s [-a] [-o] [-d] [-v] [-q] [-n] [-t] [-u] [-l]\n\
\t[-c class] [-i inst] [-O opcode] [-f fsname] [-s signature] [-C]\n\
\t[user ...] [-F format] [-r realm] [-x charset] [-m message]\n", s);
fprintf(stderr,"\t-f and -c are mutually exclusive\n\
\t-f and -i are mutually exclusive\n\
\trecipients must be specified unless -c or -f specifies a class\n\
\tother than the default class or -i or -f specifies an instance\n\
\tother than the default or urgent instance\n");
exit(1);
}
/*
if the -f option is specified, this routine is called to canonicalize
an instance of the form hostname[:pack]. It turns the hostname into the
name returned by gethostbyname(hostname)
*/
char *
fix_filsrv_inst(char *str)
{
static char fsinst[BUFSIZ];
char *ptr;
struct hostent *hp;
ptr = strchr(str,':');
if (ptr)
*ptr = '\0';
hp = gethostbyname(str);
if (!hp) {
if (ptr)
*ptr = ':';
return(str);
}
(void) strcpy(fsinst, hp->h_name);
if (ptr) {
(void) strcat(fsinst, ":");
ptr++;
(void) strcat(fsinst, ptr);
}
return(fsinst);
}
/* convert tabs in the buffer into appropriate # of spaces.
slightly tricky since the buffer can have NUL's in it. */
#ifndef TABSTOP
#define TABSTOP 8 /* #chars between tabstops */
#endif /* ! TABSTOP */
void
un_tabify(char **bufp,
int *sizep)
{
register char *cp, *cp2;
char *cp3;
register int i;
register int column; /* column of next character */
register int size = *sizep;
for (cp = *bufp, i = 0; size; size--, cp++)
if (*cp == '\t')
i++; /* count tabs in buffer */
if (!i)
return; /* no tabs == no work */
/* To avoid allocation churning, allocate enough extra space to convert
every tab into TABSTOP spaces */
/* only add (TABSTOP-1)x because we re-use the cell holding the
tab itself */
cp = malloc((unsigned)(*sizep + (i * (TABSTOP-1))));
if (!cp) /* XXX */
return; /* punt expanding if memory fails */
cp3 = cp;
/* Copy buffer, converting tabs to spaces as we go */
for (cp2 = *bufp, column = 1, size = *sizep; size; cp2++, size--) {
switch (*cp2) {
case '\n':
case '\0':
/* newline or null: reset column */
column = 1;
*cp++ = *cp2; /* copy the newline */
break;
default:
/* copy the character */
*cp = *cp2;
cp++;
column++;
break;
case '\t':
/* it's a tab, compute how many spaces to expand into. */
i = TABSTOP - ((column - 1) % TABSTOP);
for (; i > 0; i--) {
*cp++ = ' '; /* fill in the spaces */
column++;
(*sizep)++; /* increment the size */
}
(*sizep)--; /* remove one (we replaced the tab) */
break;
}
}
free(*bufp); /* free the old buf */
*bufp = cp3;
return;
}