/variants/slashem/sys/vms/vmsmail.c
https://bitbucket.org/clivecrous/ruhack · C · 486 lines · 311 code · 42 blank · 133 comment · 120 complexity · 06b515e3883f7bedad7ea21649d02e0d MD5 · raw file
- /* SCCS Id: @(#)vmsmail.c 3.4 1995/06/01 */
- /* Copyright (c) Robert Patrick Rankin, 1991. */
- /* NetHack may be freely redistributed. See license for details. */
- #include "config.h"
- #include "mail.h"
- /* lint supression due to lack of extern.h */
- unsigned long NDECL(init_broadcast_trapping);
- unsigned long NDECL(enable_broadcast_trapping);
- unsigned long NDECL(disable_broadcast_trapping);
- struct mail_info *NDECL(parse_next_broadcast);
- #ifdef MAIL
- #include "wintype.h"
- #include "winprocs.h"
- #include <ctype.h>
- #include <descrip.h>
- #include <errno.h>
- # ifndef __GNUC__
- #include <msgdef.h>
- # else
- # define MSG$_TRMHANGUP 6
- # define MSG$_TRMBRDCST 83
- # endif /*__GNUC__*/
- #include <signal.h>
- /* #include <string.h> */
- # define vms_ok(sts) ((sts)&1)
- static struct mail_info *FDECL(parse_brdcst, (char *));
- static void FDECL(filter_brdcst, (char *));
- static void NDECL(flush_broadcasts);
- static void FDECL(broadcast_ast, (int));
- extern char *FDECL(eos, (char *));
- extern char *FDECL(strstri, (const char *,const char *));
- extern int FDECL(strncmpi, (const char *,const char *,int));
- extern size_t FDECL(strspn, (const char *,const char *));
- #ifndef __DECC
- extern int VDECL(sscanf, (const char *,const char *,...));
- #endif
- extern unsigned long
- smg$create_pasteboard(),
- smg$get_broadcast_message(),
- smg$set_broadcast_trapping(),
- smg$disable_broadcast_trapping();
- extern volatile int broadcasts; /* defining declaration in mail.c */
- static long pasteboard_id = 0; /* SMG's magic cookie */
- /*
- * Mail (et al) overview:
- *
- * When a broadcast is asynchronously captured, a volatile counter
- * ('broadcasts') is incremented. Each player turn, ckmailstatus() polls
- * the counter and calls parse_next_broadcast() if it's positive; this
- * returns some display text, object name, and response command, which is
- * passed to newmail(). Routine newmail() generates a mail-daemon monster
- * who approaches the character, "speaks" the display text, and delivers
- * a scroll of mail pre-named to the object name; the response command is
- * initially appended to the name, so that the object is tagged with both
- * of them; a NUL is inserted to terminate the ordinary name and hide the
- * command. (If the player renames such a scroll, the hidden command will
- * be lost; who cares?) Unrecognized broadcasts result in the mail-daemon
- * arriving and announcing the display text, but no scroll being created.
- * If SHELL is undefined, then all broadcasts are treated as 'other'; since
- * no subproceses are allowed, there'd be no way to respond to the scroll.
- *
- * When a scroll of mail is read by the character, readmail() extracts
- * the hidden command string and uses it for the default when prompting the
- * player for a system command to spawn. The player may enter any command
- * he or she chooses, or just <return> to accept the default or <escape> to
- * avoid executing any command. If the command is "SPAWN", a regular shell
- * escape to DCL is performed; otherwise, the indicated single command is
- * spawned. Either way, NetHack resumes play when the subprocess terminates
- * or explicitly reattaches to its parent.
- *
- * Broadcast parsing:
- *
- * The following broadcast messages are [attempted to be] recognized:
- * text fragment name for scroll default command
- * New mail VMSmail MAIL
- * New ALL-IN-1 MAIL A1mail A1M
- * Software Tools mail STmail MSG [+folder]
- * MM mail MMmail MM
- * WPmail: New mail WPmail OFFICE/MAIL
- * **M400 mail M400mail M400
- * " mail", ^"mail " unknown mail SPAWN
- * " phoning" Phone call PHONE ANSWER
- * talk-daemon...by...foo Talk request TALK[/OLD] foo@bar
- * (node)user - Bitnet noise XYZZY user@node
- * Anything else results in just the message text being passed along, no
- * scroll of mail so consequently no command to execute when scroll read.
- * The user can set up ``$ XYZZY :== SEND'' prior to invoking NetHack if
- * vanilla JNET responses to Bitnet messages are prefered.
- *
- * Static return buffers are used because only one broadcast gets
- * processed at a time, and the essential information in each one is
- * either displayed and discarded or copied into a scroll-of-mail object.
- *
- * The test driver code below can be used to check out potential new
- * entries without rebuilding NetHack itself. CC/DEFINE="TEST_DRIVER"
- * Link it with hacklib.obj or nethack.olb/incl=hacklib (not nethack/lib).
- */
- static struct mail_info msg; /* parse_*()'s return buffer */
- static char nam_cmd_buf[63], /* maximum onamelth, size of ONAME(object) */
- txt_buf[255+1]; /* same size as used for message buf[] */
- /* try to decipher and categorize broadcast message text
- */
- static struct mail_info *
- parse_brdcst(buf) /* called by parse_next_broadcast() */
- char *buf; /* input: filtered broadcast text */
- {
- int typ;
- char *txt;
- const char *nam, *cmd;
- # ifdef SHELL /* only parse if spawned commands are enabled */
- register char *p, *q;
- boolean is_jnet_send;
- char cmd_buf[127+1], user[127+1], node[127+1], sentinel;
- /* Check these first; otherwise, their arbitrary text would enable
- easy spoofing of some other message patterns. Unfortunately,
- any home-grown broadcast delivery program poses a similar risk. */
- if (!strncmpi(buf, "reply received", 14)) goto other;
- is_jnet_send = (sscanf(buf, "(%[^)])%s -%c", node, user, &sentinel) == 3);
- if (is_jnet_send) goto jnet_send;
- /* scan the text more or less by brute force */
- if ((q = strstri(buf, " mail")) != 0 || /* all known mail broadcasts */
- !strncmpi(q = buf, "mail ", 5)) { /* unexpected alternative */
- typ = MSG_MAIL;
- p = strstri(q, " from");
- txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;
- if (!strncmpi(buf, "new mail", 8)) {
- /*
- New mail [on node FOO] from [SPAM::]BAR [\"personal_name\"] [\(HH:MM:SS\)]
- */
- nam = "VMSmail"; /* assume VMSmail */
- cmd = "MAIL";
- if (txt && (p = strrchr(txt, '(')) > txt && /* discard time */
- (--p, strspn(p, "0123456789( :.)") == strlen(p))) *p = '\0';
- } else if (!strncmpi(buf, "new all-in-1", 12)) {
- int i;
- /*
- New ALL-IN-1 MAIL message [on node FOO] from Personal Name \(BAR@SPAM\) [\(DD-MMM-YYYY HH:MM:SS\)]
- */
- nam = "A1mail";
- cmd = "A1M";
- if (txt && (p = strrchr(txt, '(')) > txt && /* discard date+time */
- sscanf(p-1," (%*d-%*[^-]-%*d %*d:%*d:%d) %c",&i,&sentinel) == 1)
- *--p = '\0';
- } else if (!strncmpi(buf, "software tools", 14)) {
- /*
- Software Tools mail has arrived on FOO from \'BAR\' [in SPAM]
- */
- nam = "STmail";
- cmd = "MSG";
- if (txt && (p = strstri(p, " in ")) != 0) /* specific folder */
- cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);
- } else if (q - 2 >= buf && !strncmpi(q - 2, "mm", 2)) {
- /*
- {MultiNet\ |PMDF\/}MM mail has arrived on FOO from BAR\n
- [Subject: subject_text] (PMDF only)
- */
- nam = "MMmail"; /* MultiNet's version of MM */
- cmd = "MM"; /*{ perhaps "MM READ"? }*/
- } else if (!strncmpi(buf, "wpmail:", 7)) {
- /*
- WPmail: New mail from BAR. subject_text
- */
- nam = "WPmail"; /* WordPerfect [sic] Office */
- cmd = "OFFICE/MAIL";
- } else if (!strncmpi(buf, "**m400 mail", 7)) {
- /*
- **M400 mail waiting**
- */
- nam = "M400mail"; /* Messenger 400 [not seen] */
- cmd = "M400";
- } else {
- /* not recognized, but presumed to be mail */
- nam = "unknown mail";
- cmd = "SPAWN"; /* generic escape back to DCL */
- txt = (char *) 0; /* don't rely on "from" info here */
- }
- if (!txt) txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);
- /*
- : end of mail recognition; now check for call-type interruptions...
- */
- } else if ((q = strstri(buf, " phoning")) != 0) {
- /*
- BAR is phoning you [on FOO] \(HH:MM:SS\)
- */
- typ = MSG_CALL;
- nam = "Phone call";
- cmd = "PHONE ANSWER";
- if (!strncmpi(q + 8, " you", 4)) q += (8 + 4), *q = '\0';
- txt = strcat(strcpy(txt_buf, "Do you hear ringing? "), buf);
- } else if ((q = strstri(buf, " talk-daemon")) != 0 ||
- (q = strstri(buf, " talk_daemon")) != 0) {
- /*
- Message from TALK-DAEMON@FOO at HH:MM:SS\n
- Connection request by BAR@SPAM\n
- \[Respond with: TALK[/OLD] BAR@SPAM\]
- */
- typ = MSG_CALL;
- nam = "Talk request"; /* MultiNet's TALK and/or TALK/OLD */
- cmd = "TALK";
- if ((p = strstri(q, " by ")) != 0) {
- txt = strcat(strcpy(txt_buf, "Talk request from"), p + 3);
- if ((p = strstri(p, "respond with")) != 0) {
- if (*(p-1) == '[') *(p-1) = '\0'; else *p = '\0'; /* terminate */
- p += (sizeof "respond with" - sizeof "");
- if (*p == ':') p++;
- if (*p == ' ') p++;
- cmd = strcpy(cmd_buf, p); /* "TALK[/OLD] bar@spam" */
- p = eos(cmd_buf);
- if (*--p == ']') *p = '\0';
- }
- } else
- txt = strcat(strcpy(txt_buf, "Pardon the interruption: "), buf);
- } else if (is_jnet_send) { /* sscanf(,"(%[^)])%s -%c",,,)==3 */
- jnet_send:
- /*
- \(SPAM\)BAR - arbitrary_message_text (from BAR@SPAM)
- */
- typ = MSG_CALL;
- nam = "Bitnet noise"; /* RSCS/NJE message received via JNET */
- Sprintf(cmd_buf, "XYZZY %s@%s", user, node);
- cmd = cmd_buf;
- /*{ perhaps just vanilla SEND instead of XYZZY? }*/
- Sprintf(txt_buf, "Message from %s@%s:%s", user, node,
- &buf[1+strlen(node)+1+strlen(user)+2-1]); /* "(node)user -" */
- txt = txt_buf;
- /*
- : end of call recognition; anything else is none-of-the-above...
- */
- } else {
- other:
- # endif /* SHELL */
- /* arbitrary broadcast: batch job completed, system shutdown imminent, &c */
- typ = MSG_OTHER;
- nam = (char *) 0; /*"captured broadcast message"*/
- cmd = (char *) 0;
- txt = strcat(strcpy(txt_buf, "Message for you: "), buf);
- # ifdef SHELL
- }
- /* Daemon in newmail() will append period when the text is displayed */
- if ((p = eos(txt)) > txt && *--p == '.') *p = '\0';
- /* newmail() and readmail() assume that nam and cmd are concatenated */
- if (nam) { /* object name to attach to scroll of mail */
- char *join = strcpy(nam_cmd_buf, nam);
- if (cmd) { /* append command to name; readmail() requires it */
- int len = sizeof nam_cmd_buf - sizeof "" - (strlen(join) + 1);
- cmd_buf[len] = '\0'; /* possibly truncate */
- (void) strcat(join, " ");
- cmd = strcpy(eos(join), cmd);
- }
- nam = join;
- }
- # endif /* SHELL */
- /* truncate really long messages to prevent verbalize() from blowing up */
- if (txt && strlen(txt) > BUFSZ - 50) txt[BUFSZ - 50] = '\0';
- msg.message_typ = typ; /* simple index */
- msg.display_txt = txt; /* text for daemon to pline() */
- msg.object_nam = nam; /* 'name' for mail scroll */
- msg.response_cmd = cmd; /* command to spawn when scroll read */
- return &msg;
- }
- /* filter out non-printable characters and redundant noise
- */
- static void
- filter_brdcst(buf) /* called by parse_next_broadcast() */
- register char *buf; /* in: original text; out: filtered text */
- {
- register char c, *p, *buf_p;
- /* filter the text; restrict consecutive spaces or dots to just two */
- for (p = buf_p = buf; *buf_p; buf_p++) {
- c = *buf_p & '\177';
- if (c == ' ' || c == '\t' || c == '\n')
- if (p == buf || /* ignore leading whitespace */
- (p >= buf+2 && *(p-1) == ' ' && *(p-2) == ' ')) continue;
- else c = ' ';
- else if (c == '.' || c < ' ' || c == '\177')
- if (p == buf || /* skip leading beeps & such */
- (p >= buf+2 && *(p-1) == '.' && *(p-2) == '.')) continue;
- else c = '.';
- else if (c == '%' && /* trim %%% OPCOM verbosity %%% */
- p >= buf+2 && *(p-1) == '%' && *(p-2) == '%') continue;
- *p++ = c;
- }
- *p = '\0'; /* terminate, then strip trailing junk */
- while (p > buf && (*--p == ' ' || *p == '.')) *p = '\0';
- return;
- }
- static char empty_string[] = "";
- /* fetch the text of a captured broadcast, then mangle and decipher it
- */
- struct mail_info *
- parse_next_broadcast() /* called by ckmailstatus(mail.c) */
- {
- short length, msg_type;
- $DESCRIPTOR(message, empty_string); /* string descriptor for buf[] */
- struct mail_info *result = 0;
- /* messages could actually be longer; let long ones be truncated */
- char buf[255+1];
- message.dsc$a_pointer = buf, message.dsc$w_length = sizeof buf - 1;
- msg_type = length = 0;
- smg$get_broadcast_message(&pasteboard_id, &message, &length, &msg_type);
- if (msg_type == MSG$_TRMBRDCST) {
- buf[length] = '\0';
- filter_brdcst(buf); /* mask non-printable characters */
- result = parse_brdcst(buf); /* do the real work */
- } else if (msg_type == MSG$_TRMHANGUP) {
- (void) gsignal(SIGHUP);
- }
- return result;
- }
- /* spit out any pending broadcast messages whenever we leave
- */
- static void
- flush_broadcasts() /* called from disable_broadcast_trapping() */
- {
- if (broadcasts > 0) {
- short len, typ;
- $DESCRIPTOR(msg_dsc, empty_string);
- char buf[512+1];
- msg_dsc.dsc$a_pointer = buf, msg_dsc.dsc$w_length = sizeof buf - 1;
- raw_print(""); /* print at least one line for wait_synch() */
- do {
- typ = len = 0;
- smg$get_broadcast_message(&pasteboard_id, &msg_dsc, &len, &typ);
- if (typ == MSG$_TRMBRDCST) buf[len] = '\0', raw_print(buf);
- } while (--broadcasts);
- wait_synch(); /* prompt with "Hit return to continue: " */
- }
- }
- /* AST routine called when the terminal's associated mailbox receives a message
- */
- /*ARGSUSED*/
- static void
- broadcast_ast(dummy) /* called asynchronously by terminal driver */
- int dummy; /* not used */
- {
- broadcasts++;
- }
- /* initialize the broadcast manipulation code; SMG makes this easy
- */
- unsigned long init_broadcast_trapping() /* called by setftty() [once only] */
- {
- unsigned long sts, preserve_screen_flag = 1;
- /* we need a pasteboard to pass to the broadcast setup/teardown routines */
- sts = smg$create_pasteboard(&pasteboard_id, 0, 0, 0, &preserve_screen_flag);
- if (!vms_ok(sts)) {
- errno = EVMSERR, vaxc$errno = sts;
- raw_print("");
- perror("?can't create SMG pasteboard for broadcast trapping");
- wait_synch();
- broadcasts = -1; /* flag that trapping is currently broken */
- }
- return sts;
- }
- /* set up the terminal driver to deliver $brkthru data to a mailbox device
- */
- unsigned long enable_broadcast_trapping() /* called by setftty() */
- {
- unsigned long sts = 1;
- if (broadcasts >= 0) { /* (-1 => no pasteboard, so don't even try) */
- /* register callback routine to be triggered when broadcasts arrive */
- /* Note side effect: also intercepts hangup notification. */
- /* Another note: TMPMBX privilege is required. */
- sts = smg$set_broadcast_trapping(&pasteboard_id, broadcast_ast, 0);
- if (!vms_ok(sts)) {
- errno = EVMSERR, vaxc$errno = sts;
- raw_print("");
- perror("?can't enable broadcast trapping");
- wait_synch();
- }
- }
- return sts;
- }
- /* return to 'normal'; $brkthru data goes straight to the terminal
- */
- unsigned long disable_broadcast_trapping() /* called by settty() */
- {
- unsigned long sts = 1;
- if (broadcasts >= 0) {
- /* disable trapping; releases associated MBX so that SPAWN can work */
- sts = smg$disable_broadcast_trapping(&pasteboard_id);
- if (!vms_ok(sts)) errno = EVMSERR, vaxc$errno = sts;
- flush_broadcasts(); /* don't hold on to any buffered ones */
- }
- return sts;
- }
- #else /* MAIL */
- /* simple stubs for non-mail configuration */
- unsigned long init_broadcast_trapping() { return 1; }
- unsigned long enable_broadcast_trapping() { return 1; }
- unsigned long disable_broadcast_trapping() { return 1; }
- struct mail_info *parse_next_broadcast() { return 0; }
- #endif /* MAIL */
- /*----------------------------------------------------------------------*/
- #ifdef TEST_DRIVER
- /* (Take parse_next_broadcast for a spin. :-) */
- volatile int broadcasts = 0;
- void newmail(foo)
- struct mail_info *foo;
- {
- # define STRING(s) ((s) ? (s) : "<null>")
- printf("\n\
- message type = %d\n\
- display text = \"%s\"\n\
- object name = \"%.*s\"\n\
- response cmd = \"%s\"\n\
- ", foo->message_typ, STRING(foo->display_txt),
- (foo->object_nam && foo->response_cmd) ?
- (foo->response_cmd - foo->object_nam - 1) :
- strlen(STRING(foo->object_nam)),
- STRING(foo->object_nam), STRING(foo->response_cmd));
- # undef STRING
- }
- void ckmailstatus()
- {
- struct mail_info *brdcst, *parse_next_broadcast();
- while (broadcasts > 0) { /* process all trapped broadcasts [until] */
- broadcasts--;
- if ((brdcst = parse_next_broadcast()) != 0) {
- newmail(brdcst);
- break; /* only handle one real message at a time */
- } else
- printf("\n--< non-broadcast encountered >--\n");
- }
- }
- int main()
- {
- char dummy[BUFSIZ];
- init_broadcast_trapping();
- enable_broadcast_trapping();
- for (;;) {
- ckmailstatus();
- printf("> "), fflush(stdout); /* issue a prompt */
- if (!gets(dummy)) break; /* wait for a response */
- }
- disable_broadcast_trapping();
- return 1;
- }
- void panic(s) char *s; { raw_print(s); exit(EXIT_FAILURE); }
- void raw_print(s) char *s; { puts(s); fflush(stdout); }
- void wait_synch() { char dummy[BUFSIZ];
- printf("\nPress <return> to continue: "); fflush(stdout); (void) gets(dummy);
- }
- #endif /* TEST_DRIVER */
- /*vmsmail.c*/