NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / sys / vms / vmsmail.c
bloba64bbc2fed0e0d6f4a59f51e3a1c234af9626d22
1 /* aNetHack 0.0.1 vmsmail.c $ANH-Date: 1449801741 2015/12/11 02:42:21 $ $ANH-Branch: aNetHack-3.6.0 $:$ANH-Revision: 1.10 $ */
2 /* Copyright (c) Robert Patrick Rankin, 1991. */
3 /* aNetHack may be freely redistributed. See license for details. */
5 #include "config.h"
6 #include "mail.h"
8 /* lint supression due to lack of extern.h */
9 unsigned long NDECL(init_broadcast_trapping);
10 unsigned long NDECL(enable_broadcast_trapping);
11 unsigned long NDECL(disable_broadcast_trapping);
12 struct mail_info *NDECL(parse_next_broadcast);
14 #ifdef MAIL
15 #include "wintype.h"
16 #include "winprocs.h"
17 #include <ctype.h>
18 #include <descrip.h>
19 #include <errno.h>
20 #ifndef __GNUC__
21 #include <msgdef.h>
22 #else
23 #define MSG$_TRMHANGUP 6
24 #define MSG$_TRMBRDCST 83
25 #endif /*__GNUC__*/
26 #include <signal.h>
27 /* #include <string.h> */
28 #define vms_ok(sts) ((sts) & 1)
30 static struct mail_info *FDECL(parse_brdcst, (char *));
31 static void FDECL(filter_brdcst, (char *));
32 static void NDECL(flush_broadcasts);
33 static void FDECL(broadcast_ast, (int));
34 extern char *FDECL(eos, (char *));
35 extern char *FDECL(strstri, (const char *, const char *));
36 extern int FDECL(strncmpi, (const char *, const char *, int));
38 extern size_t FDECL(strspn, (const char *, const char *));
39 #ifndef __DECC
40 extern int VDECL(sscanf, (const char *, const char *, ...));
41 #endif
42 extern unsigned long smg$create_pasteboard(), smg$get_broadcast_message(),
43 smg$set_broadcast_trapping(), smg$disable_broadcast_trapping();
45 extern volatile int broadcasts; /* defining declaration in mail.c */
47 static long pasteboard_id = 0; /* SMG's magic cookie */
50 * Mail (et al) overview:
52 * When a broadcast is asynchronously captured, a volatile counter
53 * ('broadcasts') is incremented. Each player turn, ckmailstatus() polls
54 * the counter and calls parse_next_broadcast() if it's positive; this
55 * returns some display text, object name, and response command, which is
56 * passed to newmail(). Routine newmail() generates a mail-daemon monster
57 * who approaches the character, "speaks" the display text, and delivers
58 * a scroll of mail pre-named to the object name; the response command is
59 * also attached to the scroll's oextra->omailcmd field.
60 * Unrecognized broadcasts result in the mail-daemon
61 * arriving and announcing the display text, but no scroll being created.
62 * If SHELL is undefined, then all broadcasts are treated as 'other'; since
63 * no subproceses are allowed, there'd be no way to respond to the scroll.
65 * When a scroll of mail is read by the character, readmail() extracts
66 * the command string and uses it for the default when prompting the
67 * player for a system command to spawn. The player may enter any command
68 * he or she chooses, or just <return> to accept the default or <escape> to
69 * avoid executing any command. If the command is "SPAWN", a regular shell
70 * escape to DCL is performed; otherwise, the indicated single command is
71 * spawned. Either way, aNetHack resumes play when the subprocess terminates
72 * or explicitly reattaches to its parent.
74 * Broadcast parsing:
76 * The following broadcast messages are [attempted to be] recognized:
77 * text fragment name for scroll default command
78 * New mail VMSmail MAIL
79 * New ALL-IN-1 MAIL A1mail A1M
80 * Software Tools mail STmail MSG [+folder]
81 * MM mail MMmail MM
82 * WPmail: New mail WPmail OFFICE/MAIL
83 * **M400 mail M400mail M400
84 * " mail", ^"mail " unknown mail SPAWN
85 * " phoning" Phone call PHONE ANSWER
86 * talk-daemon...by...foo Talk request TALK[/OLD] foo@bar
87 * (node)user - Bitnet noise XYZZY user@node
88 * Anything else results in just the message text being passed along, no
89 * scroll of mail so consequently no command to execute when scroll read.
90 * The user can set up ``$ XYZZY :== SEND'' prior to invoking aNetHack if
91 * vanilla JNET responses to Bitnet messages are prefered.
93 * Static return buffers are used because only one broadcast gets
94 * processed at a time, and the essential information in each one is
95 * either displayed and discarded or copied into a scroll-of-mail object.
97 * The test driver code below can be used to check out potential new
98 * entries without rebuilding aNetHack itself. CC/DEFINE="TEST_DRIVER"
99 * Link it with hacklib.obj or anethack.olb/incl=hacklib (not anethack/lib).
102 static struct mail_info msg; /* parse_*()'s return buffer */
103 static char nam_buf[63], /* maximum onamelth, size of ONAME(object) */
104 cmd_buf[99], /* arbitrary */
105 txt_buf[255 + 1]; /* same size as used for message buf[] */
107 /* try to decipher and categorize broadcast message text
109 static struct mail_info *
110 parse_brdcst(buf) /* called by parse_next_broadcast() */
111 char *buf; /* input: filtered broadcast text */
113 int typ;
114 char *txt;
115 const char *nam, *cmd;
116 #ifdef SHELL /* only parse if spawned commands are enabled */
117 register char *p, *q;
118 boolean is_jnet_send;
119 char user[127 + 1], node[127 + 1], sentinel;
121 /* Check these first; otherwise, their arbitrary text would enable
122 easy spoofing of some other message patterns. Unfortunately,
123 any home-grown broadcast delivery program poses a similar risk. */
124 if (!strncmpi(buf, "reply received", 14))
125 goto other;
126 is_jnet_send = (sscanf(buf, "(%[^)])%s -%c", node, user, &sentinel) == 3);
127 if (is_jnet_send)
128 goto jnet_send;
130 /* scan the text more or less by brute force */
131 if ((q = strstri(buf, " mail")) != 0 || /* all known mail broadcasts */
132 !strncmpi(q = buf, "mail ", 5)) { /* unexpected alternative */
133 typ = MSG_MAIL;
134 p = strstri(q, " from");
135 txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;
137 if (!strncmpi(buf, "new mail", 8)) {
139 * New mail [on node FOO] from [SPAM::]BAR
140 * [\"personal_name\"] [\(HH:MM:SS\)]
142 nam = "VMSmail"; /* assume VMSmail */
143 cmd = "MAIL";
144 if (txt && (p = strrchr(txt, '(')) > txt && /* discard time */
145 (--p, strspn(p, "0123456789( :.)") == strlen(p)))
146 *p = '\0';
147 } else if (!strncmpi(buf, "new all-in-1", 12)) {
148 int i;
150 * New ALL-IN-1 MAIL message [on node FOO] from Personal Name
151 * \(BAR@SPAM\) [\(DD-MMM-YYYY HH:MM:SS\)]
153 nam = "A1mail";
154 cmd = "A1M";
155 if (txt && (p = strrchr(txt, '(')) > txt
156 && /* discard date+time */
157 sscanf(p - 1, " (%*d-%*[^-]-%*d %*d:%*d:%d) %c", &i,
158 &sentinel) == 1)
159 *--p = '\0';
160 } else if (!strncmpi(buf, "software tools", 14)) {
162 * Software Tools mail has arrived on FOO from \'BAR\' [in SPAM]
164 nam = "STmail";
165 cmd = "MSG";
166 if (txt && (p = strstri(p, " in ")) != 0) /* specific folder */
167 cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);
168 } else if (q - 2 >= buf && !strncmpi(q - 2, "mm", 2)) {
170 * {MultiNet\ |PMDF\/}MM mail has arrived on FOO from BAR\n
171 * [Subject: subject_text] (PMDF only)
173 nam = "MMmail"; /* MultiNet's version of MM */
174 cmd = "MM"; /*{ perhaps "MM READ"? }*/
175 } else if (!strncmpi(buf, "wpmail:", 7)) {
177 * WPmail: New mail from BAR. subject_text
179 nam = "WPmail"; /* WordPerfect [sic] Office */
180 cmd = "OFFICE/MAIL";
181 } else if (!strncmpi(buf, "**m400 mail", 7)) {
183 * **M400 mail waiting**
185 nam = "M400mail"; /* Messenger 400 [not seen] */
186 cmd = "M400";
187 } else {
188 /* not recognized, but presumed to be mail */
189 nam = "unknown mail";
190 cmd = "SPAWN"; /* generic escape back to DCL */
191 txt = (char *) 0; /* don't rely on "from" info here */
194 if (!txt)
195 txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);
198 * end of mail recognition; now check for call-type interruptions...
200 } else if ((q = strstri(buf, " phoning")) != 0) {
202 * BAR is phoning you [on FOO] \(HH:MM:SS\)
204 typ = MSG_CALL;
205 nam = "Phone call";
206 cmd = "PHONE ANSWER";
207 if (!strncmpi(q + 8, " you", 4))
208 q += (8 + 4), *q = '\0';
209 txt = strcat(strcpy(txt_buf, "Do you hear ringing? "), buf);
210 } else if ((q = strstri(buf, " talk-daemon")) != 0
211 || (q = strstri(buf, " talk_daemon")) != 0) {
213 * Message from TALK-DAEMON@FOO at HH:MM:SS\n
214 * Connection request by BAR@SPAM\n
215 * \[Respond with: TALK[/OLD] BAR@SPAM\]
217 typ = MSG_CALL;
218 nam = "Talk request"; /* MultiNet's TALK and/or TALK/OLD */
219 cmd = "TALK";
220 if ((p = strstri(q, " by ")) != 0) {
221 txt = strcat(strcpy(txt_buf, "Talk request from"), p + 3);
222 if ((p = strstri(p, "respond with")) != 0) {
223 if (*(p - 1) == '[')
224 *(p - 1) = '\0';
225 else
226 *p = '\0'; /* terminate */
227 p += (sizeof "respond with" - sizeof "");
228 if (*p == ':')
229 p++;
230 if (*p == ' ')
231 p++;
232 cmd = strcpy(cmd_buf, p); /* "TALK[/OLD] bar@spam" */
233 p = eos(cmd_buf);
234 if (*--p == ']')
235 *p = '\0';
237 } else
238 txt = strcat(strcpy(txt_buf, "Pardon the interruption: "), buf);
239 } else if (is_jnet_send) { /* sscanf(,"(%[^)])%s -%c",,,)==3 */
240 jnet_send:
242 * \(SPAM\)BAR - arbitrary_message_text (from BAR@SPAM)
244 typ = MSG_CALL;
245 nam = "Bitnet noise"; /* RSCS/NJE message received via JNET */
246 Sprintf(cmd_buf, "XYZZY %s@%s", user, node);
247 cmd = cmd_buf;
248 /*{ perhaps just vanilla SEND instead of XYZZY? }*/
249 Sprintf(txt_buf, "Message from %s@%s:%s", user, node,
250 /* "(node)user -" */
251 &buf[1 + strlen(node) + 1 + strlen(user) + 2 - 1]);
252 txt = txt_buf;
255 * end of call recognition; anything else is none-of-the-above...
257 } else {
258 other:
259 #endif /* SHELL */
260 /* arbitrary broadcast: batch job completed, system shutdown
261 * imminent, &c
263 typ = MSG_OTHER;
264 nam = (char *) 0; /*"captured broadcast message"*/
265 cmd = (char *) 0;
266 txt = strcat(strcpy(txt_buf, "Message for you: "), buf);
267 #ifdef SHELL
269 /* Daemon in newmail() will append period when the text is displayed */
270 if ((p = eos(txt)) > txt && *--p == '.')
271 *p = '\0';
273 /* newmail() and readmail() used to assume that nam and cmd are
274 concatenated but that is no longer the case */
275 if (nam && nam != nam_buf) {
276 (void) strncpy(nam_buf, nam, sizeof nam_buf - 1);
277 nam_buf[sizeof nam_buf - 1] = '\0';
279 if (cmd && cmd != cmd_buf) {
280 (void) strncpy(cmd_buf, cmd, sizeof cmd_buf - 1);
281 cmd_buf[sizeof cmd_buf - 1] = '\0';
283 #endif /* SHELL */
284 /* truncate really long messages to prevent verbalize() from blowing up */
285 if (txt && strlen(txt) > BUFSZ - 50)
286 txt[BUFSZ - 50] = '\0';
288 msg.message_typ = typ; /* simple index */
289 msg.display_txt = txt; /* text for daemon to pline() */
290 msg.object_nam = nam; /* 'name' for mail scroll */
291 msg.response_cmd = cmd; /* command to spawn when scroll read */
292 return &msg;
295 /* filter out non-printable characters and redundant noise
297 static void filter_brdcst(buf) /* called by parse_next_broadcast() */
298 register char *buf; /* in: original text; out: filtered text */
300 register char c, *p, *buf_p;
302 /* filter the text; restrict consecutive spaces or dots to just two */
303 for (p = buf_p = buf; *buf_p; buf_p++) {
304 c = *buf_p & '\177';
305 if (c == ' ' || c == '\t' || c == '\n') {
306 if (p == buf || /* ignore leading whitespace */
307 (p >= buf + 2 && *(p - 1) == ' ' && *(p - 2) == ' '))
308 continue;
309 else
310 c = ' ';
311 } else if (c == '.' || c < ' ' || c == '\177') {
312 if (p == buf || /* skip leading beeps & such */
313 (p >= buf + 2 && *(p - 1) == '.' && *(p - 2) == '.'))
314 continue;
315 else
316 c = '.';
317 } else if (c == '%' && /* trim %%% OPCOM verbosity %%% */
318 p >= buf + 2 && *(p - 1) == '%' && *(p - 2) == '%') {
319 continue;
321 *p++ = c;
323 *p = '\0'; /* terminate, then strip trailing junk */
324 while (p > buf && (*--p == ' ' || *p == '.'))
325 *p = '\0';
326 return;
329 static char empty_string[] = "";
331 /* fetch the text of a captured broadcast, then mangle and decipher it
333 struct mail_info *parse_next_broadcast() /* called by ckmailstatus(mail.c) */
335 short length, msg_type;
336 $DESCRIPTOR(message, empty_string); /* string descriptor for buf[] */
337 struct mail_info *result = 0;
338 /* messages could actually be longer; let long ones be truncated */
339 char buf[255 + 1];
341 message.dsc$a_pointer = buf, message.dsc$w_length = sizeof buf - 1;
342 msg_type = length = 0;
343 smg$get_broadcast_message(&pasteboard_id, &message, &length, &msg_type);
344 if (msg_type == MSG$_TRMBRDCST) {
345 buf[length] = '\0';
346 filter_brdcst(buf); /* mask non-printable characters */
347 result = parse_brdcst(buf); /* do the real work */
348 } else if (msg_type == MSG$_TRMHANGUP) {
349 (void) gsignal(SIGHUP);
351 return result;
354 /* spit out any pending broadcast messages whenever we leave
356 static void flush_broadcasts() /* called from disable_broadcast_trapping() */
358 if (broadcasts > 0) {
359 short len, typ;
360 $DESCRIPTOR(msg_dsc, empty_string);
361 char buf[512 + 1];
363 msg_dsc.dsc$a_pointer = buf, msg_dsc.dsc$w_length = sizeof buf - 1;
364 raw_print(""); /* print at least one line for wait_synch() */
365 do {
366 typ = len = 0;
367 smg$get_broadcast_message(&pasteboard_id, &msg_dsc, &len, &typ);
368 if (typ == MSG$_TRMBRDCST)
369 buf[len] = '\0', raw_print(buf);
370 } while (--broadcasts);
371 wait_synch(); /* prompt with "Hit return to continue: " */
375 /* AST routine called when terminal's associated mailbox receives a message
377 /*ARGSUSED*/
378 static void
379 broadcast_ast(dummy) /* called asynchronously by terminal driver */
380 int dummy UNUSED;
382 broadcasts++;
385 /* initialize the broadcast manipulation code; SMG makes this easy
387 unsigned long
388 init_broadcast_trapping() /* called by setftty() [once only] */
390 unsigned long sts, preserve_screen_flag = 1;
392 /* we need a pasteboard to pass to the broadcast setup/teardown routines */
393 sts = smg$create_pasteboard(&pasteboard_id, 0, 0, 0,
394 &preserve_screen_flag);
395 if (!vms_ok(sts)) {
396 errno = EVMSERR, vaxc$errno = sts;
397 raw_print("");
398 perror("?can't create SMG pasteboard for broadcast trapping");
399 wait_synch();
400 broadcasts = -1; /* flag that trapping is currently broken */
402 return sts;
405 /* set up the terminal driver to deliver $brkthru data to a mailbox device
407 unsigned long
408 enable_broadcast_trapping() /* called by setftty() */
410 unsigned long sts = 1;
412 if (broadcasts >= 0) { /* (-1 => no pasteboard, so don't even try) */
413 /* register callback routine to be triggered when broadcasts arrive */
414 /* Note side effect: also intercepts hangup notification. */
415 /* Another note: TMPMBX privilege is required. */
416 sts = smg$set_broadcast_trapping(&pasteboard_id, broadcast_ast, 0);
417 if (!vms_ok(sts)) {
418 errno = EVMSERR, vaxc$errno = sts;
419 raw_print("");
420 perror("?can't enable broadcast trapping");
421 wait_synch();
424 return sts;
427 /* return to 'normal'; $brkthru data goes straight to the terminal
429 unsigned long
430 disable_broadcast_trapping() /* called by settty() */
432 unsigned long sts = 1;
434 if (broadcasts >= 0) {
435 /* disable trapping; releases associated MBX so that SPAWN can work */
436 sts = smg$disable_broadcast_trapping(&pasteboard_id);
437 if (!vms_ok(sts))
438 errno = EVMSERR, vaxc$errno = sts;
439 flush_broadcasts(); /* don't hold on to any buffered ones */
441 return sts;
444 #else /* MAIL */
446 /* simple stubs for non-mail configuration */
447 unsigned long
448 init_broadcast_trapping()
450 return 1;
452 unsigned long
453 enable_broadcast_trapping()
455 return 1;
457 unsigned long
458 disable_broadcast_trapping()
460 return 1;
462 struct mail_info *
463 parse_next_broadcast()
465 return 0;
468 #endif /* MAIL */
470 /*----------------------------------------------------------------------*/
472 #ifdef TEST_DRIVER
473 /* (Take parse_next_broadcast for a spin. :-) */
475 volatile int broadcasts = 0;
477 void
478 newmail(foo)
479 struct mail_info *foo;
481 #define STRING(s) ((s) ? (s) : "<null>")
482 printf("\n\
483 message type = %d\n\
484 display text = \"%s\"\n\
485 object name = \"%.*s\"\n\
486 response cmd = \"%s\"\n\
488 foo->message_typ, STRING(foo->display_txt),
489 (foo->object_nam && foo->response_cmd)
490 ? (foo->response_cmd - foo->object_nam - 1)
491 : strlen(STRING(foo->object_nam)),
492 STRING(foo->object_nam), STRING(foo->response_cmd));
493 #undef STRING
496 void
497 ckmailstatus()
499 struct mail_info *brdcst, *parse_next_broadcast();
501 while (broadcasts > 0) { /* process all trapped broadcasts [until] */
502 broadcasts--;
503 if ((brdcst = parse_next_broadcast()) != 0) {
504 newmail(brdcst);
505 break; /* only handle one real message at a time */
506 } else
507 printf("\n--< non-broadcast encountered >--\n");
512 main()
514 char dummy[BUFSIZ];
516 init_broadcast_trapping();
517 enable_broadcast_trapping();
518 for (;;) {
519 ckmailstatus();
520 printf("> "), fflush(stdout); /* issue a prompt */
521 if (!gets(dummy))
522 break; /* wait for a response */
524 disable_broadcast_trapping();
525 return 1;
528 void
529 panic(s)
530 char *s;
532 raw_print(s);
533 exit(EXIT_FAILURE);
536 void
537 raw_print(s)
538 char *s;
540 puts(s);
541 fflush(stdout);
544 void
545 wait_synch()
547 char dummy[BUFSIZ];
549 printf("\nPress <return> to continue: ");
550 fflush(stdout);
551 (void) gets(dummy);
553 #endif /* TEST_DRIVER */
555 /*vmsmail.c*/